一、概念说明
1.1 空间地理数据
MongoDB 中使用 GeoJSON对象 或 坐标对 描述空间地理数据。MongoDB使用 WGS84 参考系进行地理空间数据查询。
1、MongoDB支持空间数据的存储,数据类型需要限制为GeoJSON;
2、MongoDB可以为GeoJSON类型数据建立索引,提升空间查询的效率;
1.2 GeoJSON对象
GeoJSON 对象格式
<field>: { type: <GeoJSON type> , coordinates: <coordinates> }
GeoJSON 对象有两个filed,分别是 type 和 coordinates.其中,
-
type 指明是哪种空间地理数据类型
-
coordinates: 是描述 Geo对象的坐标数组,经度在前(经度取值范围 -180到 180),纬度在后(纬度取值范围是-90到90
二、功能演示操作
2.1 准备环境与初始数据
2.1.1、使用SpringBoot 和 MongoTemplate操作
增加MongoDB连接配置
bash
spring:
data:
# MongoDB配置
mongodb:
uri: mongodb://usr:usrpassword@192.168.xx.xx:27017/
database: filedata
authentication-database: admin
#自动创建索引
auto-index-creation: true
connections-num-min-size: 5
connections-num-max-size: 10
2.1.2、创建GeoData对象存储空间数据
java
@Data
@ApiModel
@Document(collection = "GEO-DATA")
public class GeoData {
@ApiModelProperty(name = "_id",value = "_id")
private String _id;
@ApiModelProperty(name = "recordId",value = "recordId")
private String recordId;
@ApiModelProperty(name = "name",value = "名称")
private String name;
/** 经度 */
@ApiModelProperty(name = "lng",value = "经度")
private Double lng;
/** 维度 */
@ApiModelProperty(name = "lat",value = "维度")
private Double lat;
/**
* 位置信息
*/
@ApiModelProperty(name = "location",value = "位置信息", hidden = true)
private GeoJsonPoint location;
@ApiModelProperty(name = "time",value = "录入时间")
private Long time;
}
2.1.3、增加集合GEO-DATA并创建对应的空间索引
java
db.getCollection("GEO-DATA").ensureIndex( { location :"2dsphere" } )
2.1.4、创建测试类MongoGeoTest
java
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MongoGeoTest {
@Autowired
private MongoTemplate mongoTemplate;
}
2.1.5、增加批量插入数据的方法
java
/**
* 批量插入数据
*/
public void batchInsertData() {
//准备数据
List<GeoData> geoDataList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
GeoData geoData = new GeoData();
geoData.setRecordId(UUID.fastUUID().toString(Boolean.TRUE));
geoData.setName(RandomUtil.randomNumbers(12));
geoData.setTime(new Date().getTime());
//经度
double lng = 116.3180D + RandomUtil.randomDouble(0.1d, 1.0d);
geoData.setLng(lng);
//维度
double lat = 39.9857D + RandomUtil.randomDouble(0.1d, 1.0d);
geoData.setLat(lat);
geoData.setLocation(new GeoJsonPoint(lng, lat));
geoDataList.add(geoData);
}
//保存数据
Long start = System.currentTimeMillis();
mongoTemplate.insert(geoDataList, "GEO-DATA");
log.info("Mongo save documents to GEO-DATA 耗时:{} 毫秒", System.currentTimeMillis() - start);
}
2.2 多边形区域内查询
2.2.1、创建查询参数类MultiPositionPageQueryParam
java
@Data
@ApiModel
public class MultiPositionPageQueryParam {
@ApiModelProperty(name = "positions",value = "位置集合")
private List<BDSPosition> positions;
@ApiModelProperty(name = "geoType", value = "类型: 1-多点(位置)查询;2-面(区域)查询")
private Integer geoType;
@NotNull
@ApiModelProperty(name = "pageNum",value = "pageNum 起始数字为 0")
private Long pageNum;
@NotNull
@ApiModelProperty(name = "pageSize",value = "pageSize")
private Long pageSize;
@ApiModelProperty(name = "needCount",value = "是否需要统计总记录数")
private Boolean needCount = Boolean.FALSE;
}
2.2.2、增加多边形区域查询方法
java
/**
* 多边形区域内
*
* @param queryParam
*/
public void queryGeoDataByMultiPositionPageQueryParam(MultiPositionPageQueryParam queryParam) {
Query query = new Query();
Criteria criteria = new Criteria();
List<Criteria> criteriaList = new LinkedList<>();
//过滤字段
query.fields().include("recordId", "_id", "name", "time", "lng", "lat", "location");
//位置集合过滤
if (ObjectUtil.isNotNull(queryParam.getPositions()) && queryParam.getPositions().size() > 0) {
// 类型: 1-多点(位置)查询;2-面(区域)查询
if (ObjectUtil.isNotNull(queryParam.getGeoType()) && queryParam.getGeoType() == 2 && queryParam.getPositions().size() > 2) {
List<Point> pointList = new LinkedList<>();
//经纬度获取
for (BDSPosition position : queryParam.getPositions()) {
Point point = new Point(position.getLng(), position.getLat());
pointList.add(point);
}
pointList.add(pointList.get(0));
GeoJsonPolygon geoJsonPolygon = new GeoJsonPolygon(pointList);
Criteria areaCriteria = Criteria.where("location").within(geoJsonPolygon);
query.addCriteria(areaCriteria);
criteriaList.add(areaCriteria);
} else {
List<Criteria> orCriteriaList = new LinkedList<>();
//经纬度判断
for (BDSPosition position : queryParam.getPositions()) {
orCriteriaList.add(Criteria.where("lng").is(position.getLng()).and("lat").is(position.getLat()));
}
Criteria orPositionCriteria = new Criteria().orOperator(orCriteriaList);
query.addCriteria(orPositionCriteria);
criteriaList.add(orPositionCriteria);
}
}
//总记录数统计
Long total = null;
if (queryParam.getNeedCount()) {
total = mongoTemplate.findDistinct(query, "recordId", "GEO-DATA", String.class).stream().count();
}
//排序
List<Sort.Order> orders = new LinkedList<>();
orders.add(Sort.Order.desc("time"));
AggregationOptions aggregationOptions = AggregationOptions.builder().allowDiskUse(Boolean.TRUE).build();
Aggregation aggregation = null;
if (criteriaList.size() > 0) {
criteria = criteria.andOperator(criteriaList);
aggregation = Aggregation.newAggregation(
Aggregation.project("recordId", "_id", "name", "time", "lng", "lat", "location"),
//查询条件
Aggregation.match(criteria),
//分组条件
Aggregation.group("recordId").max("time").as("time")
.first("recordId").as("recordId")
.last("time").as("time"),
Aggregation.sort(Sort.by(orders)),
//分页条件
Aggregation.skip(queryParam.getPageNum()),
Aggregation.limit(queryParam.getPageSize())
).withOptions(aggregationOptions);
} else {
aggregation = Aggregation.newAggregation(
Aggregation.project("recordId", "_id", "name", "time", "lng", "lat", "location"),
//分组条件
Aggregation.group("recordId").max("time").as("time")
.first("recordId").as("recordId")
.first("time").as("time"),
Aggregation.sort(Sort.by(orders)),
//分页条件
Aggregation.skip(queryParam.getPageNum()),
Aggregation.limit(queryParam.getPageSize())
).withOptions(aggregationOptions);
}
List<GeoData> list = mongoTemplate.aggregate(aggregation, "GEO-DATA", GeoData.class).getMappedResults();
log.info("Data: {}", list);
}
2.3 圆形区域内查询
2.3.1、创建查询参数类CirclePageQueryParam
java
@Data
@ApiModel
public class CirclePageQueryParam {
@NotNull
@ApiModelProperty(name = "lng", value = "经度")
private Double lng;
@NotNull
@ApiModelProperty(name = "lat", value = "维度")
private Double lat;
@NotNull
@ApiModelProperty(name = "radius", value = "半径")
private Double radius;
@NotNull
@ApiModelProperty(name = "pageNum",value = "pageNum 起始数字为 0")
private Long pageNum;
@NotNull
@ApiModelProperty(name = "pageSize",value = "pageSize")
private Long pageSize;
@ApiModelProperty(name = "needCount",value = "是否需要统计总记录数")
private Boolean needCount = Boolean.FALSE;
}
2.3.2、增加圆形区域查询方法
java
/**
* 圆形区域内查询
* @param queryParam
*/
public void queryGeoDataByCircle(CirclePageQueryParam queryParam) {
Query query = new Query();
Criteria criteria = new Criteria();
List<Criteria> criteriaList = new LinkedList<>();
//过滤字段
query.fields().include("recordId", "_id", "name", "time", "lng", "lat", "location");
//位置集合过滤
if (ObjectUtil.isNotNull(queryParam.getLat()) && ObjectUtil.isNotNull(queryParam.getLng())
&& ObjectUtil.isNotNull(queryParam.getRadius())) {
Point point = new Point(queryParam.getLng(), queryParam.getLat());
Distance distance = new Distance(queryParam.getRadius(), Metrics.MILES);
Circle circle = new Circle(point, distance);
Criteria areaCriteria = Criteria.where("location").withinSphere(circle);
query.addCriteria(areaCriteria);
criteriaList.add(areaCriteria);
}else{
log.info("参数有误,必要参数为空。");
return;
}
//总记录数统计
Long total = null;
if (queryParam.getNeedCount()) {
total = mongoTemplate.findDistinct(query, "recordId", "GEO-DATA", String.class).stream().count();
}
//排序
List<Sort.Order> orders = new LinkedList<>();
orders.add(Sort.Order.desc("time"));
AggregationOptions aggregationOptions = AggregationOptions.builder().allowDiskUse(Boolean.TRUE).build();
Aggregation aggregation = null;
if (criteriaList.size() > 0) {
criteria = criteria.andOperator(criteriaList);
aggregation = Aggregation.newAggregation(
Aggregation.project("recordId", "_id", "name", "time", "lng", "lat", "location"),
//查询条件
Aggregation.match(criteria),
//分组条件
Aggregation.group("recordId").max("time").as("time")
.first("recordId").as("recordId")
.last("time").as("time"),
Aggregation.sort(Sort.by(orders)),
//分页条件
Aggregation.skip(queryParam.getPageNum()),
Aggregation.limit(queryParam.getPageSize())
).withOptions(aggregationOptions);
} else {
aggregation = Aggregation.newAggregation(
Aggregation.project("recordId", "_id", "name", "time", "lng", "lat", "location"),
//分组条件
Aggregation.group("recordId").max("time").as("time")
.first("recordId").as("recordId")
.first("time").as("time"),
Aggregation.sort(Sort.by(orders)),
//分页条件
Aggregation.skip(queryParam.getPageNum()),
Aggregation.limit(queryParam.getPageSize())
).withOptions(aggregationOptions);
}
List<GeoData> list = mongoTemplate.aggregate(aggregation, "GEO-DATA", GeoData.class).getMappedResults();
log.info("Data: {}", list);
}