动态矢量瓦片缓存库方案

目录

前言

二、实现步骤

1.将数据写入postgis数据库

 2.将矢量瓦片数据写入缓存库

3.瓦片接口实现

4.瓦片局部更新接口实现

总结


前言

       矢量瓦片作为webgis目前最优秀的数据格式,其主要特点就是解决了大批量数据在前端渲染时出现加载缓慢、卡顿的问题,能够环境前端设备的计算压力。动态矢量瓦片技术,解决了矢量存储在数据库中的实时动态更新,不再需要使用离线工具对矢量进行本地切片发布的问题。但是动态矢量瓦片技术的缺陷也很大,就是因为其运行逻辑是通过对数据库矢量实时切片,那么当用户访问并发数过多的时候,pg库就会超负荷运行,会出现访问超时的情况。为解决这一问题,搭建矢量瓦片缓存库就非常重要。


一、缓存库的意义

        为解决多用户访问时pg库切片压力过大的问题,首先对数据库所有数据进行切片,然后将X,Y,Z和瓦片信息储存在缓存库中。当用户访问矢量瓦片接口时,优先判断缓存库内有无对应的瓦片数据,如果没有,则调用pg函数实时切片,切片完成后再缓存库插入数据,如果缓存库有数据,则直接返回缓存库储存的瓦片数据。如果需要对数据库的矢量进行增删改查操作,则计算更改矢量对应的瓦片范围,对缓存库做对应的局部更新即可。

二、实现步骤

1.将数据写入postgis数据库

对各大空间数据库读写最强工具,非FME莫属,直接写模块即可,需要注意一点就是如果。pg的表是MultiPolygon,那么我们需要加入aggregator把要素变为聚合体格式写入,还有就是坐标系要和表一致。

 

 2.将矢量瓦片数据写入缓存库

        将矢量数据写入一个临时的postgis数据表,然后用fme生成对应层级的瓦片范围,最后用python调用postgis函数对数据进行切片,最后过滤一下空白瓦片,最后将数据写入缓存库。

  获得了各类层级的二进制矢量瓦片数据

3.瓦片接口实现

后端框架采用python的geodjango,首先造一个x,y,z转换为wg84坐标范围的函数

import math
def xyz2lonlat(x,y,z):
    n = math.pow(2, z)
    lon_deg = (x / n) * 360.0 - 180.0
    lat_rad = math.atan(math.sinh(math.pi * (1 - (2 * y) / n)));
    lat_deg = (180 * lat_rad) / math.pi
    return [lon_deg, lat_deg]

同时造一个mvt生成器,并实现空间库和缓存库信息判定,有则直接调用缓存库数据,不调用pg函数切片,无则调用pg函数切片,切片完成后更新缓存库并同时返回瓦片数据。

def make_mvt(model,temp_model,x,y,z):
    """temp_model为矢量瓦片缓存库模型类
       model为pg矢量库模型要素
       x,y,z为前端请求参数
    """
    temp_mvt = temp_model.objects.filter(x=x,y=y,z=z)
    if len(temp_mvt) == 1:
        return HttpResponse(temp_mvt[0].byte, content_type="application/x-protobuf")
    if len(temp_mvt) > 1:
        for i in range(0, len(temp_mvt) - 1):
            temp_mvt[i].delete()
        return HttpResponse(temp_mvt[len(temp_mvt) - 1].byte, content_type="application/x-protobuf")
    tablename = model._meta.db_table
    boundbox_min = xyz2lonlat(x, y, z)
    boundbox_max = xyz2lonlat(x + 1, y + 1, z)
    sql = """SELECT
    ST_AsMVT ( P,'polygon', 4096, 'geom' ) AS "mvt" FROM	(SELECT 
      ST_AsMVTGeom (ST_Transform (st_simplify(geom,0.0), 3857 ),	ST_Transform (ST_MakeEnvelope
      ( %s,%s, %s,%s, 4326 ),3857),
      4096,	64,TRUE ) geom FROM "%s"  ) AS P""" % (
    boundbox_min[0], boundbox_min[1], boundbox_max[0], boundbox_max[1], tablename)
    cursor = connection.cursor()
    cursor.execute(sql)
    tile = bytes(cursor.fetchone()[0])
    temp_model.objects.create(
        x=x,
        y=y,
        z=z,
        byte=tile,
    )
    if not len(tile):
        return False
    else:
        return tile

视图类

#视图类
class ZJGG_mvt_ViewSet(APIView):
    def get(self,request,z, x, y):
        tile=make_mvt(ZJGG,mvt_temp,x,y,z)
        if tile:
            return HttpResponse(tile, content_type="application/x-protobuf")
        else:
            return Response(status=status.HTTP_404_NOT_FOUND)

然后用postman调试一下接口,瓦片请求成功

4.瓦片局部更新接口实现

        这一步的主要内容是为了前端需要对矢量数据增删改查的时候,同时删除缓存库的对应瓦片,用户在下一次访问的时候,通过上一步的mvt生成器完成新瓦片数据的更新。

首先造一个4326到3857坐标系的转换工具

import math

def lonlat2mercator(lon, lat):
    semimajor_axis = 6378137.0
    x = semimajor_axis * math.radians(lon)
    y = semimajor_axis * math.log(math.tan((math.pi / 4) + (math.radians(lat) / 2)))
    return x, y

def mercator2lonlat(x, y):
    semimajor_axis = 6378137.0
    lon = math.degrees(x / semimajor_axis)
    lat = math.degrees(2 * math.atan(math.exp(y / semimajor_axis)) - math.pi / 2)
    return lon, lat

def epsg4326_to_epsg3857(lon, lat):
    x, y = lonlat2mercator(lon, lat)
    r_major = 6378137.0
    x = r_major * math.radians(lon)
    scale = x / lon
    y = 180.0 / math.pi * math.log(math.tan(math.pi / 4.0 + lat * (math.pi / 180.0) / 2.0)) * scale
    return x, y

def epsg3857_to_epsg4326(x, y):
    r_major = 6378137.0
    lon = x / r_major * 180.0 / math.pi
    lat = math.atan(math.exp(y / r_major)) * 360.0 / math.pi - 90.0
    return lon, lat


造一个瓦片计算器,传入geojson的extend返回瓦片的x,y,z信息。

import math

HEMI_MAP_WIDTH = math.pi * float(6378137)
PRECISION = 6

def generate(zoomLevel, tileSize, rows, cbeg, cend):
    # Calculate x-direction tile origins
    cols = [(c, round(c * tileSize - HEMI_MAP_WIDTH, PRECISION)) for c in range(cbeg, cend + 1)]
    cols = [(cols[i][0], cols[i][1], cols[i + 1][1]) for i in range(len(cols) - 1)]
    tile_json=[]
    # Generate and output tile features.
    for row, ymin, ymax in rows:
        for column, xmin, xmax in cols:
            tile_json.append({
                "Z": zoomLevel,
                "X": column,
                "Y": row,
            })
    return tile_json

def TileGenerate(xmin,ymin,xmax,ymax):
    west = float(xmin)
    east = float(xmax)
    south = float(ymin)
    north = float(ymax)
    tile=[]
    for i in range(6, 17):
        zoomLevel = i
        if zoomLevel < 0:
            zoomLevel = 0
        numColumns = int(math.pow(2, zoomLevel))
        tileSize = 2.0 * HEMI_MAP_WIDTH / numColumns
        rbeg = int(math.floor((HEMI_MAP_WIDTH - north) / tileSize))
        rend = int(math.ceil((HEMI_MAP_WIDTH - south) / tileSize))
        rows = [(r, round(HEMI_MAP_WIDTH - r * tileSize, PRECISION)) for r in range(rbeg, rend + 1)]
        rows = [(rows[i][0], rows[i + 1][1], rows[i][1]) for i in range(len(rows) - 1)]
        cbeg = int(math.floor((HEMI_MAP_WIDTH + west) / tileSize))
        cend = int(math.ceil((HEMI_MAP_WIDTH + east) / tileSize))
        if cbeg < cend:
            tile_json=generate(zoomLevel, tileSize, rows, cbeg, cend)
        else:
            tile_json=generate(zoomLevel, tileSize, rows, cbeg, numColumns)
            tile_json1=generate(zoomLevel, tileSize, rows, 0, cend)
            tile_json.extend(tile_json1)
        tile.extend(tile_json)
    return tile

mvt局部更新函数

def del_mvt(model,temp_model,sm):
    """model为空间数据存储模型类
       temp_model为缓存库模型类
       sm为空间数据库查询结果
       实现缓存库和空间库数据的局部删除
    """
    data = make_geojson(model, sm)
    data = json.loads(data)
    xmin, ymin, xmax, ymax = bound(data)
    xmin, ymin = epsg4326_to_epsg3857(xmin, ymin)
    xmax, ymax = epsg4326_to_epsg3857(xmax, ymax)
    tilelist = TileGenerate(xmin, ymin, xmax, ymax)
    for i in tilelist:
        DEL = temp_model.objects.filter(x=i["X"], y=i["Y"], z=i["Z"])
        DEL.delete()
    sm.delete()

视图函数

class ZJGG_mvt_del_ViewSet(APIView):
    def post(self,request):
        id = request.data.get('id')
        sm=ZJGG.objects.filter(pk=id)
        del_mvt(ZJGG,mvt_temp,sm)
        return Response(status=status.HTTP_200_OK)

最后用postman测试接口,传入一个id,测试空间信息表和缓存表的对应内容是否都删除。

提交前

 提交后

 

 可以看到对应的空间库id为338的数据已经删除,同时缓存库中的对应的4条瓦片信息也被删。


总结

        这项技术的前景是非常可观的,现阶段各类webgis平台对大批量地理空间数据的展现方式,几乎都为静态矢量瓦片和geojson配合的方式实现。但是这种方式在面对较大体量需要全局展示且需要动态更新的数据的时候,就显得捉襟见肘。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/633.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode 112. 路径总和

LeetCode 112. 路径总和 给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径&#xff0c;这条路径上所有节点值相加等于目标和 targetSum 。如果存在&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 叶…

Python笔记 -- 文件和异常

文章目录1、文件1.1、with关键字1.2、逐行读取1.3、写入模式1.4、多行写入2、异常2.1、try-except-else2.2、pass1、文件 1.1、with关键字 with关键字用于自动管理资源 使用with可以让python在合适的时候释放资源 python会将文本解读为字符串 # -*- encoding:utf-8 -*- # 如…

Linux操作系统基础的常用命令

1&#xff0c;Linux简介Linux是一种自由和开放源码的操作系统&#xff0c;存在着许多不同的Linux版本&#xff0c;但它们都使用了Linux内核。Linux可安装在各种计算机硬件设备中&#xff0c;比如手机、平板电脑、路由器、台式计算机。1.1Linux介绍Linux出现于1991年&#xff0c…

操作技巧 | 在Revit中借用CAD填充图案的方法

在建模过程中&#xff0c;有时需要达到多种填充效果&#xff0c;而CAD中大量的二维填充图案&#xff0c;便是最直接的资源之一。 使用 填充图案之前 使用 填充图案之后 其中要用到主要命令便是对表面填充图案的添加与编辑 简单效果 如下 模型填充与绘图填充 区别 模型填…

Java for循环嵌套for循环,你需要懂的代码性能优化技巧

前言 本篇分析的技巧点其实是比较常见的&#xff0c;但是最近的几次的代码评审还是发现有不少兄弟没注意到。 所以还是想拿出来说下。 正文 是个什么场景呢&#xff1f; 就是 for循环 里面还有 for循环&#xff0c; 然后做一些数据匹配、处理 这种场景。 我们结合实例代码来…

SpringBoot+WebSocket实时监控异常

# 写在前面此异常非彼异常&#xff0c;标题所说的异常是业务上的异常。最近做了一个需求&#xff0c;消防的设备巡检&#xff0c;如果巡检发现异常&#xff0c;通过手机端提交&#xff0c;后台的实时监控页面实时获取到该设备的信息及位置&#xff0c;然后安排员工去处理。因为…

Java实现调用第三方相关接口(附详细思路)

目录1.0.简单版2.0.升级版2-1.call.timeout()怎么传入新的超时值2-2.timeout(10, TimeUnit.SECONDS)两个参数的意思&#xff0c;具体含义3.0.进阶版3-1.java.net.SocketTimeoutException: 超时如何解决4.0.终极版1.0.简单版 以下是一个使用 Java 实际请求“第三方”的简单示例代…

一眼看破五花八门的链表结构

文章目录&#x1f4d5;一&#xff1a;五花八门的链表结构&#x1f4d6;链表与数组的简单对比&#x1f4d6;单链表&#x1f4d6;循环链表&#x1f4d6;双向链表&#x1f4d5;二&#xff1a;链表VS数组性能大比拼&#x1f47f;最后说一句&#x1f431;‍&#x1f409;作者简介&am…

数据挖掘(2.1)--数据预处理

一、基础知识 1.数据的基本概念 1.1基础知识 数据是数据对象(Data Objects)及其属性(Attributes)的集合。 数据对象(一条记录、一个实体、一个案例、一个样本等)是对一个事物或者物理对象的描述。 数据对象的属性则是这个对象的性质或特征&#xff0c;例如一个人的肤色、眼球…

GPT-4 性能炸天:10 秒做出一个网站,在考试中击败 90% 人类

一、GPT-4&#xff0c;吊打ChatGPT&#xff01; 一觉醒来&#xff0c;万众期待的 GPT-4&#xff0c;它来了&#xff01; OpenAI老板Sam Altman直接开门见山地介绍道&#xff1a;这是我们迄今为止功能最强大的模型&#xff01; 二、GPT-4&#xff0c;新功能一览 究竟有多强&am…

Python人脸识别

#头文件&#xff1a;import cv2 as cvimport numpy as npimport osfrom PIL import Imageimport xlsxwriterimport psutilimport time#人脸录入def get_image_name(name):name_map {f.split(.)[1]:int(f.split(.)[0]) for f in os.listdir("./picture")}if not name…

Java的jar包打包成exe应用

将springboot项目使用maven打出的jar包&#xff0c;打成windows平台下exe应用程序包&#xff08;自带jre环境&#xff09;。 工具&#xff1a;1、exe4j 2、Inno Setup 工具放到网盘&#xff0c;链接&#xff1a;https://pan.baidu.com/s/1ZHX8P7u-7GBxaC6uaIC8Ag 提取码&#x…

SpringBoot-核心技术篇

技术掌握导图 六个大标题↓ 配置文件web开发数据访问单元测试指标指控原理解析 配置文件 1.文件类型 1.1、properties 同以前的properties用法 1.2、yaml 1.2.1、简介 YAML是 “YAML Aint Markup Language”&#xff08;YAML不是一种标记语言&#xff09;的递归缩写。在…

76.qt qml-QianWindow开源炫酷界面框架(支持白色暗黑渐变自定义控件均以适配)

界面介绍界面支持: 透明 白色 黑色 渐变 单色 静态图 动态图侧边栏支持:抽屉、带折叠、多模式场景控件已集成: 暗黑风格 高亮风格、并附带个人自定义控件及开源demo白色场景如下所示:单色暗黑风格如下所示:用户自定义皮肤如下所示:皮肤预览如下所示:b站入口:https://www.bilibi…

2023年跨境电商行业研究报告

第一章 行业发展 1.1 概况 跨境电商&#xff08;Cross-border e-commerce&#xff09;是指通过互联网销售商品或服务&#xff0c;跨越国家或地区边界&#xff0c;实现国际贸易的一种商业模式。跨境电商的兴起得益于全球化和数字化的趋势&#xff0c;以及互联网的普及和支付、…

Linux常用命令——基于Ubuntu22.04

本文介绍了一些Linux的常用命令。为了便于快速检索命令位置&#xff0c;文章二级标题都以“命令&#xff1a;命令的作用”展示&#xff0c;有些命令会先介绍命令的几个常用参数&#xff0c;然后结合具体的操作展示命令的使用。为了便于记忆&#xff0c;也会提到命令是由哪些短语…

【链表OJ题(五)】合并两个有序链表

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;数据结构 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 文章目录链表OJ题(五)1. 合并…

elasticsearch全解 (待续)

目录elasticsearchELK技术栈Lucene与Elasticsearch关系为什么不是其他搜索技术&#xff1f;Elasticsearch核心概念Cluster&#xff1a;集群Node&#xff1a;节点Shard&#xff1a;分片Replia&#xff1a;副本全文检索倒排索引正向和倒排es的一些概念文档和字段索引和映射mysql与…

原来CSS 也可以节流啊

Ⅰ、前言 「节流」 是为了减少请求的触发频率&#xff0c;不让用户点的太快&#xff0c;达到节省资源的目的 &#xff1b;通常 我们采用 JS 的 定时器 setTimeout &#xff0c;来控制点击多少秒才能在触发&#xff1b;其实 通过 CSS 也能达到 「节流」 的目的&#xff0c;下面…

面试官:MQ的好处到底有哪些?

&#x1f497;推荐阅读文章&#x1f497; &#x1f338;JavaSE系列&#x1f338;&#x1f449;1️⃣《JavaSE系列教程》&#x1f33a;MySQL系列&#x1f33a;&#x1f449;2️⃣《MySQL系列教程》&#x1f340;JavaWeb系列&#x1f340;&#x1f449;3️⃣《JavaWeb系列教程》…