TileCutter.js
5.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/*
* @Date: 2025-01-22 11:45:30
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-02-24 13:21:58
* @FilePath: /map-demo/src/utils/TileCutter.js
* @Description: 文件描述
*/
import JSZip from "jszip";
import { saveAs } from "file-saver";
import dayjs from "dayjs";
const tileSize = 512;
export function TileCutter(imageURL, bounds, zoomLevel) {
const img = new Image();
img.crossOrigin = "Anonymous"; // 避免跨域问题
img.src = imageURL;
img.onload = () => {
sliceImageToTiles(img, bounds, zoomLevel);
};
}
/**
* 将图片切割成瓦片并打包下载
* @param {HTMLImageElement} image - 要切割的图片元素
* @param {L.LatLngBounds} bounds - 图片边界范围,包含西南角和东北角的经纬度
* @param {number} zoomLevel - 地图缩放级别
*/
function sliceImageToTiles(image, bounds, zoomLevel) {
// 创建画布及上下文
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
// 获取原始图片尺寸
const imgWidth = image.width; // 图片原始宽度
const imgHeight = image.height; // 图片原始高度
// 获取边界经纬度
const southWest = bounds.getSouthWest(); // 西南角坐标
const northEast = bounds.getNorthEast(); // 东北角坐标
// 提取边界经纬度值
const lonStart = southWest.lng; // 起始经度
const latStart = southWest.lat; // 起始纬度
const lonEnd = northEast.lng; // 结束经度
const latEnd = northEast.lat; // 结束纬度
// 计算瓦片坐标范围
let tileStartX = lonToTileX(lonStart, zoomLevel); // 起始瓦片X坐标
let tileEndX = lonToTileX(lonEnd, zoomLevel); // 结束瓦片X坐标
let tileStartY = latToTileY(latEnd, zoomLevel); // 起始瓦片Y坐标
let tileEndY = latToTileY(latStart, zoomLevel); // 结束瓦片Y坐标
// 确保瓦片坐标范围正确(起始值小于结束值)
tileStartX = Math.min(tileStartX, tileEndX);
tileEndX = Math.max(tileStartX, tileEndX);
tileStartY = Math.min(tileStartY, tileEndY);
tileEndY = Math.max(tileStartY, tileEndY);
const scaleFactor = 2; // 调高分辨率倍率
// 设置画布尺寸
canvas.width = tileSize * scaleFactor;
canvas.height = tileSize * scaleFactor;
// 设置画布缩放
ctx.scale(scaleFactor, scaleFactor);
const zip = new JSZip(); // 创建一个 JSZip 实例,用来打包所有瓦片
let tileIndex = 0; // 瓦片索引,用来给每个瓦片命名
// 计算每个瓦片的经纬度范围
for (let tileX = tileStartX; tileX <= tileEndX; tileX++) {
for (let tileY = tileStartY; tileY <= tileEndY; tileY++) {
// 计算当前瓦片的经纬度范围
const tileLonStart = tileX * 360 / Math.pow(2, zoomLevel) - 180; // 瓦片起始经度
const tileLonEnd = (tileX + 1) * 360 / Math.pow(2, zoomLevel) - 180; // 瓦片结束经度
const tileLatStart = Math.atan(Math.sinh(Math.PI * (1 - 2 * (tileY + 1) / Math.pow(2, zoomLevel)))) * 180 / Math.PI; // 瓦片起始纬度
const tileLatEnd = Math.atan(Math.sinh(Math.PI * (1 - 2 * tileY / Math.pow(2, zoomLevel)))) * 180 / Math.PI; // 瓦片结束纬度
// 计算图片在当前瓦片中的位置和尺寸(像素坐标)
const tileImgX = (lonStart - tileLonStart) / (tileLonEnd - tileLonStart) * tileSize; // 图片在瓦片中的X坐标
const tileImgY = (tileLatEnd - latEnd) / (tileLatEnd - tileLatStart) * tileSize; // 图片在瓦片中的Y坐标
const tileImgWidth = (lonEnd - lonStart) / (tileLonEnd - tileLonStart) * tileSize; // 图片在瓦片中的宽度
const tileImgHeight = (latEnd - latStart) / (tileLatEnd - tileLatStart) * tileSize; // 图片在瓦片中的高度
// 清空画布
ctx.clearRect(0, 0, tileSize * scaleFactor, tileSize * scaleFactor);
// 绘制图片到画布
ctx.drawImage(
image,
0, 0, imgWidth, imgHeight, // 源图像区域(使用完整图片)
tileImgX, tileImgY, tileImgWidth, tileImgHeight // 目标画布区域(保持实际位置和比例)
);
// 将画布内容转换为Blob对象
canvas.toBlob((blob) => {
if (!blob) {
console.error("瓦片转换失败!");
return;
}
// 获取当前北京时间(UTC+8)
const beijingTime = dayjs().add(8, "hour").toDate();
// 使用 JSZip 将每个瓦片添加到压缩包中
zip.file(`${tileX}_${tileY}_${zoomLevel}.png`, blob, { date: beijingTime });
tileIndex++;
// 如果所有瓦片都处理完,生成并下载压缩包
const totalTiles = (tileEndX - tileStartX + 1) * (tileEndY - tileStartY + 1);
if (tileIndex === totalTiles) {
generateAndDownloadZip(zip, zoomLevel);
}
}, "image/png", 1.0);
}
}
}
// 经纬度转换为瓦片坐标
function lonToTileX(lon, zoom) {
return Math.floor(((lon + 180) / 360) * Math.pow(2, zoom));
}
function latToTileY(lat, zoom) {
return Math.floor(
((1 - Math.log(Math.tan(lat * Math.PI / 180) + 1 / Math.cos(lat * Math.PI / 180)) / Math.PI) / 2) * Math.pow(2, zoom)
);
}
function saveTile(blob, filename) {
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
// 生成并下载压缩包
function generateAndDownloadZip(zip, zoomLevel) {
zip.generateAsync({ type: "blob" }).then((content) => {
// 使用 FileSaver.js 下载压缩包
saveAs(content, `${zoomLevel}级瓦片切片包.zip`);
});
}