let map = document.getElementById('map'); let coord = document.getElementById('coord'); map.width = map.clientWidth; map.height = map.clientHeight; let ctx = map.getContext('2d'); const tilesize = 256; const minlevel = 3; const maxlevel = 18; let zoom_level = 3; //默认 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 alltilecount = 0; let downtilecount = 0; let drecord = 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(); 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); alltilecount += needtiles.length; //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); }); debug(); } 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(coord.innerText); } map.onmouseup = (e) => { console.log('鼠标松开'); moveflag = false; } map.onmousemove = (e) => { //输出latlng const mlc = viewport2mplat(e.offsetX, e.offsetY); let lat = tile2lat(mlc.y / tilesize, zoom_level); let lng = tile2long(mlc.x / tilesize, zoom_level); // const etsize = calTileSize(); // if (zoom_level != Math.floor(zoom_level)) { // console.log('resize', etsize, tilesize, lat, lng) // lat = tile2lat(mlc.y / etsize * tilesize, Math.floor(zoom_level + 1)); // lng = tile2long(mlc.x / etsize * tilesize, Math.floor(zoom_level + 1)); // } const coordstr = `(${e.offsetX},${e.offsetY}) - (${mlc.x.toFixed(3)},${mlc.y.toFixed(3)}) - (${lat},${lng})`; coord.innerText = coordstr; //console.log(e.offsetX, e.offsetY); 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(); Array.prototype.remove = function(val) { const index = this.indexOf(val); if (index > -1) { return this.splice(index, 1); } return this; } //add opeenstreetmap function downloadimg(z, x, y) { return new Promise((resolve, reject) => { //const url = `https://tile.openstreetmap.org/${z}/${x}/${y}.png`; const url = 'http://webrd01.is.autonavi.com/' + 'appmaptile?lang=zh_cn&size=1&scale=1&style=8' + `&x=${x}&y=${y}&z=${z}`; //add downloadcheck const obj = { z: z, x: x, y: y }; drecord.push(obj); //下载前加入记录 let img = new Image(); img.onload = () => { console.log('downloaded' + z + '/' + x + '/' + y + '.png'); cache.push({ img: img, z: z, x: x, y: y }); drecord.remove(obj); //下载完成后从记录中移除 downtilecount += 1; resolve(img); } img.src = url; }); } async function getImg(z, x, y) { //先判断是否加入下载记录 const isdownloading = drecord.includes({ z: z, x: x, y: y }); if (isdownloading) { //return getImg(z, x, y); return setTimeout(() => { getImg(z, x, y); }, 100); } const found = cache.find(e => (e.z == z && e.x == x && e.y == y)); if (found == undefined) { let p = await downloadimg(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); } }); } function debug() { console.log('=============debug================='); console.log('zoom_level', zoom_level); console.log('alltilecount', alltilecount); console.log('downtilecount', downtilecount); console.log('cachecount', caches.length); console.log('drecord count', drecord.length); console.log('=============debug================='); } //=====coord about===== 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)))); } //=========