切片不够技术来凑

news2024/11/17 9:55:21

概述

随着数据经度的提升,18级的切片有些场景已经不够用了,但是大部分在线的栅格切片最大级别还是18级,如果地图继续放大,有的框架(leaflet会,openlayers和mapboxGL不会)会存在没有底图的情况。为处理这种情况,本文通过node实现在级别大于18级的时候将18级的切片进行裁切,解决没有底图的问题。

实现效果

动画.gif

实现代码

获取切片图片,如果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] = xy18
            url = `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] = extent
                const [x18, y18] = xy18
                const [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_ORIGIN
    const maxX = (x + 1) * this.TILE_SIZE * res - this.TILE_ORIGIN
    const minY = this.TILE_ORIGIN - (y + 1) * this.TILE_SIZE * res
    const maxY = this.TILE_ORIGIN - y * this.TILE_SIZE * res
    return [minX, minY, maxX, maxY]
}

其中

  • TILE_SIZE,切片大小,值为256;
  • TILE_ORIGIN,切片原点,值为20037508.34;
    getTileIndexByCoords为根据坐标获取切片索引,实现代码如下:
getTileIndexByCoords(x, y) {
    const res18 = this.getResolution(18) * this.TILE_SIZE
    return [
        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_ORIGIN
        const maxX = (x + 1) * this.TILE_SIZE * res - this.TILE_ORIGIN
        const minY = this.TILE_ORIGIN - (y + 1) * this.TILE_SIZE * res
        const maxY = this.TILE_ORIGIN - y * this.TILE_SIZE * res
        return [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] = xy18
                url = `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] = extent
                    const [x18, y18] = xy18
                    const [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_SIZE
        return [
            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.params
    util.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.params
    const TILE_SIZE = 256;
    const canvas = createCanvas(TILE_SIZE, TILE_SIZE)
    const ctx = canvas.getContext('2d')
    ctx.fillStyle = '#f00'
    ctx.strokeStyle = '#f00'
    ctx.lineWidth = 2
    ctx.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')
})

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring源码篇(十二)事件机制

文章目录 前言应用示例第一种&#xff1a;EventListener第二种&#xff1a;实现ApplicationListener第三种&#xff1a;TransactionalEventListener补充&#xff1a;筛选条件 源码初始化事件器注册监听器添加监听器添加1&#xff1a;应用启动前的监听器SpringApplication实例化…

Hadoop3教程(三十五):(生产调优篇)HDFS小文件优化与MR集群简单压测

文章目录 &#xff08;168&#xff09;HDFS小文件优化方法&#xff08;169&#xff09;MapReduce集群压测参考文献 &#xff08;168&#xff09;HDFS小文件优化方法 小文件的弊端&#xff0c;之前也讲过&#xff0c;一是大量占用NameNode的空间&#xff0c;二是会使得寻址速度…

【Linux】进程优先级|进程并发概念|在vim中批量化注释

文章目录 前言tips——如何在vim中批量化注释进程更深度理解一、什么是进程优先级二、 为什么要有优先级三、Linux怎么设置优先级查看进程优先级的命令PRI and NI用top命令更改已存在进程的nice&#xff1a; 如何根据优先级开展调度呢&#xff1f;五、其他概念并发&#xff08;…

使用Github.io创建自己的博客

文章目录 1.最终效果2.操作步骤2.1 前置操作2.2 按照自己需求修改内容2.2.1 基本修改2.2.2 额外添加知乎等社交网站链接 2.3 首页修改2.4 查看发布状态2.5 奇怪的错误(头像显示错误)2.6 本地调试2.7 后续修改 3. 项目设置为私密&#xff08;要付费升级账号才行❌&#xff09;3.…

【BA-BP分类】基于蝙蝠算法优化神经网络分类研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

RHCE---Shell基础 2

文章目录 目录 文章目录 前言 一.变量 概述 定义 自定义变量 环境变量 概述&#xff1a; 定义环境变量&#xff1a; 位置变量 "$*"会把所有位置参数当成一个整体&#xff08;或者说当成一个单词 变量的赋值和作用域 read 命令 变量和引号 变量的作用域 变…

Java SOAP 调用 C# 的WebService

Java SOAP 调用 C# 的WebService&#xff0c;C# 的WebService方法的创建可以参考上一篇文章。IntelliJ IDEA Community Edition 2021.2.3的idea64.exe新建项目&#xff0c;导入需要的jar&#xff0c;代码如下&#xff1a; import org.apache.axis.client.Service; import org.…

微信小程序 picker-view 组件构建一个上下拖动选择器

picker-view是官方的一个选择器组件 支持多级选择 当然也可以单项选择 我们先来看看是个什么东西吧 简单写一个 wxml代码 <view><picker-view bindchange"pickerChange" style"width: 300rpx; height: 200rpx; font-size: 20px;"><!-- pic…

1.顺序表-头插、头删、尾插、尾删

文章目录 简介1.头插功能2.头删功能3.尾插功能4.尾删功能5.此程序共包含4个文件&#xff0c;2个.c文件和2个.h文件5.1 SeqList.h文件5.2 SeqList.c文件5.3 test.h文件5.4 test.c文件 6.测试结果6.1 测试尾插和尾删的运行结果6.2 测试头插和头删的运行结果 7.温馨提示 简介 本文…

实战:打造一个开箱即用的超丝滑超漂亮hexo博客网站-v4-(通过百度网盘同步空间来同步source核心数据)

实战&#xff1a;打造一个开箱即用的超丝滑超漂亮hexo博客网站-v4-(通过百度网盘同步空间来同步source核心数据) 目录 文章目录 实战&#xff1a;打造一个开箱即用的超丝滑超漂亮hexo博客网站-v4-(通过百度网盘同步空间来同步source核心数据)目录写在前面本次更新方案背景方案官…

C语言每日一题(17)老人的数目

力扣 2678 老人的数目 给你一个下标从 0 开始的字符串 details 。details 中每个元素都是一位乘客的信息&#xff0c;信息用长度为 15 的字符串表示&#xff0c;表示方式如下&#xff1a; 前十个字符是乘客的手机号码。接下来的一个字符是乘客的性别。接下来两个字符是乘客的…

测开不得不会的python之re模块正则表达式匹配

学习目录 正则表达式介绍 正则表达式的常用符号 python的re模块 findall()函数 finditer()函数 match()函数 search()函数 split()函数 正则表达式的介绍 Python 通过标准库中的 re 模块来支持正则表达式。 正则表达式作为高级的文本模式匹配、抽取、和搜索。简单地说…

Python OpenCV将n×n的小图拼接成m×m的大图

Python OpenCV将nn的小图拼接成mm的大图 前言前提条件相关介绍实验环境n \times n的小图拼接成m \times m的大图代码实现 前言 由于本人水平有限&#xff0c;难免出现错漏&#xff0c;敬请批评改正。更多精彩内容&#xff0c;可点击进入Python日常小操作专栏、OpenCV-Python小…

独立企业签名和共享企业签名的区别

最近两天&#xff0c;小编注意到行业内的一则消息&#xff0c;市面上有好几本企业签名证书又被封了。毋庸置疑&#xff0c;这些肯定是共享的证书。理由很简单&#xff0c;市面上的用来做共享证书的企业签名&#xff0c;基本上都是不会限制应用类型以及签名的数量。据鲲分发平台…

计算机考研自命题(6)

1、C语言–奇数求和 1、使用函数求奇数和&#xff1a;输入一批正整数&#xff08;以零或负数为结束标志&#xff09;&#xff0c;求其中的奇数和。要求定义和调用函数 odd(n) 判断数的奇偶 性&#xff0c;当 n 为偶数时返回 0 &#xff0c;否则返回 1 。试编写相应程序。 /* 解…

yolov7改进优化之蒸馏(二)

续yolov7改进优化之蒸馏&#xff08;一&#xff09;-CSDN博客 上一篇已经基本写出来yolov7/v5蒸馏的整个过程&#xff0c;不过要真的训起来我们还需要进行一些修改。 Model修改 蒸馏需要对teacher和student网络的特征层进行loss计算&#xff0c;因此我们forward时要能够返回需…

Lua入门使用与基础语法

文章目录 目的基础说明开发环境基础语法注释数据类型变量流程控制函数 总结 目的 Lua是一种非常小巧的脚本语言&#xff0c;基于C构建并且完全开源&#xff0c;可以方便的嵌入到各种项目中&#xff0c;当然也可以单独使用。Lua经常被用在很多非脚本语言的项目中&#xff0c;用…

组件通信$refs | $parent |$root

父组件传值子组件用Props 子组件传值父组件用$emit 父组件直接还可以直接取子组件的值用$refs 父组件直接从子子组件中获取值$refs 不建议使用会增加组件之间的耦合度&#xff0c;一般用于第三方插件的封装 ref如果绑定在dom节点上&#xff0c;拿到的就是原生dom节点。 ref如…

【智能家居】

面向Apple developer学习&#xff1a;AirPlay | Apple Developer Documentation Airplay AirPlay允许人们将媒体内容从iOS、ipad、macOS和tvOS设备无线传输到支持AirPlay的Apple TV、HomePod以及电视和扬声器上。 网页链接的最佳实践 首选系统提供的媒体播放器。内置的媒体播…

VRPTW(MATLAB):蜘蛛蜂优化算法SWO求解带时间窗的车辆路径问题VRPTW(提供参考文献及MATLAB代码)

一、VRPTW简介 带时间窗的车辆路径问题(Vehicle Routing Problem with Time Windows, VRPTW)是车辆路径问题(VRP)的一种拓展类型。VRPTW一般指具有容量约束的车辆在客户指定的时间内提供配送或取货服务&#xff0c;在物流领域应用广泛&#xff0c;具有重要的实际意义。VRPTW常…