Redis6+PHP:实现根据经纬度计算出附近门店距离

2023-11-02

一. 开始介绍: Redis GEO
1.Redis GEO 主要用于存储地理位置信息,并对存储的信息进行操作,该功能在 Redis 3.2 版本新增。

Redis GEO 操作方法
geoadd 添加地理位置的坐标
geopos 获取地理位置的坐标
geodist 计算两个位置之间的距离
georadius 根据用户给定的经纬度坐标来获取指定范围内的地理位置集合
georadiusbymember 根据储存在位置集合里面的某个地点获取指定范围内的地理位置集合
geohash 返回一个或多个位置对象的 geohash 值
2.geoadd
 geoadd 用于存储指定的地理空间位置,可以将一个或多个经度(longitude)、纬度(latitude)、位置名称(member)添加到指定的 key 中。

3.geopos
 geopos 用于从给定的 key 里返回所有指定名称(member)的位置(经度和纬度),不存在的返回 nil。

4.geodist
 geodist 用于返回两个给定位置之间的距离。
最后一个距离单位参数说明:
m :米,默认单位。
km :千米。
mi :英里。
ft :英尺。

5.georadius
 georadius 以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。
m :米,默认单位。
km :千米。
mi :英里。
ft :英尺。
WITHDIST: 在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。 
WITHCOORD: 将位置元素的经度和维度也一并返回。
WITHHASH: 以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大。
COUNT 限定返回的记录数。
ASC: 查找结果根据距离从近到远排序。
DESC: 查找结果根据从远到近排序。

二. Redis6 + PHP 实现附近门店思路

1.首先在项目后台创建店铺的时候,一定要接通百度地图api,返回相对输入店铺地址的经纬度,保存到数据库后才能实现,没有经纬度无法实现 本人用的是laravel框架~~
  • 百度地图api地址 : https://lbsyun.baidu.com/index.php?title=webapi

  • 接下来是测试数据,如果对读者们有帮助,可以在实际项目中更具自己的思路加强改进~

  • 如果之前创建店铺的时候保存了经纬度,这个数据可以在数据库中读取,id是门店的id,longitude 经度,latitude纬度

  • 如果仔细的读者应该发现了,在这里用的语法是上文介绍过的,在这里只是把名称换成了店铺id,后面比较好和门店进行绑定

  • 可以看到redis库中已经有我们填写的测试数据了,接下来我们结合上文所说到的georadius方法进行取值

  • 这里使用的是asc升序排序,还有desc降序排序,一般附近门店第一个显示的是最近门店,所以用asc升序排序,单位是km 比较好查询

  • 显示的是km单位,把数据拿到后可以 *1000 改成m单位,更具自己的项目需求进行改变

  • 在这里遍历用店铺id做为数组的key

  • 在这将门店距离数据和门店信息数据组合

  • 最后组合成的数据,已经可以在前端小程序中渲染附近门店距离啦,这只是测试数据哦,真实数据要更具自己项目数据进行结合 对读者有帮助的话 帮我点个赞哦 期待留言~~

  • 成品效果哦~~

(ps: 最后可以设防,读取数据库门店信息条数和redis 储存的门店经纬度数据总数 进行对比,如果一致,则直接读取redis数据,否则重新查询数据库,将数据重新赋值给redis,可以减少数据库io查询)

代码块
//模拟门店 id 经度 纬度 
    $store_location = array(
        0 => array(
            'id' => 1,
            'longitude' => '113.956843',
            'latitude'  => '22.575094',
        ),
        1 => array(
            'id' => 2,
            'longitude' => '113.957746',
            'latitude'  => '22.574877',
        ),
        2 => array(
            'id' => 3,
            'longitude' => '113.957036',
            'latitude'  => '22.575545',
        ),
    );

    //遍历测试数据 循环在redis 插入 经纬度
    foreach ($store_location as $K => $item) {
        Redis::GeoAdd('store_location', $item['longitude'], $item['latitude'], $item['id']);
    }
    
    //在这里经纬度是写死的,在实际项目中这里传进来的经纬度是更具用户授权定位信息之后拿到的经纬度 
    $positions = Redis::GeoRadius('store_location', 113.955964, 22.574741, 1000, 'km', 'WITHDIST', 'asc');

    //在这里遍历用店铺id做为数组key
    $positions_container = [];
    foreach ($positions as $k => $v) {
        $positions_container[$v[0]] = $v;
    }
    
    //这里是门店信息测试数据,读者可以更具项目数据进行读取
    $stores = array(
        0 => array(
            'id'   => 1,
            'name' => 'ck',
        ),
        1 => array(
            'id'   => 2,
            'name' => 'myj',
        ),
        2 => array(
            'id'   => 3,
            'name' => 'tyb',
        ),
    );
    //门店信息容器
    $store_info = [];
    foreach ($stores as $k => $v) {  //遍历门店信息数据
        foreach ($positions_container as $kk => $vv) {  //遍历相对距离位置

            //如果数组key等于门店信息id 则把数据重新赋值给门店信息容器 store_info
            if ($kk = $v['id']) {
                $store_info[$kk]['id']    = $v['id'];
                $store_info[$kk]['name']  = $v['name'];
                $store_info[$kk]['apart'] = ($positions_container[$kk][1] * 1) * 1000;
            }
        }
    }
    //取出门店距离数组列
    $column = array_column($store_info, 'apart');
    //更具距离进行升序排序
    array_multisort($column, SORT_ASC, $store_info);
    dd($store_info);
    /**
     * 获取附近线下门店列表
     * @Author ZzzWClock
     * @param $longitude    经度
     * @param $latitude     纬度
     * @method get
     * @return json
     */
    public function queryOfflineStore()
    {   
        $longitude = req::get('longitude');
        $latitude  = req::get('latitude');

        // $longitude = '113.955964';
        // $latitude  = '22.574741';

        // 判断用户是否授权开启定位
        if (empty($longitude) && empty($latitude)) {
            return $this->resFailed(400, '请授权开启定位');
        }

        try {
            $offline_store_info = offlineStore::checkPosition($longitude, $latitude);
            return $this->resSuccess($offline_store_info);
        } catch (\Exception $e) {
            return $this->resFailed(400, $e->getMessage());
        }
        
    }
    /**
     * 根据经纬度获取附近门店
     * @Author ZzzWClock
     * @param  $longitude    经度
     * @param  $latitude     纬度
     * @param  $data         分页/筛选数据
     * @return array
     */
    public static function checkPosition(String $longitude, String $latitude, array $data = [])
    {
        
        // 线下门店信息
        $offline_store = self::queryOfflineStoreInfo($data);
        // 判断是否有线下门店
        if (!empty($offline_store)) {
            // 线下门店经纬度 添加进redis
            foreach ($offline_store as $key => $items) {
                Redis::GeoAdd('offline_location', $items['latitude'], $items['longitude'], $items['id']);
            }
            // 用户经纬度放入redis中计算距离
            $offline_location = Redis::GeoRadius('offline_location', $longitude, $latitude, 10000, 'km', 'WITHDIST', 'asc');
            // 遍历门店根据距离进行升序排序
            $store_info       = self::eachOfflineLocation($offline_location, $offline_store);

            return $store_info;
        } else {
            return $offline_store;
        }
        
    }
    
    /**
     * 查询线下门店信息
     * @Author ZzzWClock
     * @param  $data     分页/筛选数据
     * @return array
     */
    private static function queryOfflineStoreInfo(array $data = [])
    {
        // 分页/筛选数据
        // $data['page']     = $data['page']     ? $data['page'] : 1;
        // $data['pageSize'] = $data['pageSize'] ? $data['pageSize'] : 10;
        // 线下门店信息
        $offline_store = DB::table('shop_real_stores')
                            ->select([
                              'id',                     // 线下店铺id
                              'name',                   // 店铺名称
                              'brief',                  // 店铺描述
                              'mes',                    // 店铺信息
                              'province',               // 省
                              'city',                   // 市
                              'area',                   // 区
                              'street',                 // 街道
                              'longitude',              // 经度
                              'latitude',               // 纬度
                              'distribution',           // 配送距离
                              'business_hours',         // 营业时间
                              'address',                // 门店地址
                              'phone',                  // 门店电话
                              'image_url',              // 图片地址
                              'longitude',              // 经度
                              'latitude',               // 纬度
                            ])
                            ->where(['status' => 1])
                            ->lists();
        return $offline_store;
    }
    
    /**
     * 遍历线下门店经纬度索引 返回附近门店距离
     * @Author ZzzWClock
     * @param $offline_location    redis门店距离
     * @param $offline_store_info  线下门店信息
     * @return array
     */
    private static function eachOfflineLocation(array $param, array $offline_store_info)
    {
        // 遍历 redis 门店距离 更新索引
        $positions_container = [];
        foreach ($param as $k => $v) {
            $positions_container[$v[0]] = $v;
        }
        // 遍历 门店信息
        $store_info = [];
        foreach ($offline_store_info as $k => $v) {
            //二级遍历门店距离 通过key 找到相对应门店
            foreach ($positions_container as $kk => $vv) {
                if ($kk = $v['id']) {
                    $store_info[$k]['id']             = $v['id'];
                    $store_info[$k]['name']           = $v['name'];
                    $store_info[$k]['brief']          = $v['brief'];
                    $store_info[$k]['mes']            = $v['mes'];
                    $store_info[$k]['province']       = $v['province'];
                    $store_info[$k]['city']           = $v['city'];
                    $store_info[$k]['area']           = $v['area'];
                    $store_info[$k]['street']         = $v['street'];
                    $store_info[$k]['distribution']   = $v['distribution'];
                    $store_info[$k]['business_hours'] = json_decode($v['business_hours']);
                    $store_info[$k]['address']        = $v['address'];
                    $store_info[$k]['phone']          = $v['phone'];
                    $store_info[$k]['image_url']      = explode(',', $v['image_url']);
                    $store_info[$k]['apart']          = ($positions_container[$kk][1] * 1) * 1000;
                    $store_info[$k]['score']          = '5.0';
                    $store_info[$k]['longitude']      = $v['longitude'];
                    $store_info[$k]['latitude']       = $v['latitude'];
                }
            }
        }
        // 取出门店距离列
        $apart_column = array_column($store_info, 'apart');
        // 根据门店距离进行升序排序
        array_multisort($apart_column, SORT_ASC, $store_info);
        // 获取门店评分
        $store_info = self::queryStoreRemark($store_info);
        return $store_info;
    }
    
    /**
     * 获取门店评分
     * @Author ZzzWClock
     * @param  $offline_store_info  线下门店信息
     * @return array
     */
    private static function queryStoreRemark (array $offline_store_info)
    {
        // 获取门店id
        $offline_store_ids = array_column($offline_store_info, 'id');
        // 查询线下门店评分表
        $store_remark = DB::table('shop_offline_store_remark')
                            ->select([
                                'id',
                                'offline_store_id',
                                'total_score',
                            ])
                            ->where(['is_show' => 0])
                            ->whereIn('id', $offline_store_ids)
                            ->lists();
        
        if (!empty($store_remark)) {
            // 遍历线下门店信息 children 为评论数量
            foreach ($offline_store_info as $k => $store_info) {
                $offline_store_info[$k]['children'] = [];
                foreach ($store_remark as $kk => $remark) {
                    
                    if ($remark['offline_store_id'] == $store_info['id']) {
                        $offline_store_info[$k]['children'][] = $remark; 
                    }
                }
            }
            // 声明score_num 储存门店评分数量
            $score_num = [];
            foreach ($offline_store_info as $k => $store_info) {
                $score_num[$store_info['id']][] = count($store_info['children']);
            }
            // 遍历线下门店信息评分 = 每个children 评分总合 / 门店评分数量
            foreach ($offline_store_info as $k => $store_info) {
                $total_score = 0;
                foreach ($store_info['children'] as $kk => $cld) {
                    foreach ($score_num as $kkk => $num) {
                        
                        if ($kkk == $cld['offline_store_id']) {
                            $offline_store_info[$k]['score'] = ($total_score += $cld['total_score']) / $num[0];
                            
                        }
                    }
                }
                unset($offline_store_info[$k]['children']);
            }
            return $offline_store_info;
        } else {
            return $offline_store_info;
        }
    }
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Redis6+PHP:实现根据经纬度计算出附近门店距离 的相关文章

随机推荐

  • Blinker控制esp8266 01s实现远程控制继电器开关代码

    该代码只实现Blinker通过按钮on和off两种状态来实现继电器的通断 从而控制连接的电路 define BLINKER WIFI 以WIFI方式接入 BLINKER BLE以蓝牙方式接入 include
  • 将摄像头输出的原始数据文件转换成bmp图像

    引言 从摄像头和传感器获得的视频数据是没有办法直接被电脑识别的 所以需要进行转化 我昨天在做项目的时候遇到了这个问题 根据查阅相关资料 实现了将摄像头输出的原始数据文件转换成bmp图像的程序 语言 C C 测试平台 VC6 0 先把bmp
  • scikit-learn官方文档中文版

    scikit learn sklearn 官方文档中文版简介
  • 液态大脑与固态大脑——圣塔菲最新群体智能文集

    来源 The Royal society 撰文 Ricard Sol Melanie Moses and Stephanie Forrest 大脑 神经元构成的器官根植于许多生物体内 这是一种固态的大脑 且组成它们的元素在空间中相对固定 但
  • QML VideoOutput 显示 YUV420P 数据流

    查看VideoOutPut说明文档 对source属性有以下说明 you can provide a QObject based class with a writable videoSurface property that can ac
  • Electron 收集崩溃日志

    概述 对于任何的客户端应用 开发者都希望能够在用户上的手上记录下相关信息以便了解真实的使用情况 一般情况下 分为以下两种信息 正常日志 在不涉及隐私的情况下 让开发者了解用户使用客户端的详细情况 从这些情况中提炼的信息能够让开发者根据用户的
  • QT类学习系列(8)- QPushButton,QToolButton的区别

    QPushButton与QToolButton的区别 weixin 42073232的博客 CSDN博客https blog csdn net weixin 42073232 article details 84848142 QToolBu
  • MyBatis框架(四)自定义映射

    resultMap处理字段和属性的映射关系 若字段名和实体类中的属性名不一致 则可以通过resultMap设置自定义映射 我们创建员工表如下所示 我们创建的部门表如下所示 多个员工对应一个部门 是一个多对一的关系 我们要把关系设置在多的地方
  • 七夕节教你怎么选男朋友/女朋友~基于PaddleHub的身材打分系统

    文章目录 一 项目背景 PaddleHub 介绍 二 效果展示 三 实现思路 四 具体步骤 1 安装 PaddleHub 到最新版本 2 新建目录 3 完成关键点检测 4 比例测算及打分 5 结果展示 五 总结与展望 一 项目背景 各位小哥
  • 计算机操作系统手册,轻松的开发一个操作系统(指导手册)

    轻松的开发一个操作系统 指导手册 标签 翻译家 编程 操作系统 chapter 1 前言 我们都使用过操作系统 又或者写过某个系统上运行着的程序 但操作系统到底是来做什么的 我所看到的工作多少是硬件完成的又有多少是软件完成的 电脑实际上是如
  • adb no permissions问题

    Google一番 得知可以通过用root权限启动adb server来解决问题 但是每次用adb不会很麻烦嘛 后来发现在SDK的帮助文档里有关于这个问题的说明 If you re developing on Ubuntu Linux you
  • 理解 $nextTick 的作用

    有同学在看 Vue 官方文档时 对 API 文档中的 Vue nextTick 和 vm nextTick 的作用不太理解 其实如果看一下深入响应式原理 vue js中的有关内容 可能会有所理解 不过有些同学可能看到这个标题之后就选择跳过了
  • rsync同步脚本

    bin bash export LANG C date date Y m d H M red echo e 033 0 31m blue echo e 033 0 36m white echo e 033 37m rsync usr bin
  • 解决克隆虚拟机无法上网问题

    通过VMware克隆出来一台linux的虚拟机 但是发现没有办法上网 然后上网查 原来是在linux中有唯一标识网卡的UUID 我们是通过克隆过来的 那么他们的UUID MAC地址和IP地址都相同了 所以导致克隆出来的机子没有办法上网 下面
  • access对比数据_数据分析师有理由爱Sqlserver之四-七大数据库测评Sqlserver胜出

    虽说各家数据库产品大同小易 学会一家 其他家都可以很快上手 但和编程语言的选择一样 人的精力有限下 只能深入研究一家的产品 故在学习之前 认真去评估应该选择哪一家数据库学习 这样的时间也很值得 总比学到一半不断地更换不同产品所浪费的时间好得
  • 一类学习(OCSVM)

    20201102 0 引言 我记得我第一次接触一类学习的时候 是在一本讲解异常流量的书上 大概18年的时候 当时有一个需求 就是所处的场景下 只能拥有一类数据 而其他类的数据 要不获取不到 要不获取了也不具备什么代表性 总体上就是这么一个场
  • VTK和Cmake的安装并运行一个vtk的案例

    VTK的安装并运行一个项目 1 CMAKE安装 要安装VTK的话首先需要安装CMAKE CMAKE的官网 https cmake org download 进入Cmake下载网页 可以看到很多版本 我们选择最新的 在文件列表中 我们选择最便
  • Java之类与对象

    作者简介 zoro 1 目前大一 正在学习Java 数据结构等 作者主页 zoro 1的主页 欢迎大家点赞 收藏 加关注哦 目录 初始面向对象 什么是面向对象 面向对象和面向过程区别 类的定义和使用 什么是类 类的定义 类的实例化 什么是类
  • 关于绘图的卡顿解决方案

    在Android应用中 cocos的渲染和js的逻辑是在gl线程中进行的 而android本身的UI更新是在app的UI线程进行的 所以如果我们在js中调用的Java方法有任何刷新UI的操作 都需要在UI线程进行 如果画板的控件继承于Vie
  • Redis6+PHP:实现根据经纬度计算出附近门店距离

    一 开始介绍 Redis GEO 1 Redis GEO 主要用于存储地理位置信息 并对存储的信息进行操作 该功能在 Redis 3 2 版本新增 Redis GEO 操作方法 geoadd 添加地理位置的坐标 geopos 获取地理位置的