问题背景:
用leaflet加载高德地图,对地图进行操作(如:放大、缩小或移动)后,再动态加载leaflet-canvas-marker图层,图层第一次(初始)叠加在地图上有偏移,对地图进行操作(如:放大、缩小或移动)后,偏移消失。[特别注意要先对地图进行操作,再加载leaflet-canvas-marker图层,否则问题不能重现]。如下图:
第一次(初始)叠加位置:
对地图进行操作后位置:
初步判定为leaflet-canvas-marker的一个Bug。
问题解决:动态加载leaflet-canvas-marker图层后刷新下地图。由于没找到leaflet中Map对象刷新方法,取巧采用map.setView(map.getCenter());实现leaflet中Map对象刷新,解决以上问题。
源码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>leaflet canvas marker test</title>
<!-- <link href="leaflet.css" type="text/css" rel="stylesheet"/>
<script src="leaflet.js"></script> -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.1/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.3.1/dist/leaflet.js"></script>
<!-- https://github.com/tontita/Leaflet.KoreanTmsProviders/blob/master/src/Leaflet.KoreanTmsProviders.js -->
<script src="leaflet.ChineseTmsProviders.js"></script>
</head>
<body>
<div><button onclick="addMarkers()">Add Layer</button></div>
<div id="map" style="width: 100vw;height: 100vh">
</div>
<!-- <script src="leaflet.canvas-markers.js"></script> -->
<script src="https://unpkg.com/leaflet-canvas-marker@0.2.1"></script>
<script>
L.TileLayer.ChinaProvider = L.TileLayer.extend({
initialize: function (type, options) { // (type, Object)
var providers = L.TileLayer.ChinaProvider.providers;
var parts = type.split('.');
var providerName = parts[0];
var mapName = parts[1];
var mapType = parts[2];
var url = providers[providerName][mapName][mapType];
options.subdomains = providers[providerName].Subdomains;
options.key = options.key || providers[providerName].key;
L.TileLayer.prototype.initialize.call(this, url, options);
}
});
L.TileLayer.ChinaProvider.providers = {
TianDiTu: {
Normal: {
Map: "http://t{s}.tianditu.com/DataServer?T=vec_w&X={x}&Y={y}&L={z}&tk={key}",
Annotion: "http://t{s}.tianditu.com/DataServer?T=cva_w&X={x}&Y={y}&L={z}&tk={key}"
},
Satellite: {
Map: "http://t{s}.tianditu.com/DataServer?T=img_w&X={x}&Y={y}&L={z}&tk={key}",
Annotion: "http://t{s}.tianditu.com/DataServer?T=cia_w&X={x}&Y={y}&L={z}&tk={key}"
},
Terrain: {
Map: "http://t{s}.tianditu.com/DataServer?T=ter_w&X={x}&Y={y}&L={z}&tk={key}",
Annotion: "http://t{s}.tianditu.com/DataServer?T=cta_w&X={x}&Y={y}&L={z}&tk={key}"
},
Subdomains: ['0', '1', '2', '3', '4', '5', '6', '7'],
key: "天地图的token key"
},
Mapbox: {
streets: {
streets: "https://api.tiles.mapbox.com/v4/mapbox.streets/{z}/{x}/{y}.png?access_token={key}"
},
light: {
light: "https://api.tiles.mapbox.com/v4/mapbox.light/{z}/{x}/{y}.png?access_token={key}"
},
dark: {
dark: "https://api.tiles.mapbox.com/v4/mapbox.dark/{z}/{x}/{y}.png?access_token={key}"
},
outdoors: {
outdoors: "https://api.tiles.mapbox.com/v4/mapbox.outdoors/{z}/{x}/{y}.png?access_token={key}"
},
satellite: {
satellite: "https://api.tiles.mapbox.com/v4/mapbox.satellite/{z}/{x}/{y}.png?access_token={key}"
},
Subdomains: [],
key: "mapbox的token key"
},
GaoDe: {
Normal: {
Map: 'http://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}'
},
Satellite: {
Map: 'http://webst0{s}.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}',
Annotion: 'http://webst0{s}.is.autonavi.com/appmaptile?style=8&x={x}&y={y}&z={z}'
},
Subdomains: ["1", "2", "3", "4"]
},
Google: {
Normal: {
Map: "http://www.google.cn/maps/vt?lyrs=m@189&gl=cn&x={x}&y={y}&z={z}"
},
Satellite: {
Map: "http://www.google.cn/maps/vt?lyrs=s@189&gl=cn&x={x}&y={y}&z={z}"
},
Subdomains: []
},
Geoq: {
Normal: {
Map: "http://map.geoq.cn/ArcGIS/rest/services/ChinaOnlineCommunity/MapServer/tile/{z}/{y}/{x}",
PurplishBlue: "http://map.geoq.cn/ArcGIS/rest/services/ChinaOnlineStreetPurplishBlue/MapServer/tile/{z}/{y}/{x}",
Gray: "http://map.geoq.cn/ArcGIS/rest/services/ChinaOnlineStreetGray/MapServer/tile/{z}/{y}/{x}",
Warm: "http://map.geoq.cn/ArcGIS/rest/services/ChinaOnlineStreetWarm/MapServer/tile/{z}/{y}/{x}",
},
Theme: {
Hydro: "http://thematic.geoq.cn/arcgis/rest/services/ThematicMaps/WorldHydroMap/MapServer/tile/{z}/{y}/{x}"
},
Subdomains: []
},
OSM: {
Normal: {
Map: "http://{s}.tile.osm.org/{z}/{x}/{y}.png",
},
Subdomains: ['a', 'b', 'c']
},
OfflineMap: {
Normal: {
Map: "http://127.0.0.1:8081/offlineMap/{z}/{x}/{y}.png",
},
Subdomains: []
}
};
L.tileLayer.chinaProvider = function (type, options) {
return new L.TileLayer.ChinaProvider(type, options);
};
/**
* 智图地图内容
var normalm1 = L.tileLayer.chinaProvider('Geoq.Normal.Map', {
maxZoom: 18,
minZoom: 5
});
var normalm2 = L.tileLayer.chinaProvider('Geoq.Normal.Color', {
maxZoom: 18,
minZoom: 5
});
var normalm3 = L.tileLayer.chinaProvider('Geoq.Normal.PurplishBlue', {
maxZoom: 18,
minZoom: 5
});
var normalm4 = L.tileLayer.chinaProvider('Geoq.Normal.Gray', {
maxZoom: 18,
minZoom: 5
});
var normalm5 = L.tileLayer.chinaProvider('Geoq.Normal.Warm', {
maxZoom: 18,
minZoom: 5
});
var normalm6 = L.tileLayer.chinaProvider('Geoq.Normal.Cold', {
maxZoom: 18,
minZoom: 5
});
*/
/**
* 天地图内容
*/
var normalm = L.tileLayer.chinaProvider('TianDiTu.Normal.Map', {
maxZoom: 18,
minZoom: 5
}),
normala = L.tileLayer.chinaProvider('TianDiTu.Normal.Annotion', {
maxZoom: 18,
minZoom: 5
}),
imgm = L.tileLayer.chinaProvider('TianDiTu.Satellite.Map', {
maxZoom: 18,
minZoom: 5
}),
imga = L.tileLayer.chinaProvider('TianDiTu.Satellite.Annotion', {
maxZoom: 18,
minZoom: 5
});
var normal = L.layerGroup([normalm, normala]),
image = L.layerGroup([imgm, imga]);
/**
* 谷歌
var normalMap = L.tileLayer.chinaProvider('Google.Normal.Map', {
maxZoom: 18,
minZoom: 5
}),
satelliteMap = L.tileLayer.chinaProvider('Google.Satellite.Map', {
maxZoom: 18,
minZoom: 5
});
*/
/**
* 高德地图
*/
var Gaode = L.tileLayer.chinaProvider('GaoDe.Normal.Map', {
maxZoom: 18,
minZoom: 5
});
var Gaodimgem = L.tileLayer.chinaProvider('GaoDe.Satellite.Map', {
maxZoom: 18,
minZoom: 5
});
var Gaodimga = L.tileLayer.chinaProvider('GaoDe.Satellite.Annotion', {
maxZoom: 18,
minZoom: 5
});
var Gaodimage = L.layerGroup([Gaodimgem, Gaodimga]);
/**
* 离线地图
*/
var OfflineImage = L.tileLayer.chinaProvider('OfflineMap.Normal.Map', {
maxZoom: 10,
minZoom: 4
});
//mapbox
var mb1 = L.tileLayer.chinaProvider('Mapbox.streets.streets',{
maxZoom: 18,
minZoom: 5
});
var mb2 = L.tileLayer.chinaProvider('Mapbox.light.light',{
maxZoom: 18,
minZoom: 5
});
var mb3 = L.tileLayer.chinaProvider('Mapbox.dark.dark',{
maxZoom: 18,
minZoom: 5
});
var mb4 = L.tileLayer.chinaProvider('Mapbox.outdoors.outdoors',{
maxZoom: 18,
minZoom: 5
});
var mb5 = L.tileLayer.chinaProvider('Mapbox.satellite.satellite',{
maxZoom: 18,
minZoom: 5
});
var baseLayers = {
//"智图地图": normalm1,
//"智图多彩": normalm2,
//"智图午夜蓝": normalm3,
//"智图灰色": normalm4,
//"智图暖色": normalm5,
//"智图冷色": normalm6,
"天地图": normal,
"天地图影像": image,
//"谷歌地图": normalMap,
//"谷歌影像": satelliteMap,
"高德地图": Gaode,
"高德影像": Gaodimage,
//"图盒街道": mb1,
//"图盒亮": mb2,
//"图盒暗": mb3,
//"图盒户外": mb4,
//"图盒卫星": mb5,
}
var centerPoint = L.CRS.EPSG3857.project(L.latLng(39.906888, 116.397497))
var map = L.map("map", {
center: [39.906888, 116.397497], // 默认地图中心 L.CRS.EPSG3857.project(L.latLng(latlng.lat, latlng.lng))
maxBounds: L.latLngBounds(L.latLng(53.55, 73.66), L.latLng(3.86, 135.05)), // 地图最大显示范围 L.latLng(53.55, 73.66), L.latLng(3.86, 135.05)
crs: L.CRS.EPSG3857,
zoom: 5, //12
layers: [Gaode], //Gaodimage OfflineImage Gaode
detectRetina: true,
preferCanvas: true,
zoomControl: false
});
//L.control.layers(baseLayers, null).addTo(map);
L.control.zoom({
zoomInTitle: '放大',
zoomOutTitle: '缩小'
}).addTo(map);
function addMarkers(){
var ciLayer = L.canvasIconLayer({}).addTo(map);
let icon = L.icon({
//iconUrl: 'data-icon.png',
iconUrl: "data:image/svg+xml,%3Csvg t='1629184236836' class='icon' viewBox='0 0 1024 1024' version='1.1' xmlns='http://www.w3.org/2000/svg' p-id='3737' width='48' height='48'%3E%3Cpath d='M736 32h-448c-54.4 0-96 41.6-96 96v768c0 54.4 41.6 96 96 96h448c54.4 0 96-41.6 96-96v-768c0-54.4-41.6-96-96-96z m-480 96c0-19.2 12.8-32 32-32h448c19.2 0 32 12.8 32 32v64h-512v-64z m512 768c0 19.2-12.8 32-32 32h-448c-19.2 0-32-12.8-32-32v-640h512v640z' p-id='3738'%3E%3C/path%3E%3Cpath d='M512 832m-32 0a32 32 0 1 0 64 0 32 32 0 1 0-64 0Z' p-id='3739'%3E%3C/path%3E%3C/svg%3E",
iconSize: [20, 20],
iconAnchor: [10, 20]
});
let markers = [];
for (var i = 0; i < 3; i++) {
var marker = L.marker([39.906888 + Math.random()*1.8, 116.397497 + Math.random()*3.6], {icon: icon}).bindPopup('我是' + i + '号站点');
markers.push(marker);
}
ciLayer.addLayers(markers);
map.setView(map.getCenter());
}
</script>
</body>
</html>
备注:加载leaflet原生canvas图层没有偏移问题,测试代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Canvas test</title>
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/leaflet/1.7.1/leaflet.min.css"/>
<script src="https://cdn.bootcdn.net/ajax/libs/leaflet/1.7.1/leaflet.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/zrender/5.2.1/zrender.js"></script>
<style>
*{
margin: 0;
}
html, body, .map-wrapper, #map{
height: 100%;
width: 100%;
}
.map-wrapper {
position: relative;
}
.image-coordinates-container {
position: absolute;
left: 8px;
bottom: 8px;
z-index: 1000;
color: #666;
}
.canvas-layer {
position: absolute;
right: 8px;
top: 8px;
z-index: 1000;
color: #666;
background-color: whitesmoke;
}
</style>
</head>
<body>
<div class="map-wrapper">
<div id="map"></div>
<div id="imageCoordinates" class="image-coordinates-container"></div>
<div id="canvasLayer" class="canvas-layer"><span onclick="addCanvas();">Add canvas layer</span></div>
</div>
<script>
const center = [37.7, 112.7];
const map = L.map('map', {
center:center,
zoom: 6,
});
//Add GaoDe map layer
L.tileLayer(
'https://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}',
{
subdomains: '1234',
maxZoom: 21,
minZoom: 3,
coordType: 'gcj02',
}
).addTo(map);
function addCanvas(){
//New canvas shape renderer
const myRenderer = L.canvas({ padding: 0 });
//Add canvas render layer
myRenderer.addTo(map);
//LatLng to container point
const addPoint = [32.7, 105.7];
const { x: cx, y: cy } = map.latLngToContainerPoint(addPoint);
const { _ctx: ctx, _container: container } = myRenderer;
const r = 15;
//New zrender instance
const zr = zrender.init(container);
//New circle instance
const circle = new zrender.Circle({
shape: {
cx,
cy,
r,
},
style: {
fill: 'transparent',
stroke: 'red',
},
silent: true,
});
zr.add(circle);
//New circle animation
const animator = circle
.animate('shape', true)
.when(1000, { r: 0 })
.when(2000, { r })
.start();
myRenderer.on('update', ({ target }) => {
const { x: cx, y: cy } = map.latLngToContainerPoint(addPoint);
circle.attr({
shape: {
cx,
cy,
},
});
});
//map.setView(map.getCenter());
}
</script>
</body>
</html>