第04章 - 统一图层模型详解

4.1 模型设计理念

4.1.1 为什么需要统一模型

在GIS开发中,不同的库使用不同的数据模型:

库 要素类 要素 几何
GeoTools SimpleFeatureType SimpleFeature Geometry
GDAL/OGR Layer Feature Geometry
ESRI FeatureClass Feature Geometry

这种差异带来的问题:

  1. 代码不可移植:为GeoTools写的代码不能用于GDAL
  2. 学习成本高:每个库都有自己的概念体系
  3. 维护困难:需要熟悉多套API

OGU4J的统一模型解决了这些问题,提供了与底层库无关的数据抽象。

4.1.2 模型设计原则

  1. 简洁性:只保留必要的属性和方法
  2. 通用性:适用于所有GIS数据格式
  3. 可序列化:支持JSON序列化/反序列化
  4. 可扩展性:支持元数据扩展

4.1.3 模型类图

┌─────────────────────────────────────────────────────────────────────┐
│                              OguLayer                                │
│                          (统一图层定义)                              │
├─────────────────────────────────────────────────────────────────────┤
│  - name: String              图层名称                                │
│  - wkid: Integer             坐标系WKID                              │
│  - geometryType: GeometryType 几何类型                               │
│  - fields: List<OguField>    字段列表                                │
│  - features: List<OguFeature> 要素列表                               │
│  - metadata: OguLayerMetadata 元数据                                 │
├─────────────────────────────────────────────────────────────────────┤
│  + validate()                 验证图层完整性                          │
│  + filter(OguFeatureFilter)   过滤要素                               │
│  + getFeatureCount()          获取要素数量                            │
│  + toJSON() / fromJSON()      JSON序列化                             │
└─────────────────────────────────────────────────────────────────────┘
         │                           │                      │
         │ 1:n                       │ 1:n                  │ 1:1
         ▼                           ▼                      ▼
┌─────────────────┐    ┌─────────────────────┐    ┌─────────────────────┐
│    OguField     │    │    OguFeature       │    │  OguLayerMetadata   │
│  (字段定义)     │    │    (要素类)         │    │   (图层元数据)      │
├─────────────────┤    ├─────────────────────┤    ├─────────────────────┤
│ - name          │    │ - id                │    │ - dataSource        │
│ - alias         │    │ - geometry          │    │ - coordinateSystem  │
│ - dataType      │    │ - attributes: List  │    │ - zoneNumber        │
│ - length        │    │   <OguFieldValue>   │    │ - projectionType    │
│ - description   │    │ - coordinates       │    │ - measureUnit       │
│ - nullable      │    │ - rawValues         │    │ - extendedInfos     │
├─────────────────┤    ├─────────────────────┤    └─────────────────────┘
│ + 标准getter    │    │ + getValue()        │
│ + 标准setter    │    │ + setValue()        │
└─────────────────┘    │ + getAttribute()    │
                       └─────────────────────┘
                                │
                                │ 1:n
                                ▼
                       ┌─────────────────────┐
                       │   OguFieldValue     │
                       │   (字段值容器)      │
                       ├─────────────────────┤
                       │ - field: OguField   │
                       │ - value: Object     │
                       ├─────────────────────┤
                       │ + getStringValue()  │
                       │ + getIntValue()     │
                       │ + getDoubleValue()  │
                       │ + getFieldName()    │
                       └─────────────────────┘

4.2 OguLayer - 图层类

4.2.1 类定义

/**
 * 统一的GIS图层定义
 * 代表一个完整的GIS图层,包含图层名称、坐标系、几何类型、字段定义和要素集合
 * 
 * 设计理念:
 * - 独立于任何GIS库的数据表示
 * - 支持JSON序列化,便于传输和存储
 * - 提供便捷的操作方法
 */
@Data
public class OguLayer {
    
    /**
     * 图层名称
     */
    private String name;
    
    /**
     * 坐标系WKID(Well-Known ID)
     * 例如:4490 = CGCS2000地理坐标系
     *       4527 = CGCS2000 / 3-degree Gauss-Kruger zone 39
     */
    private Integer wkid;
    
    /**
     * 几何类型
     */
    private GeometryType geometryType;
    
    /**
     * 字段定义列表
     */
    private List<OguField> fields;
    
    /**
     * 要素列表
     */
    private List<OguFeature> features;
    
    /**
     * 图层元数据(可选)
     */
    private OguLayerMetadata metadata;
    
    // ... 方法定义
}

4.2.2 创建图层

方式1:代码构建

// 创建图层
OguLayer layer = new OguLayer();
layer.setName("行政区划");
layer.setWkid(4490);
layer.setGeometryType(GeometryType.POLYGON);

// 定义字段
List<OguField> fields = new ArrayList<>();

OguField codeField = new OguField();
codeField.setName("CODE");
codeField.setAlias("行政区划代码");
codeField.setDataType(FieldDataType.STRING);
codeField.setLength(12);

OguField nameField = new OguField();
nameField.setName("NAME");
nameField.setAlias("名称");
nameField.setDataType(FieldDataType.STRING);
nameField.setLength(100);

fields.add(codeField);
fields.add(nameField);
layer.setFields(fields);

// 添加要素
List<OguFeature> features = new ArrayList<>();
OguFeature feature = new OguFeature();
feature.setId("1");
feature.setGeometry("POLYGON((116 39, 116 40, 117 40, 117 39, 116 39))");
// 属性值以List<OguFieldValue>承载,setValue只会更新已存在的字段,不会新增
feature.setAttributes(new ArrayList<>(List.of(
    new OguFieldValue(codeField, "110000"),
    new OguFieldValue(nameField, "北京市"))));
features.add(feature);
layer.setFeatures(features);

方式2:从JSON创建

String json = "{" +
    "\"name\":\"行政区划\"," +
    "\"wkid\":4490," +
    "\"geometryType\":\"POLYGON\"," +
    "\"fields\":[...]," +
    "\"features\":[...]" +
    "}";

OguLayer layer = OguLayer.fromJSON(json);

方式3:从数据源读取

OguLayer layer = OguLayerUtil.readLayer(
    DataFormatType.SHP,
    "D:/data/districts.shp",
    null, null, null,
    GisEngineType.GEOTOOLS
);

4.2.3 图层验证

/**
 * 验证图层数据完整性
 * 检查项包括:
 * - 图层名称不能为空
 * - 几何类型必须设置
 * - 字段列表不能为null
 * - 要素的几何类型必须与图层一致
 */
layer.validate();

// 可能抛出的异常
try {
    layer.validate();
} catch (LayerValidationException e) {
    System.out.println("验证失败: " + e.getMessage());
}

4.2.4 要素过滤

// 使用Lambda表达式过滤要素
List<OguFeature> beijingFeatures = layer.filter(
    feature -> "北京市".equals(feature.getValue("NAME"))
);

// 复杂过滤条件
List<OguFeature> largeAreas = layer.filter(feature -> {
    Double area = feature.getAttribute("AREA").getDoubleValue();
    return area != null && area > 10000;
});

// 组合过滤
List<OguFeature> result = layer.filter(feature -> {
    String name = feature.getAttribute("NAME").getStringValue();
    Integer level = feature.getAttribute("LEVEL").getIntValue();
    return name != null && name.contains("市") && level != null && level == 1;
});

4.2.5 JSON序列化

// 转换为JSON
String json = layer.toJSON();
System.out.println(json);

// 从JSON恢复
OguLayer restored = OguLayer.fromJSON(json);

// 格式化输出(便于阅读)
String prettyJson = JSON.toJSONString(layer, 
    JSONWriter.Feature.PrettyFormat);

4.3 OguField - 字段类

4.3.1 类定义

/**
 * 统一的字段定义类
 * 描述图层中的一个属性字段
 */
@Data
public class OguField {
    
    /**
     * 字段名称(必填)
     * 建议使用英文,符合数据库命名规范
     */
    private String name;
    
    /**
     * 字段别名(可选)
     * 用于显示的中文名称
     */
    private String alias;
    
    /**
     * 数据类型(必填)
     */
    private FieldDataType dataType;
    
    /**
     * 字段长度(字符串类型必填)
     */
    private Integer length;
    
    /**
     * 字段描述(可选)
     */
    private String description;
    
    /**
     * 是否允许空值
     */
    private Boolean nullable;
    
    /**
     * 默认值(可选)
     */
    private Object defaultValue;
}

4.3.2 字段数据类型

/**
 * 字段数据类型枚举
 * 每个枚举携带GDAL字段编码、对应Java类型等信息
 */
public enum FieldDataType {
    INTEGER(new int[]{0}, Integer.class, 0);            // 整数
    DOUBLE(new int[]{2}, Double.class, 2);              // 浮点数
    STRING(new int[]{1, 3, 4, 6, 5, 7, 13}, String.class, 4);  // 字符串
    BINARY(new int[]{8}, byte[].class, 8);              // 二进制
    DATE(new int[]{9}, LocalDate.class, 9);             // 日期
    TIME(new int[]{10}, LocalTime.class, 10);           // 时间
    DATETIME(new int[]{11}, LocalDateTime.class, 11);   // 日期时间
    LONG(new int[]{12}, Long.class, 12);                // 长整数
    
    private final int[] gdalCodes;
    private final Class<?> typeClass;
    private final int defaultGdalCode;
    
    // 类型转换方法
    public static FieldDataType fieldDataTypeByGdalCode(int gdalCode) { ... }
    public static FieldDataType fieldDataTypeByTypeClass(Class<?> typeClass) { ... }
}

4.3.3 创建字段

// 字符串字段
OguField nameField = new OguField();
nameField.setName("NAME");
nameField.setAlias("名称");
nameField.setDataType(FieldDataType.STRING);
nameField.setLength(100);
nameField.setNullable(false);

// 数值字段
OguField areaField = new OguField();
areaField.setName("AREA");
areaField.setAlias("面积");
areaField.setDataType(FieldDataType.DOUBLE);
areaField.setDefaultValue(0.0);

// 日期字段
OguField dateField = new OguField();
dateField.setName("CREATE_TIME");
dateField.setAlias("创建时间");
dateField.setDataType(FieldDataType.DATETIME);

// 布尔语义字段(FieldDataType无BOOLEAN,用INTEGER存0/1)
OguField activeField = new OguField();
activeField.setName("IS_ACTIVE");
activeField.setAlias("是否有效");
activeField.setDataType(FieldDataType.INTEGER);
activeField.setDefaultValue(1);

4.4 OguFeature - 要素类

4.4.1 类定义

/**
 * 统一的要素类
 * 代表图层中的一个地理要素,包含ID、几何信息和属性值
 */
@Data
public class OguFeature {
    
    /**
     * 要素ID
     * 注意:各引擎对FID/OID的定义不同,此处如实返回,不做兼容,
     * 不建议当作OBJECTID使用,推荐用属性过滤和查找
     */
    private String id;
    
    /**
     * 要素几何(WKT格式)
     * 例如:"POLYGON((116 39, 116 40, 117 40, 117 39, 116 39))"
     */
    private String geometry;
    
    /**
     * 属性值集合
     * 每个OguFieldValue包含字段定义(field)与具体值(value)
     */
    private List<OguFieldValue> attributes;
    
    /**
     * 坐标列表(可选,用于国土TXT格式)
     */
    private List<OguCoordinate> coordinates;
    
    /**
     * 原始属性值列表(可选,用于国土TXT格式)
     */
    private List<String> rawValues;
    
    // ... 方法定义
}

4.4.2 属性操作

获取属性值

OguFeature feature = layer.getFeatures().get(0);

// 方式1:直接获取原始值
Object value = feature.getValue("NAME");
String name = (String) value;

// 方式2:通过OguFieldValue获取(推荐)
OguFieldValue fieldValue = feature.getAttribute("NAME");
String name = fieldValue.getStringValue();
Integer code = feature.getAttribute("CODE").getIntValue();
Double area = feature.getAttribute("AREA").getDoubleValue();

// 安全获取(处理null值)
String safeName = Optional.ofNullable(feature.getAttribute("NAME"))
    .map(OguFieldValue::getStringValue)
    .orElse("未知");

设置属性值

// 设置单个属性
feature.setValue("NAME", "北京市");
feature.setValue("AREA", 16410.54);
feature.setValue("POPULATION", 21540000);

// 批量设置(setValue返回boolean,字段不存在时返回false,不会新增字段)
Map<String, Object> values = new HashMap<>();
values.put("NAME", "上海市");
values.put("AREA", 6340.5);
values.put("POPULATION", 24280000);
values.forEach(feature::setValue);

4.4.3 几何操作

// 获取WKT
String wkt = feature.getGeometry();
System.out.println(wkt);
// 输出: POLYGON((116 39, 116 40, 117 40, 117 39, 116 39))

// 设置WKT
feature.setGeometry("POLYGON((120 30, 120 31, 121 31, 121 30, 120 30))");

// 转换为JTS Geometry进行操作
Geometry geom = GeometryUtil.wkt2Geometry(feature.getGeometry());
double area = geom.getArea();
Geometry centroid = geom.getCentroid();

// 更新几何
Geometry buffered = geom.buffer(0.01);
feature.setGeometry(GeometryUtil.geometry2Wkt(buffered));

4.5 OguFieldValue - 字段值容器

4.5.1 类定义

/**
 * 字段值容器
 * 封装字段定义与原始值,提供类型安全的值获取方法
 * 
 * 设计理念:
 * - 统一处理null值
 * - 提供类型转换
 * - 避免ClassCastException
 */
@Data
@AllArgsConstructor
public class OguFieldValue implements Serializable {
    
    /**
     * 字段定义
     */
    private OguField field;
    
    /**
     * 字段值
     */
    private Object value;
    
    public OguFieldValue() {}
    
    /**
     * 获取字段名称(field为null时返回null)
     */
    public String getFieldName() {
        return field == null ? null : field.getName();
    }
    
    /**
     * 获取字符串值
     */
    public String getStringValue() {
        return value == null ? null : String.valueOf(value);
    }
    
    /**
     * 获取整数值(解析失败返回null)
     */
    public Integer getIntValue() {
        if (value == null) return null;
        if (value instanceof Integer) return (Integer) value;
        try {
            return Integer.parseInt(String.valueOf(value));
        } catch (NumberFormatException e) {
            return null;
        }
    }
    
    /**
     * 获取双精度浮点值(解析失败返回null)
     */
    public Double getDoubleValue() {
        if (value == null) return null;
        if (value instanceof Double) return (Double) value;
        try {
            return Double.parseDouble(String.valueOf(value));
        } catch (NumberFormatException e) {
            return null;
        }
    }
}

注意:OguFieldValue仅提供getStringValue/getIntValue/getDoubleValue三个类型转换方法,没有getLongValue/getBooleanValue/getDateValue;需要Long/Boolean/日期时请直接使用getValue()自行转换(值类型由对应FieldDataType决定,日期为LocalDate/LocalTime/LocalDateTime)。

4.5.2 使用示例

OguFeature feature = layer.getFeatures().get(0);

// 安全地获取各类型值
String name = feature.getAttribute("NAME").getStringValue();
Integer level = feature.getAttribute("LEVEL").getIntValue();
Double area = feature.getAttribute("AREA").getDoubleValue();
// 长整数/布尔/日期等类型需自行从getValue()转换
Long population = ((Number) feature.getAttribute("POPULATION").getValue()).longValue();

// 处理可能为null的情况
OguFieldValue areaValue = feature.getAttribute("AREA");
if (areaValue != null && areaValue.getDoubleValue() != null) {
    double area = areaValue.getDoubleValue();
    System.out.println("面积: " + area);
}

// 类型转换
OguFieldValue codeValue = feature.getAttribute("CODE");
// 原始数据可能是整数或字符串
String codeStr = codeValue.getStringValue();  // 转为字符串
Integer codeInt = codeValue.getIntValue();    // 转为整数

4.6 OguCoordinate - 坐标类

4.6.1 类定义

/**
 * 坐标类
 * 支持二维/三维坐标,以及国土TXT格式的点号和圈号
 */
@Data
public class OguCoordinate {
    
    /**
     * X坐标(经度或东坐标)
     */
    private Double x;
    
    /**
     * Y坐标(纬度或北坐标)
     */
    private Double y;
    
    /**
     * Z坐标(高程,可选)
     */
    private Double z;
    
    /**
     * 点号(用于国土TXT格式)
     */
    private String pointNumber;
    
    /**
     * 圈号(用于国土TXT格式,区分内外环)
     */
    private Integer ringNumber;
    
    // 构造函数
    public OguCoordinate() {}
    
    public OguCoordinate(Double x, Double y) {
        this.x = x;
        this.y = y;
    }
    
    public OguCoordinate(Double x, Double y, Double z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }
}

4.6.2 使用场景

主要用于国土TXT格式的界址点管理:

// 创建带点号的坐标
OguCoordinate coord = new OguCoordinate();
coord.setX(500000.123);
coord.setY(4000000.456);
coord.setPointNumber("J1");
coord.setRingNumber(1);  // 外环

// 批量创建坐标点
List<OguCoordinate> coords = new ArrayList<>();
coords.add(new OguCoordinate(500000.0, 4000000.0) {{ setPointNumber("J1"); setRingNumber(1); }});
coords.add(new OguCoordinate(500100.0, 4000000.0) {{ setPointNumber("J2"); setRingNumber(1); }});
coords.add(new OguCoordinate(500100.0, 4000100.0) {{ setPointNumber("J3"); setRingNumber(1); }});
coords.add(new OguCoordinate(500000.0, 4000100.0) {{ setPointNumber("J4"); setRingNumber(1); }});
coords.add(new OguCoordinate(500000.0, 4000000.0) {{ setPointNumber("J1"); setRingNumber(1); }});

feature.setCoordinates(coords);

4.7 OguLayerMetadata - 图层元数据

4.7.1 类定义

/**
 * 图层元数据
 * 存储图层的扩展信息,主要用于国土TXT格式
 */
@Data
public class OguLayerMetadata {
    
    /**
     * 数据来源
     * 例如:"自然资源部"
     */
    private String dataSource;
    
    /**
     * 坐标系名称
     * 例如:"2000国家大地坐标系"
     */
    private String coordinateSystemName;
    
    /**
     * 分带方式
     * 例如:"3" 表示3度分带
     */
    private String zoneDivision;
    
    /**
     * 投影类型
     * 例如:"高斯克吕格"
     */
    private String projectionType;
    
    /**
     * 计量单位
     * 例如:"米"
     */
    private String measureUnit;
    
    /**
     * 格式版本 / 数据日期 / 带号 / 精度 / 转换参数(可选)
     */
    private String formatVersion;
    private String dataDate;
    private String zoneNumber;
    private String precision;
    private String transformParams;
    
    /**
     * 扩展信息列表(每项包含名称与properties)
     */
    private List<ExtendedInfo> extendedInfos;
    
    @Data
    public static class ExtendedInfo implements Serializable {
        private String name;
        private LinkedHashMap<String, String> properties;
    }
}

4.7.2 使用示例

// 创建元数据
OguLayerMetadata metadata = new OguLayerMetadata();
metadata.setDataSource("自然资源部");
metadata.setCoordinateSystemName("2000国家大地坐标系");
metadata.setZoneDivision("3");
metadata.setProjectionType("高斯克吕格");
metadata.setMeasureUnit("米");

// 添加扩展信息(List<ExtendedInfo>,每项含名称与properties)
OguLayerMetadata.ExtendedInfo extended = new OguLayerMetadata.ExtendedInfo();
extended.setName("说明");
LinkedHashMap<String, String> extInfo = new LinkedHashMap<>();
extInfo.put("version", "1.0");
extInfo.put("createDate", "2024-01-01");
extInfo.put("author", "张三");
extended.setProperties(extInfo);
metadata.setExtendedInfos(List.of(extended));

// 设置到图层
layer.setMetadata(metadata);

// 保存为国土TXT格式时使用
GtTxtUtil.saveTxt(layer, txtPath, metadata, null, 39);

4.8 OguFeatureFilter - 要素过滤器

4.8.1 接口定义

/**
 * 要素过滤器(函数式接口)
 * 用于过滤图层中的要素
 */
@FunctionalInterface
public interface OguFeatureFilter {
    
    /**
     * 测试要素是否满足条件
     * @param feature 待测试的要素
     * @return true表示保留,false表示过滤掉
     */
    boolean apply(OguFeature feature);
}

4.8.2 使用示例

// 使用Lambda表达式
List<OguFeature> result = layer.filter(f -> 
    f.getValue("NAME").toString().contains("市"));

// 使用方法引用
List<OguFeature> result = layer.filter(this::isValid);

private boolean isValid(OguFeature feature) {
    String wkt = feature.getGeometry();
    if (wkt == null) return false;
    
    TopologyValidationResult validResult = 
        GeometryUtil.isValid(GeometryUtil.wkt2Geometry(wkt));
    return validResult.isValid();
}

// 组合过滤器
OguFeatureFilter filter1 = f -> f.getAttribute("LEVEL").getIntValue() == 1;
OguFeatureFilter filter2 = f -> f.getAttribute("AREA").getDoubleValue() > 1000;

List<OguFeature> result = layer.filter(f -> filter1.apply(f) && filter2.apply(f));

4.9 GeometryType - 几何类型枚举

4.9.1 枚举定义

/**
 * 几何类型枚举
 * 定义所有支持的几何类型(typeCode为库内编码,wkbGeometryType为WKB标准编码)
 */
public enum GeometryType {
    
    POINT(0, "Point", Point.class, 1),
    MULTIPOINT(1, "MultiPoint", MultiPoint.class, 4),
    LINESTRING(2, "LineString", LineString.class, 2),
    LINEARRING(3, "LinearRing", LinearRing.class, 101),
    MULTILINESTRING(4, "MultiLineString", MultiLineString.class, 5),
    POLYGON(5, "Polygon", Polygon.class, 3),
    MULTIPOLYGON(6, "MultiPolygon", MultiPolygon.class, 6),
    GEOMETRYCOLLECTION(7, "GeometryCollection", GeometryCollection.class, 7);
    
    private final int typeCode;
    private final String typeName;
    private final Class<?> typeClass;
    private final int wkbGeometryType;
    
    /**
     * 按类型名称获取枚举(忽略大小写)
     */
    public static GeometryType valueOfByTypeName(String typeName);
    
    /**
     * 按库内编码获取枚举
     */
    public static GeometryType valueOfByTypeCode(int typeCode);
    
    /**
     * 按JTS Geometry绑定类获取枚举
     */
    public static GeometryType valueOfByTypeClass(Class<?> typeClass);
    
    /**
     * 按WKB几何类型编码获取枚举
     */
    public static GeometryType valueOfByWkbGeometryType(int wkbGeometryType);
}

4.9.2 使用示例

// 设置图层几何类型
layer.setGeometryType(GeometryType.POLYGON);

// 从类型名称判断
GeometryType type = GeometryType.valueOfByTypeName("Polygon");

// 从Geometry的绑定类判断
Geometry geom = GeometryUtil.wkt2Geometry(wkt);
GeometryType type = GeometryType.valueOfByTypeClass(geom.getClass());

// 类型判断
if (layer.getGeometryType() == GeometryType.POLYGON || 
    layer.getGeometryType() == GeometryType.MULTIPOLYGON) {
    // 处理面状数据
    processPolygonLayer(layer);
}

4.10 实战案例

4.10.1 创建完整的图层

public class LayerExample {
    
    public static OguLayer createCityLayer() {
        // 1. 创建图层
        OguLayer layer = new OguLayer();
        layer.setName("中国城市");
        layer.setWkid(4490);
        layer.setGeometryType(GeometryType.POINT);
        
        // 2. 定义字段
        List<OguField> fields = new ArrayList<>();
        fields.add(createField("CODE", "城市代码", FieldDataType.STRING, 6));
        fields.add(createField("NAME", "城市名称", FieldDataType.STRING, 50));
        fields.add(createField("PROVINCE", "所属省份", FieldDataType.STRING, 50));
        fields.add(createField("POPULATION", "人口(万)", FieldDataType.INTEGER, null));
        fields.add(createField("GDP", "GDP(亿元)", FieldDataType.DOUBLE, null));
        fields.add(createField("LEVEL", "城市等级", FieldDataType.INTEGER, null));
        layer.setFields(fields);
        
        // 3. 添加要素
        List<OguFeature> features = new ArrayList<>();
        features.add(createCity(layer, "1", "110000", "北京", "北京", 2154, 40269.6, 1, 
            "POINT(116.407 39.904)"));
        features.add(createCity(layer, "2", "310000", "上海", "上海", 2428, 43214.9, 1, 
            "POINT(121.473 31.230)"));
        features.add(createCity(layer, "3", "440100", "广州", "广东", 1881, 28839.0, 2, 
            "POINT(113.264 23.129)"));
        features.add(createCity(layer, "4", "440300", "深圳", "广东", 1756, 32387.7, 2, 
            "POINT(114.057 22.543)"));
        layer.setFeatures(features);
        
        // 4. 验证
        layer.validate();
        
        return layer;
    }
    
    private static OguField createField(String name, String alias, 
            FieldDataType dataType, Integer length) {
        OguField field = new OguField();
        field.setName(name);
        field.setAlias(alias);
        field.setDataType(dataType);
        field.setLength(length);
        return field;
    }
    
    private static OguFeature createCity(OguLayer layer, String id, String code, String name,
            String province, int population, double gdp, int level, String wkt) {
        Map<String, Object> values = new LinkedHashMap<>();
        values.put("CODE", code);
        values.put("NAME", name);
        values.put("PROVINCE", province);
        values.put("POPULATION", population);
        values.put("GDP", gdp);
        values.put("LEVEL", level);
        
        OguFeature feature = new OguFeature();
        feature.setId(id);
        feature.setGeometry(wkt);
        // setValue不会新增属性,构造要素时需按fields装配OguFieldValue
        List<OguFieldValue> attributes = new ArrayList<>();
        for (OguField field : layer.getFields()) {
            attributes.add(new OguFieldValue(field, values.get(field.getName())));
        }
        feature.setAttributes(attributes);
        return feature;
    }
}

4.10.2 图层数据分析

public class LayerAnalysis {
    
    public static void analyze(OguLayer layer) {
        System.out.println("=== 图层分析报告 ===");
        System.out.println("图层名称: " + layer.getName());
        System.out.println("坐标系: WKID " + layer.getWkid());
        System.out.println("几何类型: " + layer.getGeometryType());
        System.out.println("要素数量: " + layer.getFeatureCount());
        
        // 字段统计
        System.out.println("\n=== 字段信息 ===");
        for (OguField field : layer.getFields()) {
            System.out.printf("%s (%s) - %s%n",
                field.getName(),
                field.getDataType(),
                field.getAlias());
        }
        
        // 数值字段统计
        System.out.println("\n=== 数值统计 ===");
        for (OguField field : layer.getFields()) {
            if (field.getDataType() == FieldDataType.DOUBLE ||
                field.getDataType() == FieldDataType.INTEGER) {
                DoubleSummaryStatistics stats = layer.getFeatures().stream()
                    .map(f -> f.getAttribute(field.getName()))
                    .filter(v -> v != null && v.getDoubleValue() != null)
                    .mapToDouble(OguFieldValue::getDoubleValue)
                    .summaryStatistics();
                
                System.out.printf("%s: 最小=%.2f, 最大=%.2f, 平均=%.2f, 总和=%.2f%n",
                    field.getName(),
                    stats.getMin(),
                    stats.getMax(),
                    stats.getAverage(),
                    stats.getSum());
            }
        }
        
        // 几何范围
        System.out.println("\n=== 空间范围 ===");
        double minX = Double.MAX_VALUE, minY = Double.MAX_VALUE;
        double maxX = Double.MIN_VALUE, maxY = Double.MIN_VALUE;
        
        for (OguFeature feature : layer.getFeatures()) {
            if (feature.getGeometry() != null) {
                Geometry geom = GeometryUtil.wkt2Geometry(feature.getGeometry());
                Envelope env = geom.getEnvelopeInternal();
                minX = Math.min(minX, env.getMinX());
                minY = Math.min(minY, env.getMinY());
                maxX = Math.max(maxX, env.getMaxX());
                maxY = Math.max(maxY, env.getMaxY());
            }
        }
        
        System.out.printf("范围: (%.4f, %.4f) - (%.4f, %.4f)%n",
            minX, minY, maxX, maxY);
    }
}