CREATE TABLE `zone_area` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(32) DEFAULT NULL, `location` geometry DEFAULT NULL, `geohash` varchar(20) GENERATED ALWAYS AS (st_geohash(`location`,8)) VIRTUAL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COMMENT='区';
ALTER TABLE zone_area ADD COLUMN `geohash` varchar(20) GENERATED ALWAYS AS (st_geohash(`location`,8)) VIRTUAL;
字段: 使用geometry类型存储空间点数据;
存储:
SET location = geomfromtext('point(108.9498710632 34.2588125935)'));
搜索: 查询方圆5公里数据? geohash字段是将二维通过geohash算法变成一维;
虚拟自增列 Generated Column是MySQL 5.7引入的新特性,Cenerated Column,就是数据库中这一列由其他列计算而得。
generated column always总是自动生成
FLOOR(X)表示向下取整,只返回值X的整数部分,小数部分舍弃。
CEILING(X) 表示向上取整,只返回值X的整数部分,小数部分舍弃。
#DECIMAL 四舍五入
SELECT CAST('123.456' AS DECIMAL) #123
SELECT CAST('123.456' AS DECIMAL(10,2)) #123.46
ROUND(X) -- 四舍五入
SELECT ROUND('123.456') #123
SELECT ROUND('123.456',2) #123.46
Mybatis如何处理
<方案1>
1.新增和更新方案: 直接手写sql进行更新
新增的话,先设置该字段为空,新增完后,走sql更新单独字段
@Update("update order set coordinate=geomfromtext(#{coordinate}) where id=#{id}") void updateCoordinateById(@Param("id") Long id, @Param("coordinate") String coordinate);
2.查看
pom.xml
<!-- 空间几何 --> <dependency> <groupId>org.geolatte</groupId> <artifactId>geolatte-geom</artifactId> <version>1.8.2</version> </dependency>
entity
@TableName(autoResultMap = true)
class Order {
@ApiModelProperty(value = "GPS")
@TableField(value = "coordinate",typeHandler = GeoPointTypeHandler.class)
private Point coordinate;
}
GeoPointTypeHandler

package com.xx.handler; import org.geolatte.geom.ByteBuffer; import org.geolatte.geom.Point; import org.geolatte.geom.codec.Wkb; import java.sql.CallableStatement; import java.sql.ResultSet; import java.sql.SQLException; /** * 自定义类型转换器映射空间几何数据 */ public class GeoPointTypeHandler extends BaseAttributeTypeHandler<Point > { @Override public Point getNullableResult(ResultSet resultSet, String columnName) throws SQLException { var bytes = (byte[])resultSet.getObject(columnName); return this.mapPoint(bytes); } @Override public Point getNullableResult(ResultSet resultSet, int i) throws SQLException { return (Point) resultSet.getObject(i); } @Override public Point getNullableResult(CallableStatement callableStatement, int i) throws SQLException { return (Point) callableStatement.getObject(i); } /* * bytes转Point对象 */ private Point mapPoint(byte[] bytes) { if (bytes == null) { return null; } try { Point point = (Point) Wkb.fromWkb(ByteBuffer.from(bytes)); return point; } catch (Exception e) { } return null; } }
BaseAttributeTypeHandler

package com.xx.handler; import cn.hutool.json.JSONUtil; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.type.TypeFactory; import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; import org.springframework.core.ResolvableType; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import java.io.IOException; import java.lang.reflect.Type; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; public class BaseAttributeTypeHandler<T> extends BaseTypeHandler<Object> { private JavaType javaType; private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); public BaseAttributeTypeHandler() { ResolvableType resolvableType = ResolvableType.forClass(getClass()); Type type = resolvableType.as(BaseAttributeTypeHandler.class).getGeneric().getType(); javaType = constructType(type); } public static JavaType constructType(Type type) { Assert.notNull(type, "[Assertion failed] - type is required; it must not be null"); return TypeFactory.defaultInstance().constructType(type); } @Override public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, JSONUtil.toJsonStr(parameter)); } @Override public Object getNullableResult(ResultSet rs, String columnName) throws SQLException { String value = rs.getString(columnName); return convertToEntityAttribute(value); } @Override public Object getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return convertToEntityAttribute(rs.getString(columnIndex)); } @Override public Object getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { String value = cs.getString(columnIndex); return convertToEntityAttribute(value); } public Object convertToEntityAttribute(String dbData) { if (StringUtils.isEmpty(dbData)) { if (List.class.isAssignableFrom(javaType.getRawClass())) { return Collections.emptyList(); } else if (Set.class.isAssignableFrom(javaType.getRawClass())) { return Collections.emptySet(); } else if (Map.class.isAssignableFrom(javaType.getRawClass())) { return Collections.emptyMap(); } else { return null; } } return toObject(dbData, javaType); } public static <T> T toObject(String json, JavaType javaType) { Assert.hasText(json, "[Assertion failed] - this json must have text; it must not be null, empty, or blank"); Assert.notNull(javaType, "[Assertion failed] - javaType is required; it must not be null"); try { return OBJECT_MAPPER.readValue(json, javaType); } catch (com.fasterxml.jackson.core.JsonParseException e) { throw new RuntimeException(e.getMessage(), e); } catch (JsonMappingException e) { throw new RuntimeException(e.getMessage(), e); } catch (IOException e) { throw new RuntimeException(e.getMessage(), e); } } }
前端提交的参数
class OrderParam { @ApiModelProperty(value = "GPS") private Coordinate coordinate; }
Coordinate

package com.xx.entity; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.NoArgsConstructor; import org.geolatte.geom.Point; import org.geolatte.geom.codec.Wkt; import org.geolatte.geom.crs.CoordinateSystemAxis; @Data @NoArgsConstructor public class Coordinate { @ApiModelProperty(value = "纬度") private Double latitude; @ApiModelProperty(value = "经度") private Double longitude; public Coordinate(Double longitude, Double latitude) { this.latitude = latitude; this.longitude = longitude; } public Coordinate(Point point) { this.longitude = point.getPosition().getCoordinate(CoordinateSystemAxis.mkLonAxis()); this.latitude = point.getPosition().getCoordinate(CoordinateSystemAxis.mkLatAxis()); } public Point toPoint() { return (Point) Wkt.fromWkt(String.format("POINT(%g %g)", this.getLongitude(), this.getLatitude())); } public double distance(Coordinate other) { if ((this.longitude == other.getLongitude()) && (this.latitude == other.getLatitude())) { return 0; } else { double metersPerDegree = 2 * Math.PI * 6378137 / 360; double theta = this.longitude - other.getLongitude(); double dist = Math.sin(Math.toRadians(this.latitude)) * Math.sin(Math.toRadians(other.getLatitude())) + Math.cos(Math.toRadians(this.latitude)) * Math.cos(Math.toRadians(other.getLatitude())) * Math.cos(Math.toRadians(theta)); dist = Math.acos(dist); dist = Math.toDegrees(dist); dist = dist * metersPerDegree; return dist; } } }
<方案2> 参考https://github.com/tzjzcy/mybatis-mysql-geo-boot