25

In my PostgreSQL 9.3 + PostGIS 2.1.5 I have a table PLACE with a column coordinates of type Geometry(Point,26910).

I want to map it to Place entity in my Spring Boot 1.1.9 web application, which uses Hibernate 4.0.0 + . Place is available with a REST repository.

Unfortunately when I GET http://localhost:8080/mywebapp/places I receive this strange JSON response:

{

  "_embedded" : {

    "venues" : [ {

      "id" : 1,

      "coordinates" : {

        "envelope" : {

          "envelope" : {

            "envelope" : {

              "envelope" : {

                "envelope" : {

                  "envelope" : {

                    "envelope" : {

                      "envelope" : {

                        "envelope" : {

                          "envelope" : {

                            "envelope" : {

                              "envelope" : {

                                "envelope" : {

                                  "envelope" : {

                                    "envelope" : {

                                      "envelope" : {

                                        "envelope" : {

                                          "envelope" : {

                                            "envelope" : {

and so on indefinetely...! Spring log doesn't help..

I'm working with this application.properties:

spring.jpa.database-platform=org.hibernate.spatial.dialect.postgis.PostgisDialect
spring.jpa.show-sql=false
spring.jpa.hibernate.ddl-auto=update

spring.datasource.url=jdbc:postgresql://192.168.1.123/mywebapp
spring.datasource.username=postgres
spring.datasource.password=mypwd
spring.datasource.driverClassName=org.postgresql.Driver

First of all, is it ok to use database-platform instead of database? And maybe do I have to use following settings instead of the above?

spring.datasource.url=jdbc:postgresql_postGIS://192.168.1.123/mywebapp
spring.datasource.driverClassName=org.postgis.DriverWrapper

Anyway my entity is something like this:

@Entity
public class Place {
    @Id
    public int id;
    @Column(columnDefinition="Geometry")
    @Type(type="org.hibernate.spatial.GeometryType")    //"org.hibernatespatial.GeometryUserType" seems to be for older versions of Hibernate Spatial
    public com.vividsolutions.jts.geom.Point coordinates;
}

My pom.xml contains this relevant part:

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>9.3-1102-jdbc41</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-spatial</artifactId>
    <version>4.3</version><!-- compatible with Hibernate 4.3.x -->
    <exclusions>
        <exclusion>
            <artifactId>postgresql</artifactId>
            <groupId>postgresql</groupId>
        </exclusion>
    </exclusions>
</dependency>

A bit strange configuration, I found it on the internet, it is the one that works best for now.

I hope that someone could help me with this mistery. :)

6 Answers 6

30

Finally I discovered that my configuration is ok and might be Jackson that cannot manage Point data type correctly. So I customized its JSON serialization and deserialization:

  • add these annotations to our coordinates field:

    @JsonSerialize(using = PointToJsonSerializer.class)
    @JsonDeserialize(using = JsonToPointDeserializer.class)
    
  • create such serializer:

    import java.io.IOException;
    import com.fasterxml.jackson.core.JsonGenerator;
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.JsonSerializer;
    import com.fasterxml.jackson.databind.SerializerProvider;
    import com.vividsolutions.jts.geom.Point;
    
    public class PointToJsonSerializer extends JsonSerializer<Point> {
    
        @Override
        public void serialize(Point value, JsonGenerator jgen,
                SerializerProvider provider) throws IOException,
                JsonProcessingException {
    
            String jsonValue = "null";
            try
            {
                if(value != null) {             
                    double lat = value.getY();
                    double lon = value.getX();
                    jsonValue = String.format("POINT (%s %s)", lat, lon);
                }
            }
            catch(Exception e) {}
    
            jgen.writeString(jsonValue);
        }
    
    }
    
  • create such deserializer:

    import java.io.IOException;
    import com.fasterxml.jackson.core.JsonParser;
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.DeserializationContext;
    import com.fasterxml.jackson.databind.JsonDeserializer;
    import com.vividsolutions.jts.geom.Coordinate;
    import com.vividsolutions.jts.geom.GeometryFactory;
    import com.vividsolutions.jts.geom.Point;
    import com.vividsolutions.jts.geom.PrecisionModel;
    
    public class JsonToPointDeserializer extends JsonDeserializer<Point> {
    
        private final static GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), 26910); 
    
        @Override
        public Point deserialize(JsonParser jp, DeserializationContext ctxt)
                throws IOException, JsonProcessingException {
    
            try {
                String text = jp.getText();
                if(text == null || text.length() <= 0)
                    return null;
    
                String[] coordinates = text.replaceFirst("POINT ?\\(", "").replaceFirst("\\)", "").split(" ");
                double lat = Double.parseDouble(coordinates[0]);
                double lon = Double.parseDouble(coordinates[1]);
    
                Point point = geometryFactory.createPoint(new Coordinate(lat, lon));
                return point;
            }
            catch(Exception e){
                return null;
            }
        }
    
    }
    

Maybe you can also use this serializer and this deserializer, available here.

Sign up to request clarification or add additional context in comments.

4 Comments

I do notice that the output latitude and longitude from your Serializer are swapped. is that expected? This code explains it double lat = value.getY(); and double lon = value.getX(); thanks
@randytan I used this code long time ago, but I remember that latitude and longitude where in the right order. Where do you see them swapped?
If you send POINT(A B) and do sys.out to your variable jsonValue = String.format("POINT (%s %s)", lat, lon); you can see it's actually written to POINT(B A). I just swap the code value.getY() and value.getX() to correct the position. Thanks
@bluish I wonder How can I make this as a POLYGON serializer and deserializer.
7

The solutions above helped me to fix the problem. I simplify it so other people can understand.

I included this library in my pom.xml:

<dependency>
  <groupId>com.bedatadriven</groupId>
  <artifactId>jackson-datatype-jts</artifactId>
  <version>2.2</version>
</dependency>

This is the POJO object I used. Then I was able to get the REST call to work without the envelope error and proper coordinates.

import com.bedatadriven.jackson.datatype.jts.serialization.GeometryDeserializer;
import com.bedatadriven.jackson.datatype.jts.serialization.GeometrySerializer;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.vividsolutions.jts.geom.Geometry;

@Entity
@Table(name = "boundary")
public class Boundary {

    private int id;
    private Geometry geometry;

    @Id
    public int getId() {
        return ogc_fid;
    }

    public void setId(int id) {
        this.id = id;
    }
    
    @JsonSerialize(using = GeometrySerializer.class)
    @JsonDeserialize(using = GeometryDeserializer.class)
    @Column(name = "geometry", columnDefinition = "Geometry")
    public Geometry getGeometry() {
        return geometry;
    }

    public void setGeometry(Geometry geometry) {
        this.geometry = geometry;
    }
}

My table had these 2 columns:

id       | integer            
geometry | geometry(Geometry,4326) | 

2 Comments

saved my day :)
@Martin Naughton If I use @JsonDeserialize(using = JsonDeserialize.class), it returns this error. incompatible types found java.lang.class com.fasterxml.jackson.databind.annotation.jsondeserialize required: java.lang.Class<? extends com.fasterxml.jackson.databind.JsonDeserializer<?>> how can I use deserializer?
4

With Hibernate 5.4 and Posgis 13+ its became very easy. We can just add latest hibernate-spatial dependency

    <!-- Hibernate Spatial for storing and retrieving geometries -->
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-spatial</artifactId>
        <version>${hibernate.version}</version>
    </dependency>

Use latest Posgis dialect. Add in application.properties:

spring.jpa.properties.hibernate.dialect=org.hibernate.spatial.dialect.postgis.PostgisPG95Dialect

Then we can simply use JST geometry because its supported by Hibernate (see here).

import org.locationtech.jts.geom.Point;

@Entity(name = "address")
public class Address {
 private Point location;
}

SQL to create entity table :

CREATE TABLE if not EXISTS address (location geometry);

Further JTS GeoJSON Reader and Writer can be used to translate geometries into JSON (see here)

1 Comment

Upon Spring boot 2 to 3 migration it's failing, if you have encountered the same issue can you please help me with some pointers? error for reference Hint: Could not choose a best candidate operator. You might need to add explicit type casts
2

This serialization/deserialization also worked fine for me.

https://github.com/bedatadriven/jackson-datatype-jts

Comments

0

The problem doesn't appear to be related to PostgreSQL. It appears that your POJO has a backreference, which means that your mapper doesn't know how to handle it. You need to explicitly define the recursive relationships so that the mapper knows when to stop. (My Goto link --> http://vard-lokkur.blogspot.com/2010/10/json-jackson-to-rescue.html)

1 Comment

Having no reference to any other entity, this is not the issue. Thank you anyway!
0

If you don't want to add the annotation on all your fields that are using a Point, you can also use the @JsonComponent to register your JsonSerializer and JsonDeserializer.

@JsonComponent
public class PointSerializer extends JsonSerializer<com.vividsolutions.jts.geom.Point>{

    @Override
 public void serialize(com.vividsolutions.jts.geom.Point value, JsonGenerator gen, SerializerProvider provider) throws IOException {
     gen.writeStartObject();
     gen.writeNumberField("lat", value.getY());
     gen.writeNumberField("lon", value.getX());
     gen.writeEndObject();
 }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.