手绘地图顾名思义就是手工绘制的地图,比普通的地图更有观赏性和生动性,通过把特定的地点绘制出来,兼具实用和纪念性,同时更加具有可看性。
需要一个专门做手绘地图的团队。
比如图片如下:
使用切片工具对图片进行切片之后会对每个瓦片进行编号,一般会有三个变量XYZ,以腾讯地图为例左上角为原点,x表示列号,y表示行号,z表示缩放等级。
注意:手绘地图的图片一般都很大,会有十几M,需要对手绘地图进行瓦片切割,加载图片会很慢,页面比较卡,影响性能。
可以使用ps软件。(网站找了很多种办法,有的要安装软件等,最简单推荐使用ps自带的zoomify瓦片切割)。
用法:文件-导出-Zoomify
地图瓦片尺寸:切割选择配置浏览器尺寸256*256。
自动生成文件包:
去官网下载组件文件包:
leaflet.js- 这是缩小后的 Leaflet JavaScript 代码。
leaflet-src.js- 这是可读的、未压缩的 Leaflet JavaScript,有时有助于调试。
leaflet.css- 这是 Leaflet 的样式表。
images- 这是一个文件夹,其中包含 引用的图像leaflet.css。它必须与leaflet.css.
手绘地图需要用到瓦片Zoomify,还需下载L.TileLayer.Zoomify.js组件,官网插件有相关参考demo:https://cmulders.github.io/Leaflet.Zoomify/examples/hubble-image.html
如果需要全屏缩放组件,还需下载:
Leaflet.fullscreen.min.js
leaflet.fullscreen.css
参考demo: https://leaflet.github.io/Leaflet.fullscreen/
var map = L.map('map', {
maxZoom: 7,/**这边7为最大放大到原图大小,可以自己设置修改*/
minZoom: 1,
zoomSnap: 0,/**强制地图的缩放级别始终是该缩放级别的倍数*/
zoomDelta: 1,/**地图缩放级别的变化量 */
zoomControl: false,/*缩放控件是否显示*/
doubleClickZoom: false, /* 禁用双击放大*/
attributionControl: false, /* 移除右下角leaflet标识*/
crs: L.CRS.Simple,/*一个简单的 CRS,可以将经纬度直接映射到 x 和 y。可用于平面地图(如手绘地图)*/
closePopupOnClick:false, /*弹窗提示一直显示*/
tap: false /* https://github.com/Leaflet/Leaflet/issues/7255*/
});
/***宽高按图片宽高 */
var layer = L.tileLayer.zoomify('./map/mapbg/{g}/{z}-{x}-{y}.jpg', {
width: 19095, /**手绘地图原始图片的宽度*/
height: 6890/**手绘地图原始图片的高度*/
}).addTo(map);
map.setMaxBounds(layer.getBounds());/*将地图视图限制在给定范围内*/
map.fitBounds(layer.getBounds());/*设置包含给定地理边界的地图视图,并具有可能的最大缩放级别*/
一个简单的手绘地图就形成了。
使用svg在地图上绘制遮罩层svgOverlay。
var mapAreaPathArr=[{
"indexId":0,
"fillColor":"#0072ff",
"textBounds":[-40.1875,106.65625 ],
"centerBounds":[-40.25,110.5625 ],
"text":"A区",
"strokeWidth":2,
"path":'M1369.3,576.6c-0.5-0.2-0.9-0.5-1.4-0.6c-12.6-3-21.6-10.6-28-21.6c-1.8-3-2.6-6.3-2.4-9.9 c0.4-5.8-1.6-10.8-6-14.8c-2.2-2-4.3-4-6.6-5.9c-3.1-2.7-6.1-5.4-8.9-8.4c-3.2-3.4-5.9-7.1-7.8-11.3c-2.8-6.1-3.2-12.3-1.2-18.7 c1.4-4.6,3.7-8.7,6.4-12.6c3.3-4.8,7-9.1,10.9-13.4c7.6-8.2,15.4-16.1,23.5-23.7c5-4.7,10.4-9.1,16.4-12.7 c4.8-2.9,9.9-5.3,15.5-6.4c6.4-1.2,12.5-0.2,18.1,3.2c2.9,1.7,5.4,3.9,7.6,6.4c1.5,1.6,3,3.2,4.8,4.4c4.7,3.3,9.6,3.9,14.9,1.7 c2.1-0.9,4.1-2.1,5.9-3.5c2-1.6,4-3.2,5.8-5c2.9-2.9,5.8-5.8,8.5-8.8c7-7.7,9.9-16.8,9.5-27.1c-0.3-7.4-2-14.4-4.6-21.3 c-1.9-5.3-4.2-10.5-6.2-15.7c-1.1-2.7-2-5.5-3-8.3c-0.6-1.6-0.7-3.3-0.5-4.9c0.2-2.5,1.4-4.3,3.6-5.5c1.6-0.8,3.3-1.2,5.2-1.3 c3.7-0.2,7.4,0.1,11,1.1c34.2,9,68,18.9,100.9,31.9c13.8,5.4,27.3,11.5,40.6,18.1c4.9,2.4,9.9,4.9,14.8,7.4c2,1,4,2.2,6,3.5 c1.6,1,3.1,2.3,4.3,3.8c4.5,5.1,4.9,11,1.1,16.5c-2.3,3.4-4.6,6.8-6.7,10.3c-11.2,18.9-19.8,39-26.3,59.9c-0.7,2.2-1.4,4.4-2.1,6.6 c-1,2.9-2.5,5.6-4.4,8.1c-4,5.3-9.4,8.2-15.9,8.9c-6.7,0.7-13.3-0.3-19.7-2c-6.6-1.7-12.9-4-19.4-6.1c-4.2-1.4-8.5-2.8-12.9-3.7 c-5.1-1.1-10.1-0.9-15,1.2c-7.2,3-14.1,6.7-20.8,10.6c-17.6,10.2-34.5,21.5-51.2,33.2c-13.1,9.2-26,18.6-38.5,28.7 c-4.4,3.6-9.3,6.2-15,7.4c-0.3,0.1-0.6,0.4-0.9,0.6C1376,576.6,1372.6,576.6,1369.3,576.6z',
"arealnglat":[[-32.1875,107.9375 ],[-36.9375,123.5625],[-45.375,120],[-45.3125,114],[-51.5625,103.625],[-43.4375,96.25],[-37.4375,103.25],[-32.1875,107.75]]
},{},{}];
function setMapSvgHtml(map,Data){
var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svgElement.setAttribute('xmlns', "http://www.w3.org/2000/svg");
svgElement.setAttribute('viewBox', "0 0 1987.5 586.5");
svgElement.setAttribute('class', "mapAreaSvg");
var html='';
var len=Data.length;
for(var i=0;i ';
var polyonOwn= L.polygon(Data[i].arealnglat, {color: 'transparent',"indexId":i}).addTo(map);
polygonLayerArr.push(polyonOwn);
}
svgElement.innerHTML=html;
polygonSvgLayer=L.svgOverlay(svgElement, [[-2,-2],[-55.75,150.25]],{opacity:0.7}).addTo(map);
}
遮罩层区域文字标注
function setMapAreaTextHtml(map,el){
var myIcon = L.divIcon({
html: el.text,
className: 'mapAreaText',
iconSize:0
});
var areaTextlayer=L.marker(el.textBounds, { icon: myIcon,indexId:el.indexId}).addTo(map);
mapAreaTextArr.push(areaTextlayer);
};
注意:
全屏控制----如果地图有用到svg遮罩层,得要放在最后一步执行完成后初始化,不然苹果13会影响遮罩层显示,或者事件点击
var fsControl = L.control.fullscreen({
position: 'topright'
});
map.addControl(fsControl);
/**
* 判断点是否多边形内
* @param {Point} point 点对象
* @param {Polyline} polygon 多边形对象
* @returns {Boolean} 点在多边形内返回true,否则返回false
*/
function isPointInArea(point, pts) {
// 下述代码来源:http:// paulbourke.net/geometry/insidepoly/,进行了部分修改
// 基本思想是利用射线法,计算射线与多边形各边的交点,如果是偶数,则点在多边形外,否则
// 在多边形内。还会考虑一些特殊情况,如点在多边形顶点上,点在多边形边上等特殊情况。
const N = pts.length
const boundOrVertex = true // 如果点位于多边形的顶点或边上,也算做点在多边形内,直接返回true
let intersectCount = 0// cross points count of x
const precision = 2e-10 // 浮点类型计算时候与0比较时候的容差
let p1, p2// neighbour bound vertices
const p = point // 测试点
p1 = pts[0]// left vertex
// console.log(pts,N)
for (let i = 1; i <= N; ++i) {
// check all rays
if (p.equals(p1)) {
return boundOrVertex// p is an vertex
}
p2 = pts[i % N]
// right vertex
if (p.lat < Math.min(p1.lat, p2.lat) || p.lat > Math.max(p1.lat, p2.lat)) {
// ray is outside of our interests
p1 = p2
continue
// next ray left point
}
if (p.lat > Math.min(p1.lat, p2.lat) && p.lat < Math.max(p1.lat, p2.lat)) {
// ray is crossing over by the algorithm (common part of)
if (p.lng <= Math.max(p1.lng, p2.lng)) {
// x is before of ray
if (p1.lat === p2.lat && p.lng >= Math.min(p1.lng, p2.lng)) {
// overlies on a horizontal ray
return boundOrVertex
}
if (p1.lng === p2.lng) {
// ray is vertical
if (p1.lng === p.lng) {
// overlies on a vertical ray
return boundOrVertex
} else {
// before ray
++intersectCount
}
} else {
// cross point on the left side
const xinters = (p.lat - p1.lat) * (p2.lng - p1.lng) / (p2.lat - p1.lat) + p1.lng
// cross point of lng
if (Math.abs(p.lng - xinters) < precision) {
// overlies on a ray
return boundOrVertex
}
if (p.lng < xinters) {
// before ray
++intersectCount
}
}
}
} else {
// special case when ray is crossing through the vertex
if (p.lat === p2.lat && p.lng <= p2.lng) {
// p crossing over p2
const p3 = pts[(i + 1) % N]
// next vertex
if (p.lat >= Math.min(p1.lat, p3.lat) && p.lat <= Math.max(p1.lat, p3.lat)) {
// p.lat lies between p1.lat & p3.lat
++intersectCount
} else {
intersectCount += 2
}
}
}
p1 = p2
// next ray left point
}
if (intersectCount % 2 === 0) {
// 偶数在多边形外
return false
} else {
// 奇数在多边形内
return true
}
}
/**
* 判断点是否在矩形内
* @param {Point} point 点对象
* @param {Bounds} bounds 矩形边界对象
* @returns {Boolean} 点在矩形内返回true,否则返回false
*/
function isPointInRect(point, bounds) {
const sw = bounds.getSouthWest()
// 西南脚点
const ne = bounds.getNorthEast()
// 东北脚点
return (point.lng >= sw.lng && point.lng <= ne.lng && point.lat >= sw.lat && point.lat <= ne.lat)
}