切片不够技术来凑
概述
随着数据经度的提升,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…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
DockerHub与私有镜像仓库在容器化中的应用与管理
哈喽,大家好,我是左手python! Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库,用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...

《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...

基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...
Element Plus 表单(el-form)中关于正整数输入的校验规则
目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入(联动)2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...
《C++ 模板》
目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板,就像一个模具,里面可以将不同类型的材料做成一个形状,其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式:templa…...

人机融合智能 | “人智交互”跨学科新领域
本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...
苹果AI眼镜:从“工具”到“社交姿态”的范式革命——重新定义AI交互入口的未来机会
在2025年的AI硬件浪潮中,苹果AI眼镜(Apple Glasses)正在引发一场关于“人机交互形态”的深度思考。它并非简单地替代AirPods或Apple Watch,而是开辟了一个全新的、日常可接受的AI入口。其核心价值不在于功能的堆叠,而在于如何通过形态设计打破社交壁垒,成为用户“全天佩戴…...