laravel示例代码

<?php
/*
 * Description: Redis GEO,地理位置信息
 * Author: Shuxiaoyuan
 * Email: sxy@shuxiaoyuan.com
 * DateTime: 2021/12/30 15:49
 *
 * 参考链接:
 * 1.百度拾取坐标系统:http://api.map.baidu.com/lbsapi/getpoint/
 * 2.GeoHash核心原理解析:https://www.cnblogs.com/LBSer/p/3310455.html
 */

namespace App\Http\Controllers\Redis;

use App\Common\Tools;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redis;

/**
 * Description: Redis GEO
 * GeoHash 算法将二维的经纬度数据映射到一维的整数,这样所有的元素都将在挂载到一条线上,距离靠近的二维坐标映射到一维后的点之间距离也会很接近。
 * 当我们想要计算「附近的人时」,首先将目标位置映射到这条线上,然后在这个一维的线上获取附近的点就行了。
 * 那这个映射算法具体是怎样的呢?
 * 它将整个地球看成一个二维平面,然后划分成了一系列正方形的方格,就好比围棋棋盘。
 * 所有的地图元素坐标都将放置于唯一的方格中。方格越小,坐标越精确。然后对这些方格进行整数编码,越是靠近的方格编码越是接近。
 * 那如何编码呢?一个最简单的方案就是切蛋糕法。设想一个正方形的蛋糕摆在你面前,二刀下去均分分成四块小正方形,这四个小正方形可以分别标记为 00,01,10,11 四个二进制整数。
 * 然后对每一个小正方形继续用二刀法切割一下,这时每个小小正方形就可以使用 4bit 的二进制整数予以表示。
 * 然后继续切下去,正方形就会越来越小,二进制整数也会越来越长,精确度就会越来越高。
 * 上面的例子中使用的是二刀法,真实算法中还会有很多其它刀法,最终编码出来的整数数字也都不一样。
 * 编码之后,每个地图元素的坐标都将变成一个整数,通过这个整数可以还原出元素的坐标,整数越长,还原出来的坐标值的损失程度就越小。对于「附近的人」这个功能而言,损失的一点精确度可以忽略不计。
 */
class RedisGeoController extends Controller
{
    public $key = 'RedisGeoController:';

    /**
     * Description: 添加地理位置的坐标
     * Author: Shuxiaoyuan
     * Email: sxy@shuxiaoyuan.com
     * DateTime: 2021/12/30 16:02
     *
     * @param Request $request
     *
     * @return array
     */
    public function geoadd(Request $request)
    {
        $key = $this->key . __FUNCTION__;

        /**
         * 上海几个地点,经纬度
         * 浦东机场:121.813454,31.152136
         * 虹桥T2:121.33267,31.20067
         * 东方明珠:121.506231,31.245524
         * 上海南站:121.437097,31.159724
         * 龙阳路磁悬浮:121.563783,31.208983
         */
        $geos = [
            ['121.813454', '31.152136', '浦东机场'],
            ['121.33267', '31.20067', '虹桥T2'],
            ['121.506231', '31.245524', '东方明珠'],
            ['121.437097', '31.159724', '上海南站'],
            ['121.464752', '31.25593', '上海火车站'],
            ['121.563783', '31.208983', '龙阳路磁悬浮'],
        ];
        foreach ($geos as $geo) {
            /**
             * 存储指定的地理空间位置
             * 语法:GEOADD key longitude latitude member [longitude latitude member ...]
             * longitude:经度
             * latitude:维度
             * member:位置名称,唯一(有序集合,每一个位置都对应一个分数)
             */
            Redis::geoadd($key, $geo[0], $geo[1], $geo[2]);
        }

        $data = [
            'redis_key'  => $key,
            'geos'       => $geos,
            'redis_geos' => Redis::ZRANGE($key, 0, -1, true),
        ];

        return Tools::outSuccessInfo($data);
    }

    /**
     * Description: 获取地理位置的坐标
     * Author: Shuxiaoyuan
     * Email: sxy@shuxiaoyuan.com
     * DateTime: 2021/12/30 16:02
     *
     * @param Request $request
     *
     * @return array|void
     */
    public function geopos(Request $request)
    {
        $key     = $this->key . $request->input('redis_key');
        $address = $request->input('address');

        if (!Redis::exists($key)) {
            return Tools::outErrorInfo(__LINE__, 'redis 键' . '<' . $key . '>' . '不存在');
        }

        /**
         * 从给定的 key 里返回所有指定名称(member)的位置(经度和纬度),不存在的返回 nil。
         * 语法:GEOPOS key member [member ...]
         */
        $geo = Redis::geopos($key, $address);

        $data = [
            'redis_key' => $key,
            'geo'       => $geo,
        ];

        return Tools::outSuccessInfo($data);
    }

    /**
     * Description: 计算两个位置之间的距离
     * Author: Shuxiaoyuan
     * Email: sxy@shuxiaoyuan.com
     * DateTime: 2021/12/30 16:03
     *
     * @param Request $request
     *
     * @return array
     */
    public function geodist(Request $request)
    {
        $key     = $this->key . $request->input('redis_key');
        $member1 = $request->input('member1', '浦东机场');
        $member2 = $request->input('member2', '虹桥T2');

        if (!Redis::exists($key)) {
            return Tools::outErrorInfo(__LINE__, 'redis 键' . '<' . $key . '>' . '不存在');
        }

        /**
         * 计算两个位置之间的距离
         * GEODIST key member1 member2 [m|km|ft|mi]
         * m :米,默认单位
         * km :千米
         * mi :英里
         * ft :英尺
         */
        $distance = Redis::geodist($key, $member1, $member2);

        $data = [
            'redis_key' => $key,
            'distance'  => $distance,
            'member1'   => $member1,
            'member2'   => $member2,
        ];

        return Tools::outSuccessInfo($data);
    }

    /**
     * Description: 根据经纬度坐标来获取指定范围内的地理位置集合
     * Author: Shuxiaoyuan
     * Email: sxy@shuxiaoyuan.com
     * DateTime: 2021/12/30 16:03
     *
     * @param Request $request
     *
     * @return array|void
     */
    public function georadius(Request $request)
    {
        $key = $this->key . $request->input('redis_key');

        // 2号线,人民广场站经纬度:121.481887,31.238603
        $lng = $request->input('lng', '121.481887');
        $lat = $request->input('lat', '31.238603');

        // 半径范围。人民广场离东方明珠:2.7公里,距上海火车站:2.3公里
        $radius = $request->input('radius', 10000);

        if (!Redis::exists($key)) {
            return Tools::outErrorInfo(__LINE__, 'redis 键' . '<' . $key . '>' . '不存在');
        }

        /**
         * 根据经纬度坐标来获取指定范围内的地理位置集合
         * GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
         * longitude:经度
         * latitude:纬度
         * radius:半径
         * m|km|ft|mi:米|千米|英里|英尺
         * WITHCOORD:将位置元素的经度和纬度也一并返回
         * WITHDIST:在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回
         * WITHHASH:以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值
         * COUNT count:限定返回的记录数
         * ASC|DESC:结果排序
         * STORE key
         * STOREDIST key
         */
        // 源码在:vendor/predis/predis/src/Command/GeospatialGeoRadius.php
        $geos = Redis::georadius($key, $lng, $lat, $radius, 'm', ['WITHCOORD', 'WITHDIST', 'ASC', 'COUNT' => 4]);
        $data = [
            'redis_key' => $key,
            'lng'       => $lng,
            'lat'       => $lat,
            'radius'    => $radius,
            'geos'      => $geos
        ];

        return Tools::outSuccessInfo($data);
    }

    /**
     * Description: 根据储存在位置集合里面的某个地点获取指定范围内的地理位置集合,重点,重点,重点
     * Author: Shuxiaoyuan
     * Email: sxy@shuxiaoyuan.com
     * DateTime: 2021/12/30 16:03
     *
     * @param Request $request
     *
     * @return array|void
     */
    public function georadiusbymember(Request $request)
    {
        $key    = $this->key . $request->input('redis_key');
        $member = $request->input('member', '东方明珠');

        // 半径范围。东方明珠距离人民广场:2.7km、上海火车站:4.1km
        $radius = $request->input('radius', 10000);

        if (!Redis::exists($key)) {
            return Tools::outErrorInfo(__LINE__, 'redis 键' . '<' . $key . '>' . '不存在');
        }

        /**
         * GEORADIUSBYMEMBER key member radius M|KM|FT|MI [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC|DESC] [STORE key] [STOREDIST key]
         * member:
         * radius:半径
         * m|km|ft|mi:米|千米|英里|英尺
         * WITHCOORD:将位置元素的经度和纬度也一并返回
         * WITHDIST:在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回
         * WITHHASH:以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值
         * COUNT count:限定返回的记录数
         * ASC|DESC:结果排序
         * STORE key
         * STOREDIST key
         */
        // 源代码位置:vendor/predis/predis/src/Command/GeospatialGeoRadiusByMember.php、vendor/predis/predis/src/Command/GeospatialGeoRadius.php
        $options = ['WITHCOORD', 'WITHDIST', 'WITHHASH', 'DESC', 'COUNT' => 30];
        $geos    = Redis::georadiusbymember($key, $member, $radius, 'm', $options);

        $data = [
            'redis_key' => $key,
            'member'    => $member,
            'radius'    => $radius,
            'geos'      => $geos
        ];

        return Tools::outSuccessInfo($data);
    }

    /**
     * Description: Redis GEO 使用 geohash 来保存地理位置的坐标,返回一个或多个位置对象的 geohash 值
     * Author: Shuxiaoyuan
     * Email: sxy@shuxiaoyuan.com
     * DateTime: 2021/12/30 16:03
     *
     * @param Request $request
     *
     * @return array
     */
    public function geohash(Request $request)
    {
        $key    = $this->key . $request->input('redis_key');
        $member = $request->input('member', '浦东机场');

        if (!Redis::exists($key)) {
            return Tools::outErrorInfo(__LINE__, 'redis 键' . '<' . $key . '>' . '不存在');
        }

        // GEOHASH key member [member ...]
        $geohash = Redis::geohash($key, $member);

        $data = [
            'redis_key' => $key,
            'member'    => $member,
            'geohash'   => $geohash,
        ];

        return Tools::outSuccessInfo($data);
    }
}