mapboxgl互联网地图纠偏插件(二)
前段时间写的mapboxgl 互联网地图纠偏插件(一)存在地图旋转时瓦片错位的问题。
这次没有再跟 mapboxgl 的变换矩阵较劲,而是另辟蹊径使用 mapboxgl 的自定义图层,重新写了一套加载瓦片的方法来实现地图纠偏。
下面把我这次打怪升级的心路历程分享一下,或许对你也有启发。
文中涉及一些 webgl 的知识细节,没有接触过 webgl 的同学,可以参考看上一次给大家推荐的电子书 《WebGL编程指南》,这次再附上一个包含书中所有示例的 github 库,会很有帮助。 书接上回
在研究偏移矩阵问题一筹莫展时,发现用天地图的栅格瓦片没有偏移的问题,因为天地图是大地2000坐标,可以直接在 wgs84 坐标地图上使用,基本没有误差。
尝试后觉得,可以倒是可以,但就是配色有点丑,可以先作为一个保底方案,高德瓦片的纠偏还要继续研究。
话说《WebGL编程指南》这本书看完后,一直想写个读书笔记,但又觉得光写笔记太枯燥,就想着结合地图看能干点啥。
mapboxgl 通过自定图层接口支持 webgl 的扩展,这个接口的好处是,对复杂的变换矩阵进行了封装,对外使用大家熟悉的 web 墨卡托坐标,并提供了经纬度坐标和 web墨卡托坐标转换的接口 。
查看 mapboxgl 的官方示例时,突然来了灵感,可以用这个接口自己写个加载栅格瓦片的程序,这样就能绕开 mapboxgl 复杂的框架,更容易实现对瓦片纠偏,出现问题也更好解决,对整体更有掌控感。
技术路线分析:
用这个思路来实现纠偏,要搞定两大问题,一个是如何用 webgl 实现显示瓦片的功能,另一个是如何计算瓦片在屏幕上的显示位置。 如何用 webgl 显示瓦片
在 webgl 中,图形的基础是三角形,要绘制正方形的瓦片,需要用两个三角形拼成一个正方形,再把图片贴到这个正方形上,就能实现地图瓦片的显示。这个过程中,图片被称为纹理,贴图被称为纹理贴图。实现效果如下(图片位置是随便写的):
这里有两点要注意:
1、要注意图片的跨域问题,需要通过设置图片的跨域属性来解决。
2、要注意顶点坐标的顺序,正确的顺序为:左上、左下、右上、右下,不然图片会像穿衣服一样,各种穿反,前后反,左右反
核心代码如下: var picLoad = false; var tileLayer = { id: "tileLayer", type: "custom", //添加图层时调用 onAdd: function (map, gl) { var vertexSource = "" + "uniform mat4 u_matrix;" + "attribute vec2 a_pos;" + "attribute vec2 a_TextCoord;" + "varying vec2 v_TextCoord;" + "void main() {" + " gl_Position = u_matrix * vec4(a_pos, 0.0, 1.0);" + " v_TextCoord = a_TextCoord;" + "}"; var fragmentSource = "" + "precision mediump float;" + "uniform sampler2D u_Sampler; " + "varying vec2 v_TextCoord; " + "void main() {" + " gl_FragColor = texture2D(u_Sampler, v_TextCoord);" + "}"; //初始化顶点着色器 var vertexShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vertexShader, vertexSource); gl.compileShader(vertexShader); //初始化片元着色器 var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragmentShader, fragmentSource); gl.compileShader(fragmentShader); //初始化着色器程序 var program = this.program = gl.createProgram(); gl.attachShader(this.program, vertexShader); gl.attachShader(this.program, fragmentShader); gl.linkProgram(this.program); //获取顶点位置变量 var a_Pos = gl.getAttribLocation(this.program, "a_pos"); var a_TextCoord = gl.getAttribLocation(this.program, "a_TextCoord"); //设置图形顶点坐标 var leftTop = mapboxgl.MercatorCoordinate.fromLngLat({lng: 110,lat: 40}); var rightTop = mapboxgl.MercatorCoordinate.fromLngLat({lng: 120,lat: 40}); var leftBottom = mapboxgl.MercatorCoordinate.fromLngLat({lng: 110,lat: 30}); var rightBottom = mapboxgl.MercatorCoordinate.fromLngLat({lng: 120,lat: 30}); //顶点坐标放入webgl缓冲区中 var attrData = new Float32Array([ leftTop.x, leftTop.y, 0.0, 1.0, leftBottom.x, leftBottom.y, 0.0, 0.0, rightTop.x, rightTop.y, 1.0, 1.0, rightBottom.x, rightBottom.y, 1.0, 0.0 ]) var FSIZE = attrData.BYTES_PER_ELEMENT; this.buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); gl.bufferData(gl.ARRAY_BUFFER, attrData, gl.STATIC_DRAW); //设置从缓冲区获取顶点数据的规则 gl.vertexAttribPointer(a_Pos, 2, gl.FLOAT, false, FSIZE * 4, 0); gl.vertexAttribPointer(a_TextCoord, 2, gl.FLOAT, false, FSIZE * 4, FSIZE * 2); //激活顶点数据缓冲区 gl.enableVertexAttribArray(a_Pos); gl.enableVertexAttribArray(a_TextCoord); var _this = this; var img = this.img = new Image(); img.onload = () => { // 创建纹理对象 _this.texture = gl.createTexture(); //向target绑定纹理对象 gl.bindTexture(gl.TEXTURE_2D, _this.texture); //对纹理进行Y轴反转 gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); //配置纹理图像 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, this.img); picLoad = true; }; img.crossOrigin = true; //设置允许跨域 img.src = "http://webrd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x=843&y=386&z=10"; }, //渲染,地图界面变化时会调用这个方法,会调用若干次(变化时的每一帧都调用) render: function (gl, matrix) { if(picLoad){ //应用着色程序 //必须写到这里,不能写到onAdd中,不然gl中的着色程序可能不是上面写的,会导致下面的变量获取不到 gl.useProgram(this.program); //向target绑定纹理对象 gl.bindTexture(gl.TEXTURE_2D, this.texture); //开启0号纹理单元 gl.activeTexture(gl.TEXTURE0); //配置纹理参数 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.MIRRORED_REPEAT); // 获取纹理的存储位置 var u_Sampler = gl.getUniformLocation(this.program, "u_Sampler"); //将0号纹理传递给着色器 gl.uniform1i(u_Sampler, 0); //给位置变换矩阵赋值 gl.uniformMatrix4fv(gl.getUniformLocation(this.program, "u_matrix"), false, matrix); //绘制图形 gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); } } }; map.on("load", function () { map.addLayer(tileLayer); });
上面是加载一个瓦片,下面看一下如何加载多个瓦片,这个问题看似简单,但对于webgl不熟悉的同学有可能会走弯路,我自己在研究时,就遇到了下面几个问题:
第一个问题:
自定义图层必须要有 onAdd 方法和 render 方法, onadd 方法在加载图层时会被调用一次, render 方法在地图平移、缩放、旋转时会被调用若干次,来实现平滑过渡的效果。
那么问题来了,哪些 webgl 代码应该放在 onadd 中,哪些应该放在 render 中?
下面是 webglfundamentals 网站给出的解释,在这里, onadd 方法就是初始化阶段, render 方法就是渲染阶段。
第二个问题:
顶点坐标是一个瓦片用一个缓冲区,还是所有坐标都放在一个缓冲区中,然后定义规则来取?
答案是,一个瓦片就是一个独立的图形,一个图形对应一套自己的顶点坐标,坐标后面可以跟渲染相关的属性,如颜色、纹理坐标等,多个图形的顶点坐标最好不要放在一起,推荐使用多个缓冲区对象分别存储图形的顶点坐标,这样分开放会更清晰,实现也更简单。
第三个问题:
如何使用多个缓冲区?
webgl 是面向过程式的,平时用惯了面向对象的开发语言,刚接触这个时有点不适应,后来就慢慢熟悉了。
我们可以把 webgl 的想象成一台老式的机械印刷机,它根据模板印刷,一次只能只使用一个模板,如果想要印刷出多个不同的图案,就需要准备多个不同图案的模板,然后在印刷时不断的更换模板。
webgl 中的着色器、缓冲区对象、纹理对象三者的组合就像是这个模板 ,模板和它们都包含了绘制图形的参数。更换模板,就是在更换着色器、缓冲区对象和纹理对象,不同的是,相比印刷机,电脑中切换这些只是一瞬间的事情,时间可以忽略不计。
webgl 在实际工作时就是像上面的印刷机一样在不停的更换模板然后印刷,再更换模板再印刷,直到全部图像绘制完成,整个过程也是一瞬间的事情。
在 webgl 中,"印刷的机器"只有一个,但"模板"你可以创建很多,它的上限取决于你的电脑性能。
我们要做的就是为每一个瓦片创建一个"模板",然后在绘制时动态切换这些"模板"。
上面三个问题搞明白以后,我成功的加载了2个瓦片。效果图:
如何计算瓦片在屏幕上的显示位置
核心还是用的上篇文章中提到的经纬度和瓦片编号互转算法
原理是:先获取当前显示范围四个角的经纬度,再根据互转算法计算出四个角对应的瓦片编号,这样就能统计出当前地图范围所有瓦片的瓦片编号。
然后遍历当前范围内的所有瓦片编号。
遍历时,根据互转算法,将遍历到的每个瓦片编号转为瓦片左上角的经纬度,再用它相邻的右方、下方、右下方3个瓦片的左上角经纬度,组成瓦片的4个顶点坐标。
在这一步加入对顶点坐标的纠偏算法,实现对瓦片的纠偏。
最后再去监听地图改变的事件,当地图发生平移、缩放、旋转时都要重复上面的计算,更新瓦片。
这里遇到个问题:纠偏后也出现了上一篇中边缘空白的情况。于是对上面的算法优化了一下,在获取到当前显示范围的四个角经纬度坐标后,对这4个坐标也进行纠偏,这样问题就解决了。
现在瓦片的地图的框架搭起来了,也能够浏览查看瓦片地图了,这一刻还真有点小兴奋的呢
但和最终想要的效果还有些差距,还有很多细节需要优化 细节优化
1、缓存瓦片
把请求过的瓦片放到存到变量中,这样请求过的瓦片可以避免重复请求,显示速度会更快,体验更好。
2、缓存网格经纬度
统一计算瓦片网格的经纬度并缓存起来,以免每次都进行重复计算。
3、瓦片加载的顺序从中间向四周
现在的顺序是从左到右,有种刷屏的感觉,需要对瓦片编号排一下序,让靠近中间的先加载,靠近边缘的后加载。
4、个别瓦片不显示问题
每次地图范围变换时,为了实现平滑的效果 render 方法会被执行几十次,时间大概在1秒左右,
如果瓦片不能在这期间加载完成,就会被落下,导致不显示。
需要把最后一次执行 render 方法时的 matrix 变换矩阵记录下来,在瓦片加载完成后主动调用 render 方法绘制。
5、影像图注记白底的问题
在加载影像图时,影像和注记是分开的,需要叠加显示,注记层在没有文字的地方是透明的。
但叠加到一起以后注记层在本该是透明的地方却是不透明的白色
原因一,因为在读取纹理像素数据时的配置有问题,要使用 gl.RGBA ,如果使用的是 gl.RGB 丢掉了透明度 A ,就会缺失透明度信息,导致不透明。
原因二,因为在绘制前没有对 webgl 开启阿尔法混合(阿尔法在这里可以理解为透明度),在 webgl 中如果要实现透明效果,这个选项是必须要开启的。
解决后的效果:
6、影像图注记白底的问题还是会偶尔出现
按上一条修改后,白底问题出现的频率明显降低,但偶尔还是会出现。
研究规律,当注记瓦片加载的时间稍长时就会出现,出现后,只要稍稍拖动一下地图就会正常,已经浏览过的区域没有这个问题。
推测,影像和注记是分图层绘制,当个别注记瓦片加载的时间长,去主动调用 render 方法重新绘制时,注记的图层会全部重绘,但影像图层不会绘制,这可能就导致两个图层无法动态的混合。
目前的解决方法是,对于注记图层如果加载慢了,就不主动调用render方法重新绘制了。因为缺一小块注记不影响大局,而且下一步操作时它也会自动变正常。 地图抖动问题
一些列优化完成后,现在地图也纠偏了,旋转时也不再错位了,本来以为程序已经很完美了,但当我叠上项目真实数据后,发现了一个很要命的问题,自定义图层在大比例尺时会出现抖动的问题。
这个问题最开始就注意到了,但没太在意,以为影响不大,但叠加上业务数据后,发现根本没法用,那种感觉就像是,坐到了行驶在乡间小路的拖拉机上, ~ 颠 ~ 颠 ~ 颠 ~ 颠 ~ 颠 ~ 颠 ~ 颠 ~
起初还以为是瓦片编号和经纬度互转导致的问题,后来发现 mapboxgl 官网的自定义图层示例也有这个问题,看来是 mapboxgl 的 bug 无疑了。
帮 mapboxgl 找问题,最终定位在了 render 方法的 matrix 变换矩阵上,这个参数是 mapboxgl 传来的,用于将 web 墨卡托坐标转为 webgl 坐标,并对瓦片进行缩放和旋转。
当只对地图进行微小的平移时,地图会动, matrix 矩阵却没有变, matrix 矩阵不变,自定义图层也就不会变,当地图平移的范围加大时,matrix矩阵才会跟着变。
翻看 mapboxgl 的源码,自定义图层和底图用的不是一个变换矩阵,所以只有自定义图层有问题。
尝试了 mapboxgl 的最新版本 v2.3.1 也有这个问题。
唉! 本来以为纠偏这事儿要翻篇儿了,这么看来还要再研究一阵子了。 启发、思路、感受
在使用自定义图层的过程中有了一些启发,上篇文章中纠偏写在了变换矩阵中,这种写法在地图旋转时会出现瓦片错位的问题。
本篇文章中纠偏是对 a_pos 变量 web 墨卡托坐标进行纠偏,在旋转时就没有出现错位的情况。
按这个思路,是不是在上篇文章中,也对 a_pos 变量纠偏,地图旋转时就不会出现错位问题了?值得一试。
所以,接下来两个思路:一、研究如何提高自定义图层变换矩阵的精度,让它不再抖动。二、研究如何对 mapboxgl 源码中的 a_pos 变量进行纠偏。
最后说一下使用 mapboxgl 自定义图层的感受,使用 mapboxgl 自定义图层 + webgl 扩展,就感觉打开了GIS世界的另一扇窗户,自己可以去实现各种炫酷高大上的功能了,感觉有了无限可能。 代码、示例
在线示例:http://gisarmory.xyz/blog/index.html?demo=mapboxglMapCorrection2
插件代码:http://gisarmory.xyz/blog/index.html?source=mapboxglMapCorrection2 总结这次尝试用 maboxgl 的自定义图层功能,自己写了一个加载互联网瓦片的程序,来实现瓦片纠偏 自己写加载瓦片的程序要搞定两大问题,一个是如何用 webgl 实现显示瓦片的功能,二个是如何计算瓦片在屏幕上的显示位置 webgl 显示瓦片的原理就是绘制个正方形再给正方形贴图片纹理 计算瓦片在屏幕上的显示位置,核心是使用瓦片号和经纬度的互转算法,在这个过程中对瓦片进行纠偏 还要进行一些细节优化,比如瓦片的加载顺序等 最终实现了对高德瓦片进行纠偏,并且旋转时也不会出现错位的情况 但这种方式有个问题,mapboxgl 的 render 方法中传过来的变换矩阵的精度不够,在大比例尺时会出现瓦片抖动的情况,这应该是mapboxgl 的 bug 在使用自定义图层的过程中有了一些启发,接下来两个思路:一、研究如何提高自定义图层变换矩阵的精度。二、研究如何对mapboxgl 源码中的 a_pos 变量进行纠偏。 目前的保底方案是使用天地图的瓦片,高德地图的瓦片还要继续研究。
原文地址:http://gisarmory.xyz/blog/index.html?blog=mapboxglMapCorrection2
关注《GIS兵器库》, 只给你网上搜不到的GIS知识技能。
本文章采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名《GIS兵器库》(包含链接: http://gisarmory.xyz/blog/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。
马赫动力卧薪尝胆为的是决战于未来如今汽车的革新速度,超出多数人的想象。近20年汽车的变化,比过去120年还要精彩。短短十几年,汽车从指针仪表单色屏幕发展到双12。3寸屏幕智能互联,从离合器手动波时代到L2自动驾驶
力拼日产轩逸2021款大众朗逸最高让利3万车优价美,值!作为上汽大众一款性价比出色的紧凑级轿车,朗逸车型自上市以来就颇受消费者欢迎。目前,据编者了解到,北京本地2021款大众朗逸持续热销中,优惠幅度在3万元左右,而2019款车型的优惠幅
家用实力型,省油又经济!广汽本田缤智限时优惠3。6万,值得买吗尽管是一款紧凑型SUV,但广汽本田缤智的销量有目共睹。对于当下的年轻人来说,靓丽的外观和舒适的配置,确实具有一定的杀伤力。目前,据编者了解到,北京地区广汽本田缤智限时优惠3。6万,
富士康真的造车了!前脸酷似小鹏雪铁龙的合体,定位四门轿跑日前,网络上曝光了一组富士康轿车的新车预告图。据悉,新车车身造型融入了跑车元素,预计尺寸与蔚来ET7等纯电中大型车相当,极具科技感。据悉,富士康除了推出轿车外,未来旗下还将推出包括
封闭式前格栅双联屏设计奔驰EQAEQB开启预售,搭载四驱系统近日,据相关媒体报道,奔驰EQA和EQB两款纯电SUV现已开启预售。据悉,EQA3004MATIC预售价为37万元,EQB3504MATIC预售价为44万元。首先是奔驰EQA,新车
胶囊机器人让胃镜检查不再恐怖通过一颗小小的胶囊,肠胃道的情况可尽在掌握,让胃镜检查不再令人望而生畏。中国工程院院士李兆申日前在云南昆明表示,随着首例遥控胶囊胃镜检查在云南省第一人民医院(昆华医院)落地,患者不
俄首位机器人宇航员奔赴太空,能双枪射击中新网8月22日电(李弘宇)当一名奇怪的乘客一个人形机器人,坐在飞船指令长的位置上飞往国际空间站时,很多人可能会想这样的场景,只能在星球大战星际迷航等科幻电影中发生。毕竟,此前首个
京东双十一将投用70个机器人仓10月29日消息,在2019年全球智能物流峰会上,京东物流集团CEO王振辉在演讲中表示两年多来,我们已经服务了20多家万企业客户,外部收入增长接近8倍,占总体收入近40。马上到双十
机器人常见末端夹持机构大全对于工业机器人来说,搬运物料是其抓取作业方式中较为重要的应用之一。工业机器人作为一种具有较强通用性的作业设备,其作业任务能否顺利完成直接取决于夹持机构,因此机器人末端的夹持机构要结
热议从中印机器人行业收入差,看两国制造业现状近年来,印度工业机器人市场在全球排名第14位,仅次于泰国和西班牙,目前印度工业机器人的销量处于快速上升的状态,以26的复合增长率持续增加。在年度安装量方面,印度现在在全球排名第十一
快手vs抖音,一场不对称的战争虽然宿华并不想承认,但在投资人眼中,快手一直被作为抖音的B面,而快手和抖音之间的竞争,也被外界视为一场长期战争,而其中竞争最为激烈的,便是双方都铆足劲的电商业务。前两天,快手发布最
高通骁龙898曝光性能一路狂飙?今年已过去大半,按照惯例,高通将于第四季度揭晓新一代旗舰处理器,用以明年的Android旗舰手机之中。近日,有爆料信息显示,SM8450基于三星4nm工艺,测试性能提升20左右。并
仙剑奇侠传7终于上市,需要配置有多高才够畅玩呢?幸福来的太突然了,等了那么多年,仙剑奇侠传七终于官宣发售日了。游戏将于今年10月15日正式与玩家见面,目前已开启预售。这次官方可是做足了情怀,重楼酒神全面回归。情怀就是仙剑最大魅力
国产旗舰受挫,iPhone13开始降价促销?iPhone13的价格也是很多的小伙伴都是关注的,毕竟现在距离这个iPhone13进行发布的时间也是越来越近了!不出意外iPhone13系列将于9月份亮相,我们一起拭目以待吧!根据
小米用不用华为鸿蒙,谷歌说的算?鸿蒙发布已经快三个月了,虽然国内各大家厂商都在积极和华为合作,但是鸿蒙的下载量仍然不太乐观,即便是国内三大运营商也在为华为专门定制手机,想突破3。6亿下载量仍困难重重。很多网友高呼
FUN肆趣野全国媒体试驾,哈弗H92022款很有神威自驾318国道,有组队的吗?每个越野爱好者的心中都有一个圣地,还有一个拥有硬核座驾的愿望!去征服这样一条越野线。哈弗H9作为车主力量源泉中的一员,尤其是升级后的哈弗H92022款强
新战场,茶饮三巨头的出海之路文消费界,作者丨晓梦茶饮是个不断壮大的产业。根据灼识咨询报告的数据,2020年现制茶饮市场规模为1136亿元,预计到2025年将达3400亿元,年复合增长率为24。5。在高增长率的
潮流兄弟齐上阵!坦克300携兄弟车型燃炸成都车展流量明星最怕啥?怕的就是新生代明星出场,夺走粉丝注意力。这个道理换到汽车上也一样,再好的车型只要遇到车展这种大型争奇斗艳的修罗场,都得为自己捏一把汗。除非,你是独一无二的又或者,你
锐龙R9RTX30602。5K屏天选2首销凭借着二次元的风格以及出色的游戏体验,华硕天选2赢得了诸多玩家的青睐,也是目前最为火爆的游戏本之一,为了满足不同玩家的需求,华硕天选2日前推出了全新配置,采用了锐龙R9RTX306
LOLM即将迎来不删档测试,腾讯又干了一件大事未成年人网络保护是一项全社会都热切关注的议题。近日有关部门发布关于坚决支持和积极响应未成年人游戏防沉迷最新规定的声明也在网络上引起了巨大的讨论。和过往一样,游戏厂商们自然是积极响应
2021成都车展上演神仙打架,魏牌摩卡能得什么名次?虽然好事多磨,但是2021成都车展在延期两天后,终于在8月29日正式开幕。本届车展共汇集了130家车企60台新车汇聚一堂。合资这边,宝马8系奔驰GLS凯迪拉克CT5等悉数亮相,是众
格科微正式登陆科创板最新的消息显示,格科微在上交所科创板正式挂牌上市,证券代码为688728,发行价格14。38元股,发行市盈率为46。92倍。截至盘前,格科微股票报价40。99元股,涨幅185。05