切片不够技术来凑
概述
随着数据经度的提升,18级的切片有些场景已经不够用了,但是大部分在线的栅格切片最大级别还是18级,如果地图继续放大,有的框架(leaflet会,openlayers和mapboxGL不会)会存在没有底图的情况。为处理这种情况,本文通过node实现在级别大于18级的时候将18级的切片进行裁切,解决没有底图的问题。
实现效果
实现代码
获取切片图片,如果z大于18,则取18级的切片进行切割;否则直接返回。
getTileData(z, x, y) {return new Promise(resolve => {let url = '', extent = [], xy18 = []if(z > 18 ) {extent = this.getTileExtent(z, x, y)const [minX, minY, maxX, maxY] = extent// 获取18级对应的索引xy18 = this.getTileIndexByCoords((minX + maxX) / 2, (minY + maxY) / 2)const [x18, y18] = xy18url = `https://webrd01.is.autonavi.com/appmaptile?style=8&lang=zh_cn&size=1&scale=1&x=${x18}&y=${y18}&z=18`} else {url = `https://webrd01.is.autonavi.com/appmaptile?style=8&lang=zh_cn&size=1&scale=1&x=${x}&y=${y}&z=${z}`}loadImage(url).then(image => {this.ctx.clearRect(0, 0, this.TILE_SIZE, this.TILE_SIZE)if(z > 18) {const [minX, minY, maxX, maxY] = extentconst [x18, y18] = xy18const [minX18, minY18, maxX18, maxY18] = this.getTileExtent(18, x18, y18)const [srcx18, srcy18] = this.toScreen(minX18, maxY18)const [srcxmin, srcymin] = this.toScreen(minX, maxY)const [srcxmax, srcymax] = this.toScreen(maxX, minY)const scrx = Math.round(srcxmin - srcx18), scry = Math.round(srcymin - srcy18)const width = Math.round(srcxmax - srcx18 - scrx), height = Math.round(srcymax - srcy18 - scry)this.ctx.drawImage(image, scrx, scry, width, height, 0, 0, this.TILE_SIZE, this.TILE_SIZE)} else {this.ctx.drawImage(image, 0, 0, this.TILE_SIZE, this.TILE_SIZE)}resolve(this.canvas.toBuffer('image/png'))})})
}
getTileExtent
为根据切片索引获取切片范围,其实现如下:
getResolution(z) {return (this.TILE_ORIGIN * 2) / (Math.pow(2, z) * this.TILE_SIZE)
}
/*** 获取切片范围* @param {number} z * @param {number} x * @param {number} y * @returns {number}*/
getTileExtent(z, x, y) {const res = this.getResolution(z)const minX = x * this.TILE_SIZE * res - this.TILE_ORIGINconst maxX = (x + 1) * this.TILE_SIZE * res - this.TILE_ORIGINconst minY = this.TILE_ORIGIN - (y + 1) * this.TILE_SIZE * resconst maxY = this.TILE_ORIGIN - y * this.TILE_SIZE * resreturn [minX, minY, maxX, maxY]
}
其中:
TILE_SIZE
,切片大小,值为256;TILE_ORIGIN
,切片原点,值为20037508.34;
getTileIndexByCoords
为根据坐标获取切片索引,实现代码如下:
getTileIndexByCoords(x, y) {const res18 = this.getResolution(18) * this.TILE_SIZEreturn [Math.floor((x + this.TILE_ORIGIN) / res18),Math.floor((this.TILE_ORIGIN - y) / res18)]
}
toScreen
实现将地理坐标转换为屏幕坐标。
toScreen(x, y) {const res18 = this.getResolution(18)return [(x + this.TILE_ORIGIN) / res18,(this.TILE_ORIGIN - y) / res18]
}
完整代码如下:
import { createCanvas, loadImage } from 'canvas'
import express from 'express'console.time('app')const app = express()// 自定义跨域中间件
const allowCors = function (req, res, next) {res.header('Access-Control-Allow-Origin', req.headers.origin);res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');res.header('Access-Control-Allow-Headers', 'Content-Type');res.header('Access-Control-Allow-Credentials', 'true');next();
};
app.use(allowCors);// 使用跨域中间件app.use(express.static('public'))class TileUtil {constructor() { this.TILE_ORIGIN = 20037508.34 // 切片原点this.TILE_SIZE = 256; // 切片大小this.canvas = createCanvas(this.TILE_SIZE, this.TILE_SIZE)this.ctx = this.canvas.getContext('2d')}/*** 计算分辨率* @param {number} z - 缩放级别* @returns {number}*/getResolution(z) {return (this.TILE_ORIGIN * 2) / (Math.pow(2, z) * this.TILE_SIZE)}/*** 获取切片范围* @param {number} z * @param {number} x * @param {number} y * @returns {number}*/getTileExtent(z, x, y) {const res = this.getResolution(z)const minX = x * this.TILE_SIZE * res - this.TILE_ORIGINconst maxX = (x + 1) * this.TILE_SIZE * res - this.TILE_ORIGINconst minY = this.TILE_ORIGIN - (y + 1) * this.TILE_SIZE * resconst maxY = this.TILE_ORIGIN - y * this.TILE_SIZE * resreturn [minX, minY, maxX, maxY]}/*** 将地理坐标转换为屏幕坐标* @param {number} x * @param {number} y * @returns {number}*/toScreen(x, y) {const res18 = this.getResolution(18)return [(x + this.TILE_ORIGIN) / res18,(this.TILE_ORIGIN - y) / res18]}/*** 获取切片图片,如果z大于18,则取18级的切片进行切割;否则直接返回* @param {number} z * @param {number} x * @param {number} y * @returns {Buffer<Image>}*/getTileData(z, x, y) {return new Promise(resolve => {let url = '', extent = [], xy18 = []if(z > 18 ) {extent = this.getTileExtent(z, x, y)const [minX, minY, maxX, maxY] = extent// 获取18级对应的索引xy18 = this.getTileIndexByCoords((minX + maxX) / 2, (minY + maxY) / 2)const [x18, y18] = xy18url = `https://webrd01.is.autonavi.com/appmaptile?style=8&lang=zh_cn&size=1&scale=1&x=${x18}&y=${y18}&z=18`} else {url = `https://webrd01.is.autonavi.com/appmaptile?style=8&lang=zh_cn&size=1&scale=1&x=${x}&y=${y}&z=${z}`}loadImage(url).then(image => {this.ctx.clearRect(0, 0, this.TILE_SIZE, this.TILE_SIZE)if(z > 18) {const [minX, minY, maxX, maxY] = extentconst [x18, y18] = xy18const [minX18, minY18, maxX18, maxY18] = this.getTileExtent(18, x18, y18)const [srcx18, srcy18] = this.toScreen(minX18, maxY18)const [srcxmin, srcymin] = this.toScreen(minX, maxY)const [srcxmax, srcymax] = this.toScreen(maxX, minY)const scrx = Math.round(srcxmin - srcx18), scry = Math.round(srcymin - srcy18)const width = Math.round(srcxmax - srcx18 - scrx), height = Math.round(srcymax - srcy18 - scry)this.ctx.drawImage(image, scrx, scry, width, height, 0, 0, this.TILE_SIZE, this.TILE_SIZE)} else {this.ctx.drawImage(image, 0, 0, this.TILE_SIZE, this.TILE_SIZE)}resolve(this.canvas.toBuffer('image/png'))})})}/*** 根据坐标获取切片索引* @param {number} x * @param {number} y * @returns {[<number>, <number>]}*/getTileIndexByCoords(x, y) {const res18 = this.getResolution(18) * this.TILE_SIZEreturn [Math.floor((x + this.TILE_ORIGIN) / res18),Math.floor((this.TILE_ORIGIN - y) / res18)]}
}const util = new TileUtil()app.get('/tile/:z/:x/:y', (req, res) => {const { z, x, y } = req.paramsutil.getTileData(Number(z), Number(x), Number(y)).then(data => {res.setHeader('Expires', new Date(Date.now() + 30 * 1000).toUTCString())res.writeHead(200, {"Content-Type": "image/png",});res.end(data);})
})app.get('/tile-bbox/:z/:x/:y', (req, res) => {const { z, x, y } = req.paramsconst TILE_SIZE = 256;const canvas = createCanvas(TILE_SIZE, TILE_SIZE)const ctx = canvas.getContext('2d')ctx.fillStyle = '#f00'ctx.strokeStyle = '#f00'ctx.lineWidth = 2ctx.textAlign = "center";ctx.textBaseline = "middle"ctx.font = "bold 18px 微软雅黑";ctx.strokeRect(0, 0, TILE_SIZE, TILE_SIZE)ctx.fillText(`${z}-${x}-${y}`, TILE_SIZE / 2, TILE_SIZE / 2)res.setHeader('Expires', new Date(Date.now() + 30 * 1000).toUTCString())res.writeHead(200, {"Content-Type": "image/png",});res.end(canvas.toBuffer('image/png'));
})app.listen(18089, () => {console.timeEnd('app')console.log('express server running at http://127.0.0.1:18089')
})
相关文章:

切片不够技术来凑
概述 随着数据经度的提升,18级的切片有些场景已经不够用了,但是大部分在线的栅格切片最大级别还是18级,如果地图继续放大,有的框架(leaflet会,openlayers和mapboxGL不会)会存在没有底图的情况。…...

特约|数码转型思考:Web3.0与银行
日前,欧科云链研究院发布重磅报告,引发银行界及金融监管机构广泛关注。通过拆解全球70余家银行的加密布局,报告认为,随着全球采用率的提升与相关技术的成熟,加密资产已成为银行业不容忽视也不能错过的创新领域。 作为…...
MySQL知识详细汇总
存储引擎 MyISAM 不支持事务,不支持外键,支持全文索引,查询、插入效率高InnoDB 支持事务(事务的特性) 原子性:一个事务中所有的操作,要么全部完成,要么全部不完成,不会在…...

【驱动开发】LED灯的亮灭——通过字符设备驱动的分步实现编写LED驱动,实现设备文件和设备的绑定
头文件: #ifndef __HEAD_H__ #define __HEAD_H__typedef struct {unsigned int MODER;unsigned int OTYPER;unsigned int OSPEEDR;unsigned int PUPDR;unsigned int IDR;unsigned int ODR; }gpio_t;//LED灯的寄存器地址 #define LED1_ADDR 0X50006000 #define L…...
华为OD 最小数字(100分)【java】A卷+B卷
华为OD统一考试A卷+B卷 新题库说明 你收到的链接上面会标注A卷还是B卷。目前大部分收到的都是B卷。 B卷对应20022部分考题以及新出的题目,A卷对应的是新出的题目。 我将持续更新最新题目 获取更多免费题目可前往夸克网盘下载,请点击以下链接进入: 我用夸克网盘分享了「华为O…...

大模型、实时需求推动湖仓平台走向开放
大模型、实时需求高涨 AGI 时代,以 ChatGPT、Midjourney 等为代表的大模型迅速应用加速了 AI 普及,越来越多的企业选择搭建自己的 AI 基础设施,训练行业大模型。 另一方面,企业为了在瞬息万变的市场环境中更快的做出商业决策&…...

Linux搭建文件服务器
搭建简单文件服务器 基于centos7.9搭建http文件服务器基于centos7.9搭建nginx文件服务器基于ubuntu2204搭建http文件服务器 IP环境192.168.200.100VMware17 基于centos7.9搭建http文件服务器 安装httpd [rootlocalhost ~]# yum install -y httpd关闭防火墙以及selinux [roo…...

跨境商城源码可以支持多种支付方式吗?
跨境商城源码是一种用于建立跨国界电商平台的程序代码。随着全球电商的繁荣发展,越来越多的商家开始寻找一种既安全可靠,又能满足用户需求的支付方式。那么,跨境商城源码是否能够支持多种支付方式呢?让我们深入探讨一下。 1. 支付宝支付 支付…...

机器学习中的核方法
一、说明 线性模型很棒,因为它们易于理解且易于优化。他们受苦是因为他们只能学习非常简单的决策边界。神经网络可以学习更复杂的决策边界,但失去了线性模型良好的凸性特性。 使线性模型表现出非线性的一种方法是转换输入。例如,通过添加特征…...

搜索问答技术学习:基于知识图谱+基于搜索和机器阅读理解(MRC)
目录 一、问答系统应用分析 二、搜索问答技术与系统 (一)需求和信息分析 问答需求类型 多样的数据源 文本组织形态 (二)主要问答技术介绍 发展和成熟度分析 重点问答技术基础:KBQA和DeepQA KBQA(…...

LeetCode2409——统计共同度过的日子数
博主的解法过于冗长,是一直对着不同的案例debug修改出来的,不建议学习。虽然提交成功了,但是自己最后都不知道写的是啥了哈哈哈。 package keepcoding.leetcode.leetcode2409; /*Alice 和 Bob 计划分别去罗马开会。给你四个字符串 arriveA…...

【MyBatisPlus】快速入门、常用注解、常用配置
🐌个人主页: 🐌 叶落闲庭 💨我的专栏:💨 c语言 数据结构 javaEE 操作系统 Redis 石可破也,而不可夺坚;丹可磨也,而不可夺赤。 MyBatisPlus 一、快速入门1.1 引入MyBatisP…...
【USRP】通信之:光通信
光通信: 光通信是使用光信号(通常是红外或可见光信号)在光纤或空气中传输信息的技术。由于光信号的特性,光通信具有非常高的数据传输率和长距离传输能力。以下是光通信的一些关键组件和概念: 光纤: 是由非常纯净的玻璃…...

bpf对内核的观测
目录 1 bpftrace常用命令1.1 列出bpftrace 相关命令的list1. 2bpftrace -e 是执行1.3 查看参数 -lv 2 bpftrace 可以用到的变量3 高级3.1 内置函数3.2 文件系统3.3 内核内存 栈3.4 Malloc 调用 统计3.5 系统调用 brk 的 统计3.6 脚本调用 4 应用5 怎么串联起来呢 bpftrace 总的…...
Tiktok shop api 调试
记录一下调试Tiktok shop api 踩坑记录。 主要是在按官网api上规则和加密生成sign时候一直通不过的问题: 官网地址:https://partner.tiktokshop.com/doc/page/63fd743e715d622a338c4eab 直接贴代码了 import lombok.extern.slf4j.Slf4j;import javax.cr…...
QFSFileEngine::open: No file name specified解决方案
问题 使用QFile类进行文件操作时,报错QFSFileEngine::open: No file name specified。 原因 QFile::open: No file name specified是Qt中的一个错误消息,提示没有指定文件名导致文件无法打开。这个错误通常出现在使用QFile::open()函数时没有提供有效…...

Flappy bird项目
一、功能分析 1、小鸟自动向右滑行 2、按下空格小鸟上升,不按下落 3、显示小鸟需要穿过的管道 4、管道自动左移和创建 5、小鸟和管道碰撞,游戏结束 6、技术 7、 项目框图 8、Ncurses 1)创建窗口界面,移动光标,产…...

高校教务系统登录页面JS分析——西安科技大学
高校教务系统密码加密逻辑及JS逆向 本文将介绍高校教务系统的密码加密逻辑以及使用JavaScript进行逆向分析的过程。通过本文,你将了解到密码加密的基本概念、常用加密算法以及如何通过逆向分析来破解密码。 本文仅供交流学习,勿用于非法用途。 一、密码加…...
Mysql 事务的实现原理
Mysql 里面的事务,满足 ACID 特性,所以Mysql 的事务实现原理,就是InnoDB 是如何保证 ACID 特性的。 ACID A 表示 Atomic 原子性,也就是需要保证多个 DML 操作是原子的,要么都成功,要么都失败。那么…...

使用vscode搭建虚拟机
首先vscode插件安装 名称: Remote - SSH ID: ms-vscode-remote.remote-ssh 说明: Open any folder on a remote machine using SSH and take advantage of VS Codes full feature set. 版本: 0.51.0 VS Marketplace 链接: https://marketplace.visualstudio.com/items?it…...

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?
编辑:陈萍萍的公主一点人工一点智能 未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战,在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
postgresql|数据库|只读用户的创建和删除(备忘)
CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...

DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...

图表类系列各种样式PPT模版分享
图标图表系列PPT模版,柱状图PPT模版,线状图PPT模版,折线图PPT模版,饼状图PPT模版,雷达图PPT模版,树状图PPT模版 图表类系列各种样式PPT模版分享:图表系列PPT模板https://pan.quark.cn/s/20d40aa…...

使用 SymPy 进行向量和矩阵的高级操作
在科学计算和工程领域,向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能,能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作,并通过具体…...

华硕a豆14 Air香氛版,美学与科技的馨香融合
在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...

HDFS分布式存储 zookeeper
hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架,允许使用简单的变成模型跨计算机对大型集群进行分布式处理(1.海量的数据存储 2.海量数据的计算)Hadoop核心组件 hdfs(分布式文件存储系统)&a…...

初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...