在地理信息系统 (GIS) 开发中,坐标参考系统 (Coordinate Reference System, CRS) 是核心概念之一。无论是处理地图投影、坐标转换,还是在 Spring Boot 应用中管理空间数据,理解和正确使用 CRS 都至关重要。本文将围绕 GeoTools 库,深入探讨 CRS 的重要性、EPSG 代码的概念与查询,以及如何在代码中定义 CRS 和执行坐标转换,最后结合 Spring Boot 展示实际应用场景。
坐标参考系统 (CRS) 的重要性
坐标参考系统定义了地理数据如何在二维或三维空间中表示。不同的 CRS 使用不同的投影方法和单位(例如,度、米),以适应特定场景的需求。简单的说坐标系类型分地理坐标(球面坐标,只有椭球信息,以经度为x轴,纬度为Y轴的),投影坐标系(平面坐标,椭球信息+投影方式,以横坐标经线为x轴,纵坐标纬线投影为Y轴。分带的投影有中央经线)以下是几种常见的 CRS 及其应用场景:
- WGS84 (EPSG:4326): 基于经纬度的全球通用 CRS,常用于 GPS 和全球地图数据。单位是度,适合存储和表示地理坐标。
- UTM (Universal Transverse Mercator): 基于米单位的投影系统,将地球划分为 60 个区域,适合局部区域的高精度计算。例如,UTM Zone 50N (EPSG:32650) 用于东亚部分地区。
- Web Mercator (EPSG:3857): 广泛用于在线地图(如 Google Maps、OpenStreetMap),以米为单位,适合网络地图渲染,但会引入投影变形。
- **CGCS2000(EPSG:4490)**2000国家大地坐标系,是中国当前最新的国家大地坐标系,000国家大地坐标系的原点为包括海洋和大气]的整个地球的质量中心
为什么 CRS 重要?
- 数据一致性: 不同数据源可能使用不同 CRS,未正确转换会导致位置错误。
- 计算精度: 不同 CRS 的单位和投影会影响距离、面积等计算结果。
- 互操作性: 在多系统集成时,统一 CRS 确保数据无缝对接。
示例场景:
- 假设你有一个 WGS84 坐标点 (经度: 116.4, 纬度: 39.9,北京),需要将其转换为 Web Mercator 以在地图上显示。如果不进行 CRS 转换,直接绘制会导致位置偏差。
EPSG 代码的概念与查询
EPSG 代码是由国际石油工业协会 (OGP) 定义的标准化编号,用于唯一标识 CRS。例如:
- EPSG:4326 表示 WGS84。
- EPSG:3857 表示 Web Mercator。
- EPSG:32650 表示 UTM Zone 50N。
如何查询 EPSG 代码?
- 在线资源:
- epsg.io: 输入地区或 CRS 名称(如 “WGS84” 或 “China”)即可查找相关 EPSG 代码。
- spatialreference.org: 提供 CRS 定义和 GeoTools 兼容的引用。
- GeoTools 内置查询: GeoTools 提供了 CRS.lookupIdentifier() 方法,可以通过 CRS 对象反查 EPSG 代码。
代码示例: 查询 CRS 的 EPSG 代码
import org.geotools.referencing.CRS;public class CRSLookupExample {public static void main(String[] args) throws Exception {// 定义 WGS84 CRSCoordinateReferenceSystem crs = CRS.decode("EPSG:4326");// 查询 EPSG 代码String epsgCode = CRS.lookupIdentifier(crs, true);System.out.println("EPSG Code: " + epsgCode); // 输出: EPSG:4326}
}
定义和获取 CRS
GeoTools 使用 CRS.decode() 方法通过 EPSG 代码定义 CRS。以下是常见操作的代码示例。
定义 CRS
import org.geotools.referencing.CRS;
import org.opengis.referencing.crs.CoordinateReferenceSystem;public class DefineCRSExample {public static void main(String[] args) throws Exception {// 定义 WGS84 (EPSG:4326)CoordinateReferenceSystem wgs84 = CRS.decode("EPSG:4326");System.out.println("WGS84 CRS: " + wgs84.getName());// 定义 Web Mercator (EPSG:3857)CoordinateReferenceSystem webMercator = CRS.decode("EPSG:3857");System.out.println("Web Mercator CRS: " + webMercator.getName());// 定义 UTM Zone 50N (EPSG:32650)CoordinateReferenceSystem utm50N = CRS.decode("EPSG:32650");System.out.println("UTM Zone 50N CRS: " + utm50N.getName());}
}
输出:
WGS84 CRS: WGS 84
Web Mercator CRS: WGS 84 / Pseudo-Mercator
UTM Zone 50N CRS: WGS 84 / UTM zone 50N
处理异常
CRS.decode() 可能抛出 FactoryException,通常是因为 EPSG 代码无效或 GeoTools 无法解析。建议始终使用 try-catch 捕获异常。
坐标转换
坐标转换是将一个 CRS 中的坐标转换为另一个 CRS 的过程。GeoTools 提供了 CRS.findMathTransform() 和 JTS.transform() 方法来实现转换。
基本流程
- 获取源和目标 CRS。
- 使用 CRS.findMathTransform(sourceCRS, targetCRS) 创建转换器。
- 使用 JTS.transform() 执行坐标转换。
代码示例:点转换
以下示例将北京的 WGS84 坐标 (116.4, 39.9) 转换为 Web Mercator。(记得看注意事项)
import org.geotools.referencing.CRS;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Point;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;public class CoordinateTransformExample {public static void main(String[] args) throws Exception {// 定义源和目标 CRSCoordinateReferenceSystem wgs84 = CRS.decode("EPSG:4326");CoordinateReferenceSystem webMercator = CRS.decode("EPSG:3857");// 创建坐标转换器MathTransform transform = CRS.findMathTransform(wgs84, webMercator);// 创建 WGS84 坐标点 (北京: 116.4, 39.9)GeometryFactory factory = new GeometryFactory();Point point = factory.createPoint(new Coordinate(116.4, 39.9));// 执行转换Point transformedPoint = (Point) JTS.transform(point, transform);// 输出结果System.out.println("WGS84 Point: " + point);System.out.println("Web Mercator Point: " + transformedPoint);}
}
输出:
WGS84 Point: POINT (116.4 39.9)
Web Mercator Point: POINT (12957326.04 4852936.40)
解释:
- WGS84 坐标 (116.4, 39.9) 是经纬度,单位为度。
- Web Mercator 坐标 (12957326.04, 4852936.40) 是以米为单位的投影坐标。
栅格坐标转换
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.io.AbstractGridFormat;
import org.geotools.coverage.grid.io.GridCoverage2DReader;
import org.geotools.coverage.grid.io.GridFormatFinder;
import org.geotools.coverage.processing.CoverageProcessor;
import org.geotools.coverage.processing.operation.Resample;
import org.geotools.referencing.CRS;
import org.geotools.util.factory.Hints;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.referencing.crs.CoordinateReferenceSystem;import java.io.File;public class RasterTransformExample {public static void main(String[] args) throws Exception {// 输入和输出文件路径File inputFile = new File("input.tif"); // 替换为你的 GeoTIFF 文件File outputFile = new File("output_transformed.tif");// 读取栅格数据GridCoverage2DReader reader = GridFormatFinder.getFormat(inputFile).getReader(inputFile);GridCoverage2D coverage = reader.read(null);// 定义源和目标 CRSCoordinateReferenceSystem sourceCRS = coverage.getCoordinateReferenceSystem();CoordinateReferenceSystem targetCRS = CRS.decode("EPSG:3857"); // Web Mercator// 配置重采样参数CoverageProcessor processor = CoverageProcessor.getInstance();ParameterValueGroup params = processor.getOperation("Resample").getParameters();params.parameter("Source").setValue(coverage);params.parameter("CoordinateReferenceSystem").setValue(targetCRS);params.parameter("InterpolationType").setValue("bilinear"); // 双线性插值// 执行重投影GridCoverage2D reprojected = (GridCoverage2D) processor.doOperation(params);// 保存结果AbstractGridFormat format = GridFormatFinder.getFormat(outputFile);format.write(reprojected, new Hints(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.TRUE), outputFile);System.out.println("Raster transformation completed. Output saved to: " + outputFile.getAbsolutePath());}
}
说明
- 输入: 一个 GeoTIFF 文件 (input.tif),假设其 CRS 为 WGS84 (EPSG:4326)。
- 转换: 使用 CoverageProcessor 和 Resample 操作将栅格数据重投影到 Web Mercator (EPSG:3857)。
- 插值: 使用双线性插值 (bilinear) 确保像素值平滑过渡,也可选择 nearest(最近邻)或 bicubic(双三次)。
- 输出: 保存为新的 GeoTIFF 文件 (output_transformed.tif)。
- 注意: 确保 input.tif 存在且具有正确的 CRS 元数据。GeoTools 依赖文件的 CRS 信息来识别源 CRS。
shape数据转换
import org.geotools.data.DataStore;
import org.geotools.data.DataUtilities;
import org.geotools.data.FeatureWriter;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.referencing.CRS;
import org.locationtech.jts.geom.Geometry;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;import java.io.File;
import java.nio.charset.StandardCharsets;public class ShapefileTransformExample {public static void main(String[] args) throws Exception {// 输入和输出文件File inputFile = new File("input.shp"); // 替换为你的 ShapefileFile outputFile = new File("output_transformed.shp");// 读取 ShapefileShapefileDataStore inputStore = new ShapefileDataStore(inputFile.toURI().toURL());String typeName = inputStore.getTypeNames()[0];SimpleFeatureSource featureSource = inputStore.getFeatureSource(typeName);SimpleFeatureCollection featureCollection = featureSource.getFeatures();// 定义源和目标 CRSCoordinateReferenceSystem sourceCRS = featureSource.getSchema().getCoordinateReferenceSystem();CoordinateReferenceSystem targetCRS = CRS.decode("EPSG:3857"); // Web Mercator// 创建坐标转换器MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS);// 创建输出 Shapefile 的 FeatureTypeSimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();builder.setName(featureSource.getSchema().getName());builder.setAttributes(featureSource.getSchema().getAttributes());builder.setCRS(targetCRS); // 设置目标 CRSSimpleFeatureType targetFeatureType = builder.buildFeatureType();// 创建输出 ShapefileShapefileDataStore outputStore = new ShapefileDataStore(outputFile.toURI().toURL());outputStore.createSchema(targetFeatureType);outputStore.setCharset(StandardCharsets.UTF_8);// 写入转换后的特征try (FeatureWriter<SimpleFeatureType, SimpleFeature> writer = outputStore.getFeatureWriterAppend(typeName)) {try (SimpleFeatureIterator iterator = featureCollection.features()) {while (iterator.hasNext()) {SimpleFeature feature = iterator.next();SimpleFeature newFeature = DataUtilities.createFeature(targetFeatureType, feature.getID());// 复制属性for (int i = 0; i < feature.getAttributeCount(); i++) {newFeature.setAttribute(i, feature.getAttribute(i));}// 转换几何Geometry geometry = (Geometry) feature.getDefaultGeometry();Geometry transformedGeometry = JTS.transform(geometry, transform);newFeature.setDefaultGeometry(transformedGeometry);// 写入新特征writer.next().setAttributes(newFeature.getAttributes());writer.write();}}}// 清理资源inputStore.dispose();outputStore.dispose();System.out.println("Shapefile transformation completed. Output: " + outputFile.getAbsolutePath());}
}
说明
- 输入: 一个 WGS84 的 Shapefile (input.shp)。
- 转换: 使用 JTS.transform() 转换每个特征的几何对象。
- 输出: 保存为新的 Shapefile (output_transformed.shp),CRS 为 Web Mercator。
- 注意:
- 确保输入 Shapefile 包含 .shp、.shx、.dbf 和 .prj 文件,尤其是 .prj 文件定义了源 CRS。
- 使用 StandardCharsets.UTF_8 避免中文属性乱码。
geojson转换
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.geojson.feature.FeatureJSON;
import org.geotools.referencing.CRS;
import org.locationtech.jts.geom.Geometry;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;import java.io.File;
import java.io.FileOutputStream;public class GeoJSONTransformExample {public static void main(String[] args) throws Exception {// 输入和输出文件File inputFile = new File("input.geojson"); // 替换为你的 GeoJSON 文件File outputFile = new File("output_transformed.geojson");// 读取 GeoJSONFeatureJSON featureJSON = new FeatureJSON();SimpleFeatureCollection featureCollection = featureJSON.readFeatureCollection(inputFile);// 定义源和目标 CRSCoordinateReferenceSystem sourceCRS = CRS.decode("EPSG:4326"); // GeoJSON 默认 WGS84CoordinateReferenceSystem targetCRS = CRS.decode("EPSG:3857"); // Web Mercator// 创建坐标转换器MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS);// 创建输出 FeatureTypeSimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();builder.setName("TransformedFeatures");builder.setAttributes(featureCollection.getSchema().getAttributes());builder.setCRS(targetCRS);SimpleFeatureType targetFeatureType = builder.buildFeatureType();// 创建新的 FeatureCollectionorg.geotools.feature.FeatureCollection<SimpleFeatureType, SimpleFeature> transformedFeatures =DataUtilities.collection(featureCollection.size());// 转换每个特征try (SimpleFeatureIterator iterator = featureCollection.features()) {while (iterator.hasNext()) {SimpleFeature feature = iterator.next();SimpleFeature newFeature = DataUtilities.createFeature(targetFeatureType, feature.getID());// 复制属性for (int i = 0; i < feature.getAttributeCount(); i++) {newFeature.setAttribute(i, feature.getAttribute(i));}// 转换几何Geometry geometry = (Geometry) feature.getDefaultGeometry();Geometry transformedGeometry = JTS.transform(geometry, transform);newFeature.setDefaultGeometry(transformedGeometry);// 添加到集合transformedFeatures.add(newFeature);}}// 保存转换后的 GeoJSONtry (FileOutputStream fos = new FileOutputStream(outputFile)) {featureJSON.writeFeatureCollection(transformedFeatures, fos);}System.out.println("GeoJSON transformation completed. Output: " + outputFile.getAbsolutePath());}
}
说明
- 输入: 一个 GeoJSON 文件 (input.geojson),默认 CRS 为 WGS84。
- 转换: 使用 JTS.transform() 转换每个特征的几何。
- 输出: 保存为新的 GeoJSON 文件 (output_transformed.geojson),几何坐标为 Web Mercator。
- 注意:
- GeoJSON 标准默认使用 WGS84,输出的 GeoJSON 文件不会显式存储 CRS 信息,需在文档或元数据中说明。
- 确保 GeoJSON 文件格式正确,包含 type: FeatureCollection 或 type: Feature。
注意事项
-
经纬度顺序: WGS84 使用 (经度, 纬度),但某些 CRS 可能要求 (纬度, 经度)。GeoTools 会自动处理,但建议检查 CRS.getAxisOrder()。
-
性能优化: MathTransform 对象可复用,避免重复创建。
-
异常处理: 确保捕获 FactoryException 和 TransformException。
上述示例代码没有任何问题,但是运行绝对不会得到输出的结果,
24-R得不到。原因就出在了经纬度顺序上。如果你换一种方式定义,就没问题了public String transformAndBufferWkt(String wkt, double bufferDistance) throws Exception {// 1. 解析 WKT 字符串为 Geometry 对象WKTReader wktReader = new WKTReader();Geometry geometry = wktReader.read(wkt);// 2. 定义源坐标系 (EPSG:4326) 和目标坐标系 (EPSG:3857)CoordinateReferenceSystem sourceCRS = getCRS4326();CoordinateReferenceSystem targetCRS =getCRS3857();// 3. 创建坐标转换器MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS, true);// 4. 将几何对象从 EPSG:4326 转换为 EPSG:3857Geometry geometry3857 = JTS.transform(geometry, transform);// 5. 生成缓冲区(单位为米,因为 EPSG:3857 使用米)Geometry buffer = geometry3857.buffer(bufferDistance);// 6. 将缓冲区几何对象转换回 WKTWKTWriter wktWriter = new WKTWriter();return wktWriter.write(buffer);}private CoordinateReferenceSystem getCRS4326(){try {String str = "GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433],AUTHORITY[\"EPSG\",4326]]";return CRS.parseWKT(str);}catch (Exception e){System.out.println(e.getMessage());return null;}}private CoordinateReferenceSystem getCRS3857() {try {String str = "PROJCS[\"WGS_1984_Web_Mercator_Auxiliary_Sphere\",GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],PROJECTION[\"Mercator_Auxiliary_Sphere\"],PARAMETER[\"False_Easting\",0.0],PARAMETER[\"False_Northing\",0.0],PARAMETER[\"Central_Meridian\",0.0],PARAMETER[\"Standard_Parallel_1\",0.0],PARAMETER[\"Auxiliary_Sphere_Type\",0.0],UNIT[\"Meter\",1.0],AUTHORITY[\"EPSG\",3857]]";return CRS.parseWKT(str);}catch (Exception e){System.out.println(e.getMessage());return null;}}
在 Spring Boot 应用中处理 CRS 数据
在实际项目中,Spring Boot 常用于构建 REST API,处理不同 CRS 的空间数据。以下是一个完整示例,展示如何在 Spring Boot 中接收 WGS84 坐标,转换为 Web Mercator,并返回结果。
项目依赖
在 pom.xml 中添加 GeoTools 和 JTS 依赖:
<dependencies><!-- GeoTools --><dependency><groupId>org.geotools</groupId><artifactId>gt-main</artifactId><version>25.0</version></dependency><dependency><groupId>org.geotools</groupId><artifactId>gt-epsg-hsql</artifactId><version>25.0</version></dependency><!-- JTS --><dependency><groupId>org.locationtech.jts</groupId><artifactId>jts-core</artifactId><version>1.18.2</version></dependency><!-- 栅格 支持 --><dependency><groupId>org.geotools</groupId><artifactId>gt-geotiff</artifactId><version>25.0</version></dependency><!-- Shapefile 支持 --><dependency><groupId>org.geotools</groupId><artifactId>gt-shapefile</artifactId><version>25.0</version></dependency><!-- GeoJSON 支持 --><dependency><groupId>org.geotools</groupId><artifactId>gt-geojson</artifactId><version>25.0</version></dependency><dependency><groupId>org.geotools</groupId><artifactId>gt-coverage</artifactId><version>25.0</version></dependency><!-- Spring Boot --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>3.2.0</version></dependency>
</dependencies><repositories><repository><id>osgeo</id><url>https://repo.osgeo.org/repository/release/</url></repository>
</repositories>
创建 REST API
以下是一个 Spring Boot 控制器,用于接收 WGS84 坐标并转换为 Web Mercator。
import org.geotools.referencing.CRS;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Point;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;@RestController
public class CoordinateController {private final GeometryFactory factory = new GeometryFactory();@GetMapping("/transform")public String transformCoordinates(@RequestParam double lon,@RequestParam double lat) throws Exception {// 定义源和目标 CRSCoordinateReferenceSystem wgs84 = CRS.decode("EPSG:4326");CoordinateReferenceSystem webMercator = CRS.decode("EPSG:3857");// 创建坐标转换器MathTransform transform = CRS.findMathTransform(wgs84, webMercator);// 创建 WGS84 坐标点Point point = factory.createPoint(new Coordinate(lon, lat));// 执行转换Point transformedPoint = (Point) JTS.transform(point, transform);// 返回结果return String.format("WGS84: %s, Web Mercator: %s", point, transformedPoint);}
}
测试 API
启动 Spring Boot 应用后,使用以下命令测试:
curl "http://localhost:8080/transform?lon=116.4&lat=39.9"
响应:
WGS84: POINT (116.4 39.9), Web Mercator: POINT (12957326.04 4852936.40)
扩展:处理多种 CRS
若需要支持用户指定的目标 CRS,可以添加参数:
@GetMapping("/transform")
public String transformCoordinates(@RequestParam double lon,@RequestParam double lat,@RequestParam String targetEpsg) throws Exception {CoordinateReferenceSystem wgs84 = CRS.decode("EPSG:4326");CoordinateReferenceSystem targetCrs = CRS.decode(targetEpsg);MathTransform transform = CRS.findMathTransform(wgs84, targetCrs);Point point = factory.createPoint(new Coordinate(lon, lat));Point transformedPoint = (Point) JTS.transform(point, transform);return String.format("WGS84: %s, %s: %s", point, targetEpsg, transformedPoint);
}
测试:
curl "http://localhost:8080/transform?lon=116.4&lat=39.9&targetEpsg=EPSG:32650"
响应:
WGS84: POINT (116.4 39.9), EPSG:32650: POINT (370139.44 4412630.12)
实际应用中的注意事项
- CRS 选择:
- 根据应用场景选择合适的 CRS。例如,Web 地图使用 EPSG:3857,区域分析使用 UTM。
- 性能优化:
- 缓存 MathTransform 对象,避免重复创建。
- 使用批量转换处理大量坐标点。
- 数据验证:
- 验证输入坐标是否在 CRS 的有效范围内(例如,UTM 仅适用于特定区域)。
- 异常处理:
- 捕获 FactoryException(CRS 定义失败)和 TransformException(转换失败)。
通过本文,我们深入探讨了 GeoTools 中 CRS 和坐标转换的核心概念:
- CRS 重要性: 确保数据一致性和计算精度。
- EPSG 代码: 标准化的 CRS 标识,可通过在线工具或 GeoTools 查询。
- 定义和转换: 使用 CRS.decode() 定义 CRS,CRS.findMathTransform() 和 JTS.transform() 实现坐标转换。
- Spring Boot 集成: 通过 REST API 实现灵活的坐标转换服务。
希望这些示例和讲解能帮助你快速上手 GeoTools 的 CRS 功能,并在实际项目中高效处理空间数据!如果有更多问题,欢迎随时交流。
参考资源:
- GeoTools 官方文档
- EPSG 数据库
- GeoTools GitHub