切片不够技术来凑
概述
随着数据经度的提升,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…...
EoH Platform:嵌入式多协议物联网边缘中间件
1. EoH Platform 概述:面向工业物联网的多协议嵌入式中间件平台 EoH Platform(Edge of Hub Platform)并非传统意义上的单功能驱动库或轻量级协议栈,而是一个专为资源受限嵌入式设备设计的 可裁剪、可扩展、协议无关的物联网边缘中…...
AI原生研发已进入临界点:2026年前必须掌握的7项核心能力清单(附Gartner实测数据)
第一章:SITS2026专家:AI原生研发的未来趋势 2026奇点智能技术大会(https://ml-summit.org) AI原生研发已不再是一种概念性演进,而是正在重塑软件生命周期的核心范式。SITS2026大会上多位工业界与学术界专家指出:下一代研发基础设…...
OpenClaw硬件要求解析:Qwen3.5-9B流畅运行配置
OpenClaw硬件要求解析:Qwen3.5-9B流畅运行配置 1. 为什么需要关注硬件配置? 去年我在尝试用OpenClaw自动化处理公司周报时,第一次深刻体会到硬件配置的重要性。当时我的MacBook Air(8GB内存)在运行Qwen3.5-9B模型时&…...
IPD实战指南:如何运用SPAN工具精准定位高潜力市场并优化产品战略布局
1. SPAN工具:市场定位的"战略导航仪" 第一次接触SPAN工具是在2015年,当时我们团队正在为智能家居产品线寻找新的市场突破口。面对十几个潜在细分市场,市场部提交的200页分析报告让所有人陷入数据沼泽。直到产品总监在白板上画出那个…...
如何在WPF中捕获窗口外的事件
捕获窗口消息 关于窗口消息,可以参考下面的文章 https://www.cnblogs.com/zhaotianff/p/11285312.html https://www.cnblogs.com/zhaotianff/p/11297319.html 在WPF中,对于操作系统层面的原始输入 / 窗口消息,如 WM_LBUTTONDOWN、WM_MOUSE…...
AI原生研发成本黑洞诊断手册(附可落地的TCO/TTV双轨评估表)
第一章:AI原生研发成本黑洞的本质解构 2026奇点智能技术大会(https://ml-summit.org) AI原生研发并非简单地将模型“接入”系统,而是一场从基础设施、数据契约、服务边界到可观测性的全栈重构。其成本黑洞常被误归因于GPU算力开销,实则根植于…...
人机交互设计避坑:控制驱动部分的7个高并发处理要点(含酒店管理系统案例)
人机交互设计避坑:控制驱动部分的7个高并发处理要点(含酒店管理系统案例) 在酒店前台同时处理数十个订单时,系统突然卡死;促销活动上线瞬间,服务器响应时间从200ms飙升到15秒——这些场景背后,往…...
IDA Pro 9.3 更新- 强大的反汇编程序、反编译器和多功能调试器工具
简介 IDA Pro 9.3 (macOS, Linux, Windows) - 强大的反汇编程序、反编译器和多功能调试器 A powerful disassembler, decompiler and a versatile debugger. In one tool.IDA Pro 一个强大的反汇编程序、反编译器和多功能调试器。集成在一个工具中。 请访问原文链接&#x…...
3个真实场景下用命令行解放百度网盘操作
3个真实场景下用命令行解放百度网盘操作 【免费下载链接】BaiduPCS-Go iikira/BaiduPCS-Go原版基础上集成了分享链接/秒传链接转存功能 项目地址: https://gitcode.com/GitHub_Trending/ba/BaiduPCS-Go 你是否曾经历过这样的场景:需要批量下载几十个文件&…...
【Maxwell16.0】实战解析:电机三维空载仿真中的常见问题与解决方案
1. Maxwell16.0电机三维空载仿真入门指南 第一次打开Maxwell16.0做电机三维仿真时,很多人都会被复杂的界面吓到。其实只要掌握几个关键步骤,就能快速上手。我刚开始学习时也走了不少弯路,现在把最实用的操作方法分享给大家。 三维空载仿真的核…...
