运营区边缘计算:基础支持
This commit is contained in:
parent
b860a5c479
commit
0f03281f42
@ -1,7 +1,6 @@
|
||||
package com.ebike.feign.model.dto;
|
||||
|
||||
|
||||
import com.mybatisflex.annotation.Id;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
@ -30,7 +29,6 @@ public class FeignEbikeRegionDto implements Serializable {
|
||||
/**
|
||||
* 运营区ID
|
||||
*/
|
||||
@Id
|
||||
private Long regionId;
|
||||
|
||||
private Long operatorId;
|
||||
|
||||
@ -26,6 +26,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static com.cdzy.operations.model.entity.table.EbikeBikeInfoTableDef.EBIKE_BIKE_INFO;
|
||||
import static com.cdzy.operations.model.entity.table.EbikeOperationConfigTableDef.EBIKE_OPERATION_CONFIG;
|
||||
import static com.cdzy.operations.model.entity.table.EbikeOperationLockConfigTableDef.EBIKE_OPERATION_LOCK_CONFIG;
|
||||
import static com.cdzy.operations.model.entity.table.EbikeOperationReturnConfigTableDef.EBIKE_OPERATION_RETURN_CONFIG;
|
||||
@ -168,8 +169,11 @@ public class EbikeRegionServiceImpl extends ServiceImpl<EbikeRegionMapper, Ebike
|
||||
@Override
|
||||
public EbikeRegion getRegionByEcuSn(String ecuSn) {
|
||||
QueryWrapper queryWrapper = QueryWrapper.create()
|
||||
.where(EBIKE_ECU_INFO.ECU_SN.eq(ecuSn))
|
||||
.leftJoin(EBIKE_ECU_INFO).on(EBIKE_ECU_INFO.OPERATOR_ID.eq(EBIKE_REGION.OPERATOR_ID));
|
||||
.select()
|
||||
.from(EBIKE_REGION)
|
||||
.innerJoin(EBIKE_BIKE_INFO).on(EBIKE_REGION.REGION_ID.eq(EBIKE_BIKE_INFO.REGION_ID))
|
||||
.innerJoin(EBIKE_ECU_INFO).on(EBIKE_BIKE_INFO.ECU_ID.eq(EBIKE_ECU_INFO.ECU_ID))
|
||||
.where(EBIKE_ECU_INFO.ECU_SN.eq(ecuSn));
|
||||
return this.mapper.selectOneByQuery(queryWrapper);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
package com.cdzy.report.config;
|
||||
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import com.cdzy.report.handler.PointDeserializer;
|
||||
import com.cdzy.report.handler.PointSerializer;
|
||||
import com.cdzy.report.handler.PolygonDeserializer;
|
||||
import com.cdzy.report.handler.PolygonSerializer;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
|
||||
@ -9,6 +13,8 @@ import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
|
||||
import org.locationtech.jts.geom.Point;
|
||||
import org.locationtech.jts.geom.Polygon;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@ -34,10 +40,14 @@ public class JacksonConfig {
|
||||
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DatePattern.NORM_TIME_FORMATTER));
|
||||
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DatePattern.NORM_DATE_FORMATTER));
|
||||
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DatePattern.NORM_DATETIME_FORMATTER));
|
||||
javaTimeModule.addSerializer(Point.class, new PointSerializer());
|
||||
javaTimeModule.addSerializer(Polygon.class, new PolygonSerializer());
|
||||
|
||||
javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DatePattern.NORM_TIME_FORMATTER));
|
||||
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DatePattern.NORM_DATE_FORMATTER));
|
||||
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DatePattern.NORM_DATETIME_FORMATTER));
|
||||
javaTimeModule.addDeserializer(Point.class, new PointDeserializer());
|
||||
javaTimeModule.addDeserializer(Polygon.class, new PolygonDeserializer());
|
||||
return javaTimeModule;
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,246 @@
|
||||
package com.cdzy.report.handler;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
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.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;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* JTS Point 反序列化器 - 将 JSON 反序列化为 Point
|
||||
* 支持 type、longitude、latitude 三个字段的格式
|
||||
*/
|
||||
@Slf4j
|
||||
public class PointDeserializer extends JsonDeserializer<Point> {
|
||||
|
||||
private final GeometryFactory geometryFactory = new GeometryFactory();
|
||||
|
||||
@Override
|
||||
public Point deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
JsonNode node = p.getCodec().readTree(p);
|
||||
|
||||
if (node.isNull()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// 格式1: 三字段格式 {"type": "point", "longitude": 116.3974, "latitude": 39.9093}
|
||||
if (node.isObject() && node.has("type") &&
|
||||
(node.has("longitude") || node.has("latitude"))) {
|
||||
return parseFromThreeFields(node);
|
||||
}
|
||||
|
||||
// 格式2: 自定义格式 {"type": "point", "coordinates": [lng, lat]}
|
||||
if (node.isObject() && node.has("type")) {
|
||||
String type = node.get("type").asText().toLowerCase(Locale.ROOT);
|
||||
if ("point".equals(type)) {
|
||||
return parseFromCustomFormat(node);
|
||||
}
|
||||
}
|
||||
// 格式3: GeoJSON 格式 {"type": "Point", "coordinates": [lng, lat]}
|
||||
else if (node.isObject() && node.has("type")) {
|
||||
String type = node.get("type").asText();
|
||||
if ("Point".equals(type)) {
|
||||
return parseFromGeoJSON(node);
|
||||
}
|
||||
}
|
||||
// 格式4: 坐标数组格式 [lng, lat]
|
||||
else if (node.isArray()) {
|
||||
return parseFromArray(node);
|
||||
}
|
||||
// 格式5: 简化的三字段格式(没有type)
|
||||
else if (node.isObject() && (node.has("longitude") || node.has("latitude"))) {
|
||||
return parseFromLongitudeLatitude(node);
|
||||
}
|
||||
// 格式6: WKT 字符串格式 "POINT(lng lat)"
|
||||
else if (node.isTextual()) {
|
||||
return parseFromWKT(node.asText());
|
||||
}
|
||||
|
||||
throw new IOException("""
|
||||
不支持的 Point JSON 格式,支持的格式包括:
|
||||
1. 三字段格式: {"type":"point","longitude":116.3974,"latitude":39.9093}
|
||||
2. 自定义格式: {"type":"point","coordinates":[lng,lat]}
|
||||
3. GeoJSON格式: {"type":"Point","coordinates":[lng,lat]}
|
||||
4. 坐标数组: [lng,lat]
|
||||
5. 简化格式: {"longitude":116.3974,"latitude":39.9093}
|
||||
6. WKT字符串: "POINT(lng lat)"
|
||||
7. PostgreSQL点格式: "(lng,lat)\"""");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("!!! PointDeserializer 反序列化失败 !!!");
|
||||
log.error("错误信息: {}", e.getMessage());
|
||||
log.error("输入 JSON: {}", node);
|
||||
throw new IOException("Point 反序列化失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Point> handledType() {
|
||||
return Point.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析三字段格式:{"type":"point","longitude":116.3974,"latitude":39.9093}
|
||||
*/
|
||||
private Point parseFromThreeFields(JsonNode node) throws IOException {
|
||||
String type = node.get("type").asText();
|
||||
if (!"point".equalsIgnoreCase(type)) {
|
||||
throw new IOException("type 字段必须是 'point',当前是: " + type);
|
||||
}
|
||||
|
||||
JsonNode lngNode = node.get("longitude");
|
||||
JsonNode latNode = node.get("latitude");
|
||||
|
||||
if (lngNode == null || latNode == null) {
|
||||
throw new IOException("longitude 或 latitude 字段缺失");
|
||||
}
|
||||
|
||||
try {
|
||||
double longitude = lngNode.asDouble();
|
||||
double latitude = latNode.asDouble();
|
||||
return geometryFactory.createPoint(new Coordinate(longitude, latitude));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IOException("longitude/latitude 格式错误", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析自定义格式:{"type":"point","coordinates":[lng,lat]}
|
||||
*/
|
||||
private Point parseFromCustomFormat(JsonNode node) throws IOException {
|
||||
JsonNode coordinatesNode = node.get("coordinates");
|
||||
if (coordinatesNode == null) {
|
||||
throw new IOException("coordinates 字段缺失");
|
||||
}
|
||||
|
||||
return parseCoordinates(coordinatesNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 GeoJSON 格式:{"type":"Point","coordinates":[lng,lat]}
|
||||
*/
|
||||
private Point parseFromGeoJSON(JsonNode node) throws IOException {
|
||||
JsonNode coordinatesNode = node.get("coordinates");
|
||||
if (coordinatesNode == null) {
|
||||
throw new IOException("coordinates 字段缺失");
|
||||
}
|
||||
|
||||
return parseCoordinates(coordinatesNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析坐标数组格式:[lng, lat]
|
||||
*/
|
||||
private Point parseFromArray(JsonNode node) throws IOException {
|
||||
return parseCoordinates(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析简化格式:{"longitude":116.3974,"latitude":39.9093}
|
||||
*/
|
||||
private Point parseFromLongitudeLatitude(JsonNode node) throws IOException {
|
||||
JsonNode lngNode = node.get("longitude");
|
||||
JsonNode latNode = node.get("latitude");
|
||||
|
||||
if (lngNode == null || latNode == null) {
|
||||
throw new IOException("longitude 或 latitude 字段缺失");
|
||||
}
|
||||
|
||||
try {
|
||||
double longitude = lngNode.asDouble();
|
||||
double latitude = latNode.asDouble();
|
||||
return geometryFactory.createPoint(new Coordinate(longitude, latitude));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IOException("longitude/latitude 格式错误", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 WKT 字符串格式:POINT(lng lat)
|
||||
*/
|
||||
private Point parseFromWKT(String wktString) throws IOException {
|
||||
if (wktString == null || wktString.trim().isEmpty()) {
|
||||
throw new IOException("WKT 字符串为空");
|
||||
}
|
||||
|
||||
try {
|
||||
wktString = wktString.trim();
|
||||
|
||||
// 支持 PostgreSQL 点格式转换:(x,y) -> POINT(x y)
|
||||
if (wktString.startsWith("(") && wktString.endsWith(")")) {
|
||||
wktString = convertPostgresPointToWKT(wktString);
|
||||
}
|
||||
|
||||
// 确保是有效的 WKT
|
||||
if (!wktString.toUpperCase().startsWith("POINT")) {
|
||||
wktString = "POINT(" + wktString + ")";
|
||||
}
|
||||
|
||||
WKTReader reader = new WKTReader(geometryFactory);
|
||||
Geometry geometry = reader.read(wktString);
|
||||
|
||||
if (!(geometry instanceof Point)) {
|
||||
throw new IOException("WKT 字符串不是有效的 Point: " + wktString);
|
||||
}
|
||||
|
||||
return (Point) geometry;
|
||||
} catch (Exception e) {
|
||||
throw new IOException("WKT 格式解析失败: " + wktString, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析坐标节点
|
||||
*/
|
||||
private Point parseCoordinates(JsonNode coordinatesNode) throws IOException {
|
||||
if (coordinatesNode == null) {
|
||||
throw new IOException("坐标节点为 null");
|
||||
}
|
||||
|
||||
if (!coordinatesNode.isArray()) {
|
||||
throw new IOException("coordinates 字段必须是数组类型");
|
||||
}
|
||||
|
||||
if (coordinatesNode.size() < 2) {
|
||||
throw new IOException("坐标数组至少需要2个值(经度和纬度)");
|
||||
}
|
||||
|
||||
try {
|
||||
double lng = coordinatesNode.get(0).asDouble();
|
||||
double lat = coordinatesNode.get(1).asDouble();
|
||||
|
||||
return geometryFactory.createPoint(new Coordinate(lng, lat));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IOException("坐标格式错误: " + coordinatesNode, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换 PostgreSQL 点格式到 WKT 格式
|
||||
*/
|
||||
private String convertPostgresPointToWKT(String pgPointString) {
|
||||
// PostgreSQL 格式: (x,y) 或 (x, y)
|
||||
// WKT 格式: POINT(x y)
|
||||
|
||||
String cleaned = pgPointString
|
||||
.replaceAll("\\s+", "") // 移除所有空格
|
||||
.replace("(", "")
|
||||
.replace(")", "");
|
||||
|
||||
String[] coords = cleaned.split(",");
|
||||
if (coords.length >= 2) {
|
||||
return "POINT(" + coords[0] + " " + coords[1] + ")";
|
||||
}
|
||||
|
||||
return pgPointString; // 无法转换,返回原值
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,108 @@
|
||||
package com.cdzy.report.handler;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.locationtech.jts.geom.Coordinate;
|
||||
import org.locationtech.jts.geom.Point;
|
||||
import org.locationtech.jts.io.WKTWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* JTS Point 序列化器 - 将 Point 序列化为 JSON
|
||||
* 输出 type、longitude、latitude 三个字段
|
||||
*/
|
||||
@Slf4j
|
||||
public class PointSerializer extends JsonSerializer<Point> {
|
||||
|
||||
private final WKTWriter wktWriter = new WKTWriter();
|
||||
|
||||
@Override
|
||||
public void serialize(Point point, JsonGenerator gen, SerializerProvider serializers)
|
||||
throws IOException {
|
||||
if (point == null) {
|
||||
gen.writeNull();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取坐标
|
||||
Coordinate coordinate = point.getCoordinate();
|
||||
|
||||
if (coordinate == null || Double.isNaN(coordinate.x) || Double.isNaN(coordinate.y)) {
|
||||
log.warn("警告: 点坐标无效: {}", coordinate);
|
||||
gen.writeNull();
|
||||
return;
|
||||
}
|
||||
|
||||
// 序列化为三字段格式:type、longitude、latitude
|
||||
gen.writeStartObject();
|
||||
gen.writeStringField("type", "point");
|
||||
gen.writeNumberField("longitude", formatCoordinate(coordinate.x));
|
||||
gen.writeNumberField("latitude", formatCoordinate(coordinate.y));
|
||||
gen.writeEndObject();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("!!! PointSerializer 序列化失败 !!!");
|
||||
log.error("错误信息: {}", e.getMessage());
|
||||
// 尝试备用序列化方式
|
||||
try {
|
||||
serializeAsAlternativeFormat(point, gen);
|
||||
} catch (Exception e2) {
|
||||
throw new IOException("Point 序列化失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Point> handledType() {
|
||||
return Point.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化坐标值
|
||||
*/
|
||||
private double formatCoordinate(double value) {
|
||||
// 返回原始值,Jackson 会处理格式化
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 备用序列化方式:序列化为 coordinates 数组格式
|
||||
*/
|
||||
private void serializeAsAlternativeFormat(Point point, JsonGenerator gen) throws IOException {
|
||||
if (point == null || point.isEmpty()) {
|
||||
gen.writeNull();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Coordinate coordinate = point.getCoordinate();
|
||||
|
||||
// 方式1:coordinates 数组格式
|
||||
gen.writeStartObject();
|
||||
gen.writeStringField("type", "point");
|
||||
|
||||
gen.writeArrayFieldStart("coordinates");
|
||||
gen.writeNumber(coordinate.x);
|
||||
gen.writeNumber(coordinate.y);
|
||||
gen.writeEndArray();
|
||||
|
||||
gen.writeEndObject();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("备用序列化失败: {}", e.getMessage());
|
||||
|
||||
// 最终备用:序列化为 WKT 字符串
|
||||
try {
|
||||
String wkt = wktWriter.write(point);
|
||||
gen.writeString(wkt);
|
||||
} catch (Exception e2) {
|
||||
throw new IOException("Point 序列化完全失败", e2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,143 @@
|
||||
package com.cdzy.report.handler;
|
||||
|
||||
import com.cdzy.common.utils.CoordinateUtil;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
import org.apache.ibatis.type.TypeHandler;
|
||||
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.*;
|
||||
|
||||
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)
|
||||
throws SQLException {
|
||||
if (parameter == null) {
|
||||
ps.setNull(i, Types.OTHER);
|
||||
} else {
|
||||
try {
|
||||
// 写入时:GCJ-02 → WGS-84
|
||||
Point wgs84Point = gcj02ToWgs84(parameter);
|
||||
|
||||
PGobject pgObject = new PGobject();
|
||||
pgObject.setType("geometry");
|
||||
|
||||
String wkt = "SRID=" + WGS84_SRID + ";" + wgs84Point.toText();
|
||||
pgObject.setValue(wkt);
|
||||
|
||||
ps.setObject(i, pgObject);
|
||||
} catch (Exception e) {
|
||||
System.err.println("Point 坐标转换失败: " + e.getMessage());
|
||||
ps.setNull(i, Types.OTHER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point getResult(ResultSet rs, String columnName) throws SQLException {
|
||||
Object object = rs.getObject(columnName);
|
||||
Point wgs84Point = convertToPoint(object);
|
||||
return wgs84ToGcj02(wgs84Point);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point getResult(ResultSet rs, int columnIndex) throws SQLException {
|
||||
Object object = rs.getObject(columnIndex);
|
||||
Point wgs84Point = convertToPoint(object);
|
||||
return wgs84ToGcj02(wgs84Point);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point getResult(CallableStatement cs, int columnIndex) throws SQLException {
|
||||
Object object = cs.getObject(columnIndex);
|
||||
Point wgs84Point = convertToPoint(object);
|
||||
return wgs84ToGcj02(wgs84Point);
|
||||
}
|
||||
|
||||
private Point convertToPoint(Object object) {
|
||||
if (object == null) return null;
|
||||
|
||||
try {
|
||||
if (object instanceof PGobject 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) {
|
||||
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;
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,243 @@
|
||||
package com.cdzy.report.handler;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
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.io.WKTReader;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* JTS Polygon 反序列化器 - 将 JSON 反序列化为 Polygon
|
||||
* 支持与 PGpolygonDeserializer 相同的 JSON 格式
|
||||
*/
|
||||
@Slf4j
|
||||
public class PolygonDeserializer extends JsonDeserializer<Polygon> {
|
||||
|
||||
private final GeometryFactory geometryFactory = new GeometryFactory();
|
||||
|
||||
@Override
|
||||
public Polygon deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
JsonNode node = p.getCodec().readTree(p);
|
||||
|
||||
if (node.isNull()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
Polygon result = null;
|
||||
|
||||
// 格式1: 自定义格式 {"type": "polygon", "coordinates": [[x1,y1], [x2,y2], ...]}
|
||||
if (node.isObject() && node.has("type")) {
|
||||
String type = node.get("type").asText().toLowerCase();
|
||||
if ("polygon".equals(type)) {
|
||||
result = parseFromCustomFormat(node);
|
||||
}
|
||||
}
|
||||
// 格式2: 直接坐标数组 [[x1,y1], [x2,y2], ...]
|
||||
else if (node.isArray()) {
|
||||
result = parseFromArray(node);
|
||||
}
|
||||
// 格式3: WKT 字符串格式 "POLYGON((x1 y1, x2 y2, ...))"
|
||||
else if (node.isTextual()) {
|
||||
result = parseFromWKT(node.asText());
|
||||
}
|
||||
// 格式4: 对象格式但没有 type 字段,尝试解析 coordinates
|
||||
else if (node.isObject() && node.has("coordinates")) {
|
||||
result = parseFromCustomFormat(node);
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
throw new IOException("""
|
||||
不支持的 Polygon JSON 格式,支持的格式包括:
|
||||
1. 自定义格式: {"type":"polygon","coordinates":[[x1,y1],[x2,y2],...]}
|
||||
2. GeoJSON格式: {"type":"Polygon","coordinates":[[[x1,y1],[x2,y2],...]]}
|
||||
3. 坐标数组: [[x1,y1],[x2,y2],...]
|
||||
4. WKT字符串: "POLYGON((x1 y1, x2 y2, ...))"
|
||||
5. PGpolygon格式: "((x1,y1),(x2,y2),...)\"""");
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("!!! PolygonDeserializer 反序列化失败 !!!");
|
||||
log.error("错误信息: {}", e.getMessage());
|
||||
log.error("输入 JSON: {}", node);
|
||||
throw new IOException("Polygon 反序列化失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Polygon> handledType() {
|
||||
return Polygon.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析自定义格式
|
||||
*/
|
||||
private Polygon parseFromCustomFormat(JsonNode node) throws IOException {
|
||||
JsonNode coordinatesNode = node.get("coordinates");
|
||||
if (coordinatesNode == null) {
|
||||
throw new IOException("coordinates 字段缺失");
|
||||
}
|
||||
|
||||
if (!coordinatesNode.isArray()) {
|
||||
throw new IOException("coordinates 字段必须是数组类型");
|
||||
}
|
||||
|
||||
List<Coordinate> coordinates = new ArrayList<>();
|
||||
for (int i = 0; i < coordinatesNode.size(); i++) {
|
||||
JsonNode coordNode = coordinatesNode.get(i);
|
||||
|
||||
if (!coordNode.isArray()) {
|
||||
throw new IOException("坐标 " + i + " 必须是数组格式");
|
||||
}
|
||||
|
||||
if (coordNode.size() < 2) {
|
||||
throw new IOException("坐标 " + i + " 至少需要2个值(经度和纬度)");
|
||||
}
|
||||
|
||||
try {
|
||||
double x = coordNode.get(0).asDouble();
|
||||
double y = coordNode.get(1).asDouble();
|
||||
coordinates.add(new Coordinate(x, y));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IOException("坐标 " + i + " 格式错误: " + coordNode, e);
|
||||
}
|
||||
}
|
||||
|
||||
return createPolygonFromCoordinates(coordinates);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析坐标数组格式
|
||||
*/
|
||||
private Polygon parseFromArray(JsonNode node) throws IOException {
|
||||
List<Coordinate> coordinates = new ArrayList<>();
|
||||
for (int i = 0; i < node.size(); i++) {
|
||||
JsonNode coordNode = node.get(i);
|
||||
|
||||
if (!coordNode.isArray()) {
|
||||
throw new IOException("坐标 " + i + " 必须是数组格式");
|
||||
}
|
||||
|
||||
if (coordNode.size() < 2) {
|
||||
throw new IOException("坐标 " + i + " 至少需要2个值(经度和纬度)");
|
||||
}
|
||||
|
||||
try {
|
||||
double x = coordNode.get(0).asDouble();
|
||||
double y = coordNode.get(1).asDouble();
|
||||
coordinates.add(new Coordinate(x, y));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IOException("坐标 " + i + " 格式错误: " + coordNode, e);
|
||||
}
|
||||
}
|
||||
|
||||
return createPolygonFromCoordinates(coordinates);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 WKT 字符串格式
|
||||
*/
|
||||
private Polygon parseFromWKT(String wktString) throws IOException {
|
||||
if (wktString == null || wktString.trim().isEmpty()) {
|
||||
throw new IOException("WKT 字符串为空");
|
||||
}
|
||||
|
||||
try {
|
||||
// 清理字符串
|
||||
wktString = wktString.trim();
|
||||
|
||||
// 支持 PGpolygon 格式转换:((x,y),(x,y),...) -> POLYGON((x y, x y, ...))
|
||||
if (wktString.startsWith("((") && wktString.endsWith("))")) {
|
||||
wktString = convertPGpolygonToWKT(wktString);
|
||||
}
|
||||
|
||||
// 确保是有效的 WKT
|
||||
if (!wktString.toUpperCase().startsWith("POLYGON")) {
|
||||
// 尝试包装
|
||||
wktString = "POLYGON(" + wktString + ")";
|
||||
}
|
||||
|
||||
WKTReader reader = new WKTReader(geometryFactory);
|
||||
Geometry geometry = reader.read(wktString);
|
||||
|
||||
if (!(geometry instanceof Polygon)) {
|
||||
throw new IOException("WKT 字符串不是有效的 Polygon: " + wktString);
|
||||
}
|
||||
|
||||
return (Polygon) geometry;
|
||||
} catch (Exception e) {
|
||||
throw new IOException("WKT 格式解析失败: " + wktString, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从坐标列表创建 Polygon
|
||||
*/
|
||||
private Polygon createPolygonFromCoordinates(List<Coordinate> coordinates) throws IOException {
|
||||
if (coordinates == null || coordinates.size() < 3) {
|
||||
throw new IOException("多边形至少需要3个点,当前只有 " +
|
||||
(coordinates == null ? 0 : coordinates.size()) + " 个点");
|
||||
}
|
||||
|
||||
// 确保多边形闭合(首尾点相同)
|
||||
Coordinate first = coordinates.get(0);
|
||||
Coordinate last = coordinates.get(coordinates.size() - 1);
|
||||
|
||||
if (!first.equals2D(last)) {
|
||||
log.debug("多边形未闭合,添加首点以闭合");
|
||||
coordinates.add(new Coordinate(first.x, first.y));
|
||||
}
|
||||
|
||||
try {
|
||||
// 转换为数组
|
||||
Coordinate[] coordArray = coordinates.toArray(new Coordinate[0]);
|
||||
|
||||
// 创建线性环
|
||||
LinearRing shell = geometryFactory.createLinearRing(coordArray);
|
||||
|
||||
// 创建多边形(无孔洞)
|
||||
return geometryFactory.createPolygon(shell);
|
||||
} catch (Exception e) {
|
||||
throw new IOException("创建 Polygon 失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换 PGpolygon 格式到 WKT 格式
|
||||
*/
|
||||
private String convertPGpolygonToWKT(String pgPolygonString) {
|
||||
// PGpolygon 格式: ((x,y),(x,y),...)
|
||||
// WKT 格式: POLYGON((x y, x y, ...))
|
||||
|
||||
String cleaned = pgPolygonString
|
||||
.replaceAll("\\s+", "") // 移除所有空格
|
||||
.replace("((", "(")
|
||||
.replace("))", ")");
|
||||
|
||||
// 替换逗号为空格,括号内逗号为空格
|
||||
String[] points = cleaned.substring(1, cleaned.length() - 1).split("\\),\\(");
|
||||
StringBuilder wktBuilder = new StringBuilder("POLYGON((");
|
||||
|
||||
for (int i = 0; i < points.length; i++) {
|
||||
String point = points[i];
|
||||
String[] coords = point.split(",");
|
||||
if (coords.length >= 2) {
|
||||
wktBuilder.append(coords[0]).append(" ").append(coords[1]);
|
||||
if (i < points.length - 1) {
|
||||
wktBuilder.append(", ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wktBuilder.append("))");
|
||||
return wktBuilder.toString();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,170 @@
|
||||
package com.cdzy.report.handler;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.locationtech.jts.geom.Coordinate;
|
||||
import org.locationtech.jts.geom.LinearRing;
|
||||
import org.locationtech.jts.geom.Polygon;
|
||||
import org.locationtech.jts.io.WKTWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* JTS Polygon 序列化器 - 将 Polygon 序列化为 JSON
|
||||
* 生成与 PGpolygonSerializer 相同的 JSON 格式
|
||||
*/
|
||||
@Slf4j
|
||||
public class PolygonSerializer extends JsonSerializer<Polygon> {
|
||||
|
||||
private final WKTWriter wktWriter = new WKTWriter();
|
||||
|
||||
@Override
|
||||
public void serialize(Polygon polygon, JsonGenerator gen, SerializerProvider serializers)
|
||||
throws IOException {
|
||||
if (polygon == null) {
|
||||
gen.writeNull();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取多边形的外环坐标
|
||||
Coordinate[] coordinates = getPolygonCoordinates(polygon);
|
||||
|
||||
if (coordinates.length == 0) {
|
||||
log.warn("警告: 多边形没有有效坐标");
|
||||
gen.writeNull();
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否有无效坐标
|
||||
for (int i = 0; i < coordinates.length; i++) {
|
||||
Coordinate coord = coordinates[i];
|
||||
if (coord == null || Double.isNaN(coord.x) || Double.isNaN(coord.y)) {
|
||||
log.error("错误: 第 {} 个坐标无效: {}", i, coord);
|
||||
}
|
||||
}
|
||||
|
||||
// 序列化为自定义格式(与 PGpolygonSerializer 格式一致)
|
||||
gen.writeStartObject();
|
||||
gen.writeStringField("type", "polygon");
|
||||
|
||||
gen.writeArrayFieldStart("coordinates");
|
||||
for (int i = 0; i < coordinates.length; i++) {
|
||||
Coordinate coord = coordinates[i];
|
||||
if (coord == null || Double.isNaN(coord.x) || Double.isNaN(coord.y)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
gen.writeStartArray();
|
||||
// 使用格式化器避免科学计数法
|
||||
gen.writeNumber(formatCoordinate(coord.x));
|
||||
gen.writeNumber(formatCoordinate(coord.y));
|
||||
gen.writeEndArray();
|
||||
} catch (Exception e) {
|
||||
throw new IOException("序列化坐标 " + i + " 失败", e);
|
||||
}
|
||||
}
|
||||
gen.writeEndArray();
|
||||
gen.writeEndObject();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("!!! PolygonSerializer 序列化失败 !!!");
|
||||
log.error("错误信息: {}", e.getMessage());
|
||||
// 尝试备用序列化方式
|
||||
try {
|
||||
serializeAsWKT(polygon, gen);
|
||||
} catch (Exception e2) {
|
||||
throw new IOException("Polygon 序列化失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Polygon> handledType() {
|
||||
return Polygon.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取多边形的坐标(仅外环,忽略孔洞)
|
||||
*/
|
||||
private Coordinate[] getPolygonCoordinates(Polygon polygon) throws IOException {
|
||||
if (polygon == null || polygon.isEmpty()) {
|
||||
return new Coordinate[0];
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取外环
|
||||
LinearRing exteriorRing = polygon.getExteriorRing();
|
||||
if (exteriorRing == null) {
|
||||
throw new IOException("多边形外环为空");
|
||||
}
|
||||
|
||||
return exteriorRing.getCoordinates();
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new IOException("获取多边形坐标失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化坐标值
|
||||
*/
|
||||
private double formatCoordinate(double value) {
|
||||
// 返回原始值,Jackson 会处理格式化
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 备用序列化方式:序列化为 WKT 字符串
|
||||
*/
|
||||
private void serializeAsWKT(Polygon polygon, JsonGenerator gen) throws IOException {
|
||||
if (polygon == null) {
|
||||
gen.writeNull();
|
||||
return;
|
||||
}
|
||||
|
||||
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(" ");
|
||||
if (xy.length >= 2) {
|
||||
try {
|
||||
double x = Double.parseDouble(xy[0]);
|
||||
double y = Double.parseDouble(xy[1]);
|
||||
|
||||
gen.writeStartArray();
|
||||
gen.writeNumber(x);
|
||||
gen.writeNumber(y);
|
||||
gen.writeEndArray();
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("坐标解析失败: {}", point);
|
||||
}
|
||||
}
|
||||
}
|
||||
gen.writeEndArray();
|
||||
gen.writeEndObject();
|
||||
} else {
|
||||
// 直接写 WKT 字符串
|
||||
gen.writeString(wkt);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("WKT 序列化失败: {}", e.getMessage());
|
||||
throw new IOException("Polygon 序列化失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,429 @@
|
||||
package com.cdzy.report.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.*;
|
||||
import org.locationtech.jts.io.ParseException;
|
||||
import org.locationtech.jts.io.WKBReader;
|
||||
import org.locationtech.jts.io.WKTReader;
|
||||
import org.postgresql.util.PGobject;
|
||||
|
||||
import java.sql.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* JTS Polygon 类型处理器 - 集成 CoordinateUtil 实现坐标转换
|
||||
* 写入时:GCJ-02 → WGS-84
|
||||
* 读取时:WGS-84 → GCJ-02
|
||||
* 支持 PostgreSQL + PostGIS 的 geometry(Polygon, 4326) 类型
|
||||
*/
|
||||
public class PolygonTypeHandler implements TypeHandler<Polygon> {
|
||||
|
||||
private static final int WGS84_SRID = 4326;
|
||||
private final GeometryFactory geometryFactory = new GeometryFactory();
|
||||
private final WKTReader wktReader = new WKTReader(geometryFactory);
|
||||
private final WKBReader wkbReader = new WKBReader(geometryFactory);
|
||||
|
||||
@Override
|
||||
public void setParameter(PreparedStatement ps, int i, Polygon parameter, JdbcType jdbcType)
|
||||
throws SQLException {
|
||||
if (parameter == null) {
|
||||
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
|
||||
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("坐标转换失败,使用原始值: " + e.getMessage());
|
||||
setPolygonWithoutConversion(ps, i, parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Polygon getResult(ResultSet rs, String columnName) throws SQLException {
|
||||
Object object = rs.getObject(columnName);
|
||||
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);
|
||||
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);
|
||||
Polygon wgs84Polygon = convertToPolygon(object);
|
||||
// 读取时:WGS-84 → GCJ-02
|
||||
return wgs84ToGcj02(wgs84Polygon);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据库对象转换为 Polygon
|
||||
*/
|
||||
private Polygon convertToPolygon(Object object) {
|
||||
if (object == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
if (object instanceof PGobject) {
|
||||
return parseFromPGobject((PGobject) object);
|
||||
} else if (object instanceof String) {
|
||||
return parseFromString((String) object);
|
||||
} else if (object instanceof byte[]) {
|
||||
return parseFromWKB((byte[]) object);
|
||||
} else if (object instanceof Polygon) {
|
||||
return (Polygon) object;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("转换 Polygon 失败: " + object.getClass().getName() +
|
||||
", 错误: " + e.getMessage());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 PGobject 解析 Polygon
|
||||
*/
|
||||
private Polygon parseFromPGobject(PGobject pgObject) throws ParseException {
|
||||
if (pgObject == null || pgObject.getValue() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String value = pgObject.getValue().trim();
|
||||
|
||||
// 判断是否是 WKT 格式
|
||||
if (value.startsWith("SRID=") ||
|
||||
value.startsWith("POLYGON") ||
|
||||
value.startsWith("0103")) { // WKB 的 polygon 类型标识
|
||||
try {
|
||||
// 尝试解析为 WKT
|
||||
if (value.contains("POLYGON")) {
|
||||
// 去除 SRID 前缀
|
||||
if (value.startsWith("SRID=")) {
|
||||
int semicolonIndex = value.indexOf(';');
|
||||
if (semicolonIndex > 0) {
|
||||
value = value.substring(semicolonIndex + 1);
|
||||
}
|
||||
}
|
||||
return (Polygon) wktReader.read(value);
|
||||
}
|
||||
// 尝试解析为 WKB
|
||||
else if (value.startsWith("0103") || value.startsWith("\\x")) {
|
||||
byte[] wkbBytes;
|
||||
if (value.startsWith("\\x")) {
|
||||
// PostgreSQL 十六进制格式
|
||||
String hex = value.substring(2);
|
||||
wkbBytes = hexStringToByteArray(hex);
|
||||
} else {
|
||||
// 已经是十六进制字符串
|
||||
wkbBytes = value.getBytes();
|
||||
}
|
||||
return (Polygon) wkbReader.read(wkbBytes);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new ParseException("解析 PGobject 失败: " + value, e);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从字符串解析 Polygon
|
||||
*/
|
||||
private Polygon parseFromString(String str) throws ParseException {
|
||||
if (str == null || str.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// 尝试 WKT 格式
|
||||
if (str.startsWith("POLYGON") || str.startsWith("SRID=")) {
|
||||
return (Polygon) wktReader.read(str);
|
||||
}
|
||||
// 尝试 PGpolygon 格式:((x y, x y, ...))
|
||||
else if (str.startsWith("((")) {
|
||||
return parseFromPGpolygonFormat(str);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new ParseException("解析字符串失败: " + str, e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 WKB 字节数组解析 Polygon
|
||||
*/
|
||||
private Polygon parseFromWKB(byte[] wkbBytes) throws ParseException {
|
||||
if (wkbBytes == null || wkbBytes.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return (Polygon) wkbReader.read(wkbBytes);
|
||||
} catch (Exception e) {
|
||||
throw new ParseException("解析 WKB 失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
if (polygon == null) {
|
||||
ps.setNull(i, Types.OTHER);
|
||||
return;
|
||||
}
|
||||
|
||||
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());
|
||||
ps.setNull(i, Types.OTHER);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 PGpolygon 格式的字符串:((x1 y1, x2 y2, x3 y3))
|
||||
*/
|
||||
private Polygon parseFromPGpolygonFormat(String pgPolygonStr) throws ParseException {
|
||||
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+");
|
||||
if (xy.length >= 2) {
|
||||
double x = Double.parseDouble(xy[0]);
|
||||
double y = Double.parseDouble(xy[1]);
|
||||
coordinates.add(new Coordinate(x, y));
|
||||
}
|
||||
}
|
||||
|
||||
// 确保多边形闭合
|
||||
if (coordinates.size() >= 3) {
|
||||
Coordinate first = coordinates.get(0);
|
||||
Coordinate last = coordinates.get(coordinates.size() - 1);
|
||||
if (!first.equals2D(last)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 十六进制字符串转字节数组
|
||||
*/
|
||||
private byte[] hexStringToByteArray(String hex) {
|
||||
int len = hex.length();
|
||||
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));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@ -269,4 +269,13 @@ public class RedisUtil {
|
||||
public Object getEcu(String ecuSn) {
|
||||
return get( BIKE_ECU_PREFIX + ecuSn);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 key 的类型
|
||||
* @param key 键
|
||||
* @return 类型字符串(如 "string", "hash", "none" 等)
|
||||
*/
|
||||
public String type(String key) {
|
||||
return redisTemplate.type(key).code();
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user