在地理信息系統 (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