/* (c) 2014 - 2015 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.catalog; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.measure.unit.Unit; import javax.media.jai.PlanarImage; import org.geoserver.catalog.impl.FeatureTypeInfoImpl; import org.geoserver.catalog.impl.ModificationProxy; import org.geoserver.catalog.impl.ResourceInfoImpl; import org.geoserver.catalog.impl.StoreInfoImpl; import org.geoserver.catalog.impl.StyleInfoImpl; import org.geoserver.catalog.impl.WMSStoreInfoImpl; import org.geoserver.data.util.CoverageStoreUtils; import org.geoserver.data.util.CoverageUtils; import org.geoserver.ows.util.OwsUtils; import org.geotools.coverage.Category; import org.geotools.coverage.GridSampleDimension; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.coverage.grid.GridEnvelope2D; import org.geotools.coverage.grid.GridGeometry2D; import org.geotools.coverage.grid.io.AbstractGridFormat; import org.geotools.coverage.grid.io.GridCoverage2DReader; import org.geotools.data.FeatureSource; import org.geotools.data.ows.CRSEnvelope; import org.geotools.data.ows.Layer; import org.geotools.factory.GeoTools; import org.geotools.feature.FeatureTypes; import org.geotools.gce.imagemosaic.ImageMosaicFormat; import org.geotools.geometry.GeneralEnvelope; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.referencing.CRS; import org.geotools.referencing.CRS.AxisOrder; import org.geotools.referencing.crs.DefaultGeographicCRS; import org.geotools.resources.image.ImageUtilities; import org.geotools.util.NumberRange; import org.geotools.util.Version; import org.geotools.util.logging.Logging; import org.opengis.coverage.grid.Format; import org.opengis.coverage.grid.GridEnvelope; import org.opengis.feature.type.FeatureType; import org.opengis.feature.type.GeometryDescriptor; import org.opengis.feature.type.Name; import org.opengis.feature.type.PropertyDescriptor; import org.opengis.geometry.Envelope; import org.opengis.metadata.Identifier; import org.opengis.parameter.ParameterValueGroup; import org.opengis.referencing.FactoryException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.crs.GeographicCRS; import org.opengis.referencing.datum.PixelInCell; import org.opengis.referencing.operation.MathTransform; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.geom.MultiLineString; import com.vividsolutions.jts.geom.MultiPoint; import com.vividsolutions.jts.geom.MultiPolygon; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.Polygon; /** * Builder class which provides convenience methods for interacting with the catalog. * <p> * Warning: this class is stateful, and is not meant to be accessed by multiple threads and should * not be an member variable of another class. * </p> * * @author Justin Deoliveira, OpenGEO * */ public class CatalogBuilder { static final Logger LOGGER = Logging.getLogger(CatalogBuilder.class); /** Default SRS; will be set on the provided feature type by lookupSRS methods if none was found */ public static final String DEFAULT_SRS = "EPSG:404000"; /** * the catalog */ Catalog catalog; /** * the current workspace */ WorkspaceInfo workspace; /** * the current store */ StoreInfo store; public CatalogBuilder(Catalog catalog) { this.catalog = catalog; } /** * Sets the workspace to be used when creating store objects. */ public void setWorkspace(WorkspaceInfo workspace) { this.workspace = workspace; } /** * Sets the store to be used when creating resource objects. */ public void setStore(StoreInfo store) { this.store = store; } /** * Updates a workspace with the properties of another. * * @param original * The workspace being updated. * @param update * The workspace containing the new values. */ public void updateWorkspace(WorkspaceInfo original, WorkspaceInfo update) { update(original, update, WorkspaceInfo.class); } /** * Updates a namespace with the properties of another. * * @param original * The namespace being updated. * @param update * The namespace containing the new values. */ public void updateNamespace(NamespaceInfo original, NamespaceInfo update) { update(original, update, NamespaceInfo.class); } /** * Updates a datastore with the properties of another. * * @param original * The datastore being updated. * @param update * The datastore containing the new values. */ public void updateDataStore(DataStoreInfo original, DataStoreInfo update) { update(original, update, DataStoreInfo.class); } /** * Updates a wms store with the properties of another. * * @param original * The wms store being updated. * @param update * The wms store containing the new values. */ public void updateWMSStore(WMSStoreInfo original, WMSStoreInfo update) { update(original, update, WMSStoreInfo.class); } /** * Updates a coveragestore with the properties of another. * * @param original * The coveragestore being updated. * @param update * The coveragestore containing the new values. */ public void updateCoverageStore(CoverageStoreInfo original, CoverageStoreInfo update) { update(original, update, CoverageStoreInfo.class); } /** * Updates a feature type with the properties of another. * * @param original * The feature type being updated. * @param update * The feature type containing the new values. */ public void updateFeatureType(FeatureTypeInfo original, FeatureTypeInfo update) { update(original, update, FeatureTypeInfo.class); } /** * Updates a coverage with the properties of another. * * @param original * The coverage being updated. * @param update * The coverage containing the new values. */ public void updateCoverage(CoverageInfo original, CoverageInfo update) { update(original, update, CoverageInfo.class); } /** * Updates a WMS layer with the properties of another. * * @param original * The wms layer being updated. * @param update * The wms layer containing the new values. */ public void updateWMSLayer(WMSLayerInfo original, WMSLayerInfo update) { update(original, update, WMSLayerInfo.class); } /** * Updates a layer with the properties of another. * * @param original * The layer being updated. * @param update * The layer containing the new values. */ public void updateLayer(LayerInfo original, LayerInfo update) { update(original, update, LayerInfo.class); } /** * Updates a layer group with the properties of another. * * @param original * The layer group being updated. * @param update * The layer group containing the new values. */ public void updateLayerGroup(LayerGroupInfo original, LayerGroupInfo update) { update(original, update, LayerGroupInfo.class); } /** * Updates a style with the properties of another. * * @param original * The style being updated. * @param update * The style containing the new values. */ public void updateStyle(StyleInfo original, StyleInfo update) { update(original, update, StyleInfo.class); } /** * Update method which uses reflection to grab property values from one object and set them on * another. * <p> * Null values from the <tt>update</tt> object are ignored. * </p> */ <T> void update(T original, T update, Class<T> clazz) { OwsUtils.copy(update, original, clazz); } /** * Builds a new data store. */ public DataStoreInfo buildDataStore(String name) { DataStoreInfo info = catalog.getFactory().createDataStore(); buildStore(info, name); return info; } /** * Builds a new coverage store. */ public CoverageStoreInfo buildCoverageStore(String name) { CoverageStoreInfo info = catalog.getFactory().createCoverageStore(); buildStore(info, name); return info; } /** * Builds a new WMS store */ public WMSStoreInfo buildWMSStore(String name) throws IOException { WMSStoreInfo info = catalog.getFactory().createWebMapServer(); buildStore(info, name); info.setType("WMS"); info.setMaxConnections(WMSStoreInfoImpl.DEFAULT_MAX_CONNECTIONS); info.setConnectTimeout(WMSStoreInfoImpl.DEFAULT_CONNECT_TIMEOUT); info.setReadTimeout(WMSStoreInfoImpl.DEFAULT_READ_TIMEOUT); return info; } /** * Builds a store. * <p> * The workspace of the resulting store is {@link #workspace} if set, else the default workspace * from the catalog. * </p> */ void buildStore(StoreInfo info, String name) { info.setName(name); info.setEnabled(true); // set workspace, falling back on default if none specified if (workspace != null) { info.setWorkspace(workspace); } else { info.setWorkspace(catalog.getDefaultWorkspace()); } } /** * Builds a {@link FeatureTypeInfo} from the current datastore and the specified type name * <p> * The resulting object is not added to the catalog, it must be done by the calling code after * the fact. * </p> */ public FeatureTypeInfo buildFeatureType(Name typeName) throws Exception { if (store == null || !(store instanceof DataStoreInfo)) { throw new IllegalStateException("Data store not set."); } DataStoreInfo dstore = (DataStoreInfo) store; return buildFeatureType(dstore.getDataStore(null).getFeatureSource(typeName)); } /** * Builds a feature type from a geotools feature source. The resulting {@link FeatureTypeInfo} * will still miss the bounds and might miss the SRS. Use {@link #lookupSRS(FeatureTypeInfo, * true)} and {@link #setupBounds(FeatureTypeInfo)} if you want to force them in (and spend time * accordingly) * <p> * The resulting object is not added to the catalog, it must be done by the calling code after * the fact. * </p> */ public FeatureTypeInfo buildFeatureType(FeatureSource featureSource) { if (store == null || !(store instanceof DataStoreInfo)) { throw new IllegalStateException("Data store not set."); } FeatureType featureType = featureSource.getSchema(); FeatureTypeInfo ftinfo = catalog.getFactory().createFeatureType(); ftinfo.setStore(store); ftinfo.setEnabled(true); // naming Name name = featureSource.getName(); if (name == null) { name = featureType.getName(); } ftinfo.setNativeName(name.getLocalPart()); ftinfo.setName(name.getLocalPart()); WorkspaceInfo workspace = store.getWorkspace(); NamespaceInfo namespace = catalog.getNamespaceByPrefix(workspace.getName()); if (namespace == null) { namespace = catalog.getDefaultNamespace(); } ftinfo.setNamespace(namespace); CoordinateReferenceSystem crs = featureType.getCoordinateReferenceSystem(); if (crs == null && featureType.getGeometryDescriptor() != null) { crs = featureType.getGeometryDescriptor().getCoordinateReferenceSystem(); } ftinfo.setNativeCRS(crs); // srs look and set (by default we just use fast lookup) try { lookupSRS(ftinfo, false); } catch (Exception e) { LOGGER.log(Level.WARNING, "SRS lookup failed", e); } setupProjectionPolicy(ftinfo); // fill in metadata, first check if the datastore itself can provide some metadata for us try { setupMetadata(ftinfo, featureSource); } catch (IOException e) { LOGGER.log(Level.WARNING, "Metadata lookup failed", e); } return ftinfo; } /** * Sets the projection policy for a resource based on the following rules: * <ul> * <li>If getSRS() returns a non null value it is set to {@Link * ProjectionPolicy#FORCE_DECLARED} * <li>If getSRS() returns a null value it is set to {@link ProjectionPolicy#NONE} * </ul> * * TODO: make this method smarter, and compare the native crs to figure out if prejection * actually needs to be done, and sync it up with setting proj policy on coverage layers. */ public void setupProjectionPolicy(ResourceInfo rinfo) { if (rinfo.getSRS() != null) { rinfo.setProjectionPolicy(ProjectionPolicy.FORCE_DECLARED); } else { rinfo.setProjectionPolicy(ProjectionPolicy.NONE); } } /** * Computes the native bounds for a {@link FeatureTypeInfo} explicitly providing the feature * source. * <p> * This method calls through to {@link #doSetupBounds(ResourceInfo, Object)}. * </p> */ public void setupBounds(FeatureTypeInfo ftinfo, FeatureSource featureSource) throws IOException { doSetupBounds(ftinfo, featureSource); } /** * Computes the native bounds for a {@link CoverageInfo} explicitly providing the coverage * reader. * <p> * This method calls through to {@link #doSetupBounds(ResourceInfo, Object)}. * </p> */ public void setupBounds(CoverageInfo cinfo, GridCoverage2DReader coverageReader) throws IOException { doSetupBounds(cinfo, coverageReader); } /** * Given a {@link ResourceInfo} this method: * <ul> * <li>computes, if missing, the native bounds (warning, this might be very expensive, cases in * which this case take minutes are not uncommon if the data set is made of million of features) * </li> * <li>updates, if possible, the geographic bounds accordingly by re-projecting the native * bounds into WGS84</li> * * @param ftinfo * @throws IOException * if computing the native bounds fails or if a transformation error occurs during * the geographic bounds computation */ public void setupBounds(ResourceInfo rinfo) throws IOException { doSetupBounds(rinfo, null); } /* * Helper method for setupBounds() methods which can optionally take a "data" object rather * than access it through the catalog. This allows for this method to be called for info objects * that might not be part of the catalog. */ void doSetupBounds(ResourceInfo rinfo, Object data) throws IOException { // setup the native bbox if needed if (rinfo.getNativeBoundingBox() == null) { ReferencedEnvelope bounds = getNativeBounds(rinfo, data); rinfo.setNativeBoundingBox(bounds); } // setup the geographic bbox if missing and we have enough info rinfo.setLatLonBoundingBox(getLatLonBounds(rinfo.getNativeBoundingBox(), rinfo.getCRS())); } /** * Fills in metadata on the {@link FeatureTypeInfo} from an underlying feature source. */ public void setupMetadata(FeatureTypeInfo ftinfo, FeatureSource featureSource) throws IOException { org.geotools.data.ResourceInfo rinfo = null; try { rinfo = featureSource.getInfo(); } catch(Exception ignore) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Unable to get resource info from feature source", ignore); } } if (ftinfo.getTitle() == null) { ftinfo.setTitle(rinfo != null ? rinfo.getTitle() : ftinfo.getName()); } if (rinfo != null && ftinfo.getDescription() == null) { ftinfo.setDescription(rinfo.getDescription()); } if (rinfo != null && (ftinfo.getKeywords() == null || ftinfo.getKeywords().isEmpty())) { if (rinfo.getKeywords() != null) { if (ftinfo.getKeywords() == null) { ((FeatureTypeInfoImpl)ftinfo).setKeywords(new ArrayList()); } for (String kw : rinfo.getKeywords()) { if (kw == null || "".equals(kw.trim())) { LOGGER.fine("Empty keyword ignored"); continue; } ftinfo.getKeywords().add(new Keyword(kw)); } } } } /** * Computes the geographic bounds of a {@link ResourceInfo} by reprojecting the available native * bounds * * @param rinfo * @return the geographic bounds, or null if the native bounds are not available * @throws IOException */ public ReferencedEnvelope getLatLonBounds(ReferencedEnvelope nativeBounds, CoordinateReferenceSystem declaredCRS) throws IOException { if (nativeBounds != null && declaredCRS != null) { // make sure we use the declared CRS, not the native one, the may differ if (!CRS.equalsIgnoreMetadata(DefaultGeographicCRS.WGS84, declaredCRS)) { // transform try { ReferencedEnvelope bounds = new ReferencedEnvelope(nativeBounds, CRS.getHorizontalCRS(declaredCRS)); return bounds.transform(DefaultGeographicCRS.WGS84, true); } catch (Exception e) { throw (IOException) new IOException("transform error").initCause(e); } } else { return new ReferencedEnvelope(nativeBounds, DefaultGeographicCRS.WGS84); } } return null; } /** * Computes the native bounds of a {@link ResourceInfo} taking into account the nature of the * data and the reprojection policy in act * * @param rinfo * @return the native bounds, or null if the could not be computed * @throws IOException */ public ReferencedEnvelope getNativeBounds(ResourceInfo rinfo) throws IOException { return getNativeBounds(rinfo, null); } /* * Helper method for getNativeBounds() methods which can optionally take a "data" object rather * than access it through the catalog. This allows for this method to be called for info objects * that might not be part of the catalog. */ ReferencedEnvelope getNativeBounds(ResourceInfo rinfo, Object data) throws IOException { ReferencedEnvelope bounds = null; if (rinfo instanceof FeatureTypeInfo) { FeatureTypeInfo ftinfo = (FeatureTypeInfo) rinfo; // bounds if (data instanceof FeatureSource) { bounds = ((FeatureSource)data).getBounds(); } else { bounds = ftinfo.getFeatureSource(null, null).getBounds(); } // fix the native bounds if necessary, some datastores do // not build a proper referenced envelope CoordinateReferenceSystem crs = ftinfo.getNativeCRS(); if (bounds != null && bounds.getCoordinateReferenceSystem() == null && crs != null) { bounds = new ReferencedEnvelope(bounds, crs); } if (bounds != null) { // expansion factor if the bounds are empty or one dimensional double expandBy = 1; // 1 meter if (bounds.getCoordinateReferenceSystem() instanceof GeographicCRS) { expandBy = 0.0001; } if (bounds.getWidth() == 0 || bounds.getHeight() == 0) { bounds.expandBy(expandBy); } } } else if (rinfo instanceof CoverageInfo) { // the coverage bounds computation path is a bit more linear, the // readers always return the bounds and in the proper CRS (afaik) CoverageInfo cinfo = (CoverageInfo) rinfo; GridCoverage2DReader reader = null; if (data instanceof GridCoverage2DReader) { reader = (GridCoverage2DReader) data; } else { reader = (GridCoverage2DReader) cinfo.getGridCoverageReader(null, GeoTools.getDefaultHints()); } // get bounds bounds = new ReferencedEnvelope(reader.getOriginalEnvelope()); } else if(rinfo instanceof WMSLayerInfo) { // the logic to compute the native bounds is pretty convoluted, // let's rebuild the layer info WMSLayerInfo rebuilt = buildWMSLayer(rinfo.getStore(), rinfo.getNativeName()); bounds = rebuilt.getNativeBoundingBox(); } // apply the bounds, taking into account the reprojection policy if need be if (rinfo.getProjectionPolicy() == ProjectionPolicy.REPROJECT_TO_DECLARED && bounds != null) { try { bounds = bounds.transform(rinfo.getCRS(), true); } catch (Exception e) { throw (IOException) new IOException("transform error").initCause(e); } } return bounds; } /** * Looks up and sets the SRS based on the feature type info native * {@link CoordinateReferenceSystem} * * @param ftinfo * @param extensive * if true an extenstive lookup will be performed (more accurate, but might take * various seconds) * @throws IOException */ public void lookupSRS(FeatureTypeInfo ftinfo, boolean extensive) throws IOException { lookupSRS(ftinfo, null, extensive); } /** * Looks up and sets the SRS based on the feature type info native * {@link CoordinateReferenceSystem}, obtained from an optional feature source. * * @param ftinfo * @param data A feature source (possibily null) * @param extensive * if true an extenstive lookup will be performed (more accurate, but might take * various seconds) * @throws IOException */ public void lookupSRS(FeatureTypeInfo ftinfo, FeatureSource data, boolean extensive) throws IOException { CoordinateReferenceSystem crs = ftinfo.getNativeCRS(); if (crs == null) { if (data != null) { crs = data.getSchema().getCoordinateReferenceSystem(); } else { crs = ftinfo.getFeatureType().getCoordinateReferenceSystem(); } } if (crs != null) { try { Integer code = CRS.lookupEpsgCode(crs, extensive); if (code != null) ftinfo.setSRS("EPSG:" + code); } catch (FactoryException e) { throw (IOException) new IOException().initCause(e); } } else { ftinfo.setSRS(DEFAULT_SRS); } } /** * Initializes basic resource info. */ private void initResourceInfo(ResourceInfo resInfo) throws Exception { // set the name if (resInfo.getNativeName() == null && resInfo.getName() != null) { resInfo.setNativeName(resInfo.getName()); } if (resInfo.getNativeName() != null && resInfo.getName() == null) { resInfo.setName(resInfo.getNativeName()); } } /** * Initializes a feature type object setting any info that has not been set. */ public void initFeatureType(FeatureTypeInfo featureType) throws Exception { if (featureType.getCatalog() == null) { featureType.setCatalog(catalog); } initResourceInfo(featureType); // setup the srs if missing if (featureType.getSRS() == null) { lookupSRS(featureType, true); } if (featureType.getProjectionPolicy() == null) { setupProjectionPolicy(featureType); } // deal with bounding boxes as possible CoordinateReferenceSystem crs = featureType.getCRS(); if (featureType.getLatLonBoundingBox() == null && featureType.getNativeBoundingBox() == null) { // both missing, we compute them setupBounds(featureType); } else if (featureType.getLatLonBoundingBox() == null) { // native available but geographic to be computed setupBounds(featureType); } else if (featureType.getNativeBoundingBox() == null && crs != null) { // we know the geographic and we can reproject back to native ReferencedEnvelope boundsLatLon = featureType.getLatLonBoundingBox(); featureType.setNativeBoundingBox(boundsLatLon.transform(crs, true)); } } /** * Initializes a wms layer object setting any info that has not been set. */ public void initWMSLayer(WMSLayerInfo wmsLayer) throws Exception { wmsLayer.setCatalog(catalog); initResourceInfo(wmsLayer); OwsUtils.resolveCollections(wmsLayer); // get a fully initialized version we can copy from WMSLayerInfo full = buildWMSLayer(store, wmsLayer.getNativeName()); // setup the srs if missing if (wmsLayer.getSRS() == null) { wmsLayer.setSRS(full.getSRS()); } if (wmsLayer.getNativeCRS() == null) { wmsLayer.setNativeCRS(full.getNativeCRS()); } if (wmsLayer.getProjectionPolicy() == null) { wmsLayer.setProjectionPolicy(full.getProjectionPolicy()); } // deal with bounding boxes as possible if (wmsLayer.getLatLonBoundingBox() == null && wmsLayer.getNativeBoundingBox() == null) { // both missing, we copy them wmsLayer.setLatLonBoundingBox(full.getLatLonBoundingBox()); wmsLayer.setNativeBoundingBox(full.getNativeBoundingBox()); } else if (wmsLayer.getLatLonBoundingBox() == null) { // native available but geographic to be computed setupBounds(wmsLayer); } else if (wmsLayer.getNativeBoundingBox() == null && wmsLayer.getNativeCRS() != null) { // we know the geographic and we can reproject back to native ReferencedEnvelope boundsLatLon = wmsLayer.getLatLonBoundingBox(); wmsLayer.setNativeBoundingBox(boundsLatLon.transform(wmsLayer.getNativeCRS(), true)); } //fill in missing metadata if (wmsLayer.getTitle() == null) { wmsLayer.setTitle(full.getTitle()); } if (wmsLayer.getDescription() == null) { wmsLayer.setDescription(full.getDescription()); } if (wmsLayer.getAbstract() == null) { wmsLayer.setAbstract(full.getAbstract()); } if (wmsLayer.getKeywords().isEmpty()) { wmsLayer.getKeywords().addAll(full.getKeywords()); } } /** * Initialize a coverage object and set any unset info. */ public void initCoverage(CoverageInfo cinfo) throws Exception { initCoverage(cinfo, null); } /** * Initialize a coverage object and set any unset info. */ public void initCoverage(CoverageInfo cinfo, final String coverageName) throws Exception { CoverageStoreInfo csinfo = (CoverageStoreInfo) store; GridCoverage2DReader reader = (GridCoverage2DReader) catalog .getResourcePool().getGridCoverageReader(cinfo, GeoTools.getDefaultHints()); if(coverageName != null) { reader = SingleGridCoverage2DReader.wrap(reader, coverageName); } initResourceInfo(cinfo); if (reader == null) throw new Exception("Unable to acquire a reader for this coverage with format: " + csinfo.getFormat().getName()); if (cinfo.getNativeCRS() == null) { cinfo.setNativeCRS(reader.getCoordinateReferenceSystem()); } CoordinateReferenceSystem nativeCRS = cinfo.getNativeCRS(); if (cinfo.getSRS() == null) { cinfo.setSRS(nativeCRS.getIdentifiers().toArray()[0].toString()); } if (cinfo.getProjectionPolicy() == null) { if (nativeCRS != null && !nativeCRS.getIdentifiers().isEmpty()) { cinfo.setProjectionPolicy(ProjectionPolicy.REPROJECT_TO_DECLARED); } if (nativeCRS == null) { cinfo.setProjectionPolicy(ProjectionPolicy.FORCE_DECLARED); } } if (cinfo.getLatLonBoundingBox() == null && cinfo.getNativeBoundingBox() == null) { GeneralEnvelope envelope = reader.getOriginalEnvelope(); cinfo.setNativeBoundingBox(new ReferencedEnvelope(envelope)); cinfo.setLatLonBoundingBox(new ReferencedEnvelope(CoverageStoreUtils.getWGS84LonLatEnvelope(envelope))); } else if (cinfo.getLatLonBoundingBox() == null) { setupBounds(cinfo); } else if (cinfo.getNativeBoundingBox() == null && cinfo.getNativeCRS() != null) { ReferencedEnvelope boundsLatLon = cinfo.getLatLonBoundingBox(); cinfo.setNativeBoundingBox(boundsLatLon.transform(cinfo.getNativeCRS(), true)); } if (cinfo.getGrid() == null) { GridEnvelope originalRange = reader.getOriginalGridRange(); cinfo.setGrid(new GridGeometry2D(originalRange, reader.getOriginalGridToWorld(PixelInCell.CELL_CENTER), nativeCRS)); } } /** * Builds the default coverage contained in the current store * * */ public CoverageInfo buildCoverage() throws Exception { return buildCoverage(null); } /** * Builds the default coverage contained in the current store * * */ public CoverageInfo buildCoverage(String coverageName) throws Exception { if (store == null || !(store instanceof CoverageStoreInfo)) { throw new IllegalStateException("Coverage store not set."); } CoverageStoreInfo csinfo = (CoverageStoreInfo) store; GridCoverage2DReader reader = (GridCoverage2DReader) catalog .getResourcePool().getGridCoverageReader(csinfo, GeoTools.getDefaultHints()); if (reader == null) throw new Exception("Unable to acquire a reader for this coverage with format: " + csinfo.getFormat().getName()); return buildCoverage(reader, coverageName, null); } /** * Builds the default coverage contained in the current store * * @param nativeCoverageName the native name for the coverage * @param specifiedName the published name for the coverage. If null, the name will be determined from the coverage store. * @return coverage for the specified name * @throws Exception if the coverage store was not found or could not be read, or if the coverage could not be created. */ public CoverageInfo buildCoverageByName(String nativeCoverageName, String specifiedName) throws Exception { if (store == null || !(store instanceof CoverageStoreInfo)) { throw new IllegalStateException("Coverage store not set."); } CoverageStoreInfo csinfo = (CoverageStoreInfo) store; GridCoverage2DReader reader = (GridCoverage2DReader) catalog .getResourcePool().getGridCoverageReader(csinfo, GeoTools.getDefaultHints()); if (reader == null) throw new Exception("Unable to acquire a reader for this coverage with format: " + csinfo.getFormat().getName()); return buildCoverageInternal(reader, nativeCoverageName, null, specifiedName); } /** * Builds a coverage from a geotools grid coverage reader. * @param customParameters */ public CoverageInfo buildCoverage(GridCoverage2DReader reader, Map customParameters) throws Exception { return buildCoverage(reader, null, customParameters); } /** * Builds a coverage from a geotools grid coverage reader. * @param customParameters */ public CoverageInfo buildCoverage(GridCoverage2DReader reader, String coverageName, Map customParameters) throws Exception { return buildCoverageInternal(reader, coverageName, customParameters, null); } private CoverageInfo buildCoverageInternal(GridCoverage2DReader reader, String nativeCoverageName, Map customParameters, String specifiedName) throws Exception { if (store == null || !(store instanceof CoverageStoreInfo)) { throw new IllegalStateException("Coverage store not set."); } // if we are dealing with a multicoverage reader, wrap to simplify code if (nativeCoverageName != null) { reader = SingleGridCoverage2DReader.wrap(reader, nativeCoverageName); } CoverageStoreInfo csinfo = (CoverageStoreInfo) store; CoverageInfo cinfo = catalog.getFactory().createCoverage(); cinfo.setStore(csinfo); cinfo.setEnabled(true); WorkspaceInfo wspace = store.getWorkspace(); NamespaceInfo namespace = catalog.getNamespaceByPrefix(wspace.getName()); if (namespace == null) { namespace = catalog.getDefaultNamespace(); } cinfo.setNamespace(namespace); GeneralEnvelope envelope = reader.getOriginalEnvelope(); CoordinateReferenceSystem nativeCRS = envelope.getCoordinateReferenceSystem(); cinfo.setNativeCRS(nativeCRS); // mind the default projection policy, Coverages do not have a flexible // handling as feature types, they do reproject if the native srs is set, // force if missing if (nativeCRS != null) { try { Integer code = CRS.lookupEpsgCode(nativeCRS, false); if (code != null) { cinfo.setSRS("EPSG:" + code); cinfo.setProjectionPolicy(ProjectionPolicy.REPROJECT_TO_DECLARED); } } catch (FactoryException e) { LOGGER.log(Level.WARNING, "SRS lookup failed", e); } } if (nativeCRS == null) { cinfo.setProjectionPolicy(ProjectionPolicy.FORCE_DECLARED); } cinfo.setNativeBoundingBox(new ReferencedEnvelope(envelope)); cinfo.setLatLonBoundingBox(new ReferencedEnvelope(CoverageStoreUtils.getWGS84LonLatEnvelope(envelope))); GridEnvelope originalRange = reader.getOriginalGridRange(); cinfo.setGrid(new GridGeometry2D(originalRange, reader.getOriginalGridToWorld(PixelInCell.CELL_CENTER), nativeCRS)); // ///////////////////////////////////////////////////////////////////// // // Now reading a fake small GridCoverage just to retrieve meta // information about bands: // // - calculating a new envelope which is just 5x5 pixels // - if it's a mosaic, limit the number of tiles we're going to read to one // (with time and elevation there might be hundreds of superimposed tiles) // - reading the GridCoverage subset // // ///////////////////////////////////////////////////////////////////// Format format = csinfo.getFormat(); final GridCoverage2D gc; final ParameterValueGroup readParams = format.getReadParameters(); final Map parameters = CoverageUtils.getParametersKVP(readParams); final int minX = originalRange.getLow(0); final int minY = originalRange.getLow(1); final int width = originalRange.getSpan(0); final int height = originalRange.getSpan(1); final int maxX = minX + (width <= 5 ? width : 5); final int maxY = minY + (height <= 5 ? height : 5); // we have to be sure that we are working against a valid grid range. final GridEnvelope2D testRange = new GridEnvelope2D(minX, minY, maxX, maxY); // build the corresponding envelope final MathTransform gridToWorldCorner = reader.getOriginalGridToWorld(PixelInCell.CELL_CORNER); final GeneralEnvelope testEnvelope = CRS.transform(gridToWorldCorner, new GeneralEnvelope(testRange.getBounds())); testEnvelope.setCoordinateReferenceSystem(nativeCRS); if (customParameters != null) { parameters.putAll(customParameters); } // make sure mosaics with many superimposed tiles won't blow up with // a "too many open files" exception String maxAllowedTiles = ImageMosaicFormat.MAX_ALLOWED_TILES.getName().toString(); if (parameters.keySet().contains(maxAllowedTiles)) { parameters.put(maxAllowedTiles, 1); } // Since the read sample image won't be greater than 5x5 pixels and we are limiting the // number of granules to 1, we may do direct read instead of using JAI String useJaiImageRead = ImageMosaicFormat.USE_JAI_IMAGEREAD.getName().toString(); if (parameters.keySet().contains(useJaiImageRead)) { parameters.put(useJaiImageRead, false); } parameters.put(AbstractGridFormat.READ_GRIDGEOMETRY2D.getName().toString(), new GridGeometry2D(testRange, testEnvelope)); // try to read this coverage gc = reader.read(CoverageUtils.getParameters(readParams, parameters, true)); if (gc == null) { throw new Exception("Unable to acquire test coverage for format:" + format.getName()); } // remove read grid geometry since it is request specific parameters.remove(AbstractGridFormat.READ_GRIDGEOMETRY2D.getName().toString()); cinfo.getDimensions().addAll(getCoverageDimensions(gc.getSampleDimensions())); if (specifiedName != null) { cinfo.setName(specifiedName); cinfo.setTitle(specifiedName); cinfo.getKeywords().add(new Keyword(specifiedName)); } else { String name = gc.getName().toString(); cinfo.setName(name); cinfo.setTitle(name); cinfo.getKeywords().add(new Keyword(name)); } cinfo.setNativeCoverageName(nativeCoverageName); cinfo.setDescription(new StringBuilder("Generated from ").append(format.getName()).toString()); // keywords cinfo.getKeywords().add(new Keyword("WCS")); cinfo.getKeywords().add(new Keyword(format.getName())); // native format name cinfo.setNativeFormat(format.getName()); cinfo.getMetadata().put("dirName", new StringBuilder(store.getName()).append("_").append(nativeCoverageName).toString()); // request SRS's if ((gc.getCoordinateReferenceSystem2D().getIdentifiers() != null) && !gc.getCoordinateReferenceSystem2D().getIdentifiers().isEmpty()) { cinfo.getRequestSRS().add(((Identifier) gc.getCoordinateReferenceSystem2D().getIdentifiers().toArray()[0]).toString()); } // response SRS's if ((gc.getCoordinateReferenceSystem2D().getIdentifiers() != null) && !gc.getCoordinateReferenceSystem2D().getIdentifiers().isEmpty()) { cinfo.getResponseSRS().add(((Identifier) gc.getCoordinateReferenceSystem2D().getIdentifiers().toArray()[0]).toString()); } // supported formats final List formats = CoverageStoreUtils.listDataFormats(); for (Iterator i = formats.iterator(); i.hasNext();) { final Format fTmp = (Format) i.next(); final String fName = fTmp.getName(); if (fName.equalsIgnoreCase("WorldImage")) { // TODO check if coverage can encode Format cinfo.getSupportedFormats().add("GIF"); cinfo.getSupportedFormats().add("PNG"); cinfo.getSupportedFormats().add("JPEG"); cinfo.getSupportedFormats().add("TIFF"); } else if (fName.toLowerCase().startsWith("geotiff")) { // TODO check if coverage can encode Format cinfo.getSupportedFormats().add("GEOTIFF"); } else { // TODO check if coverage can encode Format cinfo.getSupportedFormats().add(fName); } } // interpolation methods cinfo.setDefaultInterpolationMethod("nearest neighbor"); cinfo.getInterpolationMethods().add("nearest neighbor"); cinfo.getInterpolationMethods().add("bilinear"); cinfo.getInterpolationMethods().add("bicubic"); // read parameters (get the params again since we altered the map to optimize the // coverage read) cinfo.getParameters().putAll(CoverageUtils.getParametersKVP(readParams)); /// dispose coverage gc.dispose(true); if(gc.getRenderedImage() instanceof PlanarImage) { ImageUtilities.disposePlanarImageChain((PlanarImage) gc.getRenderedImage()); } return cinfo; } List<CoverageDimensionInfo> getCoverageDimensions(GridSampleDimension[] sampleDimensions) { final int length = sampleDimensions.length; List<CoverageDimensionInfo> dims = new ArrayList<CoverageDimensionInfo>(); for (int i = 0; i < length; i++) { CoverageDimensionInfo dim = catalog.getFactory().createCoverageDimension(); GridSampleDimension sd = sampleDimensions[i]; String name = sd.getDescription().toString(Locale.getDefault()); dim.setName(name); StringBuilder label = new StringBuilder("GridSampleDimension".intern()); final Unit uom = sd.getUnits(); String uName = name.toUpperCase(); if (uom != null) { label.append("(".intern()); parseUOM(label, uom); label.append(")".intern()); dim.setUnit(uom.toString()); } else if(uName.startsWith("RED") || uName.startsWith("GREEN") || uName.startsWith("BLUE")) { // radiance in SI dim.setUnit("W.m-2.Sr-1"); } dim.setDimensionType(sd.getSampleDimensionType()); label.append("[".intern()); label.append(sd.getMinimumValue()); label.append(",".intern()); label.append(sd.getMaximumValue()); label.append("]".intern()); dim.setDescription(label.toString()); if (sd.getRange() != null) { dim.setRange(sd.getRange()); } else { dim.setRange(NumberRange.create(sd.getMinimumValue(), sd.getMaximumValue())); } final List<Category> categories = sd.getCategories(); if (categories != null) { for (Category cat : categories) { if ((cat != null) && cat.getName().toString(Locale.ENGLISH).equalsIgnoreCase("no data")) { double min = cat.getRange().getMinimum(); double max = cat.getRange().getMaximum(); dim.getNullValues().add(min); if (min != max) { dim.getNullValues().add(max); } } } } dims.add(dim); } return dims; } public WMSLayerInfo buildWMSLayer(String layerName) throws IOException { return buildWMSLayer(this.store, layerName); } WMSLayerInfo buildWMSLayer(StoreInfo store, String layerName) throws IOException { if (store == null || !(store instanceof WMSStoreInfo)) { throw new IllegalStateException("WMS store not set."); } WMSLayerInfo wli = catalog.getFactory().createWMSLayer(); wli.setName(layerName); wli.setNativeName(layerName); wli.setStore(store); wli.setEnabled(true); WorkspaceInfo workspace = store.getWorkspace(); NamespaceInfo namespace = catalog.getNamespaceByPrefix(workspace.getName()); if (namespace == null) { namespace = catalog.getDefaultNamespace(); } wli.setNamespace(namespace); Layer layer = wli.getWMSLayer(null); // try to get the native SRS -> we use the bounding boxes, GeoServer will publish all of the // supported SRS in the root, if we use getSRS() we'll get them all for (String srs : layer.getBoundingBoxes().keySet()) { try { CoordinateReferenceSystem crs = CRS.decode(srs); wli.setSRS(srs); wli.setNativeCRS(crs); } catch (Exception e) { LOGGER.log(Level.INFO, "Skipping " + srs + " definition, it was not recognized by the referencing subsystem"); } } // fall back on WGS84 if necessary, and handle well known WMS CRS codes String srs = wli.getSRS(); try { if (srs == null || srs.equals("CRS:84")) { wli.setSRS("EPSG:4326"); srs = "EPSG:4326"; wli.setNativeCRS(CRS.decode("EPSG:4326")); } else if(srs.equals("CRS:83")) { wli.setSRS("EPSG:4269"); srs = "EPSG:4269"; wli.setNativeCRS(CRS.decode("EPSG:4269")); } else if(srs.equals("CRS:27")) { wli.setSRS("EPSG:4267"); srs = "EPSG:4267"; wli.setNativeCRS(CRS.decode("EPSG:4267")); } } catch(Exception e) { throw (IOException) new IOException("Failed to compute the layer declared SRS code").initCause(e); } wli.setProjectionPolicy(ProjectionPolicy.FORCE_DECLARED); // try to grab the envelope GeneralEnvelope envelope = layer.getEnvelope(wli.getNativeCRS()); if (envelope != null) { ReferencedEnvelope re = new ReferencedEnvelope(envelope.getMinimum(0), envelope .getMaximum(0), envelope.getMinimum(1), envelope.getMaximum(1), wli .getNativeCRS()); wli.setNativeBoundingBox(re); } CRSEnvelope llbbox = layer.getLatLonBoundingBox(); if (llbbox != null) { ReferencedEnvelope re = new ReferencedEnvelope(llbbox.getMinX(), llbbox.getMaxX(), llbbox.getMinY(), llbbox.getMaxY(), DefaultGeographicCRS.WGS84); wli.setLatLonBoundingBox(re); } else if (wli.getNativeBoundingBox() != null) { try { wli.setLatLonBoundingBox(wli.getNativeBoundingBox().transform( DefaultGeographicCRS.WGS84, true)); } catch (Exception e) { LOGGER.log(Level.INFO, "Could not transform native bbox into a lat/lon one", e); } } // reflect all the metadata that we can grab wli.setAbstract(layer.get_abstract()); wli.setDescription(layer.get_abstract()); wli.setTitle(layer.getTitle()); if (layer.getKeywords() != null) { for (String kw : layer.getKeywords()) { if(kw != null){ wli.getKeywords().add(new Keyword(kw)); } } } // strip off the prefix if we're cascading from a server that does add them String published = wli.getName(); if (published.contains(":")) { wli.setName(published.substring(published.lastIndexOf(':') + 1)); } return wli; } private boolean axisFlipped(Version version, String srsName) { if(version.compareTo(new Version("1.3.0")) < 0) { // aah, sheer simplicity return false; } else { // gah, hell gates breaking loose if(srsName.startsWith("EPSG:")) { try { String epsgNative = "urn:x-ogc:def:crs:EPSG:".concat(srsName.substring(5)); return CRS.getAxisOrder(CRS.decode(epsgNative)) == AxisOrder.NORTH_EAST; } catch(Exception e) { LOGGER.log(Level.WARNING, "Failed to determine axis order for " + srsName + ", assuming east/north", e); return false; } } else { // CRS or AUTO, none of them is flipped so far return false; } } } void parseUOM(StringBuilder label, Unit uom) { String uomString = uom.toString(); uomString = uomString.replaceAll("\u00B2", "^2"); uomString = uomString.replaceAll("\u00B3", "^3"); uomString = uomString.replaceAll("\u212B", "A"); uomString = uomString.replaceAll("�", ""); label.append(uomString); } /** * Builds a layer for a feature type. * <p> * The resulting object is not added to the catalog, it must be done by the calling code after * the fact. * </p> */ public LayerInfo buildLayer(FeatureTypeInfo featureType) throws IOException { // also create a layer for the feautre type LayerInfo layer = buildLayer((ResourceInfo) featureType); StyleInfo style = getDefaultStyle(featureType); layer.setDefaultStyle(style); return layer; } /** * Builds a layer for a coverage. * <p> * The resulting object is not added to the catalog, it must be done by the calling code after * the fact. * </p> */ public LayerInfo buildLayer(CoverageInfo coverage) throws IOException { LayerInfo layer = buildLayer((ResourceInfo) coverage); layer.setDefaultStyle(getDefaultStyle(coverage)); return layer; } /** * Builds a layer wrapping a WMS layer resource * <p> * The resulting object is not added to the catalog, it must be done by the calling code after * the fact. * </p> */ public LayerInfo buildLayer(WMSLayerInfo wms) throws IOException { LayerInfo layer = buildLayer((ResourceInfo) wms); layer.setDefaultStyle(getDefaultStyle(wms)); return layer; } /** * Returns the default style for the specified resource, or null if the layer is vector and * geometryless * * @param resource * * @throws IOException */ public StyleInfo getDefaultStyle(ResourceInfo resource) throws IOException { // raster wise, only one style if (resource instanceof CoverageInfo || resource instanceof WMSLayerInfo) return catalog.getStyleByName(StyleInfo.DEFAULT_RASTER); // for vectors we depend on the the nature of the default geometry String styleName; FeatureTypeInfo featureType = (FeatureTypeInfo) resource; if (featureType.getFeatureType() == null) { return null; } GeometryDescriptor gd = featureType.getFeatureType().getGeometryDescriptor(); if (gd == null) { return null; } Class gtype = gd.getType().getBinding(); if (Point.class.isAssignableFrom(gtype) || MultiPoint.class.isAssignableFrom(gtype)) { styleName = StyleInfo.DEFAULT_POINT; } else if (LineString.class.isAssignableFrom(gtype) || MultiLineString.class.isAssignableFrom(gtype)) { styleName = StyleInfo.DEFAULT_LINE; } else if (Polygon.class.isAssignableFrom(gtype) || MultiPolygon.class.isAssignableFrom(gtype)) { styleName = StyleInfo.DEFAULT_POLYGON; } else if (Point.class.isAssignableFrom(gtype) || MultiPoint.class.isAssignableFrom(gtype)) { styleName = StyleInfo.DEFAULT_POINT; } else { // fall back to the generic style styleName = StyleInfo.DEFAULT_GENERIC; } return catalog.getStyleByName(styleName); } public LayerInfo buildLayer(ResourceInfo resource) { LayerInfo layer = catalog.getFactory().createLayer(); layer.setResource(resource); layer.setName(resource.getName()); layer.setEnabled(true); // setup the layer type if (layer.getResource() instanceof FeatureTypeInfo) { layer.setType(PublishedType.VECTOR); } else if (layer.getResource() instanceof CoverageInfo) { layer.setType(PublishedType.RASTER); } else if (layer.getResource() instanceof WMSLayerInfo) { layer.setType(PublishedType.WMS); } return layer; } /** * Calculates the bounds of a layer group specifying a particular crs. */ public void calculateLayerGroupBounds(LayerGroupInfo layerGroup, CoordinateReferenceSystem crs) throws Exception { LayerGroupHelper helper = new LayerGroupHelper(layerGroup); helper.calculateBounds(crs); } /** * Calculate the bounds of a layer group from the CRS defined bounds. * Relies on the {@link LayerGroupHelper} * * @param layerGroup * @param crs the CRS who's bounds should be used * @see LayerGroupHelper#calculateBoundsFromCRS(CoordinateReferenceSystem) */ public void calculateLayerGroupBoundsFromCRS( LayerGroupInfo layerGroup, CoordinateReferenceSystem crs) { LayerGroupHelper helper = new LayerGroupHelper(layerGroup); helper.calculateBoundsFromCRS(crs); } /** * Calculates the bounds of a layer group by aggregating the bounds of each layer. */ public void calculateLayerGroupBounds(LayerGroupInfo layerGroup) throws Exception { LayerGroupHelper helper = new LayerGroupHelper(layerGroup); helper.calculateBounds(); } // // remove methods // /** * Removes a workspace from the catalog. * <p> * The <tt>recursive</tt> flag controls whether objects linked to the workspace such as stores * should also be deleted. * </p> */ public void removeWorkspace(WorkspaceInfo workspace, boolean recursive) { if (recursive) { workspace.accept(new CascadeDeleteVisitor(catalog)); } else { catalog.remove(workspace); } } /** * Removes a store from the catalog. * <p> * The <tt>recursive</tt> flag controls whether objects linked to the store such as resources * should also be deleted. * </p> */ public void removeStore(StoreInfo store, boolean recursive) { if (recursive) { store.accept(new CascadeDeleteVisitor(catalog)); } else { catalog.remove(store); } } /** * Removes a resource from the catalog. * <p> * The <tt>recursive</tt> flag controls whether objects linked to the resource such as layers * should also be deleted. * </p> */ public void removeResource(ResourceInfo resource, boolean recursive) { if (recursive) { resource.accept(new CascadeDeleteVisitor(catalog)); } else { catalog.remove(resource); } } /** * Reattaches a serialized {@link StoreInfo} to the catalog */ public void attach(StoreInfo storeInfo) { storeInfo = ModificationProxy.unwrap(storeInfo); ((StoreInfoImpl) storeInfo).setCatalog(catalog); } /** * Reattaches a serialized {@link ResourceInfo} to the catalog */ public void attach(ResourceInfo resourceInfo) { resourceInfo = ModificationProxy.unwrap(resourceInfo); ((ResourceInfoImpl) resourceInfo).setCatalog(catalog); } /** * Reattaches a serialized {@link LayerInfo} to the catalog */ public void attach(LayerInfo layerInfo) { attach(layerInfo.getResource()); } /** * Reattaches a serialized {@link MapInfo} to the catalog */ public void attach(MapInfo mapInfo) { // hmmm... mapInfo has a list of layers inside? Not names? for (LayerInfo layer : mapInfo.getLayers()) { attach(layer); } } /** * Reattaches a serialized {@link LayerGroupInfo} to the catalog */ public void attach(LayerGroupInfo groupInfo) { if (groupInfo.getRootLayer() != null) { attach(groupInfo.getRootLayer()); } if (groupInfo.getRootLayerStyle() != null) { attach(groupInfo.getRootLayerStyle()); } for (PublishedInfo p : groupInfo.getLayers()) { if (p instanceof LayerInfo) { attach((LayerInfo) p); } else { attach((LayerGroupInfo) p); } } for (StyleInfo style : groupInfo.getStyles()) { if (style != null) attach(style); } } /** * Reattaches a serialized {@link StyleInfo} to the catalog */ public void attach(StyleInfo styleInfo) { styleInfo = ModificationProxy.unwrap(styleInfo); ((StyleInfoImpl) styleInfo).setCatalog(catalog); } /** * Reattaches a serialized {@link NamespaceInfo} to the catalog */ public void attach(NamespaceInfo nsInfo) { // nothing to do } /** * Reattaches a serialized {@link WorkspaceInfo} to the catalog */ public void attach(WorkspaceInfo wsInfo) { // nothing to do } /** * Extracts the AttributeTypeInfo by copying them from the specified feature type. * @param ft The schema to be harvested * @param info The optional feature type info from which all the attributes belong to * */ public List<AttributeTypeInfo> getAttributes(FeatureType ft, FeatureTypeInfo info) { List<AttributeTypeInfo> attributes = new ArrayList<AttributeTypeInfo>(); for (PropertyDescriptor pd : ft.getDescriptors()) { AttributeTypeInfo att = catalog.getFactory().createAttribute(); att.setFeatureType(info); att.setName(pd.getName().getLocalPart()); att.setMinOccurs(pd.getMinOccurs()); att.setMaxOccurs(pd.getMaxOccurs()); att.setNillable(pd.isNillable()); att.setBinding(pd.getType().getBinding()); int length = FeatureTypes.getFieldLength(pd); if(length > 0) { att.setLength(length); } attributes.add(att); } return attributes; } /** * Creates referenced envelope from resource based off the native or declared SRS. This bbox * depends on the projection policy. * * <ul> * <li>force declared, reproject native to declared: use the declared SRS bounding box </li> * <li>keep native: use the native SRS bounding box</li> * <ul> * * @param resource * @return the new referenced envelope or null if there is no bounding box associated with the * CRS */ public ReferencedEnvelope getBoundsFromCRS(ResourceInfo resource) { ReferencedEnvelope crsReferencedEnvelope = null; ProjectionPolicy projPolicy = resource.getProjectionPolicy(); CoordinateReferenceSystem crs = null; //find the right crs to use based on the projection policy if (projPolicy == ProjectionPolicy.NONE) { crs = resource.getNativeCRS(); } else { crs = resource.getCRS(); } if (crs != null) { Envelope crsEnvelope = CRS.getEnvelope(crs); if (crsEnvelope != null) { crsReferencedEnvelope = new ReferencedEnvelope(crsEnvelope); } } return crsReferencedEnvelope; } }