2 geotools入门示例

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 实例的工厂接口。例如,ShapefileDataStoreFactoryGeoJSONDataStoreFactoryJDBCDataStoreFactory (具体到 PostGIS 是 PostGISDialectFactoryJDBCDataStoreFactory 结合使用)。
  • FeatureSource: 一旦你有了 DataStore,你可以通过它获取 FeatureSourceFeatureSource 代表了一层地理要素 (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:

  1. 使用 GeoJSONDataStoreFactory: 这种方式与其他 DataStore 类似,更通用。
  2. 直接使用 gt-geojson 模块 (例如 GeoJSONReaderFeatureJSON): 这种方式更直接,有时更简单,特别是当你只需要读取 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。

你需要:

  1. PostGIS (或其他空间数据库) 的 JDBC驱动 (例如 postgresql 驱动)。
  2. 数据库连接参数 (主机, 端口, 数据库名, 用户名, 密码等)。
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,因为它相对简单易用。

  1. 创建一个简单的 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: '&copy; <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>
    
  2. 运行你的 Spring Boot 应用 (如果还没运行的话)。

  3. 在浏览器中打开 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 来显示数据。
    • 安全提示: loadShapefileDataloadGeoJSONFileData 中的 prompt 仅用于本地测试。在生产应用中,你不应该让前端直接指定服务器上的任意文件路径。应该设计 API,使其通过预定义的标识符或更安全的方式来获取数据。

这就完成了你的第一个 GeoTools & Spring Boot 应用!你现在已经掌握了:

  • 设置项目和依赖。
  • 使用 GeoTools 读取 Shapefile 和 GeoJSON。
  • 理解了 GeoTools 的基本要素处理概念。
  • 创建了一个 Spring Boot REST API 来提供 GeoJSON 数据。
  • (可选) 在 Leaflet 地图上显示了这些数据。

接下来,你可以探索更多 GeoTools 的功能,例如空间分析、坐标转换、数据写入、更复杂的数据库交互等等。祝你学习愉快!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/87250.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/87250.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深度解析String不可变性:从Java底层到设计哲学

一、String不可变性的直观理解 在Java中,String对象一旦创建,其内容就不可更改。任何看似"修改"String的操作,实际上都是创建了一个全新的String对象。这种设计是Java语言基础架构的重要部分,理解其底层原理对编写高效、安全的Java程序至关重要。 String str =…

C++并发编程-2.C++ 线程管控

参考&#xff1a;https://llfc.club/category?catid225RaiVNI8pFDD5L4m807g7ZwmF#!aid/2Tuk4RfvfBC788LlqnQrWiPiEGW 1. 简历 本节介绍C线程管控&#xff0c;包括移交线程的归属权&#xff0c;线程并发数量控制以及获取线程id等基本操作。 2. 线程归属权 比如下面&#xff…

Qt面试常问

1.QT信号与槽的底层原理&#xff1f; 底层通过元对象系统和事件循环完成的&#xff0c;能够在运行期间动态处理信号槽之间的连接与断开&#xff0c;而不是像函数调用那样在编译期间就完全确定了。元对象系统包含&#xff1a;QObject类、Q_OBJECT宏定义、moc编译器当发送一个信…

【git】错误

【成功解决】开代理 unable to access ‘https://github.com/laigeoffer/pmhub.git/’: Recv failure: Connection was reset

什么是状态机?状态机入门

状态机&#xff1a;优雅管理复杂逻辑的Python实践 在软件开发中&#xff0c;状态机&#xff08;Finite State Machine, FSM&#xff09; 是管理多状态转换的利器。它将行为分解为离散的状态、事件和转移规则&#xff0c;大幅提升代码的可读性与可维护性。本文通过Python示例解析…

【Python打卡Day41】简单CNN@浙大疏锦行

可以看到即使在深度神经网络情况下&#xff0c;准确率仍旧较差&#xff0c;这是因为特征没有被有效提取----真正重要的是特征的提取和加工过程。MLP把所有的像素全部展平了&#xff08;这是全局的信息&#xff09;&#xff0c;无法布置到局部的信息&#xff0c;所以引入了卷积神…

MySQL中InnoDB存储引擎底层原理与MySQL日志机制深入解析

MySQL的内部组件结构如下&#xff1a; 大体来说&#xff0c;MySQL 可以分为 Server 层和存储引擎层两部分。 Server层 主要包括连接器、查询缓存、分析器、优化器、执行器等&#xff0c;涵盖 MySQL 的大多数核心服务功能&#xff0c;以及所有的内置函数&#xff08;如日期、…

MCP基本概念

基本概念 现在大模型交互的热门形式&#xff1a; 第一、Agent与Tools(工具)的交互Agent需要调用外部工具和APl、访问数据库、执行代码等。> MCP 第二、Agent与Agent(其他智能体或用户)的交互Agent需要理解其他Agent的意图、协同完成任务、与用户进行自然的对话。 > A2A…

Docker容器相关命令介绍和示例

Docker 容器是镜像的运行实例。以下是常用的 Docker 容器命令及其示例&#xff1a; 1. 运行容器 docker run [选项] <镜像名> [命令]常用选项&#xff1a; -d&#xff1a;后台运行&#xff08;守护模式&#xff09;-it&#xff1a;交互式终端--name&#xff1a;指定容…

【Akshare】高效下载股票和ETF数据

在量化投资与金融数据分析的世界里&#xff0c;获取高质量的市场数据是构建有效策略的关键。Python库Akshare为我们提供了一个强大且易于使用的接口&#xff0c;可以轻松地从网络上抓取各类金融数据。本文将详细介绍如何利用Akshare下载股票和ETF的历史行情数据。 安装Akshare…

分布式--3--分布式事务

1 简介 事务在单系统中的表现&#xff1a;多次数据库操作用事务进行管理&#xff0c;来保证ACID原则。 但是如果各个模块都是单独独立出来的微服务&#xff0c;进行了分布式部署&#xff0c;单系统里的事务将不能保证各个数据库操作的一致性&#xff0c;因此就需要分布式事务来…

不同建模方式的介绍 RTL建模笔记(1)

说明&#xff1a;该专栏"RTL建模笔记"是《RTL Modeling with SystemVerilog for Simulation and Synthesis》的翻译&#xff1b;该笔记略过了第一章第一小节中背景介绍内容&#xff0c;以及第二小节前面部分的门级、RTL级建模介绍&#xff0c;对于后续学习不影响。 …

<13>-MySQL用户管理

目录 一&#xff0c;用户管理操作 1&#xff0c;创建用户 2&#xff0c;查询用户 3&#xff0c;修改密码 4&#xff0c;删除用户 二&#xff0c;数据库权限 1&#xff0c;用户授权 2&#xff0c;回收权限 一&#xff0c;用户管理操作 1&#xff0c;创建用户 --创建用户…

如何使用超低噪声电源提高AD 时钟电路质量,改善超声系统的图像质量

超声波技术是医疗诊断和其他应用中广泛使用的无创工具&#xff0c;已经从静态图像进化到动态图像&#xff0c;从黑白呈现变为彩色多普勒图像。这些重大进步主要是由于引入了数字超声技术。虽然这些进步提高了超声成像的有效性和通用性&#xff0c;但同样重要的是&#xff0c;这…

【解决方案】Kali 2022.3修复仓库密钥一键安装docker,docker compose

1、Kali 2022.3 2、一键安装docker&#xff0c;docker compose #!/bin/bashecho " 安全的Kali Docker安装脚本 "# 备份重要配置 cp /etc/apt/sources.list /etc/apt/sources.list.backup.$(date %Y%m%d)# 修复Kali仓库配置 echo "修复Kali仓库配置..." ca…

Transformer、RNN (循环神经网络) 和 CNN (卷积神经网络)的区别

我们来详细对比一下 Transformer、RNN (循环神经网络) 和 CNN (卷积神经网络) 这三种在深度学习中极其重要的架构&#xff0c;并通过具体例子说明它们的区别。 核心区别总结&#xff1a; 处理数据的方式&#xff1a; CNN: 专注于局部特征和空间/时间模式。通过卷积核在输入数据…

408第二季 - 组成原理 - 数据类型转换

这章内容会比较少 闲聊 如果题目说把8位改成4位&#xff0c;你保留低位就行了 这里保留的是0101 然后是有符号数和无符号数的转换 机器数就是二进制长什么样子 然后就是小数点是不参与存储的 然后简单看看代码 这是short就说明是有符号数 unsigned就是说明是无符号数 然后y…

让 Deepseek 写电器电费计算器(html版本)

以下是一个简单的电器电费计算器的HTML和CSS代码&#xff1a; <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0">…

react_flow自定义节点、边——使用darg布局树状结构

文章目录 ⭐前言⭐引入react-flow⭐自定义节点nodeType⭐自定义边edgeType⭐添加节点⭐inscode代码块⭐结束 ⭐前言 大家好&#xff0c;我是yma16&#xff0c;本文分享 前端 ——react_flow自定义节点、边——使用darg布局树状结构。 自定义效果 可以自定义节点、边、线条流动…

word表格批量转excel,提取表格数据到excel

本文将带你一步步实现一个将 Word 中的表格内容批量提取并转换为 Excel 文件的自动化工具&#xff0c;适用于需要手动复制粘贴数据到excel的场景 假设我们有这样的表格在word中&#xff0c;图片世放在excel中便于截图&#xff0c;现在需要将表格中有颜色的数据提取到对应的exce…