基于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);
}
相关推荐
IvorySQL1 小时前
PostgreSQL 分区表的 ALTER TABLE 语句执行机制解析
数据库·postgresql·开源
·云扬·1 小时前
MySQL 8.0 Redo Log 归档与禁用实战指南
android·数据库·mysql
IT邦德1 小时前
Oracle 26ai DataGuard 搭建(RAC到单机)
数据库·oracle
惊讶的猫1 小时前
redis分片集群
数据库·redis·缓存·分片集群·海量数据存储·高并发写
不爱缺氧i2 小时前
完全卸载MariaDB
数据库·mariadb
纤纡.2 小时前
Linux中SQL 从基础到进阶:五大分类详解与表结构操作(ALTER/DROP)全攻略
linux·数据库·sql
jiunian_cn2 小时前
【Redis】渐进式遍历
数据库·redis·缓存
橙露2 小时前
Spring Boot 核心原理:自动配置机制与自定义 Starter 开发
java·数据库·spring boot
冰暮流星2 小时前
sql语言之分组语句group by
java·数据库·sql
符哥20082 小时前
Ubuntu 常用指令集大全(附实操实例)
数据库·ubuntu·postgresql