第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 的要素模型:
-
要素类型 (SimpleFeatureType)
- 使用 SimpleFeatureTypeBuilder 创建
- 属性类型和约束
- DataUtilities 快速创建
-
要素 (SimpleFeature)
- 使用 SimpleFeatureBuilder 创建
- 属性访问和修改
- 要素遍历
-
要素集合 (FeatureCollection)
- DefaultFeatureCollection
- 遍历和过滤
- 查询操作
-
数据访问
- FeatureSource(只读)
- FeatureStore(可写)
- 事务管理
-
高级特性
- ID 生成
- 要素转换
- 事件监听
关键要点
- 使用 Builder 模式创建类型和要素
- 注意迭代器的关闭
- 写操作使用事务
- 合理使用 Query 优化查询

浙公网安备 33010602011771号