前文
从本文可以认识实践yukon【openGauss】和熟悉理解空间数据库的特点并通过代码体验游客如何寻找最近的酒店的业务。
- 基于openGauss的空间数据库入门 ,并开发一个简单的DEMO示例
- 基于django驱动openGauss2.1的注意事项
- 了解yukon与OpenGauss的区别
- 理解空间数据库的作用和解决的问题 ,它与其它数据库类型的区别。
- 代码实践游客移动及新增酒店的体验
- 感受gis应用中的技术组件中 GDAL\PROJ4\GeoIP的提供的便利
Yukon背景
超图软件协同海量数据、云和恩墨,在openGauss社区深度协作,孵化了首个基于开源数据库openGauss的二三维一体化空间数据库——Yukon(禹贡)
Yukon(禹贡),基于openGauss数据库扩展地理空间数据的存储和管理能力,提供专业的GIS(Geographic Information System)功能,赋能传统关系型数据库。
Yukon 支持二三维一体化的空间数据存储能力:
同时Yukon还支持与PostgreSQL 的适配
目前,Yukon 基于 openGauss/PostgreSQL 扩展的模块包括:
- postgis:与 openGauss/PostgreSQL 适配的 PostGIS 矢量模块;
- postgis_raster:与 openGauss/PostgreSQL 适配的 PostGIS 栅格模块;
- postgis_sfcgal:与 openGauss/PostgreSQL 适配的 PostGIS 三维算法相关模块;
- yukon_geomodel:Yukon自有的三维模型数据模块;
- yukon_geogridcoder:Yukon自有的空间网格编码模块。
模块之间的依赖关系如图:
Yukon与OpenGauss的关系
Yukon就是OpenGauss,而OpenGauss未必是Yukon。
OpenGausss虽然参照了PostgreSQL9.2.4的框架,但是代码侵入到计算层和存储层,OpenGausss完全具备自主可控内核管理能力。 举个例子,PostgreSQL最新版本发展到15了,OpenGausss的路径发展并不依赖PostgreSQL,OpenGausss与PostgreSQL是两条路线。
Yukon却依赖OpenGausss,Yukon是基于OpenGauss2.1的基础研发的空间数据库。假设OpenGauss3.1新版本的读写性能更好,Yukon要使用OpenGauss更好的功能和性能,底层依赖的OpenGauss要随着升级到OpenGauss3.1。
Yukon解决什么问题
大漠孤烟直,长河落日圆。当初荒无人烟的城镇因为旅游资源吸引了大量的游客,游客到了城镇,过宿要找一家酒店住下来。小镇有成千上成的洒店住宿,游客最佳选择是哪一家酒店?从业务的角度来看,酒店与游客位置距离这个指标很重要,最好快速筛选提代前10个离游客最近的酒店。
从应用逻辑的角度,第一步获取乘客的经纬度位置 ,第二步获取所有酒店的经纬度位置,第三步计算乘客到酒店的距离,选出距离最短的前10个。
数据库传统的索引方式,B树索引、哈希索引、位图索引的具体应用场景不同,但是大的方向是相同的,利用B树索引、哈希索引、位图索引对全表里面关键的字段打上标识, 当我们进行查询时,例如我们要查找等级为3星级的酒店,数据库后台通过对rank字段的索引,对指定范围的数据查询输出。
B树索引、哈希索引、位图索引能对明显标志的数据进行快速查找,但是对模糊标志的数据软弱无力,例如我们数据库已经保存了10万篇文档里面,10万篇文档包括有酒店、住宿的字眼,我们要搜索出最合适的前面10篇文档。我们可能输入廉价酒店、短时酒店等。 B树、哈希、位图无法从我们输入的字段正向查找我们希望的字段,只能全表扫描,把包含有酒店、住宿的文档全部找出来。
解决模糊标志的数据查找可以通过全文索引解决。
全文索引通过反向索引的方式 ,根据模糊标志在 文档的曝光率,它会计算出一个权重值。应用端输入不同的模糊值,数据库的文档权重值也会不停的变化,然后响应返回给应用。通过全文索引实现模糊标志的数据不需要全表扫描。
以上说的索引技术都无法解决游客寻找最近的酒店的问题。
因为游客的位置是不固定的,它一直在变化中,1分钟你在A点,5分钟后你在B点,10分钟你开车在10公里以外的C点,假如使用以上索引技术 实现 寻找最近的酒店,数据库将会因为你的位置变化频繁做全盘扫描,疲于奔命。为了解决这个问题,所以产生了空间数据库。
空间数据库是某区域内关于一定空间要素特征的数据集合,是GIS在物理介质上存储的与应用相关的空间数据总和,具有以下技术特点。
1、数据量庞大。
空间数据库面向的是地理学及其相关对象,而在客观世界中它们所涉及的往往都是地球表面信息、地质信息、大气信息等及其复杂的现象和信息,所以描述这些信息的数据容量很大,容量通常达到 GB级。
2、具有高可访问性 。
空间信息系统要求具有强大的信息检索和分析能力, 这是建立在空间数据库基础上的,需要高效访问大量数据。
3、空间数据模型复杂
空间数据库存储的不是单一性质的数据,而是涵盖了几乎所有与地理相关的数据类型,这些数据类型主要可以分为 3 类:
(1)属性数据:与通用数据库基本一致,主要用来描述地学现象的各种属性,一般包括数字、文本、日期类型。
(2)图形图像数据:与通用数据库不同,空间数据库系统中大量的数据借助于图形图像来描述。
(3)空间关系数据:存储拓扑关系的数据,通常与图形数据是合二为一的。
4、属性数据和空间数据联合管理。
5、空间实体的属性数据和空间数据可随时间而发生相应变化。
6、空间数据的数据项长度可变,包含一个或多个对象,需要嵌套记录。
7、一种地物类型对应一个属性数据表文件。多种地物类型共用一个属性数据表文件。
8、具有空间多尺度性和时间多尺度性。
9、应用范围广泛。
Yukon使用
如果基于openGauss的环境搭建,比较费时间,通过docker方式,一行命令引入Yukon环境.
docker run --name Yukon --privileged=true -d -e GS_PASSWORD=Bigdata@123 -v /Yukon/opengauss:/var/lib/opengauss -p 26000:5432 supermap/yukon:1.0-opengauss2.1.0-amd64
复制
验证
技术架构
游客寻找最近的酒店的应用场景涉及到以下技术组件
-
Django是用于构建Web应用程序的最受欢迎的Python框架。 通过提供大量的内置API和子框架(例如GeoDjango),开发人员可以轻松地快速构建原型并按时完成项目。
-
GeoDjango是一个内置应用程序,包含在Django中作为contrib模块。 它实际上是一个完整的框架,也可以与Django分开使用。 它提供了用于构建GIS Web应用程序的实用工具箱。
-
GEOS代表“几何引擎开源”。 它是JTS(Java拓扑套件)的C ++端口。
-
GDAL代表地理空间数据抽象库。 这是一个用于处理栅格和矢量地理空间数据格式的开源库。
-
[PROJ.4]用于制图投影库。 这是一个开源GIS库,可轻松使用空间参考系统和投影。
-
GeoIP是一个库,可帮助用户根据IP地址查找地理信息。
完成上面环境的布署,通过以下命令,建议使用ubuntu做为应用环境 ,换成centos在上面编译gdal库十分麻烦。
apt-get install postgresql-client-common apt-get install python3-gdal apt-get install libpq-dev python-dev apt-get install psycopg2-binary apt-get install python3.8-dev apt-get insstall postgresql-client-common pip install django==2.1.2 pip install psycopg2==2.8.6 pip install psycopg2-binary==2.8.6
复制
遇上的问题
问题一: 高版本djago无法识数据库的驱动
故障描述
self.check_database_version_supported() File "/root/nearbyshops/venvnearbyshops/lib/python3.8/site-packages/django/db/backends/base/base.py", line 207, in check_database_version_supported raise NotSupportedError( django.db.utils.NotSupportedError: PostgreSQL 11 or later is required (found 9.204).
复制
分析及解决
出现9.204错误 ,提示目标端数据库最好是postgresql11之上, openGauss是基于postgreSQL9.2.4的基础上研发的,高版本的django内置的驱动高, 把当前的django卸载,安装一个低版本的django2.1.2就不会报错。
pip uninstall django pip install django==2.1.2
复制
问题二: 权限不够
故障描述
post_migrate_state = executor.migrate( File "/root/nearbyshops/venvnearbyshops/lib/python3.8/site-packages/django/db/migrations/executor.py", line 91, in migrate self.recorder.ensure_schema() File "/root/nearbyshops/venvnearbyshops/lib/python3.8/site-packages/django/db/migrations/recorder.py", line 70, in ensure_schema raise MigrationSchemaMissing("Unable to create the django_migrations table (%s)" % exc) django.db.migrations.exceptions.MigrationSchemaMissing: Unable to create the django_migrations table (permission denied for schema public DETAIL: N/A
复制
分析及解决
用户的权限不够,以omm的身份登陆,并切换连接数据库上,执行以下命令
GRANT ALL PRIVILEGES TO henley2;
复制
问题三: 数据类型规范
故障描述
ERROR: null value in column “last_name” violates not-null constraint
分析及解决
这里我反复与postgresql做了对比,发现有意思是openGauss与postgreSQL12对比,openGauss的数据类型更加严格,如果数据是空值就会报错,而postgreSQL12定义的数据类型是NOT NULL,但是插入不会报错。
我们就把目标表的数据类型设成空值,如下
ALTER TABLE auth_user ALTER COLUMN last_name DROP NOT NULL;
复制
数据库设计
建表结构
CREATE TABLE public.shops_shop ( id integer NOT NULL, name character varying(100) NOT NULL, location public.geometry(Point,4326) NOT NULL, address character varying(100) NOT NULL, city character varying(50) NOT NULL ); geometry是PostGIS的基本空间数据类型,用于表达点线面等空间要素,具体类型涵盖了OGC的简单对象模型,并扩展实现了 SQL/MM ( ISO/IEC 13249-3 SQL Multimedia - Spatial ) Curver相关类型,定义了包含圆弧曲线的几何子对象类型 CircularString、 CompoundCurve、 CurvePolygon、MultiCurve、 MultiSurface。 location就采用了几何的数据类型。 ALTER TABLE public.shops_shop OWNER TO henley2; -- -- Name: shops_shop_id_seq; Type: SEQUENCE; Schema: public; Owner: henley2 -- CREATE SEQUENCE public.shops_shop_id_seq START WITH 1 INCREMENT BY 1 NO MINVAL UE NO MAXVALUE CACHE 1; ALTER TABLE public.shops_shop_id_seq OWNER TO henley2; -- -- Name: shops_shop_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: henley2 -- ALTER SEQUENCE public.shops_shop_id_seq OWNED BY public.shops_shop.id; ALTER TABLE ONLY public.shops_shop ALTER COLUMN id SET DEFAULT nextval('public.shops_shop_id_seq'::regclass); test2=# \d shops_shop; Table "public.shops_shop" Column | Type | Modifiers ----------+------------------------+--------------------------------------------------------- id | integer | not null default nextval('shops_shop_id_seq'::regclass) name | character varying(100) | not null location | geometry(Point,4326) | not null address | character varying(100) | city | character varying(50) | Indexes: "shops_shop_pkey" PRIMARY KEY, btree (id) TABLESPACE pg_default "shops_shop_location_id" gist (location) TABLESPACE pg_default
复制
插入数据
INSERT INTO shops_shop(id,name,location) VALUES(110,'黑山酒店',ST_GeomFromText('POINT(113.66329182598875 23.275423539963565)',4326) ); INSERT INTO shops_shop(id,name,location) VALUES(111,'绿林酒店',ST_GeomFromText('POINT(113.66663922283934 23.28078492200335)',4326) ); INSERT INTO shops_shop(id,name,location) VALUES(112,'黄石酒店',ST_GeomFromText('POINT(113.67316235516356 23.27778888217494)',4326) ); INSERT INTO shops_shop(id,name,location) VALUES(113,'白云酒店',ST_GeomFromText('POINT(113.6839770219116 23.275896611766484)',4326) ); INSERT INTO shops_shop(id,name,location) VALUES(114,'灰色酒店',ST_GeomFromText('POINT(113.6920451066284 23.264069312695632)',4326) ); INSERT INTO shops_shop(id,name,location) VALUES(115,'红晶酒店',ST_GeomFromText('POINT(113.66243351910398 23.261624873317302)',4326) ); INSERT INTO shops_shop(id,name,location) VALUES(116,'蓝色酒店',ST_GeomFromText('POINT(113.63814343426512 23.265015535257135)',4326) ); INSERT INTO shops_shop(id,name,location) VALUES(117,'橙皮酒店',ST_GeomFromText('POINT(113.65771283123777 23.271244666070142)',4326) ); INSERT INTO shops_shop(id,name,location) VALUES(118,'黑山酒店',ST_GeomFromText('POINT(113.65041722271727 23.262965377883276)',4326) ); INSERT INTO shops_shop(id,name,location) VALUES(119,'金炯酒店',ST_GeomFromText('POINT(113.65754116986082 23.261432352288787)',4326) ); INSERT INTO shops_shop(id,name,location) VALUES(120,'银光酒店',ST_GeomFromText('POINT(113.65041722271727 23.262965377883276)',4326) ); INSERT INTO shops_shop(id,name,location) VALUES(121,'华日酒店',ST_GeomFromText('POINT(113.65754116983082 23.261332323288787)',4326) ); INSERT INTO shops_shop(id,name,location) VALUES(122,'小般酒店',ST_GeomFromText('POINT(113.65754333386082 23.261388ewe288787)',4326) ); INSERT INTO shops_shop(id,name,location) VALUES(123,'如家酒店',ST_GeomFromText('POINT(113.65041722271727 23.262965377883276)',4326) ); INSERT INTO shops_shop(id,name,location) VALUES(124,'七天酒店',ST_GeomFromText('POINT(113.65754444986082 23.261388332887871)',4326) );
复制
经纬度查询
直接查询经纬度数据是加密的 test2=# select * from shops_shop limit 10; id | name | location | address | city -----+--------------+----------------------------------------------------+---------+------ 110 | 黑山酒店 | 0101000020E6100000DA148F5F736A5C402BB1382882463740 | | 111 | 绿林酒店 | 0101000020E6100000DA148F37AA6A5C40DC364985E1473740 | | 112 | 黄石酒店 | 0101000020E6100000DA148F17156B5C403E22142C1D473740 | | 113 | 白云酒店 | 0101000020E6100000DA148F47C66B5C40409D0C29A1463740 | | 114 | 灰色酒店 | 0101000020E6100000DA148F774A6C5C40AAE7E50B9A433740 | | 115 | 红晶酒店 | 0101000020E6100000DA148F4F656A5C40CCB702D9F9423740 | | 116 | 蓝色酒店 | 0101000020E6100000DA148F57D7685C4080DCE00ED8433740 | | 117 | 橙皮酒店 | 0101000020E6100000DA148FF7176A5C4057FC594A70453740 | | 118 | 黑山酒店 | 0101000020E6100000DA148F6FA0695C402CFDF1B251433740 | | 119 | 金炯酒店 | 0101000020E6100000DA148F27156A5C405A320B3BED423740 | | (10 rows) 查询经度 test2=# SELECT ST_X(location::geometry) AS longitude FROM shops_shop; longitude ------------------ 113.663291825989 113.666639222839 113.673162355164 113.683977021912 113.692045106628 113.662433519104 113.638143434265 113.657712831238 113.650417222717 113.657541169861 113.650417222717 113.657541169831 113.650417222717 113.657544449861 (14 rows) 查询纬度 test2=# SELECT ST_Y(location::geometry) AS longitude FROM shops_shop; longitude ------------------ 23.2754235399636 23.2807849220033 23.2777888821749 23.2758966117665 23.2640693126956 23.2616248733173 23.2650155352571 23.2712446660701 23.2629653778833 23.2614323522888 23.2629653778833 23.2613323232888 23.2629653778833 23.2613883328879 (14 rows)
复制
应用测试
djago对接数据库配置
nearbyshops/nearbyshops/settings.py 文件内容如下,修改正确名称
DATABASES = { "default": { "ENGINE": "django.contrib.gis.db.backends.postgis", "NAME": "数据库名", "USER": "用户名", "PASSWORD": "密码", "HOST": "oepngauss数据库IP", "PORT": "26000", } }
复制
游客位置代码
nearbyshops/shops/views.py 文件内容如下
from django.views import generic from django.contrib.gis.geos import Point from django.contrib.gis.db.models.functions import Distance from .models import Shop longitude = 113.69470585797117 //游客经度位置 latitude = 23.275502385380918 //游客纬度位置 user_location = Point(longitude, latitude, srid=4326) class Home(generic.ListView): model = Shop context_object_name = "shops" queryset = Shop.objects.annotate( distance=Distance("location", user_location) ).order_by("distance")[0:10] //按距离排序前十名 template_name = "shops/index.html" home = Home.as_view()
复制
模拟游客移动
游客在小镇入口,当前经纬位置 如下,查询到的附近酒店以白云酒店居先
longitude = 113.69470585797117
latitude = 23.275502385380918
当游客驱车长进,经纬位置如下,经过七天酒店,再看界面的 变化,已经贴近为0
longitude = 113.65754444986082
latitude = 23.261388332887872
七天酒店经纬位置
INSERT INTO shops_shop(id,name,location) VALUES(124,'七天酒店',ST_GeomFromText('POINT(113.65754444986082 23.261388332887871)',4326) );
复制
空间数据库察觉到游客的位置在七天酒店,马上更新了数据,不需要应用方有任何变动。
模拟新增酒店
上图是游客感应的酒店位置,保持游客位置不变,小镇附近开张了三个酒店, 香格里拉酒店、赛格特酒店、富力大华酒店,伪造以下三条数据。
INSERT INTO shops_shop(id,name,location) VALUES(125,'香格里拉酒店',ST_GeomFromText('POINT(113.65754444985082 23.261388332887471)',4326) ); INSERT INTO shops_shop(id,name,location) VALUES(126,'赛格特酒店',ST_GeomFromText('POINT(113.65754444986152 23.261388332887541)',4326) ); INSERT INTO shops_shop(id,name,location) VALUES(127,'富力大华酒店',ST_GeomFromText('POINT(113.65754444985082 23.261388332887871)',4326) );
复制
空间数据库察觉到新的酒店加入,自行计算距离马上更新了数据,不需要应用方有任何变动。
源代码体验入口
代码已经上传gitee,见下
文章被以下合辑收录
评论








