diff --git a/hibernate-spatial/hibernate-spatial.gradle b/hibernate-spatial/hibernate-spatial.gradle index 573d7f6839..062657f701 100644 --- a/hibernate-spatial/hibernate-spatial.gradle +++ b/hibernate-spatial/hibernate-spatial.gradle @@ -26,6 +26,7 @@ dependencies { testCompile(libraries.junit) testCompile(project(':hibernate-testing')) testCompile([group: 'commons-dbcp', name: 'commons-dbcp', version: '1.4']) + testCompile( project( path: ':hibernate-core', configuration: 'tests' ) ) testCompile(libraries.validation) testCompile(libraries.jandex) testCompile(libraries.classmate) diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/JTSGeometryJavaTypeDescriptor.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/JTSGeometryJavaTypeDescriptor.java index 0ffcde4699..535c6fc8e2 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/JTSGeometryJavaTypeDescriptor.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/JTSGeometryJavaTypeDescriptor.java @@ -9,6 +9,7 @@ package org.hibernate.spatial; import java.util.Locale; +import org.hibernate.spatial.jts.JTSUtils; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.AbstractTypeDescriptor; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; @@ -54,6 +55,11 @@ public class JTSGeometryJavaTypeDescriptor extends AbstractTypeDescriptor X unwrap(Geometry value, Class type, WrapperOptions options) { if ( value == null ) { diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/JTSGeometryType.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/JTSGeometryType.java index 8136c455b1..4eaedf43e0 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/JTSGeometryType.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/JTSGeometryType.java @@ -48,4 +48,5 @@ public class JTSGeometryType extends AbstractSingleColumnStandardBasicType. + */ + +package org.hibernate.spatial.jts; + + +//Note that this Utility class will be available directly from +// geolatte-geom 1.9 + + +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.LineString; +import org.locationtech.jts.geom.Point; +import org.locationtech.jts.geom.Polygon; + +/** + * Some utility functions for working with JTS geometries + */ +public class JTSUtils { + + private JTSUtils() { + } + + /** + * Determines equality between geometries taking into + * account all coordinates, and the SRID. + *

+ * This is used e.g. for Dirty-checking of geometry values + * in Hibernate + * + * @param g1 + * @param g2 + * + * @return + */ + public static boolean equalsExact3D(Geometry g1, Geometry g2) { + if ( g1 == g2 ) { + return true; + } + if ( g1 == null || g2 == null ) { + return false; + } + if ( !g1.getGeometryType().equals( g2.getGeometryType() ) ) { + return false; + } + if ( g1.getSRID() != g2.getSRID() ) { + return false; + } + + //empty geometries of the same type are the same + if ( g1.isEmpty() && g2.isEmpty() ) { + return true; + } + + int ng1 = g1.getNumGeometries(); + int ng2 = g2.getNumGeometries(); + if ( ng1 != ng2 ) { + return false; + } + + if ( ng1 == 1 ) { + return equals3DPrimitiveGeometries( g1, g2 ); + } + + return equalComponentGeometries( g1, g2, ng1 ); + } + + private static boolean equalComponentGeometries(Geometry g1, Geometry g2, int ng1) { + for ( int gIdx = 0; gIdx < ng1; gIdx++ ) { + if ( !equalsExact3D( g1.getGeometryN( gIdx ), g2.getGeometryN( gIdx ) ) ) { + return false; + } + } + return true; + } + + public static boolean equals3D(Coordinate c1, Coordinate c2) { + return c1.x == c2.x && c1.y == c2.y && + ( ( Double.isNaN( c1.z ) && Double.isNaN( c2.z ) ) || c1.z == c2.z ) && + ( ( Double.isNaN( c1.getM() ) && Double.isNaN( c2.getM() ) ) || c1.getM() == c2.getM() ); + } + + private static boolean equalLineStringCoordinates(LineString g1, LineString g2) { + int np1 = g1.getNumPoints(); + int np2 = g2.getNumPoints(); + if ( np1 != np2 ) { + return false; + } + for ( int i = 0; i < np1; i++ ) { + if ( !equalsExact3D( g1.getPointN( i ), g2.getPointN( i ) ) ) { + return false; + } + } + return true; + } + + private static boolean equalPolygonCoordinates(Polygon g1, Polygon g2) { + int nr1 = g1.getNumInteriorRing(); + int nr2 = g2.getNumInteriorRing(); + if ( nr1 != nr2 ) { + return false; + } + for ( int i = 0; i < nr1; i++ ) { + if ( !equalLineStringCoordinates( g1.getInteriorRingN( i ), g2.getInteriorRingN( i ) ) ) { + return false; + } + } + return equalLineStringCoordinates( g1.getExteriorRing(), g2.getExteriorRing() ); + } + + private static boolean equals3DPrimitiveGeometries(Geometry g1, Geometry g2) { + //this method assumes that g1 and g2 are of the same type + assert ( g1.getClass().equals( g2.getClass() ) ); + if ( g1 instanceof Point ) { + return equals3D( g1.getCoordinate(), g2.getCoordinate() ); + } + + if ( g1 instanceof LineString ) { + return equalLineStringCoordinates( (LineString) g1, (LineString) g2 ); + } + + if ( g1 instanceof Polygon ) { + return equalPolygonCoordinates( (Polygon) g1, (Polygon) g2 ); + } + throw new IllegalStateException( "Only simple geometries should be used" ); + } +} diff --git a/hibernate-spatial/src/test/java/org/hibernate/spatial/integration/jts/hhh14523/DirtyCheckingTest.java b/hibernate-spatial/src/test/java/org/hibernate/spatial/integration/jts/hhh14523/DirtyCheckingTest.java new file mode 100644 index 0000000000..0516ecb738 --- /dev/null +++ b/hibernate-spatial/src/test/java/org/hibernate/spatial/integration/jts/hhh14523/DirtyCheckingTest.java @@ -0,0 +1,140 @@ +/* + * 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 . + */ + +package org.hibernate.spatial.integration.jts.hhh14523; + +import java.io.Serializable; +import java.util.List; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EntityManager; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Query; +import javax.persistence.SequenceGenerator; +import javax.persistence.Table; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.spatial.dialect.postgis.PostgisPG95Dialect; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import org.geolatte.geom.codec.Wkt; +import org.geolatte.geom.jts.JTS; +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.Point; + +import static org.junit.Assert.assertEquals; + +@TestForIssue(jiraKey = "HHH-14523") +@RequiresDialect(PostgisPG95Dialect.class) +public class DirtyCheckingTest extends BaseEntityManagerFunctionalTestCase { + + private GeometryFactory gfact = new GeometryFactory(); + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { + TestEntity.class + }; + } + + public void createtestEntity() { + Point pnt = (Point) JTS.to( Wkt.fromWkt( "POINT Z( 3.41127795 8.11062269 2.611)", Wkt.Dialect.SFA_1_2_1 ) ); + EntityManager entityManager = createEntityManager(); + TestEntity test1 = new TestEntity( "radar 5", pnt ); + + entityManager.getTransaction().begin(); + entityManager.persist( test1 ); + entityManager.getTransaction().commit(); + + entityManager.close(); + } + + // Entities are auto-discovered, so just add them anywhere on class-path + // Add your tests, using standard JUnit. + @Test + public void hhh14523() throws Exception { + + createtestEntity(); + + EntityManager entityManager = createEntityManager(); + entityManager.getTransaction().begin(); + Query query = entityManager.createQuery( "select t from TestEntity t" ); + TestEntity ent = (TestEntity) query.getResultList().get( 0 ); + Point newPnt = (Point) JTS.to( Wkt.fromWkt( "POINT Z( 3.41127795 8.11062269 8.611)", Wkt.Dialect.SFA_1_2_1 ) ); + ent.setGeom( newPnt ); + entityManager.getTransaction().commit(); + entityManager.close(); + + + entityManager = createEntityManager(); + entityManager.getTransaction().begin(); + List entities = entityManager.createQuery( "select t from TestEntity t" ).getResultList(); + TestEntity ent2 = entities.get( 0 ); + try { + assertEquals( 8.611, ent2.getGeom().getCoordinate().getZ(), 0.00001 ); + } + finally { + entityManager.getTransaction().commit(); + } + entityManager.close(); + } +} + +@Entity +@Table(name = "test") +@SequenceGenerator(name = "test_id_seq", sequenceName = "test_id_seq", allocationSize = 1) +class TestEntity implements Serializable { + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "test_id_seq") + @Column(name = "id") + private Long id; + + @Column(name = "uid", unique = true) + private String uid; + + @Column(name = "geom") + private Point geom; + + public TestEntity() { + } + + public TestEntity(String uid, Point geom) { + this.uid = uid; + this.geom = geom; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getUid() { + return uid; + } + + public void setUid(String uid) { + this.uid = uid; + } + + public Point getGeom() { + return geom; + } + + public void setGeom(Point geom) { + this.geom = geom; + } +} diff --git a/hibernate-spatial/src/test/java/org/hibernate/spatial/integration/jts/hhh14523/package-info.java b/hibernate-spatial/src/test/java/org/hibernate/spatial/integration/jts/hhh14523/package-info.java new file mode 100644 index 0000000000..939f83ef7f --- /dev/null +++ b/hibernate-spatial/src/test/java/org/hibernate/spatial/integration/jts/hhh14523/package-info.java @@ -0,0 +1,9 @@ +/* + * 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 . + */ + +//test case for bug HHH-14523 +package org.hibernate.spatial.integration.jts.hhh14523; \ No newline at end of file