Mongo’s geo query

  mongodb

maven

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>

domain

@Document(collection="coffeeShop")
public class CoffeeShop {

    @Id
    private String id;

    private String name;

    @GeoSpatialIndexed
    private double[] location;
    
    //....
    
}    

Near query

Spherical is true, the distance unit is spatial radian, false, the distance unit is horizontal unit

Degree query

Spherical is false and the parameter is kilometers divided by 111.

public GeoResults<CoffeeShop> near2(double[] poi){
        NearQuery near = NearQuery
                .near(new Point(poi[0],poi[1]))
                .spherical(false)
                .num(1);
        GeoResults<CoffeeShop> results = mongoTemplate.geoNear(near, CoffeeShop.class);
        return results;
    }

Output

GeoResults: [averageDistance: 0.08294719588991498, results: GeoResult [content: com.codecraft.domain.CoffeeShop@747f6c5a, distance: 0.08294719588991498, ]]

Spherical is not specified, and the default is false. dis in the result needs to be multiplied by 111 and converted into km.

public GeoResults<CoffeeShop> near2(double[] poi){
        NearQuery near = NearQuery
                .near(new Point(poi[0],poi[1]))
                .spherical(false)
                .distanceMultiplier(111)
                .num(1);
        GeoResults<CoffeeShop> results = mongoTemplate.geoNear(near, CoffeeShop.class);
        return results;
    }

Output

GeoResults: [averageDistance: 9.207138743780563 org.springframework.data.geo.CustomMetric@28768e25, results: GeoResult [content: com.codecraft.domain.CoffeeShop@310d57b1, distance: 9.207138743780563 org.springframework.data.geo.CustomMetric@28768e25, ]]

That is, Beijing Ali green center is 9km away from Sanlitun Starbucks

To set the maximum distance

public GeoResults<CoffeeShop> near2(double[] poi){
        NearQuery near = NearQuery
                .near(new Point(poi[0],poi[1]))
                .spherical(false)
                .maxDistance(5/111.0d)
                .distanceMultiplier(111)
                .num(1);
        GeoResults<CoffeeShop> results = mongoTemplate.geoNear(near, CoffeeShop.class);
        return results;
    }

The result is empty

Radius query

Need to store the data as (longitude, latitude), otherwise report an error

org.springframework.dao.DataIntegrityViolationException: Write failed with error code 16755 and error message 'Can't extract geo keys: { _id: ObjectId('58df9c50b45cbc069f6ff548'), _class: "com.codecraft.domain.CoffeeShop", name: "深圳市南山区星巴克(海岸城店)", location: [ 22.52395, 113.943442 ] }  can't project geometry into spherical CRS: [ 22.52395, 113.943442 ]'; nested exception is com.mongodb.WriteConcernException: Write failed with error code 16755 and error message 'Can't extract geo keys: { _id: ObjectId('58df9c50b45cbc069f6ff548'), _class: "com.codecraft.domain.CoffeeShop", name: "深圳市南山区星巴克(海岸城店)", location: [ 22.52395, 113.943442 ] }  can't project geometry into spherical CRS: [ 22.52395, 113.943442 ]'

    at org.springframework.data.mongodb.core.MongoExceptionTranslator.translateExceptionIfPossible(MongoExceptionTranslator.java:85)

Use

public GeoResults<CoffeeShop> nearRadian(double[] poi){
        NearQuery near = NearQuery
                .near(new Point(poi[0],poi[1]))
                .spherical(true)
                .maxDistance(10,Metrics.KILOMETERS) //MILES以及KILOMETERS自动设置spherical(true)
                .distanceMultiplier(6371)
                .num(1);
        GeoResults<CoffeeShop> results = mongoTemplate.geoNear(near, CoffeeShop.class);
        return results;
    }

test

@Test
    public void testInitGeo() {
        //http://map.yanue.net/toLatLng/
        CoffeeShop shop1 = new CoffeeShop("深圳市南山区星巴克(海岸城店)",new double[]{113.943442,22.52395});
        CoffeeShop shop2 = new CoffeeShop("广州市白云区星巴克(万达广场店)",new double[]{113.274643,23.180251});
        CoffeeShop shop3 = new CoffeeShop("北京市朝阳区星巴克(三里屯店)",new double[]{116.484385,39.923778});
        CoffeeShop shop4 = new CoffeeShop("上海市浦东新区星巴克(滨江店)",new double[]{121.638481,31.230895});
        CoffeeShop shop5 = new CoffeeShop("南京市鼓楼区星巴克(山西路店)",new double[]{118.788924,32.075343});
        CoffeeShop shop6 = new CoffeeShop("厦门市思明区星巴克(中华城店)",new double[]{118.089813,24.458157});
        CoffeeShop shop7 = new CoffeeShop("杭州市西湖区星巴克(杭州石函店)",new double[]{120.143005,30.280273});

        coffeeShopDao.save(Lists.newArrayList(shop1,shop2,shop3,shop4,shop5,shop6,shop7));

    }

    @Test
    public void testNear(){
        //经度\纬度
        double[] bjAli = new double[]{116.492644,40.006313};
        double[] szAli = new double[]{113.950723,22.558888};
        double[] shAli = new double[]{121.387616,31.213301};
        double[] hzAli = new double[]{120.033345,30.286398};
        Arrays.asList(bjAli,szAli,shAli,hzAli).stream().forEach(d -> {
            GeoResults<CoffeeShop> results = locationService.nearRadian(d);
            System.out.println(results);
        });
    }

Summary

  • The coordinate order of longitude and latitude is easily mistaken. The X axis is latitude and the axis is longitude, which is also the order defined by Point. However, this order is very error-prone for spherical queries using radians, that is, spherical queries require the order (longitude, latitude), that is, the data and parameters are in this order.
  • For scenes that only need to take the latest N, use num.
  • When you want to use the distance in the result, you need to pay attention to unit conversion.
  • When using non-spherical input parameters such as maxDistance, pay attention to unit conversion; When using spherical query, MILES and KILOMETERS automatically set spherical(true), and there is no need to care about the conversion of input units.

In addition, for spherical and non-spherical queries, there seems to be no difference, that is, spherical does not need to care about unit conversion when using its parameters, which is a little more convenient.

doc