一、应用场景:
基于天地图开发时需要远程调用天地图的js、css以及各层级瓦片数据,但是在内网环境或者非互联网环境下无法远程访问资源,下面详细介绍本人离线开发的步骤。
二、天地图资源下载
1.天地图key申请
进入天地图网址天地图API。
点击“控制台”进行账号注册,注册成功后,点击右上角“创建新应用”,得到Key名称。
2.天地图js、css资源下载
需要下载的天地图静态资源包括:
tiandituApi.js(http://api.tianditu.gov.cn/api?v=4.0&tk=)
components.js(https://api.tianditu.gov.cn/v4.0/components.js)
military.js(https://api.tianditu.gov.cn/v4.0/military.js)
service.js(https://api.tianditu.gov.cn/v4.0/service.js)
tianditu4.0.css(http://api.tianditu.gov.cn/v4.0/css/tianditu4.0.css)
将四个文件的代码复制到本地。
3.天地图瓦片数据下载
在天地图官网,点击“地图API”。
网站最下面有个瓦片数据连接地址。
但是不同的瓦片有不同的下载地址,上两张图中有瓦片的分类,包括:矢量底图、矢量注记、影像底图等等,不同的瓦片图不同的下载地址。
如果根据瓦片地址一个个去下载,工作量巨大,而且各个点位不清晰,无法采用人工方式去下载,我们可以使用java代码去统一抓取瓦片数据。
java抓取瓦片代码:
public class TileDownload { //不同的瓦片采用不同的下载地址,一般用的比较多的还是矢量地图 //矢量(行政) - 等经纬度 public static String vec_c = "http://{server}.tianditu.gov.cn/vec_c/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=c&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk={tk}"; //矢量(行政) - 墨卡托 public static String vec_w = "http://{server}.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk={tk}"; //矢量注记(行政) - 等经纬度 public static String cva_c = "http://{server}.tianditu.gov.cn/cva_c/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=c&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk={tk}"; //矢量注记(行政) - 墨卡托 public static String cva_w = "http://{server}.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk={tk}"; //影像 - 等经纬度 public static String img_c = "http://{server}.tianditu.gov.cn/img_c/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=c&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk={tk}"; //影像 - 墨卡托 public static String img_w = "http://{server}.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk={tk}"; //影像注记 - 等经纬度 public static String cia_c = "http://{server}.tianditu.gov.cn/cia_c/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=c&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk={tk}"; //影像注记 - 墨卡托 public static String cia_w = "http://{server}.tianditu.gov.cn/cia_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk={tk}"; //地形 - 等经纬度 public static String ter_c = "http://{server}.tianditu.gov.cn/ter_c/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=ter&STYLE=default&TILEMATRIXSET=c&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk={tk}"; //地形 - 墨卡托 public static String ter_w = "http://{server}.tianditu.gov.cn/ter_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=ter&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk={tk}"; //地形注记 - 等经纬度 public static String cta_c = "http://{server}.tianditu.gov.cn/cta_c/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cta&STYLE=default&TILEMATRIXSET=c&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk={tk}"; //地形注记 - 墨卡托 public static String cta_w = "http://{server}.tianditu.gov.cn/cta_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cta&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk={tk}"; public static String[] servers = {"t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7"}; public static void main(String[] args){ String basePath = "D:/lianyungangtianditu";//设置下载的路径 //这里放你的天地图开发者秘钥,注意天地图API访问次数限制 String tk = "1125258295c5d403d7fbf5e096***"; //将需要下载的图层地址放到一个数组中,不需要下载的可以去掉 String[] urlArr = {vec_c, vec_w, cva_c, cva_w, img_c, img_w, cia_c, cia_w, ter_c, ter_w, cta_c, cta_w}; //天地图一共有18个图层,从1-18,越往后,瓦片数据量越大 int minZoom = 1; int maxZoom = 18; //中国的经纬度 //double startLat = 53.58;//开始纬度(从北到南) //double endLat = 2.7;//结束纬度(从北到南) //double startLon = 73.2;//开始经度(从西到东) //double endLon = 135.15;//结束经度(从西到东) //江苏连云港的经纬度,这边经纬度可以根据自己需要下载的地市或者省份去百度查询经纬度信息,我这边只下载连云港 double startLat = 35.07;//开始纬度(从北到南) double endLat = 34.12;//结束纬度(从北到南) double startLon = 118.24;//开始经度(从西到东) double endLon = 119.48;//结束经度(从西到东) ExecutorService exe = Executors.newFixedThreadPool(15); for(int i=0; i<urlArr.length; i++){ String url = urlArr[i].replace("{tk}", tk); System.out.println(url); String layerName = url.split("tianditu.gov.cn/")[1].split("/wmts?")[0]; System.out.println(layerName); if(layerName.endsWith("c")){ for(int z=minZoom; z<=maxZoom; z++){ double deg = 360.0 / Math.pow(2, z) / 256;//一个像素点代表多少度 int startX = (int)((startLon + 180) / deg / 256);//减数取整 int endX = (int)((endLon + 180) / deg / 256);//加数取整 int startY = (int)((90 - startLat) / deg / 256); int endY = (int)((90 - endLat) / deg / 256); for(int y=startY; y<=endY; y++){ for(int x=startX; x<=endX; x++){ final String newUrl = url.replace("{server}", servers[(int) (Math.random()*servers.length)]).replace("{z}", z+"").replace("{y}", y+"").replace("{x}", x+""); final String filePath = basePath + "/" + layerName + "/" + z + "/" + y + "/" + x + ".png"; exe.execute(new Runnable() { @Override public void run() { File file = new File(filePath); if(!file.exists()){ if(!file.getParentFile().exists()){ file.getParentFile().mkdirs(); } boolean loop = true; int count = 0; while(loop && count<5){ count++; try { InputStream in = getFileInputStream(newUrl); OutputStream out = new BufferedOutputStream(new FileOutputStream(file)); byte[] b = new byte[8192]; int len = 0; while((len = in.read(b)) > -1){ out.write(b, 0, len); out.flush(); } out.close(); in.close(); loop = false; } catch (Exception e) { loop = true; } } if(loop){ System.out.println("下载失败:"+newUrl); System.exit(0);//下载失败后停止任务 } } } }); } } } }else{ //墨卡托 if(startLat > 85.051128){ startLat = 85.051128; } if(endLat <- 85.051128){ endLat = -85.051128; } for(int z=minZoom; z<=maxZoom; z++){ double deg = 360.0 / Math.pow(2, z) / 256; int startX = (int)((startLon + 180) / deg / 256); int endX = (int)((endLon + 180) / deg / 256); int startY = (((int)Math.pow(2, z) * 256 / 2) - (int)((Math.log(Math.tan((90 + startLat) * Math.PI / 360)) / (Math.PI / 180)) / (360/Math.pow(2, z)/256) + 0.5)) / 256; int endY = (((int)Math.pow(2, z) * 256 / 2) - (int)((Math.log(Math.tan((90 + endLat) * Math.PI / 360)) / (Math.PI / 180)) / (360/Math.pow(2, z)/256) + 0.5)) / 256; for(int y=startY; y<=endY; y++){ for(int x=startX; x<=endX; x++){ final String newUrl = url.replace("{server}", servers[(int) (Math.random()*servers.length)]).replace("{z}", z+"").replace("{y}", y+"").replace("{x}", x+""); final String filePath = basePath + "/" + layerName + "/" + z + "/" + y + "/" + x + ".png"; exe.execute(new Runnable() { @Override public void run() { File file = new File(filePath); if(!file.exists()){ if(!file.getParentFile().exists()){ file.getParentFile().mkdirs(); } boolean loop = true; int count = 0; while(loop && count<5){ count++; try { InputStream in = getFileInputStream(newUrl); OutputStream out = new BufferedOutputStream(new FileOutputStream(file)); byte[] b = new byte[8192]; int len = 0; while((len = in.read(b)) > -1){ out.write(b, 0, len); out.flush(); } out.close(); in.close(); loop = false; } catch (Exception e) { loop = true; } } if(loop){ System.out.println("下载失败:"+newUrl); System.exit(0);//下载失败后停止任务 } } } }); } } } } } exe.shutdown(); while (true) { try { Thread.sleep(1000L); } catch (InterruptedException e) { } if (exe.isTerminated()) { break; } } } public static InputStream getFileInputStream(String url) throws Exception{ InputStream is = null; CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet request = new HttpGet(url); request.setHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)"); HttpResponse response = httpclient.execute(request); response.setHeader("Content-Type", "application/octet-stream"); int statusCode = response.getStatusLine().getStatusCode(); if (statusCode == HttpStatus.SC_OK) { HttpEntity entity = response.getEntity(); is = entity.getContent(); } return is; } }
复制
下载的时间会特别长,下载后的文件路径如下:
4.瓦片下载问题汇总
问题一:下载失败
解决:将代码中tk变量改成自己申请的key。
问题二:下载中断
解决:下载中断的原因是key的访问次数受限制,个人key每天只能访问1万次,如果中途下载终端,只需要再次新建应用,采用新应用的key进行再次访问,每个账号可以申请五个key信息。
问题二:中断后重新下载,又会重头开始下载
解决一:根据下载的瓦片层级,需要手动调整代码,如果需要可以直接问我;
解决二:采用下面的实时加载方法,下面有介绍。
三、Java后端读取瓦片数据
瓦片数据下载成功后,前端需要访问数据库加载瓦片信息,代码如下:
@GetMapping("getTiles/{LAYER}") public void getTiles(HttpServletResponse response, @PathVariable("LAYER") String LAYER, @RequestParam("TILECOL") String TILECOL, @RequestParam("TILEROW") String TILEROW, @RequestParam("TILEMATRIX")String TILEMATRIX) throws IOException { String tilesPath = "D:\\lianyungangtianditu"; String tk = "e4237d5eefb7b69a4a472fd64f***"; ServletOutputStream out = null; try { BufferedImage image = ImageIO.read(Files.newInputStream(Paths.get(tilesPath + "/" + LAYER + "/" + TILEMATRIX + "/" + TILEROW + "/" + TILECOL + ".png"))); response.setContentType("image/png"); response.setHeader("Cache-Control", "public"); out = response.getOutputStream(); ImageIO.write(image, "png", out); } catch (Exception e) { response.setStatus(HttpServletResponse.SC_NOT_FOUND); } finally { if (out != null) { out.flush(); out.close(); } } }
复制
代码采用restful风格,四个参数分别为:
LAYER:瓦片类型(cva_c、cva_w、vec_c等)
TILEMATRIX、TILEROW代表:当前瓦片类型下的文件夹
TILECOL代表:瓦片号
四、Vue前端静态资源修改
1.引入天地图.js
在vue的index.html中分别引入之前下载的4个js文件和css文件。
<script type="application/javascript" src="./tiandituApi.js"></script> <script type="application/javascript" src="./components.js"></script> <script type="application/javascript" src="./military.js"></script> <script type="application/javascript" src="./service.js"></script> <link href="./tdt/tianditu4.0.css" rel="stylesheet">
复制
2.修改tiandituApi.js
天地图js文件默认远程访问瓦片数据,需要将远程访问瓦片的方式改成访问本地瓦片。
定义一个本地服务器的变量,将需要访问的瓦片请求链接修改成刚刚我们自己写的后端请求路径。
//定义一个本地服务器地址变量 window.TMAP_URL = 'http://localhost:8080/tiles'; //修改js中瓦片访问地址,本人只用了天地图的矢量瓦片,所以其他的就不需要修改了 r:function(){return window.TMAP_URL+"/getTiles/vec_c?"}, T:function(){return window.TMAP_URL+"/getTiles/cva_c?"}, t:function(){return window.TMAP_URL+"/getTiles/vec_w?"}, Y:function(){return window.TMAP_URL+"/getTiles/cva_w?"}
复制
以上就是整个方案的修改。
五、瓦片实时加载(有需要再用)
问题:当使用java方法进行下载瓦片数据时,受限于key的访问次数限制、天地图图层数等原因,尤其是到最后面,图层越高,瓦片数据量越大,会导致下载中断。
解决:可以采用按需加载的方式,在地图进行访问的时候,根据访问的位置,进行实时下载瓦片到本地。代码如下:
@RestController public class MapController { /** * * @param response * @param LAYER * @param TILECOL * @param TILEROW * @param TILEMATRIX * @throws IOException */ @GetMapping("getTiles/{LAYER}") public void getTiles(HttpServletResponse response, @PathVariable("LAYER") String LAYER, @RequestParam("TILECOL") String TILECOL, @RequestParam("TILEROW") String TILEROW, @RequestParam("TILEMATRIX")String TILEMATRIX) throws IOException { String tilesPath = "D:\\lianyungangtianditu"; String tk = "e4237d5eefb7b69a4a472fd64f***"; ServletOutputStream out = null; try { BufferedImage image = ImageIO.read(Files.newInputStream(Paths.get(tilesPath + "/" + LAYER + "/" + TILEMATRIX + "/" + TILEROW + "/" + TILECOL + ".png"))); if(null!=imageGary){ image=imageGary; }*/ response.setContentType("image/png"); response.setHeader("Cache-Control", "public"); out = response.getOutputStream(); ImageIO.write(image, "png", out); } catch (Exception e) { //如果没有找到图片,就去下载,然后保存进去 //response.setStatus(HttpServletResponse.SC_NOT_FOUND); this.saveImg(tilesPath, LAYER, TILEMATRIX, TILEROW, TILECOL, tk); response.setStatus(HttpServletResponse.SC_NOT_FOUND); } finally { if (out != null) { out.flush(); out.close(); } } } public void saveImg(String path, String layer, String TILEMATRIX, String TILEROW, String TILECOL, String tk){ StringBuffer sb = new StringBuffer(); sb.append("http://t0.tianditu.gov.cn/") .append(layer); if (layer.equals("cva_w") || layer.equals("cva_c")){ sb.append("/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles"); }else { sb.append("/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles"); } sb.append("&TILEMATRIX=").append(TILEMATRIX) .append("&TILEROW=").append(TILEROW) .append("&TILECOL=").append(TILECOL) .append("&tk=").append(tk); System.out.println(sb.toString()); String filePath = path + "/" + layer + "/" + TILEMATRIX + "/" + TILEROW + "/" + TILECOL + ".png"; File file = new File(filePath); if(!file.exists()){ if(!file.getParentFile().exists()){ file.getParentFile().mkdirs(); } try { InputStream in = getFileInputStream(sb.toString()); OutputStream out = new BufferedOutputStream(new FileOutputStream(file)); byte[] b = new byte[8192]; int len = 0; while((len = in.read(b)) > -1){ out.write(b, 0, len); out.flush(); } out.close(); in.close(); } catch (Exception e) { } } } public static InputStream getFileInputStream(String url) throws Exception{ InputStream is = null; CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet request = new HttpGet(url); request.setHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)"); HttpResponse response = httpclient.execute(request); response.setHeader("Content-Type", "application/octet-stream"); int statusCode = response.getStatusLine().getStatusCode(); if (statusCode == HttpStatus.SC_OK) { HttpEntity entity = response.getEntity(); is = entity.getContent(); } return is; } }
复制