第04章 - 统一图层模型详解
第04章 - 统一图层模型详解
4.1 模型设计理念
4.1.1 为什么需要统一模型
在GIS开发中,不同的库使用不同的数据模型:
| 库 | 要素类 | 要素 | 几何 |
|---|---|---|---|
| GeoTools | SimpleFeatureType | SimpleFeature | Geometry |
| GDAL/OGR | Layer | Feature | Geometry |
| ESRI | FeatureClass | Feature | Geometry |
这种差异带来的问题:
- 代码不可移植:为GeoTools写的代码不能用于GDAL
- 学习成本高:每个库都有自己的概念体系
- 维护困难:需要熟悉多套API
OGU4J的统一模型解决了这些问题,提供了与底层库无关的数据抽象。
4.1.2 模型设计原则
- 简洁性:只保留必要的属性和方法
- 通用性:适用于所有GIS数据格式
- 可序列化:支持JSON序列化/反序列化
- 可扩展性:支持元数据扩展
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 │ │ - fid │ │ - dataSource │
│ - alias │ │ - wkt │ │ - coordinateSystem │
│ - dataType │ │ - attributes: Map │ │ - zoneNumber │
│ - length │ │ <String, │ │ - projectionType │
│ - precision │ │ OguFieldValue> │ │ - measureUnit │
│ - nullable │ ├─────────────────────┤ │ - extendedInfo │
├─────────────────┤ │ + getValue() │ └─────────────────────┘
│ + 标准getter │ │ + setValue() │
│ + 标准setter │ │ + getAttribute() │
└─────────────────┘ └─────────────────────┘
│
│ 1:n
▼
┌─────────────────────┐
│ OguFieldValue │
│ (字段值容器) │
├─────────────────────┤
│ - value: Object │
├─────────────────────┤
│ + getStringValue() │
│ + getIntValue() │
│ + getDoubleValue() │
│ + getDateValue() │
│ + getBooleanValue() │
└─────────────────────┘
4.2 OguLayer - 图层类
4.2.1 类定义
/**
* 统一的GIS图层定义
* 代表一个完整的GIS图层,包含图层名称、坐标系、几何类型、字段定义和要素集合
*
* 设计理念:
* - 独立于任何GIS库的数据表示
* - 支持JSON序列化,便于传输和存储
* - 提供便捷的操作方法
*/
@Data
public class OguLayer {
/**
* 图层名称
*/
private String name;
/**
* 坐标系WKID(Well-Known ID)
* 例如:4490 = CGCS2000地理坐标系
* 4524 = 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.setFid("1");
feature.setWkt("POLYGON((116 39, 116 40, 117 40, 117 39, 116 39))");
feature.setValue("CODE", "110000");
feature.setValue("NAME", "北京市");
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 Integer precision;
/**
* 是否允许空值,默认true
*/
private Boolean nullable = true;
/**
* 默认值(可选)
*/
private Object defaultValue;
}
4.3.2 字段数据类型
/**
* 字段数据类型枚举
*/
public enum FieldDataType {
STRING("字符串"),
INTEGER("整数"),
LONG("长整数"),
DOUBLE("浮点数"),
FLOAT("单精度浮点"),
DATE("日期"),
DATETIME("日期时间"),
BOOLEAN("布尔值"),
BINARY("二进制");
private final String desc;
// 类型转换方法
public static FieldDataType fromJavaType(Class<?> clazz) {
if (String.class.equals(clazz)) return STRING;
if (Integer.class.equals(clazz)) return INTEGER;
if (Long.class.equals(clazz)) return LONG;
if (Double.class.equals(clazz)) return DOUBLE;
if (Float.class.equals(clazz)) return FLOAT;
if (Date.class.equals(clazz)) return DATETIME;
if (Boolean.class.equals(clazz)) return BOOLEAN;
return STRING;
}
}
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.setPrecision(2);
// 日期字段
OguField dateField = new OguField();
dateField.setName("CREATE_TIME");
dateField.setAlias("创建时间");
dateField.setDataType(FieldDataType.DATETIME);
// 布尔字段
OguField activeField = new OguField();
activeField.setName("IS_ACTIVE");
activeField.setAlias("是否有效");
activeField.setDataType(FieldDataType.BOOLEAN);
activeField.setDefaultValue(true);
4.4 OguFeature - 要素类
4.4.1 类定义
/**
* 统一的要素类
* 代表图层中的一个地理要素,包含ID、几何信息和属性值
*/
@Data
public class OguFeature {
/**
* 要素ID(FID)
* 唯一标识一个要素
*/
private String fid;
/**
* 几何信息(WKT格式)
* 例如:"POLYGON((116 39, 116 40, 117 40, 117 39, 116 39))"
*/
private String wkt;
/**
* 属性值集合
* Key: 字段名称
* Value: 字段值容器
*/
private Map<String, OguFieldValue> attributes;
/**
* 坐标列表(可选,用于国土TXT格式)
*/
private List<OguCoordinate> coordinates;
// ... 方法定义
}
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);
// 批量设置
Map<String, Object> values = new HashMap<>();
values.put("NAME", "上海市");
values.put("AREA", 6340.5);
values.put("POPULATION", 24280000);
feature.setValues(values);
4.4.3 几何操作
// 获取WKT
String wkt = feature.getWkt();
System.out.println(wkt);
// 输出: POLYGON((116 39, 116 40, 117 40, 117 39, 116 39))
// 设置WKT
feature.setWkt("POLYGON((120 30, 120 31, 121 31, 121 30, 120 30))");
// 转换为JTS Geometry进行操作
Geometry geom = GeometryUtil.wkt2Geometry(feature.getWkt());
double area = geom.getArea();
Geometry centroid = geom.getCentroid();
// 更新几何
Geometry buffered = geom.buffer(0.01);
feature.setWkt(GeometryUtil.geometry2Wkt(buffered));
4.5 OguFieldValue - 字段值容器
4.5.1 类定义
/**
* 字段值容器
* 封装原始值,提供类型安全的值获取方法
*
* 设计理念:
* - 统一处理null值
* - 提供类型转换
* - 避免ClassCastException
*/
@Data
public class OguFieldValue {
/**
* 原始值
*/
private Object value;
public OguFieldValue() {}
public OguFieldValue(Object value) {
this.value = value;
}
/**
* 获取字符串值
*/
public String getStringValue() {
if (value == null) return null;
return String.valueOf(value);
}
/**
* 获取整数值
*/
public Integer getIntValue() {
if (value == null) return null;
if (value instanceof Number) {
return ((Number) value).intValue();
}
try {
return Integer.parseInt(String.valueOf(value));
} catch (NumberFormatException e) {
return null;
}
}
/**
* 获取Double值
*/
public Double getDoubleValue() {
if (value == null) return null;
if (value instanceof Number) {
return ((Number) value).doubleValue();
}
try {
return Double.parseDouble(String.valueOf(value));
} catch (NumberFormatException e) {
return null;
}
}
/**
* 获取Long值
*/
public Long getLongValue() {
if (value == null) return null;
if (value instanceof Number) {
return ((Number) value).longValue();
}
try {
return Long.parseLong(String.valueOf(value));
} catch (NumberFormatException e) {
return null;
}
}
/**
* 获取布尔值
*/
public Boolean getBooleanValue() {
if (value == null) return null;
if (value instanceof Boolean) {
return (Boolean) value;
}
String str = String.valueOf(value).toLowerCase();
return "true".equals(str) || "1".equals(str) || "yes".equals(str);
}
/**
* 获取日期值
*/
public Date getDateValue() {
if (value == null) return null;
if (value instanceof Date) {
return (Date) value;
}
// 可以扩展日期解析逻辑
return null;
}
}
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();
Boolean active = feature.getAttribute("IS_ACTIVE").getBooleanValue();
// 处理可能为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 pointNo;
/**
* 圈号(用于国土TXT格式,区分内外环)
*/
private Integer ringNo;
// 构造函数
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.setPointNo("J1");
coord.setRingNo(1); // 外环
// 批量创建坐标点
List<OguCoordinate> coords = new ArrayList<>();
coords.add(new OguCoordinate(500000.0, 4000000.0) {{ setPointNo("J1"); setRingNo(1); }});
coords.add(new OguCoordinate(500100.0, 4000000.0) {{ setPointNo("J2"); setRingNo(1); }});
coords.add(new OguCoordinate(500100.0, 4000100.0) {{ setPointNo("J3"); setRingNo(1); }});
coords.add(new OguCoordinate(500000.0, 4000100.0) {{ setPointNo("J4"); setRingNo(1); }});
coords.add(new OguCoordinate(500000.0, 4000000.0) {{ setPointNo("J1"); setRingNo(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 Map<String, Object> extendedInfo;
}
4.7.2 使用示例
// 创建元数据
OguLayerMetadata metadata = new OguLayerMetadata();
metadata.setDataSource("自然资源部");
metadata.setCoordinateSystemName("2000国家大地坐标系");
metadata.setZoneDivision("3");
metadata.setProjectionType("高斯克吕格");
metadata.setMeasureUnit("米");
// 添加扩展信息
Map<String, Object> extInfo = new HashMap<>();
extInfo.put("version", "1.0");
extInfo.put("createDate", "2024-01-01");
extInfo.put("author", "张三");
metadata.setExtendedInfo(extInfo);
// 设置到图层
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 test(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.getWkt();
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.test(f) && filter2.test(f));
4.9 GeometryType - 几何类型枚举
4.9.1 枚举定义
/**
* 几何类型枚举
* 定义所有支持的几何类型
*/
public enum GeometryType {
POINT("Point", "点"),
MULTIPOINT("MultiPoint", "多点"),
LINESTRING("LineString", "线"),
MULTILINESTRING("MultiLineString", "多线"),
POLYGON("Polygon", "面"),
MULTIPOLYGON("MultiPolygon", "多面"),
GEOMETRYCOLLECTION("GeometryCollection", "几何集合");
private final String wktName;
private final String desc;
/**
* 从WKT几何名称获取枚举
*/
public static GeometryType fromWktName(String wktName) {
for (GeometryType type : values()) {
if (type.wktName.equalsIgnoreCase(wktName)) {
return type;
}
}
return null;
}
/**
* 从JTS Geometry获取类型
*/
public static GeometryType fromGeometry(Geometry geom) {
return fromWktName(geom.getGeometryType());
}
}
4.9.2 使用示例
// 设置图层几何类型
layer.setGeometryType(GeometryType.POLYGON);
// 从WKT判断类型
String wkt = "POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))";
GeometryType type = GeometryType.fromWktName("POLYGON");
// 从Geometry判断类型
Geometry geom = GeometryUtil.wkt2Geometry(wkt);
GeometryType type = GeometryType.fromGeometry(geom);
// 类型判断
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("1", "110000", "北京", "北京", 2154, 40269.6, 1,
"POINT(116.407 39.904)"));
features.add(createCity("2", "310000", "上海", "上海", 2428, 43214.9, 1,
"POINT(121.473 31.230)"));
features.add(createCity("3", "440100", "广州", "广东", 1881, 28839.0, 2,
"POINT(113.264 23.129)"));
features.add(createCity("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(String fid, String code, String name,
String province, int population, double gdp, int level, String wkt) {
OguFeature feature = new OguFeature();
feature.setFid(fid);
feature.setWkt(wkt);
feature.setValue("CODE", code);
feature.setValue("NAME", name);
feature.setValue("PROVINCE", province);
feature.setValue("POPULATION", population);
feature.setValue("GDP", gdp);
feature.setValue("LEVEL", level);
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.getWkt() != null) {
Geometry geom = GeometryUtil.wkt2Geometry(feature.getWkt());
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);
}
}

浙公网安备 33010602011771号