空間關係分析

概述

空間關係分析是GIS的核心功能之一。通過判斷幾何對象之間的空間關係,可以實現空間查詢、疊加分析、選址分析等常見GIS功能。

基本空間關係

相交(Intersects)

兩個幾何對象有任意公共部分(點、線或面)時,稱為相交:

/**
 * 判斷幾何是否相交
 */
public static boolean intersects(Geometry a, Geometry b) {
    return a.intersects(b);
}

應用場景

  • 空間查詢:找出與指定區域相交的所有要素
  • 碰撞檢測:判斷兩個對象是否發生空間重疊
  • 路線規劃:判斷路線是否經過某個區域

相離(Disjoint)

兩個幾何對象沒有任何公共點時,稱為相離:

/**
 * 判斷幾何是否相離
 * 一個公共點都沒有
 */
public static boolean disjoint(Geometry a, Geometry b) {
    return a.disjoint(b);
}

説明disjointintersects互為反義關係,即:

disjoint(a, b) == !intersects(a, b)

接觸(Touches)

兩個幾何對象有公共點但沒有公共內部區域時,稱為接觸:

/**
 * 判斷幾何是否接觸
 * 有公共點但沒有公共區域
 */
public static boolean touches(Geometry a, Geometry b) {
    return a.touches(b);
}

典型情況

  • 兩個相鄰的地塊共享邊界
  • 道路與地塊的邊界相接
  • 點落在線或面的邊界上

交叉(Crosses)

兩個幾何對象的內部有公共部分,但既不相等也不包含,稱為交叉:

/**
 * 判斷幾何是否交叉
 * 有公共區域但不是包含關係
 */
public static boolean crosses(Geometry a, Geometry b) {
    return a.crosses(b);
}

典型情況

  • 道路穿過區域(線與面交叉)
  • 兩條道路相交(線與線交叉)

包含(Contains)與被包含(Within)

包含關係描述一個幾何完全位於另一個幾何內部:

/**
 * 判斷幾何A是否包含幾何B
 */
public static boolean contains(Geometry a, Geometry b) {
    return a.contains(b);
}

/**
 * 判斷幾何A是否在幾何B內部
 */
public static boolean within(Geometry a, Geometry b) {
    return a.within(b);
}

關係説明

contains(a, b) == within(b, a)

應用場景

  • 判斷點是否在某個區域內
  • 判斷某個地塊是否完全位於行政區劃內
  • 空間過濾:只保留完全落入指定範圍的要素

重疊(Overlaps)

兩個同維度的幾何對象有公共區域,但又不完全相同,稱為重疊:

/**
 * 判斷幾何是否重疊
 * 有公共區域且包含關係不成立
 */
public static boolean overlaps(Geometry a, Geometry b) {
    return a.overlaps(b);
}

説明:重疊只發生在同維度幾何之間(面與面、線與線)。

距離計算

最短距離

計算兩個幾何對象之間的最短距離:

/**
 * 計算幾何之間的距離
 * 不論點線面都是幾何間最短距離
 */
public static double distance(Geometry a, Geometry b) {
    return a.distance(b);
}

注意:返回值的單位與座標系單位一致。地理座標系返回的是度,投影座標系返回的是米。

距離判斷

快速判斷兩個幾何是否在指定距離內:

/**
 * 判斷幾何間最短距離是否小於給定距離
 */
public static boolean isWithinDistance(Geometry a, Geometry b, double distance) {
    return a.isWithinDistance(b, distance);
}

應用場景

  • 近鄰搜索:查找指定距離內的設施
  • 緩衝區分析:判斷要素是否在某要素的影響範圍內
  • 預警系統:判斷兩個移動目標是否過於接近

DE-9IM模型

DE-9IM(Dimensionally Extended 9-Intersection Model)是空間關係的標準描述模型,提供了更精確的空間關係判斷。

關係矩陣

Interior  Boundary  Exterior
Interior    II        IB        IE
Boundary    BI        BB        BE
Exterior    EI        EB        EE

每個位置的值表示兩個幾何對應部分的交集維度:

  • T:有交集(任意維度)
  • F:無交集
  • 0:交集為點
  • 1:交集為線
  • 2:交集為面
  • *:任意(不關心)

獲取關係矩陣

/**
 * 獲取幾何關係矩陣
 */
public static String relate(Geometry a, Geometry b) {
    return a.relate(b).toString();
}

模式匹配

使用模式字符串判斷特定空間關係:

/**
 * 判斷幾何是否符合給定關係
 * @param pattern 關係模式,如"T*T***FF*"
 */
public static boolean relatePattern(Geometry a, Geometry b, String pattern) {
    return a.relate(b, pattern);
}

常用模式

關係

模式

內部相交

T********

邊界相交

*T*******

A完全包含B

T*****FF*

相鄰(共享邊界)

FF*FT****

幾何相等判斷

精確相等

判斷兩個幾何是否結構完全相同:

/**
 * 判斷幾何是否對象結構相等
 * 必須有相同的節點和相同的節點順序
 */
public static boolean equalsExact(Geometry a, Geometry b) {
    return a.equalsExact(b);
}

/**
 * 帶容差的結構相等判斷
 */
public static boolean equalsExactTolerance(Geometry a, Geometry b, double tolerance) {
    return a.equalsExact(b, tolerance);
}

規範化相等

判斷兩個幾何在規範化後是否相同:

/**
 * 判斷幾何是否對象結構相等(不判斷節點順序)
 */
public static boolean equalsNorm(Geometry a, Geometry b) {
    return a.equalsNorm(b);
}

拓撲相等

判斷兩個幾何是否表示相同的點集:

/**
 * 判斷幾何是否拓撲相等
 */
public static boolean equalsTopo(Geometry a, Geometry b) {
    return a.equalsTopo(b);
}

區別説明

  • equalsExact:節點順序、數量都必須完全相同
  • equalsNorm:規範化後比較,忽略節點順序
  • equalsTopo:只比較幾何表示的空間是否相同

ESRI幾何引擎

除了JTS,還可以使用ESRI幾何引擎進行空間關係分析,其API基於WKT字符串:

空間關係判斷

/**
 * 幾何是否相交
 */
public static boolean intersects(String awkt, String bwkt, Integer wkid) {
    Geometry a = EsriUtil.createGeometryByWkt(awkt);
    Geometry b = EsriUtil.createGeometryByWkt(bwkt);
    SpatialReference sr = SpatialReference.create(wkid);
    return OperatorIntersects.local().execute(a, b, sr, null);
}

/**
 * 判斷幾何是否相離
 */
public static boolean disjoint(String awkt, String bwkt, Integer wkid) {
    Geometry a = EsriUtil.createGeometryByWkt(awkt);
    Geometry b = EsriUtil.createGeometryByWkt(bwkt);
    SpatialReference sr = SpatialReference.create(wkid);
    return OperatorDisjoint.local().execute(a, b, sr, null);
}

/**
 * 判斷幾何是否接觸
 */
public static boolean touches(String awkt, String bwkt, Integer wkid) {
    Geometry a = EsriUtil.createGeometryByWkt(awkt);
    Geometry b = EsriUtil.createGeometryByWkt(bwkt);
    SpatialReference sr = SpatialReference.create(wkid);
    return OperatorTouches.local().execute(a, b, sr, null);
}

/**
 * 判斷幾何是否包含
 */
public static boolean contains(String awkt, String bwkt, Integer wkid) {
    Geometry a = EsriUtil.createGeometryByWkt(awkt);
    Geometry b = EsriUtil.createGeometryByWkt(bwkt);
    SpatialReference sr = SpatialReference.create(wkid);
    return OperatorContains.local().execute(a, b, sr, null);
}

選擇建議

  • JTS:內存中的幾何對象操作,性能更好
  • ESRI:基於WKT字符串操作,接口更簡潔

實踐案例

案例1:空間查詢

查找與指定區域相交的所有要素:

/**
 * 空間查詢:找出與指定區域相交的要素
 */
public List<WktFeature> spatialQuery(WktLayer layer, String queryWkt) {
    List<WktFeature> result = new ArrayList<>();
    Geometry queryGeom = GeometryConverter.wkt2Geometry(queryWkt);
    
    for (WktFeature feature : layer.getFeatures()) {
        Geometry featureGeom = GeometryConverter.wkt2Geometry(feature.getWkt());
        if (featureGeom.intersects(queryGeom)) {
            result.add(feature);
        }
    }
    return result;
}

案例2:點在面內判斷

判斷一個點是否位於某個多邊形內部:

/**
 * 判斷點是否在面內
 */
public boolean isPointInPolygon(double x, double y, String polygonWkt) {
    GeometryFactory factory = new GeometryFactory();
    Point point = factory.createPoint(new Coordinate(x, y));
    Geometry polygon = GeometryConverter.wkt2Geometry(polygonWkt);
    
    // 使用contains判斷(嚴格在內部)
    // 或使用intersects判斷(含邊界上的點)
    return polygon.contains(point);
}

案例3:相鄰要素查找

查找與指定要素相鄰(共享邊界)的所有要素:

/**
 * 查找相鄰要素
 */
public List<WktFeature> findAdjacentFeatures(WktLayer layer, WktFeature target) {
    List<WktFeature> result = new ArrayList<>();
    Geometry targetGeom = GeometryConverter.wkt2Geometry(target.getWkt());
    
    for (WktFeature feature : layer.getFeatures()) {
        if (feature.getWfId().equals(target.getWfId())) {
            continue;  // 跳過自己
        }
        
        Geometry featureGeom = GeometryConverter.wkt2Geometry(feature.getWkt());
        
        // 相鄰:接觸但不重疊
        if (featureGeom.touches(targetGeom)) {
            result.add(feature);
        }
    }
    return result;
}

案例4:緩衝區範圍查詢

查找指定位置周邊一定距離內的設施:

/**
 * 緩衝區範圍查詢
 * @param centerWkt 中心點WKT
 * @param distance 查詢距離(米)
 * @param layer 要素圖層
 */
public List<WktFeature> bufferQuery(String centerWkt, double distance, WktLayer layer) {
    List<WktFeature> result = new ArrayList<>();
    
    // 轉換到投影座標系計算緩衝區
    int projWkid = CrsUtil.getProjectedWkid(CrsUtil.getDh(centerWkt));
    String projWkt = CrsUtil.transform(centerWkt, 4490, projWkid);
    
    // 創建緩衝區
    Geometry center = GeometryConverter.wkt2Geometry(projWkt);
    Geometry buffer = center.buffer(distance);
    
    // 將緩衝區轉回地理座標系進行查詢
    buffer = CrsUtil.transform(buffer, projWkid, layer.getWkid());
    
    // 查詢相交要素
    for (WktFeature feature : layer.getFeatures()) {
        Geometry featureGeom = GeometryConverter.wkt2Geometry(feature.getWkt());
        if (featureGeom.intersects(buffer)) {
            result.add(feature);
        }
    }
    return result;
}

案例5:空間關係矩陣分析

使用DE-9IM進行精確的空間關係分析:

/**
 * 分析兩個地塊的空間關係
 */
public String analyzeRelation(String wkt1, String wkt2) {
    Geometry geom1 = GeometryConverter.wkt2Geometry(wkt1);
    Geometry geom2 = GeometryConverter.wkt2Geometry(wkt2);
    
    // 獲取關係矩陣
    String matrix = relate(geom1, geom2);
    
    // 解析關係類型
    if (geom1.equals(geom2)) {
        return "相等";
    } else if (geom1.contains(geom2)) {
        return "A包含B";
    } else if (geom1.within(geom2)) {
        return "A在B內";
    } else if (geom1.overlaps(geom2)) {
        return "部分重疊";
    } else if (geom1.touches(geom2)) {
        return "相鄰(共享邊界)";
    } else if (geom1.crosses(geom2)) {
        return "穿越";
    } else if (geom1.intersects(geom2)) {
        return "相交";
    } else {
        return "相離";
    }
}

性能優化

空間索引

對於大數據量的空間查詢,使用空間索引可以顯著提升性能:

// 構建R樹索引
STRtree index = new STRtree();
for (WktFeature feature : layer.getFeatures()) {
    Geometry geom = GeometryConverter.wkt2Geometry(feature.getWkt());
    index.insert(geom.getEnvelopeInternal(), feature);
}
index.build();

// 使用索引查詢
Geometry queryGeom = GeometryConverter.wkt2Geometry(queryWkt);
List<?> candidates = index.query(queryGeom.getEnvelopeInternal());

// 對候選要素進行精確判斷
for (Object obj : candidates) {
    WktFeature feature = (WktFeature) obj;
    Geometry featureGeom = GeometryConverter.wkt2Geometry(feature.getWkt());
    if (featureGeom.intersects(queryGeom)) {
        // 真正相交的要素
    }
}

外包矩形預篩選

在進行精確空間關係判斷前,先用外包矩形快速過濾:

// 先判斷外包矩形是否相交
if (!geomA.getEnvelopeInternal().intersects(geomB.getEnvelopeInternal())) {
    return false;  // 外包矩形不相交,幾何必定不相交
}
// 再進行精確判斷
return geomA.intersects(geomB);

小結

本章介紹了空間關係分析的核心內容:

  1. 基本空間關係:相交、相離、接觸、交叉、包含、重疊
  2. 距離計算:最短距離計算和距離判斷
  3. DE-9IM模型:精確的空間關係描述和模式匹配
  4. 幾何相等:精確相等、規範化相等、拓撲相等
  5. 性能優化:空間索引和外包矩形預篩選