let map = document.getElementById('map'); map.width = map.clientWidth; map.height = map.clientHeight; let ctx = map.getContext('2d'); const tilesize = 256; const minlevel = 0; const maxlevel = 19; let zoom_level = 0; //默认 let viewport = {}; viewport.width = map.width; //默认已知 viewport.height = map.height; //默认已知 viewport.centerx = Math.pow(2, zoom_level) * 256 / 2; //默认 viewport.centery = Math.pow(2, zoom_level) * 256 / 2; //默认 viewport.xoffset = (viewport.width / 2 - viewport.centerx); viewport.yoffset = (viewport.height / 2 - viewport.centery); let needtiles = new Array(); let cache = new Array(); let xhrmanager = new Array(); function calTileSize() { let etsize = tilesize; if (zoom_level != Math.floor(zoom_level)) { etsize = tilesize * Math.pow(2, zoom_level - Math.floor(zoom_level + 1)); } return etsize; } function calTileNum(coord) { return Math.floor(coord / calTileSize()); } function tilesfilter(tails) { let news = new Array(); let max = Math.pow(2, zoom_level); if (zoom_level != Math.floor(zoom_level)) { max = Math.pow(2, Math.floor(zoom_level + 1)); } for (let i = 0; i < tails.length; i++) { const e = tails[i]; if (e.x >= 0 && e.y >= 0 && e.x < max && e.y < max) { news.push(e); } } return news; } function clearcanvas() { ctx.clearRect(0, 0, map.width, map.height); } function drawRect(x, y, width, height) { ctx.strokeStyle = "white" ctx.strokeRect(x, y, width, height); } function drawtext(text, x, y) { ctx.fillStyle = "white" ctx.font = "18px serif"; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText(text, x, y); } function mplat2viewport(mx, my) { const vx = mx + viewport.xoffset; const vy = my + viewport.yoffset; return { x: vx, y: vy }; } function viewport2mplat(vx, vy) { const mx = vx - viewport.xoffset; const my = vy - viewport.yoffset; return { x: mx, y: my }; } function updateviewporttest(scrollx, scrolly, oldz, newz) { //计算出缩放后坐标系的偏移值 // xv = xm + offset ; offset = xv - xm //const newz = zoom_level; const scrollm = viewport2mplat(scrollx, scrolly); //const basescale = 2 * Math.pow(2, newz) / Math.pow(2, Math.floor(newz) + 1); console.log('zoomlevel : ', newz, oldz); const basescale = Math.pow(2, newz - oldz); console.log('basescale: ', basescale); const newscrollm = { x: scrollm.x * basescale, y: scrollm.y * basescale }; console.log('缩放点新坐标 ', newscrollm); viewport.xoffset = scrollx - newscrollm.x; viewport.yoffset = scrolly - newscrollm.y; } //test function drawtest() { ctx.clearRect(0, 0, map.width, map.height); const viewport_left_top = viewport2mplat(0, 0); const viewport_right_bottom = viewport2mplat(viewport.width, viewport.height); const mixnum = calTileNum(viewport_left_top.x); const miynum = calTileNum(viewport_left_top.y); const maxnum = calTileNum(viewport_right_bottom.x); const maynum = calTileNum(viewport_right_bottom.y); const horicount = maxnum - mixnum + 1; const verticount = maynum - miynum + 1; clearcanvas(); xhrabort(); let tails = new Array(); for (let i = 0; i < horicount; i++) { for (let j = 0; j < verticount; j++) { const x = mixnum + i; const y = miynum + j; tails.push({ x: x, y: y }); } } needtiles = tilesfilter(tails); //console.log(needtiles.length, needtiles); needtiles.forEach(et => { const etsize = calTileSize(); const tilev = mplat2viewport(et.x * etsize, et.y * etsize); //drawRect(tilev.x, tilev.y, etsize, etsize); let z = zoom_level; if (zoom_level != Math.floor(zoom_level)) { z = Math.floor(zoom_level + 1); } const text = `${z}/${et.x}/${et.y}.png`; console.log(text); drawtext(text, tilev.x + etsize / 2, tilev.y + etsize / 2); drawTileImg(z, et.x, et.y); }); } let moveflag = false; let downpoing = { x: 0, y: 0 }; map.onmousedown = (e) => { console.log('鼠标按下'); moveflag = true; downpoing.x = e.offsetX; downpoing.y = e.offsetY; //console.log(viewport2mplat(e.offsetX, 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; //实现移动逻辑 viewport.xoffset -= movex; viewport.yoffset -= movey; drawtest(); downpoing.x = e.offsetX; downpoing.y = e.offsetY; } } map.onwheel = (e) => { //console.log(e); const ds = 0.25; const zoomleveloffset = (e.deltaY > 0) ? 1 * ds : -1 * ds; const oldz = zoom_level; zoom_level = zoom_level - zoomleveloffset; if (zoom_level < minlevel) { zoom_level = minlevel; return; } if (zoom_level > maxlevel) { zoom_level = maxlevel; return; } console.log(zoom_level, e.offsetX, e.offsetY); updateviewporttest(e.offsetX, e.offsetY, oldz, zoom_level); drawtest(); } drawtest(); //add opeenstreetmap function downloadimg(z, x, y) { return new Promise((resolve, reject) => { const url = `https://tile.openstreetmap.org/${z}/${x}/${y}.png`; let img = new Image(); img.onload = () => { console.log('downloaded' + z + '/' + x + '/' + y + '.png'); cache.push({ img: img, z: z, x: x, y: y }); resolve(img); } img.src = url; }); } //尝试优化图片下载机制 function xhrdownloadimg(z, x, y) { return new Promise((resolve, reject) => { const url = `https://tile.openstreetmap.org/${z}/${x}/${y}.png`; let imgxhr = new XMLHttpRequest(); imgxhr.open('GET', url, true); //imgxhr.withCredentials = true; imgxhr.responseType = 'blob'; imgxhr.onload = function (e) { if (this.status === 200 && imgxhr.response) { let blob = new Blob([imgxhr.response], { type: 'image/png' }); let img = createImageBitmap(blob); cache.push({ img: img, z: z, x: x, y: y }); resolve(img); } }; xhrmanager.push(imgxhr); imgxhr.send(null); }); } function xhrabort() { xhrmanager.forEach(element => { element.abort(); }); xhrmanager = new Array(); } async function getImg(z, x, y) { const found = cache.find(e => (e.z == z && e.x == x && e.y == y)); if (found == undefined) { let p = await xhrdownloadimg(z, x, y); return p; } else { console.log('exist' + z + '/' + x + '/' + y + '.png'); return found.img; } } function drawTileImg(z, x, y) { const img = getImg(z, x, y); img.then((e) => { const etsize = calTileSize(); const tilev = mplat2viewport(x * etsize, y * etsize); let fz = zoom_level; if (zoom_level != Math.floor(zoom_level)) { fz = Math.floor(zoom_level + 1); } if (needtiles.find(t => z == fz && t.x == x && t.y == y)) { ctx.drawImage(e, tilev.x, tilev.y, etsize, etsize); } }); }