我们经常使用地图查位置、看公交、看街景,同时地图还开放第三方的API给开发者。利用这些API进行地图的个性化的展示和控制,例如北京被水淹了,开发一个网页显示北京被淹的地图,地图上面标志被水淹的位置、严重程度,或者我是交警,想要在地图上标志发生车祸、被交通管制的路段,甚至是利用地图的街景,控制街景的位置变化做一个tour show动画。因为地图本身就是一个比较好玩的东西,再加上一些个性化的控制会更加的有趣。

常有的地图有谷歌、百度、必应等,这些都有提供api,下面以谷歌地图为例做说明。虽然谷歌被墙,但是谷歌有一个中国域名版本的,没有被墙,可以自由访问:http://www.google.cn/maps,估计很多人都不知道。首先来看下谷歌地图是怎么显示在页面的

谷歌地图的组成

只要做一下元素审查,就可以发现谷歌地图的主体部份是用一张张的图片拼成的,只要缩放比例或者位置一改变,就会再去请求新的图片,也就是说地图的渲染是在后端进行的,后端把图片生成好发给前端,之所以没放在前端绘制主要应该是考虑了客户端的性能和兼容性。左上角和右下角的控制也是用div absolute定位上去的。

谷歌地图的使用

引入谷歌地图

首先加载地图的api,你可以指定所用语言,如果没指定,地图将根据浏览器的语言(可通过请求的http头的Accept-Language字段)自动选用语言。还可以指定谷歌地图的版本,现在最新版是ver=3.25,还可以加上一些指定的地图的lib。必填的参数是key,如果没有key去谷歌地图的开发者页面申请一个即可。大陆版的跟正常版的在使用上目测没什么区别

<script src="http://ditu.google.cn/maps/api/js?key=AIzaSyBp&language=zh-CN"></script> <!-- 中国版 -->
<!--正常版,需翻墙 <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBp"></script> -->

然后在页面写一个div,作为地图的容器,指定地图的宽高

<div id="map" style="width:100%;height:500px"></div>

初始化谷歌地图,最主要的两个参数是传一个中心点和缩放倍数,如果你点地图右下角的+号,就会再放大一倍,这里的放大倍数就指这个

var mapType = google.maps.MapTypeId.ROADMAP;
var lat = 39.915168, lng = 116.403875, zoom = 10;
var mapOptions = {
center: new google.maps.LatLng(lat, lng), //地图的中心点
zoom: zoom,           //地图缩放比例
mapTypeId: mapType,           //指定地图展示类型:卫星图像、普通道路
scrollwheel: true           //是否允许滚轮滑动进行缩放
};
var map = new google.maps.Map(document.getElementById("map"), mapOptions); //创建谷歌地图

这样在你的页面就有一个以天安门为中心的地图了

接下来,给天安门添加一个地理标志,使用谷歌自带的marker

添加一个Marker

通过上面代码的new我们已经有了个map对象,然后再创建一个marker对象,把这个marker绑定到map上

var marker = new google.maps.Marker({
map: map,
position: new google.maps.LatLng(lat, lng)
});

在地图上就可以看到天安门上标记了一个地理位置图标

接下来希望点一下这个marker的时候就显示这个位置的具体地址,如下所示,首先创建一个InfoWindow,并把它绑定到上面的那个marker的上方展示,同时给marker添加一个点击事件,一点的时候就打开提示框:

var infowindow = new google.maps.InfoWindow({content: "北京市天安门" }); //创建一个InfoWindow
infowindow.open(map, marker); //把这个infoWindow绑定在选定的marker上面
//使用谷歌地图定义的事件,给这个marker添加点击事件
google.maps.event.addListener(marker, "click", function(){
infowindow.open(map,marker);
});

效果如下:

这里所有的样式都是谷歌自带的,假设这个marker的样式跟网站的风格不太一致,我想要自定义一个marker不用谷歌自带的,那怎么办呢?在上面new一个marker的时候可以再传一个icon的参数,自定义icon,同时这个icon需要使用svg的格式。

在PSD里面将UI里面的icon形状导成一个AI文件,然后再用AI导出svg,就有了icon的svg格式。打开svg文件,将里面的path、fill等作为地图icon的参数,如下:

var locationMarker = {
path: 'M22 10.5c0 .895-.13 1.76-.35 2.588C20.025 20.723 13.137 28.032 11 28 9.05 28 3.2 21.28.926 14.71.334 13.42 0 11.997 0 10.5c0-.104.013-.206.017-.31C.014 10.117 0 10.04 0 9.967c-.005-.67.065-1.112.194-1.398C1.144 3.692 5.617 0 11 0c5.416 0 9.906 3.74 10.82 8.657.112.29.18.696.18 1.31 0 .083-.013.167-.015.25.003.095.015.188.015.283zM11 5.833c-2.705 0-4.898 2.09-4.898 4.667S8.295 15.167 11 15.167s4.898-2.09 4.898-4.667c0-2.578-2.193-4.667-4.898-4.667z',
fillColor: '#E84643',
fillOpacity: 1,
strokeColor: '#E84643',
};
var marker = new google.maps.Marker({map: map, icon: locationMarker, position: new google.maps.LatLng(lat, lng)});

就可以将默认的marker样式换掉,如下所示,你也可以换成其它各种各样的形状,像房子、车等icon

检查一下刚刚添加的marker,发现最后被谷歌地图转换成了一个canvas元素:

到这里已经可以解决在地图显示北京哪里被水淹了的问题。就是在被水淹的位置添加一个个的marker,现在我希望点击marker的时候能够显示该处的图文受灾情况。可以使用上面介绍的InfoWindow,只要把参数{content: "北京市天安门" },换成{content: "<div class='detail-info'>...</div>"},然后写detail-info的样式即可。但是这样会有两个问题:

1. 不方便改变InfoWindow那个框的样式,例如没有一个直接的方法可以去掉右上角的x按钮

2. 假设有几百个地方被水淹了,也就是说得添加几百个marker,同时每个marker都得添加一个click事件,因为谷歌的事件没有marker事件委托,每个marker都得一个个加事件,一下子加几百个事件,这样就有点egg pain了。

所以说如果使用了上面的marker的方式,谷歌把它变成了一个canvas,以后所有的操作都得处处受制于谷歌的API,同时谷歌的API并不是十分的丰富和灵活。因此必须得另辟一条路,如果能够用原生的div放到谷歌地图里面那就简单多了,因为地图本身就是用div实现的,所以用原生的应该是可以的。其实只要用谷歌搜一搜就可以找到解决办法

使用原生HTML Marker

谷歌地图还提供了另外一个往地图里面加东西的OverlayView,使用这个的原理就是创建一个OverlayView对象然后给它append一个div,把这个div的position置为absolute(相对于地图的容器container),然后再设置它在这个容器的left/top位置,关键就在于怎样根据当前marker的经纬度转换为在容器的像素位置。而这个对象已经提供了一个转换方法可以调用。将自定义的Marker封装成一个类,例如现在要做一个房源的地图展示,需要把房源标在地图上,自定义一个HouseMarker的类:

function HouseMarker(latlng, map, args) {
  this.latlng = latlng; //for google map
  this.setMap(map); //for google map
  this.args = args; //自定义参数
}

再将这个HouseMarker的原型指向谷歌的OverlayView进行继承

HouseMarker.prototype = new google.maps.OverlayView();

然后实现这个原型的draw函数:

HouseMarker.prototype.draw = function() {
//创建一个div,把marker和详情框写在一起,方便后面的展示和隐藏
$div = $("<div class='marker-container'>" +
        "  <div class="marker"></div>" +
        "  <div class='detail-info'"></div>" +
        "</div>");
//将div添加到它的dom元素里面
var panes = this.getPanes();
var div = $div[0];
panes.overlayImage.appendChild(div);
//根据经纬度计算div的像素位置
var point = this.getProjection().fromLatLngToDivPixel(this.latlng);
div.style.left = (point.x - 20) + 'px'; //减掉marker宽度的一半,居中
div.style.top = (point.y - 20) + 'px'; //减掉marker高度的一半,居中
};

再调new HouseMarker,传进当前的经纬度和map对象,就可以在地图正确的位置上显示这个marker了。接下来就能够使用原生的js事件和css控制这个marker了,这样就很方便灵活了。特别是谷歌的mouse事件,即使是上一个marker盖住了下面的marker,鼠标移到上面那个marker时,仍然会触发下面那个marker的事件,这样就有点恶心了。而使用原生的mouse事件就没有这种情况。其实这个也是可以理解的,因为谷歌地图是用的一个canvas画布展示marker,在这个画布里面只根据鼠标的位置和marker的位置判断鼠标有没有进入marker里面,所以不管上面有没有被盖住,只要算出来的位置是符合的。

如下面所示,鼠标hover的时候就显示详细信息,如果这个详细信息刚好下面有个marker就会出现上面讨论的情况:

详见:Custom HTML Markers with Google Maps

第二步是的鼠标hover的时候展示详情框,最简单的就是用CSS控制即可,使用上面定义的DOM结构,初始化时让detail-info隐藏:

.marker-container .detail-info{
display: none
}

然后再设置:

.marker-container:hover .detail-info{
display: block
}

就可以了,不用一行JS

第二种办法是监听mouse事件,使用事件委托:

$("#map").on("mouseover", ".marker-container", function(){
$(this).find(".detail-info").show();
}); $("#map").on("mouseout", ".marker-container", function(){
$(this).find(".detail-info").hide();
});

用JS的进行显示和隐藏的好处是:可以对展示做一些后续的处理,这也是下面要提到的

我们已经初步解决了marker展示的问题,但其实还有一些问题:展示这些详细信息会出现超出可见区域的情况

边界判断

当这个marker比较靠边的时候,详情的框会超出显示范围:

所以需要做边界判断,不管marker在什么位置,详情框都可以在展示区域内显示,效果如下:

也就是说需要判断当前marker是否超出了地图容器能够正常显示的范围,如果超出了就要做下处理——如果太靠上就把详情展示在下面,如果太靠右详情框就不应该是和marker水平居中了,而是要往左移一移,同时把三角形的位置挪一挪。所以关键是要做一个边界判断,而做边界判断的前提是拿到marker在容器里面的left/top位置。

已经不可以再上使用上面获取位置的方法了,因为那个位置算好之后不会再变,不会跟着地图的拖动而发化变化,谷歌地图是借助transform等设置改变它的位置,而不是用position了。

但是可以拿到当前地图在这个容器里面的边界经纬度,最东、最西、最北、最南,也可以拿到这个容器的像素宽高,所以就可以知道一个像素对应地图多少经纬度,即像素/经纬度的比例ratio。同时marker的纬度是知道的,可以算一下它距离边界的经纬度dx, dy,dx除以ratio就能够换算像素值了。代码如下:

var mapBounds = mapHandler.getBounds();      //调用谷歌的api获取容器经纬度边界并做一些处理
var xRatio = (mapBounds[1] - mapBounds[0]) / mapWidth,
yRatio = (mapBounds[3] - mapBounds[2]) / mapHeight;
//marker的经纬度
var lat = marker.latlng.lat(),
lng = marker.latlng.lng();
//转换marker的像素位置
var pos = {
top: -(+lat - mapBounds[3]) / yRatio,
left: (+lng - mapBounds[0]) / xRatio,
bottom: (+lat - mapBounds[2]) / yRatio,
right: -(+lng - mapBounds[1]) / xRatio
};
var posFlag = 0,
maxLen = 150,
maxLeftLen = 118;
//右边超出
if(pos.right < maxLen) posFlag |= 1;
//上面超出
if(pos.top < maxLen) posFlag |= 2;
//左边超出
if(pos.left < maxLeftLen) posFlag |= 4;
//对超出的情况进行处理,代码略
switch(posFlag){
case 1: //右
case 2: //上
case 3: //右上
case 4: //左
case 6: //左上
}

还有一种情况是如果详情框太长了,超出了容器的一半,不管向上显示还是向下显示,marker刚好在正中间时,详情框都会超出显示范围。这种情况可以借助第二种解决办法,就是将地图移动一下,超出的就可以显示了。需要计算移动后的地图中心点在哪里,再调API提供的panTo就可以了,如下。难点是计算要正确

绘制形状

接下来再简单讨论一个高级话题,就是在谷歌地图上面绘制一个形状,然后获取该形状的地理位置。谷歌已经提供了一个叫DrawingManager的类,只要new一个对象,传些参数,就可以在地图上显示draw tool了,如下:

然后再监听这个manager的complete事件,在complete事件里面获取当前画的图形的范围,例如上面的圆可以获取到它的圆心和半径。详见:Drawing Layer (Library)

然而谷歌提供的这个工具非常的简陋,你无法直接改变上面工具栏的icon,就连画的圆边界也是扭扭曲曲的,如上所示。只提供了完成事件,没有画时候的事件,所以你没办法在画的时候加一个不断变化的、显示所画范围多少公里的提示框。

因此另外一个解决办法是自已实现一个类似的工具,通过鼠标的mousedown、mousemove、mouseup事件搭配组合,结合上面推荐的画marker的方法,插入svg元素,动态改变它的path做到实时变化的效果。这样就很灵活了,想怎么搞就怎么搞,但是代码量应该也是挺大的。

除此之外还有街景、3D控制的API,这里不再讨论,有兴趣自己查查谷歌的API。

个人博客: http://yincheng.site/google-map ‎

Google地图开发总结的更多相关文章

  1. Google地图开发

    配置Google API SDK 如果要想进行Google Map或者说是定位服务的开发,那么肯定需要下载一个新的SDK的支持. 1.点击Android SDK Manager,下载SDK. 2.直接 ...

  2. Google地图

    Google地图开发总结 我们经常使用地图查位置.看公交.看街景,同时地图还开放第三方的API给开发者.利用这些API进行地图的个性化的展示和控制,例如北京被水淹了,开发一个网页显示北京被淹的地图,地 ...

  3. Google 地图 API V3 针对移动设备进行开发

    Google官方教程: Google 地图 API V3 使用入门 Google 地图 API V3 针对移动设备进行开发 Google 地图 API V3 之事件 Google 地图 API V3 ...

  4. 使用QT开发GoogleMap瓦片显示和下载工具(2)——Google地图瓦片投影和获取网址解析

    这篇主要说的是Google地图采用的投影方式,瓦片划分方式以及如何从给定的经纬度获取瓦片的数据的网址.所有资料均来自于网络,并亲自试验可行. Google地图投影 首先是地图投影问题,因为地球本身是一 ...

  5. 《ArcGIS Runtime SDK for Android开发笔记》——(13)、图层扩展方式加载Google地图

    1.前言 http://mt2.google.cn/vt/lyrs=m@225000000&hl=zh-CN&gl=cn&x=420&y=193&z=9& ...

  6. Google 地图 API V3 使用入门

    Google官方教程: Google 地图 API V3 使用入门 Google 地图 API V3 针对移动设备进行开发 Google 地图 API V3 之事件 Google 地图 API V3 ...

  7. Google 地图 API V3 之事件

    Google官方教程: Google 地图 API V3 使用入门 Google 地图 API V3 针对移动设备进行开发 Google 地图 API V3 之事件 Google 地图 API V3 ...

  8. Google 地图 API V3 之控件

    Google官方教程: Google 地图 API V3 使用入门 Google 地图 API V3 针对移动设备进行开发 Google 地图 API V3 之事件 Google 地图 API V3 ...

  9. Google 地图 API V3 之 叠加层

    Google官方教程: Google 地图 API V3 使用入门 Google 地图 API V3 针对移动设备进行开发 Google 地图 API V3 之事件 Google 地图 API V3 ...

随机推荐

  1. Concepts:Request 和 Task

    当SQL Server Engine 接收到Session发出的Request时,SQL Server OS将Request和Task绑定,并为Task分配一个Workder.在TSQL Query执 ...

  2. scp报错 -bash: scp: command not found

    环境:RHEL6.5 使用scp命令报错: [root@oradb23 media]# scp /etc/hosts oradb24:/etc/ -bash: scp: command not fou ...

  3. 集合(set)-Python3

    set 的 remove() 和 discard()  方法介绍. 函数/方法名   等价操作符 说明 所有集合类型 len(s)   集合基数:集合s中元素个数 set([obj])   可变集合工 ...

  4. 记一次.NET代码重构

    好久没写代码了,终于好不容易接到了开发任务,一看时间还挺充足的,我就慢慢整吧,若是遇上赶进度,基本上直接是功能优先,完全不考虑设计.你可以认为我完全没有追求,当身后有鞭子使劲赶的时候,神马设计都是浮云 ...

  5. 关于MJRefresh的下拉加载数据bug

    当没有更多数据的时候显示NoMoreData 我的理解是先结束刷新再显示没有更多 今天之前一直没发现有问题 贴之前的代码 [self.collectionView reloadData]; [self ...

  6. nuget常用命令

    nuget命令的用法: 一.安装 1.安装指定版本类库install-package <程序包名> -version <版本号> 2.安装到指定的项目install-packa ...

  7. WiFi QC 自动测试:ixChariot API初探

    Chariot虽然给我们提供了友好的界面,但是必须使用命令行或者使用它的API才能 实现自动测试.Chariot在安装的时候会让你选择命令行界面组件,在它的安装目录下面有一些工具, 暂时还不知道是干什 ...

  8. MonoTouch 二三事(三)mono mkbundle 打包程序的解包支持

    2014.10.06 更新 编写了 IDA Pro 的插件,用来解包和打包 mkbundle程序,请参见 https://github.com/binsys/MKBundleManager 许久以后, ...

  9. JavaScript进阶之路——认识和使用Promise,重构你的Js代码

    一转眼,这2015年上半年就过去了,差不多一个月没有写博客了,"罪过罪过"啊~~.进入了七月份,也就意味着我们上半年苦逼的单身生活结束了,从此刻起,我们要打起十二分的精神,开始下半 ...

  10. C#对WebApi数据操作

    目标 简化并统一程序获取WebApi对应实体数据的过程,方便对实体进行扩充.原理就是数据服务使用反射发现数据提供者,处理好泛型就行. 相关传送门:Restful WebApi开发实践 先来看下最后的请 ...