惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
DataBreaches.Net
人人都是产品经理
人人都是产品经理
爱范儿
爱范儿
WordPress大学
WordPress大学
T
Tor Project blog
Jina AI
Jina AI
美团技术团队
酷 壳 – CoolShell
酷 壳 – CoolShell
V
V2EX
雷峰网
雷峰网
博客园 - 聂微东
B
Blog RSS Feed
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - 【当耐特】
Microsoft Azure Blog
Microsoft Azure Blog
S
SegmentFault 最新的问题
Recent Announcements
Recent Announcements
有赞技术团队
有赞技术团队
GbyAI
GbyAI
aimingoo的专栏
aimingoo的专栏
U
Unit 42
博客园 - Franky
Last Week in AI
Last Week in AI
阮一峰的网络日志
阮一峰的网络日志
Microsoft Security Blog
Microsoft Security Blog
F
Fortinet All Blogs
罗磊的独立博客
云风的 BLOG
云风的 BLOG
G
Google Developers Blog
大猫的无限游戏
大猫的无限游戏
Engineering at Meta
Engineering at Meta
C
Check Point Blog
Martin Fowler
Martin Fowler
The Cloudflare Blog
N
Netflix TechBlog - Medium
小众软件
小众软件
T
Tailwind CSS Blog
T
The Blog of Author Tim Ferriss
月光博客
月光博客
博客园 - 司徒正美
Stack Overflow Blog
Stack Overflow Blog
J
Java Code Geeks
Blog — PlanetScale
Blog — PlanetScale
P
Proofpoint News Feed
H
Help Net Security
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
The Exploit Database - CXSecurity.com
S
Securelist
I
Intezer
Spread Privacy
Spread Privacy

博客园 - 我才是银古

第16章:常见问题、排错与最佳实践 第15章:扩展生态、MCAD 与外部集成 第12章:实战案例:机械结构与 3D 打印零件 第14章:构建、测试、调试与贡献流程 第13章:OpenSCAD 源码架构与核心执行流程 第11章:预览、渲染、网格精度与性能优化 第09章:列表推导、递归与算法建模 第08章:参数化零件库与复用设计 第10章:导入导出、命令行与自动化 第06章:CSG 布尔建模方法 第07章:二维图形、拉伸、旋转与投影 第05章:基础几何、坐标系与变换 第04章:参数、变量、函数、模块与作用域 OpenSCAD 教程目录 第03章:OpenSCAD 语言基础 第02章:安装、环境配置与开发工作流 第01章:OpenSCAD 项目全景与学习路线 第02章:源码获取、编译与开发环境配置 第01章:OCCT项目全景与学习路线 第18章:二次开发实战与综合案例 第18章:综合实战案例 第17章:数据交换与协同 第16章:源码架构与二次开发 第15章:插件与自定义工作台开发 第14章:Python脚本宏与自动化 第13章:FEM仿真分析 第12章:CAM数控加工 第11章:SurfaceMesh与逆向工程 第10章:Draft二维绘图与BIM建筑 第09章:工程图TechDraw 第07章:参数化表达式与Spreadsheet 第08章:装配设计Assembly 第06章:Part工作台与几何内核 第05章:PartDesign实体特征建模 第04章:草图Sketcher约束建模 第02章:安装版本与工作环境配置 第03章:界面工作台与基础操作 第01章:项目全景与学习路线 第十二章:插件开发、研究功能与最佳实践 第十章:定时任务与自动化(Cron) 第七章:技能、记忆与自学习闭环 第八章:MCP 集成与上下文文件 第六章:工具系统与终端后端 第五章:模型供应商与配置体系 Hermes Agent 教程目录 第十一章:语音、视觉、浏览器与子代理协作 第四章:CLI/TUI 与会话管理 第十二章:学习路线、实战方案与最佳实践 第十一章:源码结构、开发调试与插件开发 第十章:自动化、远程访问、日志与排障 第九章:Control UI、节点、Canvas 与语音能力 第七章:工具、技能、插件与能力扩展 第八章:安全模型、访问控制与沙箱实践 第六章:Agent 工作区、会话与多智能体路由 第五章:多通道消息接入与聊天平台配置 第四章:配置体系、模型接入与认证管理 第三章:Gateway 架构、协议与运行机制 第二章:安装、环境准备与快速上手 第一章:OpenClaw 项目概览与核心定位 oh-my-openagent 教程目录 09-命令模型回退与配置参考 10-实战案例最佳实践与故障排除 05-工作模式-Ultrawork-Prometheus-Atlas 08-Hooks与MCP系统 06-Category与Skill系统 07-核心工具链 04-智能体全景详解 03-安装与环境配置 02-整体架构与多模型编排机制 01-项目简介与核心理念 01-项目概览与学习路线 02-安装部署与工具适配 03-Skill机制与using-superpowers 05-TDD系统化调试与完成前验证 04-需求澄清方案设计与计划编写 07-并行智能体子智能体与Git-Worktree 第六章:代码审查、反馈处理与分支收尾 08-中国特色Skills与本土团队落地 09-MCP构建工作流执行与自定义Skill 第23章:FreeCAD-Python-API Clipper2 C# 源码解读教程 第19章:PolyTree 多边形树结构 第20章:实际应用与最佳实践 第18章:Minkowski 和与差 第17章:RectClip 矩形裁剪优化 第16章:ClipperOffset 偏移类详解 第15章:填充规则详解 第14章:布尔运算执行流程 第13章:ClipperD 浮点裁剪类 第11章:OutRec 与 OutPt 输出结构 第9章:Active 活动边结构 第10章:Vertex 顶点与 LocalMinima 局部极小值 第12章:Clipper64 裁剪类详解 第7章:高精度运算与128位整数 第8章:ClipperBase 基类详解 第5章:枚举类型与常量定义 第6章:InternalClipper 内部工具类 第2章:核心数据结构 - Point64、PointD 第3章:路径与多边形表示 - Path64、PathD、Paths64、PathsD 第4章:矩形边界 - Rect64、RectD
坐标系统详解
我才是银古 · 2026-06-22 · via 博客园 - 我才是银古

坐标系统详解

概述

坐标参考系统(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. 实践要点:面积计算需使用投影坐标系,空间分析需统一坐标系

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