Skip to content

Spring Boot POC implementations on Spatial and GIS

Notifications You must be signed in to change notification settings

bindstone/veloh

Repository files navigation

Spatial Data (GIS) on Spring-Boot

This is a scratch book containing skeleton POC implementations on the thematic Spatial. Most of was researching between the different implementation of the Spatial components like POINT, NEARBY…​

The data used for the POC are from the Luxembourg Open Data Project Veloh, https://data.public.lu/fr/datasets/mobilite-veloh/ and given the different bicycle stations in Luxembourg City.

Here the different implementations all based on Java and Spring-Boot. All the implementations have 3 Web entry points:

The databases can be loaded with Docker implementation: ./infrastructure

veloh-classic

Based on the database: https://postgis.net/

The implementation is build on a classical Spring-Boot JPA Schema (Entity/Repository/Service/Controller). On the current implementation, finding the proposed packages in Maven (pom.xml). Special point to take attention is a problem on Hibernate, which restricts an upgrade from the proposed solution (Point to follow up).

Special Point to mention. I use FlyWay as Version Management tool for the Database, but also to provide a custom Function to delegate the Spatial specific instructions to th DB and keep the Backend implementation as clean and straightforward as possible. Point to investigate: Reduce the Mapping on StationDistanceRepository (see veloh-rx).

veloh-rx

Based on the database: https://postgis.net/

Reactive implementation based on the Spring Reactor Project.

As JPA and Hibernate are removed, the project has no "legacy" libraries. Main problem was the conversion of the Geography/Point type Information. This was solved by a custom converter (DbConverters). Point to take attention, the WKBReader/WKBWriter are not Thread-Safe for this a new instance is currently done on each mapping (line). To evaluate in view of performance.

Swagger URLs:

{
  "openapi": "3.0.1",
  "info": {
    "title": "OpenAPI definition",
    "version": "v0"
  },
  "servers": [
    {
      "url": "http://localhost:8080/",
      "description": "Generated server url"
    }
  ],
  "paths": {
    "/station": {
      "get": {
        "tags": [
          "station-controller"
        ],
        "operationId": "findAll",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Station"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/station/next": {
      "get": {
        "tags": [
          "station-controller"
        ],
        "operationId": "findNext",
        "parameters": [
          {
            "name": "lon",
            "in": "query",
            "required": true,
            "schema": {
              "type": "number",
              "format": "double"
            }
          },
          {
            "name": "lat",
            "in": "query",
            "required": true,
            "schema": {
              "type": "number",
              "format": "double"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/StationDistanceDao"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/station/init": {
      "get": {
        "tags": [
          "station-controller"
        ],
        "operationId": "init",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Coordinate": {
        "type": "object",
        "properties": {
          "x": {
            "type": "number",
            "format": "double"
          },
          "y": {
            "type": "number",
            "format": "double"
          },
          "z": {
            "type": "number",
            "format": "double"
          },
          "coordinate": {
            "$ref": "#/components/schemas/Coordinate"
          }
        }
      },
      "CoordinateSequence": {
        "type": "object",
        "properties": {
          "dimension": {
            "type": "integer",
            "format": "int32"
          }
        }
      },
      "CoordinateSequenceFactory": {
        "type": "object"
      },
      "Envelope": {
        "type": "object",
        "properties": {
          "null": {
            "type": "boolean"
          },
          "area": {
            "type": "number",
            "format": "double"
          },
          "minX": {
            "type": "number",
            "format": "double"
          },
          "maxX": {
            "type": "number",
            "format": "double"
          },
          "minY": {
            "type": "number",
            "format": "double"
          },
          "maxY": {
            "type": "number",
            "format": "double"
          },
          "width": {
            "type": "number",
            "format": "double"
          },
          "height": {
            "type": "number",
            "format": "double"
          }
        }
      },
      "Geometry": {
        "type": "object",
        "properties": {
          "envelope": {
            "$ref": "#/components/schemas/Geometry"
          },
          "factory": {
            "$ref": "#/components/schemas/GeometryFactory"
          },
          "userData": {
            "type": "object"
          },
          "length": {
            "type": "number",
            "format": "double"
          },
          "empty": {
            "type": "boolean"
          },
          "valid": {
            "type": "boolean"
          },
          "coordinates": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Coordinate"
            }
          },
          "numPoints": {
            "type": "integer",
            "format": "int32"
          },
          "boundaryDimension": {
            "type": "integer",
            "format": "int32"
          },
          "coordinate": {
            "$ref": "#/components/schemas/Coordinate"
          },
          "geometryType": {
            "type": "string"
          },
          "boundary": {
            "$ref": "#/components/schemas/Geometry"
          },
          "numGeometries": {
            "type": "integer",
            "format": "int32"
          },
          "precisionModel": {
            "$ref": "#/components/schemas/PrecisionModel"
          },
          "rectangle": {
            "type": "boolean"
          },
          "centroid": {
            "$ref": "#/components/schemas/Point"
          },
          "interiorPoint": {
            "$ref": "#/components/schemas/Point"
          },
          "envelopeInternal": {
            "$ref": "#/components/schemas/Envelope"
          },
          "srid": {
            "type": "integer",
            "format": "int32"
          },
          "area": {
            "type": "number",
            "format": "double"
          },
          "simple": {
            "type": "boolean"
          },
          "dimension": {
            "type": "integer",
            "format": "int32"
          }
        }
      },
      "GeometryFactory": {
        "type": "object",
        "properties": {
          "precisionModel": {
            "$ref": "#/components/schemas/PrecisionModel"
          },
          "coordinateSequenceFactory": {
            "$ref": "#/components/schemas/CoordinateSequenceFactory"
          },
          "srid": {
            "type": "integer",
            "format": "int32"
          }
        }
      },
      "Point": {
        "type": "object",
        "properties": {
          "envelope": {
            "$ref": "#/components/schemas/Geometry"
          },
          "factory": {
            "$ref": "#/components/schemas/GeometryFactory"
          },
          "userData": {
            "type": "object"
          },
          "coordinates": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Coordinate"
            }
          },
          "empty": {
            "type": "boolean"
          },
          "numPoints": {
            "type": "integer",
            "format": "int32"
          },
          "boundaryDimension": {
            "type": "integer",
            "format": "int32"
          },
          "coordinate": {
            "$ref": "#/components/schemas/Coordinate"
          },
          "geometryType": {
            "type": "string"
          },
          "boundary": {
            "$ref": "#/components/schemas/Geometry"
          },
          "coordinateSequence": {
            "$ref": "#/components/schemas/CoordinateSequence"
          },
          "x": {
            "type": "number",
            "format": "double"
          },
          "y": {
            "type": "number",
            "format": "double"
          },
          "simple": {
            "type": "boolean"
          },
          "dimension": {
            "type": "integer",
            "format": "int32"
          },
          "length": {
            "type": "number",
            "format": "double"
          },
          "valid": {
            "type": "boolean"
          },
          "numGeometries": {
            "type": "integer",
            "format": "int32"
          },
          "precisionModel": {
            "$ref": "#/components/schemas/PrecisionModel"
          },
          "rectangle": {
            "type": "boolean"
          },
          "centroid": {
            "$ref": "#/components/schemas/Point"
          },
          "interiorPoint": {
            "$ref": "#/components/schemas/Point"
          },
          "envelopeInternal": {
            "$ref": "#/components/schemas/Envelope"
          },
          "srid": {
            "type": "integer",
            "format": "int32"
          },
          "area": {
            "type": "number",
            "format": "double"
          }
        }
      },
      "PrecisionModel": {
        "type": "object",
        "properties": {
          "scale": {
            "type": "number",
            "format": "double"
          },
          "type": {
            "$ref": "#/components/schemas/Type"
          },
          "floating": {
            "type": "boolean"
          },
          "maximumSignificantDigits": {
            "type": "integer",
            "format": "int32"
          },
          "offsetX": {
            "type": "number",
            "format": "double"
          },
          "offsetY": {
            "type": "number",
            "format": "double"
          }
        }
      },
      "Station": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "name": {
            "type": "string"
          },
          "address": {
            "type": "string"
          },
          "location": {
            "$ref": "#/components/schemas/Point"
          }
        }
      },
      "Type": {
        "type": "object"
      },
      "StationDistanceDao": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "name": {
            "type": "string"
          },
          "address": {
            "type": "string"
          },
          "distance": {
            "type": "number",
            "format": "double"
          }
        }
      }
    }
  }
}

veloh-mongo

Based on the database: https://www.mongodb.com/

Reactive implementation on the NoSql database MongoDB. Only problem to identify the right class for the Geography (Point) data. Clean implementation with the Aggregator.

Point to take attention and could be improved is the creation of the Index (see StationDistanceRepository). This is needed for using the Spatial Functions like $nearby.

veloh-neo4j

Based on the database: https://neo4j.com/

Reactive implementation on the NoSql database Neo4J. The current implementation has only nodes, no relations. Same as on MongoDB finding the class for representing the point (GeographicPoint2d).

velho-mybatis

An implementation using the framework MyBatis for accessing the database. The main advantage, it can handle all the special operators of PostGIS and you can write natural SQL statements in XML file. This separates code and sql in a clean way.

Recapture

In general all implementations are currently only scratches for experimenting with the different solutions. From budget, to experience level (Reactive Development, Databases…​) to personal favorites, each solution has its playground and for this it is only a starting point to digg deeper and take a choice. Currently, the main problems have been to find the right documentations on implementing the Topic Spatial in the different technology stacks.

veloh_flutter (Bonus)

Add a Flutter Map Application for displaying all the stations (Orange circle).

Flutter Screenshot