公司业务里一直都有使用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/jiechengyang/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.Normal.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;
}
}
/**
*
* 图表标记
*/
mapService.prototype.iconMarker = function (icon, marker, labelContent, popupOpts, events) {
events = events || null;
labelContent = labelContent || null;
var defaultOpts = {
minWidth: 500,
className: 'leaflet-popup'
};
popupOpts = $.extend(defaultOpts, popupOpts);
var marker = new L.Marker(marker, {
icon: icon
});
this.getMap().addLayer(marker);
if (labelContent) {
var label = new L.Label({
noHide: true
});
label.setContent(labelContent).setLatLng({lat: marker.lat, lng: marker.lng + 1.4});
this.getMap().showLabel(label);
}
if (popupOpts.id) {
marker.bindPopup(document.getElementById(popupOpts.popupId), popupOpts);
}
if (events) {
for (var eKey in events) {
marker.on(eKey, events[eKey]);
}
}
return marker;
};
/**
*
* 多边形地块
*/
mapService.prototype.landPolygon = function (polygonObj, opts, events) {
var defaultOpts = {
initColor: 'pink',
initOpacity: 0.3,
hoverColor: 'orange',
hoverOpacity: 0.3,
domId: 'leaflet_land_popup_' + polygonObj.id,
};
events = events || {};
opts = $.extend(defaultOpts, opts);
if (polygonObj.hasWarning) {
opts.initColor = 'red';
}
var land = new L.Polygon(polygonObj.polygon, {
weight: 2,
opacity: opts.initOpacity,
fillOpacity: opts.initOpacity,
color: opts.initColor
});
this.getMap().addLayer(land);
var label = new L.Label();
label.setContent(polygonObj.labelContent).setLatLng(land.getBounds().getCenter());
this.getMap().showLabel(label);
var defaultEvent = {
mouseover: function (event) {
land.setStyle({
color: opts.hoverColor,
opacity: opts.hoverOpacity,
fillOpacity: opts.hoverOpacity,
});
},
mouseout: function (event) {
land.setStyle({
color: opts.initColor,
opacity: opts.initOpacity,
fillOpacity: opts.initOpacity,
});
}
};
events = $.extend(defaultEvent, events);
for (var eKey in events) {
land.on(eKey, events[eKey]);
}
if (polygonObj.isBindPopup) {
land.bindPopup(document.getElementById(polygonObj.domId), {
minWidth: 500,
className: 'leaflet-popup'
});
}
return land;
};
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);
最后再说一下,我们提升代码质量的唯一途径只有看源码。愿得到指点,愿共勉。
-------------------------垃圾猿