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
小结
本章介绍了坐标系统的核心知识:
- 坐标系分类:地理坐标系(经纬度)和投影坐标系(平面米)
- 带号计算:中国3度带投影的带号确定方法
- 坐标转换:不同坐标系间的数据转换方法
- 实践要点:面积计算需使用投影坐标系,空间分析需统一坐标系
下一章将介绍空间关系分析,学习如何判断几何对象之间的空间关系。

浙公网安备 33010602011771号