• 通过经纬度计算距离实现附近、附近的人等功能


    需求:附近功能在很多生活类的App或软件中经常出现?那他们是怎么实现的呢?如果数据量不是很大,且功能比较简单,基于MySQL就可以实现。然而很多时候数据量很大且功能复杂,那么我们就需要使用Elasticsearch这种数据库了,不仅功能丰富,而且性能强大,大数据量情况下性能不再是问题。


    一、基于MySQL实现(8.0.28)
    1、建表

    DROP TABLE IF EXISTS `t_city`;
    CREATE TABLE `t_city` (
    `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
    `city_code` varchar(20) NOT NULL DEFAULT '' COMMENT '城市编码',
    `city_name` varchar(20) NOT NULL DEFAULT '' COMMENT '城市名称',
    `addr` varchar(255) NOT NULL DEFAULT '' COMMENT '详细地址',
    `lat` decimal(12,8) DEFAULT NULL COMMENT '纬度',
    `lon` decimal(12,7) DEFAULT NULL COMMENT '经度',
    `efficiency_level` varchar(2) DEFAULT '' COMMENT '效率评级',
    `credit_level` varchar(2) DEFAULT NULL COMMENT '信用评级',
    PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=937 DEFAULT CHARSET=utf8mb4;

    2、插入测试数据

    INSERT INTO `hb-test`.`t_city` (`city_code`, `city_name`, `addr`, `lat`, `lon`, `efficiency_level`, `credit_level`) VALUES ('1', '北京', '北海公园', null, null, 'A', '1');
    INSERT INTO `hb-test`.`t_city` (`city_code`, `city_name`, `addr`, `lat`, `lon`, `efficiency_level`, `credit_level`) VALUES ('2', '西安', '西安火车站', null, null, 'A', '2');
    INSERT INTO `hb-test`.`t_city` (`city_code`, `city_name`, `addr`, `lat`, `lon`, `efficiency_level`, `credit_level`) VALUES ('1', '北京', '北海公园', '39.93463300', '116.3927710', 'A', '1');
    INSERT INTO `hb-test`.`t_city` (`city_code`, `city_name`, `addr`, `lat`, `lon`, `efficiency_level`, `credit_level`) VALUES ('2', '西安', '西安火车站', '34.28405900', '108.9688710', 'A', '2');
    INSERT INTO `hb-test`.`t_city` (`city_code`, `city_name`, `addr`, `lat`, `lon`, `efficiency_level`, `credit_level`) VALUES ('2', '西安', '西安北站', '34.38171600', '108.9452810', 'B', '3');
    INSERT INTO `hb-test`.`t_city` (`city_code`, `city_name`, `addr`, `lat`, `lon`, `efficiency_level`, `credit_level`) VALUES ('2', '西安', '电子正街', '34.21045200', '108.9257140', 'C', '2');
    INSERT INTO `hb-test`.`t_city` (`city_code`, `city_name`, `addr`, `lat`, `lon`, `efficiency_level`, `credit_level`) VALUES ('2', '西安', '零壹广场', '34.23076900', '108.8875360', 'D', '1');
    INSERT INTO `hb-test`.`t_city` (`city_code`, `city_name`, `addr`, `lat`, `lon`, `efficiency_level`, `credit_level`) VALUES ('2', '西安', '软件新城', '34.21228800', '108.8411110', 'D', '2');

    3、第一种查询sql:基于球面距离公式

    纬度最大是90度,大于90度的一定是经度。
    百度地图上的坐标拾取。精度在前,和经纬度的字面意思一致
    地球平均半径约:6371km 最大半径(赤道半径)约:6378km
    #以西安北站(108.945281,34.381716)为原点,计算出有经纬度的城市距离当前远点的距离并正序排列
    SELECT
        id,
        city_name,
        addr,
        lat,
        lon,
        efficiency_level,
        credit_level,
        ROUND(
            6378 * 2 * ASIN(
                SQRT(
                    POW(
                        SIN(
                            (
                                34.381716 * PI() / 180 - lat * PI() / 180
                            ) / 2
                        ),
                        2
                    ) + COS(34.381716 * PI() / 180) * COS(lat * PI() / 180) * POW(
                        SIN(
                            (
                                108.945281 * PI() / 180 - lon * PI() / 180
                            ) / 2
                        ),
                        2
                    )
                )
            ) * 1000
        ) AS distance
    FROM
        t_city
    WHERE lat IS NOT NULL
    ORDER BY
        distance ASC,
        efficiency_level DESC,
        credit_level ASC
    LIMIT 2000 

    4、第二种查询sql:基于mysql自带函数st_distance

    #以西安北站(108.945281,34.381716)为原点,计算出有经纬度的城市距离当前远点的距离并正序排列
    SELECT
        *, (
            st_distance (
                point (lon, lat),
                point (108.945281, 34.381716)
            ) * 6378*1000*PI()/180
        ) AS distance
    FROM
        t_city
    where lon IS NOT NULL
    ORDER BY
        distance ASC
    LIMIT 2000

    二、通过Elasticsearch(7.15.1)的geo_point实现
    1、创建索引mapping

    PUT t_city
    {
      "settings": {
        "number_of_shards": 1,
        "number_of_replicas": 1
      },
      "mappings": {
        "properties": {
          "addr": {
            "type": "keyword"
          },
          "city_code": {
            "type": "keyword"
          },
          "city_name": {
            "type": "keyword"
          },
          "id": {
            "type": "keyword"
          },
          "location": {
            "type": "geo_point"
          },
          "time": {
            "type": "date"
          }
        }
      }
    }

    2、插入测试数据

    POST /t_city/_bulk
    {"index":{}}
    {"city_code":"1","city_name":"北京","addr":"北海公园","time":"2022-01-23","location":{"lat":39.934633,"lon":39.934633}}
    {"index":{}}
    {"city_code":"2","city_name":"西安","addr":"西安火车站","time":"2022-01-23","location":{"lat":34.284059,"lon":108.96887}}
    {"index":{}}
    {"city_code":"2","city_name":"上海","addr":"上海虹桥机场","time":"2022-01-23","location":{"lat":31.203347,"lon":121.346817}}

    3、geo_distance 询问:查找在中心点指定距离内的地理点(更多用法参考官方文档

    #以西安北站(108.94528,34.381716)为原点,查询距离西安北站100km以内的10个点
    GET /t_city/_search
    {
      "size": 10,
      "query": {
        "bool": {
          "filter": [
            {
              "geo_distance": {
                "distance": "100km",
                "location": {
                  "lat": 34.381716,
                  "lon": 108.945281
                }
              }
            }
          ]
        }
      }
    }

    4、距离范围内的桶聚合(更多功能参考官方文档

    #以西安北站(108.94528,34.381716)为原点

     #查询落在(*-100000),(100000-300000),(300000-*)的点

    GET /t_city/_search
    {
      "size": 1,
      "track_total_hits": true,
      "query": {
        "match_all": {}
      },
      "aggs": {
        "distance": {
          "geo_distance": {
            "field": "location",
            "unit": "m", 
            "origin": {
              "lat": 34.381716,
              "lon": 108.945281
            },
            "ranges": [
              {
                "to": 100000
              },
              {
                "from": 100000,
                "to": 300000
              },
              {
                "from": 300000
              }
            ]
          }
        }
      }
    }

    注意:Note that this aggregation includes the from value and excludes the to value for each range.

    后记:很多数据库都有对GEO地理位置数据的处理,除了文中说到的MySQL、Elasticsearch;还有Redis、MongoDB等。
    总结:
    1、MySQL实现简单,MySQL数据库大部分初级程序员都已掌握,实现简单且可以返回原点距离目标点的距离。开发简单,一句sql搞定。但缺点也很明显,只适用于数据量少且业务简单的场景。
    2、Elasticsearch的优点是,适用于大数据量情况,对非常复杂的业务也可以得心应手的处理。缺点是对开发人员技术栈要求比较高,需要掌握Elasticsearch。

  • 相关阅读:
    JS点击按钮,提示确认后跳转网页,并可传递参数
    JS点击按钮,提示确认后跳转网页,并可传递参数
    JS点击按钮,提示确认后跳转网页,并可传递参数
    JS点击按钮,提示确认后跳转网页,并可传递参数
    vim的四种工作模式(转载别人的)
    vim的四种工作模式(转载别人的)
    vim的四种工作模式(转载别人的)
    vim的四种工作模式(转载别人的)
    MySQL数据库的套接字文件和pid文件
    动漫授权逐渐打开,周边市场潜力无限
  • 原文地址:https://www.cnblogs.com/hbuuid/p/15840938.html
Copyright © 2020-2023  润新知