第11章 - 开发实战案例

第11章 - 开发实战案例

11.1 案例一:GIS数据转换服务

11.1.1 需求分析

开发一个GIS数据格式转换服务,支持:

  • 多种输入格式(Shapefile、GeoJSON、FileGDB)
  • 多种输出格式(Shapefile、GeoJSON、PostGIS)
  • 坐标系转换
  • 属性过滤

11.1.2 核心实现

/**
 * GIS数据转换服务
 */
public class GisConversionService {
    
    private static final Logger log = LoggerFactory.getLogger(GisConversionService.class);
    
    /**
     * 转换请求
     */
    @Data
    public static class ConversionRequest {
        private DataFormatType sourceFormat;
        private String sourcePath;
        private String sourceLayerName;
        
        private DataFormatType targetFormat;
        private String targetPath;
        private String targetLayerName;
        
        private Integer targetWkid;  // 目标坐标系,null表示不转换
        private String attributeFilter;  // 属性过滤条件
    }
    
    /**
     * 转换结果
     */
    @Data
    public static class ConversionResult {
        private boolean success;
        private String message;
        private int featureCount;
        private long timeMs;
    }
    
    /**
     * 执行转换
     */
    public ConversionResult convert(ConversionRequest request) {
        long startTime = System.currentTimeMillis();
        ConversionResult result = new ConversionResult();
        
        try {
            // 1. 验证请求
            validateRequest(request);
            
            // 2. 选择引擎
            GisEngineType readEngine = selectEngine(request.getSourceFormat());
            GisEngineType writeEngine = selectEngine(request.getTargetFormat());
            
            log.info("开始转换: {} -> {}", request.getSourceFormat(), request.getTargetFormat());
            
            // 3. 读取数据
            OguLayer layer = OguLayerUtil.readLayer(
                request.getSourceFormat(),
                request.getSourcePath(),
                request.getSourceLayerName(),
                request.getAttributeFilter(),
                null,
                readEngine
            );
            
            log.info("读取完成: {} 个要素", layer.getFeatureCount());
            
            // 4. 坐标转换
            if (request.getTargetWkid() != null && 
                !request.getTargetWkid().equals(layer.getWkid())) {
                layer = CrsUtil.reproject(layer, request.getTargetWkid());
                log.info("坐标转换完成: WKID {}", request.getTargetWkid());
            }
            
            // 5. 写入数据
            OguLayerUtil.writeLayer(
                request.getTargetFormat(),
                layer,
                request.getTargetPath(),
                request.getTargetLayerName(),
                null,
                writeEngine
            );
            
            // 6. 返回结果
            result.setSuccess(true);
            result.setMessage("转换成功");
            result.setFeatureCount(layer.getFeatureCount());
            
        } catch (DataSourceException e) {
            result.setSuccess(false);
            result.setMessage("数据源错误: " + e.getMessage());
            log.error("转换失败", e);
        } catch (FormatParseException e) {
            result.setSuccess(false);
            result.setMessage("格式错误: " + e.getMessage());
            log.error("转换失败", e);
        } catch (OguException e) {
            result.setSuccess(false);
            result.setMessage("转换错误: " + e.getMessage());
            log.error("转换失败", e);
        }
        
        result.setTimeMs(System.currentTimeMillis() - startTime);
        return result;
    }
    
    /**
     * 验证请求
     */
    private void validateRequest(ConversionRequest request) {
        if (request.getSourceFormat() == null) {
            throw new IllegalArgumentException("源格式不能为空");
        }
        if (request.getTargetFormat() == null) {
            throw new IllegalArgumentException("目标格式不能为空");
        }
        if (request.getSourcePath() == null || request.getSourcePath().isEmpty()) {
            throw new IllegalArgumentException("源路径不能为空");
        }
        if (request.getTargetPath() == null || request.getTargetPath().isEmpty()) {
            throw new IllegalArgumentException("目标路径不能为空");
        }
    }
    
    /**
     * 选择合适的引擎
     */
    private GisEngineType selectEngine(DataFormatType format) {
        if (format == DataFormatType.FILEGDB) {
            if (!GisEngineFactory.isEngineAvailable(GisEngineType.GDAL)) {
                throw new EngineNotSupportedException("FileGDB格式需要GDAL支持");
            }
            return GisEngineType.GDAL;
        }
        return GisEngineType.GEOTOOLS;
    }
}

11.1.3 使用示例

public class ConversionServiceDemo {
    
    public static void main(String[] args) {
        GisConversionService service = new GisConversionService();
        
        // Shapefile转GeoJSON
        GisConversionService.ConversionRequest request = 
            new GisConversionService.ConversionRequest();
        request.setSourceFormat(DataFormatType.SHP);
        request.setSourcePath("D:/data/cities.shp");
        request.setTargetFormat(DataFormatType.GEOJSON);
        request.setTargetPath("D:/output/cities.geojson");
        
        GisConversionService.ConversionResult result = service.convert(request);
        
        if (result.isSuccess()) {
            System.out.printf("转换成功: %d 个要素, 耗时 %d ms%n",
                result.getFeatureCount(), result.getTimeMs());
        } else {
            System.out.println("转换失败: " + result.getMessage());
        }
    }
}

11.2 案例二:空间查询服务

11.2.1 需求分析

开发一个空间查询服务,支持:

  • 点查询(查找包含指定点的要素)
  • 范围查询(查找与范围相交的要素)
  • 缓冲区查询(查找距离指定几何一定范围内的要素)

11.2.2 核心实现

/**
 * 空间查询服务
 */
public class SpatialQueryService {
    
    private final OguLayer layer;
    private Map<String, Geometry> geometryCache;
    
    public SpatialQueryService(OguLayer layer) {
        this.layer = layer;
        initCache();
    }
    
    /**
     * 初始化几何缓存
     */
    private void initCache() {
        geometryCache = new HashMap<>();
        for (OguFeature feature : layer.getFeatures()) {
            if (feature.getWkt() != null) {
                Geometry geom = GeometryUtil.wkt2Geometry(feature.getWkt());
                geometryCache.put(feature.getFid(), geom);
            }
        }
    }
    
    /**
     * 点查询
     */
    public List<OguFeature> queryByPoint(double x, double y) {
        Geometry point = GeometryUtil.wkt2Geometry(
            String.format("POINT(%f %f)", x, y));
        
        return layer.getFeatures().stream()
            .filter(f -> {
                Geometry geom = geometryCache.get(f.getFid());
                return geom != null && GeometryUtil.contains(geom, point);
            })
            .collect(Collectors.toList());
    }
    
    /**
     * 范围查询
     */
    public List<OguFeature> queryByBounds(double minX, double minY, 
            double maxX, double maxY) {
        String boundsWkt = String.format(
            "POLYGON((%f %f, %f %f, %f %f, %f %f, %f %f))",
            minX, minY, maxX, minY, maxX, maxY, minX, maxY, minX, minY);
        Geometry bounds = GeometryUtil.wkt2Geometry(boundsWkt);
        
        return layer.getFeatures().stream()
            .filter(f -> {
                Geometry geom = geometryCache.get(f.getFid());
                return geom != null && GeometryUtil.intersects(bounds, geom);
            })
            .collect(Collectors.toList());
    }
    
    /**
     * 缓冲区查询
     */
    public List<OguFeature> queryByBuffer(String wkt, double distance) {
        Geometry geom = GeometryUtil.wkt2Geometry(wkt);
        Geometry buffer = GeometryUtil.buffer(geom, distance);
        
        return layer.getFeatures().stream()
            .filter(f -> {
                Geometry fGeom = geometryCache.get(f.getFid());
                return fGeom != null && GeometryUtil.intersects(buffer, fGeom);
            })
            .collect(Collectors.toList());
    }
    
    /**
     * 最近邻查询
     */
    public List<OguFeature> queryNearest(double x, double y, int count) {
        Geometry point = GeometryUtil.wkt2Geometry(
            String.format("POINT(%f %f)", x, y));
        
        // 计算距离并排序
        return layer.getFeatures().stream()
            .filter(f -> geometryCache.containsKey(f.getFid()))
            .sorted((f1, f2) -> {
                Geometry g1 = geometryCache.get(f1.getFid());
                Geometry g2 = geometryCache.get(f2.getFid());
                double d1 = GeometryUtil.distance(point, g1);
                double d2 = GeometryUtil.distance(point, g2);
                return Double.compare(d1, d2);
            })
            .limit(count)
            .collect(Collectors.toList());
    }
    
    /**
     * 空间关系查询
     */
    public List<OguFeature> queryBySpatialRelation(String wkt, 
            SpatialRelation relation) {
        Geometry queryGeom = GeometryUtil.wkt2Geometry(wkt);
        
        return layer.getFeatures().stream()
            .filter(f -> {
                Geometry geom = geometryCache.get(f.getFid());
                if (geom == null) return false;
                
                switch (relation) {
                    case INTERSECTS:
                        return GeometryUtil.intersects(queryGeom, geom);
                    case CONTAINS:
                        return GeometryUtil.contains(queryGeom, geom);
                    case WITHIN:
                        return GeometryUtil.within(geom, queryGeom);
                    case TOUCHES:
                        return GeometryUtil.touches(queryGeom, geom);
                    case OVERLAPS:
                        return GeometryUtil.overlaps(queryGeom, geom);
                    default:
                        return false;
                }
            })
            .collect(Collectors.toList());
    }
    
    public enum SpatialRelation {
        INTERSECTS, CONTAINS, WITHIN, TOUCHES, OVERLAPS
    }
}

11.2.3 使用示例

public class SpatialQueryDemo {
    
    public static void main(String[] args) {
        // 加载数据
        OguLayer layer = OguLayerUtil.readLayer(
            DataFormatType.SHP,
            "D:/data/districts.shp",
            null, null, null,
            GisEngineType.GEOTOOLS
        );
        
        SpatialQueryService queryService = new SpatialQueryService(layer);
        
        // 点查询
        System.out.println("=== 点查询 ===");
        List<OguFeature> result1 = queryService.queryByPoint(116.4, 39.9);
        for (OguFeature f : result1) {
            System.out.println("  " + f.getValue("NAME"));
        }
        
        // 范围查询
        System.out.println("\n=== 范围查询 ===");
        List<OguFeature> result2 = queryService.queryByBounds(116, 39, 117, 40);
        System.out.println("  找到 " + result2.size() + " 个要素");
        
        // 最近邻查询
        System.out.println("\n=== 最近3个 ===");
        List<OguFeature> result3 = queryService.queryNearest(116.4, 39.9, 3);
        for (OguFeature f : result3) {
            System.out.println("  " + f.getValue("NAME"));
        }
    }
}

11.3 案例三:国土数据处理

11.3.1 需求分析

处理自然资源部门的数据:

  • 读取国土TXT坐标文件
  • 进行几何验证和修复
  • 计算面积
  • 导出为Shapefile

11.3.2 核心实现

/**
 * 国土数据处理服务
 */
public class LandDataProcessor {
    
    /**
     * 处理结果
     */
    @Data
    public static class ProcessResult {
        private int totalCount;
        private int validCount;
        private int repairedCount;
        private int failedCount;
        private double totalArea;
        private List<String> errors = new ArrayList<>();
    }
    
    /**
     * 处理国土TXT文件
     */
    public ProcessResult processTxtFile(String txtPath, String outputShpPath) {
        ProcessResult result = new ProcessResult();
        
        try {
            // 1. 读取TXT
            OguLayer layer = GtTxtUtil.loadTxt(txtPath, null);
            result.setTotalCount(layer.getFeatureCount());
            
            // 2. 添加面积字段
            addAreaField(layer);
            
            // 3. 验证和修复每个要素
            for (OguFeature feature : layer.getFeatures()) {
                try {
                    processFeature(feature, layer.getWkid(), result);
                } catch (Exception e) {
                    result.setFailedCount(result.getFailedCount() + 1);
                    result.getErrors().add(String.format("FID %s: %s", 
                        feature.getFid(), e.getMessage()));
                }
            }
            
            // 4. 移除无效要素
            layer.getFeatures().removeIf(f -> f.getWkt() == null);
            
            // 5. 导出Shapefile
            if (!layer.getFeatures().isEmpty()) {
                OguLayerUtil.writeLayer(
                    DataFormatType.SHP,
                    layer,
                    outputShpPath,
                    null, null,
                    GisEngineType.GEOTOOLS
                );
            }
            
        } catch (Exception e) {
            result.getErrors().add("处理失败: " + e.getMessage());
        }
        
        return result;
    }
    
    /**
     * 处理单个要素
     */
    private void processFeature(OguFeature feature, Integer wkid, ProcessResult result) {
        if (feature.getWkt() == null || feature.getWkt().isEmpty()) {
            // 尝试从坐标点构建几何
            if (feature.getCoordinates() != null && !feature.getCoordinates().isEmpty()) {
                feature.setWkt(buildPolygonFromCoordinates(feature.getCoordinates()));
            } else {
                result.setFailedCount(result.getFailedCount() + 1);
                return;
            }
        }
        
        Geometry geom = GeometryUtil.wkt2Geometry(feature.getWkt());
        
        // 验证几何
        TopologyValidationResult validResult = GeometryUtil.isValid(geom);
        
        if (!validResult.isValid()) {
            // 尝试修复
            try {
                geom = GeometryUtil.validate(geom);
                feature.setWkt(GeometryUtil.geometry2Wkt(geom));
                result.setRepairedCount(result.getRepairedCount() + 1);
            } catch (Exception e) {
                result.setFailedCount(result.getFailedCount() + 1);
                feature.setWkt(null);
                return;
            }
        } else {
            result.setValidCount(result.getValidCount() + 1);
        }
        
        // 计算面积
        double area = calculateArea(geom, wkid);
        feature.setValue("AREA_M2", area);
        feature.setValue("AREA_MU", area / 666.67);
        result.setTotalArea(result.getTotalArea() + area);
    }
    
    /**
     * 从坐标点构建多边形WKT
     */
    private String buildPolygonFromCoordinates(List<OguCoordinate> coordinates) {
        if (coordinates.size() < 4) {
            throw new TopologyException("坐标点太少,无法构建多边形");
        }
        
        StringBuilder sb = new StringBuilder("POLYGON((");
        for (int i = 0; i < coordinates.size(); i++) {
            OguCoordinate coord = coordinates.get(i);
            if (i > 0) sb.append(", ");
            sb.append(NumUtil.getPlainString(coord.getX()))
              .append(" ")
              .append(NumUtil.getPlainString(coord.getY()));
        }
        sb.append("))");
        
        return sb.toString();
    }
    
    /**
     * 计算面积(平方米)
     */
    private double calculateArea(Geometry geom, Integer wkid) {
        if (wkid == null || wkid == 4490) {
            // 地理坐标系,需要转换
            int dh = CrsUtil.getDh(geom);
            Integer projWkid = CrsUtil.getProjectedWkid(dh);
            geom = CrsUtil.transform(geom, wkid != null ? wkid : 4490, projWkid);
        }
        return GeometryUtil.area(geom);
    }
    
    /**
     * 添加面积字段
     */
    private void addAreaField(OguLayer layer) {
        List<OguField> fields = new ArrayList<>(layer.getFields());
        
        OguField areaM2 = new OguField();
        areaM2.setName("AREA_M2");
        areaM2.setAlias("面积(平方米)");
        areaM2.setDataType(FieldDataType.DOUBLE);
        fields.add(areaM2);
        
        OguField areaMu = new OguField();
        areaMu.setName("AREA_MU");
        areaMu.setAlias("面积(亩)");
        areaMu.setDataType(FieldDataType.DOUBLE);
        fields.add(areaMu);
        
        layer.setFields(fields);
    }
}

11.3.3 使用示例

public class LandDataDemo {
    
    public static void main(String[] args) {
        LandDataProcessor processor = new LandDataProcessor();
        
        LandDataProcessor.ProcessResult result = processor.processTxtFile(
            "D:/data/land.txt",
            "D:/output/land.shp"
        );
        
        System.out.println("=== 处理结果 ===");
        System.out.println("总数量: " + result.getTotalCount());
        System.out.println("有效: " + result.getValidCount());
        System.out.println("已修复: " + result.getRepairedCount());
        System.out.println("失败: " + result.getFailedCount());
        System.out.printf("总面积: %.2f 平方米 (%.2f 亩)%n", 
            result.getTotalArea(), result.getTotalArea() / 666.67);
        
        if (!result.getErrors().isEmpty()) {
            System.out.println("\n=== 错误信息 ===");
            for (String error : result.getErrors()) {
                System.out.println("  " + error);
            }
        }
    }
}

11.4 案例四:图层合并工具

11.4.1 需求分析

开发一个图层合并工具:

  • 合并多个同结构图层
  • 支持字段映射
  • 去除重复要素
  • 验证几何

11.4.2 核心实现

/**
 * 图层合并工具
 */
public class LayerMerger {
    
    /**
     * 合并配置
     */
    @Data
    public static class MergeConfig {
        private boolean removeDuplicates = true;
        private boolean validateGeometry = true;
        private Map<String, String> fieldMapping = new HashMap<>();  // 源字段 -> 目标字段
    }
    
    /**
     * 合并多个图层
     */
    public OguLayer merge(List<OguLayer> layers, MergeConfig config) {
        if (layers == null || layers.isEmpty()) {
            throw new IllegalArgumentException("图层列表不能为空");
        }
        
        // 使用第一个图层作为模板
        OguLayer template = layers.get(0);
        
        OguLayer result = new OguLayer();
        result.setName("merged_layer");
        result.setWkid(template.getWkid());
        result.setGeometryType(template.getGeometryType());
        result.setFields(new ArrayList<>(template.getFields()));
        result.setFeatures(new ArrayList<>());
        
        Set<String> wktSet = config.isRemoveDuplicates() ? new HashSet<>() : null;
        int fid = 1;
        
        for (OguLayer layer : layers) {
            // 验证图层兼容性
            if (layer.getGeometryType() != template.getGeometryType()) {
                throw new LayerValidationException("几何类型不一致: " + layer.getName());
            }
            
            for (OguFeature sourceFeature : layer.getFeatures()) {
                // 去重检查
                if (wktSet != null && sourceFeature.getWkt() != null) {
                    if (wktSet.contains(sourceFeature.getWkt())) {
                        continue;  // 跳过重复
                    }
                    wktSet.add(sourceFeature.getWkt());
                }
                
                // 几何验证
                if (config.isValidateGeometry() && sourceFeature.getWkt() != null) {
                    Geometry geom = GeometryUtil.wkt2Geometry(sourceFeature.getWkt());
                    TopologyValidationResult valid = GeometryUtil.isValid(geom);
                    if (!valid.isValid()) {
                        try {
                            geom = GeometryUtil.validate(geom);
                            sourceFeature.setWkt(GeometryUtil.geometry2Wkt(geom));
                        } catch (Exception e) {
                            continue;  // 跳过无效几何
                        }
                    }
                }
                
                // 创建新要素
                OguFeature newFeature = new OguFeature();
                newFeature.setFid(String.valueOf(fid++));
                newFeature.setWkt(sourceFeature.getWkt());
                
                // 复制属性(考虑字段映射)
                for (OguField field : template.getFields()) {
                    String sourceFieldName = config.getFieldMapping()
                        .getOrDefault(field.getName(), field.getName());
                    Object value = sourceFeature.getValue(sourceFieldName);
                    newFeature.setValue(field.getName(), value);
                }
                
                result.getFeatures().add(newFeature);
            }
        }
        
        return result;
    }
    
    /**
     * 从文件列表合并
     */
    public OguLayer mergeFromFiles(List<String> shpPaths, MergeConfig config) {
        List<OguLayer> layers = new ArrayList<>();
        
        for (String path : shpPaths) {
            OguLayer layer = OguLayerUtil.readLayer(
                DataFormatType.SHP,
                path,
                null, null, null,
                GisEngineType.GEOTOOLS
            );
            layers.add(layer);
        }
        
        return merge(layers, config);
    }
}

11.4.3 使用示例

public class LayerMergerDemo {
    
    public static void main(String[] args) {
        LayerMerger merger = new LayerMerger();
        
        // 配置
        LayerMerger.MergeConfig config = new LayerMerger.MergeConfig();
        config.setRemoveDuplicates(true);
        config.setValidateGeometry(true);
        
        // 合并文件
        List<String> files = Arrays.asList(
            "D:/data/region1.shp",
            "D:/data/region2.shp",
            "D:/data/region3.shp"
        );
        
        OguLayer merged = merger.mergeFromFiles(files, config);
        
        System.out.println("合并完成: " + merged.getFeatureCount() + " 个要素");
        
        // 保存结果
        OguLayerUtil.writeLayer(
            DataFormatType.SHP,
            merged,
            "D:/output/merged.shp",
            null, null,
            GisEngineType.GEOTOOLS
        );
    }
}

11.5 案例五:数据质量检查

11.5.1 需求分析

开发一个数据质量检查工具:

  • 几何有效性检查
  • 属性完整性检查
  • 坐标范围检查
  • 生成检查报告

11.5.2 核心实现

/**
 * 数据质量检查服务
 */
public class DataQualityChecker {
    
    @Data
    public static class QualityReport {
        private String layerName;
        private int totalFeatures;
        private int validGeometries;
        private int invalidGeometries;
        private int emptyGeometries;
        private Map<String, Integer> fieldCompleteness = new HashMap<>();
        private List<QualityIssue> issues = new ArrayList<>();
    }
    
    @Data
    @AllArgsConstructor
    public static class QualityIssue {
        private String featureId;
        private String issueType;
        private String description;
    }
    
    /**
     * 检查图层质量
     */
    public QualityReport check(OguLayer layer) {
        QualityReport report = new QualityReport();
        report.setLayerName(layer.getName());
        report.setTotalFeatures(layer.getFeatureCount());
        
        // 初始化字段统计
        for (OguField field : layer.getFields()) {
            report.getFieldCompleteness().put(field.getName(), 0);
        }
        
        // 检查每个要素
        for (OguFeature feature : layer.getFeatures()) {
            checkFeature(feature, layer, report);
        }
        
        return report;
    }
    
    /**
     * 检查单个要素
     */
    private void checkFeature(OguFeature feature, OguLayer layer, QualityReport report) {
        // 1. 几何检查
        checkGeometry(feature, layer.getWkid(), report);
        
        // 2. 属性检查
        checkAttributes(feature, layer.getFields(), report);
    }
    
    /**
     * 几何检查
     */
    private void checkGeometry(OguFeature feature, Integer wkid, QualityReport report) {
        String wkt = feature.getWkt();
        
        if (wkt == null || wkt.isEmpty()) {
            report.setEmptyGeometries(report.getEmptyGeometries() + 1);
            report.getIssues().add(new QualityIssue(
                feature.getFid(), "EMPTY_GEOMETRY", "几何为空"));
            return;
        }
        
        try {
            Geometry geom = GeometryUtil.wkt2Geometry(wkt);
            
            // 有效性检查
            TopologyValidationResult validResult = GeometryUtil.isValid(geom);
            if (!validResult.isValid()) {
                report.setInvalidGeometries(report.getInvalidGeometries() + 1);
                report.getIssues().add(new QualityIssue(
                    feature.getFid(), 
                    "INVALID_GEOMETRY", 
                    validResult.getErrorType().getDesc()));
            } else {
                report.setValidGeometries(report.getValidGeometries() + 1);
            }
            
            // 坐标范围检查
            if (wkid != null) {
                checkCoordinateRange(feature.getFid(), geom, wkid, report);
            }
            
        } catch (Exception e) {
            report.setInvalidGeometries(report.getInvalidGeometries() + 1);
            report.getIssues().add(new QualityIssue(
                feature.getFid(), "PARSE_ERROR", "WKT解析失败: " + e.getMessage()));
        }
    }
    
    /**
     * 坐标范围检查
     */
    private void checkCoordinateRange(String fid, Geometry geom, int wkid, 
            QualityReport report) {
        boolean isProjected = CrsUtil.isProjectedCRS(CrsUtil.getCRS(wkid));
        
        for (Coordinate coord : geom.getCoordinates()) {
            if (isProjected) {
                // 投影坐标检查
                if (coord.x < 0 || coord.y < 0 || coord.x > 1e9 || coord.y > 1e9) {
                    report.getIssues().add(new QualityIssue(
                        fid, "COORDINATE_RANGE", 
                        String.format("可疑坐标: (%.2f, %.2f)", coord.x, coord.y)));
                    break;
                }
            } else {
                // 地理坐标检查
                if (coord.x < -180 || coord.x > 180 || coord.y < -90 || coord.y > 90) {
                    report.getIssues().add(new QualityIssue(
                        fid, "COORDINATE_RANGE", 
                        String.format("坐标超出范围: (%.4f, %.4f)", coord.x, coord.y)));
                    break;
                }
            }
        }
    }
    
    /**
     * 属性检查
     */
    private void checkAttributes(OguFeature feature, List<OguField> fields, 
            QualityReport report) {
        for (OguField field : fields) {
            Object value = feature.getValue(field.getName());
            
            if (value != null && !value.toString().isEmpty()) {
                report.getFieldCompleteness().merge(field.getName(), 1, Integer::sum);
            } else if (!field.getNullable()) {
                report.getIssues().add(new QualityIssue(
                    feature.getFid(), 
                    "MISSING_VALUE", 
                    "必填字段为空: " + field.getName()));
            }
        }
    }
    
    /**
     * 生成报告文本
     */
    public String generateReport(QualityReport report) {
        StringBuilder sb = new StringBuilder();
        sb.append("=== 数据质量检查报告 ===\n\n");
        sb.append(String.format("图层名称: %s%n", report.getLayerName()));
        sb.append(String.format("要素总数: %d%n", report.getTotalFeatures()));
        sb.append(String.format("有效几何: %d (%.1f%%)%n", 
            report.getValidGeometries(),
            100.0 * report.getValidGeometries() / report.getTotalFeatures()));
        sb.append(String.format("无效几何: %d%n", report.getInvalidGeometries()));
        sb.append(String.format("空几何: %d%n", report.getEmptyGeometries()));
        
        sb.append("\n字段完整性:\n");
        for (Map.Entry<String, Integer> entry : report.getFieldCompleteness().entrySet()) {
            double rate = 100.0 * entry.getValue() / report.getTotalFeatures();
            sb.append(String.format("  %s: %.1f%%%n", entry.getKey(), rate));
        }
        
        if (!report.getIssues().isEmpty()) {
            sb.append(String.format("%n问题列表 (%d个):%n", report.getIssues().size()));
            int count = 0;
            for (QualityIssue issue : report.getIssues()) {
                if (++count > 20) {
                    sb.append("  ... 更多问题省略\n");
                    break;
                }
                sb.append(String.format("  [%s] FID=%s: %s%n", 
                    issue.getIssueType(), issue.getFeatureId(), issue.getDescription()));
            }
        }
        
        return sb.toString();
    }
}

11.5.3 使用示例

public class QualityCheckDemo {
    
    public static void main(String[] args) {
        // 加载数据
        OguLayer layer = OguLayerUtil.readLayer(
            DataFormatType.SHP,
            "D:/data/sample.shp",
            null, null, null,
            GisEngineType.GEOTOOLS
        );
        
        // 检查质量
        DataQualityChecker checker = new DataQualityChecker();
        DataQualityChecker.QualityReport report = checker.check(layer);
        
        // 输出报告
        String reportText = checker.generateReport(report);
        System.out.println(reportText);
        
        // 保存报告
        try {
            Files.write(Paths.get("D:/output/quality_report.txt"), 
                reportText.getBytes(StandardCharsets.UTF_8));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

← 上一章:实用工具类详解 | 下一章:扩展开发指南 →

posted @ 2025-12-02 10:30  我才是银古  阅读(7)  评论(0)    收藏  举报