※当サイトの記事には、広告・プロモーションが含まれます。

拡張機能「PostGIS」を有効にしたPostgreSQLのgeometry型をJavaで扱う

www.itmedia.co.jp

脆弱性は次々に出てくるから、さもありなん。

拡張機能PostGIS」を有効にしたPostgreSQLのgeometry型をJavaで扱う

PostgreSQLJDBCAPIで、

jdbc.postgresql.org

github.com

⇧ 「org.postgresql.geometric」が用意されているので、ビルドツールである「Gradle」とかでライブラリを依存関係として追加すれば良さそう。

Wikipediaさんによりますと、

spatial database is a general-purpose database (usually a relational database) that has been enhanced to include spatial data that represents objects defined in a geometric space, along with tools for querying and analyzing such data. 

https://en.wikipedia.org/wiki/Spatial_database

Most spatial databases allow the representation of simple geometric objects such as pointslines and polygons. Some spatial databases handle more complex structures such as 3D objectstopological coverages, linear networks, and triangulated irregular networks (TINs).

https://en.wikipedia.org/wiki/Spatial_database

⇧「地理情報」を扱えるようにしたRDBMSを、「空間データベース」と言うそうな。

メジャーなRDBMSは、バージョンが余程旧くなければ、だいたい「地理情報」が扱えるようになっているようです。

テーブルは、

ts0818.hatenablog.com

⇧ 上記の記事の時に用意してたPostgreSQLを利用します。

PostgreSQLに接続できるように、PostgreSQLがインストールされてる「WSL 2(Windows SubSystem for Linux 2)」のUbuntuを起動しておくのを忘れずに。

では、早速、Eclipseを起動して「Spring Boot」のプロジェクトを作成します。

「次へ(N) >」を押下。

依存関係を選択。(後で、足りない依存関係を追加します。)。「次へ(N)>」を押下。

「完了(F)」押下。

プロジェクトが作成されます。

以下のような感じで、パッケージ、ファイルを追加します。(実際にはパッケージの構成なんかは、開発現場に合わせる感じで。)

ソースファイルなどの内容は以下のようになりました。

■/geometry/build.gradle

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.1.2'
	id 'io.spring.dependency-management' version '1.1.2'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
	sourceCompatibility = '17'
}

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	runtimeOnly 'org.postgresql:postgresql'
	// https://mvnrepository.com/artifact/net.postgis/postgis-jdbc
    implementation group: 'net.postgis', name: 'postgis-jdbc', version: '2021.1.0'
	
	annotationProcessor 'org.projectlombok:lombok'
	// https://mvnrepository.com/artifact/org.hibernate.orm/hibernate-spatial
    implementation group: 'org.hibernate.orm', name: 'hibernate-spatial', version: '6.3.0.CR1'
    // https://mvnrepository.com/artifact/com.graphhopper.external/jackson-datatype-jts
    implementation group: 'com.graphhopper.external', name: 'jackson-datatype-jts', version: '2.14'
    // https://mvnrepository.com/artifact/org.locationtech.jts/jts-core
    //simplementation group: 'org.locationtech.jts', name: 'jts-core', version: '1.19.0'

	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
	useJUnitPlatform()
}
    

■/geometry/src/main/resources/application.properties

# データベース接続情報
spring.jpa.database=POSTGRESQL
spring.datasource.url=jdbc:postgresql://ubuntuhost:5432/sample
spring.datasource.username=dev_web
spring.datasource.password=password

#spring.jpa.database-platform=org.hibernate.spatial.dialect.postgis.PostgisDialect
spring.jpa.show-sql=true    

■/geometry/src/main/java/com/example/demo/entity/flood/FloodAssumedAreaShizuoka.java

package com.example.demo.entity.flood;

import org.locationtech.jts.geom.Geometry;

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 jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
//import net.postgis.jdbc.geometry.Geometry;

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class FloodAssumedAreaShizuoka {

	@Id
	@Column(name="gid")
	private Long geometryId;
	
	/** 危険区域区分 */
	@Column(name="a31_401")
	private short hazardousAreaClassification;
	
	@Column(name="geom", columnDefinition ="geometry(Multipolygon, 6668)")
	@JsonSerialize(using = GeometrySerializer.class)
	@JsonDeserialize(using = GeometryDeserializer.class)
	private Geometry geometryInfo;
}
    

■/geometry/src/main/java/com/example/demo/repository/flood/FloodAssumedAreaShizuokaRepository.java

package com.example.demo.repository.flood;

import org.springframework.data.jpa.repository.JpaRepository;

import com.example.demo.entity.flood.FloodAssumedAreaShizuoka;

public interface FloodAssumedAreaShizuokaRepository extends JpaRepository<FloodAssumedAreaShizuoka, Long> {

}
    

■/geometry/src/main/java/com/example/demo/service/flood/FloodAssumedAreaShizuokaService.java

package com.example.demo.service.flood;

import java.util.List;

import com.example.demo.entity.flood.FloodAssumedAreaShizuoka;

public interface FloodAssumedAreaShizuokaService {

	/**
	 * 全件検索
	 * @return
	 */
	public List<FloodAssumedAreaShizuoka> findAll();
	
	/**
	 * 検索(gid)
	 * @param gid geometry identifer
	 * @return
	 */
	public FloodAssumedAreaShizuoka findById(Long gid);
}    

■/geometry/src/main/java/com/example/demo/service/impl/flood/FloodAssumedAreaShizuokaServiceImpl.java

package com.example.demo.service.impl.flood;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.example.demo.entity.flood.FloodAssumedAreaShizuoka;
import com.example.demo.repository.flood.FloodAssumedAreaShizuokaRepository;
import com.example.demo.service.flood.FloodAssumedAreaShizuokaService;

@Service
public class FloodAssumedAreaShizuokaServiceImpl implements FloodAssumedAreaShizuokaService {

	@Autowired
	private FloodAssumedAreaShizuokaRepository floodAssumedAreaShizuokaRepository;
	
	@Override
	public List<FloodAssumedAreaShizuoka> findAll() {
		// 
		List<FloodAssumedAreaShizuoka> floodAssumedAreaShizuoka = floodAssumedAreaShizuokaRepository.findAll();
		return floodAssumedAreaShizuoka;
	}

	@Override
	public FloodAssumedAreaShizuoka findById(Long gid) {
		// 検索(gid)
		Optional<FloodAssumedAreaShizuoka> floodAssumedAreaShizuoka = floodAssumedAreaShizuokaRepository.findById(gid);
		return floodAssumedAreaShizuoka.isPresent() ? floodAssumedAreaShizuoka.get() : null;
	}
}    

■/geometry/src/main/java/com/example/demo/controller/flood/FloodAssumedAreaController.java

package com.example.demo.controller.flood;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.demo.entity.flood.FloodAssumedAreaShizuoka;
import com.example.demo.service.impl.flood.FloodAssumedAreaShizuokaServiceImpl;

@RestController
@RequestMapping("floodAssumedArea")
public class FloodAssumedAreaController {

	@Autowired
	FloodAssumedAreaShizuokaServiceImpl floodAssumedAreaShizuokaServiceImpl;
	
	@GetMapping(value="/shizuoka")
	public List<FloodAssumedAreaShizuoka> serachFloodForShizuoka() {
		List<FloodAssumedAreaShizuoka> floodAssumedAreaShizuoka = floodAssumedAreaShizuokaServiceImpl.findAll();
		return floodAssumedAreaShizuoka;
	}
	
	@GetMapping(value="/shizuoka/{gid}")
	public FloodAssumedAreaShizuoka searchbyIdForShizuoka(@PathVariable("gid") Long gid) {
		FloodAssumedAreaShizuoka floodAssumedAreaShizuoka = floodAssumedAreaShizuokaServiceImpl.findById(gid);
		return floodAssumedAreaShizuoka;
	}
}
   

■/geometry/src/main/java/com/example/demo/GeometryApplication.java

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class GeometryApplication {

	public static void main(String[] args) {
		SpringApplication.run(GeometryApplication.class, args);
	}

}
    

⇧ 保存して、Spring Bootを起動。

で、ブラウザから、@RestControllerのメソッドのエンドポイントにアクセスすると、

⇧ テーブルの情報が取得できてるようですが、テーブルに保存されてる値と、Javaを介してテーブルから取得してきた値を比較してみたところ、

SRID=6668;MULTIPOLYGON(((138.732864079646 35.144386441072,138.7328125 35.1439583333542,138.732711821342 35.1439123003939,138.732561323235 35.1439084690109,138.732395799928 35.1439129068734,138.732285454948 35.1439172387513,138.7321875 35.1439583333542,138.73211647731 35.1444259069651,138.732217823443 35.1444199474975,138.732470752344 35.1444065718046,138.73276977234 35.1443922957516,138.732864079646 35.144386441072)))
"geometryInfo":{"type":"MultiPolygon","coordinates":[[[[138.7328640796455,35.14438644107201],[138.73281250000002,35.14395833335422],[138.73271182134192,35.143912300393865],[138.73256132323525,35.14390846901091],[138.73239579992799,35.14391290687336],[138.73228545494794,35.143917238751285],[138.73218750000007,35.14395833335422],[138.7321164773103,35.144425906965054],[138.7322178234432,35.144419947497475],[138.73247075234394,35.144406571804595],[138.7327697723395,35.14439229575155],[138.7328640796455,35.14438644107201]]]]}}

⇧ 桁数が変わってるのと、SRID=6668;が取得できてないんよね...

う~む、デシリアライズしてるライブラリの仕様のせいだとは思うのだけど、ネットの情報を見る限り、標準のJacksonがgeometry型のデシリアライズシリアライズに対応してないらしく、エラーを回避するには、標準じゃないライブラリを導入してデシリアライズするしか無さそうなんよね...

他の対応策としては、ライブラリのメソッドをオーバーライドするか、自分で一からデシリアライズシリアライズを実装するかになるのか知らんけど、そもそも、どういうデータ構造が正解なのかが分からんのよね...

そして、何やら、ネットの情報を見てると、INSERTが上手くいかないって話が出てくるので、更新系にも難ありということですかね...

インプットのフォーマットも不明ですし...

Wikipediaさんによると、

GeoJSONJavaScript Object NotationJSON)を用いて空間データをエンコードし非空間属性を関連付けるファイルフォーマットである。属性にはポイント(住所や座標)、ライン(各種道路や境界線)、 ポリゴン(国や地域)などが含まれる。他のGISファイル形式との違いとして、Open Geospatial Consortiumではなく世界各地の開発者達が開発し管理している点で異なる。

GeoJSON - Wikipedia

TopoJSONはGeoJSONに影響され開発された。

GeoJSON - Wikipedia

⇧ GeoJSONって形のインプットになるんかね?

それにしても、相変わらず、必要な依存関係がハッキリしない...

毎度モヤモヤ感が半端ない...

今回はこのへんで。