|
|
|
|
@ -0,0 +1,316 @@
|
|
|
|
|
//椭球相关参数计算
|
|
|
|
|
const a = 6378137; //长半轴 a
|
|
|
|
|
const f = 1 / 298.257223563; //扁率 (a-b)/a
|
|
|
|
|
const b = a * (1 - f); //短半轴 b
|
|
|
|
|
const e = Math.sqrt(a * a - b * b) / a; //椭球第一偏心率
|
|
|
|
|
|
|
|
|
|
function degToRad(deg) {
|
|
|
|
|
return deg / 180 * Math.PI;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function radToDeg(rad) {
|
|
|
|
|
return rad / Math.PI * 180;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function wgs84ToWebMercator(lat, lon) {
|
|
|
|
|
let x = a * lon;
|
|
|
|
|
let y = a * Math.log(Math.tan(Math.PI / 4 + lat / 2));
|
|
|
|
|
return { x: x, y: y };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function webMercatorToWgs84(x, y) {
|
|
|
|
|
let lon = x / a;
|
|
|
|
|
let lat = 2 * Math.atan(Math.pow(Math.E, y / a)) - Math.PI / 2;
|
|
|
|
|
return { lon: lon, lat: lat }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function lon2tile(lon, z) {
|
|
|
|
|
return (Math.floor((lon + 180) / 360 * Math.pow(2, z)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function lat2tile(lat, z) {
|
|
|
|
|
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, z)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function lon2x(lon, z) {
|
|
|
|
|
return ((lon + 180) / 360 * Math.pow(2, z));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function lat2y(lat, z) {
|
|
|
|
|
return ((1 - Math.log(Math.tan(lat * Math.PI / 180) +
|
|
|
|
|
1 / Math.cos(lat * Math.PI / 180)) / Math.PI) /
|
|
|
|
|
2 * Math.pow(2, z));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function tile2long(x, z) {
|
|
|
|
|
return (x / Math.pow(2, z) * 360 - 180);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function tile2lat(y, z) {
|
|
|
|
|
var n = Math.PI - 2 * Math.PI * y / Math.pow(2, z);
|
|
|
|
|
return (180 / Math.PI * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n))));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//https://tile.openstreetmap.org/z/x/y.png
|
|
|
|
|
//https://tile.openstreetmap.org/15/26794/14256.png
|
|
|
|
|
|
|
|
|
|
let cache = new Array();
|
|
|
|
|
|
|
|
|
|
function downloadimg(z, x, y) {
|
|
|
|
|
const src = 'https://tile.openstreetmap.org/' + z + '/' + x + '/' + y + '.png';
|
|
|
|
|
let img = new Image();
|
|
|
|
|
img.src = src;
|
|
|
|
|
img.onload = () => {
|
|
|
|
|
console.log('downloaded' + z + '/' + x + '/' + y + '.png');
|
|
|
|
|
cache.push({ img: img, z: z, x: x, y: y });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getIndex(z, x, y) {
|
|
|
|
|
for (let i = 0; i < cache.length; i++) {
|
|
|
|
|
const e = cache[i];
|
|
|
|
|
if (e.z == z && e.x == x && e.y == y) {
|
|
|
|
|
return i;
|
|
|
|
|
} else {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
//last
|
|
|
|
|
downloadimg(z, x, y);
|
|
|
|
|
return cache.length;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//已知条件 lat long zoom_level 显示范围的宽高
|
|
|
|
|
let zoom_level = 15;
|
|
|
|
|
let lat = 22.7490073;
|
|
|
|
|
let long = 114.3781213;
|
|
|
|
|
let width = 1600;
|
|
|
|
|
let height = 900;
|
|
|
|
|
|
|
|
|
|
function calBasetile(lat, long, zoom_level) {
|
|
|
|
|
let basetile = {};
|
|
|
|
|
basetile.x = lon2x(long, zoom_level); //对应zoomlevel下long的x坐标
|
|
|
|
|
basetile.y = lat2y(lat, zoom_level); //对应zoomlevel下lat的y坐标
|
|
|
|
|
basetile.tilex = lon2tile(long, zoom_level); //对应zoomlevel下long的x tile编号
|
|
|
|
|
basetile.tiley = lat2tile(lat, zoom_level); //对应zoomlevel下lat的y tile编号
|
|
|
|
|
basetile.offsetx = Math.floor((basetile.x - basetile.tilex) * 256); //相对于左上角,其点的x偏移值
|
|
|
|
|
basetile.offsety = Math.floor((basetile.y - basetile.tiley) * 256); //相对于左上角,其点的y偏移值
|
|
|
|
|
|
|
|
|
|
basetile.minx = width / 2 - basetile.offsetx;
|
|
|
|
|
basetile.maxx = basetile.minx + 256;
|
|
|
|
|
basetile.miny = height / 2 - basetile.offsety;
|
|
|
|
|
basetile.maxy = basetile.miny + 256;
|
|
|
|
|
|
|
|
|
|
basetile.p1 = Math.floor(basetile.maxy / 256 + 0.5);
|
|
|
|
|
basetile.p2 = Math.floor((height - basetile.miny) / 256 + 0.5);
|
|
|
|
|
basetile.p3 = Math.floor(basetile.maxx / 256 + 0.5);
|
|
|
|
|
basetile.p4 = Math.floor((width - basetile.minx) / 256 + 0.5);
|
|
|
|
|
console.log(basetile);
|
|
|
|
|
return basetile;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//基于已知条件计算需要的各个tile
|
|
|
|
|
function needtiles(basetile, p1, p2, p3, p4) {
|
|
|
|
|
let tiles = new Array();
|
|
|
|
|
let tilex = basetile.tilex;
|
|
|
|
|
let tiley = basetile.tiley;
|
|
|
|
|
tiles.push({ x: tilex, y: tiley });
|
|
|
|
|
for (let i = 0; i < p4; i++) {
|
|
|
|
|
tiles.push({ x: tilex + i + 1, y: tiley });
|
|
|
|
|
}
|
|
|
|
|
for (let i = 0; i < p3; i++) {
|
|
|
|
|
tiles.push({ x: tilex - i - 1, y: tiley });
|
|
|
|
|
}
|
|
|
|
|
for (let i = 0; i < p1; i++) {
|
|
|
|
|
for (let j = 0; j < p3 + p4 + 1; j++) {
|
|
|
|
|
tiles.push({ x: tilex - p3 + j, y: tiley - i - 1 });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for (let i = 0; i < p2; i++) {
|
|
|
|
|
for (let j = 0; j < p3 + p4 + 1; j++) {
|
|
|
|
|
tiles.push({ x: tilex - p3 + j, y: tiley + i + 1 });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
//console.log(tiles);
|
|
|
|
|
//console.log(tiles.length);
|
|
|
|
|
return tilesfilter(tiles, zoom_level);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//过滤超过限制的tiles
|
|
|
|
|
function tilesfilter(tiles, zoom_level) {
|
|
|
|
|
const maxrange = Math.pow(2, zoom_level) - 1;
|
|
|
|
|
let newtiles = new Array();
|
|
|
|
|
for (let i = 0; i < tiles.length; i++) {
|
|
|
|
|
if (tiles[i].x < 0 || tiles[i].x > maxrange) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (tiles[i].y < 0 || tiles[i].y > maxrange) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
newtiles.push(tiles[i]);
|
|
|
|
|
}
|
|
|
|
|
return newtiles;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let basetile = calBasetile(lat, long, zoom_level);
|
|
|
|
|
let tiles = needtiles(basetile, basetile.p1, basetile.p2, basetile.p3, basetile.p4);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//渲染问题
|
|
|
|
|
//通过与基准之间的位置关系计算渲染的坐标
|
|
|
|
|
//minx miny 即为基准tile当前渲染位置
|
|
|
|
|
|
|
|
|
|
// for (let i = 0; i < tiles.length; i++) {
|
|
|
|
|
// const element = tiles[i];
|
|
|
|
|
// let renderx = (element.x - tilex) * 256 + minx;
|
|
|
|
|
// let rendery = (element.y - tiley) * 256 + miny;
|
|
|
|
|
// console.log({ x: renderx, y: rendery });
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
let canvas = document.getElementById('map');
|
|
|
|
|
canvas.width = map.clientWidth;
|
|
|
|
|
canvas.height = map.clientHeight;
|
|
|
|
|
let ctx = canvas.getContext('2d');
|
|
|
|
|
|
|
|
|
|
function clearcanvas() {
|
|
|
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function drawPoint(x, y) {
|
|
|
|
|
ctx.fillStyle = "white"
|
|
|
|
|
ctx.strokeStyle = "#a35312"
|
|
|
|
|
ctx.beginPath();
|
|
|
|
|
ctx.arc(x, y, 3, 0, Math.PI * 2, true);
|
|
|
|
|
ctx.closePath();
|
|
|
|
|
ctx.fill();
|
|
|
|
|
ctx.stroke();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function drawRect(x, y) {
|
|
|
|
|
ctx.strokeStyle = "white"
|
|
|
|
|
ctx.strokeRect(x, y, 256, 256);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function drawImg(z, tilex, tiley, x, y, ) {
|
|
|
|
|
const index = getIndex(z, tilex, tiley);
|
|
|
|
|
if (cache[index]) {
|
|
|
|
|
ctx.drawImage(cache[index].img, x, y);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function drawtext(text, x, y) {
|
|
|
|
|
ctx.fillStyle = "white"
|
|
|
|
|
ctx.font = "18px serif";
|
|
|
|
|
ctx.textAlign = "center";
|
|
|
|
|
ctx.textBaseline = "middle";
|
|
|
|
|
ctx.fillText(text, x, y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function drawgrid(basetile, tiles) {
|
|
|
|
|
let minx = basetile.minx;
|
|
|
|
|
let miny = basetile.miny;
|
|
|
|
|
let offsetx = basetile.offsetx;
|
|
|
|
|
let offsety = basetile.offsety;
|
|
|
|
|
let tilex = basetile.tilex;
|
|
|
|
|
let tiley = basetile.tiley;
|
|
|
|
|
clearcanvas();
|
|
|
|
|
drawPoint(minx + offsetx, miny + offsety);
|
|
|
|
|
for (let i = 0; i < tiles.length; i++) {
|
|
|
|
|
const element = tiles[i];
|
|
|
|
|
let renderx = (element.x - tilex) * 256 + minx;
|
|
|
|
|
let rendery = (element.y - tiley) * 256 + miny;
|
|
|
|
|
//console.log({ x: renderx, y: rendery });
|
|
|
|
|
drawPoint(renderx, rendery);
|
|
|
|
|
drawRect(renderx, rendery);
|
|
|
|
|
drawImg(zoom_level, tiles[i].x, tiles[i].y, renderx, rendery);
|
|
|
|
|
const showtext = 'x:' + element.x + ' ' + 'y:' + element.y;
|
|
|
|
|
drawtext(zoom_level, renderx + 256 / 2, rendery + 256 / 2);
|
|
|
|
|
drawtext(showtext, renderx + showtext.length / 4 * 18 + 4, rendery + 9);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
drawgrid(basetile, tiles);
|
|
|
|
|
|
|
|
|
|
// drawPoint(width / 2, height / 2);
|
|
|
|
|
|
|
|
|
|
// const element = tiles[0];
|
|
|
|
|
// let renderx = (element.x - tilex) * 256 + minx;
|
|
|
|
|
// let rendery = (element.y - tiley) * 256 + miny;
|
|
|
|
|
// drawPoint(renderx, rendery);
|
|
|
|
|
// drawPoint(renderx + 256 / 2, rendery + 256 / 2);
|
|
|
|
|
// drawRect(renderx, rendery);
|
|
|
|
|
|
|
|
|
|
// drawtext(showtext, renderx + 256 / 2, rendery + 256 / 2);
|
|
|
|
|
// console.log({ x: renderx, y: rendery });
|
|
|
|
|
|
|
|
|
|
//实现移动缩放方法
|
|
|
|
|
//缩放
|
|
|
|
|
map.onwheel = (e) => {
|
|
|
|
|
//console.log(e);
|
|
|
|
|
const zoomleveloffset = (e.deltaY > 0) ? 1 : -1;
|
|
|
|
|
zoom_level -= zoomleveloffset;
|
|
|
|
|
if (zoom_level < 0) {
|
|
|
|
|
zoom_level = 0;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (zoom_level > 19) {
|
|
|
|
|
zoom_level = 19;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
console.log(zoom_level);
|
|
|
|
|
//clearcanvas();
|
|
|
|
|
//重新计算basetile
|
|
|
|
|
basetile = calBasetile(lat, long, zoom_level);
|
|
|
|
|
tiles = needtiles(basetile, basetile.p1, basetile.p2, basetile.p3, basetile.p4);
|
|
|
|
|
drawgrid(basetile, tiles);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let moveflag = false;
|
|
|
|
|
let downpoing = { x: 0, y: 0 };
|
|
|
|
|
|
|
|
|
|
map.onmousedown = (e) => {
|
|
|
|
|
console.log('鼠标按下');
|
|
|
|
|
moveflag = true;
|
|
|
|
|
downpoing.x = e.offsetX;
|
|
|
|
|
downpoing.y = e.offsetY;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
map.onmouseup = (e) => {
|
|
|
|
|
console.log('鼠标松开');
|
|
|
|
|
moveflag = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
map.onmousemove = (e) => {
|
|
|
|
|
if (moveflag) {
|
|
|
|
|
//console.log('有效位移');
|
|
|
|
|
const movex = downpoing.x - e.offsetX;
|
|
|
|
|
const movey = downpoing.y - e.offsetY;
|
|
|
|
|
//实现移动逻辑
|
|
|
|
|
let xp = basetile.x + movex / 256; //注意像素移动要除以256
|
|
|
|
|
let yp = basetile.y + movey / 256;
|
|
|
|
|
long = tile2long(xp, zoom_level);
|
|
|
|
|
let latp = tile2lat(yp, zoom_level);
|
|
|
|
|
//需要在y方向限制移动范围
|
|
|
|
|
//(2*Math.atan(Math.pow(Math.E,Math.PI))-Math.PI/2)/Math.PI*180
|
|
|
|
|
//85.0511287798066
|
|
|
|
|
if (Math.abs(latp) > 89.99999) return;
|
|
|
|
|
// if (lat2y(latp, zoom_level) == Infinity || lat2y(latp, zoom_level) == NaN) {
|
|
|
|
|
// console.log('outrange');
|
|
|
|
|
// return;
|
|
|
|
|
// }
|
|
|
|
|
lat = latp;
|
|
|
|
|
console.log(long + ' ' + lat);
|
|
|
|
|
basetile = calBasetile(lat, long, zoom_level);
|
|
|
|
|
tiles = needtiles(basetile, basetile.p1, basetile.p2, basetile.p3, basetile.p4);
|
|
|
|
|
drawgrid(basetile, tiles);
|
|
|
|
|
|
|
|
|
|
downpoing.x = e.offsetX;
|
|
|
|
|
downpoing.y = e.offsetY;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|