03-坐标系统详解

坐标系统详解

概述

坐标参考系统(CRS,Coordinate Reference System)是GIS的基础概念之一。不同的坐标系会导致相同位置的坐标值完全不同,理解和正确使用坐标系是避免数据错误的关键。

坐标系分类

地理坐标系(Geographic CRS)

地理坐标系使用经度和纬度来表示地球表面的位置:

  • 经度(Longitude):东西方向,范围 -180° 到 180°
  • 纬度(Latitude):南北方向,范围 -90° 到 90°

常用地理坐标系

EPSG代码 名称 说明
4490 CGCS2000 中国2000国家大地坐标系
4326 WGS84 GPS使用的全球坐标系
4214 BJ54 北京54坐标系
4610 XA80 西安80坐标系

投影坐标系(Projected CRS)

投影坐标系将球面坐标转换为平面坐标,使用米作为单位:

常用投影类型

  • 高斯-克吕格投影:中国官方测绘使用
  • UTM投影:国际通用
  • 墨卡托投影:Web地图常用

CGCS2000投影坐标系

EPSG代码 带号 中央经线
4491 13 75°E
4502 24 120°E
4526 38 114°E

投影坐标系的命名规律:EPSG:4488 + 带号 = 投影坐标系代码

坐标系识别与管理

支持的坐标系列表

系统预置了常用的坐标系,并支持动态扩展:

/**
 * 获取支持的投影坐标系列表
 */
private static Map<Integer, CoordinateReferenceSystem> supportedCRSList() {
    if (supportedCRSList != null && !supportedCRSList.isEmpty()) {
        return supportedCRSList;
    }
    
    supportedCRSList = new HashMap<>();
    for (int i = 4490; i < 4555; i++) {
        supportedCRSList.put(i, CRS.decode("EPSG:" + i, true));
    }
    return supportedCRSList;
}

动态获取坐标系

对于不在预置列表中的坐标系,可以动态获取:

/**
 * 获取被支持的EPSG Code和对应坐标系信息
 * 如果不存在则增加到支持列表
 */
public static Map.Entry<Integer, CoordinateReferenceSystem> getSupportedCRS(Integer wkid) {
    if (!supportedCRSList().containsKey(wkid)) {
        CoordinateReferenceSystem crs = CRS.decode("EPSG:" + wkid, true);
        supportedCRSList().put(wkid, crs);
        return new HashMap.SimpleEntry<>(wkid, crs);
    }
    return new HashMap.SimpleEntry<>(wkid, supportedCRSList().get(wkid));
}

坐标系WKT解析

从WKT字符串解析坐标系:

/**
 * 在支持的坐标系范围内获取标准EPSG Code
 */
public static Map.Entry<Integer, CoordinateReferenceSystem> standardizeCRS(String wkt) {
    CoordinateReferenceSystem crs = CRS.parseWKT(wkt);
    return standardizeCRS(crs);
}

坐标系判断

判断是否为投影坐标系

/**
 * 判断坐标系是否为投影坐标系
 */
public static boolean isProjectedCRS(CoordinateReferenceSystem crs) {
    Map.Entry<Integer, CoordinateReferenceSystem> entry = standardizeCRS(crs);
    return entry.getValue() instanceof ProjectedCRS;
}

判断坐标系是否相同

不同来源的坐标系可能参数相同但定义方式不同,需要深度比较:

/**
 * 判断坐标系是否相同
 */
public static boolean isSameCRS(CoordinateReferenceSystem sourceCrs, 
                                CoordinateReferenceSystem targetCrs) {
    if (sourceCrs instanceof ProjectedCRS && targetCrs instanceof ProjectedCRS) {
        ProjectedCRS sourceProjectedCRS = (ProjectedCRS) sourceCrs;
        ProjectedCRS targetProjectedCRS = (ProjectedCRS) targetCrs;
        
        // 比较基础地理坐标系
        boolean isSameBaseCRS = isSameCRS(
            sourceProjectedCRS.getBaseCRS(), 
            targetProjectedCRS.getBaseCRS()
        );
        
        // 比较投影参数
        boolean isSameConversionFromBase = true;
        ParameterValueGroup sourceParams = sourceProjectedCRS
            .getConversionFromBase().getParameterValues();
        ParameterValueGroup targetParams = targetProjectedCRS
            .getConversionFromBase().getParameterValues();
        
        for (int i = 0; i < sourceParams.values().size(); i++) {
            GeneralParameterValue s = sourceParams.values().get(i);
            GeneralParameterValue t = targetParams.values().get(i);
            if (!s.equals(t)) {
                isSameConversionFromBase = false;
                break;
            }
        }
        return isSameBaseCRS && isSameConversionFromBase;
    } else if (sourceCrs instanceof GeographicCRS && 
               targetCrs instanceof GeographicCRS) {
        // 比较椭球体参数
        GeographicCRS sourceGeoCRS = (GeographicCRS) sourceCrs;
        GeographicCRS targetGeoCRS = (GeographicCRS) targetCrs;
        Ellipsoid s = sourceGeoCRS.getDatum().getEllipsoid();
        Ellipsoid t = targetGeoCRS.getDatum().getEllipsoid();
        
        return s.getSemiMajorAxis() == t.getSemiMajorAxis()
            && s.getSemiMinorAxis() == t.getSemiMinorAxis()
            && s.getInverseFlattening() == t.getInverseFlattening();
    }
    return false;
}

带号计算

中国的3度带投影需要根据经度确定带号:

从几何获取带号

/**
 * 获取几何所在带号
 */
public static int getDh(Geometry geometry) {
    Point point = geometry.getCentroid();
    int dh = 0;
    
    if (point.getX() < 180) {
        // 经纬度坐标,根据经度计算带号
        dh = (int) ((point.getX() + 1.5) / 3);
    } else if (point.getX() / 10000000 > 3) {
        // 投影坐标,从X坐标提取带号
        dh = (int) (point.getX() / 1000000);
    }
    return dh;
}

/**
 * 从WKT获取带号
 */
public static int getDh(String wkt) {
    Geometry geom = GeometryConverter.wkt2Geometry(wkt);
    return getDh(geom);
}

带号与投影坐标系换算

/**
 * 从投影坐标系WKID获取带号
 */
public static int getDh(int projectedWkid) {
    return projectedWkid - 4488;
}

/**
 * 从带号获取投影坐标系WKID
 */
public static Integer getProjectedWkid(int dh) {
    return 4488 + dh;
}

/**
 * 从几何获取对应的投影坐标系
 */
public static Integer getProjectedWkid(Geometry geometry) {
    return getProjectedWkid(getDh(geometry));
}

智能识别几何坐标系

/**
 * 获取几何WKID
 * 经纬度返回4490,投影坐标返回对应投影坐标系
 */
public static Integer getWkid(Geometry geometry) {
    Point point = geometry.getCentroid();
    if (point.getX() < 180) {
        return 4490;  // CGCS2000地理坐标系
    } else {
        return getProjectedWkid(getDh(geometry));
    }
}

坐标系容差

不同坐标系的容差不同:

/**
 * 获取坐标系容差
 */
public static double getTolerance(Integer wkid) {
    Map.Entry<Integer, CoordinateReferenceSystem> entry = getSupportedCRS(wkid);
    return getTolerance(entry.getValue());
}

public static double getTolerance(CoordinateReferenceSystem crs) {
    if (isProjectedCRS(crs)) {
        return 0.0001;      // 投影坐标系:0.1毫米
    } else {
        return 0.000000001; // 地理坐标系:约0.1毫米(赤道处)
    }
}

设计思路:容差的设置需要考虑坐标系的单位。投影坐标系以米为单位,0.0001米约等于0.1毫米;地理坐标系以度为单位,需要更小的数值来表达相同的实际距离。

坐标转换

几何对象坐标转换

/**
 * 几何对象转换坐标系
 */
public static Geometry transform(Geometry geometry, 
                                  Integer sourceWkid, 
                                  Integer targetWkid) {
    if (sourceWkid.equals(targetWkid)) {
        return geometry;
    }
    
    CoordinateReferenceSystem sourceCRS = getSupportedCRS(sourceWkid).getValue();
    CoordinateReferenceSystem targetCRS = getSupportedCRS(targetWkid).getValue();
    return transform(geometry, sourceCRS, targetCRS);
}

/**
 * 使用CRS对象进行坐标转换
 */
public static Geometry transform(Geometry geometry, 
                                  CoordinateReferenceSystem sourceCRS, 
                                  CoordinateReferenceSystem targetCRS) {
    if (isSameCRS(sourceCRS, targetCRS)) {
        return geometry;
    }
    
    MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS);
    return JTS.transform(geometry, transform);
}

WKT字符串坐标转换

/**
 * WKT格式的坐标转换
 */
public static String transform(String wkt, Integer sourceWkid, Integer targetWkid) {
    Geometry geom = GeometryConverter.wkt2Geometry(wkt);
    geom = transform(geom, sourceWkid, targetWkid);
    return geom == null ? null : geom.toText();
}

图层坐标转换

/**
 * 转换WktLayer的坐标系
 */
public static WktLayer reproject(WktLayer wktLayer, Integer targetWkid) {
    wktLayer.check();
    
    WktLayer clone = ObjectUtil.cloneByStream(wktLayer);
    if (clone.getWkid().equals(targetWkid)) {
        return clone;
    }
    
    // 转换每个要素的几何
    for (WktFeature feature : clone.getFeatures()) {
        Geometry geometry = new WKTReader().read(feature.getWkt());
        Geometry targetGeometry = transform(geometry, wktLayer.getWkid(), targetWkid);
        feature.setWkt(targetGeometry.toText());
    }
    
    // 更新图层信息
    clone.setWkid(targetWkid);
    clone.setTolerance(getTolerance(targetWkid));
    return clone;
}

要素集合坐标转换

/**
 * 转换FeatureCollection的坐标系
 */
public static FeatureCollection<SimpleFeatureType, SimpleFeature> reproject(
        FeatureCollection<SimpleFeatureType, SimpleFeature> featureCollection, 
        Integer targetWkid) {
    
    CoordinateReferenceSystem sourceCRS = standardizeCRS(
        featureCollection.getSchema().getCoordinateReferenceSystem()
    ).getValue();
    CoordinateReferenceSystem targetCRS = getSupportedCRS(targetWkid).getValue();
    
    if (isSameCRS(sourceCRS, targetCRS)) {
        return featureCollection;
    }
    
    // 设置源坐标系
    if (sourceCRS != null) {
        featureCollection = new ForceCoordinateSystemFeatureResults(
            featureCollection, sourceCRS, false
        );
    }
    
    // 转换到目标坐标系
    if (targetCRS != null) {
        featureCollection = new ReprojectingFeatureCollection(
            featureCollection, targetCRS
        );
    }
    
    return featureCollection;
}

实践案例

案例1:经纬度转投影坐标

// 某地块的经纬度坐标
String wkt = "POLYGON((116.3 39.9, 116.4 39.9, 116.4 40.0, 116.3 40.0, 116.3 39.9))";
Geometry geom = GeometryConverter.wkt2Geometry(wkt);

// 确定带号(116.3°E位于39带)
int dh = CrsUtil.getDh(geom);
System.out.println("带号: " + dh);

// 获取投影坐标系
int projWkid = CrsUtil.getProjectedWkid(dh);
System.out.println("投影坐标系: EPSG:" + projWkid);

// 坐标转换
Geometry projGeom = CrsUtil.transform(geom, 4490, projWkid);
System.out.println("投影后坐标: " + projGeom.toText());

// 计算面积(投影坐标系下计算才准确)
double area = projGeom.getArea();
System.out.println("面积: " + area + " 平方米");

案例2:不同坐标系数据统一

// 数据源1:CGCS2000地理坐标系
WktLayer layer1 = loadLayer("data1.shp");  // wkid = 4490

// 数据源2:CGCS2000 38带投影坐标系
WktLayer layer2 = loadLayer("data2.shp");  // wkid = 4526

// 统一转换为地理坐标系
WktLayer layer2Reprojected = CrsUtil.reproject(layer2, 4490);

// 现在可以进行空间叠加分析
for (WktFeature f1 : layer1.getFeatures()) {
    Geometry g1 = GeometryConverter.wkt2Geometry(f1.getWkt());
    for (WktFeature f2 : layer2Reprojected.getFeatures()) {
        Geometry g2 = GeometryConverter.wkt2Geometry(f2.getWkt());
        if (g1.intersects(g2)) {
            // 处理相交要素
        }
    }
}

案例3:Web展示坐标准备

// 从数据库读取数据(投影坐标)
WktLayer layer = loadFromPostGIS(dbConn, "buildings");  // wkid = 4526

// 转换为经纬度(Web地图通常使用WGS84或CGCS2000)
WktLayer webLayer = CrsUtil.reproject(layer, 4490);

// 转换为GeoJSON供前端使用
WktLayerConverter.toGeoJSON(webLayer, "buildings.geojson", GisEngineType.GEOTOOLS);

常见问题

1. 坐标系不匹配导致的问题

现象:叠加分析结果为空,或计算结果异常偏大/偏小

原因:两组数据使用了不同的坐标系

解决:在进行空间分析前,将所有数据统一到同一坐标系

2. 面积计算结果不正确

现象:计算的面积值是一个很小的数或很大的数

原因:使用经纬度坐标直接计算面积

解决:先将数据转换到投影坐标系,再计算面积

3. 带号选择

问题:如何选择合适的带号?

建议:根据数据的主要分布区域选择中央经线最接近的带:

  • 查看数据的经度范围
  • 选择中心经度对应的带号
  • 3度带:带号 = (经度 + 1.5) / 3

4. CGCS2000与WGS84

问题:是否需要区分CGCS2000和WGS84?

说明

  • CGCS2000和WGS84的差异在厘米级,一般应用可以视为相同
  • 高精度测量场景需要严格区分
  • 互联网地图通常使用WGS84,国内官方数据使用CGCS2000

小结

本章介绍了坐标系统的核心知识:

  1. 坐标系分类:地理坐标系(经纬度)和投影坐标系(平面米)
  2. 带号计算:中国3度带投影的带号确定方法
  3. 坐标转换:不同坐标系间的数据转换方法
  4. 实践要点:面积计算需使用投影坐标系,空间分析需统一坐标系

下一章将介绍空间关系分析,学习如何判断几何对象之间的空间关系。

posted @ 2025-11-26 20:20  我才是银古  阅读(0)  评论(0)    收藏  举报