附近的人?你zao嗎?

前幾天收到一個新的需求,須要實現相似「附近的人」的功能:根據本身當前的定位,獲取距離範圍內的全部任務地點。剛看到這個需求時有點懵逼,第一想到的就是要利用地球的半徑公式去計算距離,也就是把地球想成一個球體,去計算球上兩點之間的距離。可想而知,這樣的方法效率會比較低,每條數據都要來與本人的座標作計算,太過繁瑣。通過大佬的指點,想到了用redis自帶的GEO來實現此功能。java

實戰演習

如下是給你們準備的sql腳本git

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for job_base_info
-- ----------------------------
DROP TABLE IF EXISTS `job_base_info`;
CREATE TABLE `job_base_info`  (
  `job_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '任務ID',
  `job_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '任務名稱',
  `job_location` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '任務地點位置經緯度-逗號隔開',
  `job_detail_address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '任務詳細地點位置',
  PRIMARY KEY (`job_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 24 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '工做任務詳情基礎信息表' ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of job_base_info
-- ----------------------------
INSERT INTO `job_base_info` VALUES (1, '軟件開發', '120.433576,36.139697', '青島市嶗山區海爾路1號');
INSERT INTO `job_base_info` VALUES (2, '兒童攝影', '120.420297,36.156589', '山東省青島市李滄區書院路188號');
INSERT INTO `job_base_info` VALUES (3, '清潔家用電器', '120.025706,36.281478', '山東省青島市膠州市福州支路232號東60米');
INSERT INTO `job_base_info` VALUES (4, '辯論學習', '120.505042,36.171247', '松嶺路238號中國海洋大學內');

SET FOREIGN_KEY_CHECKS = 1;

廢話很少說,讓咱們來看看具體的實現redis

(1)咱們要在程序啓動時,將數據庫中的任務數據的座標信息初始化到redis中(此處暫且忽略任務的增刪改查對redis中數據的影響)sql

@PostConstruct
public void init(){
    //首先要刪除該key的全部值
    redisTemplate.delete("company-task");
    List<JobBaseInfo> jobBaseInfoList = jobBaseInfoMapper.selectList(Wrappers.emptyWrapper());
    jobBaseInfoList.stream().forEach(item->{
        String jobLocation = item.getJobLocation();
        if(StrUtil.isNotEmpty(jobLocation)){
            String[] split = jobLocation.split(",");comp
            if(split.length==2){
                //Point(經度, 緯度) 
                Point point = new Point(Double.parseDouble(split[0]),Double.parseDouble(split[1]));
                //將經緯度數據及其id存到key爲「company-task」中
                redisTemplate.opsForGeo().add("company-task",point,item.getJobId());
            }
        }
    });
}

(2)查詢當前座標下3km範圍內的任務地點(外加根據任務名搜索的聯合查詢)數據庫

@Override
public List<JobBaseInfo> selectJobList(JobBaseInfoDTO jobBaseInfoDTO) {
    String jobLocation = jobBaseInfoDTO.getJobLocation();
    //距離
    Double distance = jobBaseInfoDTO.getDistance();
    List<Integer> idList = new ArrayList<>();
    if(StringUtils.isNotNull(jobLocation) && StringUtils.isNotNull(distance)){
        String[] split = jobLocation.split(",");
        if(split.length==2){
            //Point(經度, 緯度) Distance(距離量, 距離單位)
            Circle circle = new Circle(new Point(Double.parseDouble(split[0]),Double.parseDouble(split[1])),
                                       new Distance(distance, Metrics.KILOMETERS));
            //params: key, Circle 獲取存儲到redis中的distance範圍內的全部任務地點數據
            GeoResults radius = redisTemplate.opsForGeo().radius("company-task", circle);
            List<GeoResult> contentList = radius.getContent();
            if(contentList.size()>0){
                contentList.stream().forEach(item->{
                    RedisGeoCommands.GeoLocation content = (RedisGeoCommands.GeoLocation) item.getContent();
                    idList.add((Integer) content.getName());
                });
            }
        }
    }
    jobBaseInfoDTO.setIdList(idList);
    return jobBaseInfoMapper.selectJobList(jobBaseInfoDTO);
}

selectJobList(jobBaseInfoDTO)方法的sql以下api

<select id="selectJobList" resultType="com.itzyq.redislikes.model.entity.JobBaseInfo">
	select
	<include refid="Base_Column_List"></include>
	from job_base_info
	<where>
		<if test="jobName!=null and jobName!=''">
			and job_name like CONCAT("%",#{jobName},"%")
		</if>

		<if test="idList!=null and idList.size > 0 ">
			and job_id in
			<foreach collection="idList" item="id" open="(" close=")" separator=",">
				#{id}
			</foreach>
		</if>
	</where>
</select>

到這兒咱們就已經實現了「附近的人」的功能了,接下來就讓咱們具體的瞭解一下Redis中的GEO都有哪些騷操做吧。gzh回覆「pSearch」獲取源碼呦!app

GEO操做

Redis GEO 主要用於存儲地理位置信息,並對存儲的信息進行操做,該功能在 Redis 3.2 版本新增,GEO 是基於zset的一種擴展數據格式。Redis GEO 操做方法有:ide

  • geoadd:添加地理位置的座標。
  • geopos:獲取地理位置的座標。
  • geodist:計算兩個位置之間的距離。
  • georadius:根據用戶給定的經緯度座標來獲取指定範圍內的地理位置集合。
  • georadiusbymember:根據儲存在位置集合裏面的某個地點獲取指定範圍內的地理位置集合。
  • geohash:返回一個或多個位置對象的 geohash 值。

一、geoadd學習

geoadd 用於存儲指定的地理空間位置,能夠將一個或多個經度(longitude)、緯度(latitude)、位置名稱(member)添加到指定的 key 中。編碼

geoadd 語法格式以下:

GEOADD key longitude latitude member [longitude latitude member ...]

如下實例中 key 爲 Sicily,Palermo 和 Catania 爲位置名稱 :

實例

redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
(integer) 2
redis>

二、geopos

geopos 用於從給定的 key 裏返回全部指定名稱(member)的位置(經度和緯度),不存在的返回 nil。

geopos 語法格式以下:

GEOPOS key member [member ...]

實例

redis> GEOPOS Sicily Palermo Catania NonExisting
1) 1) "13.36138933897018433"
  2) "38.11555639549629859"
2) 1) "15.08726745843887329"
  2) "37.50266842333162032"
3) (nil)
redis>

注:也能夠使用zrange返回全部的位置元素而不帶經緯度信息

redis> ZRANGE Sicily 0 -1
1)  "Palermo"
 2)  "Catania"
redis>

三、geodist

geodist 用於返回兩個給定位置之間的距離。

geodist 語法格式以下:

GEODIST key member1 member2 [m|km|ft|mi]

member1 member2 爲兩個地理位置。

最後一個距離單位參數說明:

  • m :米,默認單位。
  • km :公里。
  • mi :英里。
  • ft :英尺。

實例: 計算 Palermo 與 Catania 之間的距離

redis> GEODIST Sicily Palermo Catania
"166274.1516"
redis> GEODIST Sicily Palermo Catania km
"166.2742"
redis> GEODIST Sicily Palermo Catania mi
"103.3182"
redis> GEODIST Sicily Foo Bar
(nil)
redis>

四、georadius、georadiusbymember

georadius 以給定的經緯度爲中心, 返回鍵包含的位置元素當中, 與中心的距離不超過給定最大距離的全部位置元素。

georadiusbymember 和 GEORADIUS 命令同樣, 均可以找出位於指定範圍內的元素, 可是 georadiusbymember 的中心點是由給定的位置元素決定的, 而不是使用經度和緯度來決定中心點。

georadius 與 georadiusbymember 語法格式以下:

GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]

參數說明:

  • m :米,默認單位。
  • km :公里。
  • mi :英里。
  • ft :英尺。
  • WITHDIST: 在返回位置元素的同時, 將位置元素與中心之間的距離也一併返回。
  • WITHCOORD: 將位置元素的經度和維度也一併返回。
  • WITHHASH: 以 52 位有符號整數的形式, 返回位置元素通過原始 geohash 編碼的有序集合分值。 這個選項主要用於底層應用或者調試, 實際中的做用並不大。
  • COUNT 限定返回的記錄數。
  • ASC: 查找結果根據距離從近到遠排序。
  • DESC: 查找結果根據從遠到近排序。

georadius 實例

redis> GEORADIUS Sicily 15 37 200 km WITHDIST
1) 1) "Palermo"
  2) "190.4424"
2) 1) "Catania"
  2) "56.4413"
redis> GEORADIUS Sicily 15 37 200 km WITHCOORD
1) 1) "Palermo"
  2) 1) "13.36138933897018433"
   2) "38.11555639549629859"
2) 1) "Catania"
  2) 1) "15.08726745843887329"
   2) "37.50266842333162032"
redis> GEORADIUS Sicily 15 37 200 km WITHDIST WITHCOORD
1) 1) "Palermo"
  2) "190.4424"
  3) 1) "13.36138933897018433"
   2) "38.11555639549629859"
2) 1) "Catania"
  2) "56.4413"
  3) 1) "15.08726745843887329"
   2) "37.50266842333162032"
redis>

georadiusbymember 實例:

redis> GEOADD Sicily 13.583333 37.316667 "Agrigento"
(integer) 1
redis> GEORADIUSBYMEMBER Sicily Agrigento 100 km
1) "Agrigento"
2) "Palermo"
redis>

五、geohash

Redis GEO 使用 geohash 來保存地理位置的座標。geohash 用於獲取一個或多個位置元素的 geohash 值。

geohash 語法格式以下:

GEOHASH key member [member ...]

實例:

redis> GEOHASH Sicily Palermo Catania
1) "sqc8b49rny0"
2) "sqdtr74hyu0"
redis>

geo並無提供刪除指令,但根據其底層是zset實現,咱們能夠使用zrem對數據進行刪除

redis> ZREM Sicily Agrigento
"1"
redis>

Redis GEO JAVA API

有了以上GEO的操做,咱們能夠在java中找到對應的api

/**
 *  將指定的地理空間位置(緯度、經度、名稱)添加到指定的key中。
 */
//params: key, Point(經度, 緯度), 地方名稱
Long addedNum = redisTemplate.opsForGeo().add("Sicily", new Point(13.361389,38.115556), "Palermo");

/**
 *  從key裏返回全部給定位置元素的位置(經度和緯度)。
 */
//params: key, 地方名稱...
List<Point> points = redisTemplate.opsForGeo().position("Sicily","Palermo","Catania");

/**
 *  返回兩個給定位置之間的距離。
 */
//params: key, 地方名稱1, 地方名稱2, 距離單位
Distance distance = redisTemplate.opsForGeo()
                .distance("Sicily","Palermo","Catania", RedisGeoCommands.DistanceUnit.KILOMETERS);

/**
 * 以給定的經緯度爲中心, 返回鍵包含的位置元素當中, 與中心的距離不超過給定最大距離的全部位置元素,並給出全部位置元素與中心的平均距離。
 */
//Point(經度, 緯度) Distance(距離量, 距離單位)
Circle circle = new Circle(new Point(13.361389,38.115556), new Distance(200, Metrics.KILOMETERS));
RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().
    //包含距離,包含經緯度,升序前五個
    includeDistance().includeCoordinates().sortAscending().limit(5);
//params: key, Circle, GeoRadiusCommandArgs
GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisTemplate.opsForGeo()
                .radius("Sicily",circle,args);

/**
 *  以給定的城市爲中心, 返回鍵包含的位置元素當中, 與中心的距離不超過給定最大距離的全部位置元素,並給出全部位置元素與中心的平均距離。
 */
//params: 距離量, 距離單位
Distance distance = new Distance(200,Metrics.KILOMETERS);
RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
    .includeDistance().includeCoordinates().sortAscending().limit(5);
//params: key, 地方名稱, Circle, GeoRadiusCommandArgs
GeoResults<RedisGeoCommands.GeoLocation<String>>  results = redisTemplate.opsForGeo()
    .radius("Sicily","Palermo",distance,args);
     

/**
 *  返回一個或多個位置元素的 Geohash 表示
 */
//params: key, 地方名稱...
List<String> results = redisTemplate.opsForGeo()
                .hash("Sicily","Palermo","Catania");

看到這裏,「附近的人」功能你必定掌握了吧!若是你以爲這篇文章對你有所幫助,請移步關注gzh「阿Q說代碼」或者直接聯繫阿Q:qingqing-4132,阿Q期待你的到來!

相關文章
相關標籤/搜索