第03章 - 核心架构解析

3.1 项目结构概览

3.1.1 包结构

OGU4J采用清晰的分层架构,各包职责明确:

com.znlgis.ogu4j
├── datasource          # 数据源工具层
│   ├── OguLayerUtil    # 统一的图层读写工具
│   └── GtTxtUtil       # 国土TXT格式工具
├── engine              # GIS引擎核心层
│   ├── enums           # 枚举类型定义
│   ├── io              # 读写器接口
│   ├── model           # 数据模型
│   │   └── layer       # 图层模型
│   └── util            # 引擎工具类
├── exception           # 异常处理层
├── geometry            # 几何处理层
└── utils               # 通用工具层

3.1.2 层次依赖关系

┌─────────────────────────────────────────────────────────────┐
│                     datasource (数据源工具层)                │
│     OguLayerUtil, GtTxtUtil                                 │
├─────────────────────────────────────────────────────────────┤
│                      engine (引擎核心层)                     │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │
│  │   GisEngine  │  │  LayerReader│  │ LayerWriter │         │
│  │   Factory    │  │  Interface  │  │ Interface   │         │
│  └─────────────┘  └─────────────┘  └─────────────┘         │
│  ┌─────────────────────────────────────────────────┐       │
│  │              model (数据模型)                     │       │
│  │  OguLayer, OguFeature, OguField, OguFieldValue  │       │
│  └─────────────────────────────────────────────────┘       │
│  ┌─────────────────────────────────────────────────┐       │
│  │              util (引擎工具)                      │       │
│  │  CrsUtil, ShpUtil, PostgisUtil, OgrUtil         │       │
│  └─────────────────────────────────────────────────┘       │
├─────────────────────────────────────────────────────────────┤
│  geometry (几何处理)  │  exception (异常)  │  utils (工具)   │
│     GeometryUtil      │   OguException     │   ZipUtil      │
│                       │   及其子类          │   EncodingUtil │
└─────────────────────────────────────────────────────────────┘

3.2 核心模块详解

3.2.1 datasource - 数据源工具层

这是面向开发者的顶层API,提供最简洁的数据访问接口。

OguLayerUtil

统一的图层读写工具类,是使用OGU4J最常用的入口:

/**
 * 数据源工具类,提供统一的图层读写接口
 * 屏蔽底层引擎差异,简化开发者调用
 */
public class OguLayerUtil {
    
    /**
     * 读取图层
     * @param dataFormatType 数据格式类型
     * @param path 数据路径(文件路径或连接字符串)
     * @param layerName 图层名称
     * @param attributeFilter 属性过滤条件(CQL表达式)
     * @param spatialFilter 空间过滤条件(WKT格式)
     * @param engineType 使用的GIS引擎
     * @return 统一的OguLayer对象
     */
    public static OguLayer readLayer(
        DataFormatType dataFormatType,
        String path,
        String layerName,
        String attributeFilter,
        String spatialFilter,
        GisEngineType engineType
    ) throws OguException;
    
    /**
     * 写入图层
     * @param dataFormatType 数据格式类型
     * @param layer 要写入的图层
     * @param path 输出路径
     * @param layerName 图层名称
     * @param options 额外选项
     * @param engineType 使用的GIS引擎
     */
    public static void writeLayer(
        DataFormatType dataFormatType,
        OguLayer layer,
        String path,
        String layerName,
        Map<String, Object> options,
        GisEngineType engineType
    ) throws OguException;
}

设计思想:

  1. 统一入口:所有格式读写都通过同一方法
  2. 参数标准化:使用枚举确保类型安全
  3. 引擎透明:开发者无需了解底层引擎细节

GtTxtUtil

专门处理国土TXT坐标格式的工具类:

/**
 * 国土TXT坐标文件处理工具
 * 支持自然资源部门的标准TXT格式
 */
public class GtTxtUtil {
    
    /**
     * 读取TXT文件
     * @param txtPath TXT文件路径
     * @param fields 字段定义(可为null,为null时按TXT内容推断)
     * @return 图层对象
     */
    public static OguLayer loadTxt(String txtPath, List<OguField> fields);
    
    /**
     * 保存为TXT文件
     * @param layer 图层对象
     * @param txtPath 输出路径
     * @param metadata 元数据信息
     * @param fieldNames 需要导出的字段名(可为null)
     * @param zoneNumber 带号(为null时按首要素推导)
     */
    public static void saveTxt(
        OguLayer layer, 
        String txtPath, 
        OguLayerMetadata metadata,
        List<String> fieldNames,
        Integer zoneNumber
    );
}

3.2.2 engine - 引擎核心层

引擎层是OGU4J的核心,实现了双引擎架构。

引擎接口设计

/**
 * GIS引擎接口
 * 定义了所有引擎必须实现的操作
 */
public interface GisEngine {
    
    /**
     * 获取引擎名称
     */
    String getName();
    
    /**
     * 检查引擎是否可用
     */
    boolean isAvailable();
    
    /**
     * 检查引擎是否支持指定的数据格式
     */
    boolean supports(DataFormatType formatType);
    
    /**
     * 获取指定格式的图层读取器
     */
    LayerReader getReader(DataFormatType formatType) throws OguException;
    
    /**
     * 获取指定格式的图层写入器
     */
    LayerWriter getWriter(DataFormatType formatType) throws OguException;
    
    /**
     * 读取图层数据
     */
    OguLayer readLayer(DataFormatType formatType, String path, String layerName,
                       String attributeFilter, String spatialFilterWkt) throws OguException;
    
    /**
     * 写入图层数据
     */
    void writeLayer(DataFormatType formatType, OguLayer layer, String path,
                    String layerName, Map<String, Object> options) throws OguException;
}

引擎工厂

/**
 * GIS引擎工厂
 * 负责创建和管理引擎实例(引擎实例为静态单例,不缓存于Map)
 */
public final class GisEngineFactory {
    
    private static final GeoToolsEngine GEOTOOLS_ENGINE = new GeoToolsEngine();
    private static final GdalEngine GDAL_ENGINE = new GdalEngine();
    
    /**
     * 根据引擎类型获取GIS引擎
     * engineType为null或AUTO时自动选择(GDAL优先,否则GeoTools)
     * @param engineType 引擎类型
     * @return 引擎实例
     */
    public static GisEngine getEngine(GisEngineType engineType) throws EngineNotSupportedException {
        // GDAL不可用而显式请求GDAL时抛EngineNotSupportedException
        ...
    }
    
    /**
     * 根据数据格式获取合适的引擎(FILEGDB必须GDAL,其余GDAL可用则优先GDAL)
     */
    public static GisEngine getEngine(DataFormatType formatType) throws EngineNotSupportedException { ... }
    
    /**
     * 校验引擎是否支持该格式后返回引擎
     */
    public static GisEngine getEngine(GisEngineType engineType, DataFormatType formatType) throws OguException { ... }
    
    /**
     * 直接获取GeoTools引擎
     */
    public static GeoToolsEngine getGeoToolsEngine() { ... }
    
    /**
     * 直接获取GDAL引擎(不可用时抛EngineNotSupportedException)
     */
    public static GdalEngine getGdalEngine() throws EngineNotSupportedException { ... }
}

GeoTools引擎实现

/**
 * GeoTools引擎实现
 */
public class GeoToolsEngine implements GisEngine {
    
    private static final String ENGINE_NAME = "GeoTools";
    
    private static final Set<DataFormatType> SUPPORTED_FORMATS = EnumSet.of(
            DataFormatType.SHP, DataFormatType.GEOJSON,
            DataFormatType.POSTGIS, DataFormatType.TXT);
    
    @Override
    public String getName() {
        return ENGINE_NAME;
    }
    
    @Override
    public boolean supports(DataFormatType formatType) {
        return SUPPORTED_FORMATS.contains(formatType);
    }
    
    // 读写器按格式创建,无常驻字段
    @Override
    public LayerReader getReader(DataFormatType formatType) throws OguException {
        // 不支持的格式抛EngineNotSupportedException
        return new GeoToolsLayerReader(formatType);
    }
    
    @Override
    public LayerWriter getWriter(DataFormatType formatType) throws OguException {
        return new GeoToolsLayerWriter(formatType);
    }
    
    @Override
    public boolean isAvailable() {
        return true; // GeoTools是纯Java实现,始终可用
    }
}

GDAL引擎实现

/**
 * GDAL引擎实现
 */
public class GdalEngine implements GisEngine {
    
    private static final Set<DataFormatType> SUPPORTED_FORMATS = EnumSet.of(
            DataFormatType.SHP, DataFormatType.GEOJSON,
            DataFormatType.FILEGDB, DataFormatType.POSTGIS);
    
    /**
     * 可用性取决于GDAL本地库(gdalalljni)是否加载成功
     * @see OgrUtil#getOgrInitSuccess()
     */
    @Override
    public boolean isAvailable() {
        return Boolean.TRUE.equals(OgrUtil.getOgrInitSuccess());
    }
    
    @Override
    public boolean supports(DataFormatType formatType) {
        return isAvailable() && SUPPORTED_FORMATS.contains(formatType);
    }
    
    @Override
    public LayerReader getReader(DataFormatType formatType) throws OguException {
        if (!isAvailable()) {
            throw new EngineNotSupportedException("GDAL engine is not available.");
        }
        return new GdalLayerReader(formatType);
    }
    
    // getName()/getWriter()/readLayer()/writeLayer() 其余实现省略
}

3.2.3 model - 数据模型层

数据模型是OGU4J的核心抽象,位于engine.model包中。

模型类关系图

┌─────────────────────────────────────────────────────────────────┐
│                          OguLayer                               │
│  - name: String                                                 │
│  - wkid: Integer                                                │
│  - geometryType: GeometryType                                   │
│  - fields: List<OguField>                                       │
│  - features: List<OguFeature>                                   │
│  - metadata: OguLayerMetadata                                   │
├─────────────────────────────────────────────────────────────────┤
│  + validate(): void                                             │
│  + filter(OguFeatureFilter): List<OguFeature>                   │
│  + getFeatureCount(): int                                       │
│  + toJSON(): String                                             │
│  + fromJSON(String): OguLayer                                   │
└─────────────────────────────────────────────────────────────────┘
                              │
                              │ 1:n
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                         OguFeature                              │
│  - id: String                                                   │
│  - geometry: String (WKT)                                       │
│  - attributes: List<OguFieldValue>                              │
│  - coordinates: List<OguCoordinate>                             │
│  - rawValues: List<String>                                      │
├─────────────────────────────────────────────────────────────────┤
│  + getValue(String): Object                                     │
│  + setValue(String, Object): boolean                            │
│  + getAttribute(String): OguFieldValue                          │
└─────────────────────────────────────────────────────────────────┘
                              │
                              │ 1:n
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                        OguFieldValue                            │
│  - field: OguField                                              │
│  - value: Object                                                │
├─────────────────────────────────────────────────────────────────┤
│  + getFieldName(): String                                       │
│  + getStringValue(): String                                     │
│  + getIntValue(): Integer                                       │
│  + getDoubleValue(): Double                                     │
└─────────────────────────────────────────────────────────────────┘

为什么使用WKT存储几何

OGU4J选择使用WKT(Well-Known Text)格式存储几何数据,这是一个深思熟虑的设计决策:

方案 优点 缺点
JTS Geometry对象 直接使用,无需转换 与JTS强耦合
二进制WKB 存储效率高 不便调试
WKT字符串 跨库兼容、易读、易于序列化 存储稍大

WKT作为国际标准格式,可以轻松与任何GIS库交互。

3.2.4 geometry - 几何处理层

GeometryUtil是几何处理的核心类,提供两套API:

/**
 * 几何处理工具类
 * 基于JTS和ESRI Geometry API
 */
public class GeometryUtil {
    
    // ========== 基于JTS Geometry对象的方法 ==========
    
    // 格式转换
    public static Geometry wkt2Geometry(String wkt);
    public static String geometry2Wkt(Geometry geom);
    
    // 空间关系
    public static boolean intersects(Geometry a, Geometry b);
    public static boolean contains(Geometry a, Geometry b);
    
    // 空间分析
    public static Geometry buffer(Geometry geom, double distance);
    public static Geometry intersection(Geometry a, Geometry b);
    
    // ========== 基于WKT字符串的方法(使用ESRI API) ==========
    
    // 空间关系
    public static boolean intersectsWkt(String wktA, String wktB, int wkid);
    public static boolean containsWkt(String wktA, String wktB, int wkid);
    
    // 空间分析
    public static String bufferWkt(String wkt, int wkid, double distance);
    public static String intersectionWkt(String wktA, String wktB, int wkid);
}

两套API的设计理由:

  1. JTS方法:适合需要多次操作几何对象的场景
  2. WKT方法:适合简单场景,避免对象转换开销

3.2.5 exception - 异常处理层

OGU4J设计了层次化的异常体系:

/**
 * OGU4J基础异常(受检异常,读写API均声明throws OguException)
 */
public class OguException extends Exception {
    public OguException(String message) {
        super(message);
    }
    
    public OguException(String message, Throwable cause) {
        super(message, cause);
    }
    
    public OguException(Throwable cause) {
        super(cause);
    }
}

/**
 * 数据源异常 - 数据读写相关错误
 */
public class DataSourceException extends OguException {
    // 文件不存在、连接失败等
}

/**
 * 格式解析异常 - 数据格式相关错误
 */
public class FormatParseException extends OguException {
    // WKT解析错误、JSON格式错误等
}

/**
 * 引擎不支持异常 - 引擎功能限制
 */
public class EngineNotSupportedException extends OguException {
    // GDAL未安装、格式不支持等
}

/**
 * 拓扑异常 - 几何拓扑错误
 */
public class TopologyException extends OguException {
    // 自相交、环未闭合等
}

/**
 * 图层验证异常 - 数据完整性错误
 * 注意:直接继承RuntimeException,不是OguException的子类
 */
public class LayerValidationException extends RuntimeException {
    // 缺少名称、几何类型、坐标系等
}

3.2.6 utils - 通用工具层

提供与GIS无关的通用工具:

// ZIP工具
public class ZipUtil {
    public static void zip(File folder, String zipPath);
    public static void zip(File folder, String zipPath, Charset charset);
    public static void unzip(String zipPath, String destPath);
    public static void unzip(String zipPath, String destPath, Charset charset);
}

// 编码检测工具
public class EncodingUtil {
    public static Charset getFileEncoding(File file);
}

// 自然排序工具
public class SortUtil {
    public static int compareString(String s1, String s2);
}

// 数字工具
public class NumUtil {
    public static String getPlainString(Double value);
}

3.3 设计模式应用

3.3.1 工厂模式

GisEngineFactory使用工厂模式创建引擎实例:

// 工厂模式 - 根据类型创建对应的引擎
GisEngine engine = GisEngineFactory.getEngine(GisEngineType.GEOTOOLS);

优点:

  • 封装对象创建逻辑
  • 支持引擎缓存复用
  • 便于扩展新引擎

3.3.2 策略模式

引擎切换使用策略模式:

// 策略模式 - 不同策略(引擎)可互换
OguLayer layer = OguLayerUtil.readLayer(
    DataFormatType.SHP,
    path,
    null, null, null,
    GisEngineType.GEOTOOLS  // 策略1
    // GisEngineType.GDAL   // 策略2
);

优点:

  • 算法(引擎实现)与使用分离
  • 运行时动态切换
  • 符合开闭原则

3.3.3 模板方法模式

Reader和Writer使用模板方法:

// LayerReader接口定义了读取模板
public interface LayerReader {
    OguLayer read(DataFormatType format, String path, 
                  String layerName, String attrFilter, 
                  String spatialFilter);
}

// 各实现类提供具体实现
public class GeoToolsLayerReader implements LayerReader {
    @Override
    public OguLayer read(...) {
        // GeoTools特定实现
    }
}

3.3.4 建造者模式

OguLayer支持链式构建:

// 建造者模式示例
OguLayer layer = new OguLayer();
layer.setName("示例图层")
     .setWkid(4490)
     .setGeometryType(GeometryType.POLYGON);

3.4 扩展点设计

3.4.1 添加新引擎

要添加新的GIS引擎,需要:

  1. 实现GisEngine接口
  2. 实现LayerReader和LayerWriter
  3. 在GisEngineFactory中注册
// 1. 实现引擎接口
public class NewEngine implements GisEngine {
    @Override
    public LayerReader getReader() { ... }
    
    @Override
    public LayerWriter getWriter() { ... }
    
    @Override
    public GisEngineType getEngineType() { 
        return GisEngineType.NEW_ENGINE; 
    }
}

// 2. 在工厂中注册
case NEW_ENGINE:
    return new NewEngine();

3.4.2 添加新数据格式

要支持新的数据格式:

  1. 在DataFormatType中添加枚举值
  2. 在对应引擎的Reader/Writer中实现处理逻辑
// 1. 添加格式枚举
public enum DataFormatType {
    SHP, GEOJSON, FILEGDB, POSTGIS,
    NEW_FORMAT  // 新格式
}

// 2. 在Reader中处理
case NEW_FORMAT:
    return readNewFormat(path, layerName);

3.4.3 添加新几何操作

在GeometryUtil中添加新方法:

/**
 * 新增几何操作
 */
public static Geometry newOperation(Geometry geom, Object params) {
    // 使用JTS或ESRI API实现
}

/**
 * WKT版本
 */
public static String newOperationWkt(String wkt, Object params) {
    // WKT版本实现
}

3.5 线程安全性

3.5.1 线程安全的类

  • GeometryUtil:静态方法基本无状态;注意validate(Geometry)对合法几何会执行normalize()并返回同一实例(存在副作用),多线程共享同一几何对象时需自行规避
  • CrsUtil:静态CRS缓存使用ConcurrentHashMap(并配合computeIfAbsent),线程安全
  • GisEngineFactory:GeoTools/GDAL引擎各持有一个static final单例实例,无缓存Map,线程安全

3.5.2 非线程安全的类

  • OguLayer:数据容器,不保证线程安全
  • OguFeature:数据容器,不保证线程安全
  • GeoToolsLayerReader/Writer:非线程安全,建议每次操作创建新实例

3.5.3 并发使用建议

// 推荐:在多线程环境中为每个任务创建独立的Layer
ExecutorService executor = Executors.newFixedThreadPool(4);

for (String shpFile : shpFiles) {
    executor.submit(() -> {
        // 每个线程读取自己的图层
        OguLayer layer = OguLayerUtil.readLayer(
            DataFormatType.SHP, shpFile, 
            null, null, null, 
            GisEngineType.GEOTOOLS
        );
        // 处理...
    });
}

3.6 性能考量

3.6.1 引擎选择

场景 推荐引擎 理由
一般用途 GeoTools 稳定、纯Java
大量数据 GDAL 性能更好
FileGDB GDAL GeoTools不支持
跨平台部署 GeoTools 无需本地库

3.6.2 内存优化

  • 大文件处理:使用流式读取,避免一次性加载
  • 几何缓存:避免重复创建Geometry对象
  • 及时释放:处理完成后置空引用

3.6.3 IO优化

  • 批量写入:使用事务批量提交
  • 合理过滤:在读取时使用过滤条件减少数据量
  • 连接池:PostGIS操作时使用连接池