PHP地理围栏与后台地图绘制全解析
super
2021-09-29 18:24
6895
2021-10-11日更新:PHP地理围栏与后台地图绘制全解析(二),绘制图形的修改
使用腾讯地图制作地理围栏
- 1) 申请腾讯地图key
- 2) 上代码!!
关于Html源代码需要注意的点:
① Html使用的layuiadmin已取得授权
② 由于layuiadmin关联文件较多, 我不会把admin css和admin js相关代码贴出来
③ 为不影响使用, 你需要将 Html底部的保存事件 重写
④ 由于没有layuiadmin css 页面将会很乱 , 你需要对地图某些css进行微调, 效果如上图1
⑤ 代码仅展示 多边形围栏 及相关计算
⑥ 关于Html中的 店铺名称 和 详细地址 可不必理会
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>绘制几何图形</title>
<link rel="stylesheet" href="/static/layuiadmin/layui/css/layui.css" media="all">
<link rel="stylesheet" href="/static/layuiadmin/style/admin.css" media="all">
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&libraries=tools&key=XXXX----你的key----OOOO"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 80%;
}
#toolControl {
position: absolute;
top: 100px;
left: 0px;
right: 0px;
margin: auto;
width: 252px;
z-index: 1001;
}
.toolItem {
width: 30px;
height: 30px;
float: left;
margin: 1px;
padding: 4px;
border-radius: 3px;
background-size: 30px 30px;
background-position: 4px 4px;
background-repeat: no-repeat;
box-shadow: 0 1px 2px 0 #e4e7ef;
background-color: #ffffff;
border: 1px solid #ffffff;
}
.toolItem:hover {
border-color: #789cff;
}
.active {
border-color: #d5dff2;
background-color: #d5dff2;
}
#marker {
background-image: url('https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/marker_editor.png');
}
#polyline {
background-image: url('https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/polyline.png');
}
#polygon {
background-image: url('https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/polygon.png');
}
#circle {
background-image: url('https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/circle.png');
}
#rectangle {
background-image: url('https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/rectangle.png');
}
#ellipse {
background-image: url('https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/ellipse.png');
}
/* 搜索 */
#panel {
position: absolute;
background: #FFF;
width:350px;
padding: 20px;
z-index: 9999;
top: 65px;
left: 30px;
}
#suggestionList {
list-style-type: none;
padding: 0;
margin: 0;
}
#suggestionList li {
cursor: pointer;
}
#suggestionList li:hover, #suggestionList li:focus {
background-color: #ebf2ff;
color: #0062FF;
}
#suggestionList li a {
margin-top: -1px;
background-color: #f6f6f6;
text-decoration: none;
font-size: 18px;
color: black;
display: block;
}
#suggestionList li .item_info{
font-size: 12px;
color:grey;
}
#suggestionList li a:hover:not(.header) {
background-color: #eee;
}
</style>
<body onload="initMap()">
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">新增店铺</div>
<div class="layui-card-body" style="padding: 15px;">
<form class="layui-form" action="" lay-filter="component-form-group">
<div class="layui-form-item">
<label class="layui-form-label"><span style="color:red;">*</span>店铺名称</label>
<div class="layui-input-block">
<input type="text" name="name" autocomplete="off" placeholder="请输入店铺名称" class="layui-input" value="{$data.name}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"><span style="color:red;">*</span>详细地址</label>
<div class="layui-input-block">
<textarea name="add_info" class="layui-textarea" cols="30" rows="2" placeholder="建议在25字左右">{$data.add_info}</textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"><span style="color:red;">*</span>选择位置</label>
<div class="layui-input-block">
<div style="display: flex; margin-bottom: 5px">
<input type="text" id="address" name="address" class="layui-input" placeholder="请在地图上绘制图形" value="" readonly>
<button type="button" class="layui-btn layui-btn-primary" onclick="clearMapChoose();">重置</button>
</div>
<!--<input type="text" id="search" oninput="inputTip(this);" name="search" class="layui-input" placeholder="搜索" value="" >-->
<div id="panel">
<p>输入关键字,将展示相关地点提示,点击提示可定位到该处。</p>
<input id='keyword' name="searchAddress" type="text" value='尚峰座' />
<input id="search" type="button" class="btn" value="搜索" onclick="searchByKeyword()" />
<input id="clearSearch" type="button" class="btn" value="清空" onclick="clearSearchResult()" />
<ul id="suggestionList">
</ul>
</div>
<div id="container" style="width: 100%; height: 500px;margin-top: 5px"></div>
<div id="toolControl">
<div class="toolItem" id="marker" title="点标记" style="display: none"></div>
<div class="toolItem" id="polyline" title="折线" style="display: none"></div>
<div class="toolItem active" id="polygon" title="多边形"></div>
<div class="toolItem" id="circle" title="圆形" style="display: none"></div>
<div class="toolItem" id="rectangle" title="矩形" style="display: none"></div>
<div class="toolItem" id="ellipse" title="椭圆" style="display: none"></div>
</div>
<div>
绘制:鼠标左键点击及移动即可绘制图形。<span style="color: red">绘制顺序:【左上】->【右上】->【右下】->【左下】!!!</span><br />
结束绘制:鼠标左键双击即可结束绘制。<!--折线、多边形、多边形会自动闭合;圆形、椭圆单击即可结束--><br />
中断:绘制过程中按下esc键可中断该过程。<br />
删除:选中图形后按下delete键或"重置"按钮可删除图形。
</div>
</div>
</div>
<div class="layui-form-item layui-layout-admin">
<div class="layui-input-block">
<div class="layui-footer" style="left: 0; z-index: 9999;">
<button class="layui-btn" lay-submit="" lay-filter="submitbtn">保存</button>
<button type="button" class="layui-btn layui-btn-primary" onclick="clearInfo();">清空</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
<script src="/static/layuiadmin/layui/layui.js"></script>
<script>
var $;
layui.config({
base: '/static/layuiadmin/' //静态资源所在路径
}).extend({
index: 'lib/index' //主入口模块
}).use(['index', 'form'], function () {
var $ = layui.$,
form = layui.form;
form.on('submit(submitbtn)', function(data){
$.ajax({
url:"{:url('add')}",
type:"post",
dataType: "json",
data:data.field,
success:function(res){
console.log(res);
if (res.code == 200) {
layer.msg(res.msg, {icon: 1});
} else {
layer.msg(res.msg, {icon: 5});
}
}
})
return false;
});
});
function clearInfo() {
var $ = layui.$;
$("input[name='name']").val('');
$("textarea[name='add_info']").val('');
clearMapChoose();
}
function searchByKeyword() {
var $ = layui.$;
var keyword = $('#keyword').val();
if (keyword) {
$.post("{:url('getAddress')}", {keyword: keyword}, function (data) {
var _li = '';
if (data.status === 0 && data.message === 'query ok' && data.data.length > 0) {
$.each(data.data, function (k, v) {
_li += '<li onclick="changeAddress(this);" data-title="'+v.title+'" data-address="'+v.address+'" data-lat="'+v.location.lat+'" data-lng="'+v.location.lng+'">' + v.city + ' - ' +v.title + '</li>';
});
$('#suggestionList').html(_li);
} else {
$('#suggestionList').html(_li);
}
}, 'json');
}
}
function clearSearchResult() {
var $ = layui.$;
$('#keyword').val('');
$('#suggestionList').html('');
}
var infoWindow,marker;
function changeAddress(obj) {
var $ = layui.$;
if (infoWindow) {
infoWindow.close();
}
if (marker) {
marker.setMap(null);
marker = null;
}
var lat = $(obj).data('lat');
var lng = $(obj).data('lng');
var title = $(obj).data('title');
var address = $(obj).data('address');
infoWindow = new TMap.InfoWindow({
map: map,
position: new TMap.LatLng(lat, lng),
content: '<h3>'+title+'</h3><p>'+address+'</p>',
offset: { x: 0, y: -50 },
});
map.setCenter(new TMap.LatLng(lat, lng));
marker = new TMap.MultiMarker({
id: 'marker-layer',
map: map
});
marker.add({
position: new TMap.LatLng(lat, lng)
});
}
// ======= 以下是地图相关代码 =======
var map; // 地图
var editor; // 编辑器
var activeType = 'polygon'; // 激活的图形编辑类型
var num = 0; // 这个没啥用 , 可以删了
var drawingID;
// 切换激活图层
document.getElementById('toolControl').addEventListener('click', (e) => {
var id = e.target.id;
if (id !== 'toolControl') {
document.getElementById(activeType).className = 'toolItem';
document.getElementById(id).className = 'toolItem active';
activeType = id;
editor.setActiveOverlay(id);
}
});
function initMap() {
// 初始化地图
map = new TMap.Map('container', {
zoom: 12, // 设置地图缩放级别
center: new TMap.LatLng(34.759215, 113.774887), // 设置地图中心点坐标
});
// 初始化几何图形及编辑器
editor = new TMap.tools.GeometryEditor({
// TMap.tools.GeometryEditor 文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocEditor
map: map, // 编辑器绑定的地图对象
overlayList: [
// 可编辑图层 文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocEditor#4
{
overlay: new TMap.MultiMarker({
map: map,
}),
id: 'marker',
drawingStyleId: 'marker',
},
{
overlay: new TMap.MultiPolyline({
map: map,
}),
id: 'polyline',
drawingStyleId: 'polyline',
},
{
overlay: new TMap.MultiPolygon({
map: map,
}),
id: 'polygon',
drawingStyleId: 'polygon',
},
{
overlay: new TMap.MultiCircle({
map: map,
}),
id: 'circle',
drawingStyleId: 'circle',
},
{
overlay: new TMap.MultiRectangle({
map: map,
}),
id: 'rectangle',
drawingStyleId: 'rectangle',
},
{
overlay: new TMap.MultiEllipse({
map: map,
}),
id: 'ellipse',
drawingStyleId: 'ellipse',
},
],
actionMode: TMap.tools.constants.EDITOR_ACTION.DRAW, // 编辑器的工作模式
selectable: true, // 开启点选功能
activeOverlayId: 'polygon', // 激活图层
snappable: true, // 开启吸附
});
// 监听绘制结束事件,获取绘制几何图形
editor.on('draw_complete', (geometry) => {
if (geometry.paths.length > 0) {
var latlng_str = '';
for (var i = 0; i < geometry.paths.length; i++) {
var l = geometry.paths[i].lng + ',' + geometry.paths[i].lat + '/';
latlng_str += l
}
document.getElementById('address').value = latlng_str;
}
num++;
// 顶部菜单栏消失
// document.getElementById('toolControl').style.display = 'none';
// 取消绘制状态
// editor.setActiveOverlay('');
// 设置操作状态为交互模式
editor.setActionMode(TMap.tools.constants.EDITOR_ACTION.INTERACT);
drawingID = geometry.id;
console.log('设置为操作模式');
});
// 监听修改事件
editor.on('adjust_complete', (geometry) => {
if (geometry.paths.length > 0) {
var latlng_str = '';
for (var i = 0; i < geometry.paths.length; i++) {
var l = geometry.paths[i].lng + ',' + geometry.paths[i].lat + '/';
latlng_str += l
}
document.getElementById('address').value = latlng_str;
}
})
// 监听几何图形删除事件
editor.on('delete_complete', (geometry) => {
// 清空输入框内的值
document.getElementById('address').value = '';
// 设置操作状态为绘制模式
editor.setActionMode(TMap.tools.constants.EDITOR_ACTION.DRAW);
})
}
function clearMapChoose() {
document.getElementById('address').value = '';
// 地图重置: 模式一
// 不推荐使用, 此方式会短时间内重载地图多次有加载失败的bug
// document.getElementById('container').innerHTML = '';
// document.getElementById('container').style = 'width: 100%';
// initMap();
// 地图重置: 模式二
editor.setActiveOverlay('polygon'); // 设置图层处于激活状态
editor.select([drawingID]); // 选中激活中图层内的几何图形
editor.delete(); // 删除已选中图形
editor.setActionMode(TMap.tools.constants.EDITOR_ACTION.DRAW);
console.log('设置为绘图模式');
// 移除窗体
if (infoWindow) {
infoWindow.close();
}
// 移除点标记
if (marker) {
marker.setMap(null);
marker = null;
}
}
</script>
</body>
</html>
PHP代码:
/**
* 围栏算法,判断坐标是否在围栏里(点越多越准确)
* @param array $fences 围栏数组 (左上 -> 右上 -> 右下 -> 左下) ['113.664673,34.810146','113.681667,34.796896','113.69231,34.794711','113.702009,34.809159']
* @param string $point 要判断的坐标
* @return mixed
*/
public function in_fences($fences, $point) {
$nvert = count($fences);
$vertx = [];
$verty = [];
list($testy, $testx) = explode(',', $point);
foreach ($fences as $r) {
list($lng, $lat) = explode(',', $r);
$vertx[] = $lat;
$verty[] = $lng;
}
try {
$i = $j = $c = 0;
for ($i = 0, $j = $nvert - 1; $i < $nvert; $j = $i++) {
if (( ($verty[$i] > $testy) != ($verty[$j] > $testy) ) &&
($testx < ($vertx[$j] - $vertx[$i]) * ($testy - $verty[$i]) / ($verty[$j] - $verty[$i]) + $vertx[$i]))
$c = !$c;
}
return $c;
} catch (Exception $e) {
echo json_encode(['code' => 500, 'msg' => '绘制错误 , 请按照要求方向绘制图形!']);
exit;
}
}
public function add()
{
if (Request::isAjax()) {
$address = input('address');
$address_array = explode('/', $address);
// 东站: 113.774887, 34.759215
if ($this->in_fences($address_array, '113.774887, 34.759215')) {
return ['code' => 200, 'msg' => '郑州东站在范围内!'];
} else {
return ['code' => 500, 'msg' => '郑州东站不在范围内!'];
}
}
return $this->fetch('Store/add');
}
public function getAddress()
{
$key = 'XXXX-----你的key----OOOO';
$keyword = input('keyword/s');
// 文档地址: https://lbs.qq.com/service/webService/webServiceGuide/webServiceSuggestion
$url = "https://apis.map.qq.com/ws/place/v1/suggestion/?region=郑州&keyword={$keyword}&key={$key}&policy=1®ion_fix=1";
$curl = curl_init();
// 设置抓取的url
curl_setopt($curl, CURLOPT_URL, $url);
// 设置头文件的信息作为数据流输出
curl_setopt($curl, CURLOPT_HEADER, false);
// 超时设置,以秒为单位
curl_setopt($curl, CURLOPT_TIMEOUT, 1);
// 超时设置,以毫秒为单位
// curl_setopt($curl, CURLOPT_TIMEOUT_MS, 500);
// 设置获取的信息以文件流的形式返回,而不是直接输出。
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
// 执行命令
$data = curl_exec($curl);
$error = curl_error($curl);
curl_close($curl);
echo $data;
}
3) 效果演示
注意:
- 1. 注意绘制多边形时, 绘制顺序一定最好是由左上开始绘制, 不会计算时可能会出错
2. 如果你在API里面用了地理围栏算法, 请将这一块儿修改为 return 0; 我是由于做后台演示所以返回了json格式的数据.
3. 关于绘制图形那块的文档在 https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocEditor
有啥不明白的发邮件或者留言吧
此文章不允许转载
0 条讨论