基于GeoTools的道路相交多个点容差冗余计算实战

发布于:2025-06-13 ⋅ 阅读:(32) ⋅ 点赞:(0)

目录

前言

一、关于道路相交

1、相交四个点

2、点更多的情况

二、基于距离的相交点去重

1、冗余距离计算

2、调用过程

3、去重后的结果

三、总结


前言

        在地理信息系统(GIS)领域,道路网络数据的处理与分析一直是关键课题。随着城市化进程的加速,日益复杂的道路网络对数据精度和处理效率提出了更高要求。而道路相交多个点容差冗余计算,作为道路网络分析中的基础且重要环节,关乎着后续诸多应用的准确性和可靠性。在之前的博客中,我们介绍了基于道路的名称来进行两条道路的相交点计算。原文地址:基于GeoTools和OSM路网求解两条道路相交点-以长沙市为例

        在实际场景中,使用这种处理办法时往往会出现道路相交处存在多个相近点的情况。这些冗余点不仅增加了数据量,还会对诸如路径分析、网络建模等操作产生干扰。例如,在构建道路连通性模型时,过多的冗余点可能导致错误的连通性判断,进而影响整个模型的准确性。那么如何解决这种问题呢?这里使用一种基于容差的处理方法,即设定固定距离阈值进行点的删除或合并。而借助 GeoTools,我们可以通过灵活运用其几何处理工具,结合不同的数据结构和算法,实现更具针对性和适应性的容差冗余计算。通过深入研究其几何对象的操作方法,如点、线几何关系的判定,能够精准地定位道路相交处的冗余点。同时,利用 GeoTools 的数据结构优势,可对道路数据进行高效组织和遍历,从而快速地进行容差范围内的点对比和甄别。

        本次实战将从 GeoTools 的环境搭建入手,详细阐述如何导入和读取道路数据,展示如何对道路几何对象进行解析和操作。接着,重点介绍容差冗余计算的核心算法实现,包括如何设定合理的容差参数,以及如何根据几何计算结果对冗余点进行处理。通过对基于 GeoTools 的道路相交多个点容差冗余计算的实战研究,不仅可以提升道路数据的质量和可用性,还能为后续的地理数据分析和应用奠定坚实的基础。同时,这一过程也展示了 GeoTools 在解决实际 GIS 问题中的强大能力和灵活性,为相关的地理信息开发工作提供了宝贵的实践经验和参考案例。接下来,我们将逐步深入到具体的实现细节中,一同探索这一关键问题的有效解决方案。

一、关于道路相交

        在之前的博文中留了两个问题给朋友们,这两个问题就是:1、表示经过我们的计算,为什么两条道路(金星北路和岳麓大道)的相交点是四个?2、会不会有更多的情况?这里就这两个问题来进行详细的答疑。

1、相交四个点

        首先来回答第一个问题,为什么是相交四个点?关于这个问题,其实大家可以结合生活的常识进行判断。这里为了方便将不同的道路类型在地图上进行展示,这里我们根据道路级别将道路的宽度进行不同的设置。设置的方法为,在对道路进行标绘时,选择分类标绘,界面如下图所示:

        根据分类列表信息,点击更多的分类,使用鼠标双击分类,打开当前选择分类的样式设置,管理界面如下:

         完成后,点击Ok进行应用,可以看到以下的最终结果:

        为了方便更加直观地查看两条道路的相交,我们将数据进一步的放大,能更直观的显示道路相交信息。这里选取骑龙路和枫林三路为例,如下所示:

         相信通过上面的图,大家应该很直观的就能看到根据两条路来求解相交是会有A、B、C、D四个相交点的。到这里就详细的说明了为什么存在多个交点的问题。

2、点更多的情况

        在上一小节中,我们使用QGIS软件来介绍了如何根据道路的名称对道路进行相交求解,并且回答了上一篇博客中提到的两条路相交,为什么有四个点的原因。之前还留了一个小问题,即除了上述的四个点的情况之外,还有没有其它更多的相交点的时候呢?答案是一定的。确实会存在,同上而言,我们的道路一般是上面的这种连接关系,或者是下面的连接关系:

        还有一种情况需要大家考虑,就是其中有一条道路是一个环形或者可以类似的S型,这里使用画图软件来表示:

        上面是一个简单的示意图,道路2是一条曲折蜿蜒的路,而道路1是一条直线,在道路的A和B出分别有前面描述的点相交的情况。共八个点,同理,如果两条路更加蜿蜒曲折,那么可以知道的是两条路势必有更多的交点。 可能在城市道路中这种道路不多见。通过本例也回答了相交点是否存在更多的问题。以长沙为例,城市环岛道路就会有这种情况,以中山路和黄兴路环岛为例:

二、基于距离的相交点去重

        在知晓了道路相交计算的几种结果情况之后,接下来我们来讲讲如何基于距离的计算将道路的相交点保留一个,在一些路口的计算当中,保留一个点比保留四个点更加容易理解。当然,是否需要这么处理,请根据自身的业务和需求来确定。

1、冗余距离计算

        这里介绍一种基于冗余距离的计算方法,将四个交点合并成一个交点信息。基于冗余距离的计算方法可以描述为如下文字:

假设道路在同一个交叉点有四个交点:

  1. 第一个点被添加到结果集和空间索引

  2. 后续三个点距离第一个点很近(< 阈值)

  3. 每个后续点都会与第一个点进行距离比较

  4. 距离小于阈值,被视为重复点而跳过

  5. 最终结果集中只保留第一个点

        使用Geotools来求解上述过程的关键代码如下:

//近似计算
private static final BigDecimal DISTANCE_TOLERANCE =  new BigDecimal(0.00003) ; // 不同坐标系下需要处理单位,4326单位为度,3米以视为同一个点

private static void addUniquePointSimple(Point point,Set<Point> output,Set<String> uniqueKeys,String pointName) {
    for (Point np : output) {
        BigDecimal distance = new BigDecimal(point.distance(np));
        System.out.println(distance);
        if ( DISTANCE_TOLERANCE.compareTo(distance) < 0 ) {
            System.out.println("存在容差");
            // 在容差范围内,跳过
            return;
        }
    }
    output.add(point);
}

2、调用过程

        那么基于上面的距离来进行冗余点计算,与之前的生成逻辑相比。这里需要增加一步,即将根据Geotools求解的四个相交点进行容差计算,最后保留容差阈值外的点位置信息。调用过程关键代码如下:

/**
 * -两条道路集合的所有交点(简单实现,带名称属性且空间唯一)
 * 
 * @param road1 第一条道路要素集合
 * @param road2 第二条道路要素集合
 * @param road1Name 第一条道路名称(用于属性)
 * @param road2Name 第二条道路名称(用于属性)
 * @return 带名称属性的相交点集合(空间唯一)
 */
private static Set<Point> findUniqueIntersectionsSimple(SimpleFeatureCollection road1,SimpleFeatureCollection road2,String road1Name,String road2Name) {
   // 1. 使用组合名称
   final String pointName = road1Name + " & " + road2Name + " 交叉点";
   // 2. 使用坐标键值进行去重
   Set<String> uniqueKeys = new HashSet<>();
   Set<Point> uniquePoints = new HashSet<>();
   try (SimpleFeatureIterator iter1 = road1.features()) {
        while (iter1.hasNext()) {
           SimpleFeature f1 = iter1.next();
           Geometry geom1 = (Geometry) f1.getDefaultGeometry();
           if (geom1 == null || geom1.isEmpty()) continue;
           try (SimpleFeatureIterator iter2 = road2.features()) {
                while (iter2.hasNext()) {
                    SimpleFeature f2 = iter2.next();
                    Geometry geom2 = (Geometry) f2.getDefaultGeometry();
                    if (geom2 == null || geom2.isEmpty()) continue;
                    // 3. 计算交点
                    Geometry result = OverlayNGRobust.overlay(geom1, geom2, OverlayNG.INTERSECTION);
                    // 4. 处理结果并添加唯一点processIntersectionsSimple(result,uniquePoints,uniqueKeys,pointName);
                }
             }
         }
     }
     return uniquePoints;
}
/**
 * -处理交点结果(简单实现)
 */
private static void processIntersectionsSimple(Geometry geom,Set<Point> output,Set<String> uniqueKeys,String pointName) {
   if (geom == null || geom.isEmpty()) return;
    if (geom instanceof Point) {
        addUniquePointSimple((Point) geom, output, uniqueKeys, pointName);
     } else if (geom instanceof MultiPoint) {
          for (int i = 0; i < geom.getNumGeometries(); i++) {
             Geometry g = geom.getGeometryN(i);
             if (g instanceof Point) {
                 addUniquePointSimple((Point) g, output, uniqueKeys, pointName);
             }
         }
     } else if (geom instanceof GeometryCollection) {
         for (int i = 0; i < geom.getNumGeometries(); i++) {
            processIntersectionsSimple(geom.getGeometryN(i), output,uniqueKeys, pointName);
       }
    }
}

3、去重后的结果

        最后来调用一下去重之后的道路相交点代码,并返回最后的计算结果信息。主要的去重调用代码如下:

public static void main(String[] args) throws Exception {
    // 1. 加载OSM道路shp文件
    File shpFile = new File("F:/vector_data/2024年OSM长沙路网/长沙路网OSM2024.shp");
    SimpleFeatureSource featureSource = loadShapefile(shpFile);
    // 2. 输入两条道路名称
    String roadName1 = "听雨路";
    String roadName2 = "赏月路";
    // 3. 获取两条道路的所有几何线
    SimpleFeatureCollection road1Features = filterRoadsByName(featureSource, roadName1);
    SimpleFeatureCollection road2Features = filterRoadsByName(featureSource, roadName2);
    // 4. 计算所有相交点
    Set<Point> intersections = findIntersections(road1Features, road2Features);
    // 5. 输出结果
    System.out.println("Found " + intersections.size() + " intersections:");
    for (Point p : intersections) {
        System.out.printf("POINT (%.6f %.6f)\n", p.getX(), p.getY());
    }
    System.out.println("*******************************");
    System.out.println("执行冗余计算");
    //6、去掉可以清理的冗余点
    intersections = findUniqueIntersectionsSimple(road1Features, road2Features,roadName1,roadName2);
    System.out.println("Found " + intersections.size() + " intersections:");
    for (Point p : intersections) {
        System.out.printf("POINT (%.6f %.6f)\n", p.getX(), p.getY());
    }
}

        这里对比了执行冗余计算前后的两种相交点的求解,并且将点的经纬度信息进行了打印输入,在IDE中执行完上述代码,可以看到以下的输出:

        可以直观的看到,经过我们的计算之后,最后的相交点就是1个,即第一个点。 输出信息如下:

六月 12, 2025 10:02:58 下午 org.geotools.image.ImageWorker <clinit>
信息: Warp/affine reduction enabled: true
Found 4 intersections:
POINT (112.867947 28.194721)
POINT (112.867948 28.194679)
POINT (112.867790 28.194655)
POINT (112.867791 28.194618)
*******************************
执行冗余计算
0.00017015393031127507331677628510391286908998154103755950927734375
存在容差
0.0000412077662565200070611363958317241440454381518065929412841796875
存在容差
0.00018686000107234120048461945007289841669262386858463287353515625
存在容差
Found 1 intersections:
POINT (112.867947 28.194721)

        基本以上输出是符合我们的预期的,也符合冗余计算的结果要求。 

三、总结

        以上就是本文的主要内容。本次实战将从 GeoTools 的环境搭建入手,详细阐述如何导入和读取道路数据,展示如何对道路几何对象进行解析和操作。接着,重点介绍容差冗余计算的核心算法实现,包括如何设定合理的容差参数,以及如何根据几何计算结果对冗余点进行处理。通过对基于 GeoTools 的道路相交多个点容差冗余计算的实战研究,不仅可以提升道路数据的质量和可用性,还能为后续的地理数据分析和应用奠定坚实的基础。同时,这一过程也展示了 GeoTools 在解决实际 GIS 问题中的强大能力和灵活性,为相关的地理信息开发工作提供了宝贵的实践经验和参考案例。

        未来的展望,在面对复杂多变的道路数据时,本案例可能存在局限性,比如无法充分考虑到不同区域、不同道路类型对容差的差异化要求,容易造成过度简化或冗余点未被有效排除的问题。也期待更多的朋友一起探讨研究。行文仓促,定有不足之处,欢迎各位朋友在评论区批评指正,不胜感激