zoukankan      html  css  js  c++  java
  • 使用Redis+GEO实现查询附近司机

    在工作中开发网约车相关功能的时候,需要提供一个通过指定位置查询附近司机的接口。现将研究成果记录下来

    1、使用场景

     司机在空闲时,会在司机端定时上报其位置。当乘客下单后,会通过乘客的位置查询附近司机然后进行匹配

    2、GEO简介

    reids在版本 3.2.0之后,引入了geo功能,可用于处理地理位置。涉及到的相关命令有:GEOADDDEODISTGEORADIUS

    3、代码示例

    pom依赖:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <!-- redis -->
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    GEO工具类:

    @Service
    public class RedisGeoService {
    
        @Autowired
        private StringRedisTemplate redisTemplate;
    
        /**
         * 添加经纬度信息
         * 
         * redis 命令:geoadd key 116.405285 39.904989 "北京"
         */
        public Long geoAdd(String key, Point point, String member) {
            if (redisTemplate.hasKey(key)) {
                redisTemplate.opsForGeo().remove(key, member);
            }
            return redisTemplate.opsForGeo().add(key, point, member);
        }
    
        /**
         * 查找指定key的经纬度信息,可以指定多个member,批量返回
         * 
         * redis命令:geopos key 北京
         */
        public List<Point> geoGet(String key, String... members) {
            return redisTemplate.opsForGeo().position(key, members);
        }
    
        /**
         * 返回两个地方的距离,可以指定单位,比如米m,千米km,英里mi,英尺ft
         * 
         * redis命令:geodist key 北京 上海
         */
        public Distance geoDist(String key, String member1, String member2, Metric metric) {
            return redisTemplate.opsForGeo().distance(key, member1, member2, metric);
        }
    
        /**
         * 根据给定的经纬度,返回半径不超过指定距离的元素
         * 
         * redis命令:georadius key 116.405285 39.904989 100 km WITHDIST WITHCOORD ASC
         * COUNT 5
         */
        public GeoResults<RedisGeoCommands.GeoLocation<String>> nearByXY(String key, Circle circle, long count) {
            // includeDistance 包含距离
            // includeCoordinates 包含经纬度
            // sortAscending 正序排序
            // limit 限定返回的记录数
            RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
                    .includeDistance().includeCoordinates().sortAscending().limit(count);
            return redisTemplate.opsForGeo().radius(key, circle, args);
        }
    
        /**
         * 根据指定的地点查询半径在指定范围内的位置
         * 
         * redis命令:georadiusbymember key 北京 100 km WITHDIST WITHCOORD ASC COUNT 5
         */
        public GeoResults<RedisGeoCommands.GeoLocation<String>> nearByPlace(String key, String member, Distance distance,
                long count) {
            // includeDistance 包含距离
            // includeCoordinates 包含经纬度
            // sortAscending 正序排序
            // limit 限定返回的记录数
            RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
                    .includeDistance().includeCoordinates().sortAscending().limit(count);
            return redisTemplate.opsForGeo().radius(key, member, distance, args);
        }
    
        /**
         * 返回的是geohash值
         * 
         * redis命令:geohash key 北京
         */
        public List<String> geoHash(String key, String member) {
            return redisTemplate.opsForGeo().hash(key, member);
        }
    
    }

    建立一个实体,用来封装司机位置信息:

    @Getter
    @Setter
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public class DriverPosition {
    
        /** 司机id */
        private String driverId;
    
        /** 城市编码 */
        private String cityCode;
    
        /** 经度 */
        private double lng;
    
        /** 纬度 */
        private double lat;
    
    }

    建立一个controller,用来做测试:

    @RestController
    @RequestMapping("redisGeo")
    public class RedisGeoController {
    
        @Autowired
        private RedisGeoService redisGeoService;
    
        private final String GEO_KEY = "geo_key";
    
        /**
         * 使用redis+GEO,上报司机位置
         */
        @PostMapping("addDriverPosition")
        public Long addDriverPosition(String cityId, String driverId, Double lng, Double lat) {
            String redisKey = CommonUtil.buildRedisKey(GEO_KEY, cityId);
            Long addnum = redisGeoService.geoAdd(redisKey, new Point(lng, lat), driverId);
    
            List<Point> points = redisGeoService.geoGet(redisKey, driverId);
            System.out.println("添加位置坐标点:" + points);
    
            return addnum;
        }
    
        /**
         * 使用redis+GEO,查询附近司机位置
         */
        @GetMapping("getNearDrivers")
        public List<DriverPosition> getNearDrivers(String cityId, Double lng, Double lat) {
            String redisKey = CommonUtil.buildRedisKey(GEO_KEY, cityId);
    
            Circle circle = new Circle(lng, lat, Metrics.KILOMETERS.getMultiplier());
            GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisGeoService.nearByXY(redisKey, circle, 5);
            System.out.println("查询附近司机位置:" + results);
    
            List<DriverPosition> list = new ArrayList<>();
            results.forEach(item -> {
                GeoLocation<String> location = item.getContent();
                Point point = location.getPoint();
                DriverPosition position = DriverPosition.builder().cityCode(cityId).driverId(location.getName())
                        .lng(point.getX()).lat(point.getY()).build();
                list.add(position);
            });
    
            return list;
        }
    
    }

    通过高德地图取点4个位置,所对应的坐标分别是:

    东方雨林(114.366386, 30.408199)、怡景江南(114.365281, 30.406869)、梅南山居(114.368049, 30.412896)、武汉大学(114.365248, 30.537860)

    其中前三个地址是在一起的,最后一个隔的很远

    4、测试

    使用postman,分别发送如下请求,添加司机的位置:

    http://localhost:18081/redisGeo/addDriverPosition?cityId=420000&driverId=000001&lng=114.366386&lat=30.408199
    http://localhost:18081/redisGeo/addDriverPosition?cityId=420000&driverId=000002&lng=114.365281&lat=30.406869
    http://localhost:18081/redisGeo/addDriverPosition?cityId=420000&driverId=000003&lng=114.368049&lat=30.412896
    http://localhost:18081/redisGeo/addDriverPosition?cityId=420000&driverId=000004&lng=114.365248&lat=30.537860

    使用Redis Desktop Manager工具查看刚添加的数据:

     

     可以看到,保存到redis的数据格式是ZSET,即有序集合。上面的key中包含了城市id,value表示司机id

    接下来查询“东方雨林”附近的所有司机位置:http://localhost:18081/redisGeo/getNearDrivers?cityId=420000&lng=114.366386&lat=30.408199

    控制台打印日志如下:

    GeoResults: [averageDistance: 242.78286666666668 METERS, results: GeoResult [content: RedisGeoCommands.GeoLocation(name=000001, point=Point [x=114.366386, y=30.408199]), distance: 0.0521 METERS, ],GeoResult [content: RedisGeoCommands.GeoLocation(name=000002, point=Point [x=114.365281, y=30.406869]), distance: 182.0457 METERS, ],GeoResult [content: RedisGeoCommands.GeoLocation(name=000003, point=Point [x=114.368049, y=30.412896]), distance: 546.2508 METERS, ]]

    上面的结果,包含间隔距离的平均值,附近坐标点经纬度、间隔距离,同时结果是按间隔距离正序排序的

    请求返回结果如下:

    [
        {
            "driverId": "000001",
            "cityCode": "420000",
            "lng": 114.36638563871384,
            "lat": 30.408199349640434
        },
        {
            "driverId": "000002",
            "cityCode": "420000",
            "lng": 114.3652805685997,
            "lat": 30.406868621031784
        },
        {
            "driverId": "000003",
            "cityCode": "420000",
            "lng": 114.36804860830307,
            "lat": 30.412896187948697
        }
    ]

    再来试下“武汉大学”附近的司机位置,请求返回结果如下:

    [
        {
            "driverId": "000004",
            "cityCode": "420000",
            "lng": 114.36524838209152,
            "lat": 30.537860475825262
        }
    ]
  • 相关阅读:
    Git配置
    第一次作业
    第二次作业
    python目前最好用的IDE——pycharm
    九九乘法表
    python语言的优点和缺点
    Python高效编程的19个技巧
    Python中 filter | map | reduce | lambda的用法
    Python 代码优化常见技巧
    求逆序对
  • 原文地址:https://www.cnblogs.com/xuwenjin/p/12715339.html
Copyright © 2011-2022 走看看