【GIS系列】挑战千万级数据:Java和Elasticsearch在GIS中的叠加分析实践

813 阅读7分钟

作者:后端小肥肠

创作不易,未经允许严禁转载。

1. 前言

在处理千万级图斑叠加分析时,传统的后端GIS工具,如Geotools和PostGIS,往往难以满足实时性和高效性的要求。在这一挑战背景下,引入Elasticsearch作为空间叠加查询的解决方案,成为了一种创新且高效的选择。本文将探讨如何利用Java和Elasticsearch实现GIS中的千万级图斑叠加分析,以项目场景为基础,通过对传统后端GIS工具与Elasticsearch的性能比较,旨在为读者展示Elasticsearch作为一种新兴的空间数据处理工具的价值和潜力,为解决类似问题的开发者提供新的思路和解决方案。

本文适合有Elasticsearch和GIS后台编程基础的jym,如本文对你有帮助和启示,请三连支持一下小肥肠~

2. 叠加分析场景方案对比

假设叠加分析的场景为求一个面要素下包含的点要素,点要素图层有1000万个点,要求在短时间内返回叠加分析结果。以后端为JAVA的背景下,常用的叠加分析方案有Geotools,Postgis,ES,目前将从技术方案特点,性能,优缺点,以及处理千万级图斑的可行性这几个方面依次介绍这几种技术方案。

2.1. Geotools

  • 技术方案特点: Geotools是一个开源的Java GIS工具库,提供了丰富的地理信息处理功能。
  • 性能评估: 在处理大规模空间数据时,Geotools可能性能较差,因其设计更偏向于灵活性和功能的全面性,而不是针对大规模数据的优化。
  • 优缺点: Geotools拥有丰富的功能和灵活的定制性,但在处理千万级图斑等大规模数据时,可能表现不佳。
  • 处理千万级图斑的可行性: 在处理大规模数据时可能性能较差,可能需要额外的优化和资源才能满足实时性和高效性的要求

2.2. PostGIS

  • 技术方案特点: PostGIS是一个基于PostgreSQL的空间数据库扩展,提供了丰富的空间分析功能。
  • 性能评估: PostGIS在处理大规模空间数据时性能较好,可以通过索引和优化查询来提高处理效率。
  • 优缺点: PostGIS具有成熟的空间数据处理功能和优秀的性能,但在某些情况下可能需要额外的硬件资源来支持高负载
  • 处理千万级图斑的可行性: PostGIS在处理大规模数据时性能较好,通过适当的索引和优化查询可以有效提高处理效率,但要在短时间内完千万级图斑成叠加分析可能存在一些限制,特别是在复杂的叠加条件或者硬件资源受限的情况下。

2.3. Elasticsearch

  • 技术方案特点

    • 分布式实时搜索与分析引擎: Elasticsearch是一个基于分布式架构的实时搜索和分析引擎,专注于快速、实时的数据处理和查询。
    • 地理空间支持: Elasticsearch通过地理空间索引和查询功能,能够有效地处理地理空间数据,包括点、线、面等要素。
    • 高度可扩展性: Elasticsearch具有良好的横向扩展能力,能够轻松地处理大规模数据,因此适合处理千万级图斑等大规模空间数据。
  • 性能评估

    • 实时性和高效性: Elasticsearch在处理大规模数据时表现出色,具有较快的响应速度和高效的查询性能,能够满足实时性和高效性的要求。
    • 水平扩展性: 通过横向扩展集群节点,Elasticsearch能够有效地处理大规模数据,保持良好的性能表现。
  • 优缺点

    • 优点:

      • 实时性高:Elasticsearch支持实时索引和查询,能够快速处理最新的空间数据。
      • 强大的查询功能:Elasticsearch提供丰富的查询语法和功能,能够灵活地满足各种空间分析需求。
      • 可扩展性强:Elasticsearch具有良好的横向扩展能力,能够轻松地处理大规模数据。
    • 缺点:

      • 学习曲线较陡:对于新手来说,可能需要一定时间来学习Elasticsearch的数据模型和查询语法。
      • 硬件资源需求较高:在处理大规模数据时,可能需要较多的硬件资源来支持高负载的查询请求。
  • 处理千万级图斑的可行性

    • 可行性: Elasticsearch在处理大规模数据时表现优异,尤其擅长于实时性和高效性要求较高的场景,因此对于处理千万级图斑是可行的选择。通过合适的索引设计和优化查询,Elasticsearch能够有效地处理千万级图斑数据,并提供快速准确的查询结果。

3. 基于ElastcSearch实现叠加分析代码实践

3.1. 开发环境搭建

3.1.1. 所需版本和工具

依赖版本
Spring Boot2.6.3
Java1.8以上
Elasticsearch7.9.3

3.1.2. pom依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    </dependency>
    <dependency>
        <groupId>org.locationtech.jts</groupId>
        <artifactId>jts-core</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

3.2. 数据导入

基于工具将点要素图层导入Elasticsearch,这里怎么导入的不细讲,相关资料可自行百度:

image.png

注意看Shape字段类型要如上图才能支持面包含点类型的叠加分析。

3.3. 返回结构设计

{
    "code": 200,
    "status": "success",
    "message": "OK",
    "data": {
        "list": [
            {
                "name": "地类名称",
                "code": "地类编码",
                "count": 178,
                "hectares": 541213,
                "ares": 3243,
                "squareMetres": 32432
            }
        ]
    }
}

由返回结构可看出,我们需要返回点要素的name,code和面积属性(平方米,公亩,公顷)。

3.4. 核心代码讲解

3.4.1. 将面要素wkt转换为GeoPoint类型的数组

    private List<GeoPoint> parseWKTGeometry(String wkt) {
        List<GeoPoint> points = new ArrayList<>();
        GeometryFactory geometryFactory = new GeometryFactory();
        WKTReader reader = new WKTReader(geometryFactory);

        try {
            Geometry geometry = reader.read(wkt);
            if (geometry instanceof Polygon) {
                Polygon polygon = (Polygon) geometry;
                LinearRing ring = (LinearRing) polygon.getExteriorRing();
                Coordinate[] coordinates = ring.getCoordinates();
                for (Coordinate coord : coordinates) {
                    double lat = coord.y;
                    double lon = coord.x;
                    points.add(new GeoPoint(lat, lon));
                }
            }
        } catch (ParseException e) {
            e.printStackTrace();
        }

        return points;
    }

3.4.2. 编写叠加分析方法函数

 public ResponseStructure overlayAnalysis(OverlayDTO overlayDTO) {
        try {
            if(StringUtils.isEmpty(overlayDTO.getGeometry())){
                return ResponseStructure.failed("空间范围不可为空");
            }
            String geometry=overlayDTO.getGeometry();
            // 1. 指定检索 Index
            SearchRequest request = new SearchRequest();
            request.indices(ALIAS);

            // 2. 指定检索方式
            SearchSourceBuilder builder = new SearchSourceBuilder();

            // 3. 解析 WKT 格式的多边形
            List<GeoPoint> points = parseWKTGeometry(geometry);

            // geoPolygonQuery 代表着的是多边形查询
            builder.query(QueryBuilders.geoPolygonQuery("Shape", points)).trackTotalHits(true).size(0);

            // 4. 添加聚合查询
            TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms("code").field(FIELD_CODE)
                    .subAggregation(AggregationBuilders.sum("MJ").field("YJJBNTMJ")).size(500);
            builder.aggregation(termsAggregationBuilder);

            request.source(builder);

            // 5. 执行查询
            SearchResponse resp = restHighLevelClient.search(request, RequestOptions.DEFAULT);

            // 6.1 输出总数
            long total = resp.getHits().getTotalHits().value;

            // 6.2 聚合结果
            List<DataVo> lists = new ArrayList<>();
            Terms terms = resp.getAggregations().get("code");
            List<? extends Terms.Bucket> buckets = terms.getBuckets();
            for (Terms.Bucket bucket : buckets) {
                // 获取分组总面积
                Sum mj = bucket.getAggregations().get("MJ");
                Double sum = mj.getValue();
                log.info("面积:{}", sum);

                // 获取代码
                String code = bucket.getKeyAsString();
                log.info("代码:{}", code);
                QuickStatistics quickStatistics = quickStatisticsMapper.selectOne(new LambdaQueryWrapper<QuickStatistics>()
                        .eq(QuickStatistics::getCode, code).eq(QuickStatistics::getType, FIELD_CODE));

                // 如果大数据库查到的代码没有在 pg 库的数据字典中,忽略不统计
                if (quickStatistics == null) {
                    log.info("未统计的代码:{}", code);
                    continue;
                }

                String name = quickStatistics.getName();
                lists.add(new DataVo(name, code, bucket.getDocCount(), sum, sum / 100, sum / 10000));
            }

            JBNTVO jbntvo = new JBNTVO(lists);
            return ResponseStructure.success(jbntvo);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return ResponseStructure.failed("叠加分析异常");
        }
    }

上述方法使用Elasticsearch进行查询,根据指定的几何图形执行地理多边形查询,并添加聚合查询以计算特定区域内的指标数据。最后,它将查询结果进行处理,包括统计各个区域的指标数据和计算相关指标,然后封装成特定的VO对象返回。如果在执行过程中出现异常,则记录错误信息并返回相应的错误响应。

3.5. 结果测试

image.png

由上图可知,只需要输入叠加面要素的wkt格式数据,便可在短时间内拿到叠加分析结果。

4. 结语

本文以千万级图斑叠加分析为背景,首先对比了常规技术栈和ES的优缺点,最后以实际代码讲解了如何基于ES实现千万级图斑叠加分析,如有更好的想法欢迎在评论区留言进行讨论~

结语3.jpg