带你剖析WebGis的世界奥秘----点和线的世界
前言
昨天写了好久的博文我没保存,今天在来想继续写居然没了,气死人啊这种情况你们见到过没,所以今天重新写,我还是切换到了HTML格式的书写上。废话不多说了,我们现在就进入主题,上周我仔细研究了WebGis基于openlayers的显示问题,同事也略微的实现了地图上的点击事件当然啦,这周我们将细分为点和线的点击事件,如果读者有兴趣也可以自己研究区域的点击事件,说白了就是我们初中时候学的那个三维立体的思想,或者读者也可以私下找我交流。
逻辑思想
(点)在处理这些点击事件中我突然想到一个很常见的思路,就是我通过点击来获取屏幕的坐标,然后在将屏幕的坐标转换成对应地图上的经纬度,拿到经纬度之后去和数据库里面一条一条匹配,最后从数据查到这个点了我就将这个点的信息给输出,如果我没有查到这个点就说明我没有点击到这个点上。但是值得注意的一点就是我们地图在屏幕上是很小的,是存在点击误差的,什么叫点击误差就是我们点击地图上的点我们肉眼上觉得是点击了点,但是实际上我们点击的那个点的坐标和地图上的那个点的坐标是存在一定的误差的,所以我为了实现肉眼上的点击点的事件,我在查找数据库的时候给定了一个我们肉眼默认的误差范围的,也就是说我在数据中并不是真正去查这个点,而是查询数据库中的点到我这个店的距离的,只要这个距离小于我指定的误差值,我就默认为这两个点是同一个点,这也就是我实现点的思路;那么问题来了如果我在地图上点击的时候出现了两个点都和我这个点的距离在我的误差范围之内呢,答案是肯定会的,那么有的读者会问,这个该怎么办呢。不用担心我有解决方法。在取到不止两个的情况下我们去距离最小的那一个,离的最近的我们默认选择这个点的(线)处理完点自然就是线了,在处理线的时候我一开始的思路就是拿角度去比对,如果是统一角度的就说明我们点击的点在这条线上面,如果不是统一角度就不在这条线上,这个方法后来我实行了,当然这个也是存在误差的,我指定好了误差之后就可行的,但是在性能上不好,没有在距离的方法有效,因为如果我们用角度的话,角度会根据大小的不同误差级别,也就是说角度本身就有误差,在加上我们的肉眼的误差这样就相当于放大了误差,所以最后在项目里我放弃了角度的方法,后来我换了一种思路,我可以求点到直线的距离啊,这样我就成功的将角度的问题转换成了上面的距离的问题了,而且高中我们都会用点到直线的距离公式,而且俩个点确定直线的方程我们也都会使用最后整理了一下就是下面的代码来实现球点到直线的距离,拿到了距离了就回归了我们上面的点的处理中了,我们只要指定误差范围就可以了。(总结)理论呢就是这么多,估计有的同学已经开始着急了,怎么还没有代码啊,个人觉得!授人以渔不如授人以渔。所以我在上面才唠叨半天,只要你们理解我的思路,下面的代码只需要有初中的水平就可以看得懂了。
/*续上*/
代码实现
<span style="font-size:24px;"><span style="font-size:18px;">1、在map地图上我们注册点击事件,唯一不同的是我们在这里自行区分点线</span> map.events.register("click", map, function(e) { //编写点击事件 }</span>
2、获取屏幕坐标从而转向地理坐标
var lonlat = map.getLonLatFromPixel(e.xy);3、通过地理坐标去和已有的数据进行匹配查询,这里说的已有数据是说数据库中已经存在的点和线的数据,我现在js模拟一下数据库的写法(data就是数据里的数据)
if(data.length>1){ var min=getDis(data[0].x, data[0].y, data[1].x, data[1].y, lonlat.lon, lonlat.lat); var index=0; for(var i=0;i<data.length;i++){ <span style="font-family:SimHei;"> </span>if(i<data.length-1){ var fx = data[i].x; var fy = data[i].y; var tx = data[i+1].x; var ty = data[i+1].y; <span style="font-family:SimHei;"> </span>} var dis = getDis(fx, fy, tx, ty, lonlat.lon, lonlat.lat); if(dis<min){ min=dis; index=i; } } var wc = judgediswc(map.getZoom()); if(wc>=min){ //在误差范围内,可以认为是同一线 var lineName = data[index].site_name+">"+data[index+1].site_name; //alert(lineName); $("#qds").val(lineName); }else{ searRailLine(lonlat.lon, lonlat.lat); } }else{ searRailLine(lonlat.lon, lonlat.lat); }4、这里提到的getDis 和 judgediswc 还有searRailLine这几个方法都是自己去写的,他们的作用是分别计算点到直线的距离、点到直线距离和误差的比较、去真实数据库查找线的方法。下面进行这三个方法的讲解4-1、获得点到直线的距离,这个就是我们初高中经常用到的点到直线距离公式,自己稍加推到就可以看得懂了,这个没有什么技术含量,只有别把坐标位置放错了,就可以了,这个返回的dis就是我们需要的距离。这个算法我在数据里也封装好了,待会最后我会帖进来的。
function getDis(fx,fy,tx,ty,lon,lat){ var dis = 637800.138 * ( Math.abs( ( (ty - fy) * lon ) + ( (fx - tx) * lat ) + tx * fy - ty * fx ) ) / ( Math.pow( (ty - fy) * (ty - fy) + (fx - tx) * (fx - tx), 0.5 ) ); return dis; }4-2、误差的大小(由于地图的缩放级别不同,我们的误差也不同,这个在前面的理论中我已经解释过了,这个就不多说了)这个就返回我们肉眼能够接受的误差最大值
function judgediswc(level){ var dis=0; var angle=0; if(level==15){ dis=12; angle=42; }else if(level==14){ dis=16; angle=94; }else if(level==13){ dis=52; angle=149; }else if(level==12){ dis=69; angle=403; }else if(level==11){ dis=178; angle=479; }else if(level==10){ dis=294; angle=1093; }else if(level==9){ dis=682; angle=1449; }else if(level==8){ dis=1488; angle=4143; }else if(level==7){ dis=3521; angle=4617; }else if(level==6){ dis=6636; angle=5265; }else if(level==5){ dis=7051; angle=30217; }else if(level==4){ dis=25763; angle=68126; }else if(level==3){ dis=60342; angle=120000; }else if(level==2){ dis=88189; angle=220000; }else if(level==1){ dis=110189; angle=1000000; } return angle; }4-3、下面的方法我们就是真正去数据库查找的,上面的方法没有实际用处,只是为了掩饰给读者的快捷方法(注意我ajax的传参 很重要)
function searRailLine(lon,lat){ var sus = function(json, status) { //alert(); var lineName = json.map[0].station_name+"站"+">"+json.map[0].next_name+"站"; $("#qds").val(lineName); siteJson = json.map; }; AjaxUtil.ajaxCon(path + "/echartMap/selectLineNameStr.json", {lon:lon,lat:lat,angle:judgediswc(map.getZoom())}, sus); }5、具体的三层架构实现我就不写了,相信能看到这里的孩子们三层架构一定都了解。我直接贴sql语句(x,y,z分别是经度、纬度、地图的显示级别)方法中返回的是点到直线的最小距离(有可能有很多个直线都是符合的我们取最小的那个)
<!-- 点在线上的方法啊 --> <select id="selectLine" resultType="java.util.HashMap"> SELECT line_name, station_name, station_x, station_y, next_name, next_x, next_y, t FROM ( SELECT line_name, station_name, station_x, station_y, next_name, next_x, next_y, 637800.138 * ( ABS( ( (station_y - next_y) * #{x} ) + ( (next_x - station_x) * #{y} ) + station_x * next_y - station_y * next_x ) ) / ( POWER( (station_y - next_y) * (station_y - next_y) + (next_x - station_x) * (next_x - station_x), 0.5 ) ) AS t FROM rail_line ) AS t WHERE t < #{angle} AND station_y >= #{y} AND next_y <= #{y} order by t asc limit 0,1 </select>
6、有的读者会问了,说好的点和线呢,你的点呢,别着急啊,我先把难的讲完简单,点呢我就把sql语句贴出来,返回了距离的值,在后台你想怎么办就怎么办,那就看你自己了。对吧
<!-- 获取站名 --> <select id="selectPosition" resultType="String" parameterType="HashMap"> <![CDATA[ SELECT t.name FROM ( SELECT x.name, ROUND( 6378.138 * 2 * ASIN( SQRT( POW( SIN( (#{y} * PI() / 180 - y * PI() / 180) / 2 ), 2 ) + COS( #{y} * PI() / 180) * COS(y * PI() / 180 ) * POW( SIN( ( #{x} * PI() / 180 - x * PI() / 180 ) / 2 ), 2 ) ) ) * 1000 ) AS dis FROM (select SITE_NAME name ,fx x,fy y from TKYZ_BASE UNION select SITE_NAME name,fx x,fy y from THYZ_BASE UNION select SITE_NAME name,fx x,fy y from TKHYZ_BASE UNION select SITE_NAME name,fx x,fy y from TYXHR_BASE UNION select SITE_NAME name,fx x,fy y from TBZQD_BASE)x ) AS t WHERE t.dis BETWEEN 0 AND #{z} order by t.dis asc limit 1 ]]> </select>
效果贴图
1、途中高亮显示的是模拟的js中的数据,点击高亮部分就会在脚本中查询我模拟的数据
2、点击高亮线路(北京--》北京南--》丰台)跳出相应的线路信息
3、点击非高亮地区会去查询数据库中的数据,就是真正的数据,如果点击在线上了也会弹出线的详细信息
4、如果我们点击在点上面就会 是点的相应信息,就不贴出来了,在高亮的线上我留个bug就是在线的延长线上也是认为在线上的,实际上不应该认为是线上,这点由于时间紧急我当时就没有写,读者有兴趣自己补充,提示一下判断点的位置是否在线的两端的中间就可以了,这个功能在点击非高亮线的sql语句我实现了的,可以参考一下。
总结
研究了openlayers也有两周了,这两周学到的东西很多。学东西一定要从底层学,才可以学到真正的东西。openlayers的几个基本功能我都已经实现了(地图显示+点线点击事件+定位点+定位线+居中显示墨点+手型显示),其他一些实时定位并随地理位置变动而变动的这个功能没有去实现,因为就两周时间,也就这样了,之间走了很多的弯路,所以发出博文希望对学习WebGis的新同学有点帮助