/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2005-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2007-2012, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotoolkit.internal.sql.table;
import java.awt.RenderingHints;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import java.util.HashMap;
import java.util.Properties;
import javax.sql.DataSource;
import org.opengis.referencing.crs.SingleCRS;
import org.opengis.referencing.crs.VerticalCRS;
import org.opengis.referencing.crs.TemporalCRS;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.crs.CRSAuthorityFactory;
import org.opengis.referencing.crs.CRSFactory;
import org.opengis.referencing.datum.PixelInCell;
import org.opengis.referencing.operation.MathTransformFactory;
import org.opengis.util.FactoryException;
import org.apache.sis.internal.referencing.GeodeticObjectBuilder;
import org.apache.sis.metadata.iso.extent.Extents;
import org.apache.sis.referencing.CommonCRS;
import org.apache.sis.referencing.crs.DefaultTemporalCRS;
import org.apache.sis.referencing.CRS;
import org.geotoolkit.metadata.Citations;
import org.geotoolkit.referencing.IdentifiedObjects;
import org.geotoolkit.referencing.factory.wkt.DirectPostgisFactory;
import org.geotoolkit.referencing.factory.wkt.AuthorityFactoryProvider;
import org.geotoolkit.factory.AuthorityFactoryFinder;
import org.geotoolkit.factory.Factory;
import org.geotoolkit.factory.Hints;
import org.geotoolkit.resources.Errors;
import org.apache.sis.referencing.crs.AbstractCRS;
import org.apache.sis.referencing.cs.AxesConvention;
import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
/**
* A specialization of {@link Database} which specify the {@link CoordinateReferenceSystem}
* of the horizontal, vertical and temporal extents.
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @version 3.19
*
* @since 3.10 (derived from Seagis)
* @module
*/
public class SpatialDatabase extends Database {
/**
* The horizontal SRID of {@link #horizontalCRS}, as declared in the PostGIS geometry column.
*/
public final int horizontalSRID;
/**
* The horizontal coordinate reference system used for performing the search in the database.
* It must match the CRS used in the geometry columns indexed by PostGIS.
*/
public final SingleCRS horizontalCRS;
/**
* The vertical reference system, or {@code null} if none.
*/
public final VerticalCRS verticalCRS;
/**
* The temporal reference system, or {@code null} if none.
*/
public final DefaultTemporalCRS temporalCRS;
/**
* The horizontal and vertical CRS, without the temporal component.
*/
public final CoordinateReferenceSystem spatialCRS;
/**
* The complete CRS, including the vertical and temporal components if any.
*/
public final CoordinateReferenceSystem spatioTemporalCRS;
/**
* The spatio-temporal CRS without the vertical component.
*/
public final CoordinateReferenceSystem horizTemporalCRS;
/**
* The vertical CRS with time.
*/
public final CoordinateReferenceSystem vertTemporalCRS;
/**
* Whatever default grid range computation should be performed on transforms
* relative to pixel center or relative to pixel corner. The former is OGC
* convention while the later is Java convention.
*/
public final PixelInCell pixelInCell;
/**
* The authority factory connected to the PostGIS {@code "spatial_ref_sys"} table.
* Will be created when first needed.
*/
private transient CRSAuthorityFactory crsFactory;
/**
* The math transform factory, created only when first needed.
*/
private transient MathTransformFactory mtFactory;
/**
* Creates a new instance using the same configuration than the given instance.
* The new instance will have its own, initially empty, cache.
*
* @param toCopy The existing instance to copy.
*/
public SpatialDatabase(final SpatialDatabase toCopy) {
super(toCopy);
horizontalSRID = toCopy.horizontalSRID;
horizontalCRS = toCopy.horizontalCRS;
verticalCRS = toCopy.verticalCRS;
temporalCRS = toCopy.temporalCRS;
spatialCRS = toCopy.spatialCRS;
spatioTemporalCRS = toCopy.spatioTemporalCRS;
horizTemporalCRS = toCopy.horizTemporalCRS;
vertTemporalCRS = toCopy.vertTemporalCRS;
pixelInCell = toCopy.pixelInCell;
}
/**
* Creates a new instance using the provided data source and configuration properties.
* A default Coordinate Reference System is used.
* <p>
* If the given properties contains only one entry, and the key for this entry is
* {@value org.geotoolkit.internal.sql.table.ConfigurationKey#PARAMETERS}, then the
* value will be used as {@link org.opengis.parameter.ParameterValueGroup}.
*
* @param datasource The data source, or {@code null} for creating it from the URL.
* @param properties The configuration properties, or {@code null} if none.
*/
public SpatialDatabase(final DataSource datasource, final Properties properties) {
this(datasource, properties, CommonCRS.Temporal.TRUNCATED_JULIAN.crs());
}
/**
* Creates a new instance using the provided data source, temporal CRS and configuration
* properties.
* <p>
* If the given properties contains only one entry, and the key for this entry is
* {@value org.geotoolkit.internal.sql.table.ConfigurationKey#PARAMETERS}, then the
* value will be used as {@link org.opengis.parameter.ParameterValueGroup}.
*
* @param datasource The data source, or {@code null} for creating it from the URL.
* @param properties The configuration properties, or {@code null} if none.
* @param temporalCRS The temporal vertical reference system, or {@code null} if none.
* @throws IllegalStateException if problem during Spatio Temporal CRS creation.
*/
public SpatialDatabase(final DataSource datasource, final Properties properties, final TemporalCRS temporalCRS) {
super(datasource, properties);
this.horizontalSRID = 4326;
this.horizontalCRS = CommonCRS.WGS84.normalizedGeographic();
this.verticalCRS = CommonCRS.Vertical.ELLIPSOIDAL.crs();
this.temporalCRS = DefaultTemporalCRS.castOrCopy(temporalCRS);
this.spatialCRS = AbstractCRS.castOrCopy(CommonCRS.WGS84.geographic3D()).forConvention(AxesConvention.RIGHT_HANDED);
try {
spatioTemporalCRS = createSpatioTemporalCRS(spatialCRS, temporalCRS, true);
horizTemporalCRS = createSpatioTemporalCRS(horizontalCRS, temporalCRS, true);
vertTemporalCRS = createSpatioTemporalCRS(verticalCRS, temporalCRS, true);
} catch (FactoryException ex) { //-- TODO leave exception propagation.
throw new IllegalStateException(ex);
}
pixelInCell = PixelInCell.CELL_CORNER;
}
/**
* Creates a new spatio-temporal CRS from the given spatial CRS and the given temporal CRS.
* If any of those argument is null, the other one is returned directly.
*
* @param spatialCRS The spatial component, or {@code null}.
* @param temporalCRS The vertical component, or {@code null}.
* @param world {@code true} if the spatial CRS is valid for the world extent.
* @return The spatio-temporal CRS.
*/
private static CoordinateReferenceSystem createSpatioTemporalCRS(
final CoordinateReferenceSystem spatialCRS, final TemporalCRS temporalCRS, final boolean world)
throws FactoryException {
if (temporalCRS == null) return spatialCRS;
if (spatialCRS == null) return temporalCRS;
final Map<String,Object> id = new HashMap<>(4);
id.put(CoordinateReferenceSystem.NAME_KEY, spatialCRS.getName().getCode() +
" + Time(" + temporalCRS.getName().getCode() + ')');
if (world) {
id.put(CoordinateReferenceSystem.DOMAIN_OF_VALIDITY_KEY, Extents.WORLD);
}
return new GeodeticObjectBuilder().addName(spatialCRS.getName().getCode() +
" + Time(" + temporalCRS.getName().getCode() + ')')
//-- TODO .addDomainOfValidity(Extents.WORLD)
.createCompoundCRS(spatialCRS, temporalCRS);
}
/**
* Creates a new instance using the provided data source, spatio-temporal CRS and configuration
* properties.
* <p>
* If the given properties contains only one entry, and the key for this entry is
* {@value org.geotoolkit.internal.sql.table.ConfigurationKey#PARAMETERS}, then the
* value will be used as {@link org.opengis.parameter.ParameterValueGroup}.
*
* @param datasource The data source, or {@code null} for creating it from the URL.
* @param properties The configuration properties, or {@code null} if none.
* @param spatialCRS The spatial CRS, not including the temporal component.
* @param temporalCRS The temporal CRS, or {@code null} if none.
* @throws FactoryException If an error occurred while fetching the SRID of the horizontal CRS.
*/
public SpatialDatabase(final DataSource datasource, final Properties properties,
final CoordinateReferenceSystem spatialCRS, final TemporalCRS temporalCRS)
throws FactoryException
{
super(datasource, properties);
ensureNonNull("spatialCRS", spatialCRS);
this.horizontalCRS = CRS.getHorizontalComponent(spatialCRS);
this.verticalCRS = CRS.getVerticalComponent(spatialCRS, true);
this.temporalCRS = (DefaultTemporalCRS) temporalCRS; // TODO DefaultTemporalCRS.castOrCopy(temporalCRS);
this.spatialCRS = spatialCRS;
this.pixelInCell = PixelInCell.CELL_CORNER;
horizTemporalCRS = createSpatioTemporalCRS(horizontalCRS, temporalCRS, false);
vertTemporalCRS = createSpatioTemporalCRS(verticalCRS, temporalCRS, false);
if (horizontalCRS == spatialCRS) {
spatioTemporalCRS = horizTemporalCRS;
} else {
spatioTemporalCRS = createSpatioTemporalCRS(spatialCRS, temporalCRS, false);
}
/*
* Try to get the PostGIS SRID from the horizontal CRS. First, search for an explicit
* PostGIS code. If none are found, lookup for the EPSG code and convert that code to
* a PostGIS code (this is usually the same, but not necessarily).
*/
if (horizontalCRS == null) {
horizontalSRID = 0;
return;
}
final String code = IdentifiedObjects.lookupIdentifier(Citations.POSTGIS, horizontalCRS, false);
if (code != null) try {
horizontalSRID = Integer.parseInt(code);
return;
} catch (NumberFormatException e) {
throw new FactoryException(Errors.format(Errors.Keys.NotAnInteger_1, code), e);
}
/*
* No PostGIS code. Search for an EPSG code...
*/
Integer id = IdentifiedObjects.lookupEpsgCode(horizontalCRS, true);
if (id != null) {
try (Connection c = getDataSource(true).getConnection()) {
final DirectPostgisFactory postgis = new DirectPostgisFactory(null, c);
id = postgis.getPrimaryKey(CoordinateReferenceSystem.class, id.toString());
} catch (SQLException e) {
throw new FactoryException(e);
}
if (id != null) {
horizontalSRID = id;
return;
}
}
throw new FactoryException(Errors.format(Errors.Keys.UndefinedProperty_1, "SRID"));
}
/**
* Derives a {@link CRSFactory} from the {@link #getCRSAuthorityFactory() authority factory}.
* If the authority factory is already a {@code CRSFactory} instance, then it is returned.
* Otherwise the implementation hints are inspected. If no {@code CRSFactory} is found, then
* the default instance is fetched from the {@link AuthorityFactoryFinder}.
*
* @return The CRS factory.
* @throws FactoryException If the factory can not be created.
*
* @since 3.19
*/
public final CRSFactory getCRSFactory() throws FactoryException {
final CRSAuthorityFactory factory = getCRSAuthorityFactory();
if (factory instanceof CRSFactory) {
return (CRSFactory) factory;
}
Hints hints = null;
if (factory instanceof Factory) {
final Map<RenderingHints.Key,?> impl = ((Factory) factory).getImplementationHints();
final Object candidate = impl.get(Hints.CRS_FACTORY);
if (candidate instanceof CRSFactory) {
return (CRSFactory) candidate;
}
hints = new Hints(impl);
}
return AuthorityFactoryFinder.getCRSFactory(hints);
}
/**
* Returns the CRS authority factory backed by the PostGIS {@code "spatial_ref_sys"} table.
* The factory is determined by the hints given at construction time.
*
* @return The shared CRS authority factory.
* @throws FactoryException If the factory can not be created.
*/
public final synchronized CRSAuthorityFactory getCRSAuthorityFactory() throws FactoryException {
if (crsFactory == null) {
crsFactory = new AuthorityFactoryProvider(hints).createFromPostGIS(getDataSource(true));
}
return crsFactory;
}
/**
* Returns the math transform factory.
* The factory is determined by the hints given at construction time.
*
* @return The shared math transform factory.
*/
public final synchronized MathTransformFactory getMathTransformFactory() {
if (mtFactory == null) {
mtFactory = AuthorityFactoryFinder.getMathTransformFactory(hints);
}
return mtFactory;
}
}