feat(geometry): 添加几何工具类和坐标转换支持
- 新增 GeometryUtil 工具类,提供几何对象创建和操作方法 - 添加 GpsConvertUtil 类,实现坐标系转换功能 - 在 EnterprisesUnit 模型中增加坐标字段- 更新相关参数和 VO 类,支持坐标信息 - 新增 PointTypeHandler 以支持 MyBatis-Plus 对 Point 类型的处理 - 在 FastJson2Config 中注册地理坐标相关的序列化和反序列化器 - 添加 GeometryInnerInterceptor 以支持 MyBatis-Plus 几何操作
This commit is contained in:
parent
18f6bd715a
commit
201f2112e8
|
@ -23,6 +23,7 @@
|
||||||
<easyexcel.version>3.3.4</easyexcel.version>
|
<easyexcel.version>3.3.4</easyexcel.version>
|
||||||
<mysql.driver.version>8.0.32</mysql.driver.version>
|
<mysql.driver.version>8.0.32</mysql.driver.version>
|
||||||
<mybatis.plus.version>3.5.7</mybatis.plus.version>
|
<mybatis.plus.version>3.5.7</mybatis.plus.version>
|
||||||
|
<geotools.version>25.2</geotools.version>
|
||||||
<druid.version>1.2.20</druid.version>
|
<druid.version>1.2.20</druid.version>
|
||||||
<minio.version>8.4.3</minio.version>
|
<minio.version>8.4.3</minio.version>
|
||||||
<okhttp.version>4.8.1</okhttp.version>
|
<okhttp.version>4.8.1</okhttp.version>
|
||||||
|
@ -190,6 +191,17 @@
|
||||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||||
<version>${mybatis.plus.version}</version>
|
<version>${mybatis.plus.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- 处理地理空间数据的读取、写入、转换、分析以及可视化 geotools-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.geotools</groupId>
|
||||||
|
<artifactId>gt-main</artifactId>
|
||||||
|
<version>${geotools.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.geotools</groupId>
|
||||||
|
<artifactId>gt-geojson</artifactId>
|
||||||
|
<version>${geotools.version}</version>
|
||||||
|
</dependency>
|
||||||
<!-- minio对象存储 https://www.minio.org.cn/ -->
|
<!-- minio对象存储 https://www.minio.org.cn/ -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.minio</groupId>
|
<groupId>io.minio</groupId>
|
||||||
|
|
|
@ -0,0 +1,428 @@
|
||||||
|
package com.changhu.common.utils;
|
||||||
|
|
||||||
|
import cn.hutool.core.convert.Convert;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.changhu.common.exception.MessageException;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.geotools.geojson.geom.GeometryJSON;
|
||||||
|
import org.geotools.geometry.jts.JTS;
|
||||||
|
import org.geotools.referencing.CRS;
|
||||||
|
import org.geotools.referencing.GeodeticCalculator;
|
||||||
|
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.opengis.referencing.FactoryException;
|
||||||
|
import org.opengis.referencing.crs.CoordinateReferenceSystem;
|
||||||
|
import org.opengis.referencing.operation.MathTransform;
|
||||||
|
import org.opengis.referencing.operation.TransformException;
|
||||||
|
|
||||||
|
import java.awt.geom.Point2D;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* author: luozhun
|
||||||
|
* desc: GeometryUtil
|
||||||
|
* createTime: 2023/8/25 18:03
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class GeometryUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* - WGS 84(srid=4326):用于全球地理坐标系统的标准参考坐标系。
|
||||||
|
* - 美国地理参考系统(srid=4269):用于美国本土的地图和地理数据。
|
||||||
|
* - 欧洲地理参考系统(srid=4258):用于欧洲地图和地理数据。
|
||||||
|
* - 中国国家大地坐标系(srid=4490):用于中国的地图和地理数据。
|
||||||
|
*/
|
||||||
|
public static final int SRID = 4326;
|
||||||
|
public static final String ST_GeomFromText = StrUtil.format("ST_GeomFromText({},{},{})", "?", SRID, "'axis-order=long-lat'");
|
||||||
|
private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory(new PrecisionModel(), SRID);
|
||||||
|
private static final GeometryFactory LOCATION_TECH_GEOMETRY_FACTORY = new GeometryFactory(new PrecisionModel(), SRID);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 空对象,因为简历空间索引不能为空 Mysql 不支持创建 Empty 的数据 这里自定义一个类型 标识为null
|
||||||
|
*/
|
||||||
|
public static Point emptyPoint() {
|
||||||
|
return createPoint("POINT(0 0)");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Polygon emptyPolygon() {
|
||||||
|
return createPolygon("POLYGON((0 0, 0 0, 0 0, 0 0))");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LineString emptyLineString() {
|
||||||
|
return createLineString("LINESTRING(0 0, 0 0)");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MultiPoint emptyMultiPoint() {
|
||||||
|
return createMultiPoint("MULTIPOINT((0 0))");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MultiPolygon emptyMultiPolygon() {
|
||||||
|
return createMultiPolygon("MULTIPOLYGON(((0 0, 0 0, 0 0, 0 0)))");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MultiLineString emptyMultiLineString() {
|
||||||
|
return createMultiLineString("MULTILINESTRING(((0 0, 0 0)))");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static boolean equals(Geometry geometry1, Geometry geometry2) {
|
||||||
|
return StrUtil.equals(geometry1.toText(), geometry2.toText());
|
||||||
|
}
|
||||||
|
|
||||||
|
//** 点 **\\
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建点对象
|
||||||
|
*/
|
||||||
|
public static Point createPoint(List<BigDecimal> param) {
|
||||||
|
if (param == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
BigDecimal longitude = param.get(0);
|
||||||
|
BigDecimal latitude = param.get(1);
|
||||||
|
if (longitude == null || latitude == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Coordinate coordinate = new Coordinate(longitude.doubleValue(), latitude.doubleValue());
|
||||||
|
return GEOMETRY_FACTORY.createPoint(coordinate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Point createPoint(double longitude, double latitude) {
|
||||||
|
Coordinate coordinate = new Coordinate(longitude, latitude);
|
||||||
|
return GEOMETRY_FACTORY.createPoint(coordinate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从WKT创建点对象
|
||||||
|
*
|
||||||
|
* @param wkt 注WKT类似: POINT(109.013388 32.715519)
|
||||||
|
*/
|
||||||
|
public static Point createPoint(String wkt) {
|
||||||
|
return createGeometryFromWkt(wkt, Point.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从WKT创建点对象
|
||||||
|
*
|
||||||
|
* @param bytes 数据库二进制数据
|
||||||
|
*/
|
||||||
|
public static Point createPoint(byte[] bytes) {
|
||||||
|
return createGeometryFromBytes(bytes, Point.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//** 多边形 **\\
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建多边形对象
|
||||||
|
* <p>
|
||||||
|
* 注:后面可能考虑兼容折线类型,如果是折线类型那么需要规范首尾结点要相同才能构成 polygon 否则构成 polyline
|
||||||
|
* 目前暂时没有做这方面的规划,用代码兼容了首尾不相等的情况构成polygon
|
||||||
|
*/
|
||||||
|
public static Polygon createPolygon(List<List<BigDecimal>> params) {
|
||||||
|
if (params == null || params.size() < 4) {
|
||||||
|
throw new MessageException("构成多边形至少需要三个有效坐标,并且保证尾部坐标和首部坐标相同");
|
||||||
|
}
|
||||||
|
// 检测首尾是否相等,不相等则补充尾坐标
|
||||||
|
List<BigDecimal> startPoint = params.get(0);
|
||||||
|
List<BigDecimal> entPoint = params.get(params.size() - 1);
|
||||||
|
if (!Objects.equals(startPoint.get(0), entPoint.get(0))
|
||||||
|
|| !Objects.equals(startPoint.get(1), entPoint.get(1))) {
|
||||||
|
params.add(params.get(0));
|
||||||
|
}
|
||||||
|
Coordinate[] coordinates = convertBigDecimalList2CoordinateArray(params);
|
||||||
|
return GEOMETRY_FACTORY.createPolygon(coordinates);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Polygon createPolygonWithDouble(List<List<Double>> params) {
|
||||||
|
if (params == null || params.size() < 4) {
|
||||||
|
throw new MessageException("构成多边形至少需要三个有效坐标,并且保证尾部坐标和首部坐标相同");
|
||||||
|
}
|
||||||
|
// 检测首尾是否相等,不相等则补充尾坐标
|
||||||
|
List<Double> startPoint = params.get(0);
|
||||||
|
List<Double> entPoint = params.get(params.size() - 1);
|
||||||
|
if (!Objects.equals(startPoint.get(0), entPoint.get(0))
|
||||||
|
|| !Objects.equals(startPoint.get(1), entPoint.get(1))) {
|
||||||
|
params.add(params.get(0));
|
||||||
|
}
|
||||||
|
Coordinate[] coordinates = convertDoubleList2CoordinateArray(params);
|
||||||
|
return GEOMETRY_FACTORY.createPolygon(coordinates);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从WKT创建多边形对象
|
||||||
|
*
|
||||||
|
* @param wkt 注WKT类似:POLYGON((20 10, 30 0, 40 10, 30 20, 20 10))
|
||||||
|
*/
|
||||||
|
public static Polygon createPolygon(String wkt) {
|
||||||
|
return createGeometryFromWkt(wkt, Polygon.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建多边形对象
|
||||||
|
*
|
||||||
|
* @param bytes 数据库二进制数据
|
||||||
|
*/
|
||||||
|
public static Polygon createPolygon(byte[] bytes) {
|
||||||
|
return createGeometryFromBytes(bytes, Polygon.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
//** 折线 **\\
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建折线对象
|
||||||
|
*/
|
||||||
|
public static LineString createLineString(List<List<BigDecimal>> params) {
|
||||||
|
if (params == null || params.size() < 2) {
|
||||||
|
throw new MessageException("构成折线至少需要两个有效坐标");
|
||||||
|
}
|
||||||
|
Coordinate[] coordinates = convertBigDecimalList2CoordinateArray(params);
|
||||||
|
return GEOMETRY_FACTORY.createLineString(coordinates);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 wkt 创建折线对象
|
||||||
|
*/
|
||||||
|
public static LineString createLineString(String wkt) {
|
||||||
|
return createGeometryFromWkt(wkt, LineString.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 bytes 创建折线对象
|
||||||
|
*/
|
||||||
|
public static LineString createLineString(byte[] bytes) {
|
||||||
|
return createGeometryFromBytes(bytes, LineString.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//** 多点 **\\
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建多点对象
|
||||||
|
*/
|
||||||
|
public static MultiPoint createMultiPoint(List<List<BigDecimal>> multiParam) {
|
||||||
|
int size = multiParam.size();
|
||||||
|
Point[] points = new Point[size];
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
points[i] = createPoint(multiParam.get(i));
|
||||||
|
}
|
||||||
|
return GEOMETRY_FACTORY.createMultiPoint(points);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 wkt 创建多点对象
|
||||||
|
*/
|
||||||
|
public static MultiPoint createMultiPoint(String wkt) {
|
||||||
|
return createGeometryFromWkt(wkt, MultiPoint.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 bytes 创建多点对象
|
||||||
|
*/
|
||||||
|
public static MultiPoint createMultiPoint(byte[] bytes) {
|
||||||
|
return createGeometryFromBytes(bytes, MultiPoint.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//** 多折线 **\\
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建多折线对象
|
||||||
|
*/
|
||||||
|
public static MultiLineString createMultiLineString(List<List<List<BigDecimal>>> multiParams) {
|
||||||
|
int size = multiParams.size();
|
||||||
|
LineString[] lineStrings = new LineString[size];
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
lineStrings[i] = createLineString(multiParams.get(i));
|
||||||
|
}
|
||||||
|
return GEOMETRY_FACTORY.createMultiLineString(lineStrings);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 wkt 创建多折线对象
|
||||||
|
*/
|
||||||
|
public static MultiLineString createMultiLineString(String wkt) {
|
||||||
|
return createGeometryFromWkt(wkt, MultiLineString.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 wkt 创建多折线对象
|
||||||
|
*/
|
||||||
|
public static MultiLineString createMultiLineString(byte[] bytes) {
|
||||||
|
return createGeometryFromBytes(bytes, MultiLineString.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
//** 多多边形 **\\
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建多多边形
|
||||||
|
*/
|
||||||
|
public static MultiPolygon createMultiPolygon(List<List<List<BigDecimal>>> multiParams) {
|
||||||
|
int size = multiParams.size();
|
||||||
|
Polygon[] polygons = new Polygon[size];
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
polygons[i] = createPolygon(multiParams.get(i));
|
||||||
|
}
|
||||||
|
return GEOMETRY_FACTORY.createMultiPolygon(polygons);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 wkt 创建多多边形
|
||||||
|
*/
|
||||||
|
public static MultiPolygon createMultiPolygon(String wkt) {
|
||||||
|
return createGeometryFromWkt(wkt, MultiPolygon.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 bytes 创建多多边形
|
||||||
|
*/
|
||||||
|
public static MultiPolygon createMultiPolygon(byte[] bytes) {
|
||||||
|
return createGeometryFromBytes(bytes, MultiPolygon.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
//** 通用工具 **\\
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* bytes 创建空间对象
|
||||||
|
*
|
||||||
|
* @param bytes 数据库的二进制数据
|
||||||
|
*/
|
||||||
|
public static <T extends Geometry> T createGeometryFromBytes(byte[] bytes, Class<T> clazz) {
|
||||||
|
if (bytes == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
WKBReader wkbReader = new WKBReader(GEOMETRY_FACTORY);
|
||||||
|
byte[] geomBytes = ByteBuffer.allocate(bytes.length - 4).order(ByteOrder.LITTLE_ENDIAN)
|
||||||
|
.put(bytes, 4, bytes.length - 4).array();
|
||||||
|
Geometry geometry = wkbReader.read(geomBytes);
|
||||||
|
return Convert.convert(clazz, geometry);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new MessageException("从 Bytes 创建空间对象 {} 时出现错误:{}", clazz.getSimpleName(), e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 wkt 创建空间对象
|
||||||
|
*
|
||||||
|
* @param wkt 类似:POLYGON((20 10, 30 0, 40 10, 30 20, 20 10)) , POINT(109.013388 32.715519) 等
|
||||||
|
* @param clazz 继承
|
||||||
|
*/
|
||||||
|
public static <T extends Geometry> T createGeometryFromWkt(String wkt, Class<T> clazz) {
|
||||||
|
if (wkt == null || StrUtil.isBlank(wkt)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
WKTReader reader = new WKTReader(GEOMETRY_FACTORY);
|
||||||
|
Geometry geometry = reader.read(wkt);
|
||||||
|
return Convert.convert(clazz, geometry);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
throw new MessageException("从WKT创建空间对象 {} 时出现错误。wkt:{}", clazz.getSimpleName(), wkt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 GeoJson 创建空间对象
|
||||||
|
*/
|
||||||
|
public static <T extends Geometry> T createGeometryFromGeoJson(String geoJson, Class<T> clazz) {
|
||||||
|
try {
|
||||||
|
GeometryJSON geometryJson = new GeometryJSON(20);
|
||||||
|
Geometry read = geometryJson.read(new StringReader(geoJson));
|
||||||
|
return createGeometryFromWkt(read.toString(), clazz);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("从 GeoJson 创建空间对象出错:{}", e.getMessage());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 List<LngLatParam> 转化为 Coordinate[]
|
||||||
|
*/
|
||||||
|
private static Coordinate[] convertBigDecimalList2CoordinateArray(List<List<BigDecimal>> params) {
|
||||||
|
int size = params.size();
|
||||||
|
Coordinate[] coordinates = new Coordinate[size];
|
||||||
|
for (int i = 0; i < params.size(); i++) {
|
||||||
|
List<BigDecimal> param = params.get(i);
|
||||||
|
Coordinate coordinate = new Coordinate(param.get(0).doubleValue(), param.get(1).doubleValue());
|
||||||
|
coordinates[i] = coordinate;
|
||||||
|
}
|
||||||
|
return coordinates;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Coordinate[] convertDoubleList2CoordinateArray(List<List<Double>> params) {
|
||||||
|
int size = params.size();
|
||||||
|
Coordinate[] coordinates = new Coordinate[size];
|
||||||
|
for (int i = 0; i < params.size(); i++) {
|
||||||
|
List<Double> param = params.get(i);
|
||||||
|
Coordinate coordinate = new Coordinate(param.get(0), param.get(1));
|
||||||
|
coordinates[i] = coordinate;
|
||||||
|
}
|
||||||
|
return coordinates;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算面积 平方米
|
||||||
|
* 28155 .195928.042862
|
||||||
|
*/
|
||||||
|
public static BigDecimal getApproximateArea(Geometry geometry) {
|
||||||
|
try {
|
||||||
|
String wkt = geometry.toText();
|
||||||
|
WKTReader wktReader = new WKTReader(LOCATION_TECH_GEOMETRY_FACTORY);
|
||||||
|
Geometry geom = wktReader.read(wkt);
|
||||||
|
// WGS84(一般项目中常用的是CSR:84和EPSG:4326)
|
||||||
|
CoordinateReferenceSystem sourceCRS = CRS.decode("EPSG:4326");
|
||||||
|
// Pseudo-Mercator(墨卡托投影)
|
||||||
|
CoordinateReferenceSystem targetCRS = CRS.decode("EPSG:4490");
|
||||||
|
MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS, true);
|
||||||
|
Geometry geometryMercator = JTS.transform(geom, transform);
|
||||||
|
// 面积、周长
|
||||||
|
return BigDecimal.valueOf(geometryMercator.getArea());
|
||||||
|
} catch (FactoryException | TransformException | ParseException e) {
|
||||||
|
log.error("计算面积出错:{}", e.getMessage());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前数据库的数据来源都是高德数据源 坐标系为GCJ02 如果来源是其他的地图的需要根据规则转换成GCJ02
|
||||||
|
* 同理如果需要转换到其他的数据源的坐标系也需要进行转换
|
||||||
|
*/
|
||||||
|
public static <T extends Geometry> T convertWgs84ToGcj02(T geometry) {
|
||||||
|
log.info("原始数据:{}", geometry.toText());
|
||||||
|
Coordinate[] coordinates = geometry.getCoordinates();
|
||||||
|
for (Coordinate coordinate : coordinates) {
|
||||||
|
double lon = coordinate.x;
|
||||||
|
double lat = coordinate.y;
|
||||||
|
double[] doubles = GpsConvertUtil.wgs84ToGcj02(lon, lat);
|
||||||
|
coordinate.x = doubles[0];
|
||||||
|
coordinate.y = doubles[1];
|
||||||
|
}
|
||||||
|
log.info("转后数据:{}", geometry.toText());
|
||||||
|
return geometry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double distance(Point p0, Point p1) {
|
||||||
|
GeodeticCalculator calculator = new GeodeticCalculator();
|
||||||
|
calculator.setStartingGeographicPoint(p0.getX(), p0.getY());
|
||||||
|
calculator.setDestinationGeographicPoint(p1.getX(), p1.getY());
|
||||||
|
return calculator.getOrthodromicDistance();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Point2D calculateEndingGlobalCoordinates(double longitude, double latitude, double azi, double dis) {
|
||||||
|
GeodeticCalculator calculator = new GeodeticCalculator();
|
||||||
|
calculator.setStartingGeographicPoint(longitude, latitude);
|
||||||
|
calculator.setDirection(azi, dis);
|
||||||
|
return calculator.getDestinationGeographicPoint();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
package com.changhu.common.utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* author: luozhun
|
||||||
|
* desc: 坐标系转换
|
||||||
|
* createTime: 2023/8/25 18:18
|
||||||
|
*/
|
||||||
|
public class GpsConvertUtil {
|
||||||
|
public static final double pi = 3.1415926535897932384626;
|
||||||
|
public static final double xpi = 3.14159265358979324 * 3000.0 / 180.0;
|
||||||
|
public static final double a = 6378245.0;
|
||||||
|
public static final double ee = 0.00669342162296594323;
|
||||||
|
|
||||||
|
public static double transformLat(double x, double y) {
|
||||||
|
double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y
|
||||||
|
+ 0.2 * Math.sqrt(Math.abs(x));
|
||||||
|
ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;
|
||||||
|
ret += (20.0 * Math.sin(y * pi) + 40.0 * Math.sin(y / 3.0 * pi)) * 2.0 / 3.0;
|
||||||
|
ret += (160.0 * Math.sin(y / 12.0 * pi) + 320 * Math.sin(y * pi / 30.0)) * 2.0 / 3.0;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double transformLon(double x, double y) {
|
||||||
|
double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1
|
||||||
|
* Math.sqrt(Math.abs(x));
|
||||||
|
ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;
|
||||||
|
ret += (20.0 * Math.sin(x * pi) + 40.0 * Math.sin(x / 3.0 * pi)) * 2.0 / 3.0;
|
||||||
|
ret += (150.0 * Math.sin(x / 12.0 * pi) + 300.0 * Math.sin(x / 30.0
|
||||||
|
* pi)) * 2.0 / 3.0;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double[] transform(double lon, double lat) {
|
||||||
|
if (outOfChina(lon, lat)) {
|
||||||
|
return new double[]{lon, lat};
|
||||||
|
}
|
||||||
|
double dLat = transformLat(lon - 105.0, lat - 35.0);
|
||||||
|
double dLon = transformLon(lon - 105.0, lat - 35.0);
|
||||||
|
double radLat = lat / 180.0 * pi;
|
||||||
|
double magic = Math.sin(radLat);
|
||||||
|
magic = 1 - ee * magic * magic;
|
||||||
|
double sqrtMagic = Math.sqrt(magic);
|
||||||
|
dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi);
|
||||||
|
dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi);
|
||||||
|
double mgLat = lat + dLat;
|
||||||
|
double mgLon = lon + dLon;
|
||||||
|
return new double[]{mgLon, mgLat};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean outOfChina(double lon, double lat) {
|
||||||
|
if (lon < 72.004 || lon > 137.8347)
|
||||||
|
return true;
|
||||||
|
return lat < 0.8293 || lat > 55.8271;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 84 to 火星坐标系 (GCJ-02) World Geodetic System ==> Mars Geodetic System
|
||||||
|
*/
|
||||||
|
public static double[] wgs84ToGcj02(double lon, double lat) {
|
||||||
|
if (outOfChina(lon, lat)) {
|
||||||
|
return new double[]{lon, lat};
|
||||||
|
}
|
||||||
|
double dLat = transformLat(lon - 105.0, lat - 35.0);
|
||||||
|
double dLon = transformLon(lon - 105.0, lat - 35.0);
|
||||||
|
double radLat = lat / 180.0 * pi;
|
||||||
|
double magic = Math.sin(radLat);
|
||||||
|
magic = 1 - ee * magic * magic;
|
||||||
|
double sqrtMagic = Math.sqrt(magic);
|
||||||
|
dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi);
|
||||||
|
dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi);
|
||||||
|
double mgLat = lat + dLat;
|
||||||
|
double mgLon = lon + dLon;
|
||||||
|
return new double[]{mgLon, mgLat};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* * 火星坐标系 (GCJ-02) to 84 * * @param lon * @param lat * @return
|
||||||
|
*/
|
||||||
|
public static double[] gcj02ToWgs84(double lon, double lat) {
|
||||||
|
double[] gps = transform(lon, lat);
|
||||||
|
double longitude = lon * 2 - gps[0];
|
||||||
|
double latitude = lat * 2 - gps[1];
|
||||||
|
return new double[]{longitude, latitude};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换算法 将 GCJ-02 坐标转换成 BD-09 坐标
|
||||||
|
*/
|
||||||
|
public static double[] gcj02ToBd09(double lon, double lat) {
|
||||||
|
double z = Math.sqrt(lon * lon + lat * lat) + 0.00002 * Math.sin(lat * xpi);
|
||||||
|
double theta = Math.atan2(lat, lon) + 0.000003 * Math.cos(lon * xpi);
|
||||||
|
double tempLon = z * Math.cos(theta) + 0.0065;
|
||||||
|
double tempLat = z * Math.sin(theta) + 0.006;
|
||||||
|
return new double[]{tempLon, tempLat};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* * 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换算法 * * 将 BD-09 坐标转换成GCJ-02 坐标 * * @param
|
||||||
|
* bdlat * @param bdlon * @return
|
||||||
|
*/
|
||||||
|
public static double[] bd09ToGcj02(double lon, double lat) {
|
||||||
|
double x = lon - 0.0065, y = lat - 0.006;
|
||||||
|
double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * xpi);
|
||||||
|
double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * xpi);
|
||||||
|
double tempLon = z * Math.cos(theta);
|
||||||
|
double tempLat = z * Math.sin(theta);
|
||||||
|
return new double[]{tempLon, tempLat};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将wgs84转为bd09
|
||||||
|
*/
|
||||||
|
public static double[] wgs84ToBd09(double lon, double lat) {
|
||||||
|
double[] gcj02 = wgs84ToGcj02(lon, lat);
|
||||||
|
return gcj02ToBd09(gcj02[0], gcj02[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double[] bd09ToWgs84(double lon, double lat) {
|
||||||
|
double[] gcj02 = bd09ToGcj02(lon, lat);
|
||||||
|
double[] wgs84 = gcj02ToWgs84(gcj02[0], gcj02[1]);
|
||||||
|
// 保留小数点后六位
|
||||||
|
wgs84[1] = retain6(wgs84[1]);
|
||||||
|
wgs84[0] = retain6(wgs84[0]);
|
||||||
|
return wgs84;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保留小数点后六位
|
||||||
|
*/
|
||||||
|
private static double retain6(double num) {
|
||||||
|
String result = String.format("%.6f", num);
|
||||||
|
return Double.parseDouble(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.experimental.SuperBuilder;
|
import lombok.experimental.SuperBuilder;
|
||||||
|
import org.locationtech.jts.geom.Point;
|
||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
@ -73,6 +74,11 @@ public class EnterprisesUnit extends BaseEntity implements Serializable {
|
||||||
*/
|
*/
|
||||||
private String address;
|
private String address;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 坐标
|
||||||
|
*/
|
||||||
|
private Point point;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 联系人
|
* 联系人
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -7,6 +7,7 @@ import jakarta.validation.constraints.NotBlank;
|
||||||
import jakarta.validation.constraints.NotEmpty;
|
import jakarta.validation.constraints.NotEmpty;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import org.locationtech.jts.geom.Point;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -40,6 +41,9 @@ public class EnterprisesUnitSaveOrUpdateParams {
|
||||||
@Schema(description = "详细地址")
|
@Schema(description = "详细地址")
|
||||||
private String address;
|
private String address;
|
||||||
|
|
||||||
|
@Schema(description = "坐标")
|
||||||
|
private Point point;
|
||||||
|
|
||||||
@Schema(description = "联系人")
|
@Schema(description = "联系人")
|
||||||
private ContactPersonInfo contactPersonInfo;
|
private ContactPersonInfo contactPersonInfo;
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import com.changhu.common.db.enums.EnterprisesUnitType;
|
||||||
import com.changhu.module.management.pojo.model.ContactPersonInfo;
|
import com.changhu.module.management.pojo.model.ContactPersonInfo;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import org.locationtech.jts.geom.Point;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author 20252
|
* @author 20252
|
||||||
|
@ -46,6 +47,9 @@ public class EnterprisesUnitPagerVo {
|
||||||
@Schema(description = "地址")
|
@Schema(description = "地址")
|
||||||
private String address;
|
private String address;
|
||||||
|
|
||||||
|
@Schema(description = "坐标")
|
||||||
|
private Point point;
|
||||||
|
|
||||||
@Schema(description = "联系方式")
|
@Schema(description = "联系方式")
|
||||||
private ContactPersonInfo contactPersonInfo;
|
private ContactPersonInfo contactPersonInfo;
|
||||||
|
|
||||||
|
|
|
@ -8,9 +8,18 @@ import com.alibaba.fastjson2.support.spring6.http.converter.FastJsonHttpMessageC
|
||||||
import com.changhu.common.db.BaseEnum;
|
import com.changhu.common.db.BaseEnum;
|
||||||
import com.changhu.common.properties.Fastjson2Properties;
|
import com.changhu.common.properties.Fastjson2Properties;
|
||||||
import com.changhu.support.fastjson2.deserialze.DbEnumDeserializer;
|
import com.changhu.support.fastjson2.deserialze.DbEnumDeserializer;
|
||||||
|
import com.changhu.support.fastjson2.deserialze.LatitudeDeserializer;
|
||||||
|
import com.changhu.support.fastjson2.deserialze.LongitudeDeserializer;
|
||||||
|
import com.changhu.support.fastjson2.deserialze.PointDeserializer;
|
||||||
import com.changhu.support.fastjson2.filter.DesensitizedFilter;
|
import com.changhu.support.fastjson2.filter.DesensitizedFilter;
|
||||||
import com.changhu.support.fastjson2.serializer.DbEnumSerializer;
|
import com.changhu.support.fastjson2.serializer.DbEnumSerializer;
|
||||||
|
import com.changhu.support.fastjson2.serializer.LatitudeSerializer;
|
||||||
|
import com.changhu.support.fastjson2.serializer.LongitudeSerializer;
|
||||||
|
import com.changhu.support.fastjson2.serializer.PointSerializer;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.geotools.measure.Latitude;
|
||||||
|
import org.geotools.measure.Longitude;
|
||||||
|
import org.locationtech.jts.geom.Point;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
|
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
@ -47,9 +56,17 @@ public class FastJson2Config {
|
||||||
JSON.register(clazz, DbEnumDeserializer.instance);
|
JSON.register(clazz, DbEnumDeserializer.instance);
|
||||||
});
|
});
|
||||||
|
|
||||||
JSON.config(JSONWriter.Feature.WriteLongAsString);
|
JSON.register(Latitude.class, LatitudeSerializer.instance);
|
||||||
|
JSON.register(Longitude.class, LongitudeSerializer.instance);
|
||||||
|
JSON.register(Point.class, PointSerializer.instance);
|
||||||
|
|
||||||
//反序列化配置
|
//反序列化配置
|
||||||
|
JSON.register(Latitude.class, LatitudeDeserializer.instance);
|
||||||
|
JSON.register(Longitude.class, LongitudeDeserializer.instance);
|
||||||
|
JSON.register(Point.class, PointDeserializer.instance);
|
||||||
|
|
||||||
|
JSON.config(JSONWriter.Feature.WriteLongAsString);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
package com.changhu.support.fastjson2.deserialze;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONReader;
|
||||||
|
import com.alibaba.fastjson2.reader.ObjectReader;
|
||||||
|
import org.geotools.measure.Latitude;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* author: luozhun
|
||||||
|
* desc: LatitudeDeserializer
|
||||||
|
* createTime: 2023/8/26 16:10
|
||||||
|
*/
|
||||||
|
public class LatitudeDeserializer implements ObjectReader<Latitude> {
|
||||||
|
|
||||||
|
public static final LatitudeDeserializer instance = new LatitudeDeserializer();
|
||||||
|
|
||||||
|
private LatitudeDeserializer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Latitude readObject(JSONReader jsonReader, Type fieldType, Object fieldName, long features) {
|
||||||
|
//读取为BigDecimal来确保精度和避免浮点数的舍入误差
|
||||||
|
BigDecimal value = jsonReader.readBigDecimal();
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new Latitude(value.doubleValue());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package com.changhu.support.fastjson2.deserialze;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONReader;
|
||||||
|
import com.alibaba.fastjson2.reader.ObjectReader;
|
||||||
|
import org.geotools.measure.Longitude;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* author: luozhun
|
||||||
|
* desc: LongitudeDeserializer
|
||||||
|
* createTime: 2023/8/26 16:15
|
||||||
|
*/
|
||||||
|
public class LongitudeDeserializer implements ObjectReader<Longitude> {
|
||||||
|
|
||||||
|
public static final LongitudeDeserializer instance = new LongitudeDeserializer();
|
||||||
|
|
||||||
|
private LongitudeDeserializer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Longitude readObject(JSONReader jsonReader, Type fieldType, Object fieldName, long features) {
|
||||||
|
//读取为BigDecimal来确保精度和避免浮点数的舍入误差
|
||||||
|
BigDecimal value = jsonReader.readBigDecimal();
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new Longitude(value.doubleValue());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package com.changhu.support.fastjson2.deserialze;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import com.alibaba.fastjson2.JSONReader;
|
||||||
|
import com.alibaba.fastjson2.reader.ObjectReader;
|
||||||
|
import com.changhu.common.utils.GeometryUtil;
|
||||||
|
import org.locationtech.jts.geom.Point;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* author: luozhun
|
||||||
|
* desc: PointDeserializer
|
||||||
|
* createTime: 2023/8/26 16:28
|
||||||
|
*/
|
||||||
|
public class PointDeserializer implements ObjectReader<Point> {
|
||||||
|
|
||||||
|
public static final PointDeserializer instance = new PointDeserializer();
|
||||||
|
|
||||||
|
private PointDeserializer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Point readObject(JSONReader jsonReader, Type fieldType, Object fieldName, long features) {
|
||||||
|
List<BigDecimal> list = new ArrayList<>();
|
||||||
|
jsonReader.readArray(list, BigDecimal.class);
|
||||||
|
if (CollUtil.isEmpty(list)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return GeometryUtil.createPoint(list);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package com.changhu.support.fastjson2.serializer;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONWriter;
|
||||||
|
import com.alibaba.fastjson2.writer.ObjectWriter;
|
||||||
|
import org.geotools.measure.Latitude;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* author: luozhun
|
||||||
|
* desc: LatitudeSerializer
|
||||||
|
* createTime: 2023/8/26 15:44
|
||||||
|
*/
|
||||||
|
public class LatitudeSerializer implements ObjectWriter<Latitude> {
|
||||||
|
|
||||||
|
public static final LatitudeSerializer instance = new LatitudeSerializer();
|
||||||
|
|
||||||
|
private LatitudeSerializer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(JSONWriter jsonWriter, Object object, Object fieldName, Type fieldType, long features) {
|
||||||
|
if (object == null) {
|
||||||
|
jsonWriter.writeNull();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
jsonWriter.writeDouble(((Latitude) object).degrees());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
package com.changhu.support.fastjson2.serializer;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONWriter;
|
||||||
|
import com.alibaba.fastjson2.writer.ObjectWriter;
|
||||||
|
import org.geotools.measure.Longitude;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* author: luozhun
|
||||||
|
* desc: LongitudeSerializer
|
||||||
|
* createTime: 2023/8/26 15:46
|
||||||
|
*/
|
||||||
|
public class LongitudeSerializer implements ObjectWriter<Longitude> {
|
||||||
|
|
||||||
|
public static final LongitudeSerializer instance = new LongitudeSerializer();
|
||||||
|
|
||||||
|
private LongitudeSerializer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(JSONWriter jsonWriter, Object object, Object fieldName, Type fieldType, long features) {
|
||||||
|
if (object == null) {
|
||||||
|
jsonWriter.writeNull();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
jsonWriter.writeDouble(((Longitude) object).degrees());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
package com.changhu.support.fastjson2.serializer;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONWriter;
|
||||||
|
import com.alibaba.fastjson2.writer.ObjectWriter;
|
||||||
|
import com.changhu.common.utils.GeometryUtil;
|
||||||
|
import org.geotools.measure.Latitude;
|
||||||
|
import org.geotools.measure.Longitude;
|
||||||
|
import org.locationtech.jts.geom.Coordinate;
|
||||||
|
import org.locationtech.jts.geom.Point;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* author: luozhun
|
||||||
|
* desc: PointSerializer
|
||||||
|
* createTime: 2023/8/26 15:59
|
||||||
|
*/
|
||||||
|
public class PointSerializer implements ObjectWriter<Point> {
|
||||||
|
|
||||||
|
public static final PointSerializer instance = new PointSerializer();
|
||||||
|
|
||||||
|
private PointSerializer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(JSONWriter jsonWriter, Object object, Object fieldName, Type fieldType, long features) {
|
||||||
|
if (object == null) {
|
||||||
|
jsonWriter.writeNull();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Point point = (Point) object;
|
||||||
|
if (GeometryUtil.equals(GeometryUtil.emptyPoint(), point)) {
|
||||||
|
jsonWriter.writeNull();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
jsonWriter.writeAny(
|
||||||
|
Arrays.asList(
|
||||||
|
new Longitude(point.getCoordinate().getOrdinate(Coordinate.X)).degrees(),
|
||||||
|
new Latitude(point.getCoordinate().getOrdinate(Coordinate.Y)).degrees()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionIntercepto
|
||||||
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
|
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||||
import com.changhu.support.mybatisplus.interceptor.CustomDataPermissionHandler;
|
import com.changhu.support.mybatisplus.interceptor.CustomDataPermissionHandler;
|
||||||
|
import com.changhu.support.mybatisplus.interceptor.GeometryInnerInterceptor;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@ -30,6 +31,8 @@ public class CustomMybatisPlusConfig {
|
||||||
interceptor.addInnerInterceptor(paginationInterceptor(DbType.MYSQL));
|
interceptor.addInnerInterceptor(paginationInterceptor(DbType.MYSQL));
|
||||||
// sql性能规范
|
// sql性能规范
|
||||||
// interceptor.addInnerInterceptor(new IllegalSQLInnerInterceptor());
|
// interceptor.addInnerInterceptor(new IllegalSQLInnerInterceptor());
|
||||||
|
// 地理位置支持 配合
|
||||||
|
interceptor.addInnerInterceptor(new GeometryInnerInterceptor());
|
||||||
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
|
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
|
||||||
//乐观锁插件
|
//乐观锁插件
|
||||||
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
|
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.changhu.support.mybatisplus.handler.global.geo;
|
||||||
|
|
||||||
|
import org.apache.ibatis.type.BaseTypeHandler;
|
||||||
|
import org.locationtech.jts.geom.Geometry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* author: luozhun
|
||||||
|
* desc: GeometryTypeHandler
|
||||||
|
* createTime: 2023/8/25 17:59
|
||||||
|
*/
|
||||||
|
public abstract class AbstractGeometryTypeHandler<T extends Geometry> extends BaseTypeHandler<T> {
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
package com.changhu.support.mybatisplus.handler.global.geo;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ClassUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import com.changhu.common.utils.GeometryUtil;
|
||||||
|
import org.apache.ibatis.type.JdbcType;
|
||||||
|
import org.apache.ibatis.type.MappedJdbcTypes;
|
||||||
|
import org.apache.ibatis.type.MappedTypes;
|
||||||
|
import org.locationtech.jts.geom.Point;
|
||||||
|
|
||||||
|
import java.sql.CallableStatement;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* author: luozhun
|
||||||
|
* desc: PointTypeHandler
|
||||||
|
* createTime: 2023/8/26 13:17
|
||||||
|
*/
|
||||||
|
@MappedJdbcTypes(JdbcType.VARCHAR)
|
||||||
|
@MappedTypes(Point.class)
|
||||||
|
public class PointTypeHandler extends AbstractGeometryTypeHandler<Point> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNonNullParameter(PreparedStatement preparedStatement, int i, Point point, JdbcType jdbcType) throws SQLException {
|
||||||
|
preparedStatement.setString(i, point.toText());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Point getNullableResult(ResultSet resultSet, String s) throws SQLException {
|
||||||
|
return createPoint(resultSet.getString(s), resultSet.getBytes(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Point getNullableResult(ResultSet resultSet, int i) throws SQLException {
|
||||||
|
return createPoint(resultSet.getString(i), resultSet.getBytes(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Point getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
|
||||||
|
return createPoint(callableStatement.getString(i), callableStatement.getBytes(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Point createPoint(String pointStr, byte[] pointBytes) {
|
||||||
|
if (JSONUtil.isTypeJSON(pointStr)) {
|
||||||
|
return GeometryUtil.createGeometryFromGeoJson(pointStr, Point.class);
|
||||||
|
}
|
||||||
|
if (StrUtil.startWithIgnoreCase(pointStr, ClassUtil.getClassName(Point.class, true))) {
|
||||||
|
return GeometryUtil.createGeometryFromWkt(pointStr, Point.class);
|
||||||
|
}
|
||||||
|
return GeometryUtil.createPoint(pointBytes);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,133 @@
|
||||||
|
package com.changhu.support.mybatisplus.interceptor;
|
||||||
|
|
||||||
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.util.ReUtil;
|
||||||
|
import cn.hutool.core.util.ReflectUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.Constants;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
|
||||||
|
import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.ibatis.executor.statement.StatementHandler;
|
||||||
|
import org.apache.ibatis.mapping.BoundSql;
|
||||||
|
import org.apache.ibatis.mapping.MappedStatement;
|
||||||
|
import org.apache.ibatis.mapping.SqlCommandType;
|
||||||
|
import org.locationtech.jts.geom.*;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static com.changhu.common.utils.GeometryUtil.ST_GeomFromText;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author 20252
|
||||||
|
* @createTime 2024/6/6 下午4:32
|
||||||
|
* @desc 注意!!!此拦截器只针对mp提供的基本构造方法管用 可以进行参数对象拦截 如果是xml,请自行添加ST_GeomFromText函数
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class GeometryInnerInterceptor extends JsqlParserSupport implements InnerInterceptor {
|
||||||
|
|
||||||
|
|
||||||
|
public static final List<Class<? extends Geometry>> GEOMETRY_CLASS_LIST = CollUtil.newArrayList(
|
||||||
|
Point.class,
|
||||||
|
MultiPoint.class,
|
||||||
|
Polygon.class,
|
||||||
|
MultiPolygon.class,
|
||||||
|
LineString.class,
|
||||||
|
MultiLineString.class
|
||||||
|
);
|
||||||
|
|
||||||
|
public GeometryInnerInterceptor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
|
||||||
|
PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
|
||||||
|
MappedStatement ms = mpSh.mappedStatement();
|
||||||
|
//sql类型:INSERT UPDATE DELETE
|
||||||
|
SqlCommandType sct = ms.getSqlCommandType();
|
||||||
|
//mp参数
|
||||||
|
Object parameter = mpSh.parameterHandler().getParameterObject();
|
||||||
|
//参数对象
|
||||||
|
Object object = null;
|
||||||
|
//获取参数的属性
|
||||||
|
switch (sct) {
|
||||||
|
case UPDATE:
|
||||||
|
try {
|
||||||
|
object = BeanUtil.beanToMap(parameter).get(Constants.ENTITY);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case INSERT:
|
||||||
|
object = parameter;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
//如果没有参数 就不进行sql修改
|
||||||
|
if (object == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//获取 parameter 对象 中有关 geometry 的属性名对应
|
||||||
|
List<String> geoFields = this.getGeoFields(object);
|
||||||
|
//如果没有geometry字段 就不进行sql修改
|
||||||
|
if (geoFields.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//获取原始sql
|
||||||
|
BoundSql boundSql = ms.getBoundSql(parameter);
|
||||||
|
//UPDATE geo_test SET update_by=?,update_time=?,delete_flag=1 WHERE id=? AND delete_flag=0
|
||||||
|
String originalSql = StrUtil.removeAllLineBreaks(boundSql.getSql());
|
||||||
|
switch (sct) {
|
||||||
|
case UPDATE:
|
||||||
|
for (String geometryField : geoFields) {
|
||||||
|
String regex = geometryField + "\\s*=\\s*\\?";
|
||||||
|
originalSql = ReUtil.replaceAll(originalSql, regex, StrUtil.format("{}={}", geometryField, ST_GeomFromText));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case INSERT:
|
||||||
|
for (String geometryField : geoFields) {
|
||||||
|
originalSql = this.replaceFindGeo(originalSql, geometryField);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
|
||||||
|
//修改sql
|
||||||
|
mpBs.sql(originalSql);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String replaceFindGeo(String originalSql, String geometryField) {
|
||||||
|
List<String> split = StrUtil.split(originalSql, geometryField);
|
||||||
|
//找出geo字段在sql中的位置
|
||||||
|
int geometryFieldIndex = StrUtil.count(split.get(0), ",");
|
||||||
|
StringBuilder stringBuffer = new StringBuilder();
|
||||||
|
Matcher matcher = Pattern.compile("\\?").matcher(originalSql);
|
||||||
|
int count = 0;
|
||||||
|
while (matcher.find()) {
|
||||||
|
if (count == geometryFieldIndex) {
|
||||||
|
matcher.appendReplacement(stringBuffer, ST_GeomFromText);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
matcher.appendTail(stringBuffer);
|
||||||
|
return stringBuffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取对象中的geo字段
|
||||||
|
*/
|
||||||
|
private List<String> getGeoFields(Object object) {
|
||||||
|
return Arrays.stream(ReflectUtil.getFields(object.getClass()))
|
||||||
|
.filter(field -> GEOMETRY_CLASS_LIST.contains(field.getType()))
|
||||||
|
.map(Field::getName)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -18,3 +18,4 @@ VITE_APP_MINIO_BUCKET=police-security-dev
|
||||||
# 高德
|
# 高德
|
||||||
VITE_APP_GAODE_KEY=f379a3f860a68d7438526275d6a94b05
|
VITE_APP_GAODE_KEY=f379a3f860a68d7438526275d6a94b05
|
||||||
VITE_APP_GAODE_VERSION=2.0
|
VITE_APP_GAODE_VERSION=2.0
|
||||||
|
VITE_APP_SECURITY_JS_CODE=432125a0f8d8cad2dac38b77d6f6728f
|
||||||
|
|
|
@ -18,3 +18,4 @@ VITE_APP_MINIO_BUCKET=police-security
|
||||||
# 高德
|
# 高德
|
||||||
VITE_APP_GAODE_KEY=f379a3f860a68d7438526275d6a94b05
|
VITE_APP_GAODE_KEY=f379a3f860a68d7438526275d6a94b05
|
||||||
VITE_APP_GAODE_VERSION=2.0
|
VITE_APP_GAODE_VERSION=2.0
|
||||||
|
VITE_APP_SECURITY_JS_CODE=432125a0f8d8cad2dac38b77d6f6728f
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {initMap} from "@/utils/aMapUtil";
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
plugins?: string[],
|
plugins?: string[],
|
||||||
initCallback?: () => void,
|
initCallback?: (map: AMap.Map) => void,
|
||||||
mapOptions?: AMap.MapOptions
|
mapOptions?: AMap.MapOptions
|
||||||
}>(), {
|
}>(), {
|
||||||
plugins: () => {
|
plugins: () => {
|
||||||
|
@ -36,8 +36,8 @@ defineExpose({
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initMap(props.plugins).then(AMap => {
|
initMap(props.plugins).then(AMap => {
|
||||||
props.initCallback && props.initCallback()
|
|
||||||
map.value = new AMap.Map(mapId, props.mapOptions)
|
map.value = new AMap.Map(mapId, props.mapOptions)
|
||||||
|
props.initCallback && props.initCallback(map.value)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -76,6 +76,7 @@ export interface EnterprisesUnitPagerVo extends BaseTableRowRecord {
|
||||||
streetName?: string;
|
streetName?: string;
|
||||||
/** 地址 **/
|
/** 地址 **/
|
||||||
address?: string;
|
address?: string;
|
||||||
|
point: [number, number]
|
||||||
/** 联系方式 **/
|
/** 联系方式 **/
|
||||||
contactPersonInfo?: {
|
contactPersonInfo?: {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -98,6 +99,7 @@ export interface EnterprisesUnitSaveOrUpdateParams {
|
||||||
administrativeDivisionCodes: string[];
|
administrativeDivisionCodes: string[];
|
||||||
/** 详细地址 **/
|
/** 详细地址 **/
|
||||||
address?: string;
|
address?: string;
|
||||||
|
point?: [number, number]
|
||||||
/** 联系人 **/
|
/** 联系人 **/
|
||||||
contactPersonInfo?: {
|
contactPersonInfo?: {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
|
@ -4,7 +4,7 @@ type Amap = typeof AMap;
|
||||||
export const initMap = (plugins?: string[]): Promise<Amap> => new Promise((resolve, reject) => {
|
export const initMap = (plugins?: string[]): Promise<Amap> => new Promise((resolve, reject) => {
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
window._AMapSecurityConfig = {
|
window._AMapSecurityConfig = {
|
||||||
securityJsCode: '432125a0f8d8cad2dac38b77d6f6728f'
|
securityJsCode: __APP_ENV.VITE_APP_SECURITY_JS_CODE
|
||||||
}
|
}
|
||||||
AMapLoader.load({
|
AMapLoader.load({
|
||||||
key: __APP_ENV.VITE_APP_GAODE_KEY,
|
key: __APP_ENV.VITE_APP_GAODE_KEY,
|
||||||
|
|
|
@ -29,6 +29,21 @@ const saveOrUpdateEnterprisesUnit = (params: _FormType, callback: Function) => {
|
||||||
|
|
||||||
let city = '';
|
let city = '';
|
||||||
|
|
||||||
|
const initMarker = (map: AMap.Map) => {
|
||||||
|
//添加maker点 设置point
|
||||||
|
const maker = new AMap.Marker({
|
||||||
|
position: _formParams.value.point,
|
||||||
|
draggable: true
|
||||||
|
})
|
||||||
|
maker.on("dragend", ({lnglat}) => {
|
||||||
|
_formParams.value.point = lnglat
|
||||||
|
})
|
||||||
|
map.clearMap()
|
||||||
|
map.add(maker)
|
||||||
|
map.setFitView()
|
||||||
|
console.log(123);
|
||||||
|
}
|
||||||
|
|
||||||
const _formOptions = ref<FormProMaxItemOptions<_FormType>>({
|
const _formOptions = ref<FormProMaxItemOptions<_FormType>>({
|
||||||
name: {
|
name: {
|
||||||
type: 'input',
|
type: 'input',
|
||||||
|
@ -62,7 +77,7 @@ const saveOrUpdateEnterprisesUnit = (params: _FormType, callback: Function) => {
|
||||||
customRender: () => <MapContainer
|
customRender: () => <MapContainer
|
||||||
ref={_mapRef}
|
ref={_mapRef}
|
||||||
style={{width: '100%', height: '300px', position: 'relative'}}
|
style={{width: '100%', height: '300px', position: 'relative'}}
|
||||||
initCallback={() => {
|
initCallback={(map) => {
|
||||||
AMap.plugin(['AMap.AutoComplete'], () => {
|
AMap.plugin(['AMap.AutoComplete'], () => {
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
const auto = new AMap.AutoComplete({
|
const auto = new AMap.AutoComplete({
|
||||||
|
@ -76,25 +91,19 @@ const saveOrUpdateEnterprisesUnit = (params: _FormType, callback: Function) => {
|
||||||
message.error('所选点位没有经纬度信息 建议选则附近的手动移动!');
|
message.error('所选点位没有经纬度信息 建议选则附近的手动移动!');
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//添加maker点 设置point
|
_formParams.value.point = e.poi.location
|
||||||
const maker = new AMap.Marker({
|
initMarker(map)
|
||||||
position: e.poi.location,
|
|
||||||
draggable: true
|
|
||||||
})
|
|
||||||
console.log(e);
|
|
||||||
maker.on("dragend", (e) => {
|
|
||||||
console.log(e);
|
|
||||||
|
|
||||||
})
|
|
||||||
_mapRef.value.mapInstance.add(maker)
|
|
||||||
_mapRef.value.mapInstance.setFitView()
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
if (_formParams.value.point) {
|
||||||
|
initMarker(map)
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{position: 'absolute', left: '10px', top: '10px', zIndex: 9999}}>
|
<div style={{position: 'absolute', left: '10px', top: '10px', zIndex: 9999}}>
|
||||||
<input id={'tipinput'}
|
<input id={'tipinput'}
|
||||||
placeholder={'请输入详细地址'}
|
placeholder={'请输入详细地址'}
|
||||||
|
autocomplete="off"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</MapContainer>
|
</MapContainer>
|
||||||
|
@ -176,6 +185,7 @@ export const showEnterprisesUnit = (policeUnitPagerVo: PoliceUnitPagerVo) => {
|
||||||
type: record.type.value,
|
type: record.type.value,
|
||||||
administrativeDivisionCodes: [record.province, record.city, record.districts, record.street].filter(Boolean),
|
administrativeDivisionCodes: [record.province, record.city, record.districts, record.street].filter(Boolean),
|
||||||
address: record.address,
|
address: record.address,
|
||||||
|
point: record.point,
|
||||||
contactPersonInfoName: record.contactPersonInfo?.name,
|
contactPersonInfoName: record.contactPersonInfo?.name,
|
||||||
contactPersonInfoTelephone: record.contactPersonInfo?.telephone,
|
contactPersonInfoTelephone: record.contactPersonInfo?.telephone,
|
||||||
remark: record.remark
|
remark: record.remark
|
||||||
|
|
|
@ -129,6 +129,29 @@ const searchFormOptions = ref<TableProps["searchFormOptions"]>({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const a = {
|
||||||
|
groupId1: {
|
||||||
|
itemId1: {
|
||||||
|
standardId: 123123,
|
||||||
|
deductionPoints: 2
|
||||||
|
},
|
||||||
|
itemId2: {
|
||||||
|
standardId: 345345,
|
||||||
|
deductionPoints: 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
groupId2: {
|
||||||
|
itemId1: {
|
||||||
|
standardId: 456456,
|
||||||
|
deductionPoints: 2
|
||||||
|
},
|
||||||
|
itemId2: {
|
||||||
|
standardId: 567567,
|
||||||
|
deductionPoints: 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|
|
@ -23,6 +23,7 @@ interface ImportMetaEnv {
|
||||||
// 高德
|
// 高德
|
||||||
VITE_APP_GAODE_KEY: string
|
VITE_APP_GAODE_KEY: string
|
||||||
VITE_APP_GAODE_VERSION: string
|
VITE_APP_GAODE_VERSION: string
|
||||||
|
VITE_APP_SECURITY_JS_CODE: string
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module '*.vue' {
|
declare module '*.vue' {
|
||||||
|
|
Loading…
Reference in New Issue