Puh, habe heute einwenig gebraucht, bis auch der letzte Integrationstest lief. Das letzte Mal, als ich mit Postgresql und PostGIS gearbeitet habe, liegt nun drei Jahre her und mein Wissen ist nicht mehr so frisch. In diesem Projekt ist es nicht Ruby on Rails, sondern Hibernate und Grails.
Ich habe heute eine Funktion benötigt, die mir ausgehend von einem bestimmten Punkt, die nächst-mögliche Entität liefert. Dafür habe ich Postgresql aufgesetzt und meine Datenbanken mit den PostGIS Funktionen erweitert.
In Grails ist es erstaunlich einfach eine Domain Klasse um ein Attribut zu erweitern, das georeferenzierte Informationen speichert:
1 2 3 4 5 6 7 8 9 10 11 12 | import com.vividsolutions.jts.geom.Point import org.hibernatespatial.GeometryUserType class Station { Point location static mapping = { columns { location type: GeometryUserType } } } |
Damit die Imports aufgelöst werden können liegen im Classpath folgende Bibliotheken: (Versionen können abweichen)
* hibernate-spatial-1.0-M2.jar
* hibernate-spatial-postgis-1.0-M2.jar
* jts-1.8.jar
* postgis-2.0.0SVN.jar
* postgresql-8.4-701.jdbc4.jar
In der DataSource.groovy muss der Dialekt angepasst werden, wodurch eine menge cooler PostGIS Funktionen dazu kommen. Dazu später mehr.
1 2 3 4 5 6 7 8 | environments { test { dataSource { driverClassName = "org.postgresql.Driver" dialect = org.hibernatespatial.postgis.PostgisDialect } } } |
Jetzt geht es um die eigentliche Funktion. In meinem ersten Entwurf habe ich den Code so geschrieben, wie ich es von Grails gewohnt bin.
1 2 3 4 5 6 7 | class Station { Point location def static findNearest(Point point) { Station.executeQuery("from Station order by distance(location, :p)", [p: point], [max: 1]) } } |
Das hat nicht ganz geklappt und ich habe eine SQLException mit der Begründung You must specify a valid OGC WKT geometry type such as POINT, LINESTRING or POLYGON bekommen. Ich habe einiges ausprobiert, aber wirklich weit bin ich nicht gekommen. Aber wie heißt es schön: RTFM
Das Tutorial auf der Hibernate Spatial Seite brachte mich weiter. Im Abschnitt Spatial Queries ist ganz kurz beschrieben wie das Problem in HQL gelöst wird. Der Trick ist Hibernate mitzuteilen, dass der Wert des Referenzpunktes vom Typ GeometryUserType ist, wodurch Hibernate die Query sauber zusammenbauen kann:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Station { Point location def static findNearest(Point point) { Station.withSession {Session session -> final String q = "from Station order by distance(location, :point) asc" final Query query = session.createQuery(q) final Type geometryType = new CustomType(GeometryUserType.class, null); query.setParameter("point", point, geometryType) query.setMaxResults(1) return query.uniqueResult() } } } |
distance ist übrigens eine der vielen HQL Funktionen, die der PostGIS Dialekt mit sich bringt. In einem Post habe ich eine Liste weiterer Funktionen gefunden: dimension, srid, envelope, astext, asbinary, isempty, issimple, boundary, overlaps, intersects, equals, contains, crosses, disjoint, touches, within, relate, distance, buffer, convexhull, difference, intersection, symdifference, geomunion, extent

Bei mir führt ein Station.get(12345) schon zur Fehlermeldung.
PGobject cannot be cast to PGgeometry
Eine Idee?
Hallo Kay,
spontan nicht, aber wir kommen noch dahinter. Alle genannten Libs im Classpath? Mapping in der Domainklasse richtig? Ansonsten bräuchte ich mehr Infos. Stacktrace, Quellcode. Das wäre für den Anfang ganz gut.
Libs sind drin. Außer das ich diese Version habe: postgresql-9.0-801.jdbc4.jar. Speichern funktioniert auch ohne Probleme. Und über sessionFactory.getCurrentSession().createSQLQuery(…) kann ich die Daten auch auslesen. Aber Aufrufe über die Domainklasse, sowie hql gehen nicht.
StackTrace:
java.lang.ClassCastException: org.postgresql.util.PGobject cannot be cast to org.postgis.PGgeometry
at org.hibernatespatial.postgis.PGGeometryUserType.convert2JTS(PGGeometryUserType.java:75)
at org.hibernatespatial.AbstractDBGeometryType.nullSafeGet(AbstractDBGeometryType.java:127)
at org.hibernatespatial.GeometryUserType.nullSafeGet(GeometryUserType.java:170)
at de.test.TestController$_closure1.doCall(de.test.TestController:40)
at de.test.TestController$_closure1.doCall(de.test.TestController)
at java.lang.Thread.run(Thread.java:619)
Domainklasse:
package de.kaufda.domains
import com.vividsolutions.jts.geom.MultiPolygon
import org.hibernatespatial.GeometryUserType
class ZipPolygon {
String code
MultiPolygon geog
static mapping = {
columns {
geog type: GeometryUserType
}
}
static hasMany = [
zips: Zip
]
static belongsTo = [Zip]
}
Leider bekomme ich den Fehler nicht reproduziert. Ich habe folgende Maven Abhängigkeiten definiert:
+- com.vividsolutions:jts:jar:1.11:compile
| \- xerces:xercesImpl:jar:2.4.0:compile
+- org.hibernatespatial:hibernate-spatial-postgis:jar:1.0:compile
| \- org.hibernatespatial:hibernate-spatial:jar:1.0:compile
+- org.postgis:postgis-jdbc:jar:1.3.3:compile
| \- org.postgis:postgis-stubs:jar:1.3.3:compile
\- postgresql:postgresql:jar:9.0-801.jdbc4:compile
Ich schicke dir das Projekt gleich per Mail. Im test/integration Order ist ein Test: ZipPolygonTests. Schaumal, ob du ihn ausgeführt bekommst. (die DataSource.groovy noch anpassen)
Ich habe gegen einen Postgresql 9.0.1 Datenbankserver mit Postgis 1.5.2 unter FreeBSD getestet.
Vielen Dank.
Ich sag bescheid, ob es geklappt hat.
i get this exception on the following line:
final Type geometryType = new CustomType(GeometryUserType.class, null);
org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object ‘org.hibernate.type.CustomType@5995d03f’ with class ‘org.hibernate.type.CustomType’ to class ‘com.vividsolutions.jts.geom.PrecisionModel$Type’
Hi Iskandar,
maybe it is an import problem. Should be org.hibernate.type.Type;
Greetings,
Sergej