springboot+mongodb實現對指定經緯度,半徑範圍內的數據進行搜索

目前公司開的線下店鋪有這麼一個要求:需要知道店鋪附近1000米內是否存在中小學,以供後續的業務邏輯操作。
數據前提:大數據的同學已經把公司將要入駐和已經入住的城市的中小學數據通過爬蟲將數據存放到mongodb中。所以需要在mongodb中查詢指定店鋪所在經緯度的一公里範圍內的學校信息。
而mongodb正好提供了這種地理經緯度搜索的能力,只要數據符合指定的結構,給要搜索的字段加上特定的索引,即可輕鬆完成上面的業務能力。

這裏需要知道一些基礎知識:


地理空間數據

在MongoDB中,可以將地理空間數據存儲爲 GeoJSON對象傳統座標對

以上兩種數據都在何種情況下使用?

  1. GeoJSON對象:要計算類球體上的幾何體,位置數據應存儲爲GeoJSON對象。
  2. 傳統座標對:要計算歐幾里得平面上的距離,位置數據應存儲爲傳統座標對,並使用2d索引。(將數據轉換爲GeoJSON Point類型後,並通過2dsphere索引後傳統座標對也支持球面曲面計算。)

那二者的數據都長甚麼樣?數據結構是怎樣的?

1.GeoJSON對象數據結構:這裏我們只以類型爲點的數據舉例(type:point),這個也最常用。GeoJSON數據結構根據type不同分爲幾類,詳情可以看這裏去官網瞭解:GeoJSON Objects¶
下面是我存在mongodb中的表數據,注意下面數據中的字段:loc既滿足這種類型。loc字段需要指定索引爲:2dsphere

{"address" : "南京 祿口國際機場","loc" : { "type": "Point", "coordinates": [118.783799,31.979234]}}
{"address" : "南京 浦口公園","loc" : { "type": "Point", "coordinates": [118.639523,32.070078]}} 
{"address" : "南京 火車站","loc" : { "type": "Point", "coordinates": [118.803032,32.09248]}}  
{"address" : "南京 新街口","loc" : { "type": "Point", "coordinates": [118.790611,32.047616]}}  
{"address" : "南京 張府園","loc" : { "type": "Point", "coordinates": [118.790427,32.03722]}}  
{"address" : "南京 三山街","loc" : { "type": "Point", "coordinates": [118.788135,32.029064]}}  
{"address" : "南京 中華門","loc" : { "type": "Point", "coordinates": [118.781161,32.013023]}}  
{"address" : "南京 安德門","loc" : { "type": "Point", "coordinates": [118.768964,31.99646]}}

2.傳統座標對:要將數據指定爲傳統座標對,也有兩種數據格式可以使用,數組 或 嵌入式文檔 。 官方推薦首先使用 數組。即double[2]。
注意loc字段,既滿足這種類型,loc字段這是需要指定索引爲:2d


 {'name':'楊帥哥', 'address':'江西省南昌市青山湖區市場和質量監督管理局', 'gender':1, loc:[115.993121,28.676436]},
 {'name':'王美眉', 'address':'江西省南昌市青山湖區創新一路職位小廚', 'gender':0, loc:[116.000093,28.679402]},
 {'name':'張美眉', 'address':'江西省南昌市青山湖區紫陽大道1916號', 'gender':0, loc:[115.999967,28.679743]},
 {'name':'李美眉', 'address':'江西省南昌市青山湖區雲中城', 'gender':0, loc:[115.995593,28.681632]},
 {'name':'彭美眉', 'address':'江西省南昌市青山湖區北京東路1666號', 'gender':0, loc:[115.975543,28.679509]},
 {'name':'趙美眉', 'address':'江西省南昌市青山湖區市場一路大潤發', 'gender':0, loc:[115.968428,28.669368]},
 {'name':'廖美眉', 'address':'江西省南昌市南昌縣奧林匹克中心', 'gender':0, loc:[116.035262,28.677037]},
 {'name':'餘帥哥', 'address':'江西省南昌市南昌縣科技學院瑤湖校區', 'gender':1, loc:[116.02477,28.68667]},
 {'name':'吳帥哥', 'address':'江西省南昌市青山湖區創新一路母嬰店', 'gender':1, loc:[116.002384,28.683865]},
 {'name':'何帥哥', 'address':'江西省南昌市青山湖區紫陽大道2999號', 'gender':1, loc:[116.000821,28.68129]},

注意:緯度和經度座標必須使用 longitude(經度)在前,latitude(緯度)在後的順序。

地理空間索引

在MongoDB中,地理數據相關的索引有兩種 2dsphere 和 2d。地球狀球體計算幾何的查詢應使用 2dsphere 索引。 2d 索引在二維平面上使用存儲爲點的數據的索引。該 2d 索引適用於MongoDB 2.2及更早版本中使用的傳統座標對。

當然通過將數據轉換爲GeoJSON Point類型,MongoDB通過2dsphere索引支持傳統座標對上的球面曲面計算。所以 GeoJSON對象 比 傳統座標對 更加強大複雜,但是 傳統座標對 也是支持 2dsphere

兩種索引的方式雖然不同,不過,只要座標跨度不太大(比如幾百幾千公里),這兩個索引計算出的距離相差幾乎可以忽略不計。

  • 2dsphere索引

2dsphere索引支持在地球球上計算幾何的查詢。

db.bj_school.createIndex({ "loc": "2dsphere" })
  • 2d索引

2d索引支持在二維平面上計算幾何的查詢 。

db.bj_school.createIndex({ "loc" : "2d" })

實戰部分

上面講了一些必須瞭解的東西,下面是具體實例
首先,看mongodb中的collection(表名)bj_school的數據。注意紅線框住的重點。location字段即二維平面的數據結構:double[2]

在這裏插入圖片描述
location設置的索引爲2d

在這裏插入圖片描述
代碼部分我們採用springboot的MongoTemplate來操作mongodb。
首先引入依賴:

        <!--springboot整合mongodb-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mongodb</groupId>
            <artifactId>mongo-java-driver</artifactId>
        </dependency>

數據源的配置放在bootstrap.yml文件中:

spring:
  profiles:
    active: dev
  data:
    mongodb:
      uri: mongodb://localhost:27017/data

對應的接口ElectricFenceService.java

/**
 * 功能描述:接口
 *
 * @author zhanglifeng
 * @date 2019/11/21
 */
public interface ElectricFenceService {
    /**
     * 功能描述:根據經緯度參數查詢當前位置的附近800m範圍內是否有學校
     *
     * @param lon      經度
     * @param lat      緯度
     * @param radius   半徑,單位爲米
     * @return java.util.Map
     * @author zhanglifeng
     * @date 2019/11/21
     */
    ElectricFenceVo checkAddressInFenceOrNot(double lon, double lat,double radius);
}

接口的實現類:

import com.zhanglf.electric.fence.entity.ElectricFenceVo;
import com.zhanglf.electric.fence.entity.SchoolAddressInfo;
import com.zhanglf.electric.fence.service.ElectricFenceService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.geo.*;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author zhanglifeng
 * @description
 * @date 2019/11/21
 */
@Service
public class ElectricFenceServiceImpl implements ElectricFenceService {
    private final static Logger LOGGER = LoggerFactory.getLogger(ElectricFenceServiceImpl.class);
    @Resource
    private MongoTemplate mongoTemplate;

    @Override
    public ElectricFenceVo checkAddressInFenceOrNot(double longitude, double latitude, double radius) {
        ElectricFenceVo electricFenceVo = new ElectricFenceVo();
        Point point = new Point(longitude, latitude);
        //.num()是設置查詢返回的結果數量。如果大於5條就只返回5條。
        //radius 單位爲m,所以要轉成km.
        NearQuery query = NearQuery
                .near(point)
                .maxDistance(new Distance(radius / 1000, Metrics.KILOMETERS))
                .num(5L);
         //SchoolAddressInfo.class爲查詢結果要封裝的類。
         //mongoTemplate.geoNear()方法是專門查詢地圖附近集合的集成mongo的方法
        GeoResults<SchoolAddressInfo> geoResults = mongoTemplate.geoNear(query, SchoolAddressInfo.class, "bj_school");//bj_school爲表名。這個必須填。不然會執行報錯:can`t find ns。
        if (geoResults != null) {
            List<GeoResult<SchoolAddressInfo>> content = geoResults.getContent();
            //如果想獲取查詢結果集是自己封裝的數據。只需要在進行一次遍歷
            // for (GeoResult<SchoolAddressInfo> geoResult: content) {
            //    SchoolAddressInfo schoolAddressInfo = geoResult.getContent();
            //}
            electricFenceVo.setGeoResultList(content);
            if (content != null && content.size() > 0) {
                LOGGER.info("查詢出來的學校數量:{}",content.size());
                electricFenceVo.setCheckResult(true);
            } else {
                LOGGER.info("查詢出來的學校數量:{}",0);
                electricFenceVo.setCheckResult(false);
            }
        } else {
            electricFenceVo.setCheckResult(false);
            electricFenceVo.setGeoResultList(null);
        }
        return electricFenceVo;
    }
}

啓動程序,用postman 調用。入參我們以:北京市東城區東久大廈的經緯度作爲入參。查詢附近100米範圍內的學校數據。可以看到只有一條數據。在這裏插入圖片描述
在高德地圖上可以看到效果!附近100米只有一個幼兒園。距離數據爲80m。
在這裏插入圖片描述
如果我們把距離改成200米,看附近都有哪些學校。
在這裏插入圖片描述
對應高德地圖上的數據,可以看到多了一個 :北京市廣渠門中學(南校區),距離160m
在這裏插入圖片描述
達到想要的預期。

參考博文:
1.LBS-查找附近的人-mongodb實現-基礎知識
2. MongoDB實現附近的人
3. mongo的geo查詢
4. mongodb 內置根據經緯度獲取圓形範圍的數據
5. mongodb 存儲經緯度以及查詢

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章