mongo地理坐标计算距离


前言

之前接到这样的需求:
1:用户会保存多个位置
2:计算用户当前位置与保存的位置的距离

之前使用redisGEO实现的,但是感觉redisGEO没有mongoGEO好用,所以记录下mongo的实现方式
实现:使用2dsphere,查询每个位置距离当前位置的距离

用到的依赖:

        <!-- redisson -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!--  mongoDB  -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>
        
       <!-- lombook -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        
        <!-- fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.56</version>
        </dependency>

一、创建含Geo的对象

使用2dsphere,集合必须含有Geo的格式,如:{type:“point”,coordinates:[105,41]}

1:存在集合中的BSON格式

@Data
@Document(value = "location2")
public class Location2 {
    @Id
    private Long id;
    /**
     * 位置名称
     **/
    private String name;
    /**
     * 位置类型:1-景点,2-加油站,3-酒店
     **/
    private Integer type;
    /**
     * 坐标对象
     **/
    private GeoJson geoJson;
    /**
     * 距离,单位m
     **/
    @JsonInclude(JsonInclude.Include.NON_NULL)
    private Double distance;
}
@Data
public class GeoJson {
    private String type;
    private Double[] coordinates;
}

2:查询对象

@Data
public class Location2Query {
    /**
     * 当前经纬度[xxx,xxx]
     **/
    private Double[] coordinates;
    /**
     * 距离,如:200,500,1,3,5,10,20
     **/
    private Double distance;
    /**
     * 距离单位,如:m,km
     **/
    private String unit;
    /**
     * 位置类型:1-景点,2-加油站,3-酒店
     **/
    private Integer type;
}

二、接口继承MongoRepository

MongoRepository内部封装了一些常用的api。

public interface ILocation2service extends MongoRepository<Location2,Long> {
}

三、加上2dsphere索引

1:不写代码的方式,在mongo执行下面的命令:

db.location2.ensureIndex({geoJson:”2dsphere”}) 或者 db.location2.createIndex({‘geoJson’:‘2dsphere’})

2:通过代码的形式,系统启动成功后,加载配置:

通过redis来判断是否需要给集合(location2)添加2dsphere索引

@Slf4j
@Component
public class Location2Config implements CommandLineRunner {
    @Autowired
    private MongoTemplate mongoTemplate;
    @Autowired
    private RedisTemplate redisTemplate;

    @Value("${spring.data.mongodb.database}")
    private String database;
    @Override
    public void run(String... args) throws Exception {
        //从redis中查找location是否已经创建了2dsphere索引
        Integer temp = (Integer)redisTemplate.opsForValue().get(database + ":location2:flag");
        if(temp == null){
            MongoCollection<Document> location = mongoTemplate.getCollection("location2");
            location.createIndex(new BasicDBObject("geoJson","2dsphere"));
            log.debug(database+".location2.geoJson成功创建2dsphere索引");
            redisTemplate.opsForValue().set(database + ":location2:flag",1);
        }else{
            log.debug(database+".location2.geoJson已有2dsphere索引,不需要再次创建");
        }
    }
}

四、controller提供接口

提供了两个接口:(两个接口的参数,在文末)

1:批量增加位置
2:查询各个位置与指定位置的距离

@RestController
@RequestMapping(value = "location2")
public class Location2Controller {
    @Autowired
    private ILocation2service iLocation2service;
    @Autowired
    private MongoTemplate mongoTemplate;

    /**
     * 批量增加地址
     **/
    @RequestMapping(value = "saveBatch", method = RequestMethod.POST)
    public List<Location2> saveBatch(@RequestBody List<Location2> list) {
        List<Location2> locations = iLocation2service.saveAll(list);
        return locations;
    }
	
	 /**
     * 查询存储的位置,距离指定位置的距离,排序为:由近到远
     **/
    @RequestMapping(value = "geoNear", method = RequestMethod.POST)
    public List<Location2>  geoNear(@RequestBody Location2Query model) {
        List<BasicDBObject> pipeLine = new ArrayList<>();
        BasicDBObject aggregate = new BasicDBObject("$geoNear",
                new BasicDBObject("near"
                        , new BasicDBObject("type", "Point")
                        .append("coordinates", model.getCoordinates()))
                        .append("distanceField", "distance")
                        .append("maxDistance", model.getDistance())
                        .append("spherical", true)

        );
        pipeLine.add(aggregate);
        AggregateIterable<Document> location2 = mongoTemplate.getCollection("location2").aggregate(pipeLine);
        MongoCursor<Document> cursor = location2.iterator();
        List<Location2> resultList = new ArrayList<>();

        //将查询的结果,封装成对象返回出去
        while (cursor.hasNext()) {
            Document document = cursor.next();
            Location2 node = JSONObject.parseObject(JSONObject.toJSONString(document),Location2.class);
            resultList.add(node);
        }
        return resultList;
    }

}

总结

看到网上有使用2d索引的,但是执行结果不太理想,我就不贴出来了。

请求样例

1:saveBatch

[
  {
    "id": 20220118001001,
    "name": "天安门",
    "type": 1,
    "geoJson": {
      "type": "Point",
      "coordinates": [
        116.4041602659,
        39.9096215780
      ]
    }
  },
  {
    "id": 20220118001002,
    "name": "东单",
    "type": 1,
    "geoJson": {
      "type": "Point",
      "coordinates": [
        116.4244857287,
        39.9144951360
      ]
    }
  },
  {
    "id": 20220118001003,
    "name": "王府井",
    "type": 1,
    "geoJson": {
      "type": "Point",
      "coordinates": [
        116.4177807251,
        39.9175129885
      ]
    }
  },
  {
    "id": 20220118001004,
    "name": "西单",
    "type": 1,
    "geoJson": {
      "type": "Point",
      "coordinates": [
        116.3834863095,
        39.9133467579
      ]
    }
  },
  {
    "id": 20220118001005,
    "name": "复兴门",
    "type": 1,
    "geoJson": {
      "type": "Point",
      "coordinates": [
        116.3631701881,
        39.9129554253
      ]
    }
  },
  {
    "id": 20220118001006,
    "name": "西四",
    "type": 1,
    "geoJson": {
      "type": "Point",
      "coordinates": [
        116.3799838526,
        39.9299098531
      ]
    }
  },
  {
    "id": 20220118001007,
    "name": "菜市口",
    "type": 1,
    "geoJson": {
      "type": "Point",
      "coordinates": [
        116.3809950293,
        39.8952009239
      ]
    }
  },
  {
    "id": 20220118001008,
    "name": "东四",
    "type": 1,
    "geoJson": {
      "type": "Point",
      "coordinates": [
        116.4239387439,
        39.9306126797
      ]
    }
  }
]

2:geoNear

{
    "distance": 5000,
    "coordinates": [
                116.4041602659,
                39.909621578
            ]
}

执行结果:

[
    {
        "id": 20220118001001,
        "name": "天安门",
        "type": 1,
        "geoJson": {
            "type": "Point",
            "coordinates": [
                116.4041602659,
                39.909621578
            ]
        },
        "distance": 0.0
    },
    {
        "id": 20220118001003,
        "name": "王府井",
        "type": 1,
        "geoJson": {
            "type": "Point",
            "coordinates": [
                116.4177807251,
                39.9175129885
            ]
        },
        "distance": 1457.4510212007635
    },
    {
        "id": 20220118001004,
        "name": "西单",
        "type": 1,
        "geoJson": {
            "type": "Point",
            "coordinates": [
                116.3834863095,
                39.9133467579
            ]
        },
        "distance": 1813.31187529057
    },
    {
        "id": 20220118001002,
        "name": "东单",
        "type": 1,
        "geoJson": {
            "type": "Point",
            "coordinates": [
                116.4244857287,
                39.914495136
            ]
        },
        "distance": 1818.308155754512
    },
    {
        "id": 20220118001007,
        "name": "菜市口",
        "type": 1,
        "geoJson": {
            "type": "Point",
            "coordinates": [
                116.3809950293,
                39.8952009239
            ]
        },
        "distance": 2547.624749109959
    },
    {
        "id": 20220118001008,
        "name": "东四",
        "type": 1,
        "geoJson": {
            "type": "Point",
            "coordinates": [
                116.4239387439,
                39.9306126797
            ]
        },
        "distance": 2882.968922534015
    },
    {
        "id": 20220118001006,
        "name": "西四",
        "type": 1,
        "geoJson": {
            "type": "Point",
            "coordinates": [
                116.3799838526,
                39.9299098531
            ]
        },
        "distance": 3059.58388993285
    },
    {
        "id": 20220118001005,
        "name": "复兴门",
        "type": 1,
        "geoJson": {
            "type": "Point",
            "coordinates": [
                116.3631701881,
                39.9129554253
            ]
        },
        "distance": 3519.594241397785
    }
]
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值