使用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
    }
]

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM