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 的功能,例如空间分析、坐标转换、数据写入、更复杂的数据库交互等等。祝你学习愉快!