切片不够技术来凑
概述
随着数据经度的提升,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…...
ARM 架构 JuiceFS 性能优化:基于 MLPerf 的实践与调优妒
Qt是一个跨平台C图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本笔记将重点介绍QSpinBox数值微调组件的常用方法及灵活应用。…...
如何处理phpMyAdmin提示配置文件读取失败_文件属组与读写权限调整
根本原因是PHP进程无法读取config.inc.php文件,主因是系统级权限问题:Web服务器用户(如www-data)无读取权限,或文件权限为666/660等不安全组合,或SELinux/AppArmor拦截,或符号链接目标权限错误。…...
(手把手实战指南)利用NoneBot2与QQ官方API,从零构建智能群聊机器人
1. 环境准备与项目初始化 想要搭建一个QQ群聊机器人,首先需要准备好开发环境。我推荐使用Python 3.8版本,这是目前NoneBot2最稳定的支持版本。如果你还没有安装Python,可以去官网下载最新版本。 安装好Python后,我们需要创建一个虚…...
Fan-Out晶圆级封装(FOWLP)的三种工艺对比:面朝上、面朝下、RDL-first,哪种更适合你的芯片?
Fan-Out晶圆级封装(FOWLP)的三种工艺对比:面朝上、面朝下、RDL-first,哪种更适合你的芯片? 在半导体封装领域,Fan-Out晶圆级封装(FOWLP)技术正逐渐成为高性能芯片的首选方案。这种技…...
不止于Secure Boot:深入英飞凌TC3XX HSM内核,解锁CAN FD安全通信与调校保护
不止于Secure Boot:深入英飞凌TC3XX HSM内核,解锁CAN FD安全通信与调校保护 在智能网联汽车的高速发展中,车载电子系统的安全防线正面临前所未有的挑战。传统ECU架构中,主处理器既要处理实时控制任务,又要承担安全运算…...
2025届毕业生推荐的AI写作神器实测分析
Ai论文网站排名(开题报告、文献综述、降aigc率、降重综合对比) TOP1. 千笔AI TOP2. aipasspaper TOP3. 清北论文 TOP4. 豆包 TOP5. kimi TOP6. deepseek 当下,人工智能技术已然渗透进学术写作范畴,借助AI辅助来撰写毕业论文…...
别再写CompletableFuture了!Loom时代响应式编程新范式:结构化并发+协程式错误传播(附可运行Demo仓库)
第一章:Loom时代响应式编程的范式跃迁Project Loom 的正式落地标志着 JVM 并发模型的根本性重构——虚拟线程(Virtual Threads)将轻量级协程原生引入 Java 生态。这一变革不再仅是“提升吞吐量”的工程优化,而是直接重塑响应式编程…...
如何关闭Data Guard保护模式_降级为Max Performance以恢复主库读写
必须先确认保护模式和数据库角色,仅MAXIMUM AVAILABILITY或MAXIMUM PROTECTION需降级;执行前须停同步、确保主库OPEN且备库无MRP进程;降级后若仍不可写,需排查STANDBY_FILE_MANAGEMENT、归档目标状态及FORCE LOGGING等隐含依赖。确…...
Notepad--:国产跨平台文本编辑器的终极选择
Notepad--:国产跨平台文本编辑器的终极选择 【免费下载链接】notepad-- 一个支持windows/linux/mac的文本编辑器,目标是做中国人自己的编辑器,来自中国。 项目地址: https://gitcode.com/GitHub_Trending/no/notepad-- 还在为寻找一款…...
写段代码教会你什么是HOOK技术?HOOK技术能干什么?褂
为 HagiCode 添加 GitHub Pages 自动部署支持 本项目早期代号为 PCode,现已正式更名为 HagiCode。本文记录了如何为项目引入自动化静态站点部署能力,让内容发布像喝水一样简单。 背景/引言 在 HagiCode 的开发过程中,我们遇到了一个很现实的问…...
