MySQL Spatial et JDBC
MySQL possède quelques fonctionnalités géographiques: un type de données “Geometry”, quelques fonctions sur ce type et un type d’index spatial (R-Tree). La version 5.6 apporte un lot de fonctions supplémentaires dont l’objectif est le support de la norme OpenGIS For SQL.
Comment manipuler ce type de données SQL en Java? Contrairement à PostgreSQL ou Oracle, pour lesquelles les drivers JDBC contiennent des objets PGgeometry
ou JGeometry
, MySQL ne propose rien.
Pour palier à ce manque, on peut s’appuyer sur JTS (Java Topology Suite). JTS contient les classes nécessaires pour décrire des points, lignes brisées et surfaces.
Nous verrons dans cet article comment lire/écrire des géométries depuis du code Java.
Préparation du modèle
Pour commencer, on crée une table avec une colonne de type GEOMETRY
:
create table VILLE ( ID_VILLE int not null primary key, GEOM geometry not null ) ENGINE=MyISAM;
Seul le storage engine MyISAM supporte les index spatiaux; InnoDB supporte seulement les colonnes géométriques. On est donc contraint de faire un choix: InnoDB ou index spatiaux.
create spatial index IDX_VILLE_GEOM on VILLE(GEOM);
Une fois la table et l’index spatial créés voyons comment écrire dedans.
Conversion WKB/WKT
La norme OpenGIS SQL définit 2 formats pour décrire et échanger des géométries: un format textuel, le WKT (Well Known Text), et un format binaire, le WKB (Well Known Binary).
La première possibilité est d’utiliser le format WKT comme format pivot entre le monde Java et MySQL. Pour une écriture:
insert into VILLE(ID_VILLE, GEOM) values (?, GeomFromText(?)); Geometry geometry = new Point(...); String wkt = new WKTWriter().write(geometry); preparedStatement.setString(2, wkt);
On procède de même pour une lecture:
select AsWKT(GEOM) from VILLE where ID_VILLE=? String wkt = resultSet.getString(1); Geometry geometry = new WKTReader().read(wkt);
On peut appliquer cette même stratégie avec WKB:
En SQL: GeomFromText
et AsWKT
deviennent GeomFromBinary
et AsWKB
En Java: WKTReader
et WKTWriter
deviennent WKBReader
et WKBWriter
Format interne
Une autre stratégie est de passer directement par le format interne MySQL. On manipule alors les colonnes de type GEOMETRY comme des BLOBs. La structure binaire de ce type de colonne est:
- 4 octets (soit un entier 32 bits): le SRID (référentiel spatial dans lequel sont exprimées les coordonnées)
- Tout le reste du flux: la géometrie au format binaire WKB
Du coup pour les écritures:
insert into VILLE(ID_VILLE, GEOM) values (?, ?); int byteOrder = ByteOrderValues.LITTLE_ENDIAN; // SRID ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); byte[] sridBytes = new byte[4]; ByteOrderValues.putInt(userObject.getSRID(), sridBytes, byteOrder); outputStream.write(sridBytes); // Geometry WKBWriter wkbWriter = new WKBWriter(2, byteOrder); wkbWriter.write(geometry, new OutputStreamOutStream(outputStream)); byte[] bytes = outputStream.toByteArray(); preparedStatement.setBytes(2, bytes);
Et pour les lectures:
select GEOM from VILLE where ID_VILLE=? int byteOrder = ByteOrderValues.LITTLE_ENDIAN; byte[] bytes = resultSet.getBytes(1); ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes); // SRID byte[] sridBytes = new byte[4]; inputStream.read(sridBytes); int srid = ByteOrderValues.getInt(sridBytes, byteOrder); // Geometry GeometryFactory geometryFactory = new GeometryFactory(precisionModel, srid, coordinateSequenceFactory); WKBReader wkbReader = new WKBReader(geometryFactory); Geometry geometry = wkbReader.read(new InputStreamInStream(inputStream));
Le code Java de cette variante est plus compliqué, mais on s’évite les conversions dans le code SQL. Correctement enveloppé dans notre framework JDBC favori (TypeHandler
dans MyBatis, Converter
dans jOOQ, DataType
dans DBUnit), cette stratégie simplifie le SQL.
Les heureux utilisateurs d’Hibernate n’auront rien à faire, puisque Hibernate Spatial fait déjà tout le boulot (en utilisant le format interne).