com.rgi.geopackage.features.GeoPackageFeatures Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of swagd Show documentation
Show all versions of swagd Show documentation
SWAGD: Software to Aggregate Geospatial Data
The newest version!
/* The MIT License (MIT)
*
* Copyright (c) 2015 Reinventing Geospatial, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.rgi.geopackage.features;
import com.rgi.common.BoundingBox;
import com.rgi.common.Pair;
import com.rgi.common.util.functional.ThrowingFunction;
import com.rgi.common.util.jdbc.JdbcUtility;
import com.rgi.geopackage.core.GeoPackageCore;
import com.rgi.geopackage.core.SpatialReferenceSystem;
import com.rgi.geopackage.features.geometry.Geometry;
import com.rgi.geopackage.features.geometry.GeometryFactory;
import com.rgi.geopackage.utility.DatabaseUtility;
import com.rgi.geopackage.verification.VerificationIssue;
import com.rgi.geopackage.verification.VerificationLevel;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
* @author Luke Lambert
*
*/
public class GeoPackageFeatures
{
/**
* Constructor
*
* @param databaseConnection
* The open connection to the database that contains a GeoPackage
* @param core
* Access to GeoPackage's "core" methods
*/
public GeoPackageFeatures(final Connection databaseConnection, final GeoPackageCore core)
{
this.databaseConnection = databaseConnection;
this.core = core;
}
/**
* @param verificationLevel
* Controls the level of verification testing performed
* @return the Feature GeoPackage requirements this GeoPackage fails to conform to
* @throws SQLException
* If there is a database error
*/
public Collection getVerificationIssues(final VerificationLevel verificationLevel) throws SQLException
{
return new FeaturesVerifier(this.databaseConnection, verificationLevel).getVerificationIssues();
}
/**
* Creates a user defined features table, and adds a corresponding entry to
* the content table
*
* @param tableName
* The name of the features table. The table name must begin
* with a letter (A..Z, a..z) or an underscore (_) and may only
* be followed by letters, underscores, or numbers, and may not
* begin with the prefix "gpkg_"
* @param identifier
* A human-readable identifier (e.g. short name) for the
* tableName content
* @param description
* A human-readable description for the tableName content
* @param boundingBox
* Bounding box for all content in tableName
* @param spatialReferenceSystem
* Spatial Reference System (SRS)
* @param primaryKeyColumnName
* Column name for the primary key. The column name must begin
* with a letter (A..Z, a..z) or an underscore (_) and may
* only be followed by letters, underscores, or numbers
* @param geometryColumn
* Geometry column definition
* @param columnDefinitions
* Definitions of non-geometry columns
* @return Returns a newly created user defined features table
* @throws SQLException
* throws if the method {@link #getFeatureSet(String)
* getFeatureSet} or the method {@link
* DatabaseUtility#tableOrViewExists(Connection, String)
* tableOrViewExists} or if the database cannot roll back the
* changes after a different exception throws will throw an
* SQLException
*/
public FeatureSet addFeatureSet(final String tableName,
final String identifier,
final String description,
final BoundingBox boundingBox,
final SpatialReferenceSystem spatialReferenceSystem,
final String primaryKeyColumnName,
final GeometryColumnDefinition geometryColumn,
final ColumnDefinition... columnDefinitions) throws SQLException
{
return this.addFeatureSet(tableName,
identifier,
description,
boundingBox,
spatialReferenceSystem,
primaryKeyColumnName,
geometryColumn,
Arrays.asList(columnDefinitions));
}
/**
* Creates a user defined features table, and adds a corresponding entry to
* the content table
*
* @param tableName
* The name of the features table. The table name must begin
* with a letter (A..Z, a..z) or an underscore (_) and may only
* be followed by letters, underscores, or numbers, and may not
* begin with the prefix "gpkg_"
* @param identifier
* A human-readable identifier (e.g. short name) for the
* tableName content
* @param description
* A human-readable description for the tableName content
* @param boundingBox
* Bounding box for all content in tableName
* @param spatialReferenceSystem
* Spatial Reference System (SRS)
* @param primaryKeyColumnName
* Column name for the primary key. The column name must begin
* with a letter (A..Z, a..z) or an underscore (_) and may
* only be followed by letters, underscores, or numbers
* @param geometryColumn
* Geometry column definition
* @param columnDefinitions
* Definitions of non-geometry columns
* @return Returns a newly created user defined features table
* @throws SQLException
* throws if the method {@link #getFeatureSet(String)
* getFeatureSet} or the method {@link
* DatabaseUtility#tableOrViewExists(Connection, String)
* tableOrViewExists} or if the database cannot roll back the
* changes after a different exception throws will throw an
* SQLException
*/
public FeatureSet addFeatureSet(final String tableName,
final String identifier,
final String description,
final BoundingBox boundingBox,
final SpatialReferenceSystem spatialReferenceSystem,
final String primaryKeyColumnName,
final GeometryColumnDefinition geometryColumn,
final Collection columnDefinitions) throws SQLException
{
DatabaseUtility.validateTableName(tableName);
if(geometryColumn == null)
{
throw new IllegalArgumentException("Geometry column definition name may not be null");
}
if(columnDefinitions == null || columnDefinitions.contains(null))
{
throw new IllegalArgumentException("Column definitions may not be null");
}
if(DatabaseUtility.tableOrViewExists(this.databaseConnection, tableName))
{
throw new IllegalArgumentException("A table already exists with this feature set's table name");
}
try
{
// Create the feature set table
this.addFeatureTableNoCommit(tableName,
primaryKeyColumnName,
geometryColumn,
columnDefinitions);
// Add feature set to the content table
this.core.addContent(tableName,
FeatureSet.FeatureContentType,
identifier,
description,
boundingBox,
spatialReferenceSystem);
this.addGeometryColumnNoCommit(tableName,
geometryColumn,
spatialReferenceSystem.getIdentifier());
this.databaseConnection.commit();
return this.getFeatureSet(tableName); // TODO this is a lazy way of doing things (carried on from the tiles implementation). There should be a method in core to query for the only information that can't already be obtained in this function - the last_change
}
catch(final Throwable th)
{
this.databaseConnection.rollback();
throw th;
}
}
/**
* Gets the geometry column definition for a specified {@link FeatureSet}
*
* @param featureSet
* Target feature set
* @return a {@link GeometryColumn}
* @throws SQLException
* When there is a database error
*/
public GeometryColumn getGeometryColumn(final FeatureSet featureSet) throws SQLException
{
if(featureSet == null)
{
throw new IllegalArgumentException("Feature set may not be null");
}
final String geometryColumnQuery = String.format("SELECT %s, %s, %s, %s, %s FROM %s WHERE %s = ?",
"column_name",
"geometry_type_name",
"srs_id",
"z",
"m",
GeoPackageFeatures.GeometryColumnsTableName,
"table_name");
return JdbcUtility.selectOne(this.databaseConnection,
geometryColumnQuery,
preparedStatement -> preparedStatement.setString(1, featureSet.getTableName()),
resultSet -> new GeometryColumn(featureSet.getTableName(),
resultSet.getString(1), // geometry column name
resultSet.getString(2), // geometry column type
resultSet.getInt (3), // geometry column srs id
ValueRequirement.fromInt(resultSet.getInt(4)), // z value requirement
ValueRequirement.fromInt(resultSet.getInt(5)))); // m value requirement
}
/**
* Gets the non-identifier (primary key) and non-geometry columns
*
* @param featureSet
* Handle to a feature table
* @return a collection of {@link Column}s that describe the attributes the feature set contains
* @throws SQLException
* If there is a database error
*/
public List getAttributeColumns(final FeatureSet featureSet) throws SQLException
{
if(featureSet == null)
{
throw new IllegalArgumentException("Feature set may not be null");
}
try(final Statement statement = GeoPackageFeatures.this.databaseConnection.createStatement())
{
//noinspection JDBCExecuteWithNonConstantString
try(final ResultSet tableInfo = statement.executeQuery(String.format("PRAGMA table_info(%s)", featureSet.getTableName())))
{
final List columns = new ArrayList<>();
while(tableInfo.next())
{
final String name = tableInfo.getString("name");
if(!tableInfo.getBoolean("pk") && // We don't want the primary key column
!name.equalsIgnoreCase(featureSet.getGeometryColumnName())) // We also don't want the geometry column
{
final String type = tableInfo.getString("type");
final String defaultValue = tableInfo.getString("dflt_value");
final EnumSet flags = EnumSet.noneOf(ColumnFlag.class);
if(tableInfo.getBoolean("notnull"))
{
flags.add(ColumnFlag.NotNull);
}
// TODO there are other ColumnFlags that need to be checked here: AutoIncrement, Unique
// TODO neither those flags aren't directly available in table_info
columns.add(new Column(name,
type,
flags,
null,
defaultValue));
}
}
return columns;
}
}
}
/**
* Gets a feature set object based on its table name
*
* @param featureSetTableName
* Name of a feature set table
* @return Returns a {@link FeatureSet} or null if there isn't with the
* supplied table name
* @throws SQLException
* If there is a database error
*/
@SuppressWarnings("JDBCExecuteWithNonConstantString")
public FeatureSet getFeatureSet(final String featureSetTableName) throws SQLException
{
if(!DatabaseUtility.tableOrViewExists(this.databaseConnection, GeoPackageFeatures.GeometryColumnsTableName))
{
return null;
}
final String geometryColumnQuery = String.format("SELECT %s FROM %s WHERE %s = ?",
"column_name",
GeoPackageFeatures.GeometryColumnsTableName,
"table_name");
final String geometryColumnName = JdbcUtility.selectOne(this.databaseConnection,
geometryColumnQuery,
preparedStatement -> preparedStatement.setString(1, featureSetTableName),
resultSet -> resultSet.getString(1)); // geometry column name
if(geometryColumnName == null) // If the table exists, but isn't an entry in the geometry column table...
{
return null;
}
try(final Statement statement = GeoPackageFeatures.this.databaseConnection.createStatement())
{
try(final ResultSet tableInfo = statement.executeQuery(String.format("PRAGMA table_info(%s)", featureSetTableName)))
{
String primaryKeyColumnName = null;
final Collection attributeColumnNames = new ArrayList<>();
while(tableInfo.next())
{
final String name = tableInfo.getString("name");
if(!name.equalsIgnoreCase(geometryColumnName))
{
if(tableInfo.getBoolean("pk"))
{
primaryKeyColumnName = name;
}
else // non-primary key columns (there can only be one primary key column according to the spec)
{
attributeColumnNames.add(name);
}
}
}
final String finalPrimaryKeyColumnName = primaryKeyColumnName;
return this.core.getContent(featureSetTableName,
(tableName,
dataType,
identifier,
description,
lastChange,
minimumX,
minimumY,
maximumX,
maximumY,
spatialReferenceSystem) -> new FeatureSet(tableName,
identifier,
description,
lastChange,
minimumX,
minimumY,
maximumX,
maximumY,
spatialReferenceSystem,
finalPrimaryKeyColumnName,
geometryColumnName,
attributeColumnNames));
}
}
}
/**
* Gets all entries in the GeoPackage's contents table with the "features"
* data_type
*
* @return Returns a collection of {@link FeatureSet}s
* @throws SQLException
* throws if the method
* {@link #getFeatureSets(SpatialReferenceSystem) getFeatureSets}
* throws
*/
public Collection getFeatureSets() throws SQLException
{
return this.getFeatureSets(null);
}
/**
* Gets all entries in the GeoPackage's contents table with the "features"
* data_type that also match the supplied spatial reference system
*
* @param matchingSpatialReferenceSystem
* Spatial reference system that returned {@link FeatureSet}s
* much refer to
* @return Returns a collection of {@link FeatureSet}s
* @throws SQLException
* if there's an SQL error
*/
public Collection getFeatureSets(final SpatialReferenceSystem matchingSpatialReferenceSystem) throws SQLException
{
return this.core
.getContentTableNames(FeatureSet.FeatureContentType,
matchingSpatialReferenceSystem)
.stream()
.map((ThrowingFunction)(this::getFeatureSet))
.collect(Collectors.toList());
}
/**
* Returns a list of all features that correspond to the given feature set.
* If a large set of features is anticipated use {@link #visitFeatures} to
* avoid memory issues
*
* @param featureSet
* Handle to a feature table
* @return List of features
* @throws SQLException
* if there is a database error
* @throws WellKnownBinaryFormatException
* Handle to a feature table
*/
public List getFeatures(final FeatureSet featureSet) throws SQLException, WellKnownBinaryFormatException
{
if(featureSet == null)
{
throw new IllegalArgumentException("Feature set may not be null");
}
final String featureQuery = String.format("SELECT %s, %s%s FROM %s",
featureSet.getPrimaryKeyColumnName(),
featureSet.getGeometryColumnName(),
featureSet.getAttributeColumnNames().isEmpty() ? ""
: ", " + String.join(", ", featureSet.getAttributeColumnNames()),
featureSet.getTableName());
try(final Statement statement = this.databaseConnection.createStatement())
{
//noinspection JDBCExecuteWithNonConstantString
try(final ResultSet resultSet = statement.executeQuery(featureQuery))
{
final List results = new ArrayList<>();
while(resultSet.next())
{
final Map attributes = new HashMap<>();
for(final String columnName : featureSet.getAttributeColumnNames())
{
attributes.put(columnName, resultSet.getObject(columnName));
}
results.add(new Feature(resultSet.getInt(featureSet.getPrimaryKeyColumnName()),
this.createGeometry(resultSet.getBytes(featureSet.getGeometryColumnName())),
attributes));
}
return results;
}
}
}
/**
* Gets a {@link Feature} given a geometry column and feature identifier
*
* @param featureSet
* Feature set containing the requested feature
* @param featureIdentifier
* Identifier for a feature
* @return a {@link Feature}
* @throws SQLException
* if there is a database error
* @throws WellKnownBinaryFormatException
* if any of the features contain malformed Well Known Binary data
*/
public Feature getFeature(final FeatureSet featureSet,
final int featureIdentifier) throws SQLException, WellKnownBinaryFormatException
{
if(featureSet == null)
{
throw new IllegalArgumentException("Feature set may not be null");
}
final String featureQuery = String.format("SELECT %s%s FROM %s WHERE %s = ?",
featureSet.getGeometryColumnName(),
featureSet.getAttributeColumnNames().isEmpty() ? ""
: ", " + String.join(", ", featureSet.getAttributeColumnNames()),
featureSet.getTableName(),
featureSet.getPrimaryKeyColumnName());
final Pair> feature = JdbcUtility.selectOne(this.databaseConnection,
featureQuery,
preparedStatement -> preparedStatement.setInt(1, featureIdentifier),
resultSet -> { final Map attributes = new HashMap<>();
for(final String columnName : featureSet.getAttributeColumnNames())
{
attributes.put(columnName, resultSet.getObject(columnName));
}
return Pair.of(resultSet.getBytes(featureSet.getGeometryColumnName()),
attributes);
});
if(feature == null)
{
return null;
}
return new Feature(featureIdentifier,
this.createGeometry(feature.getLeft()),
feature.getRight());
}
/**
* Applies a consumer to every feature in a feature set
*
* @param featureSet
* Handle to a feature table
* @param featureConsumer
* Callback that operates on a single feature
* @throws SQLException
* if there is a database error
* @throws WellKnownBinaryFormatException
* Handle to a feature table
*/
public void visitFeatures(final FeatureSet featureSet,
final Consumer featureConsumer) throws SQLException, WellKnownBinaryFormatException
{
if(featureSet == null)
{
throw new IllegalArgumentException("Geometry column may not be null");
}
if(featureConsumer == null)
{
throw new IllegalArgumentException("Feature consumer may not be null");
}
final String featureQuery = String.format("SELECT %s, %s%s FROM %s",
featureSet.getPrimaryKeyColumnName(),
featureSet.getGeometryColumnName(),
featureSet.getAttributeColumnNames().isEmpty() ? ""
: ", " + String.join(", ", featureSet.getAttributeColumnNames()),
featureSet.getTableName());
try(final Statement statement = this.databaseConnection.createStatement())
{
//noinspection JDBCExecuteWithNonConstantString
try(final ResultSet resultSet = statement.executeQuery(featureQuery))
{
while(resultSet.next())
{
final Map attributes = new HashMap<>();
for(final String columnName : featureSet.getAttributeColumnNames())
{
attributes.put(columnName, resultSet.getObject(columnName));
}
featureConsumer.accept(new Feature(resultSet.getInt(featureSet.getPrimaryKeyColumnName()),
this.createGeometry(resultSet.getBytes(featureSet.getGeometryColumnName())),
attributes));
}
}
}
}
/**
* Adds a feature to a feature set
*
* @param geometryColumn
* Geometry column of a feature set
* @param geometry
* Geometry of a feature
* @param attributeColumnNames
* List of attribute column names, specified in the same order as the supplied values
* @param attributeValues
* List of attribute values, specified in the same order as the supplied column names
* @return a handle to the newly created {@link Feature} object
* @throws SQLException
* if there is a database error
*/
public Feature addFeature(final GeometryColumn geometryColumn,
final Geometry geometry,
final List attributeColumnNames,
final List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy