第05章 - 要素模型与数据结构

第05章 - 要素模型与数据结构

5.1 要素模型概述

5.1.1 OGC Simple Feature 规范

GeoTools 的要素模型基于 OGC Simple Feature Access 规范,该规范定义了地理要素的标准表示方式。

┌─────────────────────────────────────────────────────────────┐
│                 Simple Feature 模型                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   Feature(要素)                                            │
│   ┌───────────────────────────────────────────────────────┐│
│   │  FeatureType(要素类型)                                ││
│   │  ├── Name(名称)                                      ││
│   │  ├── AttributeDescriptor(属性描述符)                  ││
│   │  │   ├── 属性名                                        ││
│   │  │   ├── 数据类型                                      ││
│   │  │   └── 约束                                          ││
│   │  └── GeometryDescriptor(几何描述符)                   ││
│   │      ├── 几何类型                                      ││
│   │      └── 坐标参考系                                    ││
│   │                                                        ││
│   │  Properties(属性值)                                   ││
│   │  ├── 属性1: 值1                                        ││
│   │  ├── 属性2: 值2                                        ││
│   │  └── geometry: Geometry                                ││
│   └───────────────────────────────────────────────────────┘│
│                                                             │
└─────────────────────────────────────────────────────────────┘

5.1.2 GeoTools 要素接口

/**
 * Feature 接口层次
 */
Feature (接口)
    │
    └── SimpleFeature (接口)
            │
            └── SimpleFeatureImpl (实现类)

/**
 * FeatureType 接口层次
 */
FeatureType (接口)
    │
    └── SimpleFeatureType (接口)
            │
            └── SimpleFeatureTypeImpl (实现类)

核心接口

// Feature 接口 - 单个要素
public interface Feature {
    FeatureType getType();              // 获取要素类型
    FeatureId getIdentifier();          // 获取要素ID
    Collection<Property> getProperties(); // 获取所有属性
    Property getProperty(String name);   // 获取指定属性
}

// SimpleFeature 接口 - 简单要素
public interface SimpleFeature extends Feature {
    SimpleFeatureType getFeatureType();        // 获取简单要素类型
    String getID();                            // 获取要素ID
    Object getAttribute(String name);          // 获取属性值
    Object getAttribute(int index);            // 按索引获取属性值
    void setAttribute(String name, Object value); // 设置属性值
    Object getDefaultGeometry();               // 获取默认几何
    void setDefaultGeometry(Object geometry);  // 设置默认几何
    int getAttributeCount();                   // 获取属性数量
}

5.2 SimpleFeatureType 详解

5.2.1 创建 FeatureType

使用 SimpleFeatureTypeBuilder 创建要素类型:

import org.geotools.api.feature.simple.SimpleFeatureType;
import org.geotools.api.referencing.crs.CoordinateReferenceSystem;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.referencing.CRS;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;

// 创建要素类型构建器
SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();

// 设置类型名称
builder.setName("City");

// 设置命名空间
builder.setNamespaceURI("http://example.com/gis");

// 设置坐标参考系
CoordinateReferenceSystem crs = CRS.decode("EPSG:4326");
builder.setCRS(crs);

// 添加属性
// 几何属性(第一个几何属性为默认几何)
builder.add("location", Point.class);

// 字符串属性
builder.add("name", String.class);

// 数值属性
builder.add("population", Long.class);
builder.add("area", Double.class);

// 日期属性
builder.add("founded", java.util.Date.class);

// 布尔属性
builder.add("capital", Boolean.class);

// 构建要素类型
SimpleFeatureType cityType = builder.buildFeatureType();

// 打印要素类型信息
System.out.println("类型名称: " + cityType.getTypeName());
System.out.println("属性数量: " + cityType.getAttributeCount());
System.out.println("几何属性: " + cityType.getGeometryDescriptor().getLocalName());
System.out.println("CRS: " + cityType.getCoordinateReferenceSystem().getName());

// 遍历所有属性描述符
for (AttributeDescriptor descriptor : cityType.getAttributeDescriptors()) {
    System.out.printf("  %s: %s%n",
        descriptor.getLocalName(),
        descriptor.getType().getBinding().getSimpleName());
}

5.2.2 属性约束

SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();
builder.setName("ConstrainedType");
builder.setCRS(CRS.decode("EPSG:4326"));

// 添加几何属性
builder.add("geometry", Polygon.class);

// 添加带长度约束的字符串
builder.length(100);  // 最大长度 100
builder.add("name", String.class);

// 添加非空约束
builder.nillable(false);  // 不允许空值
builder.add("code", String.class);

// 添加带范围约束的数值
builder.minOccurs(0);
builder.maxOccurs(1);
builder.add("count", Integer.class);

// 设置默认值
builder.defaultValue(0.0);
builder.add("score", Double.class);

SimpleFeatureType constrainedType = builder.buildFeatureType();

// 检查约束
AttributeDescriptor nameDesc = constrainedType.getDescriptor("name");
System.out.println("name 最大长度: " + 
    ((AttributeType) nameDesc.getType()).getRestrictions());

AttributeDescriptor codeDesc = constrainedType.getDescriptor("code");
System.out.println("code 是否可空: " + codeDesc.isNillable());

5.2.3 从现有类型创建新类型

// 原始类型
SimpleFeatureType originalType = ...;

// 复制并修改
SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();
builder.init(originalType);  // 从现有类型初始化

// 修改名称
builder.setName("NewType");

// 添加新属性
builder.add("newAttribute", String.class);

// 移除属性(先获取所有属性,排除要移除的)
builder.setAttributes(
    originalType.getAttributeDescriptors().stream()
        .filter(d -> !d.getLocalName().equals("oldAttribute"))
        .collect(Collectors.toList())
);

SimpleFeatureType newType = builder.buildFeatureType();

5.2.4 使用 DataUtilities 快速创建

import org.geotools.data.DataUtilities;

// 快速创建要素类型(使用规范字符串)
SimpleFeatureType quickType = DataUtilities.createType(
    "City",
    "location:Point:srid=4326," +
    "name:String," +
    "population:Long," +
    "area:Double," +
    "founded:Date," +
    "capital:Boolean"
);

// 类型规范语法:
// name:Type:options
// 常用类型:
//   Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon
//   String, Integer, Long, Double, Float, Boolean
//   Date, Timestamp
// 选项:
//   srid=4326 - 指定坐标系
//   nillable=false - 不允许空值

// 更复杂的例子
SimpleFeatureType complexType = DataUtilities.createType(
    "http://example.com",  // 命名空间
    "Building",             // 类型名
    "footprint:Polygon:srid=4326," +
    "name:String:nillable=false," +
    "height:Double," +
    "floors:Integer"
);

5.3 SimpleFeature 详解

5.3.1 创建 Feature

使用 SimpleFeatureBuilder 创建要素:

import org.geotools.api.feature.simple.SimpleFeature;
import org.geotools.feature.simple.SimpleFeatureBuilder;

// 获取要素类型
SimpleFeatureType cityType = DataUtilities.createType(
    "City",
    "location:Point:srid=4326,name:String,population:Long"
);

// 创建几何
GeometryFactory gf = JTSFactoryFinder.getGeometryFactory();
Point beijing = gf.createPoint(new Coordinate(116.4074, 39.9042));

// 方式1:按顺序设置属性
SimpleFeatureBuilder builder = new SimpleFeatureBuilder(cityType);
builder.add(beijing);
builder.add("北京");
builder.add(21540000L);
SimpleFeature feature1 = builder.buildFeature("city.1");

// 方式2:按名称设置属性
builder.reset();
builder.set("location", beijing);
builder.set("name", "北京");
builder.set("population", 21540000L);
SimpleFeature feature2 = builder.buildFeature("city.1");

// 方式3:使用数组
Object[] values = { beijing, "北京", 21540000L };
SimpleFeature feature3 = builder.buildFeature("city.1", values);

// 打印要素信息
System.out.println("要素ID: " + feature1.getID());
System.out.println("名称: " + feature1.getAttribute("name"));
System.out.println("人口: " + feature1.getAttribute("population"));
System.out.println("位置: " + feature1.getDefaultGeometry());

5.3.2 访问和修改属性

SimpleFeature feature = ...;

// 读取属性
// 按名称读取
String name = (String) feature.getAttribute("name");
Long population = (Long) feature.getAttribute("population");
Point location = (Point) feature.getDefaultGeometry();

// 按索引读取
Object attr0 = feature.getAttribute(0);  // 第一个属性

// 获取所有属性值
List<Object> attributes = feature.getAttributes();

// 获取属性数量
int count = feature.getAttributeCount();

// 修改属性
feature.setAttribute("name", "新名称");
feature.setAttribute("population", 22000000L);
feature.setDefaultGeometry(newGeometry);

// 批量设置属性
feature.setAttributes(new Object[] { newLocation, "新城市", 1000000L });

// 检查属性是否存在
boolean hasName = feature.getType().getDescriptor("name") != null;

5.3.3 Feature 属性遍历

SimpleFeature feature = ...;

// 遍历所有属性
System.out.println("要素属性列表:");
SimpleFeatureType type = feature.getFeatureType();

for (int i = 0; i < feature.getAttributeCount(); i++) {
    AttributeDescriptor descriptor = type.getDescriptor(i);
    String attrName = descriptor.getLocalName();
    Object attrValue = feature.getAttribute(i);
    Class<?> attrType = descriptor.getType().getBinding();
    
    System.out.printf("  %s (%s): %s%n",
        attrName,
        attrType.getSimpleName(),
        attrValue);
}

// 使用 Property 遍历
for (Property property : feature.getProperties()) {
    System.out.printf("  %s: %s%n",
        property.getName(),
        property.getValue());
}

// 只遍历非几何属性
for (AttributeDescriptor descriptor : type.getAttributeDescriptors()) {
    if (!(descriptor instanceof GeometryDescriptor)) {
        String name = descriptor.getLocalName();
        Object value = feature.getAttribute(name);
        System.out.printf("  %s: %s%n", name, value);
    }
}

5.4 FeatureCollection 详解

5.4.1 创建 FeatureCollection

import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.feature.DefaultFeatureCollection;

// 创建可写的要素集合
DefaultFeatureCollection collection = new DefaultFeatureCollection("cities", cityType);

// 添加要素
collection.add(feature1);
collection.add(feature2);
collection.add(feature3);

// 从数组创建
List<SimpleFeature> featureList = Arrays.asList(feature1, feature2, feature3);
DefaultFeatureCollection collection2 = new DefaultFeatureCollection();
collection2.addAll(featureList);

// 使用 DataUtilities 创建
SimpleFeatureCollection immutableCollection = DataUtilities.collection(featureList);

System.out.println("集合大小: " + collection.size());
System.out.println("边界: " + collection.getBounds());
System.out.println("Schema: " + collection.getSchema().getTypeName());

5.4.2 遍历 FeatureCollection

SimpleFeatureCollection collection = ...;

// 方式1:使用 try-with-resources(推荐)
try (SimpleFeatureIterator features = collection.features()) {
    while (features.hasNext()) {
        SimpleFeature feature = features.next();
        System.out.println(feature.getID() + ": " + feature.getAttribute("name"));
    }
}

// 方式2:使用 accepts 方法
collection.accepts(new FeatureVisitor() {
    @Override
    public void visit(Feature feature) {
        System.out.println(((SimpleFeature) feature).getAttribute("name"));
    }
}, null);

// 方式3:转换为 List(注意内存消耗)
List<SimpleFeature> featureList = DataUtilities.list(collection);
for (SimpleFeature feature : featureList) {
    System.out.println(feature.getAttribute("name"));
}

// 方式4:使用 Stream
DataUtilities.stream(collection).forEach(feature -> {
    System.out.println(feature.getAttribute("name"));
});

5.4.3 过滤和查询

import org.geotools.api.filter.Filter;
import org.geotools.api.filter.FilterFactory;
import org.geotools.factory.CommonFactoryFinder;

FilterFactory ff = CommonFactoryFinder.getFilterFactory();

// 创建过滤器
Filter filter = ff.greater(
    ff.property("population"),
    ff.literal(10000000)
);

// 应用过滤器获取子集合
SimpleFeatureCollection filtered = collection.subCollection(filter);
System.out.println("过滤后数量: " + filtered.size());

// 排序(如果支持)
SortBy[] sortBy = new SortBy[] {
    ff.sort("population", SortOrder.DESCENDING)
};
// 注意:DefaultFeatureCollection 不支持排序,需要数据源支持

5.5 属性类型详解

5.5.1 支持的属性类型

/**
 * GeoTools 支持的属性类型映射
 */
// 基本类型
String.class       // 字符串
Integer.class      // 整数
Long.class         // 长整数
Short.class        // 短整数
Byte.class         // 字节
Float.class        // 单精度浮点
Double.class       // 双精度浮点
Boolean.class      // 布尔值
Character.class    // 字符

// 日期时间类型
java.util.Date.class           // 日期
java.sql.Date.class            // SQL 日期
java.sql.Time.class            // SQL 时间
java.sql.Timestamp.class       // SQL 时间戳

// 几何类型
Point.class              // 点
LineString.class         // 线串
LinearRing.class         // 线环
Polygon.class            // 多边形
MultiPoint.class         // 多点
MultiLineString.class    // 多线串
MultiPolygon.class       // 多多边形
GeometryCollection.class // 几何集合
Geometry.class           // 通用几何

// 其他类型
byte[].class             // 二进制数据
BigDecimal.class         // 高精度数值
BigInteger.class         // 大整数

5.5.2 类型转换

import org.geotools.util.Converters;

// 字符串转数值
Integer intValue = Converters.convert("123", Integer.class);
Double doubleValue = Converters.convert("3.14", Double.class);

// 字符串转几何
Geometry geom = Converters.convert("POINT (116 39)", Geometry.class);

// 数值转字符串
String str = Converters.convert(123, String.class);

// 日期转换
Date date = Converters.convert("2024-01-15", Date.class);

// 自定义转换器
public class CustomConverter implements Converter {
    @Override
    public <T> T convert(Object source, Class<T> target) throws Exception {
        if (source instanceof String && target == MyClass.class) {
            return (T) MyClass.parse((String) source);
        }
        return null;
    }
}

5.6 FeatureSource 与 FeatureStore

5.6.1 FeatureSource(只读)

import org.geotools.api.data.SimpleFeatureSource;
import org.geotools.api.data.Query;

// 获取 FeatureSource
SimpleFeatureSource source = dataStore.getFeatureSource("layer_name");

// 获取要素类型
SimpleFeatureType schema = source.getSchema();

// 获取所有要素
SimpleFeatureCollection allFeatures = source.getFeatures();

// 获取边界
ReferencedEnvelope bounds = source.getBounds();

// 使用过滤器查询
Filter filter = ff.equals(ff.property("name"), ff.literal("Beijing"));
SimpleFeatureCollection filtered = source.getFeatures(filter);

// 使用 Query 查询
Query query = new Query(schema.getTypeName());
query.setFilter(filter);
query.setPropertyNames(new String[] { "name", "population" });  // 选择属性
query.setMaxFeatures(100);  // 限制数量
query.setStartIndex(0);     // 分页起始

SimpleFeatureCollection queried = source.getFeatures(query);

// 获取要素数量
int count = source.getCount(Query.ALL);

5.6.2 FeatureStore(可写)

import org.geotools.api.data.SimpleFeatureStore;
import org.geotools.api.data.Transaction;
import org.geotools.data.DefaultTransaction;

// 检查是否支持写入
SimpleFeatureSource source = dataStore.getFeatureSource("layer_name");
if (source instanceof SimpleFeatureStore) {
    SimpleFeatureStore store = (SimpleFeatureStore) source;
    
    // 创建事务
    Transaction transaction = new DefaultTransaction("edit");
    store.setTransaction(transaction);
    
    try {
        // 添加要素
        DefaultFeatureCollection newFeatures = new DefaultFeatureCollection();
        newFeatures.add(newFeature1);
        newFeatures.add(newFeature2);
        List<FeatureId> addedIds = store.addFeatures(newFeatures);
        System.out.println("添加了 " + addedIds.size() + " 个要素");
        
        // 修改要素
        Filter updateFilter = ff.equals(ff.property("name"), ff.literal("Old Name"));
        store.modifyFeatures("name", "New Name", updateFilter);
        
        // 批量修改多个属性
        store.modifyFeatures(
            new Name[] { 
                new NameImpl("name"), 
                new NameImpl("population") 
            },
            new Object[] { "Updated Name", 1000000L },
            updateFilter
        );
        
        // 删除要素
        Filter deleteFilter = ff.less(ff.property("population"), ff.literal(1000));
        store.removeFeatures(deleteFilter);
        
        // 提交事务
        transaction.commit();
        System.out.println("事务提交成功");
        
    } catch (Exception e) {
        // 回滚事务
        transaction.rollback();
        System.err.println("事务回滚: " + e.getMessage());
        
    } finally {
        transaction.close();
    }
}

5.7 要素事件监听

5.7.1 FeatureListener

import org.geotools.api.data.FeatureEvent;
import org.geotools.api.data.FeatureListener;

// 创建监听器
FeatureListener listener = new FeatureListener() {
    @Override
    public void changed(FeatureEvent event) {
        FeatureEvent.Type type = event.getType();
        
        switch (type) {
            case ADDED:
                System.out.println("添加了要素");
                break;
            case REMOVED:
                System.out.println("删除了要素");
                break;
            case CHANGED:
                System.out.println("修改了要素");
                break;
            case COMMIT:
                System.out.println("事务提交");
                break;
            case ROLLBACK:
                System.out.println("事务回滚");
                break;
        }
        
        // 获取受影响的边界
        ReferencedEnvelope bounds = event.getBounds();
        if (bounds != null) {
            System.out.println("影响范围: " + bounds);
        }
    }
};

// 注册监听器
SimpleFeatureSource source = dataStore.getFeatureSource("layer");
source.addFeatureListener(listener);

// 移除监听器
source.removeFeatureListener(listener);

5.8 高级特性

5.8.1 要素 ID 生成

import org.geotools.api.feature.simple.SimpleFeature;
import org.geotools.feature.simple.SimpleFeatureBuilder;

// 自动生成 ID
SimpleFeatureBuilder builder = new SimpleFeatureBuilder(featureType);
builder.addAll(values);
SimpleFeature feature1 = builder.buildFeature(null);  // ID 自动生成
System.out.println("自动 ID: " + feature1.getID());

// 指定 ID
SimpleFeature feature2 = builder.buildFeature("custom.id.123");
System.out.println("自定义 ID: " + feature2.getID());

// 使用 FeatureId
import org.geotools.api.filter.identity.FeatureId;
FeatureId fid = feature2.getIdentifier();
System.out.println("FeatureId: " + fid.getID());

// 基于属性生成 ID
builder.setFeatureTypeFactory((tb, values) -> {
    String name = (String) values[1];  // 假设 name 是第二个属性
    return name.toLowerCase().replace(" ", "_");
});

5.8.2 要素复制与转换

import org.geotools.feature.FeatureTypes;
import org.geotools.data.DataUtilities;

// 复制要素(浅拷贝)
SimpleFeature copy = DataUtilities.template(originalFeature);

// 深拷贝要素
SimpleFeature deepCopy = SimpleFeatureBuilder.copy(originalFeature);

// 重投影要素
import org.geotools.data.DataUtilities;
import org.geotools.referencing.CRS;

CoordinateReferenceSystem targetCRS = CRS.decode("EPSG:3857");
SimpleFeatureCollection reprojected = DataUtilities.reType(
    collection,
    DataUtilities.createSubType(
        collection.getSchema(),
        null,  // 所有属性
        targetCRS
    )
);

// 修改要素类型
SimpleFeatureType newType = DataUtilities.createSubType(
    originalType,
    new String[] { "name", "geometry" }  // 只保留这些属性
);

// 转换要素到新类型
SimpleFeature converted = DataUtilities.reType(newType, originalFeature);

5.8.3 要素比较

import org.geotools.feature.FeatureComparators;

// 按属性排序
Comparator<SimpleFeature> nameComparator = 
    FeatureComparators.byAttribute("name");

List<SimpleFeature> features = DataUtilities.list(collection);
Collections.sort(features, nameComparator);

// 按数值属性降序
Comparator<SimpleFeature> popComparator = 
    FeatureComparators.byAttribute("population");
Collections.sort(features, Collections.reverseOrder(popComparator));

// 自定义比较器
Comparator<SimpleFeature> customComparator = (f1, f2) -> {
    Long pop1 = (Long) f1.getAttribute("population");
    Long pop2 = (Long) f2.getAttribute("population");
    return Long.compare(pop2, pop1);  // 降序
};

5.9 实践示例

5.9.1 创建城市数据集

public class CityDataset {
    
    public static SimpleFeatureCollection createCities() throws Exception {
        // 定义要素类型
        SimpleFeatureType cityType = DataUtilities.createType(
            "City",
            "location:Point:srid=4326," +
            "name:String," +
            "country:String," +
            "population:Long," +
            "area:Double," +
            "timezone:String"
        );
        
        GeometryFactory gf = JTSFactoryFinder.getGeometryFactory();
        SimpleFeatureBuilder builder = new SimpleFeatureBuilder(cityType);
        DefaultFeatureCollection collection = new DefaultFeatureCollection("cities", cityType);
        
        // 添加城市数据
        Object[][] cityData = {
            {gf.createPoint(new Coordinate(116.4074, 39.9042)), "北京", "中国", 21540000L, 16410.54, "Asia/Shanghai"},
            {gf.createPoint(new Coordinate(121.4737, 31.2304)), "上海", "中国", 24280000L, 6340.5, "Asia/Shanghai"},
            {gf.createPoint(new Coordinate(113.2644, 23.1291)), "广州", "中国", 15300000L, 7434.4, "Asia/Shanghai"},
            {gf.createPoint(new Coordinate(114.0579, 22.5431)), "深圳", "中国", 17560000L, 1997.47, "Asia/Shanghai"},
            {gf.createPoint(new Coordinate(139.6917, 35.6895)), "东京", "日本", 13960000L, 2194.07, "Asia/Tokyo"},
            {gf.createPoint(new Coordinate(-74.0060, 40.7128)), "纽约", "美国", 8336817L, 783.8, "America/New_York"},
            {gf.createPoint(new Coordinate(-0.1278, 51.5074)), "伦敦", "英国", 8982000L, 1572.0, "Europe/London"},
        };
        
        for (int i = 0; i < cityData.length; i++) {
            builder.addAll(cityData[i]);
            collection.add(builder.buildFeature("city." + (i + 1)));
        }
        
        return collection;
    }
    
    public static void main(String[] args) throws Exception {
        SimpleFeatureCollection cities = createCities();
        
        System.out.println("城市数据集:");
        System.out.println("总数: " + cities.size());
        System.out.println("边界: " + cities.getBounds());
        
        // 查询中国城市
        FilterFactory ff = CommonFactoryFinder.getFilterFactory();
        Filter chinaFilter = ff.equals(ff.property("country"), ff.literal("中国"));
        
        try (SimpleFeatureIterator iter = cities.subCollection(chinaFilter).features()) {
            System.out.println("\n中国城市:");
            while (iter.hasNext()) {
                SimpleFeature city = iter.next();
                System.out.printf("  %s - 人口: %,d%n",
                    city.getAttribute("name"),
                    city.getAttribute("population"));
            }
        }
        
        // 统计总人口
        long totalPopulation = 0;
        try (SimpleFeatureIterator iter = cities.features()) {
            while (iter.hasNext()) {
                SimpleFeature city = iter.next();
                totalPopulation += (Long) city.getAttribute("population");
            }
        }
        System.out.println("\n总人口: " + String.format("%,d", totalPopulation));
    }
}

5.10 本章小结

本章详细介绍了 GeoTools 的要素模型:

  1. 要素类型 (SimpleFeatureType)

    • 使用 SimpleFeatureTypeBuilder 创建
    • 属性类型和约束
    • DataUtilities 快速创建
  2. 要素 (SimpleFeature)

    • 使用 SimpleFeatureBuilder 创建
    • 属性访问和修改
    • 要素遍历
  3. 要素集合 (FeatureCollection)

    • DefaultFeatureCollection
    • 遍历和过滤
    • 查询操作
  4. 数据访问

    • FeatureSource(只读)
    • FeatureStore(可写)
    • 事务管理
  5. 高级特性

    • ID 生成
    • 要素转换
    • 事件监听

关键要点

  • 使用 Builder 模式创建类型和要素
  • 注意迭代器的关闭
  • 写操作使用事务
  • 合理使用 Query 优化查询

← 上一章:几何对象与JTS集成 | 返回目录 | 下一章:数据源访问与管理 →

posted @ 2025-12-29 11:40  我才是银古  阅读(5)  评论(0)    收藏  举报