The article directories

    • preface
    • General implementation
    • The database
      • code
    • Custom type + spatial data type
      • MySQL underlying storage format
      • The database
      • code
      • test
      • Calculation of distance

preface

There is a requirement in the recent project to store some latitude and longitude information for the realization of similar return points of interest within 5km (such as surrounding toilets within 5km). In consideration of technical proficiency and operation and maintenance costs, the selection is as follows:

  • Persistence layer framework: Mybatis – Plus
  • Database: mysql5.7.x

General implementation

The database

The database creates two fields, longitude: LNG, latitude: LAT

  • LNG: field type decimal(9,6)
  • Lat: field type decimal(9,6)

code

class toilet {
    String name;
    double lng;
    double lat
}
Copy the code

I won’t go into detail here, but it’s a very common implementation

Because the data structure is not of spatial type, spatial indexes cannot be created, and the query performance is prone to bottlenecks.

Custom type + spatial data type

MySQL supports spatial data types.

Spatial data types and functions are available for MyISAM, InnoDB, NDB, and ARCHIVE tables. For indexing SPATIAL columns, MyISAM and InnoDB support both SPATIAL and non-spatial indexes. Other storage engines support non-spatial indexing

MySQL underlying storage format

Query mysql official website, the result is as follows

The actual storage format of Geometry is 25 bytes

  • 4 bytes for the integer SRID (0)
  • 1 byte (integer byte order) (1 = small byte order)
  • 4 bytes for integer type information (MySQL uses values from 1 to 7 to representPoint.LineString.Polygon.MultiPoint.MultiLineString.MultiPolygon, andGeometryCollection).
  • 8 – byte double – precision X coordinates
  • 8 – byte double – precision Y coordinates

For example, POINT(1-1) consists of the following 25-byte sequence, each represented by two hexadecimal digits:

mysql> SET @g = ST_GeomFromText('POINT(1 -1)');
mysql> SELECT LENGTH(@g);
+------------+
| LENGTH(@g) |
+------------+
|         25 |
+------------+
mysql> SELECT HEX(@g);
+----------------------------------------------------+
| HEX(@g)                                            |
+----------------------------------------------------+
| 000000000101000000000000000000F03F000000000000F0BF |
+----------------------------------------------------+
Copy the code
composition The size of the value
SRID 4 bytes 00000000
Byte order 1 byte 01
WKB type 4 bytes 01000000
X coordinate 8 bytes 000000000000F03F
Y 8 bytes 000000000000F0BF

The database

The database creates a field coordinate

  • Coordinate: Field type Point

code

Entity class

class toilet {
    String name;
    
	geopoint location;
}
// Customize the data type
class geopoint {
    double lng;
    double lat 
}
Copy the code

GeoPointTypeHandler

@Slf4j
@MappedTypes({GeoPoint.class})
public class GeoPointTypeHandler extends BaseTypeHandler<GeoPoint> {
    /** * space reference identifier MySQL database default is 0 */
    private static int SRID = 0;
    A byte order indicator of 1 or 0 indicates small-endian or big-endian storage. Small and large byte orders are also called network data representation (NDR) and external data representation (XDR) */, respectively
    private static byte ENDIAN = (byte) 1;


    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, GeoPoint parameter, JdbcType jdbcType) throws SQLException {
        ps.setBytes(i, to(parameter));
    }

    @Override
    public GeoPoint getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return parse(rs.getBytes(columnName));
    }

    @Override
    public GeoPoint getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return parse(rs.getBytes(columnIndex));
    }

    @Override
    public GeoPoint getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return parse(cs.getBytes(columnIndex));
    }

    /** * bytes Go to GeoPoint object **@param bytes
     */
    private GeoPoint parse(byte[] bytes) {
        ByteBuffer wrap = ByteBuffer.wrap(bytes)
                // Small endpoint sort (Java default is big endpoint sort, this should be changed)
                .order(ByteOrder.LITTLE_ENDIAN);
        int SRID = wrap.getInt();
        byte endian = wrap.get();
        int wkbType = wrap.getInt();
        double x = wrap.getDouble();
        double y = wrap.getDouble();
        GeoPoint geoPoint = new GeoPoint(x, y);
        log.info("geo-point:{}", JSONUtil.toJsonStr(geoPoint));
        return geoPoint;
    }

    /** * GeoPoint Converts bytes to **@param geoPoint
     */
    private byte[] to(GeoPoint geoPoint) {
        ByteBuffer wrap = ByteBuffer.allocate(25)
                // Small endpoint sort (Java default is big endpoint sort, this should be changed)
                .order(ByteOrder.LITTLE_ENDIAN);
        // SRID: 0
        wrap.putInt(SRID);
        A byte order indicator of 1 or 0 indicates small-endian or big-endian storage. Small and large byte orders are also known as network Data Representation (NDR) and External Data Representation (XDR), respectively
        wrap.put(ENDIAN);
        // the WKB type is the code indicating the geometry type wkbType: 1 MySQL uses values from 1 to 7 to represent Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, and GeometryCollection.
        wrap.putInt(1);
        / / X coordinate
        wrap.putDouble(geoPoint.getLon());
        / / Y
        wrap.putDouble(geoPoint.getLat());
        returnwrap.array(); }}Copy the code

The entity class is annotated

@TableName(autoResultMap = true) // Attention!! Mapping annotations must be enabled
class toilet {
    String name;
    @TableField(typeHandler = GeoPointTypeHandler.class)
	geopoint location;
}
Copy the code

test

        Toilet toilet = new Toilet();
        toilet.setName("laker");
        toilet.setLocation(new GeoPoint(123.23.1.2)); // Plug the entity directly
        toiletService.save(toilet);
		/ / queryList<Toilet> toilets = toiletService.list(); . [{"name":"laker",location:{"lng":123.23."lat":1.2}}]
Copy the code

Calculation of distance

The calculation results are consistent with the calculation distance API provided by Autonavi. Autonavi calculates distance webAPI

SELECT ( st_distance_sphere ( point ( 116.481028.39.989643  ), point ( 114.465302.40.004717 ), 6378137.0))AS distance
Copy the code

Note here that the data should not be arbitrarily manipulated or the st_distance_sphere parameter will be returned with an error.

Reference:

  • Dev.mysql.com/doc/refman/…