Skip to content

Commit

Permalink
Add JTS PostGIS data type support
Browse files Browse the repository at this point in the history
Support for Postgres PostGIS extension.

Co-Authored-By: Henning Schmiedehausen <henning@schmiedehausen.org>
  • Loading branch information
bchapuis and hgschmie committed Jul 24, 2022
1 parent ef3b9eb commit d06f565
Show file tree
Hide file tree
Showing 5 changed files with 308 additions and 0 deletions.
7 changes: 7 additions & 0 deletions internal/build/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
<dep.jdbi-policy.version>3.31.1-SNAPSHOT</dep.jdbi-policy.version>
<dep.jetbrainsAnnotations.version>21.0.1</dep.jetbrainsAnnotations.version>
<dep.joda-time.version>2.9.9</dep.joda-time.version>
<dep.jts.version>1.19.0</dep.jts.version>
<dep.junit5.version>5.8.2</dep.junit5.version>
<dep.kotlin.version>1.7.10</dep.kotlin.version>
<dep.mockito.version>4.2.0</dep.mockito.version>
Expand Down Expand Up @@ -226,6 +227,12 @@
</exclusions>
</dependency>

<dependency>
<groupId>org.locationtech.jts</groupId>
<artifactId>jts-core</artifactId>
<version>${dep.jts.version}</version>
</dependency>

<dependency>
<groupId>com.opentable.components</groupId>
<artifactId>otj-pg-embedded</artifactId>
Expand Down
12 changes: 12 additions & 0 deletions postgres/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@
<artifactId>spotbugs-annotations</artifactId>
</dependency>

<dependency>
<groupId>org.locationtech.jts</groupId>
<artifactId>jts-core</artifactId>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
Expand Down Expand Up @@ -93,6 +99,12 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.opentable.components</groupId>
<artifactId>otj-pg-embedded</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jdbi.v3.postgres.postgis;

import java.util.function.Function;

import org.jdbi.v3.core.argument.Argument;
import org.jdbi.v3.core.codec.Codec;
import org.jdbi.v3.core.mapper.ColumnMapper;
import org.jdbi.v3.meta.Alpha;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKBReader;
import org.locationtech.jts.io.WKBWriter;

import static org.locationtech.jts.io.WKBConstants.wkbNDR;

@Alpha
final class PostgisCodec implements Codec<Geometry> {

@Override
public ColumnMapper<Geometry> getColumnMapper() {
return (resultSet, index, context) -> {
byte[] bytes = hexStringToByteArray(resultSet.getString(index));
return (Geometry) deserialize(bytes);
};
}

@Override
public Function<Geometry, Argument> getArgumentFunction() {
return data -> (position, statement, context) -> statement.setBytes(position, serialize(data));
}

/**
* Serializes a geometry in the WKB format.
*
* @param geometry
* @return
*/
private static byte[] serialize(Geometry geometry) {
if (geometry == null) {
return null;
}
WKBWriter writer = new WKBWriter(2, wkbNDR, true);
return writer.write(geometry);
}

/**
* Deserializes a geometry in the WKB format.
*
* @param wkb
* @return
*/
private static Geometry deserialize(byte[] wkb) {
if (wkb == null) {
return null;
}
try {
WKBReader reader = new WKBReader(new GeometryFactory());
return reader.read(wkb);
} catch (ParseException e) {
throw new IllegalArgumentException(e);
}
}

private static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] =
(byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
}
return data;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.jdbi.v3.postgres.postgis;

import org.jdbi.v3.core.Jdbi;
import org.jdbi.v3.core.codec.Codec;
import org.jdbi.v3.core.codec.CodecFactory;
import org.jdbi.v3.core.spi.JdbiPlugin;
import org.jdbi.v3.meta.Alpha;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.LinearRing;
import org.locationtech.jts.geom.MultiLineString;
import org.locationtech.jts.geom.MultiPoint;
import org.locationtech.jts.geom.MultiPolygon;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;

/**
* Postgis plugin. Adds support for binding and mapping the following data types:
*
* <ul>
* <li>{@link org.locationtech.jts.geom.Point}</li>
* <li>{@link org.locationtech.jts.geom.LineString}</li>
* <li>{@link org.locationtech.jts.geom.LinearRing}</li>
* <li>{@link org.locationtech.jts.geom.Polygon}</li>
* <li>{@link org.locationtech.jts.geom.MultiPoint}</li>
* <li>{@link org.locationtech.jts.geom.MultiLineString}</li>
* <li>{@link org.locationtech.jts.geom.MultiPolygon}</li>
* <li>{@link org.locationtech.jts.geom.GeometryCollection}</li>
* <li>{@link org.locationtech.jts.geom.Geometry}</li>
* </ul>
*/
@Alpha
public class PostgisPlugin extends JdbiPlugin.Singleton {

@Override
public void customizeJdbi(Jdbi jdbi) {
final Codec<Geometry> codec = new PostgisCodec();

jdbi.registerCodecFactory(CodecFactory.builder()
.addCodec(Geometry.class, codec)
.addCodec(GeometryCollection.class, codec)
.addCodec(LinearRing.class, codec)
.addCodec(LineString.class, codec)
.addCodec(MultiLineString.class, codec)
.addCodec(MultiPoint.class, codec)
.addCodec(MultiPolygon.class, codec)
.addCodec(Point.class, codec)
.addCodec(Polygon.class, codec)
.build());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.jdbi.v3.postgres.postgis;

import java.util.List;

import org.jdbi.v3.core.Handle;
import org.jdbi.v3.postgres.PostgresPlugin;
import org.jdbi.v3.sqlobject.SqlObjectPlugin;
import org.jdbi.v3.testing.junit5.JdbiExtension;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;

import static org.junit.jupiter.api.Assertions.assertEquals;

class PostgisPluginTest {

static {
if (System.getProperty("PG_FULL_IMAGE") == null) {
System.setProperty("PG_FULL_IMAGE", "postgis/postgis:13-3.2-alpine");
}
}

@RegisterExtension
public JdbiExtension pgExtension = JdbiExtension.otjEmbeddedPostgres()
.withPlugins(new SqlObjectPlugin(), new PostgresPlugin(), new PostgisPlugin())
.withInitializer((ds, h) -> {
h.execute("CREATE TABLE record (id INTEGER PRIMARY KEY, point geometry(point), linestring geometry(linestring), polygon geometry(polygon))");
});

private Handle handle;

@BeforeEach
public void before() {
this.handle = pgExtension.openHandle();
}

@Test
void postgisSmokeTest() {
PostgisRecord record = new PostgisRecord();
record.setId(1);
record.setPoint(new GeometryFactory().createPoint(new Coordinate(1, 1)));
record.setLineString(
new GeometryFactory()
.createLineString(
new Coordinate[]{
new Coordinate(1, 1),
new Coordinate(1, 2),
new Coordinate(2, 2),
new Coordinate(2, 1)
}));
record.setPolygon(
new GeometryFactory()
.createPolygon(
new Coordinate[]{
new Coordinate(1, 1),
new Coordinate(1, 2),
new Coordinate(2, 2),
new Coordinate(2, 1),
new Coordinate(1, 1),
}));

handle.createUpdate(
"INSERT INTO record (id, point, linestring, polygon) VALUES (:id, :point, :lineString, :polygon)")
.bindBean(record)
.execute();

List<PostgisRecord> result = handle
.createQuery("SELECT * FROM record ORDER BY id")
.mapToBean(PostgisRecord.class)
.list();

assertEquals(record.getPoint(), result.get(0).getPoint());
assertEquals(record.getLineString(), result.get(0).getLineString());
assertEquals(record.getPolygon(), result.get(0).getPolygon());
}

public static final class PostgisRecord {

private Integer id;
private Point point;
private LineString lineString;
private Polygon polygon;

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public Point getPoint() {
return point;
}

public void setPoint(Point point) {
this.point = point;
}

public LineString getLineString() {
return lineString;
}

public void setLineString(LineString lineString) {
this.lineString = lineString;
}

public Polygon getPolygon() {
return polygon;
}

public void setPolygon(Polygon polygon) {
this.polygon = polygon;
}
}

}

0 comments on commit d06f565

Please sign in to comment.