HHH-15160 - Adds the Postgis distance operators
This commit is contained in:
parent
6746c30275
commit
2a1aa73319
|
@ -62,10 +62,9 @@ For Maven, you need to add the following dependency:
|
|||
====
|
||||
|
||||
|
||||
Not all databases support all the functions defined by Hibernate Spatial.
|
||||
The table below provides an overview of the functions provided by each database. If the function is defined in the
|
||||
https://portal.opengeospatial.org/files/?artifact_id=829[Simple Feature Specification], the description references the
|
||||
relevant section.
|
||||
Hibernate defines common spatial functions uniformly over all databases. These
|
||||
functions largely correspond to those specified in the https://portal.opengeospatial.org/files/?artifact_id=829[Simple Feature Specification]. Not all databases are capable of supporting every function, however. The table below details which functions are supported by various database systems.
|
||||
|
||||
|
||||
:yes: icon:check[role="green"]
|
||||
:no: icon:times[role="red"]
|
||||
|
@ -110,13 +109,31 @@ relevant section.
|
|||
|================================
|
||||
^(1)^ Argument Geometries need to have the same dimensionality.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
In previous versions Hibernate Spatial registered the SFS spatial functions under names without the "st_" prefix. Starting
|
||||
from Hibernate 6.0, the functions are registered both with and without the prefix. So, e.g., both `st_dimension(geom)` and
|
||||
`dimension(geom)` will work.
|
||||
====
|
||||
Note that beyond the common spatial functions mentioned above, Hibernate may define additional spatial functions for each database dialect. These will be documented in the
|
||||
Database notes below.
|
||||
|
||||
=== Database notes
|
||||
[[spatial-configuration-dialect-postgresql]]
|
||||
Postgresql::
|
||||
|
||||
The Postgresql dialect has support for the https://postgis.net/[Postgis spatial extension], but not the Geometric types mentioned in the
|
||||
https://www.postgresql.org/docs/current/datatype-geometric.html[Postgresql documentation].
|
||||
|
||||
In addition tot he common spatial functions, the following functions are supported:
|
||||
|
||||
|
||||
.Additional Postgis function support
|
||||
|
||||
|===
|
||||
| Function | Purpose | Syntax | Postgis function operator
|
||||
|`distance_2d` | 2D distance between two geometries|`distance_2d(geom,geom)`| https://postgis.net/docs/manual-3.3/geometry_distance_knn.html[\<\->]
|
||||
|`distance_2d_bbox` | 2D distance between the bounding boxes of tow geometries|`distance_2d_bbox(geom,geom)`| https://postgis.net/docs/manual-3.3/geometry_distance_box.html[<#>]
|
||||
|`distance_cpa` | 3D distance between 2 trajectories|`distance_cpa(geom,geom)`| https://postgis.net/docs/manual-3.3/geometry_distance_cpa.html[\|=\|]
|
||||
|`distance_centroid_nd` | the n-D distance between the centroids of the bounding boxes of two geometries|`distance_centroid_nd(geom,geom)`| https://postgis.net/docs/manual-3.3/geometry_distance_centroid_nd.html[<\<\->>]
|
||||
|
||||
|===
|
||||
|
||||
|
||||
|
||||
[[spatial-configuration-dialect-mysql]]
|
||||
MySQL::
|
||||
|
@ -202,10 +219,6 @@ create transform for db2gse.st_geometry db2_program (
|
|||
[[spatial-types]]
|
||||
=== Types
|
||||
|
||||
Hibernate Spatial comes with the following types:
|
||||
|
||||
TODO
|
||||
|
||||
It suffices to declare a property as either a JTS or a Geolatte-geom `Geometry` and Hibernate Spatial will map it using the
|
||||
relevant type.
|
||||
|
||||
|
|
|
@ -7,22 +7,78 @@
|
|||
|
||||
package org.hibernate.spatial.dialect.postgis;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.boot.model.FunctionContributions;
|
||||
import org.hibernate.query.sqm.function.NamedSqmFunctionDescriptor;
|
||||
import org.hibernate.query.sqm.produce.function.ArgumentsValidator;
|
||||
import org.hibernate.query.sqm.produce.function.FunctionReturnTypeResolver;
|
||||
import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators;
|
||||
import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
|
||||
import org.hibernate.spatial.BaseSqmFunctionDescriptors;
|
||||
import org.hibernate.spatial.CommonSpatialFunction;
|
||||
import org.hibernate.spatial.FunctionKey;
|
||||
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
|
||||
import org.hibernate.sql.ast.SqlAstTranslator;
|
||||
import org.hibernate.sql.ast.spi.SqlAppender;
|
||||
import org.hibernate.sql.ast.tree.SqlAstNode;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.type.BasicTypeRegistry;
|
||||
import org.hibernate.type.StandardBasicTypes;
|
||||
|
||||
import static org.hibernate.query.sqm.produce.function.StandardArgumentsValidators.exactly;
|
||||
|
||||
public class PostgisSqmFunctionDescriptors extends BaseSqmFunctionDescriptors {
|
||||
|
||||
private final BasicTypeRegistry typeRegistry;
|
||||
|
||||
public PostgisSqmFunctionDescriptors(FunctionContributions functionContributions) {
|
||||
super( functionContributions );
|
||||
typeRegistry = functionContributions.getTypeConfiguration().getBasicTypeRegistry();
|
||||
addOperator("distance_2d", "<->");
|
||||
addOperator("distance_2d_bbox", "<#>");
|
||||
addOperator("distance_cpa", "|=|");
|
||||
addOperator( "distance_centroid_nd", "<<->>" );
|
||||
// <<#>> operator is apparently no longer supported?
|
||||
//addOperator( "distance_nd_bbox", "<<#>>" );
|
||||
}
|
||||
|
||||
protected void addOperator(String name, String operator) {
|
||||
map.put(
|
||||
FunctionKey.apply( name ),
|
||||
new PostgisOperator(
|
||||
name,
|
||||
operator,
|
||||
exactly( 2 ),
|
||||
StandardFunctionReturnTypeResolvers.invariant( typeRegistry.resolve(
|
||||
StandardBasicTypes.DOUBLE )
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
static class PostgisOperator extends NamedSqmFunctionDescriptor {
|
||||
final private String operator;
|
||||
|
||||
public PostgisOperator(
|
||||
String name,
|
||||
String op,
|
||||
ArgumentsValidator validator,
|
||||
FunctionReturnTypeResolver returnTypeResolver) {
|
||||
super( name, false, validator, returnTypeResolver );
|
||||
this.operator = op;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(
|
||||
SqlAppender sqlAppender,
|
||||
List<? extends SqlAstNode> sqlAstArguments,
|
||||
SqlAstTranslator<?> walker) {
|
||||
sqlAppender.appendSql( '(' );
|
||||
final Expression arg1 = (Expression) sqlAstArguments.get( 0 );
|
||||
walker.render( arg1, SqlAstNodeRenderingMode.DEFAULT );
|
||||
sqlAppender.appendSql( operator );
|
||||
final Expression arg2 = (Expression) sqlAstArguments.get( 1 );
|
||||
walker.render( arg2, SqlAstNodeRenderingMode.DEFAULT );
|
||||
sqlAppender.appendSql( ')' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
|
||||
package org.hibernate.spatial.dialect.postgis;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.dialect.PostgreSQLDialect;
|
||||
|
||||
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.RequiresDialect;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.TypedQuery;
|
||||
import org.geolatte.geom.C2D;
|
||||
import org.geolatte.geom.Point;
|
||||
import org.geolatte.geom.crs.CoordinateReferenceSystem;
|
||||
import org.geolatte.geom.crs.CoordinateReferenceSystems;
|
||||
|
||||
import static org.geolatte.geom.builder.DSL.c;
|
||||
import static org.geolatte.geom.builder.DSL.point;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* Tests for the Postgis KNN distance functions (corresponding to the <-> and <<->> operators).
|
||||
*/
|
||||
@RequiresDialect(PostgreSQLDialect.class)
|
||||
@DomainModel(annotatedClasses = { PostgisDistanceOperatorsTest.Neighbor.class })
|
||||
@SessionFactory(useCollectingStatementInspector = true)
|
||||
public class PostgisDistanceOperatorsTest {
|
||||
public static CoordinateReferenceSystem<C2D> crs = CoordinateReferenceSystems.PROJECTED_2D_METER;
|
||||
|
||||
private final Point<C2D> searchPoint = point( crs, c( 0.0, 0.0 ) );
|
||||
|
||||
@BeforeEach
|
||||
public void setUp(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
for ( int i = 10; i > 0; i-- ) {
|
||||
Neighbor neighbor = Neighbor.from( point( crs, c( i, 0.0 ) ), i );
|
||||
session.persist( neighbor );
|
||||
}
|
||||
session.flush();
|
||||
session.clear();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDistance2D(SessionFactoryScope scope) {
|
||||
SQLStatementInspector inspector = scope.getCollectingStatementInspector();
|
||||
inspector.clear();
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
TypedQuery<Neighbor> query = session.createQuery(
|
||||
"select n from Neighbor n order by distance_2d(n.point, :pnt )", Neighbor.class )
|
||||
.setParameter( "pnt", searchPoint );
|
||||
List<Neighbor> results = query.getResultList();
|
||||
assertFalse( results.isEmpty() );
|
||||
String sql = inspector.getSqlQueries().get( 0 );
|
||||
assertTrue(sql.matches(".*order by.*point\\w*<->.*"), "<-> operator is not rendered correctly");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDistance2DBBox(SessionFactoryScope scope) {
|
||||
SQLStatementInspector inspector = scope.getCollectingStatementInspector();
|
||||
inspector.clear();
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
TypedQuery<Neighbor> query = session.createQuery(
|
||||
"select n from Neighbor n order by distance_2d_bbox(n.point, :pnt )", Neighbor.class )
|
||||
.setParameter( "pnt", searchPoint );
|
||||
List<Neighbor> results = query.getResultList();
|
||||
assertFalse( results.isEmpty() );
|
||||
String sql = inspector.getSqlQueries().get( 0 );
|
||||
assertTrue(sql.matches(".*order by.*point\\w*<#>.*"), "<#> operator is not rendered correctly");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDistanceNDBBox(SessionFactoryScope scope) {
|
||||
SQLStatementInspector inspector = scope.getCollectingStatementInspector();
|
||||
inspector.clear();
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
TypedQuery<Neighbor> query = session.createQuery(
|
||||
"select n from Neighbor n order by distance_centroid_nd(n.point, :pnt )", Neighbor.class )
|
||||
.setParameter( "pnt", searchPoint );
|
||||
List<Neighbor> results = query.getResultList();
|
||||
assertFalse( results.isEmpty() );
|
||||
String sql = inspector.getSqlQueries().get( 0 );
|
||||
assertTrue(sql.matches(".*order by.*point\\w*<<->>.*"), "<<->>> operator is not rendered correctly");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void cleanUp(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
session.createMutationQuery( "delete from Neighbor" );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Entity(name = "Neighbor")
|
||||
@Table(name = "neighbor")
|
||||
public static class Neighbor {
|
||||
|
||||
static Neighbor from(Point<C2D> pnt, Integer i) {
|
||||
Neighbor res = new Neighbor();
|
||||
res.point = pnt;
|
||||
res.num = i;
|
||||
return res;
|
||||
}
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private Integer num;
|
||||
|
||||
Point<C2D> point;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue