第10章 - 坐标参考系统与投影转换

第10章 - 坐标参考系统与投影转换

10.1 CRS 基础概念

10.1.1 坐标参考系统

坐标参考系统(Coordinate Reference System, CRS)定义了如何将地球上的位置映射到平面或三维坐标。

┌─────────────────────────────────────────────────────────────────────┐
│                        坐标参考系统分类                              │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   地理坐标系(Geographic CRS)                                       │
│   ├── 使用经纬度表示位置                                            │
│   ├── 单位:度、分、秒                                              │
│   └── 例:WGS84 (EPSG:4326)、CGCS2000 (EPSG:4490)                  │
│                                                                     │
│   投影坐标系(Projected CRS)                                        │
│   ├── 将球面投影到平面                                              │
│   ├── 单位:米、英尺等                                              │
│   └── 例:Web Mercator (EPSG:3857)、UTM                            │
│                                                                     │
│   地心坐标系(Geocentric CRS)                                       │
│   ├── 以地心为原点的三维坐标                                        │
│   └── 用于卫星定位等                                                │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

10.1.2 常用 EPSG 代码

EPSG 代码 名称 类型 说明
4326 WGS 84 地理 GPS 全球坐标系
3857 Web Mercator 投影 Web 地图标准
4490 CGCS2000 地理 中国大地坐标系
4479 CGCS2000 3D 地理 中国大地坐标系(3D)
32650 UTM Zone 50N 投影 UTM 50N(北京地区)
4547 CGCS2000 / 3-degree Gauss-Kruger zone 39 投影 中国高斯投影

10.2 CRS 操作

10.2.1 获取 CRS

import org.geotools.api.referencing.crs.CoordinateReferenceSystem;
import org.geotools.referencing.CRS;

public class CRSOperations {
    
    public static void getCRS() throws Exception {
        // 1. 通过 EPSG 代码获取
        CoordinateReferenceSystem wgs84 = CRS.decode("EPSG:4326");
        System.out.println("WGS84: " + wgs84.getName());
        
        // 2. 强制经度优先(lon/lat)
        CoordinateReferenceSystem wgs84LonFirst = CRS.decode("EPSG:4326", true);
        
        // 3. 通过 WKT 获取
        String wkt = "GEOGCS[\"WGS 84\"," +
            "DATUM[\"WGS_1984\"," +
            "SPHEROID[\"WGS 84\",6378137,298.257223563]]," +
            "PRIMEM[\"Greenwich\",0]," +
            "UNIT[\"degree\",0.0174532925199433]]";
        CoordinateReferenceSystem fromWKT = CRS.parseWKT(wkt);
        
        // 4. 从 Geometry 获取
        // Point point = ...; // 设置了 CRS 的几何
        // CoordinateReferenceSystem geomCRS = 
        //     CRS.getHorizontalCRS(JTS.getCRS(point));
        
        // 5. 预定义 CRS
        CoordinateReferenceSystem defaultCRS = 
            org.geotools.referencing.crs.DefaultGeographicCRS.WGS84;
    }
    
    public static void crsInfo(CoordinateReferenceSystem crs) {
        System.out.println("=== CRS 信息 ===");
        System.out.println("名称: " + crs.getName());
        System.out.println("标识符: " + crs.getIdentifiers());
        System.out.println("域: " + crs.getDomainOfValidity());
        System.out.println("范围: " + crs.getScope());
        
        // 获取 EPSG 代码
        try {
            Integer epsgCode = CRS.lookupEpsgCode(crs, true);
            System.out.println("EPSG 代码: " + epsgCode);
        } catch (Exception e) {
            System.out.println("无法获取 EPSG 代码");
        }
        
        // WKT 表示
        System.out.println("\nWKT:");
        System.out.println(crs.toWKT());
    }
}

10.2.2 CRS 比较

public class CRSComparison {
    
    public static void compareCRS() throws Exception {
        CoordinateReferenceSystem crs1 = CRS.decode("EPSG:4326");
        CoordinateReferenceSystem crs2 = CRS.decode("EPSG:4326", true);
        CoordinateReferenceSystem crs3 = CRS.decode("EPSG:3857");
        
        // 严格比较
        boolean strictEqual = CRS.equalsIgnoreMetadata(crs1, crs2);
        System.out.println("4326 vs 4326(lonFirst): " + strictEqual);
        
        // 检查是否等效
        boolean equivalent = CRS.findMathTransform(crs1, crs2, true).isIdentity();
        System.out.println("转换是否为恒等: " + equivalent);
        
        // 检查轴顺序
        System.out.println("CRS1 轴顺序: " + 
            CRS.getAxisOrder(crs1));  // LAT_LON 或 LON_LAT
        System.out.println("CRS2 轴顺序: " + 
            CRS.getAxisOrder(crs2));
    }
}

10.3 坐标转换

10.3.1 基本转换

import org.geotools.api.referencing.operation.MathTransform;
import org.geotools.geometry.jts.JTS;

public class CoordinateTransform {
    
    public static void basicTransform() throws Exception {
        // 源和目标 CRS
        CoordinateReferenceSystem sourceCRS = CRS.decode("EPSG:4326", true);
        CoordinateReferenceSystem targetCRS = CRS.decode("EPSG:3857");
        
        // 创建转换器
        MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS, true);
        
        // 转换坐标数组
        double[] srcPt = {116.4074, 39.9042};  // 北京 (lon, lat)
        double[] dstPt = new double[2];
        transform.transform(srcPt, 0, dstPt, 0, 1);
        
        System.out.printf("WGS84: (%.4f, %.4f)%n", srcPt[0], srcPt[1]);
        System.out.printf("Web Mercator: (%.2f, %.2f)%n", dstPt[0], dstPt[1]);
        
        // 反向转换
        MathTransform inverse = transform.inverse();
        double[] backPt = new double[2];
        inverse.transform(dstPt, 0, backPt, 0, 1);
        System.out.printf("反向: (%.4f, %.4f)%n", backPt[0], backPt[1]);
    }
    
    public static void transformGeometry() throws Exception {
        GeometryFactory gf = JTSFactoryFinder.getGeometryFactory();
        
        // 创建 WGS84 点
        Point wgs84Point = gf.createPoint(new Coordinate(116.4074, 39.9042));
        
        // 获取转换器
        CoordinateReferenceSystem sourceCRS = CRS.decode("EPSG:4326", true);
        CoordinateReferenceSystem targetCRS = CRS.decode("EPSG:3857");
        MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS, true);
        
        // 转换几何
        Geometry webMercatorPoint = JTS.transform(wgs84Point, transform);
        System.out.println("转换后: " + webMercatorPoint);
        
        // 转换坐标
        Coordinate srcCoord = new Coordinate(116.4074, 39.9042);
        Coordinate dstCoord = JTS.transform(srcCoord, null, transform);
        System.out.println("坐标转换: " + dstCoord);
    }
}

10.3.2 转换复杂几何

public class GeometryReproject {
    
    public static Geometry reproject(Geometry geometry, 
            CoordinateReferenceSystem sourceCRS,
            CoordinateReferenceSystem targetCRS) throws Exception {
        
        MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS, true);
        return JTS.transform(geometry, transform);
    }
    
    public static SimpleFeatureCollection reprojectCollection(
            SimpleFeatureCollection fc,
            CoordinateReferenceSystem targetCRS) throws Exception {
        
        SimpleFeatureType sourceType = fc.getSchema();
        CoordinateReferenceSystem sourceCRS = sourceType.getCoordinateReferenceSystem();
        
        // 创建目标类型
        SimpleFeatureTypeBuilder typeBuilder = new SimpleFeatureTypeBuilder();
        typeBuilder.init(sourceType);
        typeBuilder.setCRS(targetCRS);
        SimpleFeatureType targetType = typeBuilder.buildFeatureType();
        
        // 获取转换器
        MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS, true);
        
        // 转换要素
        DefaultFeatureCollection result = new DefaultFeatureCollection();
        SimpleFeatureBuilder builder = new SimpleFeatureBuilder(targetType);
        
        try (SimpleFeatureIterator iter = fc.features()) {
            while (iter.hasNext()) {
                SimpleFeature feature = iter.next();
                
                for (int i = 0; i < feature.getAttributeCount(); i++) {
                    Object attr = feature.getAttribute(i);
                    if (attr instanceof Geometry) {
                        attr = JTS.transform((Geometry) attr, transform);
                    }
                    builder.add(attr);
                }
                
                result.add(builder.buildFeature(feature.getID()));
            }
        }
        
        return result;
    }
}

10.3.3 测地线计算

import org.geotools.referencing.GeodeticCalculator;

public class GeodeticOperations {
    
    public static void calculateDistance() throws Exception {
        GeodeticCalculator calculator = new GeodeticCalculator(
            CRS.decode("EPSG:4326", true)
        );
        
        // 设置起点(北京)
        calculator.setStartingGeographicPoint(116.4074, 39.9042);
        
        // 设置终点(上海)
        calculator.setDestinationGeographicPoint(121.4737, 31.2304);
        
        // 计算测地线距离
        double distance = calculator.getOrthodromicDistance();
        System.out.printf("北京到上海距离: %.2f 公里%n", distance / 1000);
        
        // 获取方位角
        double azimuth = calculator.getAzimuth();
        System.out.printf("方位角: %.2f 度%n", azimuth);
    }
    
    public static void calculateDestination() throws Exception {
        GeodeticCalculator calculator = new GeodeticCalculator(
            CRS.decode("EPSG:4326", true)
        );
        
        // 起点
        calculator.setStartingGeographicPoint(116.4074, 39.9042);
        
        // 设置方向和距离
        calculator.setDirection(90, 100000);  // 向东 100 公里
        
        // 获取目的地
        java.awt.geom.Point2D dest = calculator.getDestinationGeographicPoint();
        System.out.printf("目的地: (%.4f, %.4f)%n", dest.getX(), dest.getY());
    }
    
    public static double calculateArea(Polygon polygon, 
            CoordinateReferenceSystem crs) throws Exception {
        
        // 对于地理坐标系,需要投影到适当的投影坐标系计算面积
        if (crs.equals(CRS.decode("EPSG:4326"))) {
            // 获取多边形中心
            Point centroid = polygon.getCentroid();
            
            // 找到适当的 UTM 带
            int zone = (int) Math.floor((centroid.getX() + 180) / 6) + 1;
            String utmCode = centroid.getY() >= 0 ? 
                "EPSG:326" + String.format("%02d", zone) :  // 北半球
                "EPSG:327" + String.format("%02d", zone);   // 南半球
            
            CoordinateReferenceSystem utmCRS = CRS.decode(utmCode);
            MathTransform transform = CRS.findMathTransform(crs, utmCRS, true);
            
            Geometry projected = JTS.transform(polygon, transform);
            return projected.getArea();  // 平方米
        }
        
        return polygon.getArea();
    }
}

10.4 自定义 CRS

10.4.1 从 WKT 创建

public class CustomCRS {
    
    public static CoordinateReferenceSystem createFromWKT() throws Exception {
        // 自定义横轴墨卡托投影
        String wkt = "PROJCS[\"Custom_Transverse_Mercator\"," +
            "GEOGCS[\"GCS_WGS_1984\"," +
            "DATUM[\"D_WGS_1984\"," +
            "SPHEROID[\"WGS_1984\",6378137.0,298.257223563]]," +
            "PRIMEM[\"Greenwich\",0.0]," +
            "UNIT[\"Degree\",0.0174532925199433]]," +
            "PROJECTION[\"Transverse_Mercator\"]," +
            "PARAMETER[\"False_Easting\",500000.0]," +
            "PARAMETER[\"False_Northing\",0.0]," +
            "PARAMETER[\"Central_Meridian\",117.0]," +
            "PARAMETER[\"Scale_Factor\",1.0]," +
            "PARAMETER[\"Latitude_Of_Origin\",0.0]," +
            "UNIT[\"Meter\",1.0]]";
        
        return CRS.parseWKT(wkt);
    }
    
    public static CoordinateReferenceSystem createGaussKruger(int zone) throws Exception {
        // 中国高斯-克吕格投影
        double centralMeridian = zone * 3;  // 3度带中央经线
        
        String wkt = String.format(
            "PROJCS[\"CGCS2000 / 3-degree Gauss-Kruger zone %d\"," +
            "GEOGCS[\"China Geodetic Coordinate System 2000\"," +
            "DATUM[\"China_2000\"," +
            "SPHEROID[\"CGCS2000\",6378137,298.257222101]]," +
            "PRIMEM[\"Greenwich\",0]," +
            "UNIT[\"degree\",0.0174532925199433]]," +
            "PROJECTION[\"Transverse_Mercator\"]," +
            "PARAMETER[\"latitude_of_origin\",0]," +
            "PARAMETER[\"central_meridian\",%.1f]," +
            "PARAMETER[\"scale_factor\",1]," +
            "PARAMETER[\"false_easting\",%d]," +
            "PARAMETER[\"false_northing\",0]," +
            "UNIT[\"metre\",1]]",
            zone, centralMeridian, zone * 1000000 + 500000
        );
        
        return CRS.parseWKT(wkt);
    }
}

10.5 处理轴顺序

10.5.1 轴顺序问题

public class AxisOrderHandling {
    
    public static void handleAxisOrder() throws Exception {
        // EPSG:4326 标准顺序是 lat/lon,但很多软件使用 lon/lat
        
        // 默认(lat/lon)
        CoordinateReferenceSystem crs1 = CRS.decode("EPSG:4326");
        System.out.println("默认轴顺序: " + CRS.getAxisOrder(crs1));
        
        // 强制 lon/lat
        CoordinateReferenceSystem crs2 = CRS.decode("EPSG:4326", true);
        System.out.println("强制轴顺序: " + CRS.getAxisOrder(crs2));
        
        // 全局设置(不推荐,会影响整个应用)
        // System.setProperty("org.geotools.referencing.forceXY", "true");
        
        // 使用 Hints
        Hints hints = new Hints(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.TRUE);
        CoordinateReferenceSystem crs3 = CRS.decode("EPSG:4326", hints);
    }
    
    public static void transformWithAxisOrder() throws Exception {
        // 源数据是 lon/lat 顺序
        double lon = 116.4074;
        double lat = 39.9042;
        
        // 确保使用正确的轴顺序
        CoordinateReferenceSystem sourceCRS = CRS.decode("EPSG:4326", true);
        CoordinateReferenceSystem targetCRS = CRS.decode("EPSG:3857");
        
        MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS, true);
        
        double[] srcPt = {lon, lat};
        double[] dstPt = new double[2];
        transform.transform(srcPt, 0, dstPt, 0, 1);
        
        System.out.printf("转换结果: (%.2f, %.2f)%n", dstPt[0], dstPt[1]);
    }
}

10.6 批量重投影

10.6.1 重投影 FeatureCollection

public class BatchReproject {
    
    public static SimpleFeatureCollection reproject(
            SimpleFeatureCollection source,
            String targetEPSG) throws Exception {
        
        SimpleFeatureType sourceType = source.getSchema();
        CoordinateReferenceSystem sourceCRS = sourceType.getCoordinateReferenceSystem();
        CoordinateReferenceSystem targetCRS = CRS.decode(targetEPSG, true);
        
        if (CRS.equalsIgnoreMetadata(sourceCRS, targetCRS)) {
            return source;  // 无需转换
        }
        
        MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS, true);
        
        // 构建目标类型
        SimpleFeatureTypeBuilder typeBuilder = new SimpleFeatureTypeBuilder();
        typeBuilder.init(sourceType);
        typeBuilder.setCRS(targetCRS);
        SimpleFeatureType targetType = typeBuilder.buildFeatureType();
        
        DefaultFeatureCollection result = new DefaultFeatureCollection();
        SimpleFeatureBuilder builder = new SimpleFeatureBuilder(targetType);
        
        try (SimpleFeatureIterator iter = source.features()) {
            while (iter.hasNext()) {
                SimpleFeature feature = iter.next();
                
                for (int i = 0; i < feature.getAttributeCount(); i++) {
                    Object attr = feature.getAttribute(i);
                    if (attr instanceof Geometry) {
                        attr = JTS.transform((Geometry) attr, transform);
                    }
                    builder.add(attr);
                }
                
                result.add(builder.buildFeature(feature.getID()));
            }
        }
        
        return result;
    }
}

10.7 本章小结

本章详细介绍了坐标参考系统和投影转换:

  1. CRS 基础

    • 地理坐标系
    • 投影坐标系
    • EPSG 代码
  2. CRS 操作

    • 获取 CRS
    • CRS 比较
    • 信息查询
  3. 坐标转换

    • 基本转换
    • 几何转换
    • 测地线计算
  4. 高级特性

    • 自定义 CRS
    • 轴顺序处理
    • 批量重投影

关键要点

  • 注意轴顺序问题(lon/lat vs lat/lon)
  • 使用 CRS.decode(code, true) 强制经度优先
  • 地理坐标系下的面积需要投影后计算
  • 使用 GeodeticCalculator 进行精确的距离计算

← 上一章:数据库空间数据访问 | 返回目录 | 下一章:样式与符号化 →

posted @ 2025-12-29 11:40  我才是银古  阅读(8)  评论(0)    收藏  举报