Skip to content

场景一: 查找附近的充电桩

需求:车主在某个位置,想查找附近的我们的充电桩

其他业务类似:查找附近的门店,附近的人

实现方案一

  • 1.在添加充电桩站点信息的时候,有2个字段,分别是站点的经度和维度,在这2个字段数据入库的时候,我同时往mongodb中存储了一份数据,包括:站点id,站点经度,站点维度。
  • 2.当车主在某个位置时,小程序的getLocation相关的接口就能获取到车主的位置,得到车主的经纬度数据
  • 3.小程序那端把车主的经纬度、查找范围传递到后端接口,利用mondodb提供的api能力,做以下几个步骤
    • 以车主位置的经纬度构建一个GeoJsonPoint 对象,形成圆心
    • 再根据查找范围,构建Distance,也即圆的半径
    • 再利用 Circle对象,结合上面的圆心和半径,画一个圆
    • 再使用Query对象的query方法根据经纬度的圆在mongodb中进行查找,就能得到附近的所有充电站点的id了
    • 一个站点下有多个充电桩,那么根据站点id,就能找到所有的充电桩了

实现方案二

Redis中有个数据类型,我们使用Redis中的Geo(Geospatial)数据结构实现的

Redis的Geo功能允许我们存储地理位置信息,并提供了根据地理位置查询数据的各种功能,比如本文中的查询附近的充电桩功能。

  • 1.在添加充电桩站点信息的时候,有2个字段,分别是站点的经度和维度,在这2个字段数据入库的时候,我同时往redis中存储了一份数据,使用geoadd命令,包括:站点id,站点经度,站点维度。

    powershell
    GEOADD stations:locations 121.245996 31.114995 站点id1 
    GEOADD stations:locations 121.241343 31.113264 站点id2  
    GEOADD stations:locations 121.356703 31.161321 站点id3
    ...
  • 2.查找附近的充电桩

    使用GEORADIUS命令来查询指定范围内的用户

    powershell
    GEORADIUS stations:locations 121.2439(当前位置精度) 31.114678(当前位置维度) 5000 m WITHDIST WITHCOORD COUNT 10 ASC

    上面这条命令:查找以(116.405285, 39.904989)为中心,半径为5000米内的最近的10个站点,并返回站点ID、距离、和坐标

参考代码

java
package com.itsoku.lesson066.controller;

import com.itsoku.lesson066.dto.NearbyUserDto;
import com.itsoku.lesson066.dto.AddUserLocationReq;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.domain.geo.Metrics;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;

/**
 * <b>description</b>: Java高并发、微服务、性能优化实战案例100讲,视频号:程序员路人,源码 & 文档 & 技术支持,请加个人微信号:itsoku <br>
 * <b>time</b>:2024/7/18 20:23 <br>
 * <b>author</b>:ready likun_557@163.com
 */
@RestController
public class UserLocationController {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 批量将用户地理位置信息添加到redis中(实际工作中,大家可以提供一个用户地理位置上报的接口,客户端可以每隔10秒,上报一下地理位置坐标,将其丢到redis中)
     *
     * @param userLocationReqList
     * @return
     */
    @PostMapping("/addUserLocation")
    public boolean addUserLocation(@RequestBody List<AddUserLocationReq> userLocationReqList) {
        String key = "users:locations";
        for (AddUserLocationReq userLocationReq : userLocationReqList) {
            String userId = userLocationReq.getUserId();
            Double longitude = userLocationReq.getLongitude();
            Double latitude = userLocationReq.getLatitude();
            this.stringRedisTemplate.opsForGeo().add(key, new Point(longitude, latitude), userId);
        }
        return true;
    }


    /**
     * 获取附近的人列表,以(longitude,latitude)为圆心,以 radius 为半径,获取count个用户
     *
     * @param longitude 进度
     * @param latitude  纬度
     * @param radius    圆的半径(米)
     * @param count     获取用户的数量
     * @return
     */
    @GetMapping("/findNearbyUserList")
    public List<NearbyUserDto> findNearbyUserList(@RequestParam("longitude") double longitude,
                                                 @RequestParam("latitude") double latitude,
                                                 @RequestParam("radius") double radius,
                                                 @RequestParam("count") int count) {
        List<NearbyUserDto> nearbyUserDtoList = new ArrayList<>();

        //从redis中获取附近的用户列表
        String key = "users:locations";
        Circle circle = new Circle(new Point(longitude, latitude), new Distance(radius, Metrics.METERS));
        RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs
                .newGeoRadiusArgs()
                .includeCoordinates()
                .includeDistance()
                .sortAscending().limit(count);
        GeoResults<RedisGeoCommands.GeoLocation<String>> geoResults = stringRedisTemplate.opsForGeo().radius(key, circle, args);
        List<GeoResult<RedisGeoCommands.GeoLocation<String>>> content = geoResults.getContent();
        for (GeoResult<RedisGeoCommands.GeoLocation<String>> geoResultGeoResult : content) {
            RedisGeoCommands.GeoLocation<String> geoLocation = geoResultGeoResult.getContent();
            Point point = geoLocation.getPoint();
            String userId = geoLocation.getName();

            //拿到用于的id、经纬度、距离
            NearbyUserDto nearbyUserDto = new NearbyUserDto();
            nearbyUserDto.setUserId(userId);
            nearbyUserDto.setLongitude(point.getX());
            nearbyUserDto.setLatitude(point.getY());
            nearbyUserDto.setDistance(geoResultGeoResult.getDistance().getValue());
            nearbyUserDtoList.add(nearbyUserDto);
        }
        return nearbyUserDtoList;
    }
}

行车路线

调用高德或者腾讯地图接口即可