坐标偏移算法

This commit is contained in:
PC 2026-01-16 11:25:03 +08:00
parent 513e0e45fc
commit 5c7fb64ad8
6 changed files with 302 additions and 160 deletions

View File

@ -5,7 +5,10 @@ import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.io.WKTReader;
import java.io.IOException;
@ -14,7 +17,6 @@ import java.util.Locale;
/**
* JTS Point 反序列化器 - JSON 反序列化为 Point
* 支持 typelongitudelatitude 三个字段的格式
* @author attiya
*/
@Slf4j
public class PointDeserializer extends JsonDeserializer<Point> {

View File

@ -104,4 +104,5 @@ public class PointSerializer extends JsonSerializer<Point> {
}
}
}
}

View File

@ -1,18 +1,17 @@
package com.cdzy.operations.handler;
import com.cdzy.common.utils.CoordinateUtil;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.io.WKTReader;
import org.postgresql.util.PGobject;
import java.sql.*;
/**
* JTS Point 类型处理器 - 用于 PostgreSQL PostGIS Point 类型
* 直接存储和读取 WGS-84 坐标系坐标
* @author attiya
*/
public class PointTypeHandler implements TypeHandler<Point> {
private static final int WGS84_SRID = 4326;
@ -25,19 +24,18 @@ public class PointTypeHandler implements TypeHandler<Point> {
ps.setNull(i, Types.OTHER);
} else {
try {
// 创建 PGobject 并设置 PostGIS 类型
// 写入时GCJ-02 WGS-84
Point wgs84Point = gcj02ToWgs84(parameter);
PGobject pgObject = new PGobject();
pgObject.setType("geometry");
// 使用 WKT 格式设置正确的 SRID
parameter.setSRID(WGS84_SRID);
String wkt = parameter.toText();
String wktWithSRID = "SRID=" + WGS84_SRID + ";" + wkt;
pgObject.setValue(wktWithSRID);
String wkt = "SRID=" + WGS84_SRID + ";" + wgs84Point.toText();
pgObject.setValue(wkt);
ps.setObject(i, pgObject);
} catch (Exception e) {
System.err.println("Point 设置参数失败: " + e.getMessage());
System.err.println("Point 坐标转换失败: " + e.getMessage());
ps.setNull(i, Types.OTHER);
}
}
@ -46,62 +44,37 @@ public class PointTypeHandler implements TypeHandler<Point> {
@Override
public Point getResult(ResultSet rs, String columnName) throws SQLException {
Object object = rs.getObject(columnName);
return convertToPoint(object);
Point wgs84Point = convertToPoint(object);
return wgs84ToGcj02(wgs84Point);
}
@Override
public Point getResult(ResultSet rs, int columnIndex) throws SQLException {
Object object = rs.getObject(columnIndex);
return convertToPoint(object);
Point wgs84Point = convertToPoint(object);
return wgs84ToGcj02(wgs84Point);
}
@Override
public Point getResult(CallableStatement cs, int columnIndex) throws SQLException {
Object object = cs.getObject(columnIndex);
return convertToPoint(object);
Point wgs84Point = convertToPoint(object);
return wgs84ToGcj02(wgs84Point);
}
/**
* 将数据库对象转换为 Point
*/
private Point convertToPoint(Object object) {
if (object == null) {
return null;
}
if (object == null) return null;
try {
if (object instanceof PGobject pgObject) {
return parseFromPGobject(pgObject);
} else if (object instanceof String str) {
return parseFromString(str);
}
} catch (Exception e) {
System.err.println("转换 Point 失败: " + e.getMessage() +
", 对象类型: " + object.getClass().getName());
}
return null;
}
/**
* PGobject 解析 Point
*/
private Point parseFromPGobject(PGobject pgObject) throws Exception {
if (pgObject == null || pgObject.getValue() == null) {
return null;
}
String value = pgObject.getValue().trim();
String value = pgObject.getValue();
if (value == null) return null;
// 解析 WKT 格式SRID=4326;POINT(lng lat)
if (value.contains("POINT")) {
String wkt = value;
// 去除 SRID 前缀
if (value.startsWith("SRID=")) {
int semicolonIndex = value.indexOf(';');
if (semicolonIndex > 0) {
wkt = value.substring(semicolonIndex + 1);
}
wkt = value.substring(value.indexOf(';') + 1);
}
WKTReader reader = new WKTReader(geometryFactory);
@ -111,55 +84,60 @@ public class PointTypeHandler implements TypeHandler<Point> {
return point;
}
}
return null;
}
/**
* 从字符串解析 Point
*/
private Point parseFromString(String str) throws Exception {
if (str == null || str.trim().isEmpty()) {
return null;
}
str = str.trim();
// 尝试解析 WKT 格式
} else if (object instanceof String str) {
WKTReader reader = new WKTReader(geometryFactory);
Geometry geometry = reader.read(str);
if (geometry instanceof Point point) {
point.setSRID(WGS84_SRID);
return point;
}
}
} catch (Exception e) {
System.err.println("转换 Point 失败: " + e.getMessage());
}
return null;
}
/**
* 设置多边形到 PreparedStatement辅助方法
*/
private void setPointToStatement(PreparedStatement ps, int i, Point point)
throws SQLException {
if (point == null) {
ps.setNull(i, Types.OTHER);
return;
private Point gcj02ToWgs84(Point gcj02Point) {
if (gcj02Point == null || gcj02Point.isEmpty()) {
return gcj02Point;
}
try {
PGobject pgObject = new PGobject();
pgObject.setType("geometry");
Coordinate coord = gcj02Point.getCoordinate();
double[] wgs84 = CoordinateUtil.GCJ02ToWGS84(coord.x, coord.y);
// 使用 WKT 格式设置正确的 SRID
point.setSRID(WGS84_SRID);
String wkt = point.toText();
String wktWithSRID = "SRID=" + WGS84_SRID + ";" + wkt;
pgObject.setValue(wktWithSRID);
Point wgs84Point = geometryFactory.createPoint(
new Coordinate(wgs84[0], wgs84[1])
);
wgs84Point.setSRID(WGS84_SRID);
ps.setObject(i, pgObject);
return wgs84Point;
} catch (Exception e) {
System.err.println("设置 Point 失败: " + e.getMessage());
ps.setNull(i, Types.OTHER);
System.err.println("GCJ-02 → WGS-84 点坐标转换失败: " + e.getMessage());
gcj02Point.setSRID(WGS84_SRID);
return gcj02Point;
}
}
private Point wgs84ToGcj02(Point wgs84Point) {
if (wgs84Point == null || wgs84Point.isEmpty()) {
return wgs84Point;
}
try {
Coordinate coord = wgs84Point.getCoordinate();
double[] gcj02 = CoordinateUtil.WGS84ToGCJ02(coord.x, coord.y);
Point gcj02Point = geometryFactory.createPoint(
new Coordinate(gcj02[0], gcj02[1])
);
gcj02Point.setSRID(WGS84_SRID);
return gcj02Point;
} catch (Exception e) {
throw new RuntimeException("WGS-84 → GCJ-02 点坐标转换失败: " + e.getMessage());
}
}
}

View File

@ -129,6 +129,7 @@ public class PolygonSerializer extends JsonSerializer<Polygon> {
try {
String wkt = wktWriter.write(polygon);
// 转换为与 PGpolygon 相似的格式
if (wkt.startsWith("POLYGON")) {
// 提取坐标部分
String coordsPart = wkt.substring(wkt.indexOf("((") + 2, wkt.lastIndexOf("))"));

View File

@ -1,5 +1,7 @@
package com.cdzy.operations.handler;
import com.cdzy.common.ex.EbikeException;
import com.cdzy.common.utils.CoordinateUtil;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
import org.locationtech.jts.geom.*;
@ -13,8 +15,10 @@ import java.util.ArrayList;
import java.util.List;
/**
* JTS Polygon 类型处理器 - 用于 PostgreSQL PostGIS Polygon 类型
* 直接存储和读取 WGS-84 坐标系的多边形
* JTS Polygon 类型处理器 - 集成 CoordinateUtil 实现坐标转换
* 写入时GCJ-02 WGS-84
* 读取时WGS-84 GCJ-02
* 支持 PostgreSQL + PostGIS geometry(Polygon, 4326) 类型
*/
public class PolygonTypeHandler implements TypeHandler<Polygon> {
@ -30,40 +34,52 @@ public class PolygonTypeHandler implements TypeHandler<Polygon> {
ps.setNull(i, Types.OTHER);
} else {
try {
// 写入数据库时GCJ-02 WGS-84
Polygon wgs84Polygon = gcj02ToWgs84(parameter);
if (wgs84Polygon != null) {
// 创建 PGobject 并设置 PostGIS 类型
PGobject pgObject = new PGobject();
pgObject.setType("geometry");
// 使用 WKT 格式设置正确的 SRID
parameter.setSRID(WGS84_SRID);
String wkt = parameter.toText();
String wkt = wgs84Polygon.toText();
String wktWithSRID = "SRID=" + WGS84_SRID + ";" + wkt;
pgObject.setValue(wktWithSRID);
ps.setObject(i, pgObject);
} catch (Exception e) {
System.err.println("Polygon 设置参数失败: " + e.getMessage());
} else {
ps.setNull(i, Types.OTHER);
}
} catch (Exception e) {
// 如果转换失败直接使用原始值不带坐标转换
System.err.println("坐标转换失败,使用原始值: " + e.getMessage());
setPolygonWithoutConversion(ps, i, parameter);
}
}
}
@Override
public Polygon getResult(ResultSet rs, String columnName) throws SQLException {
Object object = rs.getObject(columnName);
return convertToPolygon(object);
Polygon wgs84Polygon = convertToPolygon(object);
// 读取时WGS-84 GCJ-02
return wgs84ToGcj02(wgs84Polygon);
}
@Override
public Polygon getResult(ResultSet rs, int columnIndex) throws SQLException {
Object object = rs.getObject(columnIndex);
return convertToPolygon(object);
Polygon wgs84Polygon = convertToPolygon(object);
// 读取时WGS-84 GCJ-02
return wgs84ToGcj02(wgs84Polygon);
}
@Override
public Polygon getResult(CallableStatement cs, int columnIndex) throws SQLException {
Object object = cs.getObject(columnIndex);
return convertToPolygon(object);
Polygon wgs84Polygon = convertToPolygon(object);
// 读取时WGS-84 GCJ-02
return wgs84ToGcj02(wgs84Polygon);
}
/**
@ -179,7 +195,151 @@ public class PolygonTypeHandler implements TypeHandler<Polygon> {
}
/**
* 设置多边形到 PreparedStatement辅助方法
* GCJ-02 WGS-84写入数据库时调用
*/
private Polygon gcj02ToWgs84(Polygon gcj02Polygon) {
if (gcj02Polygon == null || gcj02Polygon.isEmpty()) {
return gcj02Polygon;
}
try {
// 获取多边形外环坐标
Coordinate[] coordinates = gcj02Polygon.getExteriorRing().getCoordinates();
if (coordinates == null || coordinates.length == 0) {
return gcj02Polygon;
}
List<Coordinate> wgs84Coordinates = new ArrayList<>();
// 对每个坐标点进行转换
for (Coordinate gcj02Coord : coordinates) {
double[] wgs84 = CoordinateUtil.GCJ02ToWGS84(gcj02Coord.x, gcj02Coord.y);
wgs84Coordinates.add(new Coordinate(wgs84[0], wgs84[1]));
}
// 确保多边形闭合
Coordinate first = wgs84Coordinates.get(0);
Coordinate last = wgs84Coordinates.get(wgs84Coordinates.size() - 1);
if (!first.equals2D(last)) {
wgs84Coordinates.add(new Coordinate(first.x, first.y));
}
// 创建 WGS-84 多边形
Coordinate[] wgs84Array = wgs84Coordinates.toArray(new Coordinate[0]);
LinearRing shell = geometryFactory.createLinearRing(wgs84Array);
// 如果有孔洞也需要转换
int numHoles = gcj02Polygon.getNumInteriorRing();
LinearRing[] holes = new LinearRing[numHoles];
for (int i = 0; i < numHoles; i++) {
LineString holeRing = gcj02Polygon.getInteriorRingN(i);
Coordinate[] holeCoords = holeRing.getCoordinates();
List<Coordinate> wgs84HoleCoords = new ArrayList<>();
for (Coordinate holeCoord : holeCoords) {
double[] wgs84Hole = CoordinateUtil.GCJ02ToWGS84(holeCoord.x, holeCoord.y);
wgs84HoleCoords.add(new Coordinate(wgs84Hole[0], wgs84Hole[1]));
}
// 确保孔洞闭合
Coordinate holeFirst = wgs84HoleCoords.get(0);
Coordinate holeLast = wgs84HoleCoords.get(wgs84HoleCoords.size() - 1);
if (!holeFirst.equals2D(holeLast)) {
wgs84HoleCoords.add(new Coordinate(holeFirst.x, holeFirst.y));
}
holes[i] = geometryFactory.createLinearRing(
wgs84HoleCoords.toArray(new Coordinate[0])
);
}
Polygon wgs84Polygon = geometryFactory.createPolygon(shell, holes);
wgs84Polygon.setSRID(WGS84_SRID);
System.out.println("GCJ-02 → WGS-84 转换成功,点数: " + wgs84Coordinates.size());
return wgs84Polygon;
} catch (Exception e) {
System.err.println("GCJ-02 → WGS-84 坐标系转换错误: " + e.getMessage());
// 转换失败时返回原始多边形但设置正确的 SRID
gcj02Polygon.setSRID(WGS84_SRID);
return gcj02Polygon;
}
}
/**
* WGS-84 GCJ-02从数据库读取时调用
*/
private Polygon wgs84ToGcj02(Polygon wgs84Polygon) {
if (wgs84Polygon == null || wgs84Polygon.isEmpty()) {
return wgs84Polygon;
}
try {
// 获取多边形外环坐标
Coordinate[] coordinates = wgs84Polygon.getExteriorRing().getCoordinates();
if (coordinates == null || coordinates.length == 0) {
return wgs84Polygon;
}
List<Coordinate> gcj02Coordinates = new ArrayList<>();
// 对每个坐标点进行转换
for (Coordinate wgs84Coord : coordinates) {
double[] gcj02 = CoordinateUtil.WGS84ToGCJ02(wgs84Coord.x, wgs84Coord.y);
gcj02Coordinates.add(new Coordinate(gcj02[0], gcj02[1]));
}
// 确保多边形闭合
Coordinate first = gcj02Coordinates.get(0);
Coordinate last = gcj02Coordinates.get(gcj02Coordinates.size() - 1);
if (!first.equals2D(last)) {
gcj02Coordinates.add(new Coordinate(first.x, first.y));
}
// 创建 GCJ-02 多边形
Coordinate[] gcj02Array = gcj02Coordinates.toArray(new Coordinate[0]);
LinearRing shell = geometryFactory.createLinearRing(gcj02Array);
// 如果有孔洞也需要转换
int numHoles = wgs84Polygon.getNumInteriorRing();
LinearRing[] holes = new LinearRing[numHoles];
for (int i = 0; i < numHoles; i++) {
LineString holeRing = wgs84Polygon.getInteriorRingN(i);
Coordinate[] holeCoords = holeRing.getCoordinates();
List<Coordinate> gcj02HoleCoords = new ArrayList<>();
for (Coordinate holeCoord : holeCoords) {
double[] gcj02Hole = CoordinateUtil.WGS84ToGCJ02(holeCoord.x, holeCoord.y);
gcj02HoleCoords.add(new Coordinate(gcj02Hole[0], gcj02Hole[1]));
}
// 确保孔洞闭合
Coordinate holeFirst = gcj02HoleCoords.get(0);
Coordinate holeLast = gcj02HoleCoords.get(gcj02HoleCoords.size() - 1);
if (!holeFirst.equals2D(holeLast)) {
gcj02HoleCoords.add(new Coordinate(holeFirst.x, holeFirst.y));
}
holes[i] = geometryFactory.createLinearRing(
gcj02HoleCoords.toArray(new Coordinate[0])
);
}
Polygon gcj02Polygon = geometryFactory.createPolygon(shell, holes);
gcj02Polygon.setSRID(WGS84_SRID); // 保持 SRID 不变
return gcj02Polygon;
} catch (Exception e) {
throw new EbikeException("WGS-84 → GCJ-02 坐标系转换错误: " + e.getMessage());
}
}
/**
* 设置多边形到 PreparedStatement不进行坐标转换
*/
private void setPolygonWithoutConversion(PreparedStatement ps, int i, Polygon polygon)
throws SQLException {

View File

@ -41,7 +41,7 @@ public class ReoprtHandler {
public void reportHandler(JsonNode response) {
int c = response.get("c").asInt();
JsonNode param = response.get("param");
String deviceId = param.get("SN").asText();
String deviceId = response.get("SN").asText();
switch (c) {
case 56:
gpsMsgHandler(param, deviceId);