坐标偏移算法

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,43 +1,41 @@
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;
private final GeometryFactory geometryFactory = new GeometryFactory();
@Override
public void setParameter(PreparedStatement ps, int i, Point parameter, JdbcType jdbcType)
public void setParameter(PreparedStatement ps, int i, Point parameter, JdbcType jdbcType)
throws SQLException {
if (parameter == null) {
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,120 +44,100 @@ 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);
String value = pgObject.getValue();
if (value == null) return null;
// 解析 WKT 格式SRID=4326;POINT(lng lat)
if (value.contains("POINT")) {
String wkt = value;
if (value.startsWith("SRID=")) {
wkt = value.substring(value.indexOf(';') + 1);
}
WKTReader reader = new WKTReader(geometryFactory);
Geometry geometry = reader.read(wkt);
if (geometry instanceof Point point) {
point.setSRID(WGS84_SRID);
return point;
}
}
} 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();
// 解析 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);
WKTReader reader = new WKTReader(geometryFactory);
Geometry geometry = reader.read(str);
if (geometry instanceof Point point) {
point.setSRID(WGS84_SRID);
return point;
}
}
WKTReader reader = new WKTReader(geometryFactory);
Geometry geometry = reader.read(wkt);
if (geometry instanceof Point point) {
point.setSRID(WGS84_SRID);
return point;
}
}
return null;
}
/**
* 从字符串解析 Point
*/
private Point parseFromString(String str) throws Exception {
if (str == null || str.trim().isEmpty()) {
return null;
}
str = str.trim();
// 尝试解析 WKT 格式
WKTReader reader = new WKTReader(geometryFactory);
Geometry geometry = reader.read(str);
if (geometry instanceof Point point) {
point.setSRID(WGS84_SRID);
return point;
}
return null;
}
/**
* 设置多边形到 PreparedStatement辅助方法
*/
private void setPointToStatement(PreparedStatement ps, int i, Point point)
throws SQLException {
if (point == null) {
ps.setNull(i, Types.OTHER);
return;
}
try {
PGobject pgObject = new PGobject();
pgObject.setType("geometry");
// 使用 WKT 格式设置正确的 SRID
point.setSRID(WGS84_SRID);
String wkt = point.toText();
String wktWithSRID = "SRID=" + WGS84_SRID + ";" + wkt;
pgObject.setValue(wktWithSRID);
ps.setObject(i, pgObject);
} catch (Exception e) {
System.err.println("设置 Point 失败: " + e.getMessage());
ps.setNull(i, Types.OTHER);
System.err.println("转换 Point 失败: " + e.getMessage());
}
return null;
}
private Point gcj02ToWgs84(Point gcj02Point) {
if (gcj02Point == null || gcj02Point.isEmpty()) {
return gcj02Point;
}
try {
Coordinate coord = gcj02Point.getCoordinate();
double[] wgs84 = CoordinateUtil.GCJ02ToWGS84(coord.x, coord.y);
Point wgs84Point = geometryFactory.createPoint(
new Coordinate(wgs84[0], wgs84[1])
);
wgs84Point.setSRID(WGS84_SRID);
return wgs84Point;
} catch (Exception e) {
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

@ -17,11 +17,11 @@ import java.io.IOException;
*/
@Slf4j
public class PolygonSerializer extends JsonSerializer<Polygon> {
private final WKTWriter wktWriter = new WKTWriter();
@Override
public void serialize(Polygon polygon, JsonGenerator gen, SerializerProvider serializers)
public void serialize(Polygon polygon, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
if (polygon == null) {
gen.writeNull();
@ -103,7 +103,7 @@ public class PolygonSerializer extends JsonSerializer<Polygon> {
}
return exteriorRing.getCoordinates();
} catch (Exception e) {
throw new IOException("获取多边形坐标失败: " + e.getMessage(), e);
}
@ -128,15 +128,16 @@ 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("))"));
String[] points = coordsPart.split(", ");
gen.writeStartObject();
gen.writeStringField("type", "polygon");
gen.writeArrayFieldStart("coordinates");
for (String point : points) {
String[] xy = point.split(" ");
@ -144,7 +145,7 @@ public class PolygonSerializer extends JsonSerializer<Polygon> {
try {
double x = Double.parseDouble(xy[0]);
double y = Double.parseDouble(xy[1]);
gen.writeStartArray();
gen.writeNumber(x);
gen.writeNumber(y);
@ -160,7 +161,7 @@ public class PolygonSerializer extends JsonSerializer<Polygon> {
// 直接写 WKT 字符串
gen.writeString(wkt);
}
} catch (Exception e) {
log.error("WKT 序列化失败: {}", e.getMessage());
throw new IOException("Polygon 序列化失败", e);

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> {
@ -24,26 +28,32 @@ public class PolygonTypeHandler implements TypeHandler<Polygon> {
private final WKBReader wkbReader = new WKBReader(geometryFactory);
@Override
public void setParameter(PreparedStatement ps, int i, Polygon parameter, JdbcType jdbcType)
public void setParameter(PreparedStatement ps, int i, Polygon parameter, JdbcType jdbcType)
throws SQLException {
if (parameter == null) {
ps.setNull(i, Types.OTHER);
} else {
try {
// 创建 PGobject 并设置 PostGIS 类型
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);
ps.setObject(i, pgObject);
// 写入数据库时GCJ-02 WGS-84
Polygon wgs84Polygon = gcj02ToWgs84(parameter);
if (wgs84Polygon != null) {
// 创建 PGobject 并设置 PostGIS 类型
PGobject pgObject = new PGobject();
pgObject.setType("geometry");
// 使用 WKT 格式设置正确的 SRID
String wkt = wgs84Polygon.toText();
String wktWithSRID = "SRID=" + WGS84_SRID + ";" + wkt;
pgObject.setValue(wktWithSRID);
ps.setObject(i, pgObject);
} else {
ps.setNull(i, Types.OTHER);
}
} catch (Exception e) {
System.err.println("Polygon 设置参数失败: " + e.getMessage());
ps.setNull(i, Types.OTHER);
// 如果转换失败直接使用原始值不带坐标转换
System.err.println("坐标转换失败,使用原始值: " + e.getMessage());
setPolygonWithoutConversion(ps, i, parameter);
}
}
}
@ -51,19 +61,25 @@ public class PolygonTypeHandler implements TypeHandler<Polygon> {
@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);
}
/**
@ -85,8 +101,8 @@ public class PolygonTypeHandler implements TypeHandler<Polygon> {
return (Polygon) object;
}
} catch (Exception e) {
System.err.println("转换 Polygon 失败: " + object.getClass().getName() +
", 错误: " + e.getMessage());
System.err.println("转换 Polygon 失败: " + object.getClass().getName() +
", 错误: " + e.getMessage());
}
return null;
@ -101,11 +117,11 @@ public class PolygonTypeHandler implements TypeHandler<Polygon> {
}
String value = pgObject.getValue().trim();
// 判断是否是 WKT 格式
if (value.startsWith("SRID=") ||
value.startsWith("POLYGON") ||
value.startsWith("0103")) { // WKB polygon 类型标识
if (value.startsWith("SRID=") ||
value.startsWith("POLYGON") ||
value.startsWith("0103")) { // WKB polygon 类型标识
try {
// 尝试解析为 WKT
if (value.contains("POLYGON")) {
@ -135,7 +151,7 @@ public class PolygonTypeHandler implements TypeHandler<Polygon> {
throw new ParseException("解析 PGobject 失败: " + value, e);
}
}
return null;
}
@ -179,9 +195,153 @@ public class PolygonTypeHandler implements TypeHandler<Polygon> {
}
/**
* 设置多边形到 PreparedStatement辅助方法
* GCJ-02 WGS-84写入数据库时调用
*/
private void setPolygonWithoutConversion(PreparedStatement ps, int i, Polygon polygon)
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 {
if (polygon == null) {
ps.setNull(i, Types.OTHER);
@ -191,13 +351,13 @@ public class PolygonTypeHandler implements TypeHandler<Polygon> {
try {
PGobject pgObject = new PGobject();
pgObject.setType("geometry");
// 使用 WKT 格式设置正确的 SRID
polygon.setSRID(WGS84_SRID);
String wkt = polygon.toText();
String wktWithSRID = "SRID=" + WGS84_SRID + ";" + wkt;
pgObject.setValue(wktWithSRID);
ps.setObject(i, pgObject);
} catch (Exception e) {
System.err.println("设置 Polygon 失败: " + e.getMessage());
@ -212,16 +372,16 @@ public class PolygonTypeHandler implements TypeHandler<Polygon> {
try {
// 清理字符串
String cleaned = pgPolygonStr.trim();
// 移除外层括号
if (cleaned.startsWith("((") && cleaned.endsWith("))")) {
cleaned = cleaned.substring(2, cleaned.length() - 2);
}
// 分割坐标点
String[] pointStrings = cleaned.split(",");
List<Coordinate> coordinates = new ArrayList<>();
for (String pointStr : pointStrings) {
pointStr = pointStr.trim();
String[] xy = pointStr.split("\\s+");
@ -231,7 +391,7 @@ public class PolygonTypeHandler implements TypeHandler<Polygon> {
coordinates.add(new Coordinate(x, y));
}
}
// 确保多边形闭合
if (coordinates.size() >= 3) {
Coordinate first = coordinates.get(0);
@ -240,15 +400,15 @@ public class PolygonTypeHandler implements TypeHandler<Polygon> {
coordinates.add(new Coordinate(first.x, first.y));
}
}
// 创建多边形
Coordinate[] coordArray = coordinates.toArray(new Coordinate[0]);
LinearRing shell = geometryFactory.createLinearRing(coordArray);
Polygon polygon = geometryFactory.createPolygon(shell);
polygon.setSRID(WGS84_SRID);
return polygon;
} catch (Exception e) {
throw new ParseException("解析 PGpolygon 格式失败: " + pgPolygonStr, e);
}
@ -262,7 +422,7 @@ public class PolygonTypeHandler implements TypeHandler<Polygon> {
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
+ Character.digit(hex.charAt(i + 1), 16));
+ Character.digit(hex.charAt(i + 1), 16));
}
return data;
}

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);