Tags:,,, Posted in Grails:-)7 Kommentare

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

Februar 23, 2010