⇧ 脆弱性は次々に出てくるから、さもありなん。
拡張機能「PostGIS」を有効にしたPostgreSQLのgeometry型をJavaで扱う
⇧ 「org.postgresql.geometric」が用意されているので、ビルドツールである「Gradle」とかでライブラリを依存関係として追加すれば良さそう。
Wikipediaさんによりますと、
A 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.
Most spatial databases allow the representation of simple geometric objects such as points, lines and polygons. Some spatial databases handle more complex structures such as 3D objects, topological coverages, linear networks, and triangulated irregular networks (TINs).
⇧「地理情報」を扱えるようにしたRDBMSを、「空間データベース」と言うそうな。
メジャーなRDBMSは、バージョンが余程旧くなければ、だいたい「地理情報」が扱えるようになっているようです。
テーブルは、
⇧ 上記の記事の時に用意してた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さんによると、
GeoJSONはJavaScript Object Notation(JSON)を用いて空間データをエンコードし非空間属性を関連付けるファイルフォーマットである。属性にはポイント(住所や座標)、ライン(各種道路や境界線)、 ポリゴン(国や地域)などが含まれる。他のGISファイル形式との違いとして、Open Geospatial Consortiumではなく世界各地の開発者達が開発し管理している点で異なる。
TopoJSONはGeoJSONに影響され開発された。
⇧ GeoJSONって形のインプットになるんかね?
それにしても、相変わらず、必要な依存関係がハッキリしない...
毎度モヤモヤ感が半端ない...
今回はこのへんで。