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):

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

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 格式的字符串或 WKT
                        if (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

java 复制代码
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

java 复制代码
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(); // 用于读取 FeatureCollection

    try (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. 数据库连接参数 (主机, 端口, 数据库名, 用户名, 密码等)。
java 复制代码
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:

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 {

    @Autowired
    private 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. 创建 FeatureCollection
        List<SimpleFeature> features = new ArrayList<>();
        GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();
        SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(featureType);

        // 创建第一个 Feature
        Point point1 = geometryFactory.createPoint(new Coordinate(-73.985130, 40.758896)); // Times Square
        featureBuilder.add(point1);
        featureBuilder.add("Times Square");
        featureBuilder.add(10000); // 假设的人口
        SimpleFeature feature1 = featureBuilder.buildFeature("fid-1");
        features.add(feature1);

        // 创建第二个 Feature
        Point point2 = geometryFactory.createPoint(new Coordinate(-74.0060, 40.7128)); // Wall Street
        featureBuilder.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 应用:

在你的项目根目录下运行:

Bash 复制代码
./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 目录下提供静态内容。

    代码段

    html 复制代码
    <!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/[email protected]/dist/leaflet.css"
              integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
              crossorigin=""/>
        <script src="https://unpkg.com/[email protected]/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 的功能,例如空间分析、坐标转换、数据写入、更复杂的数据库交互等等。祝你学习愉快!

相关推荐
clmm12327 分钟前
Java动态生成Nginx服务配置
java·开发语言·nginx
草履虫建模41 分钟前
Web开发全栈流程 - Spring boot +Vue 前后端分离
java·前端·vue.js·spring boot·阿里云·elementui·mybatis
code bean1 小时前
【C#】 C#中 nameof 和 ToString () 的用法与区别详解
android·java·c#
圆仔0071 小时前
【Java生成指定背景图片的PDF文件】
java
小猫咪怎么会有坏心思呢1 小时前
华为OD机考-分班问题/幼儿园分班-字符串(JAVA 2025B卷)
java·开发语言·华为od
在未来等你2 小时前
设计模式精讲 Day 4:建造者模式(Builder Pattern)
java·: design-patterns·builder-pattern·software-design·object-oriented-programming
今天我要乾重生2 小时前
java基础学习(三十)
java·开发语言·学习
Zik----3 小时前
Spring Boot 管理系统项目解读
spring boot·web
JWASX4 小时前
【RocketMQ 生产者和消费者】- 消费者重平衡(1)
java·rocketmq·重平衡
剽悍一小兔4 小时前
自动化文档生成工具(亲测可运行)
java