原生2d web地图引擎

原生2d web地图引擎

下面这段核心代码,不依赖任何2d地图引擎如leaflet,open layers,基于原生的JavaScript实现了地图瓦片的加载渲染,以及地图平移和缩放等功能

<template>
	<div id="mapWrapper">
		<div id="mapPanel">
			<canvas
				@mousedown="startDrag"
				@mousemove="dragging"
				@mouseup="endDrag"
				@wheel="zoomMap"
			></canvas>
		</div>
	</div>
</template>
<script setup>
import { onMounted } from "vue";
// 地球参考椭球半径
const EARTHRADIUS = 6378137;
// 瓦片大小
const TILESIZE = 256;
// 是否是第一次加载
let initFlag = false;
// 初始化层级
let zoom = 16;
// 初始化比例尺
let mapSize = 0;
let scale = 0;
// 计算比例尺
// 根据地图层级计算整个地图瓦片阵列的行列数以及地图比例尺
const getMapScale = () => {
	mapSize = TILESIZE * Math.pow(2, zoom);
	scale = (2 * Math.PI * EARTHRADIUS) / mapSize;
};
getMapScale();
// 地图中心点坐标
let mapCenter = {
	lon: 0,
	lat: 0,
};
let pixelBound = {
	minPixelX: 0,
	maxPixelX: 0,
	minPixelY: 0,
	maxPixelY: 0,
};
let canvas;
// 基于中心点坐标计算地图的四至像素范围
const getPixelBound = () => {
	const [x, y] = lonlatToxy(mapCenter.lon, mapCenter.lat);
	const [pixelX, pixelY] = xyTopixel(x, y);
	pixelBound.minPixelX = pixelX - canvas.width / 2;
	pixelBound.maxPixelX = pixelX + canvas.width / 2;
	pixelBound.minPixelY = pixelY - canvas.height / 2;
	pixelBound.maxPixelY = pixelY + canvas.height / 2;
};
// 经纬度坐标转web墨卡托坐标
const lonlatToxy = (lon, lat) => {
	const x = (lon / 180) * Math.PI * EARTHRADIUS;
	const y =
		Math.log(Math.tan(Math.PI / 4 + ((lat / 180) * Math.PI) / 2)) *
		EARTHRADIUS;
	return [x, y];
};
// web墨卡托坐标转经纬度坐标
const xyTolonlat = (x, y) => {
	const lon = x / EARTHRADIUS / Math.PI * 180;
	const lat = (2 * Math.atan(Math.exp(y / EARTHRADIUS)) - Math.PI / 2) / Math.PI * 180;
	return [lon, lat];
};
// web墨卡托坐标转像素坐标
const xyTopixel = (x, y) => {
	//计算比例尺
	const pixelX = x / scale + mapSize / 2;
	const pixelY = mapSize / 2 - y / scale;
	return [pixelX, pixelY];
};
// 像素坐标转web墨卡托坐标
const pixelToxy = (pixelX, pixelY) => {
	const x = (pixelX - mapSize / 2) * scale;
	const y = (mapSize / 2 - pixelY) * scale;
	return [x, y];
};



//基于像素坐标计算瓦片编号
const pixelToTileNo = (pixelX, pixelY) => {
	return [
		Number.parseInt(pixelY / TILESIZE),
		Number.parseInt(pixelX / TILESIZE),
	];
};
//影像地图瓦片
const drawImage = (ctx, row, col, zoom) => {
	const url = `https://khms3.google.com/kh/v=947?x=${col}&y=${row}&z=${zoom}`;
	const tileImg = new Image();
	tileImg.setAttribute("crossOrigin", "anonymous");
	tileImg.onload = () => {
		const leftTopX = col * TILESIZE;
		const leftTopY = row * TILESIZE;
		const sx = leftTopX - pixelBound.minPixelX;
		const sy = leftTopY - pixelBound.minPixelY;
		ctx.drawImage(tileImg, sx, sy);
	};
	tileImg.src = url;
};

const drawMap = () => {
	//计算行列号的范围
	const [minRow, minCol] = pixelToTileNo(
		pixelBound.minPixelX,
		pixelBound.minPixelY
	);
	const [maxRow, maxCol] = pixelToTileNo(
		pixelBound.maxPixelX,
		pixelBound.maxPixelY
	);
	//绘制视口范围内的地图瓦片
	const ctx = canvas.getContext("2d");
	ctx.clearRect(0, 0, canvas.width, canvas.height);
	for (let i = minRow; i <= maxRow; i++) {
		for (let j = minCol; j <= maxCol; j++) {
			drawImage(ctx, i, j, Number.parseInt(zoom));
		}
	}
};

onMounted(() => {
	canvas = document.getElementsByTagName("canvas")[0];
	canvas.style.letf = "0px";
	canvas.style.top = "0px";
	canvas.height = canvas.clientHeight;
	canvas.width = canvas.clientWidth;
	const getPosition = (position) => {
		const lat = position.coords.latitude;
		const lon = position.coords.longitude;
		mapCenter.lon = 116.411455;
		mapCenter.lat = 39.918255;
		getPixelBound();
		drawMap();
		initFlag = true;
	};
	navigator.geolocation.getCurrentPosition(getPosition, (error) => {
		console.log(error.code);
	});
});

//鼠标拖动地图事件
let startX, startY;
const startDrag = (e) => {
	if (!initFlag || e.button != 0) {
		return;
	}
	startX = e.x;
	startY = e.y;
};
const dragging = (e) => {
	if (!initFlag || e.buttons != 1) {
		return;
	}
	const currentX = e.x;
	const currentY = e.y;
	const dx = currentX - startX;
	const dy = currentY - startY;
	canvas.style.left = `${dx}px`;
	canvas.style.top = `${dy}px`;
};
const endDrag = (e) => {
	if (!initFlag || e.button != 0) {
		return;
	}
	canvas.style.left = "0px";
	canvas.style.top = "0px";
	let endX = e.x;
	let endY = e.y;
	const dx = endX - startX;
	const dy = endY - startY;
	// 更新四至像素坐标
	pixelBound.minPixelX -= dx;
	pixelBound.minPixelY -= dy;
	pixelBound.maxPixelX -= dx;
	pixelBound.maxPixelY -= dy;
	// 更新地图中心点坐标
	const mapCenterXY = pixelToxy(
		pixelBound.minPixelX + canvas.width / 2,
		pixelBound.minPixelY + canvas.height / 2
	);
	const mapCenterLonLat = xyTolonlat(mapCenterXY[0], mapCenterXY[1]);
	mapCenter.lon = mapCenterLonLat[0];
	mapCenter.lat = mapCenterLonLat[1];
	drawMap();
};
const zoomMap = (e) => {
	// 向下滚动鼠标缩小,拉远镜头
	// 向上滚动鼠标放大,拉近镜头
	let zoomFlag = e.deltaY > 0 ? "out" : "in";
	if (zoomFlag == "out") {
		if (zoom <= 0) {
			return;
		}
		zoom--;
	} else {
		if (zoom >= 19) {
			return;
		}
		zoom++;
	}
	// 缩放时保持鼠标所在位置不变
	const canvasX = e.offsetX;
	const canvasY = e.offsetY;
	const mousePixelX = pixelBound.minPixelX + canvasX;
	const mousePixelY = pixelBound.minPixelY + canvasY;
	const mousexy = pixelToxy(mousePixelX, mousePixelY);
	// 更新比例尺
	getMapScale();
	// 更新中心点坐标
	const dx = canvasX * scale;
	const dy = canvasY * scale;
	const leftTopX = mousexy[0] - dx;
	const leftTopY = mousexy[1] + dy;
	const mapCenterX = leftTopX + canvas.width / 2 * scale;
	const mapCenterY = leftTopY - canvas.height / 2 * scale;
	const _mapCenter = xyTolonlat(mapCenterX, mapCenterY);
	mapCenter.lon = _mapCenter[0];
	mapCenter.lat = _mapCenter[1];
	//计算像素坐标的四至
	getPixelBound();
	drawMap();
};
</script>

<style scoped>
#mapWrapper {
	width: 100%;
	height: 100%;
	position: relative;
}
#mapPanel {
	width: 1200px;
	height: 800px;
	position: absolute;
	left: calc(50% - 600px);
	top: calc(50% - 400px);
	overflow: hidden;
	background-color: #bbffaa;
}
canvas {
	width: 100%;
	height: 100%;
	position: absolute;
}
</style>

效果展示

Video

工程源码

https://github.com/Lcy0801/LearnThreeJS/tree/MapRender

相关推荐

  1. 原生2d web地图引擎

    2024-06-16 10:46:01       37 阅读
  2. Kubernetes:云原生时代的核心引擎

    2024-06-16 10:46:01       25 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-06-16 10:46:01       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-06-16 10:46:01       101 阅读
  3. 在Django里面运行非项目文件

    2024-06-16 10:46:01       82 阅读
  4. Python语言-面向对象

    2024-06-16 10:46:01       91 阅读

热门阅读

  1. 配置 SSH 管理多个 Git 仓库和以及多个 Github 账号

    2024-06-16 10:46:01       32 阅读
  2. 1527. 患某种疾病的患者

    2024-06-16 10:46:01       41 阅读
  3. springMVC中的注解

    2024-06-16 10:46:01       37 阅读
  4. GitHub每周最火火火项目(6.10-6.16)

    2024-06-16 10:46:01       36 阅读
  5. 从零开始!Jupyter Notebook的安装教程

    2024-06-16 10:46:01       33 阅读
  6. 基于YOLOv5的钢材表面缺陷检测

    2024-06-16 10:46:01       34 阅读
  7. RDF 简介

    2024-06-16 10:46:01       28 阅读
  8. python字符串篇进阶练习

    2024-06-16 10:46:01       30 阅读
  9. 数据结构之B树

    2024-06-16 10:46:01       43 阅读