分析 Layer Spy 这个 demo,官网介绍是:
Layer rendering can be manipulated in prerender
and postrender
event listeners. These listeners get an event with a reference to the Canvas rendering context. In this example, the prerender listener sets a clipping mask around the most recent mouse position, giving you a spyglass effect for viewing one layer over another. Move around the map to see the effect. Use the ↑ up and ↓ down arrow keys to adjust the spyglass size.
图层渲染是被prerender
和postrender
事件监听器控制的。这些监听器获得一个带有 Canvas 渲染上下文引用的事件。在这个例子中,prerender
事件监听器在最近的鼠标位置处设置一个剪贴蒙版,这个剪贴蒙版能让你用看望远镜的感觉来观察普通地图图层上面的航拍图层。
在地图上移动鼠标来查看效果。使用 ↑ 上键 和 ↓ 下键来调整望远镜的大小。
定义基本结构 在这里应用了必应地图的普通带道路地图(RoadOnDemand)和航拍地图(Aerial),可以发现跟之前地图的source
定义相比,多了一个必填的key
,这个key
是用 bing 地图的开发者账号来申请的,申请链接:http://www.bingmapsportal.com/。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <!DOCTYPE html > <html > <head > <meta charset ="UTF-8" /> <meta name ="viewport" content ="width=device-width, initial-scale=1.0" /> <meta http-equiv ="X-UA-Compatible" content ="ie=edge" /> <link rel ="stylesheet" href ="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.1.1/css/ol.css" type ="text/css" /> <script src ="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.1.1/build/ol.js" > </script > <title > Layer Spy(图层探查)</title > <style > html, body, .map { height: 100%; width: 100%; } </style > </head > <body > <div id ="map" class ="map" > </div > <script type ="text/javascript" > let key = "Your Bing Maps Key from http://www.bingmapsportal.com/ here" ; let roads = new ol.layer.Tile({ source: new ol.source.BingMaps({ key: key, imagerySet: "RoadOnDemand" }) }); let imagery = new ol.layer.Tile({ source: new ol.source.BingMaps({ key: key, imagerySet: "Aerial" }) }); let container = document .getElementById("map" ); let map = new ol.Map({ layers: [roads, imagery], target: container, view: new ol.View({ center: ol.proj.fromLonLat([-109, 46.5]), zoom: 6 }) }); </script > </body > </html >
在map
中设置view
中center
的时候,使用了ol.proj.fromLonLat ,如下图所示,可以把经纬度转化为指定坐标系下的坐标,默认为EPSG:3857
坐标系。
监听鼠标和键盘事件 修改望远镜大小 首先给望远镜设置一个初始大小 75px,然后监听键盘按下(keydown )事件,同时通过被按下键which
属性,也就是keyCode
或者charCode
来判断是哪一个键(which 是一个已经从 web 标准中弃用的事件属性,推荐使用key 或者code 代替)。
在计算radius
(半径)的时候,同时给radius
限制了最大值和最小值。在修改了radius
后重新渲染地图并阻止按键的默认事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 let radius = 75 ;document .addEventListener("keydown" , evt => { if (evt.which === 38 ) { radius = Math .min(radius + 5 , 150 ); map.render(); evt.preventDefault(); } else if (evt.which === 40 ) { radius = Math .max(radius - 5 , 25 ); map.render(); evt.preventDefault(); } });
保存鼠标位置 因为需要让望远镜跟随鼠标移动,所以需要监听鼠标位置的变化。如代码所示,监听mousemove
,同时利用getEventPixel
来获取浏览器事件相对于视区的像素位置,也就是鼠标相对于地图的位置,最后再渲染地图。
1 2 3 4 5 6 7 8 9 10 11 12 13 let mousePosition = null ;container.addEventListener("mousemove" , event => { mousePosition = map.getEventPixel(event); map.render(); }); container.addEventListener("mouseout" , () => { mousePosition = null ; map.render(); });
绘制望远镜 绘制望远镜圆圈的代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 imagery.on("prerender" , event => { let ctx = event.context; let pixelRatio = event.frameState.pixelRatio; ctx.save(); ctx.beginPath(); if (mousePosition) { ctx.arc( mousePosition[0 ] * pixelRatio, mousePosition[1 ] * pixelRatio, radius * pixelRatio, 0 , 2 * Math .PI ); ctx.lineWidth = 5 * pixelRatio; ctx.strokeStyle = "rgba(0,0,0,0.5)" ; ctx.stroke(); } ctx.clip(); }); imagery.on("postrender" , event => { let ctx = event.context; ctx.restore(); });
在imagery
图层上监听事件prerender 和postrender ,prerender
是图层渲染之前触发,postrender
是图层渲染之后触发。
在prerender
事件中,首先拿到pixelRatio
,通过API 可以发现实际上是window.devicePixelRatio ,简单点说,就是一个渲染出来的像素大小与设备像素的大小比值,一般情况下,普通屏幕都是1
,高清屏幕为2
。
再获取上下文context
,熟悉 canvas 绘制的同志们肯定很熟悉。这里需要先save
,然后再开始绘制,绘制完成以后还需要clip
一下,才会把绘制好的圆圈截取出来(关于clip
的作用可以看这里 )。
最后的时候,还需要在postrender
的时候使用restore
重置上下文环境到save
的位置,清空 canvas,为下一次渲染做准备。不断循环渲染就能实现了跟随鼠标的望远镜效果。
全部代码 最后贴上全部代码,key
需要自己申请。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 <!DOCTYPE html > <html > <head > <meta charset ="UTF-8" /> <meta name ="viewport" content ="width=device-width, initial-scale=1.0" /> <meta http-equiv ="X-UA-Compatible" content ="ie=edge" /> <link rel ="stylesheet" href ="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.1.1/css/ol.css" type ="text/css" /> <script src ="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.1.1/build/ol.js" > </script > <title > Layer Spy(图层探查)</title > <style > html, body, .map { height: 100%; width: 100%; } </style > </head > <body > <div id ="map" class ="map" > </div > <script type ="text/javascript" > let key = "Your Bing Maps Key from http://www.bingmapsportal.com/ here" ; let roads = new ol.layer.Tile({ source: new ol.source.BingMaps({ key: key, imagerySet: "RoadOnDemand" }) }); let imagery = new ol.layer.Tile({ source: new ol.source.BingMaps({ key: key, imagerySet: "Aerial" }) }); let container = document .getElementById("map" ); let map = new ol.Map({ layers: [roads, imagery], target: container, view: new ol.View({ center: ol.proj.fromLonLat([-109, 46.5]), zoom: 6 }) }); let radius = 75 ; document .addEventListener("keydown" , evt => { if (evt.which === 38) { radius = Math .min(radius + 5 , 150 ); map.render(); evt.preventDefault(); } else if (evt.which === 40 ) { radius = Math .max(radius - 5 , 25 ); map.render(); evt.preventDefault(); } }); let mousePosition = null ; container.addEventListener("mousemove" , event => { mousePosition = map.getEventPixel(event); map.render(); }); container.addEventListener("mouseout" , () => { mousePosition = null ; map.render(); }); imagery.on("prerender" , event => { let ctx = event.context; let pixelRatio = event.frameState.pixelRatio; ctx.save(); ctx.beginPath(); if (mousePosition) { ctx.arc( mousePosition[0] * pixelRatio, mousePosition[1] * pixelRatio, radius * pixelRatio, 0, 2 * Math .PI ); ctx.lineWidth = 5 * pixelRatio; ctx.strokeStyle = "rgba(0,0,0,0.5)" ; ctx.stroke(); } ctx.clip(); }); imagery.on("postrender" , event => { let ctx = event.context; ctx.restore(); }); </script > </body > </html >