前言
项目中需要用到地图,百度地图加载自定义底图是通过切片的方式加载,而不像高德地图直接加载一张完整图片,这里瓦片加载的好处得到体现:不会因为底图文件过大导致页面加载失败或假死。
另一方面,由于自己私下学习过百度地图API的用法,对百度地图更为熟悉,故最终采用了百度地图而非高德地图。
百度底图API网址:
/map/index.php?title=jspopular
百度地图Demo网址:
/map/jsdemo.htm#a1_2
百度地图加载自定义底图
我个人比较习惯先总览一下API了解某技术有哪些功能,再通过研习demo体会对应功能的用法,感觉这样学习更实用,更快点。
想要加载底图,首先要申请自己的APP密钥,API中第一步有详解,这里不赘述。直接上例子。
原demo地址:/map/jsdemo.htm#g0_1
以下为源码:
<!DOCTYPE html><html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><meta name="viewport" content="initial-scale=1.0, user-scalable=no" /><style type="text/css">body, html {width: 100%;height: 100%;margin:0;font-family:"微软雅黑";}#allmap{width:100%;height:500px;}#r-result{width:100%;margin-top:5px;}p{margin:5px; font-size:14px;}</style><script type="text/javascript" src="http://api./api?v=2.0&ak=您的密钥"></script><title>叠加魔兽地图</title></head><body><div id="allmap"></div><div id="r-result"><input type="button" onclick="add_control();" value="添加" /><input type="button" onclick="delete_control();" value="删除" /></div></body></html><script type="text/javascript">// 百度地图API功能var map = new BMap.Map('allmap');map.addControl(new BMap.NavigationControl({anchor: BMAP_ANCHOR_TOP_RIGHT, type: BMAP_NAVIGATION_CONTROL_SMALL}));map.centerAndZoom(new BMap.Point(0, 0), 3);var tileLayer = new BMap.TileLayer();tileLayer.getTilesUrl = function(tileCoord, zoom) {var x = tileCoord.x;var y = tileCoord.y;var url = '/map/jsdemo/demo/tiles/' + zoom + '/tile' + x + '_' + y + '.png';//根据当前坐标,选取合适的瓦片图return url;}function add_control(){map.addTileLayer(tileLayer);}function delete_control(){map.removeTileLayer(tileLayer);}add_control();</script>
有点js基本功的话,不难看出,地图的瓦片是通过拼接底图瓦片的文件名加载的,因此我们需要将自己的底图进行切片,并按规则命名图片。
切片暂未找到较好工具,只在网络上找到一段代码(感谢源代码作者,不过不记得在哪找到的了,这里先说声抱歉),虽然不够完善,但暂时基本够用,代码有个不足是:
1、不能切很大的图片文件;
2、切较大的图片文件切片有点慢,代码作者也说了原因是由于切片使用单线程,可以进一步优化改成多线程提高效率。
由于项目急用,暂时也还没改,我会抽空试试。
以下是我个人测试用的代码:
package com.haycm.service;import java.io.*;import java.awt.*;import java.awt.image.*;import java.awt.Graphics;import java.awt.color.ColorSpace;import javax.imageio.ImageIO;public class ImageCut {public static void main(String[] args) {cut("E:\\world.jpg", "E:\\", 300, 300);}/*** 图像切割** @param srcImagePath 源图像地址* @param destDir切片目标文件夹* @param destWidth 目标切片宽度* @param destHeight 目标切片高度*/public static void cut(String srcImagePath, String destDir, int destWidth, int destHeight) {try {Image img;ImageFilter cropFilter;// 读取源图像BufferedImage bi = ImageIO.read(new File(srcImagePath));int srcWidth = bi.getHeight(); // 源图宽度int srcHeight = bi.getWidth(); // 源图高度if (srcWidth > destWidth && srcHeight > destHeight) {Image image = bi.getScaledInstance(srcWidth, srcHeight, Image.SCALE_DEFAULT);//destWidth = 200; // 切片宽度//destHeight = 200; // 切片高度int cols = 0; // 切片横向数量int rows = 0; // 切片纵向数量// 计算切片的横向和纵向数量if (srcWidth % destWidth == 0) {cols = srcWidth / destWidth;} else {cols = (int) Math.floor(srcWidth / destWidth) + 1;}if (srcHeight % destHeight == 0) {rows = srcHeight / destHeight;} else {rows = (int) Math.floor(srcHeight / destHeight) + 1;}// 循环建立切片// 改进的想法:是否可用多线程加快切割速度for (int y = 0; y < rows; y++) {for (int x = 0; x < cols; x++) {// 四个参数分别为图像起点坐标和宽高// 即: CropImageFilter(int x,int y,int width,int height)cropFilter = new CropImageFilter(x * destWidth, y * destHeight, destWidth, destHeight);img = Toolkit.getDefaultToolkit().createImage(new FilteredImageSource(image.getSource(),cropFilter));BufferedImage tag = new BufferedImage(destWidth,destHeight, BufferedImage.TYPE_INT_RGB);Graphics g = tag.getGraphics();g.drawImage(img, 0, 0, null); // 绘制缩小后的图g.dispose();// 输出为文件if (!new File(destDir).exists())new File(destDir).mkdirs();ImageIO.write(tag, "JPEG", new File(destDir + "map_-" + (y) + "_" + (x) + ".jpg"));}}System.out.println("image cut finished ");}} catch (Exception e) {e.printStackTrace();}}/*** 缩放图像** @param srcImageFile 源图像文件地址* @param result 缩放后的图像地址* @param scale 缩放比例* @param flag 缩放选择:true 放大; false 缩小;*/public static void scale(String srcImageFile, String result, int scale,boolean flag) {try {BufferedImage src = ImageIO.read(new File(srcImageFile)); // 读入文件int width = src.getWidth(); // 得到源图宽int height = src.getHeight(); // 得到源图长if (flag) {// 放大width = width * scale;height = height * scale;} else {// 缩小width = width / scale;height = height / scale;}Image image = src.getScaledInstance(width, height,Image.SCALE_DEFAULT);BufferedImage tag = new BufferedImage(width, height,BufferedImage.TYPE_INT_RGB);Graphics g = tag.getGraphics();g.drawImage(image, 0, 0, null); // 绘制缩小后的图g.dispose();ImageIO.write(tag, "JPEG", new File(result));// 输出到文件流} catch (IOException e) {e.printStackTrace();}}/*** 图像类型转换* GIF->JPG GIF->PNG PNG->JPG PNG->GIF(X)*/public static void convert(String source, String result) {try {File f = new File(source);f.canRead();f.canWrite();BufferedImage src = ImageIO.read(f);ImageIO.write(src, "JPG", new File(result));} catch (Exception e) {e.printStackTrace();}}/*** 彩色转为黑白** @param source* @param result*/public static void gray(String source, String result) {try {BufferedImage src = ImageIO.read(new File(source));ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);ColorConvertOp op = new ColorConvertOp(cs, null);src = op.filter(src, null);ImageIO.write(src, "JPEG", new File(result));} catch (IOException e) {e.printStackTrace();}}}
这里要注意的是,要根据自己需要,定义图片文件的命名规律,否则切出来的图片名称在百度地图中加载顺序有错,不能拼接为完整图片。
(这里也可以不用关心切图文件的命名规则,只需要在百度地图中加载图片的时候修改加载图片的文件名规律其实也可以的,只是我觉得切图的时候确定命名规则更好理解一点)
先上传一张原图,在通过切图结果比较命名规律和预览效果。
以下为完整原图:
以下为切片后文件夹预览截图:
由此可见,只要命名规则定义好,切出来的图片和原图是没多大差别的,这时就可以用于百度地图中作为底图了。
以下为我的百度地图测试代码:
<!DOCTYPE html><html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"/><meta name="viewport" content="initial-scale=1.0, user-scalable=no"/><style type="text/css">body, html {width: 100%;height: 100%;margin: 0;font-family: "微软雅黑";}#allmap {width: 100%;height: 100%;}#r-result {width: 100%;margin-top: 5px;}p {margin: 5px;font-size: 14px;}</style><script type="text/javascript" src="http://api./api?v=2.0&ak=您的密钥"></script><title>叠加底图</title></head><body><div id="allmap"></div><div id="r-result"><input type="button" onclick="add_control();" value="添加"/><input type="button" onclick="delete_control();" value="删除"/></div></body></html><script type="text/javascript">var sValue = 1;// 百度地图API功能var map = new BMap.Map('allmap');// map.enableScrollWheelZoom(false);map.addControl(new BMap.NavigationControl({anchor: BMAP_ANCHOR_TOP_RIGHT, type: BMAP_NAVIGATION_CONTROL_SMALL}));map.centerAndZoom(new BMap.Point(0, 0), 8);var tileLayer = new BMap.TileLayer();tileLayer.getTilesUrl = function (tileCoord, zoom) {var x = tileCoord.x;var y = tileCoord.y;console.log("x: " + x + "---" + "y:" + y + "---->");var url = '../../image/sg/map_' + (y) + '_' + (x) + '.jpg'; //根据当前坐标,选取合适的瓦片图return url;};function add_control() {map.addTileLayer(tileLayer);}function delete_control() {map.removeTileLayer(tileLayer);}add_control();</script>
这样就可以通过百度地图加载自定义底图了。
效果预览:
但是这样加载的底图是不能缩放的,或者说,缩放地图的时候,虽然百度地图的比例尺缩放了,但是自定义的底图并没有缩放,还是显示的原来大小,项目中暂时还没有用到缩放,具体还没研究。
我的思路是:根据不同比例进行切图,切出多份瓦片图,当加载时通过“map_比例尺+x坐标+y坐标.png”的方式来加载图片,这样一来就可以实现不同比例尺加载不同比例瓦片的效果了。
高德地图加载底图简介
高德加载底图也有利有弊,它是直接加载一张完整图片,并且可以根据比例尺缩放,省去了切图的过程,不足是当底图很大(比如我项目用到一个20MB的底图)的时候,页面会加载很慢,甚至可能假死,这肯定是我们不愿意看到的,这里我只贴以下我用高德地图的测试代码(其实可以直接参考官网demo,换一下图片地址就好,用很大的图片尝试一下)
<!DOCTYPE html><html><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width"><title>叠加图片图层</title><link rel="stylesheet" href="/lbs/static/main.css"/><script src="./amap.js"></script><style>.marker {color: #ff6600;padding: 4px 10px;border: 1px solid #fff;white-space: nowrap;font-size: 12px;font-family: "";background-color: #0066ff;}</style></head><body><div id="mapContainer"></div><script>var imageLayer = new AMap.ImageLayer({url: '111.jpg',// url: '../../image/sanguo/world.jpg',bounds: new AMap.Bounds([116.327911, 39.939229],[116.342659, 39.946275]),zooms: [15, 18]});var map = new AMap.Map('mapContainer', {scrollWheel: true,center: [116.33910, 39.945084],zoom: 18,layers: [new AMap.TileLayer(),imageLayer]});var _onClick = function (e) {new AMap.Marker({position: e.lnglat,map: map});console.log(e.lnglat);};AMap.event.addListener(map, "click", _onClick); //绑定事件,返回监听对象</script></body></html>
下面是加载时间对比图:
通过chrome的devTools不难看出,这种加载方式加载较大文件的底图时较慢,相比于其它的几百毫秒,大图片加载花费了1.74S的时间。
我试过多次,这还是通过本地文件方式加载的,如果是网络环境的话,可想而知一个页面加载40MB的图片需要多久,这显然不是我们想要的。
不过综合高德和百度的利弊各自选择吧。
说明
最后,记下博文TODO:
1、修改切片程序,解决切图慢的问题和大图片切片时内存不足问题;
2、继续研究百度地图的比例尺缩放