1. 設置 Spring Boot 項目并集成 GeoTools 依賴
首先,你需要創建一個新的 Spring Boot 項目。你可以使用 Spring Initializr 來快速生成項目骨架。
選擇以下依賴:
- Web: Spring Web (用于創建 REST API)
- Developer Tools: Spring Boot DevTools (可選,用于熱加載)
添加 GeoTools 依賴:
在你的 pom.xml
(如果你使用 Maven) 或 build.gradle
(如果你使用 Gradle) 文件中添加 GeoTools 的依賴。GeoTools 是一個龐大的庫,你可以只添加你需要的模塊。為了讀取 Shapefile 和 GeoJSON,你至少需要以下依賴:
Maven (pom.xml
):
<properties><java.version>17</java.version><geotools.version>29.1</geotools.version> </properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.geotools</groupId><artifactId>gt-main</artifactId><version>${geotools.version}</version></dependency><dependency><groupId>org.geotools</groupId><artifactId>gt-shapefile</artifactId><version>${geotools.version}</version></dependency><dependency><groupId>org.geotools</groupId><artifactId>gt-data</artifactId> <version>${geotools.version}</version></dependency><dependency><groupId>org.geotools.xsd</groupId><artifactId>gt-xsd-geojson</artifactId><version>${geotools.version}</version></dependency><dependency><groupId>org.geotools</groupId><artifactId>gt-geojson</artifactId><version>${geotools.version}</version></dependency><dependency><groupId>org.geotools.jdbc</groupId><artifactId>gt-jdbc-postgis</artifactId><version>${geotools.version}</version></dependency><dependency><groupId>org.postgresql</groupId><artifactId>postgresql</artifactId><scope>runtime</scope> </dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
</dependencies><repositories><repository><id>osgeo</id><name>OSGeo Release Repository</name><url>https://repo.osgeo.org/repository/release/</url><snapshots><enabled>false</enabled></snapshots><releases><enabled>true</enabled></releases></repository><repository><id>osgeo-snapshot</id><name>OSGeo Snapshot Repository</name><url>https://repo.osgeo.org/repository/snapshot/</url><snapshots><enabled>true</enabled></snapshots><releases><enabled>false</enabled></releases></repository>
</repositories>
注意: 請將 ${geotools.version}
替換為最新的 GeoTools 穩定版本。你可以在 GeoTools 官網 或 Maven 中央倉庫查找最新版本。
2. 讀取常見的空間數據格式
理解核心 GeoTools 概念
在開始讀取數據之前,我們先來理解幾個 GeoTools 的核心概念:
DataStore
:DataStore
是訪問特定數據格式或服務的入口點。例如,ShapefileDataStore
用于 Shapefile,GeoJSONDataStore
用于 GeoJSON 文件,JDBCDataStore
用于數據庫。DataStoreFactorySpi
: 這是用于創建DataStore
實例的工廠接口。例如,ShapefileDataStoreFactory
、GeoJSONDataStoreFactory
、JDBCDataStoreFactory
(具體到 PostGIS 是PostGISDialectFactory
與JDBCDataStoreFactory
結合使用)。FeatureSource
: 一旦你有了DataStore
,你可以通過它獲取FeatureSource
。FeatureSource
代表了一層地理要素 (features),你可以從中讀取要素。它通常是只讀的,如果需要寫入,則使用其子接口FeatureStore
。FeatureCollection
:FeatureCollection
是從FeatureSource
中檢索到的要素的集合。FeatureIterator
:FeatureIterator
用于遍歷FeatureCollection
中的每一個要素。重要的是:使用完畢后一定要關閉FeatureIterator
以釋放資源。SimpleFeature
:SimpleFeature
代表一個單獨的地理要素,它包含了地理屬性 (geometry) 和非地理屬性 (attributes)。它的結構由SimpleFeatureType
定義。
讀取 Shapefile
假設你有一個名為 your_shapefile.shp
的 Shapefile 文件 (通常還伴隨著 .dbf
, .shx
等輔助文件)。
創建一個服務類來處理數據讀取,例如 SpatialDataService.java
:
package com.example.geotoolsdemo.service;import org.geotools.api.data.*;
import org.geotools.api.feature.simple.SimpleFeature;
import org.geotools.api.feature.simple.SimpleFeatureType;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.springframework.stereotype.Service;import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;@Service
public class SpatialDataService {public List<Map<String, Object>> readShapefile(String filePath) throws IOException {File file = new File(filePath);if (!file.exists()) {throw new IOException("Shapefile not found at: " + filePath);}Map<String, Object> params = new HashMap<>();try {params.put("url", file.toURI().toURL());} catch (MalformedURLException e) {throw new RuntimeException("Failed to convert file path to URL", e);}params.put("create spatial index", true); // 可選,提高性能ShapefileDataStoreFactory dataStoreFactory = new ShapefileDataStoreFactory();ShapefileDataStore dataStore = null;List<Map<String, Object>> featuresList = new ArrayList<>();try {dataStore = (ShapefileDataStore) dataStoreFactory.createDataStore(params);if (dataStore == null) {throw new IOException("Could not create ShapefileDataStore for: " + filePath);}String typeName = dataStore.getTypeNames()[0]; // Shapefile 通常只包含一個類型FeatureSource<SimpleFeatureType, SimpleFeature> source = dataStore.getFeatureSource(typeName);FeatureCollection<SimpleFeatureType, SimpleFeature> collection = source.getFeatures();try (FeatureIterator<SimpleFeature> features = collection.features()) {while (features.hasNext()) {SimpleFeature feature = features.next();Map<String, Object> featureAttributes = new HashMap<>();feature.getProperties().forEach(property -> {// GeoTools 的 geometry 對象不能直接序列化為 JSON,// 在 REST API 中通常會轉換為 GeoJSON 格式的字符串或 WKTif (property.getValue() instanceof com.locationtech.jts.geom.Geometry) {// 在 API 控制器中處理幾何對象的 GeoJSON 轉換featureAttributes.put(property.getName().getLocalPart(), property.getValue().toString()); // 暫時用 WKT} else {featureAttributes.put(property.getName().getLocalPart(), property.getValue());}});featuresList.add(featureAttributes);}}} finally {if (dataStore != null) {dataStore.dispose(); // 非常重要:釋放資源}}return featuresList;}
}
重要:
- 確保你的 Shapefile 路徑正確。
- 使用
try-with-resources
或者在finally
塊中調用dataStore.dispose()
和features.close()
來釋放資源,這非常重要,否則可能導致文件鎖等問題。
讀取 GeoJSON
GeoTools 提供了兩種主要方式來處理 GeoJSON:
- 使用
GeoJSONDataStoreFactory
: 這種方式與其他DataStore
類似,更通用。 - 直接使用
gt-geojson
模塊 (例如GeoJSONReader
或FeatureJSON
): 這種方式更直接,有時更簡單,特別是當你只需要讀取 GeoJSON 內容而不一定需要完整的DataStore
抽象時。
方法 1: 使用 GeoJSONDataStoreFactory
package com.example.geotoolsdemo.service;// ... 其他 import ...
import org.geotools.data.geojson.GeoJSONDataStoreFactory;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureSource;// ... 在 SpatialDataService.java 中添加以下方法 ...public List<Map<String, Object>> readGeoJSON(String filePath) throws IOException {File file = new File(filePath);if (!file.exists()) {throw new IOException("GeoJSON file not found at: " + filePath);}Map<String, Object> params = new HashMap<>();try {params.put(GeoJSONDataStoreFactory.URLP.key, file.toURI().toURL());} catch (MalformedURLException e) {throw new RuntimeException("Failed to convert file path to URL", e);}GeoJSONDataStoreFactory dataStoreFactory = new GeoJSONDataStoreFactory();DataStore dataStore = null;List<Map<String, Object>> featuresList = new ArrayList<>();try {dataStore = dataStoreFactory.createDataStore(params);if (dataStore == null) {throw new IOException("Could not create GeoJSONDataStore for: " + filePath);}String typeName = dataStore.getTypeNames()[0];SimpleFeatureSource featureSource = dataStore.getFeatureSource(typeName);SimpleFeatureCollection collection = featureSource.getFeatures();try (FeatureIterator<SimpleFeature> features = collection.features()) {while (features.hasNext()) {SimpleFeature feature = features.next();Map<String, Object> featureAttributes = new HashMap<>();feature.getProperties().forEach(property -> {if (property.getValue() instanceof com.locationtech.jts.geom.Geometry) {featureAttributes.put(property.getName().getLocalPart(), property.getValue().toString()); // 暫時用 WKT} else {featureAttributes.put(property.getName().getLocalPart(), property.getValue());}});featuresList.add(featureAttributes);}}} finally {if (dataStore != null) {dataStore.dispose();}}return featuresList;
}
方法 2: 直接使用 gt-geojson
(例如 FeatureJSON
)
這個模塊允許你更直接地將 GeoJSON 字符串或流解析為 FeatureCollection
。
package com.example.geotoolsdemo.service;// ... 其他 import ...
import org.geotools.geojson.feature.FeatureJSON; // 用于解析和編碼 FeatureCollection
import org.geotools.feature.FeatureCollection;
import org.geotools.api.feature.simple.SimpleFeature;
import org.geotools.api.feature.simple.SimpleFeatureType;import java.io.FileInputStream;
import java.io.InputStream;// ... 在 SpatialDataService.java 中添加以下方法 ...public List<Map<String, Object>> readGeoJSONDirectly(String filePath) throws IOException {File file = new File(filePath);if (!file.exists()) {throw new IOException("GeoJSON file not found at: " + filePath);}List<Map<String, Object>> featuresList = new ArrayList<>();FeatureJSON fjson = new FeatureJSON(); // 用于讀取 FeatureCollectiontry (InputStream in = new FileInputStream(file)) {FeatureCollection<SimpleFeatureType, SimpleFeature> collection = fjson.readFeatureCollection(in);try (FeatureIterator<SimpleFeature> features = collection.features()) {while (features.hasNext()) {SimpleFeature feature = features.next();Map<String, Object> featureAttributes = new HashMap<>();feature.getProperties().forEach(property -> {if (property.getValue() instanceof com.locationtech.jts.geom.Geometry) {featureAttributes.put(property.getName().getLocalPart(), property.getValue().toString()); // 暫時用 WKT} else {featureAttributes.put(property.getName().getLocalPart(), property.getValue());}});featuresList.add(featureAttributes);}}}return featuresList;
}
選擇哪種方式取決于你的具體需求和偏好。DataStore
方式更通用,而直接解析更輕量級。
PostGIS/其他空間數據庫 (初步了解)
使用 JDBCDataStoreFactory
可以連接到多種支持 JDBC 的空間數據庫,包括 PostGIS。
你需要:
- PostGIS (或其他空間數據庫) 的 JDBC驅動 (例如
postgresql
驅動)。 - 數據庫連接參數 (主機, 端口, 數據庫名, 用戶名, 密碼等)。
package com.example.geotoolsdemo.service;// ... 其他 import ...
import org.geotools.data.DataStoreFinder;
import org.geotools.data.postgis.PostgisNGDataStoreFactory; // 推薦使用NG (Next Generation) 版本// ... 在 SpatialDataService.java 中添加以下方法 ...public List<Map<String, Object>> readPostGIS(String tableName) throws IOException {Map<String, Object> params = new HashMap<>();params.put(PostgisNGDataStoreFactory.DBTYPE.key, "postgis");params.put(PostgisNGDataStoreFactory.HOST.key, "localhost"); // 你的數據庫主機params.put(PostgisNGDataStoreFactory.PORT.key, 5432); // 你的數據庫端口params.put(PostgisNGDataStoreFactory.DATABASE.key, "your_database"); // 你的數據庫名params.put(PostgisNGDataStoreFactory.SCHEMA.key, "public"); // 你的模式名 (通常是 public)params.put(PostgisNGDataStoreFactory.USER.key, "your_user"); // 你的用戶名params.put(PostgisNGDataStoreFactory.PASSWD.key, "your_password"); // 你的密碼// params.put(PostgisNGDataStoreFactory.SSL_MODE.key, "disable"); // 根據你的 SSL 配置DataStore dataStore = null;List<Map<String, Object>> featuresList = new ArrayList<>();try {// 使用 DataStoreFinder 自動查找合適的工廠dataStore = DataStoreFinder.getDataStore(params);if (dataStore == null) {throw new IOException("Could not connect to PostGIS database. Check connection parameters.");}// 或者直接使用 PostgisNGDataStoreFactory// PostgisNGDataStoreFactory factory = new PostgisNGDataStoreFactory();// if (!factory.canProcess(params)) {// throw new IOException("PostgisNGDataStoreFactory cannot process the provided parameters.");// }// dataStore = factory.createDataStore(params);SimpleFeatureSource featureSource = dataStore.getFeatureSource(tableName); // tableName 是數據庫中的表名SimpleFeatureCollection collection = featureSource.getFeatures();try (FeatureIterator<SimpleFeature> features = collection.features()) {while (features.hasNext()) {SimpleFeature feature = features.next();Map<String, Object> featureAttributes = new HashMap<>();feature.getProperties().forEach(property -> {if (property.getValue() instanceof com.locationtech.jts.geom.Geometry) {featureAttributes.put(property.getName().getLocalPart(), property.getValue().toString()); // 暫時用 WKT} else {featureAttributes.put(property.getName().getLocalPart(), property.getValue());}});featuresList.add(featureAttributes);}}} finally {if (dataStore != null) {dataStore.dispose();}}return featuresList;
}
注意:
- 確保已將 PostgreSQL JDBC 驅動添加到項目的依賴中。
- 替換上述代碼中的數據庫連接參數為你自己的配置。
DataStoreFinder.getDataStore(params)
會嘗試根據參數找到合適的DataStoreFactory
。對于 PostGIS,通常會找到PostgisNGDataStoreFactory
。
3. 創建一個簡單的 Spring Boot REST API 返回空間數據 (GeoJSON 格式)
現在我們來創建一個 REST 控制器,它將使用 SpatialDataService
讀取數據,并將數據轉換為 GeoJSON 格式返回。
GeoTools 的 gt-geojson
模塊中的 FeatureJSON
類可以非常方便地將 FeatureCollection
或單個 Feature
編碼為 GeoJSON 字符串。
創建 SpatialDataController.java
:
package com.example.geotoolsdemo.controller;import com.example.geotoolsdemo.service.SpatialDataService;
import org.geotools.api.data.*;
import org.geotools.api.feature.simple.SimpleFeature;
import org.geotools.api.feature.simple.SimpleFeatureType;
import org.geotools.data.DataUtilities;
import org.geotools.data.DefaultTransaction;
import org.geotools.data.FeatureWriter;
import org.geotools.data.Transaction;
import org.geotools.data.collection.ListFeatureCollection;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.geojson.feature.FeatureJSON; // 用于編碼為 GeoJSON
import org.geotools.geometry.jts.JTSFactoryFinder;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Point;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;@RestController
@RequestMapping("/api/spatial")
public class SpatialDataController {@Autowiredprivate SpatialDataService spatialDataService;// 將 FeatureCollection 轉換為 GeoJSON 字符串的輔助方法private String convertFeatureCollectionToGeoJSON(FeatureCollection<SimpleFeatureType, SimpleFeature> featureCollection) throws IOException {StringWriter writer = new StringWriter();FeatureJSON featureJSON = new FeatureJSON();featureJSON.writeFeatureCollection(featureCollection, writer);return writer.toString();}@GetMapping(value = "/shapefile", produces = MediaType.APPLICATION_JSON_VALUE)public ResponseEntity<String> getShapefileAsGeoJSON(@RequestParam String filePath) {try {File file = new File(filePath);Map<String, Object> params = new HashMap<>();params.put("url", file.toURI().toURL());params.put("create spatial index", false);ShapefileDataStoreFactory dataStoreFactory = new ShapefileDataStoreFactory();ShapefileDataStore dataStore = (ShapefileDataStore) dataStoreFactory.createDataStore(params);if (dataStore == null) {return ResponseEntity.status(500).body("{\"error\":\"Could not create ShapefileDataStore for: " + filePath + "\"}");}String typeName = dataStore.getTypeNames()[0];FeatureSource<SimpleFeatureType, SimpleFeature> source = dataStore.getFeatureSource(typeName);FeatureCollection<SimpleFeatureType, SimpleFeature> collection = source.getFeatures();String geoJson = convertFeatureCollectionToGeoJSON(collection);dataStore.dispose(); // 確保釋放資源return ResponseEntity.ok(geoJson);} catch (MalformedURLException e) {return ResponseEntity.status(500).body("{\"error\":\"Invalid file path URL: " + e.getMessage() + "\"}");} catch (IOException e) {return ResponseEntity.status(500).body("{\"error\":\"Error reading Shapefile: " + e.getMessage() + "\"}");}}@GetMapping(value = "/geojson-file", produces = MediaType.APPLICATION_JSON_VALUE)public ResponseEntity<String> getGeoJSONFileAsGeoJSON(@RequestParam String filePath) {// 對于 GeoJSON 文件,我們實際上可以直接返回其內容,// 但為了演示 GeoTools 的處理流程,我們先讀取再寫回。try {File file = new File(filePath);Map<String, Object> params = new HashMap<>();params.put(org.geotools.data.geojson.GeoJSONDataStoreFactory.URLP.key, file.toURI().toURL());org.geotools.data.geojson.GeoJSONDataStoreFactory dataStoreFactory = new org.geotools.data.geojson.GeoJSONDataStoreFactory();DataStore dataStore = dataStoreFactory.createDataStore(params);if (dataStore == null) {return ResponseEntity.status(500).body("{\"error\":\"Could not create GeoJSONDataStore for: " + filePath + "\"}");}String typeName = dataStore.getTypeNames()[0];SimpleFeatureSource featureSource = dataStore.getFeatureSource(typeName);SimpleFeatureCollection collection = featureSource.getFeatures();String geoJson = convertFeatureCollectionToGeoJSON(collection);dataStore.dispose();return ResponseEntity.ok(geoJson);} catch (MalformedURLException e) {return ResponseEntity.status(500).body("{\"error\":\"Invalid file path URL: " + e.getMessage() + "\"}");} catch (IOException e) {return ResponseEntity.status(500).body("{\"error\":\"Error reading GeoJSON file: " + e.getMessage() + "\"}");}}@GetMapping(value = "/postgis", produces = MediaType.APPLICATION_JSON_VALUE)public ResponseEntity<String> getPostgisLayerAsGeoJSON(@RequestParam String host,@RequestParam int port,@RequestParam String database,@RequestParam String schema,@RequestParam String user,@RequestParam String password,@RequestParam String tableName) {Map<String, Object> params = new HashMap<>();params.put(PostgisNGDataStoreFactory.DBTYPE.key, "postgis");params.put(PostgisNGDataStoreFactory.HOST.key, host);params.put(PostgisNGDataStoreFactory.PORT.key, port);params.put(PostgisNGDataStoreFactory.DATABASE.key, database);params.put(PostgisNGDataStoreFactory.SCHEMA.key, schema);params.put(PostgisNGDataStoreFactory.USER.key, user);params.put(PostgisNGDataStoreFactory.PASSWD.key, password);DataStore dataStore = null;try {dataStore = DataStoreFinder.getDataStore(params);if (dataStore == null) {return ResponseEntity.status(500).body("{\"error\":\"Could not connect to PostGIS database.\"}");}SimpleFeatureSource featureSource = dataStore.getFeatureSource(tableName);SimpleFeatureCollection collection = featureSource.getFeatures();String geoJson = convertFeatureCollectionToGeoJSON(collection);return ResponseEntity.ok(geoJson);} catch (IOException e) {return ResponseEntity.status(500).body("{\"error\":\"Error reading from PostGIS: " + e.getMessage() + "\"}");} finally {if (dataStore != null) {dataStore.dispose();}}}// 一個簡單的端點,用于測試直接創建 GeoJSON FeatureCollection@GetMapping(value = "/sample-geojson", produces = MediaType.APPLICATION_JSON_VALUE)public ResponseEntity<String> getSampleGeoJSON() throws IOException {// 1. 定義 FeatureType (Schema)SimpleFeatureType featureType = DataUtilities.createType("Location","geometry:Point:srid=4326," + // a geometry attribute: Point type, SRID 4326 (WGS84)"name:String," + // a String attribute"population:Integer" // an Integer attribute);// 2. 創建 FeatureCollectionList<SimpleFeature> features = new ArrayList<>();GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(featureType);// 創建第一個 FeaturePoint point1 = geometryFactory.createPoint(new Coordinate(-73.985130, 40.758896)); // Times SquarefeatureBuilder.add(point1);featureBuilder.add("Times Square");featureBuilder.add(10000); // 假設的人口SimpleFeature feature1 = featureBuilder.buildFeature("fid-1");features.add(feature1);// 創建第二個 FeaturePoint point2 = geometryFactory.createPoint(new Coordinate(-74.0060, 40.7128)); // Wall StreetfeatureBuilder.add(point2);featureBuilder.add("Wall Street");featureBuilder.add(5000);SimpleFeature feature2 = featureBuilder.buildFeature("fid-2");features.add(feature2);SimpleFeatureCollection collection = new ListFeatureCollection(featureType, features);// 3. 將 FeatureCollection 轉換為 GeoJSON 字符串String geoJson = convertFeatureCollectionToGeoJSON(collection);return ResponseEntity.ok(geoJson);}
}
解釋:
@RestController
和@RequestMapping("/api/spatial")
定義了 API 的基礎路徑。@GetMapping
定義了處理 GET 請求的端點。produces = MediaType.APPLICATION_JSON_VALUE
表明端點將返回 JSON 格式的數據。@RequestParam String filePath
允許你通過 URL 參數傳遞文件路徑 (例如http://localhost:8080/api/spatial/shapefile?filePath=/path/to/your/data.shp
)。在生產環境中,直接暴露文件路徑是非常不安全的,這里僅作演示。你應該使用更安全的方式來管理和訪問數據文件。- 我們重用了之前
SpatialDataService
中讀取數據的邏輯 (或者直接在控制器中實現)。 FeatureJSON().writeFeatureCollection(collection, writer)
將FeatureCollection
轉換為 GeoJSON 字符串。- 錯誤處理和資源釋放:在控制器中,我們同樣需要確保
DataStore
等資源被正確關閉。
運行你的 Spring Boot 應用:
在你的項目根目錄下運行:
./mvnw spring-boot:run
# 或者 (如果你使用的是 Gradle)
./gradlew bootRun
然后你可以通過瀏覽器或 Postman/curl 等工具訪問你的 API 端點:
http://localhost:8080/api/spatial/shapefile?filePath=你的shapefile絕對路徑.shp
http://localhost:8080/api/spatial/geojson-file?filePath=你的geojson絕對路徑.geojson
http://localhost:8080/api/spatial/postgis?host=...&port=...&database=...&schema=...&user=...&password=...&tableName=...
http://localhost:8080/api/spatial/sample-geojson
(用于測試)
4. (可選) 在前端簡單展示 (例如,使用 Leaflet.js 或 OpenLayers)
這一步是可選的,但有助于你理解數據如何在前端地圖上顯示。我們將使用 Leaflet.js,因為它相對簡單易用。
-
創建一個簡單的 HTML 文件 (例如 src/main/resources/static/index.html):
Spring Boot 會自動從 src/main/resources/static 或 src/main/resources/public 目錄下提供靜態內容。
代碼段
<!DOCTYPE html> <html> <head><title>GeoTools & Spring Boot - Leaflet Map</title><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0"><link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="crossorigin=""/><script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="crossorigin=""></script><style>#map { height: 600px; }</style> </head> <body><div id="map"></div> <button onclick="loadShapefileData()">Load Shapefile Data (Sample)</button> <button onclick="loadGeoJSONFileData()">Load GeoJSON File Data (Sample)</button> <button onclick="loadSampleData()">Load Sample GeoJSON</button><script>var map = L.map('map').setView([40.7128, -74.0060], 10); // 默認視圖 (紐約)L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'}).addTo(map);var geoJsonLayer; // 用于存儲 GeoJSON 圖層,方便移除和更新function displayGeoJSON(geoJsonData) {if (geoJsonLayer) {map.removeLayer(geoJsonLayer); // 移除舊圖層}geoJsonLayer = L.geoJSON(geoJsonData, {onEachFeature: function (feature, layer) {// 嘗試顯示一個屬性作為彈出窗口if (feature.properties) {let popupContent = '';for (const key in feature.properties) {popupContent += `<strong>${key}:</strong> ${feature.properties[key]}<br>`;}if (popupContent === '') {popupContent = "No properties found for this feature.";}layer.bindPopup(popupContent);}}}).addTo(map);// 縮放到圖層范圍if (geoJsonLayer.getBounds().isValid()) {map.fitBounds(geoJsonLayer.getBounds());}}function loadShapefileData() {// !!!重要!!!// 在生產環境中,不要直接將本地文件路徑暴露給前端。// 這里假設你有一個本地的 Shapefile,你需要替換為你的實際路徑。// 為了安全和方便,更好的做法是讓后端API知道數據源的位置,// 前端只需要調用一個不帶敏感路徑參數的API端點。// 例如: /api/spatial/data/myPredefinedShapefile// 這里為了簡單演示,我們仍然使用 filePath 參數。// 請將 '/path/to/your/data.shp' 替換為你的 Shapefile 文件的 *URL編碼后的絕對路徑*// 或者,將后端API修改為不需要此參數,而是從配置中讀取路徑。const filePath = prompt("Enter the absolute path to your .shp file (e.g., C:/data/my_shapefile.shp or /Users/user/data/my_shapefile.shp):");if (!filePath) return;fetch(`/api/spatial/shapefile?filePath=${encodeURIComponent(filePath)}`).then(response => {if (!response.ok) {return response.json().then(err => { throw new Error(err.error || `HTTP error! status: ${response.status}`) });}return response.json();}).then(data => {console.log("Shapefile data loaded:", data);displayGeoJSON(data);}).catch(error => console.error('Error loading Shapefile data:', error));}function loadGeoJSONFileData() {const filePath = prompt("Enter the absolute path to your .geojson file (e.g., C:/data/my_data.geojson or /Users/user/data/my_data.geojson):");if (!filePath) return;fetch(`/api/spatial/geojson-file?filePath=${encodeURIComponent(filePath)}`).then(response => {if (!response.ok) {return response.json().then(err => { throw new Error(err.error || `HTTP error! status: ${response.status}`) });}return response.json();}).then(data => {console.log("GeoJSON file data loaded:", data);displayGeoJSON(data);}).catch(error => console.error('Error loading GeoJSON file data:', error));}function loadSampleData() {fetch('/api/spatial/sample-geojson').then(response => {if (!response.ok) {return response.json().then(err => { throw new Error(err.error || `HTTP error! status: ${response.status}`) });}return response.json();}).then(data => {console.log("Sample GeoJSON data loaded:", data);displayGeoJSON(data);}).catch(error => console.error('Error loading sample GeoJSON data:', error));}// 默認加載示例數據// loadSampleData();</script></body> </html>
-
運行你的 Spring Boot 應用 (如果還沒運行的話)。
-
在瀏覽器中打開
http://localhost:8080/index.html
。你應該能看到一個地圖,并且可以通過點擊按鈕從你的 Spring Boot API 加載和顯示 GeoJSON 數據。
前端代碼解釋:
-
引入 Leaflet 的 CSS 和 JS 文件。
-
創建一個
div
作為地圖容器 (<div id="map"></div>
)。 -
初始化地圖 (
L.map('map').setView(...)
) 并添加一個 OpenStreetMap 的瓦片圖層。 -
displayGeoJSON(geoJsonData)
函數:
- 如果已存在 GeoJSON 圖層,則先移除。
- 使用
L.geoJSON(geoJsonData, { onEachFeature: ... })
將 GeoJSON 數據添加到地圖上。 onEachFeature
允許你為每個要素執行操作,例如綁定一個包含其屬性的彈出窗口 (bindPopup
)。map.fitBounds(...)
將地圖縮放到加載的數據的范圍。
-
loadShapefileData()
、loadGeoJSONFileData()
和loadSampleData()
函數:- 使用
fetch
API 調用你的 Spring Boot 后端端點。 - 獲取到 GeoJSON 響應后,調用
displayGeoJSON
來顯示數據。 - 安全提示:
loadShapefileData
和loadGeoJSONFileData
中的prompt
僅用于本地測試。在生產應用中,你不應該讓前端直接指定服務器上的任意文件路徑。應該設計 API,使其通過預定義的標識符或更安全的方式來獲取數據。
- 使用
這就完成了你的第一個 GeoTools & Spring Boot 應用!你現在已經掌握了:
- 設置項目和依賴。
- 使用 GeoTools 讀取 Shapefile 和 GeoJSON。
- 理解了 GeoTools 的基本要素處理概念。
- 創建了一個 Spring Boot REST API 來提供 GeoJSON 數據。
- (可選) 在 Leaflet 地圖上顯示了這些數據。
接下來,你可以探索更多 GeoTools 的功能,例如空間分析、坐標轉換、數據寫入、更復雜的數據庫交互等等。祝你學習愉快!