基于MongoDB的空间数据存储与查询

一、概念说明

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);
}
相关推荐
FIN技术铺38 分钟前
问:数据库的六种锁机制实践总结?
数据库·sql·oracle
程序员劝退师_1 小时前
优惠券秒杀的背后原理
java·数据库
Gauss松鼠会1 小时前
GaussDB全密态数据库等值查询
数据库·oracle·gaussdb
JSUITDLWXL1 小时前
在Oracle数据中更新整个对象和更新对象的某几个字段时,他们的锁是相同的吗
数据库·oracle
杏花春雨江南1 小时前
ddl/dml/dcl
数据库·oracle
Matrix702 小时前
HBase理论_HBase架构组件介绍
大数据·数据库·hbase
不太灵光的程序员2 小时前
【HBase分布式数据库】第七章 数据的导入导出 importtsv导入数据
数据库·分布式·hbase
Mephisto.java2 小时前
【大数据学习 | HBASE高级】region split机制和策略
数据库·学习·hbase
Lucifer三思而后行2 小时前
YashanDB YAC 入门指南与技术详解
数据库·后端
大气层煮月亮2 小时前
python调用MySql详细步骤
数据库·mysql