基于leaflet.js实现的多边形绘制,附带可以判断点是否再多边形内,本着无私奉献的原则,这里直接将js组件代码贴上,大家需要可以自取,记得点赞。
先看下效果图是不是你想要的:
- 绘制过程中:
DrawTool.js: 自己封装的画布js文件,在使用时引入即可,需要大家自己创建一个js,把以下代码copy进去
/**
* 画布js,用于在地图中画多边形,点击以绘画,右键回退,绘制完成后右键取消,可以通过覆盖DrawTool.onDoubleClick函数来实现双击操作,
* 通过DrawTool.iconUrl='path'来设置绘制图像的点图标
*/
var DrawTool = {};
DrawTool.points = [];// 绘制图形的点纬度和经度集合 【【】,【】】
DrawTool.markers=[];// 标记图标
DrawTool.lines = new L.polyline(DrawTool.points);
DrawTool.tempLines = new L.polyline([],{dashArray: 12});
DrawTool.polygons = new L.polygon(DrawTool.points);
DrawTool.tempPolygons = new L.polygon([],{color: 'none',fillColor: 'red'});
// 双击执行的函数
DrawTool.onDoubleClick = function (e){
alert("选定完成: \n "+ JSON.stringify(DrawTool.points));
return false;
}
DrawTool.init = function (map,iconUrl){
// var circldRadius = 50;
map.off('click');
map.on('click', onClick); //点击地图
map.off('contextmenu');
map.on('contextmenu', contextmeanClick); // 右键地图
function onClick(e) {
var marker;
if(iconUrl){
marker = L.marker(e.latlng,{icon: L.icon({iconUrl: 'img/move.png', iconSize: [25,25]})});
}else{
marker = L.marker(e.latlng);
}
if(DrawTool.points.length <= 0){
marker.on('click',clickMarker);
}
marker.addTo(map);
DrawTool.points.push([e.latlng.lat, e.latlng.lng]);
DrawTool.lines .addLatLng(e.latlng);
map.addLayer(DrawTool.lines );
// map.addLayer(marker);
DrawTool.markers.push(marker);
map.on('mousemove', onMove);//鼠标移动
}
function onMove(e) {
if (DrawTool.points.length > 0) {
ls = [DrawTool.points[DrawTool.points.length - 1], [e.latlng.lat, e.latlng.lng]];
DrawTool.tempLines.setLatLngs(ls);
map.addLayer(DrawTool.tempLines);
DrawTool.tempPolygons.setLatLngs(DrawTool.points);
DrawTool.tempPolygons.addTo(map);
}
}
// 右键回退一步
function contextmeanClick(e){
DrawTool.points.splice(DrawTool.points.length - 1,1);
if (DrawTool.points.length > 0) {
ls = [DrawTool.points[DrawTool.points.length - 1], [e.latlng.lat, e.latlng.lng]];
DrawTool.tempLines.setLatLngs(ls);
map.addLayer(DrawTool.tempLines);
DrawTool.tempPolygons.setLatLngs(DrawTool.points);
DrawTool.tempPolygons.addTo(map);
DrawTool.lines .remove();
DrawTool.lines = new L.polyline(DrawTool.points);
DrawTool.lines .addTo(map);
DrawTool.markers[DrawTool.markers.length - 1].remove();
DrawTool.markers.splice(DrawTool.markers.length - 1,1);
}else{
DrawTool.tempLines.remove();
DrawTool.lines .remove();
if(DrawTool.markers.length > 0){
DrawTool.markers[DrawTool.markers.length - 1].remove();
DrawTool.markers.splice(DrawTool.markers.length - 1,1);
}
DrawTool.points =[];
DrawTool.markers = [];
DrawTool.lines = new L.polyline(DrawTool.points);
}
}
// 点击第一个节点停止绘画
function clickMarker(e){
// 关闭画图事件
map.off('dblclick');
map.off('mousemove');
map.off('click');
// 停止右键事件并且绑定新的
map.off('contextmenu'); // 右键地图
map.on('contextmenu', cancle); // 右键地图
// 移除临时线和面
DrawTool.tempLines.remove();
DrawTool.tempPolygons.remove();
DrawTool.polygons = L.polygon(DrawTool.points);
DrawTool.polygons.addTo(map);
DrawTool.polygons.on('dblclick', DrawTool.onDoubleClick);
// 移动标记(微调)
for(var marker of DrawTool.markers){
marker.dragging.enable();
marker.on('dragend',function(event){
resetRegion();
});
}
}
// 调整了坐标点
function resetRegion(){
DrawTool.points = [];
for(var marker of DrawTool.markers){
DrawTool.points.push([marker.getLatLng().lat,marker.getLatLng().lng]);
};
DrawTool.polygons.remove();
DrawTool.lines .remove();
DrawTool.polygons = L.polygon(DrawTool.points);
DrawTool.polygons.on('dblclick', DrawTool.onDoubleClick);
DrawTool.polygons.addTo(map);
}
// 右键取消
function cancle(e){
map.on('click',onClick);
map.off('contextmenu');
map.on('contextmenu', contextmeanClick); // 右键地图
for(var marker of DrawTool.markers){
marker.remove();
}
DrawTool.points = [];
DrawTool.markers=[];
DrawTool.lines .remove();
DrawTool.tempLines.remove();
DrawTool.tempPolygons.remove();
DrawTool.polygons.remove();
DrawTool.polygons = new L.polygon(DrawTool.points);
DrawTool.lines = new L.polyline(DrawTool.points);
DrawTool.tempLines = new L.polyline([],{dashArray: 12});
DrawTool.tempPolygons = new L.polygon([],{color: 'none',fillColor: 'red'});
}
}
赠送一个判断点是否在多边形内部的工具类GeoUtils.js:
var GeoUtils = {};
/**
* 判断点是否在矩形内
* @param {Point} point 点对象
* @param {Bounds} bounds 矩形边界对象
* @returns {Boolean} 点在矩形内返回true,否则返回false
*/
GeoUtils.isPointInRect = function (point, bounds) {
var sw = bounds.getSouthWest(); //西南脚点
var ne = bounds.getNorthEast(); //东北脚点
return (point.lng >= sw.lng && point.lng <= ne.lng && point.lat >= sw.lat && point.lat <= ne.lat);
}
/**
* 判断点是否多边形内
* @param {Point} point 点对象
* @param {Polyline} polygon 多边形对象
* @returns {Boolean} 点在多边形内返回true,否则返回false
*/
GeoUtils.isPointInPolygon = function (point, polygon) {
//首先判断点是否在多边形的外包矩形内,如果在,则进一步判断,否则返回false
var polygonBounds = polygon.getBounds();
if (!this.isPointInRect(point, polygonBounds)) {
return false;
}
var pts = polygon.getLatLngs()[0];//获取多边形点
//下述代码来源:http://paulbourke.net/geometry/insidepoly/,进行了部分修改
//基本思想是利用射线法,计算射线与多边形各边的交点,如果是偶数,则点在多边形外,否则
//在多边形内。还会考虑一些特殊情况,如点在多边形顶点上,点在多边形边上等特殊情况。
var N = pts.length;
var boundOrVertex = true; //如果点位于多边形的顶点或边上,也算做点在多边形内,直接返回true
var intersectCount = 0;//cross points count of x
var precision = 2e-10; //浮点类型计算时候与0比较时候的容差
var p1, p2;//neighbour bound vertices
var p = point; //测试点
p1 = pts[0];//left vertex
for (var 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
var 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
var 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;
}
}
一个简单的样例Demo(大家需要根据自己的情况适当修改):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>绘制多边形</title>
<!-- 这些是leaflet。js的依赖,必须引入 -->
<link rel="stylesheet" type="text/css" href="../leaflet/dist/leaflet.css">
<script type="text/javascript" src="../leaflet/dist/leaflet.js"></script>
<script type="text/javascript" src="../lib/leaflet.motion.min.js"></script>
<!-- 引入画布js -->
<script type="text/javascript" src="js/DrawTool.js"></script>
<!-- 引入判断点是否在多边形内 -->
<script type="text/javascript" src="js/GeoUtils.js"></script>
<style type="text/css">
body {
padding: 0;
margin: 0;
}
html,
body,
#map {
height: 100%;
}
</style>
</head>
<body>
<div id='map'></div>
</body>
<script type="text/javascript">
// 加载的地图地址,这里需要修改为你们自己的 todo
var url = 'http://localhost:9090/img/{z}/{x}/{y}.png';
var map = L.map('map', {
center: [34.694, 113.587],
zoom: 6,
zoomControl: false
});
//将图层加载到地图上,并设置最大的聚焦还有map样式
L.tileLayer(url, {
maxZoom: 18,
minZoom: 3
}).addTo(map);
// 以下代码是绘制多边形的操作
// 初始化画布,给定地图容器,第一个参数是地图对象,第二个参数是绘制时图标的样式,第二个参数可以不写
DrawTool.init(map,'img/move.png');
// 通过重写双击方法来实现双击执行一系列操作,通常是获取多边形的点集合,以做其他操作
DrawTool.onDoubleClick = function(e){
// 打印绘制多边形的点集合
console.log(JSON.stringify(DrawTool.points));
// console.log(DrawTool.polygon);
var marker = L.marker([34.694, 113.587]);
marker.addTo(map);
alert('我就是这么强大!啦啦啦啦啦!!!点是否在多边形内 >> '+GeoUtils.isPointInPolygon(marker.getLatLng(),DrawTool.polygon));
}
</script>
</html>
使用说明:
在绘制时,单击鼠标右键会回退一步,单击鼠标左键进行绘制,绘制完成后可以通过拖动图标进行微调,此时单机鼠标左键会直接将绘制完成的图片清除。
福利
附上一个好用的离线地图,能够进行瓦片下载和交互,支持街道图、卫星图、内置的有开发手册,亲测可用