公司业务里一直都有使用leaflet地图插件来做地图展示、绘图等操作。公司有个项目已经有好几年了,由于项目原因一直在使用,今年由于google 地图 api过期,导致已经使用的地图无法加载。我作为现在该项目的维护者,虽然未参与项目的开发阶段,但是事情在我手上了还是要处理的。
上面说到api过期,无法加载地图了。我首先想到的是更换api key,翻墙出去,搞了许久,没有成功。想想也是,就我这个英语水平成功就是偶然。当然也不全是英语问题,在我在墙外的世界捣鼓来捣鼓去的时候,隐约的发现谷歌api 有关的认证加强了(事情过了1一个月了,也忘记了)。
在最简单的方法尝试无效后,下定决心重写吧。好,写,一顿搜索猛如虎(差点写成:猛如狗了^_^),最后找了个现成的代码来改吧改吧就可用了。其实改完效果还可以,比以前多了 图层切换功能,而且使用国内地图也不怕哪天项目上说用谷歌不行了。
上面的事情距离我现在写这个文章的时候有一个月了。我是按照用一个项目改一个项目的原则,有些项目无人问津了,我便没改了。按照之前的套路,合并了其他已改项目的分支,项目类似,基本合并无冲突。代码到了现在的项目上了,由于这个项目用到了这套系统且要更新功能,我真nm的欲哭无泪,好老的项目了(用的还是php yii1的框架,前端用的是一框bjui的框架,说起它可能知道的人不多,但是jui框架应该要多点,它就是基于jui的框架)。
埋怨项目老,技术老之后还是要做事,因为在系统升级完成之前必须用它苟延残喘下去。翻看着代码,终于忍不住了,怎么可以这么重复的做某件事呢,难道是对它不想忘记吗? 重复不说,js里面混入php的判断,这些老的套路,越看越那啥了
又吐槽了代码之后,我先来展示一下那些 旋转代码吧(╯﹏╰)
var mainLayer = new L.Google('HYBRID', { // maxNativeZoom : 3, // maxZoom : 4, // minZoom : 3, // noWrap : true, errorTileUrl : '<?php echo Yii::app()->baseUrl; ?>/images/tiles/empty.jpg?t=<?php echo time(); ?>', // continuousWorld : true, attribution : '' }); var mainMap = L.map('map', { zoomControl: false }) .setView([31.2288614009,104.0252005317], 17) .addLayer(mainLayer); <?php foreach ($lands as $key => $land) { if (!empty($land->polygon)) { $isBindPopup = $hasWarning = 'false'; $labelContent = $land->name; if ((!empty($land->nodes) && count($land->nodes[0]->tempDataArr > 0) || count($land->devices) > 0)) { $isBindPopup = 'true'; } if ($land->hasWarning()) { $hasWarning = 'true'; $labelContent .= '<br><span style="font-weight:normal; color:#c00;">' . $land->warningMessage . '</span>'; } ?> land_polygon(<?php echo $land->polygon; ?>, mainMap, '<?php echo $labelContent; ?>', <?php echo $land->id; ?>, <?php echo $isBindPopup; ?>, <?php echo $hasWarning; ?>); <?php } } ?> <?php foreach ($hikvideos as $video) { if (!empty($video->marker)) { $isNull = 'true'; if (!empty($video->land->nodes) && count($video->land->nodes[0]->tempDataArr) > 0) { $isNull = 'false'; } if (BUtils::isLocalClient()) { $video->ip = $video->local_ip; $video->http_port = $video->local_http_port; } ?> videoMarker(mainMap, <?php echo $video->marker; ?>, <?php echo $video->id; ?>, '<?php echo $video->name; ?>', <?php echo CJSON::encode($video); ?>, <?php echo $isNull ?>); <?php } } ?> <?php if (!empty($this->_config['xph_marker'])) {?> (function weatherStationMarker (map, latLng, labelContent) { var myIcon = L.divIcon({ iconSize: [50, 67], className: 'weather-station-marker' }); var marker = new L.Marker(latLng, { icon: myIcon }); map.addLayer(marker); if (labelContent) { // marker.bindLabel(labelContent, {noHide: false}).addTo(map); var label = new L.Label({ noHide: true }); label.setContent(labelContent).setLatLng({lat: latLng.lat, lng:latLng.lng + 1.4}); map.showLabel(label); } marker.on('popupopen', function(event) { $('#map').bjuiajax('refreshDiv', 'leaflet_land_popup_xph'); }).on('mouseout', function(event) { }).bindPopup(document.getElementById('leaflet_land_popup_xph'), { minWidth: 500, className: 'leaflet-popup' }); })(mainMap, <?php echo $this->_config['xph_marker']; ?>, '气象站'); <?php }?> <?php if (!empty($this->_config['xph_soil_marker'])) {?> (function soilStationMarker (map, latLng, labelContent) { var myIcon = L.divIcon({ iconSize: [50, 67], className: 'soil-station-marker' }); var marker = new L.Marker(latLng, { icon: myIcon }); map.addLayer(marker); if (labelContent) { // marker.bindLabel(labelContent, {noHide: false}).addTo(map); var label = new L.Label({ noHide: true }); label.setContent(labelContent).setLatLng({lat: latLng.lat, lng:latLng.lng + 1.4}); map.showLabel(label); } marker.on('popupopen', function(event) { $('#map').bjuiajax('refreshDiv', 'leaflet_land_popup_xph_soil'); }).on('mouseout', function(event) { }).bindPopup(document.getElementById('leaflet_land_popup_xph_soil'), { minWidth: 500, className: 'leaflet-popup' }); })(mainMap, <?php echo $this->_config['xph_soil_marker']; ?>, '土壤墒情监测点'); <?php }?> function videoMarker (map, latLng, id, labelContent, videoOption, isNull) { var myIcon = L.divIcon({ iconSize: [25, 41], className: 'video-marker' }); var marker = new L.Marker(latLng, { icon: myIcon }); wth = 350; if (!isNull) { wth = 800; } map.addLayer(marker); if (labelContent) { marker.bindLabel(labelContent, {noHide: false}).addTo(map); // var label = new L.Label({ // noHide: true // }); // label.setContent(labelContent).setLatLng(latLng); // map.showLabel(label); } marker.on('mouseover', function(event) { }).on('mouseout', function(event) { }).bindPopup(document.getElementById('leaflet_video_popup'), { minWidth: wth, className: 'leaflet-popup', }); marker.on('popupopen', function(event) { setTimeout(function () { $('#leaflet_video_refresh').show(); $('#leaflet_video_name').text(videoOption.name); video_window = ''; var html = $('#bjui-navtab .tabsPageContent').find('#video_window').html(); if (html) { video_window = $('#video_window').remove(); $('#leaflet_video_window').html(video_window); $('#video_window').addClass('video_window'); WebVideoCtrl.I_ChangeWndNum(1); } videoInit(); videoLogin(videoOption); if (!isNull) { $('#refresh_land_info').show(); } else { $('#refresh_land_info').hide(); } $('#leaflet_video_window').bjuiajax('doLoad', {url:"<?php echo $this->createUrl('land/realdata', ['id' => '_ID_']); ?>".replace('_ID_', videoOption.land_id), target:"#refresh_land_info"}); }, 10); }).on('popupclose', function(event) { console.log('popupclose', videoOption); }); } function land_polygon (polygon, map, labelContent, id, isBindPopup, hasWarning) { var initColor = 'pink', initOpacity = 0.3, hoverColor = 'orange', hoverOpacity = 0.3; if (hasWarning) { initColor = 'red'; } var land = new L.Polygon(polygon, { weight: 2, opacity: initOpacity, fillOpacity: initOpacity, color: initColor }); var domId = 'leaflet_land_popup_' + id; map.addLayer(land); var label = new L.Label(); label.setContent(labelContent).setLatLng(land.getBounds().getCenter()); map.showLabel(label); land.on('mouseover', function(event) { land.setStyle({ color: hoverColor, opacity: hoverOpacity, fillOpacity: hoverOpacity, }); }).on('mouseout', function(event) { land.setStyle({ color:initColor, opacity: initOpacity, fillOpacity: initOpacity, }); }).on('popupopen', function(event) { $('#map').bjuiajax('refreshDiv', domId); }); if (isBindPopup) { land.bindPopup(document.getElementById(domId), { minWidth: 500, className: 'leaflet-popup' }); } } </script>
就这样的代码,能在一个代码里面好几个地方重复出现,你说是有多大的心去维护啊,又难受了。
说了这么的故事,该说重点了,基于上面的情况呢,必须有个什么东西来优化它,想来想去,考虑地图是基于dom生成加载的,那这样的话,写一个jquery的插件应该是比较好的了,当然的话自己封装一个类也行。
对于jquery插件的话,我分享一个网址吧:jquery插件
附上巨人的肩膀
1、ChineseTmsProviders:Leaflet.ChineseTmsProviders
2、tileLayer.baidu
L.CRS.Baidu = new L.Proj.CRS('EPSG:3395', '+proj=merc +lon_0=0 +k=1 +x_0=1440 +y_0=255 +datum=WGS84 +units=m +no_defs', { resolutions: function () { level = 19 var res = []; res[0] = Math.pow(2, 18); for (var i = 1; i < level; i++) { res[i] = Math.pow(2, (18 - i)) } return res; }(), origin: [0, 0], bounds: L.bounds([20037508.342789244, 0], [0, 20037508.342789244]) }); L.tileLayer.baidu = function (option) { option = option || {}; var layer; var subdomains = '0123456789'; switch (option.layer) { //单图层 case "vec": default: //'http://online{s}.map.bdimg.com/tile/?qt=tile&x={x}&y={y}&z={z}&styles=pl&b=0&limit=60&scaler=1&udt=20170525' layer = L.tileLayer('http://online{s}.map.bdimg.com/onlinelabel/?qt=tile&x={x}&y={y}&z={z}&styles=' + (option.bigfont ? 'ph' : 'pl') + '&scaler=1&p=1', { name:option.name,subdomains: subdomains, tms: true }); break; case "img_d": layer = L.tileLayer('http://shangetu{s}.map.bdimg.com/it/u=x={x};y={y};z={z};v=009;type=sate&fm=46', { name: option.name, subdomains: subdomains, tms: true }); break; case "img_z": layer = L.tileLayer('http://online{s}.map.bdimg.com/tile/?qt=tile&x={x}&y={y}&z={z}&styles=' + (option.bigfont ? 'sh' : 'sl') + '&v=020', { name: option.name, subdomains: subdomains, tms: true }); break; case "custom"://Custom 各种自定义样式 //可选值:dark,midnight,grayscale,hardedge,light,redalert,googlelite,grassgreen,pink,darkgreen,bluish option.customid = option.customid || 'midnight'; layer = L.tileLayer('http://api{s}.map.bdimg.com/customimage/tile?&x={x}&y={y}&z={z}&scale=1&customid=' + option.customid, { name: option.name, subdomains: "012", tms: true }); break; case "time"://实时路况 var time = new Date().getTime(); layer = L.tileLayer('http://its.map.baidu.com:8002/traffic/TrafficTileService?x={x}&y={y}&level={z}&time=' + time + '&label=web2D&v=017', { name: option.name, subdomains: subdomains, tms: true }); break; //合并 case "img": layer = L.layerGroup([ L.tileLayer.baidu({ name: "底图", layer: 'img_d', bigfont: option.bigfont }), L.tileLayer.baidu({ name: "注记", layer: 'img_z', bigfont: option.bigfont }) ]); break; } return layer; };
再谈谈我心路吧,其实也是司空见惯的东西了。
- 为了更好的配置,我选择了两种配置,也就是除了自己传递配置参数的话,也可以通过标签属性传递
- 不好解决坐标系问题,我选择了加一个参数来区分百度和其它地图
- 外部更好的使用,必须在需要的地方添加回调参数
- 为了统一的管理和不重复的添加,我选择了使用一个变量存储所有加载的图层。在设置的时候,已经出现的图层会直接返回
最后我贴上目前的一个代码,改代码下午才写,不完善,下周应该会完善自己需要使用的地方。虽然已过了青葱的岁月,可是代码水平还是难^_^。
/** * jQuery and leaflet Plugin createMap * jquery插件:生成百度、谷歌、高德、天地图等地图到页面 * * version 1.0, 2020-03-20 * by jcy * Git repository : https://github.com/jiechenyang/jqLeafletCreateMap */ /** * example: * * 默认生成谷歌等多图层【查看地图右上角图层管理工具】:var mapService = $("#map").createMap({center: [lat, lng]}); * * 生成百度地图:var mapService = $("#map").createMap({center: [lat, lng], imap: 'Bd.Normal.Map'}); * * 生成单张地图请传递:single:true * * 修改默认加载地图请传:iLayer:'对应chinaProviderKeys的key值' * */ /** * add method: * iconMarker: 图标加载 * addPolygon: 面加载 * */ (function ($) { function arrIndexOf(array, find, field) { var index = -1; for (var i = 0; i < array.length; i++) { var arr = array[i]; if (arr[field] === find) { index = i; break; } } return index; } function mapService() { this.layers = []; this.chinaProviderKeys = [ { key: 'Geoq.Normal.Map', name: '智图地图', group: 'Geog' }, { key: 'Geoq.Normal.PurplishBlue', name: '智图午夜蓝', group: 'Geog' }, { key: 'Geoq.Normal.Gray', name: '智图灰色', group: 'Geog' }, { key: 'Geoq.Normal.Warm', name: '智图暖色', group: 'Geog' }, { key: 'TianDiTu.Normal.Map', name: '天地图', group: 'TianDiTu' }, { key: 'TianDiTu.Normal.Annotion', name: '', group: 'TianDiTu' }, { key: 'TianDiTu.Satellite.Map', name: '天地图影像', group: 'TianDiTu' }, { key: 'TianDiTu.Satellite.Annotion', name: '', group: 'TianDiTu' }, { key: 'Google.Normal.Map', name: '谷歌地图', group: 'Google' }, { key: 'Google.Satellite.Map', name: '谷歌影像', group: 'Google' }, { key: 'GaoDe.Normal.Map', name: '高德地图', group: 'GaoDe' }, { key: 'GaoDe.Satellite.Map', name: '高德影像', group: 'GaoDe' }, { key: 'GaoDe.Satellite.Annotion', name: '', group: 'GaoDe' } ]; this.map = null; this.get = function (key) { return this.layers.indexOf(key) === 1 ? this.layers[key] : null; } this.set = function (key, options) { if (this.layers.indexOf(key) >= 0) { return this.get(key); } if (arrIndexOf(this.chinaProviderKeys, key, 'key') === -1) { return null; } var layer = L.tileLayer.chinaProvider(key, options); this.layers[key] = layer; return layer; } this.delete = function (key) { delete this.layers[key]; } this.getMap = function () { return this.map; } this.destroy = function () { this.layers = []; this.map = null; } this.init = function (domId, settings) { if (!settings.hasOwnProperty('iLayer')) { settings.iLayer = settings.imap; } var defaultMapOpts = { zoom: settings.zoom, zoomControl: settings.zoomControl, center: settings.center }; var bdMap = false; if (settings.imap === 'Bd.Normal.Map') { defaultMapOpts.crs = L.CRS.Baidu; bdMap = true; } var mainMap = L.map(domId, defaultMapOpts); this.map = mainMap; var opts = { maxZoom: settings.maxZoom, minZoom: settings.minZoom, }; if (bdMap) { opts.layer = settings.bdLayer ? settings.bdLayer : 'custom'; opts.customid = settings.bdCustomid ? settings.bdCustomid : 'googlelite'; var firstLayer = this.getBdMap(opts); settings.single = true; } else { var firstLayer = this.set(settings.iLayer, opts); } if (firstLayer) { mainMap.addLayer(firstLayer); } if (!settings.single) { var baseLayers = this.addLayers(settings.method, opts); if (baseLayers) { var providerName = this.chinaProviderKeys[arrIndexOf(this.chinaProviderKeys, settings.iLayer, 'key')]['name']; baseLayers[providerName] = firstLayer; L.control.layers(baseLayers, null).addTo(mainMap); } } L.Util.requestAnimFrame(mainMap.invalidateSize, mainMap, !1, mainMap._container); } this.addLayers = function (method, opts) { method = 'add' + method; if (this.hasOwnProperty(method)) { return this[method].call(this, opts); } return {}; } /** * 智图内容 */ this.addGeoNorMaps = function (options) { var layers = {}; for (var i = 0; i < this.chinaProviderKeys.length; i++) { var provider = this.chinaProviderKeys[i]; if (provider.group !== 'Geog') continue; var layer = this.set(provider.key, options); if (layer) { layers[provider.name] = layer; } } return layers; } /** * 百度地图 */ this.getBdMap = function (options) { options = $.extend({ // maxZoom: 18, // minZoom: 4, attribution: 'ⓒ 2020 Bd Map', layer: 'custom', customid: 'grassgreen', // crs: L.CRS.Baidu }, options); //百度:titleLayer.baidu.js //TODO: 请引入 proj4.js 和 proj4leaflet.js var layer = new L.tileLayer.baidu(options); this.layers['Bd.Map'] = layer; return layer; } /** * 天地图内容 */ this.addTianDiMaps = function (options) { var normalm = this.set('TianDiTu.Normal.Map', options), normala = this.set('TianDiTu.Satellite.Annotion', options), imgm = this.set('TianDiTu.Satellite.Map', options), imga = this.set('TianDiTu.Normal.Map', options); var normal = L.layerGroup([normalm, normala]), image = L.layerGroup([imgm, imga]); return { "天地图": normal, "天地图影像": image, } } /** * 谷歌地图内容 */ this.addGoogleMaps = function (options) { var normalMap = this.set('Google.Normal.Map', options), satelliteMap = this.set('Google.Satellite.Map', options); return { "谷歌地图": normalMap, "谷歌影像": satelliteMap, } } /** * 高德地图 */ this.addGaoDeMaps = function (options) { var Gaode = this.set('GaoDe.Normal.Map', options); var Gaodimgem = this.set('GaoDe.Satellite.Map', options); var Gaodimga = this.set('GaoDe.Satellite.Annotion', options); var Gaodimage = L.layerGroup([Gaodimgem, Gaodimga]); return { "高德地图": Gaode, "高德影像": Gaodimage, } } /** * 所有地图 */ this.addAllMaps = function (options) { var keys = [ 'GeoNorMaps', 'TianDiMaps', 'GoogleMaps', 'GaoDeMaps', ]; var layers = {}; for (var i in keys) { var method = 'add' + keys[i]; if (this.hasOwnProperty(method)) { layers = $.extend(layers, this[method](options)); } } return layers; } this.iconMarker = function (latLng, popupID, icon, labelContent, popupopenCb, opts) { if (!this.map) return null; labelContent = labelContent || ''; popupopenCb = popupopenCb || null; opts = opts || { minWidth: 500, className: 'leaflet-popup' } icon = icon || { iconSize: [50, 67], className: 'weather-station-marker' } var myIcon = L.divIcon(icon); var marker = new L.Marker(latLng, { icon: myIcon }); this.map.addLayer(marker); if (labelContent) { // marker.bindLabel(labelContent, {noHide: false}).addTo(map); var label = new L.Label({ noHide: true }); label.setContent(labelContent).setLatLng({lat: latLng.lat, lng: latLng.lng + 1.4}); map.showLabel(label); } marker.on('popupopen', function (event) { // $('#map').bjuiajax('refreshDiv', 'leaflet_land_popup_xph'); if (typeof popupopenCb === 'function') { popupopenCb(event); } }).on('mouseout', function (event) { }).bindPopup(document.getElementById(popupID), opts); } } var methods = { init: function (options) { var tData = this.data() || {}; // TODO: 参数合并:隐式参数与显示参数;隐式参数:dom标签的data-属性 显示参数:调用对象传参 options = $.extend(tData, options); var settings = $.extend({ maxZoom: 18,// leaflet 参数 minZoom: 5,// leaflet 参数 zoom: 14,// leaflet 参数 zoomControl: false,// leaflet 参数 center: [30.29898916168688, 103.57831210632321],// leaflet 参数 imap: 'Google.Normal.Map',// 插件 参数:默认地图加载谷歌地图,Bd.Normal.Map:由于坐标系问题,暂时只能加载它一个地图 bdLayer: 'custom',// 百度地图图层选择, bdCustomid: 'googlelite',// 插件 参数:百度地图图层样式选择 single: false,// 插件 参数:是否加载单个地图图层:默认是加载多张 method: 'AllMaps'// 插件 参数:加载多张地图图层时有几种类型选择:智图、天地图、百度、谷歌、高德 }, options); var service = new mapService(); service.init(this.attr('id'), settings); return service; }, destroy: function () { this.empty(); } }; $.fn.createMap = function (method) { method = method || null; if (methods[method]) { return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); } else if (typeof method === 'object' || !method) { return methods.init.apply(this, arguments); // return methods.init(this, methods); } else { $.error('Method' + method + 'does not exist on jQuery.tooltip'); } } })(jQuery);
最后再说一下,我们提升代码质量的唯一途径只有看源码。愿得到指点,愿共勉。
-------------------------垃圾猿