# 02.1 空间表征

## 核心问题

> 机器如何"看懂"地理空间？
> 栅格和矢量，谁更智能？还是"小孩子才做选择"？

---

## 概念讲解

### 空间表征的本质

空间表征是空间智能的基石——它解决的是**"如何用计算机能理解的方式描述空间"**这一根本问题。

```
现实世界 → 空间表征 → 计算操作

─────────────────────────────────────────────

真实景观    计算机表示      算法处理
  🏔️      →  栅格/矢量   →  分析/推理
  🌲                              ↘
  🏙️                    图表示    决策/优化
  🛣️
```

### 表征类型对比

| 维度 | 栅格 (Raster) | 矢量 (Vector) |
|-----|-------------|-------------|
| **基本单元** | 像元 (Pixel/Cell) | 点、线、面 (Point, Line, Polygon) |
| **数据结构** | 规则网格 | 坐标序列 |
| **适用场景** | 连续场、表面分析 | 离散要素、边界精确 |
| **典型操作** | 邻域分析、代数运算 | 拓扑分析、几何运算 |
| **存储效率** | 与分辨率强相关 | 与复杂度相关 |
| **代表数据** | DEM、遥感影像 | 行政边界、道路网 |

### 图表示 (Graph Representation)

许多空间问题可以抽象为图：

```
空间 → 图的转换

生态网络场景          图表示
─────────────       ───────
源地A ──廊道──→ 源地B    节点A ──边(weight=5)──→ 节点B
   │                      │
   └──廊道──→ 源地C       └──边(weight=8)──→ 节点C

道路网络              图表示
交叉路口              节点
道路路段              加权边
```

**图表示的优势**：
- 将空间问题转化为成熟的图算法
- 天然支持连通性、路径分析
- 易于扩展（添加权重、方向）

### 多尺度表征

空间智能必须处理多尺度问题：

```
┌─────────────────────────────────────────────────────────────┐
│                        多尺度金字塔                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Level 3 (1:1,000,000)    ┌─────┐                          │
│  区域尺度                  │  A  │                          │
│                           └─────┘                          │
│                           一个像元 = 1km²                   │
│                                                             │
│  Level 2 (1:100,000)     ┌─────┬─────┐                      │
│  景观尺度                 │  A  │  B  │                      │
│                           └─────┴─────┘                      │
│                           一个像元 = 100m × 100m             │
│                                                             │
│  Level 1 (1:10,000)      ┌─┬─┬─┬─┬─┐                        │
│  局地尺度                 │A│A│A│B│B│                        │
│                           └─┴─┴─┴─┴─┘                        │
│                           一个像元 = 10m × 10m               │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

**多尺度处理策略**：
- **尺度金字塔**：预先生成多分辨率版本
- **自适应网格**：关键区域高分辨率，其他区域低分辨率
- **层次聚类**：构建空间层次结构

### 空间索引 (Spatial Indexing)

当数据量大时，空间索引是效率的关键：

```
┌─────────────────────────────────────────────────────────────┐
│                      空间索引类型对比                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. R-Tree (常用)                                           │
│     ┌────────────────────┐                                  │
│     │ ┌───┐  ┌────┐      │  层次包围盒                      │
│     │ │ A│  │ B  │      │  适合：复杂几何体                │
│     │ └───┘  └────┘      │                                  │
│     └────────────────────┘                                  │
│                                                             │
│  2. Quadtree (栅格友好)                                     │
│     ┌─────────┬─────────┐                                  │
│     │   ●     │         │  四叉递归分解                     │
│     ├─────────┼─────────┤  适合：点数据、栅格              │
│     │     ●   │    ●    │                                  │
│     └─────────┴─────────┘                                  │
│                                                             │
│  3. Grid Index (简单高效)                                   │
│     ┌───┬───┬───┬───┐                                      │
│     │   │ ● │   │   │  规则网格分桶                        │
│     ├───┼───┼───┼───┤  适合：均匀分布数据                  │
│     │   │   │ ● │   │                                      │
│     └───┴───┴───┴───┘                                      │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

---

## 设计原理

### 表征选择决策树

```
                        开始
                         │
                         ▼
                  数据是什么类型？
                  ┌──────┴──────┐
                  │             │
             连续场/表面      离散要素
             (高程、温度)    (边界、道路)
                  │             │
                  ▼             ▼
              栅格优先      需要精确边界？
                  │          ┌──┴──┐
                  │          │     │
                  │         是    否
                  │          │     │
                  │          ▼     ▼
                  │       矢量   栅格也可
                  │          │     │
                  │          ▼     │
                  │    需要拓扑分析？
                  │          │     │
                  │      ┌───┴───┐ │
                  │      │       │ │
                  │     是      否 │
                  │      │       │ │
                  ▼      ▼       ▼ ▼
              栅格   拓扑矢量  简单矢量
```

### 混合表征策略

实践中，最佳方案往往是混合使用：

```python
class HybridSpatialRepresentation:
    """
    混合空间表征

    设计理念：不同操作用最合适的表征
    """

    def __init__(self, raster_resolution=30):
        """
        Args:
            raster_resolution: 栅格分辨率(米)
        """
        self.raster_resolution = raster_resolution
        self.vector_features = {}  # 矢量要素
        self.raster_surfaces = {}   # 栅格表面
        self.spatial_index = None   # 空间索引

    def add_vector_feature(self, feature_id, geometry, attributes):
        """添加矢量要素（适合精确边界）"""
        import shapely.geometry as geom

        self.vector_features[feature_id] = {
            'geometry': geometry if isinstance(geometry, geom.base.BaseGeometry)
                              else geom.shape(geometry),
            'attributes': attributes
        }

        # 更新空间索引
        self._build_spatial_index()

    def add_raster_surface(self, surface_id, array, transform=None):
        """添加栅格表面（适合连续场）"""
        import numpy as np

        self.raster_surfaces[surface_id] = {
            'array': np.asarray(array),
            'transform': transform,
            'resolution': self.raster_resolution
        }

    def _build_spatial_index(self):
        """构建R-Tree空间索引"""
        from rtree import index

        idx = index.Index()
        for i, (fid, feature) in enumerate(self.vector_features.items()):
            idx.insert(i, feature['geometry'].bounds, fid)

        self.spatial_index = idx

    def query_by_location(self, point, buffer_distance=0):
        """
        基于位置查询

        使用矢量+索引：高效精确
        """
        from shapely.geometry import Point

        query_point = Point(point) if not isinstance(point, Point) else point
        query_box = query_point.buffer(buffer_distance).bounds

        # 使用空间索引快速筛选
        candidates = []
        for i in self.spatial_index.intersection(query_box):
            fid = list(self.vector_features.keys())[i]
            feature = self.vector_features[fid]

            if feature['geometry'].intersects(query_point):
                candidates.append({
                    'id': fid,
                    'geometry': feature['geometry'],
                    'attributes': feature['attributes']
                })

        return candidates

    def extract_values_at_points(self, surface_id, points):
        """
        在点上提取栅格值

        使用栅格：快速采样
        """
        import numpy as np

        surface = self.raster_surfaces.get(surface_id)
        if not surface:
            raise ValueError(f"Surface {surface_id} not found")

        array = surface['array']
        values = []

        for point in points:
            # 坐标转换（如果有transform）
            if surface['transform']:
                # 这里简化处理，实际需要仿射变换
                col = int(point[0] / self.raster_resolution)
                row = int(point[1] / self.raster_resolution)
            else:
                col, row = int(point[0]), int(point[1])

            # 边界检查
            if 0 <= row < array.shape[0] and 0 <= col < array.shape[1]:
                values.append(array[row, col])
            else:
                values.append(np.nan)

        return values

    def vector_to_raster(self, feature_id, value_field=None,
                         surface_id=None, default_value=1):
        """
        矢量转栅格

        适用场景：需要栅格分析（如邻域、成本距离）
        """
        import numpy as np
        from rasterio.features import rasterize

        feature = self.vector_features.get(feature_id)
        if not feature:
            raise ValueError(f"Feature {feature_id} not found")

        # 确定输出范围
        geom = feature['geometry']
        bounds = geom.bounds
        width = int((bounds[2] - bounds[0]) / self.raster_resolution) + 1
        height = int((bounds[3] - bounds[1]) / self.raster_resolution) + 1

        # 确定栅格化值
        if value_field and value_field in feature['attributes']:
            value = feature['attributes'][value_field]
        else:
            value = default_value

        # 栅格化
        output_array = rasterize(
            [(geom, value)],
            out_shape=(height, width),
            transform=None,  # 简化处理
            fill=0,
            dtype=np.float32
        )

        # 保存栅格
        new_surface_id = surface_id or f"{feature_id}_raster"
        self.add_raster_surface(new_surface_id, output_array)

        return new_surface_id

    def to_graph(self, threshold_distance=None):
        """
        转换为图表示

        适用场景：连通性分析、路径优化
        """
        import networkx as nx
        from shapely.geometry import Point

        G = nx.Graph()

        # 添加节点（矢量要素）
        for fid, feature in self.vector_features.items():
            centroid = feature['geometry'].centroid
            G.add_node(fid, pos=(centroid.x, centroid.y))

        # 添加边（基于距离阈值）
        if threshold_distance:
            fids = list(self.vector_features.keys())
            for i, fid1 in enumerate(fids):
                for fid2 in fids[i+1:]:
                    geom1 = self.vector_features[fid1]['geometry']
                    geom2 = self.vector_features[fid2]['geometry']
                    dist = geom1.distance(geom2)

                    if dist <= threshold_distance:
                        G.add_edge(fid1, fid2, weight=dist)

        return G
```

### 设计权衡

| 决策维度 | 选项A | 选项B | 权衡考量 |
|---------|-------|-------|---------|
| 数据结构 | 栅格 | 矢量 | 精度 vs 效率；连续 vs 离散 |
| 分辨率 | 高精度 | 低精度 | 存储成本 vs 信息保留 |
| 索引方式 | R-Tree | Quadtree | 数据分布、查询类型 |
| 单一 vs 混合 | 统一表征 | 按需选择 | 一致性 vs 灵活性 |

---

## 代码示例

### 示例1：栅格数据处理

```python
"""
栅格数据处理示例
"""
import numpy as np
from typing import Tuple, List, Optional
from scipy.ndimage import convolve

class RasterProcessor:
    """栅格数据处理器"""

    def __init__(self, array: np.ndarray, resolution: float = 1.0):
        """
        Args:
            array: 栅格数据数组
            resolution: 分辨率(单位/像元)
        """
        self.array = np.asarray(array)
        self.resolution = resolution
        self.nodata = -9999  # 无数据值

    def slope(self) -> np.ndarray:
        """
        计算坡度

        Returns:
            坡度数组(度)
        """
        # Sobel算子
        kernel_x = np.array([[-1, 0, 1],
                             [-2, 0, 2],
                             [-1, 0, 1]])
        kernel_y = np.array([[-1, -2, -1],
                             [ 0,  0,  0],
                             [ 1,  2,  1]])

        # 计算梯度
        dz_dx = convolve(self.array, kernel_x) / (8 * self.resolution)
        dz_dy = convolve(self.array, kernel_y) / (8 * self.resolution)

        # 坡度(度)
        slope = np.arctan(np.sqrt(dz_dx**2 + dz_dy**2)) * 180 / np.pi

        return slope

    def aspect(self) -> np.ndarray:
        """
        计算坡向

        Returns:
            坡向数组(度, 0-360)
        """
        kernel_x = np.array([[-1, 0, 1],
                             [-2, 0, 2],
                             [-1, 0, 1]])
        kernel_y = np.array([[-1, -2, -1],
                             [ 0,  0,  0],
                             [ 1,  2,  1]])

        dz_dx = convolve(self.array, kernel_x)
        dz_dy = convolve(self.array, kernel_y)

        aspect = np.arctan2(dz_dy, -dz_x) * 180 / np.pi
        aspect = (90 - aspect) % 360

        return aspect

    def neighborhood_stats(self, radius: int = 1) -> dict:
        """
        邻域统计

        Args:
            radius: 邻域半径(像元)

        Returns:
            统计指标字典
        """
        from scipy.ndimage import uniform_filter

        size = 2 * radius + 1

        # 均值
        mean = uniform_filter(self.array.astype(float), size=size, mode='reflect')

        # 方差
        squared_mean = uniform_filter((self.array ** 2).astype(float),
                                     size=size, mode='reflect')
        variance = squared_mean - mean ** 2

        # 最值
        from scipy.ndimage import maximum_filter, minimum_filter
        maximum = maximum_filter(self.array, size=size, mode='reflect')
        minimum = minimum_filter(self.array, size=size, mode='reflect')

        return {
            'mean': mean,
            'std': np.sqrt(np.maximum(variance, 0)),
            'max': maximum,
            'min': minimum,
            'range': maximum - minimum
        }

    def resample(self, target_resolution: float,
                 method: str = 'bilinear') -> 'RasterProcessor':
        """
        重采样

        Args:
            target_resolution: 目标分辨率
            method: 'nearest', 'bilinear', 'cubic'

        Returns:
            新的RasterProcessor
        """
        from scipy.ndimage import zoom

        scale_factor = self.resolution / target_resolution

        if method == 'nearest':
            order = 0
        elif method == 'bilinear':
            order = 1
        elif method == 'cubic':
            order = 3
        else:
            raise ValueError(f"Unknown method: {method}")

        resampled = zoom(self.array, scale_factor, order=order)

        return RasterProcessor(resampled, target_resolution)

# 使用示例
if __name__ == "__main__":
    # 创建示例DEM
    dem_data = np.array([
        [100, 105, 110, 108, 102],
        [102, 108, 115, 112, 105],
        [105, 112, 120, 118, 110],
        [108, 115, 122, 120, 112],
        [106, 110, 115, 112, 108]
    ], dtype=float)

    processor = RasterProcessor(dem_data, resolution=30)

    # 计算坡度
    slope = processor.slope()
    print(f"Average slope: {np.mean(slope):.2f} degrees")

    # 计算坡向
    aspect = processor.aspect()

    # 邻域统计
    stats = processor.neighborhood_stats(radius=1)
    print(f"Smoothed elevation mean: {np.mean(stats['mean']):.2f}")
```

### 示例2：矢量数据处理

```python
"""
矢量数据处理示例
"""
import numpy as np
from typing import List, Dict, Any, Optional
from shapely.geometry import Point, LineString, Polygon, MultiPolygon
from shapely.ops import unary_union, voronoi_diagram
import geopandas as gpd

class VectorProcessor:
    """矢量数据处理器"""

    def __init__(self, crs: str = "EPSG:4326"):
        """
        Args:
            crs: 坐标参考系统
        """
        self.crs = crs
        self.features = []
        self.gdf = None

    def add_feature(self, geometry: Any, attributes: Dict[str, Any]):
        """添加要素"""
        # 确保是Shapely几何对象
        if isinstance(geometry, dict):
            from shapely.geometry import shape
            geometry = shape(geometry)

        self.features.append({
            'geometry': geometry,
            'attributes': attributes
        })

    def to_geodataframe(self) -> gpd.GeoDataFrame:
        """转换为GeoDataFrame"""
        if not self.features:
            return gpd.GeoDataFrame(geometry=[], crs=self.crs)

        data = {
            'geometry': [f['geometry'] for f in self.features],
            **{k: [f['attributes'].get(k) for f in self.features]
               for k in self.features[0]['attributes'].keys()}
        }

        self.gdf = gpd.GeoDataFrame(data, crs=self.crs)
        return self.gdf

    def buffer_all(self, distance: float,
                   resolution: int = 16) -> 'VectorProcessor':
        """
        缓冲区分析

        Args:
            distance: 缓冲距离(与CRS单位一致)
            resolution: 缓冲圆弧的分辨率

        Returns:
            新的VectorProcessor
        """
        result = VectorProcessor(self.crs)

        for feature in self.features:
            buffered = feature['geometry'].buffer(
                distance,
                resolution=resolution
            )
            result.add_feature(buffered, feature['attributes'])

        return result

    def intersect_all(self, other: 'VectorProcessor') -> 'VectorProcessor':
        """
        相交分析

        Args:
            other: 另一个VectorProcessor

        Returns:
            相交结果的新VectorProcessor
        """
        result = VectorProcessor(self.crs)

        for feat1 in self.features:
            for feat2 in other.features:
                intersection = feat1['geometry'].intersection(feat2['geometry'])

                if not intersection.is_empty:
                    # 合并属性
                    merged_attrs = {
                        **{f"left_{k}": v for k, v in feat1['attributes'].items()},
                        **{f"right_{k}": v for k, v in feat2['attributes'].items()}
                    }
                    result.add_feature(intersection, merged_attrs)

        return result

    def centroid(self) -> List[Point]:
        """计算所有要素的质心"""
        return [f['geometry'].centroid for f in self.features]

    def area(self) -> List[float]:
        """计算所有面要素的面积"""
        areas = []
        for f in self.features:
            geom = f['geometry']
            if isinstance(geom, (Polygon, MultiPolygon)):
                # 使用投影后的CRS计算更准确
                areas.append(geom.area)
            else:
                areas.append(0.0)
        return areas

    def length(self) -> List[float]:
        """计算所有线要素的长度"""
        lengths = []
        for f in self.features:
            geom = f['geometry']
            if isinstance(geom, (LineString, Point)):
                lengths.append(geom.length)
            else:
                lengths.append(0.0)
        return lengths

    def dissolve(self, by_attribute: Optional[str] = None) -> 'VectorProcessor':
        """
        融合要素

        Args:
            by_attribute: 按此属性分组融合

        Returns:
            融合后的新VectorProcessor
        """
        if not self.features:
            return VectorProcessor(self.crs)

        if by_attribute:
            # 按属性分组
            groups = {}
            for f in self.features:
                key = f['attributes'].get(by_attribute)
                if key not in groups:
                    groups[key] = []
                groups[key].append(f['geometry'])

            result = VectorProcessor(self.crs)
            for key, geometries in groups.items():
                dissolved = unary_union(geometries)
                result.add_feature(dissolved, {by_attribute: key})

            return result
        else:
            # 全部融合
            dissolved = unary_union([f['geometry'] for f in self.features])
            result = VectorProcessor(self.crs)
            result.add_feature(dissolved, {})
            return result

# 使用示例
if __name__ == "__main__":
    # 创建示例矢量数据
    processor = VectorProcessor(crs="EPSG:3857")  # 投影坐标系，单位米

    # 添加一些面要素
    processor.add_feature(
        Polygon([(0, 0), (100, 0), (100, 100), (0, 100)]),
        {'id': 1, 'type': 'forest'}
    )
    processor.add_feature(
        Polygon([(120, 20), (200, 20), (200, 120), (120, 120)]),
        {'id': 2, 'type': 'forest'}
    )
    processor.add_feature(
        Polygon([(80, 80), (150, 80), (150, 150), (80, 150)]),
        {'id': 3, 'type': 'wetland'}
    )

    # 转换为GeoDataFrame
    gdf = processor.to_geodataframe()
    print(f"Number of features: {len(gdf)}")

    # 缓冲区分析
    buffered = processor.buffer_all(distance=50)
    print(f"Buffered features: {len(buffered.features)}")

    # 融合分析
    dissolved = processor.dissolve(by_attribute='type')
    print(f"Dissolved groups: {len(dissolved.features)}")
```

### 示例3：图构建与分析

```python
"""
空间图表示示例
"""
import numpy as np
import networkx as nx
from typing import List, Tuple, Dict, Optional
from shapely.geometry import Point, LineString

class SpatialGraphBuilder:
    """空间图构建器"""

    @staticmethod
    def from_points(points: List[Point],
                    distance_threshold: float,
                    distance_type: str = 'euclidean') -> nx.Graph:
        """
        从点集构建图（基于距离阈值）

        Args:
            points: 点列表
            distance_threshold: 连接阈值
            distance_type: 'euclidean' 或 'manhattan'

        Returns:
            NetworkX图
        """
        G = nx.Graph()

        # 添加节点
        for i, point in enumerate(points):
            G.add_node(i, pos=(point.x, point.y))

        # 添加边
        for i in range(len(points)):
            for j in range(i + 1, len(points)):
                if distance_type == 'euclidean':
                    dist = points[i].distance(points[j])
                else:  # manhattan
                    dist = abs(points[i].x - points[j].x) + \
                          abs(points[i].y - points[j].y)

                if dist <= distance_threshold:
                    G.add_edge(i, j, weight=dist)

        return G

    @staticmethod
    def from_polygons(polygons: List[Polygon],
                     connectivity_type: str = 'shared_boundary') -> nx.Graph:
        """
        从多边形构建图（基于拓扑关系）

        Args:
            polygons: 多边形列表
            connectivity_type: 'shared_boundary' 或 'within_distance'

        Returns:
            NetworkX图
        """
        G = nx.Graph()

        # 添加节点
        for i, poly in enumerate(polygons):
            G.add_node(i, centroid=poly.centroid, area=poly.area)

        # 添加边
        for i in range(len(polygons)):
            for j in range(i + 1, len(polygons)):
                if connectivity_type == 'shared_boundary':
                    # 共享边界
                    if polygons[i].touches(polygons[j]):
                        # 计算共享边界长度
                        shared = polygons[i].intersection(polygons[j])
                        weight = shared.length if not shared.is_empty else 0
                        G.add_edge(i, j, weight=weight)
                elif connectivity_type == 'within_distance':
                    # 距离阈值
                    dist = polygons[i].distance(polygons[j])
                    if dist >= 0 and dist <= 100:  # 100米阈值
                        G.add_edge(i, j, weight=dist)

        return G

    @staticmethod
    def from_network(lines: List[LineString]) -> nx.Graph:
        """
        从线网络构建图（如道路网）

        Args:
            lines: 线列表

        Returns:
            NetworkX图
        """
        G = nx.Graph()

        # 收集所有端点
        endpoints = []
        for i, line in enumerate(lines):
            coords = list(line.coords)
            endpoints.append((i, Point(coords[0]), 'start'))
            endpoints.append((i, Point(coords[-1]), 'end'))

        # 构建节点（合并接近的端点）
        tolerance = 1e-6
        node_id = 0
        point_to_node = {}

        for line_idx, point, _ in endpoints:
            # 检查是否已有接近的节点
            matched = False
            for existing_point, existing_node in point_to_node.items():
                if existing_point.distance(point) < tolerance:
                    point_to_node[point] = existing_node
                    matched = True
                    break

            if not matched:
                point_to_node[point] = node_id
                G.add_node(node_id, pos=(point.x, point.y))
                node_id += 1

        # 添加边
        for i, line in enumerate(lines):
            coords = list(line.coords)
            start_point = Point(coords[0])
            end_point = Point(coords[-1])

            start_node = point_to_node[start_point]
            end_node = point_to_node[end_point]

            G.add_edge(start_node, end_node,
                      weight=line.length,
                      geometry=line,
                      edge_id=i)

        return G

    @staticmethod
    def compute_connectivity_metrics(G: nx.Graph) -> Dict[str, float]:
        """
        计算图的连通性指标

        Args:
            G: NetworkX图

        Returns:
            指标字典
        """
        metrics = {}

        # 基本指标
        metrics['n_nodes'] = G.number_of_nodes()
        metrics['n_edges'] = G.number_of_edges()

        if G.number_of_nodes() == 0:
            return metrics

        # 连通分量
        metrics['n_components'] = nx.number_connected_components(G)

        # 最大连通分量
        largest_cc = max(nx.connected_components(G), key=len) if G.nodes() else set()
        metrics['largest_component_size'] = len(largest_cc)
        metrics['largest_component_ratio'] = len(largest_cc) / G.number_of_nodes()

        # 平均度
        degrees = [d for n, d in G.degree()]
        metrics['avg_degree'] = np.mean(degrees) if degrees else 0

        # 网络密度
        metrics['density'] = nx.density(G)

        # 平均最短路径长度（仅当图连通时）
        if nx.is_connected(G):
            metrics['avg_path_length'] = nx.average_shortest_path_length(G)
            metrics['diameter'] = nx.diameter(G)
        else:
            # 对最大连通分量计算
            if largest_cc:
                subgraph = G.subgraph(largest_cc)
                metrics['avg_path_length_lcc'] = nx.average_shortest_path_length(subgraph)
                metrics['diameter_lcc'] = nx.diameter(subgraph)

        # 聚类系数
        metrics['avg_clustering'] = nx.average_clustering(G)

        return metrics

# 使用示例
if __name__ == "__main__":
    # 示例1：从点构建图
    points = [
        Point(0, 0), Point(50, 0), Point(100, 0),
        Point(0, 50), Point(50, 50), Point(100, 50),
        Point(0, 100), Point(50, 100), Point(100, 100)
    ]

    G_points = SpatialGraphBuilder.from_points(points, distance_threshold=80)
    metrics = SpatialGraphBuilder.compute_connectivity_metrics(G_points)
    print("Point Graph Metrics:", {k: round(v, 2) if isinstance(v, float) else v
                                   for k, v in metrics.items()})

    # 示例2：从多边形构建图
    from shapely.geometry import box

    polygons = [
        box(0, 0, 50, 50),
        box(50, 0, 100, 50),
        box(0, 50, 50, 100),
        box(50, 50, 100, 100)
    ]

    G_poly = SpatialGraphBuilder.from_polygons(polygons)
    metrics_poly = SpatialGraphBuilder.compute_connectivity_metrics(G_poly)
    print("Polygon Graph Metrics:", {k: round(v, 2) if isinstance(v, float) else v
                                     for k, v in metrics_poly.items()})
```

---

## 案例分析

### ENAgent中的混合表征策略

ENAgent（生态网络分析智能体）在处理生态网络时采用了混合表征策略：

```python
class ENAgentSpatialManager:
    """
    ENAgent的空间管理模块

    核心设计：不同数据类型用最合适的表征方式
    """

    def __init__(self, raster_resolution=30):
        # 矢量：生态源地（精确边界）
        self.sources = VectorProcessor()

        # 栅格：阻力面（连续场）
        self.resistance_surface = None

        # 图：生态网络（连通性分析）
        self.network_graph = None

    def load_sources_from_vector(self, vector_file):
        """从矢量文件加载源地"""
        import geopandas as gpd

        gdf = gpd.read_file(vector_file)

        for _, row in gdf.iterrows():
            self.sources.add_feature(
                row.geometry,
                {'id': row.get('id', len(self.sources.features)),
                 'name': row.get('name', ''),
                 'area': row.geometry.area}
            )

    def create_resistance_surface(self, land_use_raster, resistance_dict):
        """
        创建阻力面（栅格）

        Args:
            land_use_raster: 土地利用栅格
            resistance_dict: {土地类型: 阻力值}
        """
        import numpy as np

        # 栅格计算：矢量化重映射
        land_use_array = self._read_raster(land_use_raster)

        # 创建阻力面
        resistance = np.zeros_like(land_use_array, dtype=float)
        for land_type, resist_value in resistance_dict.items():
            resistance[land_use_array == land_type] = resist_value

        self.resistance_surface = resistance

        return resistance

    def build_network_graph(self, connectivity_threshold=5000):
        """
        构建生态网络图

        用于连通性分析和优化
        """
        # 从源地质心构建点集
        centroids = self.sources.centroid()

        # 构建图
        self.network_graph = SpatialGraphBuilder.from_points(
            centroids,
            distance_threshold=connectivity_threshold
        )

        return self.network_graph

    def analyze_connectivity(self):
        """分析生态网络连通性"""
        if self.network_graph is None:
            self.build_network_graph()

        return SpatialGraphBuilder.compute_connectivity_metrics(
            self.network_graph
        )

    def _read_raster(self, raster_file):
        """读取栅格文件"""
        import rasterio

        with rasterio.open(raster_file) as src:
            return src.read(1)
```

**关键设计决策**：

1. **源地用矢量**：需要精确边界和面积计算
2. **阻力面用栅格**：需要邻域分析和成本距离计算
3. **网络用图**：需要连通性分析和路径优化

这种混合策略充分发挥了各种表征的优势。

---

## 反思与延伸

### 思考问题

1. **尺度效应**：在不同分析尺度下，同一空间现象的表征会有什么变化？

2. **不确定性传播**：从一种表征转换到另一种（如矢量转栅格）时，不确定性如何传播？

3. **动态数据**：时变的空间数据应该如何表征？

4. **三维扩展**：如何将这些二维表征扩展到三维空间？

5. **存储与效率**：当数据量达到TB级别时，表征策略需要做什么调整？

### 延伸阅读

- **"Fundamentals of Geographic Information Systems"** (Demers) - 空间数据模型基础
- **"Geographic Information Systems and Science"** (Longley) - 第3-4章
- **"Spatial Databases"** (Rigaux) - 空间索引原理
- Shapely Documentation - Python几何操作
- Rasterio Documentation - Python栅格处理

---

## 关键要点

1. **空间表征是空间智能的基础**：选择合适的表征方式直接影响后续分析的效率和准确性

2. **栅格和矢量各有优势**：栅格适合连续场和邻域分析，矢量适合离散要素和精确边界

3. **图表示连接空间与算法**：将空间问题转化为图问题，可以应用丰富的图算法

4. **多尺度是现实需求**：空间智能系统必须能处理不同尺度的数据和分析

5. **混合策略往往是最佳选择**：ENAgent的实践表明，根据数据类型和操作需求选择表征方式是最有效的
