早睡早起,方能养生
Sleep early rise early, way to keep healthy

PHP地理围栏与后台地图绘制全解析

super
2021-09-29 18:24
views 6013

2021-10-11日更新:PHP地理围栏与后台地图绘制全解析(二),绘制图形的修改

 

使用腾讯地图制作地理围栏

 

 

  1. 1) 申请腾讯地图key

 

 

  1. 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. 1. 注意绘制多边形时, 绘制顺序一定最好是由左上开始绘制, 不会计算时可能会出错

 

2. 如果你在API里面用了地理围栏算法, 请将这一块儿修改为 return 0;  我是由于做后台演示所以返回了json格式的数据.

 

 

3. 关于绘制图形那块的文档在 https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocEditor

 

有啥不明白的发邮件或者留言吧 

此文章不允许转载



分享
0 条讨论
top