之前接到这样的需求:
1:用户会保存多个位置
2:计算用户当前位置与保存的位置的距离
之前使用redisGEO实现的,但是感觉redisGEO没有mongoGEO好用,所以记录下mongo的实现方式
实现:使用2dsphere,查询每个位置距离当前位置的距离
用到的依赖:
org.springframework.boot
spring-boot-starter-data-redis
org.springframework.boot
spring-boot-starter-data-mongodb
org.projectlombok
lombok
com.alibaba
fastjson
1.2.56
使用2dsphere,集合必须含有Geo的格式,如:{type:“point”,coordinates:[105,41]}
@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;
}
@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内部封装了一些常用的api。
public interface ILocation2service extends MongoRepository<Location2,Long> {
}
db.location2.ensureIndex({geoJson:”2dsphere”}) 或者 db.location2.createIndex({‘geoJson’:‘2dsphere’})
通过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索引,不需要再次创建");
}
}
}
提供了两个接口:(两个接口的参数,在文末)
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索引的,但是执行结果不太理想,我就不贴出来了。
[
{
"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
]
}
}
]
{
"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
}
]