diff --git a/impexp-client-gui/build.gradle b/impexp-client-gui/build.gradle index 0d9279850..fe94019c9 100644 --- a/impexp-client-gui/build.gradle +++ b/impexp-client-gui/build.gradle @@ -11,11 +11,11 @@ configurations { dependencies { api project(':impexp-client-cli') - api 'com.formdev:flatlaf:3.2.1' - api 'com.formdev:flatlaf-extras:3.2.1' - api 'com.formdev:flatlaf-swingx:3.2.1' - api 'com.fifesoft:rsyntaxtextarea:3.3.4' - api 'com.github.vertical-blank:sql-formatter:2.0.4' + api 'com.formdev:flatlaf:3.4.1' + api 'com.formdev:flatlaf-extras:3.4.1' + api 'com.formdev:flatlaf-swingx:3.4.1' + api 'com.fifesoft:rsyntaxtextarea:3.4.0' + api 'com.github.vertical-blank:sql-formatter:2.0.5' api ('org.citydb:swingx-ws:1.1.5') { transitive = false } diff --git a/impexp-core/build.gradle b/impexp-core/build.gradle index 8ef66b621..ef23c376e 100644 --- a/impexp-core/build.gradle +++ b/impexp-core/build.gradle @@ -3,21 +3,21 @@ dependencies { api 'info.picocli:picocli:4.7.5' api 'org.citydb:sqlbuilder:2.2.1' api 'org.citydb:3dcitydb-ade-citygml4j:1.1.3' - api 'org.geotools:gt-epsg-extension:28.4' - api 'org.geotools:gt-epsg-wkt:28.4' - api 'org.geotools:gt-referencing:28.4' + api 'org.geotools:gt-epsg-extension:31.0' + api 'org.geotools:gt-epsg-wkt:31.0' + api 'org.geotools:gt-referencing:31.0' api 'org.locationtech.jts:jts-core:1.19.0' - api 'org.postgresql:postgresql:42.6.0' - api 'net.postgis:postgis-jdbc:2021.1.0' + api 'org.postgresql:postgresql:42.7.3' + api 'net.postgis:postgis-jdbc:2023.1.0' api 'com.oracle.database.jdbc:ojdbc8:21.3.0.0' api 'com.oracle.sdoapi:sdoapi:21.3.0.0' - api 'org.apache.tika:tika-core:2.8.0' + api 'org.apache.tika:tika-core:2.9.2' api 'com.univocity:univocity-parsers:2.9.1' - api 'org.slf4j:slf4j-nop:2.0.7' - implementation 'com.h2database:h2:2.2.220' - implementation 'net.sf.saxon:Saxon-HE:12.3' - implementation 'org.apache.tomcat:tomcat-jdbc:10.0.27' - implementation 'org.apache.commons:commons-compress:1.24.0' + api 'org.slf4j:slf4j-nop:2.0.10' + implementation 'com.h2database:h2:2.2.224' + implementation 'net.sf.saxon:Saxon-HE:12.4' + implementation 'org.apache.tomcat:tomcat-jdbc:10.1.20' + implementation 'org.apache.commons:commons-compress:1.26.1' } javadoc { diff --git a/impexp-core/src/main/java/org/citydb/core/database/adapter/AbstractUtilAdapter.java b/impexp-core/src/main/java/org/citydb/core/database/adapter/AbstractUtilAdapter.java index 407198b3b..8c6d00d56 100644 --- a/impexp-core/src/main/java/org/citydb/core/database/adapter/AbstractUtilAdapter.java +++ b/impexp-core/src/main/java/org/citydb/core/database/adapter/AbstractUtilAdapter.java @@ -46,9 +46,9 @@ import org.citydb.core.query.builder.sql.SQLQueryBuilder; import org.citydb.sqlbuilder.schema.Table; import org.citydb.sqlbuilder.select.Select; +import org.geotools.api.referencing.FactoryException; +import org.geotools.api.referencing.crs.CoordinateReferenceSystem; import org.geotools.referencing.CRS; -import org.opengis.referencing.FactoryException; -import org.opengis.referencing.crs.CoordinateReferenceSystem; import javax.xml.bind.JAXBException; import java.sql.*; diff --git a/impexp-core/src/main/java/org/citydb/core/file/output/ScatterZipOutputStream.java b/impexp-core/src/main/java/org/citydb/core/file/output/ScatterZipOutputStream.java index 84c239351..bd639d491 100644 --- a/impexp-core/src/main/java/org/citydb/core/file/output/ScatterZipOutputStream.java +++ b/impexp-core/src/main/java/org/citydb/core/file/output/ScatterZipOutputStream.java @@ -34,7 +34,7 @@ import org.apache.commons.compress.parallel.InputStreamSupplier; import org.apache.commons.compress.parallel.ScatterGatherBackingStore; import org.apache.commons.compress.parallel.ScatterGatherBackingStoreSupplier; -import org.apache.commons.compress.utils.BoundedInputStream; +import org.apache.commons.io.input.BoundedInputStream; import java.io.*; import java.nio.charset.StandardCharsets; diff --git a/impexp-core/src/main/java/org/citydb/core/query/builder/sql/SpatialOperatorBuilder.java b/impexp-core/src/main/java/org/citydb/core/query/builder/sql/SpatialOperatorBuilder.java index 7db1eca92..f2e43bae3 100644 --- a/impexp-core/src/main/java/org/citydb/core/query/builder/sql/SpatialOperatorBuilder.java +++ b/impexp-core/src/main/java/org/citydb/core/query/builder/sql/SpatialOperatorBuilder.java @@ -48,10 +48,10 @@ import org.citydb.sqlbuilder.select.operator.comparison.ComparisonFactory; import org.citydb.sqlbuilder.select.operator.logical.LogicalOperationFactory; import org.citygml4j.model.module.gml.GMLCoreModule; +import org.geotools.api.referencing.FactoryException; +import org.geotools.api.referencing.crs.CoordinateReferenceSystem; import org.geotools.measure.Units; import org.geotools.referencing.util.CRSUtilities; -import org.opengis.referencing.FactoryException; -import org.opengis.referencing.crs.CoordinateReferenceSystem; import si.uom.SI; import javax.measure.Unit; @@ -59,230 +59,230 @@ import java.sql.SQLException; public class SpatialOperatorBuilder { - private final Query query; - private final SchemaPathBuilder schemaPathBuilder; - private final SchemaMapping schemaMapping; - private final AbstractDatabaseAdapter databaseAdapter; - private final String schemaName; - - protected SpatialOperatorBuilder(Query query, SchemaPathBuilder schemaPathBuilder, SchemaMapping schemaMapping, AbstractDatabaseAdapter databaseAdapter, String schemaName) { - this.query = query; - this.schemaPathBuilder = schemaPathBuilder; - this.schemaMapping = schemaMapping; - this.databaseAdapter = databaseAdapter; - this.schemaName = schemaName; - } - - protected void buildSpatialOperator(AbstractSpatialOperator operator, SQLQueryContext queryContext, boolean negate, boolean useLeftJoins) throws QueryBuildException { - switch (operator.getOperatorName()) { - case BBOX: - case EQUALS: - case DISJOINT: - case TOUCHES: - case WITHIN: - case OVERLAPS: - case INTERSECTS: - case CONTAINS: - buildBinaryOperator((BinarySpatialOperator)operator, queryContext, negate, useLeftJoins); - break; - case DWITHIN: - case BEYOND: - buildDistanceOperator((DistanceOperator)operator, queryContext, negate, useLeftJoins); - break; - } - - } - - private void buildBinaryOperator(BinarySpatialOperator operator, SQLQueryContext queryContext, boolean negate, boolean useLeftJoins) throws QueryBuildException { - if (!SpatialOperatorName.BINARY_SPATIAL_OPERATORS.contains(operator.getOperatorName())) - throw new QueryBuildException(operator.getOperatorName() + " is not a binary spatial operator."); - - ValueReference valueReference = null; - GeometryObject spatialDescription = operator.getSpatialDescription(); - - // we currently only support ValueReference as left operand - if (operator.getLeftOperand() != null && operator.getLeftOperand().getExpressionName() == ExpressionName.VALUE_REFERENCE) - valueReference = (ValueReference)operator.getLeftOperand(); - - if (valueReference == null && operator.getOperatorName() != SpatialOperatorName.BBOX) - throw new QueryBuildException("The spatial " + operator.getOperatorName() + " operator requires a ValueReference pointing to the geometry property to be tested."); - - if (spatialDescription == null) - throw new QueryBuildException("The spatial description may not be null."); - - if (valueReference == null) - valueReference = getBoundedByProperty(query); - - // transform coordinate values if required - DatabaseSrs targetSrs = databaseAdapter.getConnectionMetaData().getReferenceSystem(); - - if (spatialDescription.getSrid() != targetSrs.getSrid()) { - try { - spatialDescription = databaseAdapter.getUtil().transform(spatialDescription, targetSrs); - } catch (SQLException e) { - throw new QueryBuildException("Failed to transform coordinates of test geometry.", e); - } - } - - // build the value reference and spatial predicate - schemaPathBuilder.addSchemaPath(valueReference.getSchemaPath(), queryContext, useLeftJoins); - Table toTable = queryContext.getToTable(); - Column targetColumn = queryContext.getTargetColumn(); - - GeometryProperty property = (GeometryProperty)valueReference.getSchemaPath().getLastNode().getPathElement(); - if (property.isSetInlineColumn()) { - PredicateToken predicate = databaseAdapter.getSQLAdapter().getBinarySpatialPredicate(operator.getOperatorName(), queryContext.getTargetColumn(), spatialDescription, negate); - queryContext.addPredicate(predicate); - } else { - SpatialOperatorName operatorName = operator.getOperatorName(); - if (operatorName == SpatialOperatorName.CONTAINS || operatorName == SpatialOperatorName.EQUALS) - throw new QueryBuildException("The spatial " + operatorName + " operator is not supported for the geometry property '" + valueReference.getSchemaPath().getLastNode() + "'."); - - if (operatorName == SpatialOperatorName.DISJOINT) { - operatorName = SpatialOperatorName.INTERSECTS; - negate = !negate; - } - - GeometryObject bbox = spatialDescription.toEnvelope(); - boolean all = operatorName == SpatialOperatorName.WITHIN; - Table surfaceGeometry = new Table(MappingConstants.SURFACE_GEOMETRY, schemaName, schemaPathBuilder.getAliasGenerator()); - Table cityObject = getCityObjectTable(query, queryContext, useLeftJoins); - - Select inner = new Select() - .addProjection(surfaceGeometry.getColumn(MappingConstants.ID)) - .addSelection(ComparisonFactory.equalTo(surfaceGeometry.getColumn(MappingConstants.ROOT_ID), targetColumn)) - .addSelection(ComparisonFactory.isNotNull(surfaceGeometry.getColumn(MappingConstants.GEOMETRY))) - .addSelection(databaseAdapter.getSQLAdapter().getBinarySpatialPredicate(operatorName, surfaceGeometry.getColumn(MappingConstants.GEOMETRY), spatialDescription, all)); - - PredicateToken spatialPredicate = LogicalOperationFactory.AND( - databaseAdapter.getSQLAdapter().getBinarySpatialPredicate(SpatialOperatorName.BBOX, cityObject.getColumn(MappingConstants.ENVELOPE), bbox, false), - ComparisonFactory.exists(inner, all)); - - if (negate) - spatialPredicate = LogicalOperationFactory.NOT(spatialPredicate); - - queryContext.addPredicates(ComparisonFactory.isNotNull(targetColumn), spatialPredicate); - } - - // add optimizer hint if required - if (databaseAdapter.getSQLAdapter().spatialPredicateRequiresNoIndexHint() - && targetColumn.getName().equalsIgnoreCase(MappingConstants.ENVELOPE)) - queryContext.getSelect().addOptimizerHint("no_index(" + toTable.getAlias() + " cityobject_objectclass_fkx)"); - } - - private void buildDistanceOperator(DistanceOperator operator, SQLQueryContext queryContext, boolean negate, boolean useLeftJoins) throws QueryBuildException { - if (!SpatialOperatorName.DISTANCE_OPERATORS.contains(operator.getOperatorName())) - throw new QueryBuildException(operator.getOperatorName() + " is not a distance operator."); - - ValueReference valueReference; - GeometryObject spatialDescription = operator.getSpatialDescription(); - Distance distance = operator.getDistance(); - - // we currently only support ValueReference as left operand - if (operator.getLeftOperand() != null && operator.getLeftOperand().getExpressionName() == ExpressionName.VALUE_REFERENCE) - valueReference = (ValueReference)operator.getLeftOperand(); - else - valueReference = getBoundedByProperty(query); - - if (spatialDescription == null) - throw new QueryBuildException("The spatial description may not be null."); - - // transform coordinate values if required - DatabaseSrs targetSrs = databaseAdapter.getConnectionMetaData().getReferenceSystem(); - - if (spatialDescription.getSrid() != targetSrs.getSrid()) { - try { - spatialDescription = databaseAdapter.getUtil().transform(spatialDescription, targetSrs); - } catch (SQLException e) { - throw new QueryBuildException("Failed to transform coordinates of test geometry.", e); - } - } - - // convert distance value into unit of srs - Unit srsUnit; - DistanceUnit distanceUnit = distance.isSetUnit() ? distance.getUnit() : DistanceUnit.METER; - UnitConverter converter; - - try { - CoordinateReferenceSystem crs = databaseAdapter.getUtil().decodeDatabaseSrs(targetSrs); - srsUnit = CRSUtilities.getUnit(crs.getCoordinateSystem()); - } catch (FactoryException e) { - // assume meter per default - srsUnit = SI.METRE; - } - - try { - converter = Units.getConverterToAny(distanceUnit.toUnit(), srsUnit); - } catch (IllegalArgumentException e) { - throw new QueryBuildException("Cannot convert from the unit '" + distanceUnit + "' to the unit of the database SRS."); - } - - double value = converter.convert(distance.getValue()); - - // build the value reference and spatial predicate - schemaPathBuilder.addSchemaPath(valueReference.getSchemaPath(), queryContext, useLeftJoins); - Table toTable = queryContext.getToTable(); - Column targetColumn = queryContext.getTargetColumn(); - - GeometryProperty property = (GeometryProperty)valueReference.getSchemaPath().getLastNode().getPathElement(); - if (property.isSetInlineColumn()) { - PredicateToken predicate = databaseAdapter.getSQLAdapter().getDistancePredicate(operator.getOperatorName(), queryContext.getTargetColumn(), spatialDescription, value, negate); - queryContext.addPredicate(predicate); - } else { - SpatialOperatorName operatorName = operator.getOperatorName(); - if (operatorName == SpatialOperatorName.BEYOND) { - operatorName = SpatialOperatorName.DWITHIN; - negate = !negate; - } - - // get bbox of query geometry and buffer it by the distance - GeometryObject bbox = spatialDescription.toEnvelope(); - double[] coords = bbox.getCoordinates(0); - coords[0] -= value; - coords[1] -= value; - coords[bbox.getDimension()] += value; - coords[bbox.getDimension() + 1] += value; - - Table surfaceGeometry = new Table(MappingConstants.SURFACE_GEOMETRY, schemaName, schemaPathBuilder.getAliasGenerator()); - Table cityObject = getCityObjectTable(query, queryContext, useLeftJoins); - - Select inner = new Select() - .addProjection(surfaceGeometry.getColumn(MappingConstants.ID)) - .addSelection(ComparisonFactory.equalTo(surfaceGeometry.getColumn(MappingConstants.ROOT_ID), targetColumn)) - .addSelection(ComparisonFactory.isNotNull(surfaceGeometry.getColumn(MappingConstants.GEOMETRY))) - .addSelection(databaseAdapter.getSQLAdapter().getDistancePredicate(operatorName, surfaceGeometry.getColumn(MappingConstants.GEOMETRY), spatialDescription, value, false)); - - PredicateToken spatialPredicate = LogicalOperationFactory.AND( - databaseAdapter.getSQLAdapter().getBinarySpatialPredicate(SpatialOperatorName.BBOX, cityObject.getColumn(MappingConstants.ENVELOPE), bbox, false), - ComparisonFactory.exists(inner, false)); - - if (negate) - spatialPredicate = LogicalOperationFactory.NOT(spatialPredicate); - - queryContext.addPredicates(ComparisonFactory.isNotNull(targetColumn), spatialPredicate); - } - - // add optimizer hint if required - if (databaseAdapter.getSQLAdapter().spatialPredicateRequiresNoIndexHint() - && targetColumn.getName().equalsIgnoreCase(MappingConstants.ENVELOPE)) - queryContext.getSelect().addOptimizerHint("no_index(" + toTable.getAlias() + " cityobject_objectclass_fkx)"); - } - - private ValueReference getBoundedByProperty(Query query) throws QueryBuildException { - try { - FeatureType superType = schemaMapping.getCommonSuperType(query.getFeatureTypeFilter().getFeatureTypes()); - SchemaPath path = new SchemaPath(superType); - path.appendChild(superType.getProperty("boundedBy", GMLCoreModule.v3_1_1.getNamespaceURI(), true)); - return new ValueReference(path); - } catch (InvalidSchemaPathException e) { - throw new QueryBuildException(e.getMessage()); - } - } - - private Table getCityObjectTable(Query query, SQLQueryContext queryContext, boolean useLeftJoins) throws QueryBuildException { - SchemaPath schemaPath = getBoundedByProperty(query).getSchemaPath(); - schemaPathBuilder.addSchemaPath(schemaPath, queryContext, useLeftJoins); - return queryContext.getToTable(); - } + private final Query query; + private final SchemaPathBuilder schemaPathBuilder; + private final SchemaMapping schemaMapping; + private final AbstractDatabaseAdapter databaseAdapter; + private final String schemaName; + + protected SpatialOperatorBuilder(Query query, SchemaPathBuilder schemaPathBuilder, SchemaMapping schemaMapping, AbstractDatabaseAdapter databaseAdapter, String schemaName) { + this.query = query; + this.schemaPathBuilder = schemaPathBuilder; + this.schemaMapping = schemaMapping; + this.databaseAdapter = databaseAdapter; + this.schemaName = schemaName; + } + + protected void buildSpatialOperator(AbstractSpatialOperator operator, SQLQueryContext queryContext, boolean negate, boolean useLeftJoins) throws QueryBuildException { + switch (operator.getOperatorName()) { + case BBOX: + case EQUALS: + case DISJOINT: + case TOUCHES: + case WITHIN: + case OVERLAPS: + case INTERSECTS: + case CONTAINS: + buildBinaryOperator((BinarySpatialOperator) operator, queryContext, negate, useLeftJoins); + break; + case DWITHIN: + case BEYOND: + buildDistanceOperator((DistanceOperator) operator, queryContext, negate, useLeftJoins); + break; + } + + } + + private void buildBinaryOperator(BinarySpatialOperator operator, SQLQueryContext queryContext, boolean negate, boolean useLeftJoins) throws QueryBuildException { + if (!SpatialOperatorName.BINARY_SPATIAL_OPERATORS.contains(operator.getOperatorName())) + throw new QueryBuildException(operator.getOperatorName() + " is not a binary spatial operator."); + + ValueReference valueReference = null; + GeometryObject spatialDescription = operator.getSpatialDescription(); + + // we currently only support ValueReference as left operand + if (operator.getLeftOperand() != null && operator.getLeftOperand().getExpressionName() == ExpressionName.VALUE_REFERENCE) + valueReference = (ValueReference) operator.getLeftOperand(); + + if (valueReference == null && operator.getOperatorName() != SpatialOperatorName.BBOX) + throw new QueryBuildException("The spatial " + operator.getOperatorName() + " operator requires a ValueReference pointing to the geometry property to be tested."); + + if (spatialDescription == null) + throw new QueryBuildException("The spatial description may not be null."); + + if (valueReference == null) + valueReference = getBoundedByProperty(query); + + // transform coordinate values if required + DatabaseSrs targetSrs = databaseAdapter.getConnectionMetaData().getReferenceSystem(); + + if (spatialDescription.getSrid() != targetSrs.getSrid()) { + try { + spatialDescription = databaseAdapter.getUtil().transform(spatialDescription, targetSrs); + } catch (SQLException e) { + throw new QueryBuildException("Failed to transform coordinates of test geometry.", e); + } + } + + // build the value reference and spatial predicate + schemaPathBuilder.addSchemaPath(valueReference.getSchemaPath(), queryContext, useLeftJoins); + Table toTable = queryContext.getToTable(); + Column targetColumn = queryContext.getTargetColumn(); + + GeometryProperty property = (GeometryProperty) valueReference.getSchemaPath().getLastNode().getPathElement(); + if (property.isSetInlineColumn()) { + PredicateToken predicate = databaseAdapter.getSQLAdapter().getBinarySpatialPredicate(operator.getOperatorName(), queryContext.getTargetColumn(), spatialDescription, negate); + queryContext.addPredicate(predicate); + } else { + SpatialOperatorName operatorName = operator.getOperatorName(); + if (operatorName == SpatialOperatorName.CONTAINS || operatorName == SpatialOperatorName.EQUALS) + throw new QueryBuildException("The spatial " + operatorName + " operator is not supported for the geometry property '" + valueReference.getSchemaPath().getLastNode() + "'."); + + if (operatorName == SpatialOperatorName.DISJOINT) { + operatorName = SpatialOperatorName.INTERSECTS; + negate = !negate; + } + + GeometryObject bbox = spatialDescription.toEnvelope(); + boolean all = operatorName == SpatialOperatorName.WITHIN; + Table surfaceGeometry = new Table(MappingConstants.SURFACE_GEOMETRY, schemaName, schemaPathBuilder.getAliasGenerator()); + Table cityObject = getCityObjectTable(query, queryContext, useLeftJoins); + + Select inner = new Select() + .addProjection(surfaceGeometry.getColumn(MappingConstants.ID)) + .addSelection(ComparisonFactory.equalTo(surfaceGeometry.getColumn(MappingConstants.ROOT_ID), targetColumn)) + .addSelection(ComparisonFactory.isNotNull(surfaceGeometry.getColumn(MappingConstants.GEOMETRY))) + .addSelection(databaseAdapter.getSQLAdapter().getBinarySpatialPredicate(operatorName, surfaceGeometry.getColumn(MappingConstants.GEOMETRY), spatialDescription, all)); + + PredicateToken spatialPredicate = LogicalOperationFactory.AND( + databaseAdapter.getSQLAdapter().getBinarySpatialPredicate(SpatialOperatorName.BBOX, cityObject.getColumn(MappingConstants.ENVELOPE), bbox, false), + ComparisonFactory.exists(inner, all)); + + if (negate) + spatialPredicate = LogicalOperationFactory.NOT(spatialPredicate); + + queryContext.addPredicates(ComparisonFactory.isNotNull(targetColumn), spatialPredicate); + } + + // add optimizer hint if required + if (databaseAdapter.getSQLAdapter().spatialPredicateRequiresNoIndexHint() + && targetColumn.getName().equalsIgnoreCase(MappingConstants.ENVELOPE)) + queryContext.getSelect().addOptimizerHint("no_index(" + toTable.getAlias() + " cityobject_objectclass_fkx)"); + } + + private void buildDistanceOperator(DistanceOperator operator, SQLQueryContext queryContext, boolean negate, boolean useLeftJoins) throws QueryBuildException { + if (!SpatialOperatorName.DISTANCE_OPERATORS.contains(operator.getOperatorName())) + throw new QueryBuildException(operator.getOperatorName() + " is not a distance operator."); + + ValueReference valueReference; + GeometryObject spatialDescription = operator.getSpatialDescription(); + Distance distance = operator.getDistance(); + + // we currently only support ValueReference as left operand + if (operator.getLeftOperand() != null && operator.getLeftOperand().getExpressionName() == ExpressionName.VALUE_REFERENCE) + valueReference = (ValueReference) operator.getLeftOperand(); + else + valueReference = getBoundedByProperty(query); + + if (spatialDescription == null) + throw new QueryBuildException("The spatial description may not be null."); + + // transform coordinate values if required + DatabaseSrs targetSrs = databaseAdapter.getConnectionMetaData().getReferenceSystem(); + + if (spatialDescription.getSrid() != targetSrs.getSrid()) { + try { + spatialDescription = databaseAdapter.getUtil().transform(spatialDescription, targetSrs); + } catch (SQLException e) { + throw new QueryBuildException("Failed to transform coordinates of test geometry.", e); + } + } + + // convert distance value into unit of srs + Unit srsUnit; + DistanceUnit distanceUnit = distance.isSetUnit() ? distance.getUnit() : DistanceUnit.METER; + UnitConverter converter; + + try { + CoordinateReferenceSystem crs = databaseAdapter.getUtil().decodeDatabaseSrs(targetSrs); + srsUnit = CRSUtilities.getUnit(crs.getCoordinateSystem()); + } catch (FactoryException e) { + // assume meter per default + srsUnit = SI.METRE; + } + + try { + converter = Units.getConverterToAny(distanceUnit.toUnit(), srsUnit); + } catch (IllegalArgumentException e) { + throw new QueryBuildException("Cannot convert from the unit '" + distanceUnit + "' to the unit of the database SRS."); + } + + double value = converter.convert(distance.getValue()); + + // build the value reference and spatial predicate + schemaPathBuilder.addSchemaPath(valueReference.getSchemaPath(), queryContext, useLeftJoins); + Table toTable = queryContext.getToTable(); + Column targetColumn = queryContext.getTargetColumn(); + + GeometryProperty property = (GeometryProperty) valueReference.getSchemaPath().getLastNode().getPathElement(); + if (property.isSetInlineColumn()) { + PredicateToken predicate = databaseAdapter.getSQLAdapter().getDistancePredicate(operator.getOperatorName(), queryContext.getTargetColumn(), spatialDescription, value, negate); + queryContext.addPredicate(predicate); + } else { + SpatialOperatorName operatorName = operator.getOperatorName(); + if (operatorName == SpatialOperatorName.BEYOND) { + operatorName = SpatialOperatorName.DWITHIN; + negate = !negate; + } + + // get bbox of query geometry and buffer it by the distance + GeometryObject bbox = spatialDescription.toEnvelope(); + double[] coords = bbox.getCoordinates(0); + coords[0] -= value; + coords[1] -= value; + coords[bbox.getDimension()] += value; + coords[bbox.getDimension() + 1] += value; + + Table surfaceGeometry = new Table(MappingConstants.SURFACE_GEOMETRY, schemaName, schemaPathBuilder.getAliasGenerator()); + Table cityObject = getCityObjectTable(query, queryContext, useLeftJoins); + + Select inner = new Select() + .addProjection(surfaceGeometry.getColumn(MappingConstants.ID)) + .addSelection(ComparisonFactory.equalTo(surfaceGeometry.getColumn(MappingConstants.ROOT_ID), targetColumn)) + .addSelection(ComparisonFactory.isNotNull(surfaceGeometry.getColumn(MappingConstants.GEOMETRY))) + .addSelection(databaseAdapter.getSQLAdapter().getDistancePredicate(operatorName, surfaceGeometry.getColumn(MappingConstants.GEOMETRY), spatialDescription, value, false)); + + PredicateToken spatialPredicate = LogicalOperationFactory.AND( + databaseAdapter.getSQLAdapter().getBinarySpatialPredicate(SpatialOperatorName.BBOX, cityObject.getColumn(MappingConstants.ENVELOPE), bbox, false), + ComparisonFactory.exists(inner, false)); + + if (negate) + spatialPredicate = LogicalOperationFactory.NOT(spatialPredicate); + + queryContext.addPredicates(ComparisonFactory.isNotNull(targetColumn), spatialPredicate); + } + + // add optimizer hint if required + if (databaseAdapter.getSQLAdapter().spatialPredicateRequiresNoIndexHint() + && targetColumn.getName().equalsIgnoreCase(MappingConstants.ENVELOPE)) + queryContext.getSelect().addOptimizerHint("no_index(" + toTable.getAlias() + " cityobject_objectclass_fkx)"); + } + + private ValueReference getBoundedByProperty(Query query) throws QueryBuildException { + try { + FeatureType superType = schemaMapping.getCommonSuperType(query.getFeatureTypeFilter().getFeatureTypes()); + SchemaPath path = new SchemaPath(superType); + path.appendChild(superType.getProperty("boundedBy", GMLCoreModule.v3_1_1.getNamespaceURI(), true)); + return new ValueReference(path); + } catch (InvalidSchemaPathException e) { + throw new QueryBuildException(e.getMessage()); + } + } + + private Table getCityObjectTable(Query query, SQLQueryContext queryContext, boolean useLeftJoins) throws QueryBuildException { + SchemaPath schemaPath = getBoundedByProperty(query).getSchemaPath(); + schemaPathBuilder.addSchemaPath(schemaPath, queryContext, useLeftJoins); + return queryContext.getToTable(); + } }