暂无图片
暂无图片
23
暂无图片
暂无图片
6
暂无图片

【第五届我和openGauss的故事】干货输出【通过openGauss游客寻找最近的酒店】

原创 大数据模型 2022-11-11
1416

前文

从本文可以认识实践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 扩展的模块包括:

  1. postgis:与 openGauss/PostgreSQL 适配的 PostGIS 矢量模块;
  2. postgis_raster:与 openGauss/PostgreSQL 适配的 PostGIS 栅格模块;
  3. postgis_sfcgal:与 openGauss/PostgreSQL 适配的 PostGIS 三维算法相关模块;
  4. yukon_geomodel:Yukon自有的三维模型数据模块;
  5. yukon_geogridcoder:Yukon自有的空间网格编码模块。

模块之间的依赖关系如图:

1.png

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

复制

验证

2.png

技术架构

游客寻找最近的酒店的应用场景涉及到以下技术组件

  • 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

3.png

当游客驱车长进,经纬位置如下,经过七天酒店,再看界面的 变化,已经贴近为0

longitude = 113.65754444986082
latitude = 23.261388332887872

七天酒店经纬位置

INSERT INTO shops_shop(id,name,location)    VALUES(124,'七天酒店',ST_GeomFromText('POINT(113.65754444986082     23.261388332887871)',4326) );
复制

4.png

空间数据库察觉到游客的位置在七天酒店,马上更新了数据,不需要应用方有任何变动。

模拟新增酒店

5.png

上图是游客感应的酒店位置,保持游客位置不变,小镇附近开张了三个酒店, 香格里拉酒店、赛格特酒店、富力大华酒店,伪造以下三条数据。

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) );
复制

空间数据库察觉到新的酒店加入,自行计算距离马上更新了数据,不需要应用方有任何变动。

6.png

源代码体验入口

代码已经上传gitee,见下

nearbyshops源代码

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

文章被以下合辑收录

评论

庸人
暂无图片
2年前
评论
暂无图片 0
牛人
2年前
暂无图片 点赞
评论
牛人
2年前
暂无图片 点赞
评论
手机用户2327
暂无图片
2年前
评论
暂无图片 0
厉害👍🏻
2年前
暂无图片 点赞
评论
李宏达
暂无图片
2年前
评论
暂无图片 0
2年前
暂无图片 点赞
评论
bicewow
暂无图片
2年前
评论
暂无图片 0
2年前
暂无图片 点赞
评论
严少安
暂无图片 暂无图片
2年前
评论
暂无图片 0
2年前
暂无图片 点赞
评论
暂无图片
获得了395次点赞
暂无图片
内容获得140次评论
暂无图片
获得了94次收藏
TA的专栏
大数据模型
收录66篇内容
oceanbase
收录10篇内容
数据库文章
收录8篇内容
目录
  • 前文
  • Yukon背景
  • Yukon与OpenGauss的关系
  • Yukon解决什么问题
  • Yukon使用
  • 技术架构
  • 遇上的问题
    • 问题一: 高版本djago无法识数据库的驱动
    • 问题二: 权限不够
    • 问题三: 数据类型规范
  • 数据库设计
    • 建表结构
    • 插入数据
    • 经纬度查询
  • 应用测试
    • djago对接数据库配置
    • 游客位置代码
    • 模拟游客移动
    • 模拟新增酒店
  • 源代码体验入口