第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;
}
设计思想:
- 统一入口:所有格式读写都通过同一方法
- 参数标准化:使用枚举确保类型安全
- 引擎透明:开发者无需了解底层引擎细节
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的设计理由:
- JTS方法:适合需要多次操作几何对象的场景
- 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引擎,需要:
- 实现
GisEngine接口 - 实现
LayerReader和LayerWriter - 在
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 添加新数据格式
要支持新的数据格式:
- 在
DataFormatType中添加枚举值 - 在对应引擎的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操作时使用连接池