mysql:列类型之Spatial

79 篇文章 11 订阅

环境:

  • window10
  • vs2022
  • .net 6
  • mysql 8.0.25
  • DBeaver

参考:《mysql:11.4 Spatial Data Types》

1. 认识Spatial Data Types、WKT、WKB

ogc定义的地理空间数据格式。可以存储经纬度地理坐标、空间点、线、面、体等数据。
具体参考:《ogc:OGC Standards》

mysql的实现:
在这里插入图片描述
简单解释下:

  • Geometry : 所有spatial类的父类(可以定义此类型列,但由于它是noninstantiable,所以插入的时候要是指定具体的spatial类);
  • Point:表示点(可以定义此类型列);
  • Curve:表示曲线,弧(不能定义此类型列,也不能创建此实例);
  • LineString:表示线段(可以定义此类型列);
  • Line:表示只有两个点的线段(不能定义此类型列,也不能创建此实例);
  • LinearRing:表示闭合的线段(不能定义此类型列,也不能创建此实例);
  • Surface:表示二维面(不能定义此类型列,也不能创建此实例);
  • Polygon:表示多边形(可以定义此类型列);
  • GeometryCollection:表示geometry的集合(可以定义此类型列);
  • MultiPoint:表示多个点(可以定义此类型列);
  • MultiCurve:表示多个线段、弧(不能定义此类型列,也不能创建此实例);
  • MultiLineString:表示多线段;(可以定义此类型列);
  • MultiSurface:表示多面(不能定义此类型列,也不能创建此实例);
  • MultiPolygon:表示多个多边形;(可以定义此类型列);

wkt和wkb
 WKT(well-known text)是一种文本标记语言,该格式由开放地理空间联盟(OGC)制定,用于表示矢量数据中的几何对象,在数据传输与数据库存储时,常用到它的二进制形式,即WKB(well-known binary)。WKT与WKB在GIS中的重要作用在于,他们能利用文本简洁明了的表达矢量空间要素的几何信息,使得几何信息能以字段的形式存储于数据库中。
WKT相比WKB更方便人们理解,具有很高的可读性。

注意:wkt和wkb中均不存储坐标系信息,它们内部只认X、Y。
关于它们定义可以参考:

《博文:2.3. WKT 描述的几何对象》
《博文:2.4. WKB 描述的几何对象》

2. Spatial数据的读写

创建表和插入数据

create table test(
	t_geometry geometry,
	t_point point SRID 4326,
	-- t_curve curve, -- 报错: 不支持
	t_linestring linestring SRID 4326,
	-- t_line line -- 报错: 不支持
	-- t_LinearRing LinearRing -- 报错: 不支持
	-- t_Surface Surface -- 报错: 不支持
	t_Polygon Polygon SRID 4326,
	t_GeometryCollection GeometryCollection,
	t_MultiPoint MultiPoint SRID 4326,
	-- t_MultiCurve MultiCurve -- 报错: 不支持
	t_MultiLineString MultiLineString SRID 4326,
	-- t_MultiSurface MultiSurface -- 报错: 不支持
	t_MultiPolygon MultiPolygon
)

insert into test(t_geometry) values(ST_GeomFromText('POINT(15 20)'));
insert into test(t_point) values(ST_GeomFromText('POINT(15 20)',4326));
insert into test(t_linestring) values(ST_GeomFromText('LINESTRING(0 0, 10 10, 20 25, 50 60)',4326));
insert into test(t_Polygon) values(ST_GeomFromText('POLYGON((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7, 5 5))',4326));
insert into test(t_GeometryCollection) values(GEOMETRYCOLLECTION(ST_GeomFromText('POINT(10 10)'), ST_GeomFromText('POINT(30 30)')));
insert into test(t_MultiPoint) values(ST_GeomFromText('MULTIPOINT(0 0, 20 20, 60 60)',4326));
insert into test(t_MultiLineString) values(ST_GeomFromText('MULTILINESTRING((10 10, 20 20), (15 15, 30 15))',4326));
insert into test(t_MultiPolygon) values(ST_GeomFromText('MULTIPOLYGON(((0 0,10 0,10 10,0 10,0 0)),((5 5,7 5,7 7,5 7, 5 5)))',4326));


SELECT 
	ST_AsText(t_geometry),
	ST_AsText(t_point),
	ST_AsText(t_linestring),
	ST_AsText(t_Polygon),
	ST_AsText(t_GeometryCollection),
	ST_AsText(t_MultiPoint),
	ST_AsText(t_MultiLineString),
	ST_AsText(t_MultiPolygon)
FROM test;

输出:
在这里插入图片描述
可以看下它的元数据:

select c.TABLE_SCHEMA ,c.TABLE_NAME ,c.COLUMN_NAME ,c.COLUMN_TYPE  ,c.SRS_ID 
from information_schema.`COLUMNS` c where c.TABLE_SCHEMA ='test' and c.TABLE_NAME ='test'

输出:
在这里插入图片描述

3. 是否存储地理数据的坐标系?

当我们需要存储地理数据时是需要指定坐标系的,如果我们仅存储几何数据就不用了。
比如:

  • POINT (113.720512 34.773274):这个是在4326坐标系下郑州市如意湖的坐标;
    在这里插入图片描述

  • POINT (20 15):这个根据经验应该只是几何数据,我们可以不用指定它的坐标系;

那么,我们定义列的时候应该怎么抉择?

如果我们能知道存储地理位置的坐标,那么我们应该在定义时声明它的坐标系,声明后mysql将支持更多的地理操作,否则,很难支持!!!

另外,当我们声明列的坐标系后,我们在插入数据时也要声明数据的坐标系,不声明或声明的与定义的不一致都会报错,如下:

create table test(
	id int auto_increment primary key,
	t_geometry geometry,
	t_point point srid 4326
)
-- 正常: 定义时没有坐标系,可以插入任意坐标系数据
insert into test(t_geometry) values(ST_GeomFromText('POINT(15 20)',4326)); 
-- 正常:定义时没有坐标系,插入时也可以不指定坐标系
insert into test(t_geometry) values(ST_GeomFromText('POINT(15 20)'));-- 正常

-- 正常:定义时声明坐标系(4326),插入4326的坐标数据没毛病
insert into test(t_point) values(ST_GeomFromText('POINT(15 20)',4326)); 
-- 报错:定义时声明坐标系(4326),插入4336的坐标数据会报错
-- insert into test(t_point) values(ST_GeomFromText('POINT(15 20)',4336)); 
-- 报错:定义时声明坐标系(4326),插入时也必须显示声明4326
-- insert into test(t_point) values(ST_GeomFromText('POINT(15 20)')); 

4. wkb标准格式和mysql存储格式

4.1 wkb标准格式

下面是mysql文档中关于wkb标准的介绍
在这里插入图片描述
mysql在实现的时候仅实现了7中类型的结构(Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon and GeometryCollection),其实,wkb标准还有很多格式,如:
在这里插入图片描述

4.2 mysql内部存储格式

mysql内部存储数据的时候在wkb标准前增加4个字节表示坐标系。

如下图:
在这里插入图片描述

5. wkt疑惑点:关于经纬度、xy顺序

参考:

《博文:mysql 8.x 中的 geometry 变更/缓存命中/SRID/EPSG/WKT等问题》
《Axis Order Confusion》

在使用wkt的时候,我经常迷惑,到底是 POINT (113.720512 34.773274)还是POINT (34.773274 113.720512 )

首先,我们明确,wkt和wkb它们都不存储地理坐标系,它们也不认什么经纬度,它们只认XY,所以对wkt和wkb来说,怎么存都一样。

其次,在先后顺序上并不是那么统一,有的软件是精度在前,有的是维度在前。

其实,到底应该哪个在前,地理坐标系中有定义,如:4326的定义(http://epsg.io/4326):
在这里插入图片描述
所以,mysql中默认认为4326坐标系下的wkt应该是POINT (34.773274 113.720512 )(但注意,mysql中允许我们手动指定它的顺序)。

注意,mysql存储的时候是将精度(long)存到X的位置,将维度(lat)存储到Y的位置。

看下面的sql:

-- 默认4326坐标系: point(lat long) 存储: long -> X, lat -> Y
insert into test(t_geometry) values(ST_GeomFromText('POINT (34.773274 113.720512)',4326));

-- 声明wkt中的顺序: point(long lat) 存储: long -> X, lat -> Y
insert into test(t_geometry) values(ST_GeomFromText('POINT (113.720512 34.773274)',4326,'axis-order=long-lat'));

-- 上面两条数据存储到数据库是一样的,不过后者不声明顺序的话会报错,因为"维度113.720512"超范围了(-90.90)

下图展示了不同的存储方式存入到mysql中实际数据:
在这里插入图片描述

6. 使用 NetTopologySuite 软件包处理mysql数据

默认情况,我们用程序读出来的地理数据是mysql内部的二进制格式,如果我们需要用专业的地理软件处理应该怎么样呢?
直接看示例:

//mysql中的存储: srid=4326,type:point,x:113.720512,y:34.773274
//mysql中的16进制: "E6100000010100000009185DDE1C6E5C406E6C76A4FA624140";
//移除前4个字节
var hex = "010100000009185DDE1C6E5C406E6C76A4FA624140";
var bs = BytesToHexConverter.FromHex(hex);
var reader = new NetTopologySuite.IO.WKBReader(new NtsGeometryServices(PrecisionModel.Floating.Value, 4326));
var point = reader.Read(bs) as Point;
var wkt = point.AsText();
//输出: srid=4326,wkt=POINT (113.720512 34.773274)
Console.WriteLine($"srid={point.SRID},wkt={wkt}");

你可能注意到,NetTopologySuite在输出wkt的时候怎么不是POINT (34.773274 113.720512),4326的坐标系中不是定义的维度在前吗?

所以说,到底哪个在前在不同软件中还是有区别的?

  • mysql中认为应该按照4326声明的那样,维度在前,所以在输入输出4326坐标系的数据的时候都接受和输出 wkt(lat long) 格式的数据;
  • NetTopologySuite认为wkt的格式就是 x在前,而wkt/wkb中只有xy,没有经纬度,所以输出POINT (113.720512 34.773274)自然理所当然;
  • 有一点是不变的,维度(lat)永远对应Y,精度(long)永远对应X;
  • 在不同软件中读写的xy顺序不一致在所难免,但它们有的也会留给我们指定顺序的机会;

7. c#实验读写mysql的坐标数据

public static void Main()
{
    ReadMySqlGeometryToBytes();

    //ReadMySqlWKBGeometry("0000000001010000000000000000002E400000000000003440");
    //ReadMySqlWKBGeometry("E6100000010100000000000000000034400000000000002E40");
    //ReadMySqlWKBGeometry("E6100000010100000009185DDE1C6E5C406E6C76A4FA624140");


    Console.WriteLine("ok");
}

public static void ReadMySqlGeometryToBytes()
{
    var connString = "Server=127.0.0.1;Database=test;Uid=root;Pwd=123456;";
    var conn = new MySqlConnector.MySqlConnection(connString);
    conn.Open();
    var cmd = conn.CreateCommand();

    cmd.CommandText = "drop table if exists test";
    cmd.ExecuteNonQuery();

    cmd.CommandText = @"create table test(
id int auto_increment primary key,
t_geometry geometry
)";
    cmd.ExecuteNonQuery();

    cmd.CommandText = "insert into test(t_geometry) values(ST_GeomFromText('POINT(15 20)'));";
    cmd.ExecuteNonQuery();

    cmd.CommandText = "insert into test(t_geometry) values(ST_GeomFromText('POINT(15 20)',4326));";
    cmd.ExecuteNonQuery();

    cmd.CommandText = "insert into test(t_geometry) values(ST_GeomFromText('point(34.773274 113.720512)',4326));";
    cmd.ExecuteNonQuery();

    cmd.CommandText = "insert into test(t_geometry) values(ST_GeomFromText('point(113.720512 34.773274)',4326,'axis-order=long-lat'));";
    cmd.ExecuteNonQuery();



    cmd.CommandText = @"
select id,t_geometry,
st_astext(t_geometry) as stastext,
st_astext(t_geometry,'axis-order=long-lat') as sttextlonglat,
st_astext(t_geometry,'axis-order=lat-long') as sttextlatlong
from test
";
    var reader = cmd.ExecuteReader();
    while (reader.Read())
    {
        //id
        var id = (int)reader[0];
        //t_geometry二进制
        var t_geometry = (byte[])reader[1];
        var hex = Convert.ToHexString(t_geometry);
        //st_astext(t_geometry) as stastext,
        var stastext = reader[2].ToString();
        //st_astext(t_geometry,'axis-order=long-lat') 强制精度在前
        var sttextlonglat = reader[3].ToString();
        //st_astext(t_geometry,'axis-order=lat-long') 强制维度在前
        var sttextlatlong = reader[4].ToString();
        Console.WriteLine($"id={id},t_geometry={hex},stastext={stastext},sttextlonglat={sttextlonglat},sttextlatlong={sttextlatlong}");
    }
    reader.Close();
    conn.Close();
    Console.WriteLine("ok");
    Console.ReadLine();
}

public static void ReadMySqlWKBGeometry(string hex)
{
    var bs = BytesToHexConverter.FromHex(hex);
    var actBs = new byte[bs.Length - 4];
    Array.Copy(bs, 4, actBs, 0, bs.Length - 4);
    int srid = 0;
    int byteOrder = 0;
    int geometryType = 0;
    double x = 0;
    double y = 0;
    unsafe
    {
        //bs[0-3] srid
        fixed (byte* ptr = &bs[0])
        {
            int* p = (int*)ptr;
            srid = p[0];
        }

        //bs[4] 字节序Byte order
        byteOrder = bs[4];

        //bs[5-8] WKB type
        fixed (byte* ptr = &bs[5])
        {
            int* p = (int*)ptr;
            geometryType = p[0];
        }

        //bs[9-16] x
        fixed (byte* ptr = &bs[9])
        {
            double* p = (double*)ptr;
            x = p[0];
        }

        //bs[17-24] y
        fixed (byte* ptr = &bs[17])
        {
            double* p = (double*)ptr;
            y = p[0];
        }
    }

    Console.WriteLine($"--------------源数据: {hex}------------");
    Console.WriteLine($"自行读取结果: srid={srid},字节序={byteOrder},wkbtype={geometryType},x={x},y={y}");

    var reader = new NetTopologySuite.IO.WKBReader();
    var point = reader.Read(actBs) as Point;
    //tx.SRID = srid;
    var wkt = point!.AsText();
    Console.WriteLine($"NetTopologySuite 读取结果: srid={point.SRID},wkbtype={point.GeometryType},wkt={wkt}");
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jackletter

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值