第10章 - 实用工具类详解

第10章 - 实用工具类详解

10.1 工具类概述

OGU4J提供了一系列实用工具类,位于com.znlgis.ogu4j.utils包:

工具类 功能 主要用途
ZipUtil ZIP压缩/解压 Shapefile打包、数据分发
EncodingUtil 编码检测 中文属性读取
SortUtil 自然排序 带数字的字符串排序
NumUtil 数字格式化 去除科学计数法

此外,还有位于com.znlgis.ogu4j.engine.util包的引擎工具类:

工具类 功能 主要用途
ShpUtil Shapefile工具 Shapefile相关操作
PostgisUtil PostGIS工具 数据库连接管理
OgrUtil OGR工具 GDAL/OGR相关
GeotoolsUtil GeoTools工具 GeoTools相关
GdalCmdUtil GDAL命令行 命令行工具封装

10.2 ZipUtil - ZIP压缩解压

10.2.1 类定义

/**
 * ZIP压缩/解压工具类
 * 基于Zip4j库实现
 */
public class ZipUtil {
    
    /**
     * 压缩文件夹(使用系统默认编码)
     */
    public static void zip(File folder, String zipPath);
    
    /**
     * 压缩文件夹(指定编码)
     */
    public static void zip(File folder, String zipPath, Charset charset);
    
    /**
     * 解压文件(使用系统默认编码)
     */
    public static void unzip(String zipPath, String destPath);
    
    /**
     * 解压文件(指定编码)
     */
    public static void unzip(String zipPath, String destPath, Charset charset);
}

10.2.2 基本使用

import com.znlgis.ogu4j.utils.ZipUtil;
import java.io.File;
import java.nio.charset.StandardCharsets;

// 压缩文件夹
File folder = new File("D:/data/shapefile_folder");
ZipUtil.zip(folder, "D:/output/data.zip");

// 指定UTF-8编码(推荐,避免中文乱码)
ZipUtil.zip(folder, "D:/output/data.zip", StandardCharsets.UTF_8);

// 解压文件
ZipUtil.unzip("D:/data/data.zip", "D:/output/extracted");

// 指定编码解压
ZipUtil.unzip("D:/data/data.zip", "D:/output/extracted", StandardCharsets.UTF_8);

10.2.3 Shapefile打包

public class ShapefilePackager {
    
    /**
     * 打包Shapefile(包含所有关联文件)
     */
    public static void packageShapefile(String shpPath, String zipPath) {
        File shpFile = new File(shpPath);
        String baseName = shpFile.getName().replace(".shp", "");
        File folder = shpFile.getParentFile();
        
        // 创建临时目录
        File tempDir = new File(System.getProperty("java.io.tmpdir"), 
            "shp_" + System.currentTimeMillis());
        tempDir.mkdirs();
        
        try {
            // 复制所有相关文件
            String[] extensions = {".shp", ".shx", ".dbf", ".prj", ".cpg", ".sbn", ".sbx"};
            for (String ext : extensions) {
                File srcFile = new File(folder, baseName + ext);
                if (srcFile.exists()) {
                    Files.copy(srcFile.toPath(), 
                        new File(tempDir, srcFile.getName()).toPath());
                }
            }
            
            // 压缩
            ZipUtil.zip(tempDir, zipPath, StandardCharsets.UTF_8);
            
        } finally {
            // 清理临时目录
            deleteDirectory(tempDir);
        }
    }
    
    /**
     * 解压Shapefile并返回shp文件路径
     */
    public static String unpackShapefile(String zipPath, String destDir) {
        ZipUtil.unzip(zipPath, destDir, StandardCharsets.UTF_8);
        
        // 查找shp文件
        File dir = new File(destDir);
        File[] shpFiles = dir.listFiles((d, name) -> name.endsWith(".shp"));
        
        if (shpFiles != null && shpFiles.length > 0) {
            return shpFiles[0].getAbsolutePath();
        }
        
        throw new DataSourceException("ZIP包中未找到Shapefile");
    }
}

10.2.4 处理中文路径

public class ChinesePathHandler {
    
    public static void zipWithChinesePath(String folderPath, String zipPath) {
        try {
            // 使用GBK编码处理Windows中文路径
            ZipUtil.zip(new File(folderPath), zipPath, Charset.forName("GBK"));
        } catch (Exception e) {
            // 回退到UTF-8
            ZipUtil.zip(new File(folderPath), zipPath, StandardCharsets.UTF_8);
        }
    }
}

10.3 EncodingUtil - 编码检测

10.3.1 类定义

/**
 * 文件编码检测工具类
 */
public class EncodingUtil {
    
    /**
     * 自动检测文件编码
     * @param file 要检测的文件
     * @return 检测到的字符集
     */
    public static Charset getFileEncoding(File file);
}

10.3.2 基本使用

import com.znlgis.ogu4j.utils.EncodingUtil;
import java.io.File;
import java.nio.charset.Charset;

// 检测文件编码
File file = new File("D:/data/sample.txt");
Charset charset = EncodingUtil.getFileEncoding(file);
System.out.println("检测到编码: " + charset.name());

// 使用检测到的编码读取文件
String content = new String(Files.readAllBytes(file.toPath()), charset);

10.3.3 Shapefile编码检测

public class ShapefileEncodingHelper {
    
    /**
     * 获取Shapefile的DBF文件编码
     */
    public static Charset getDbfEncoding(String shpPath) {
        // 首先检查cpg文件
        String cpgPath = shpPath.replace(".shp", ".cpg");
        File cpgFile = new File(cpgPath);
        
        if (cpgFile.exists()) {
            try {
                String encoding = new String(Files.readAllBytes(cpgFile.toPath())).trim();
                return Charset.forName(encoding);
            } catch (Exception e) {
                // cpg文件无效,继续检测
            }
        }
        
        // 检测dbf文件编码
        String dbfPath = shpPath.replace(".shp", ".dbf");
        File dbfFile = new File(dbfPath);
        
        if (dbfFile.exists()) {
            return EncodingUtil.getFileEncoding(dbfFile);
        }
        
        // 默认返回UTF-8
        return StandardCharsets.UTF_8;
    }
    
    /**
     * 创建cpg文件
     */
    public static void createCpgFile(String shpPath, Charset charset) {
        String cpgPath = shpPath.replace(".shp", ".cpg");
        try {
            Files.write(Paths.get(cpgPath), charset.name().getBytes());
        } catch (IOException e) {
            throw new DataSourceException("创建cpg文件失败", e);
        }
    }
}

10.3.4 批量编码转换

public class EncodingConverter {
    
    /**
     * 转换文件编码
     */
    public static void convertEncoding(String inputPath, String outputPath,
            Charset sourceCharset, Charset targetCharset) throws IOException {
        
        byte[] bytes = Files.readAllBytes(Paths.get(inputPath));
        String content = new String(bytes, sourceCharset);
        Files.write(Paths.get(outputPath), content.getBytes(targetCharset));
    }
    
    /**
     * 批量转换目录下所有文本文件的编码
     */
    public static void batchConvert(String dir, Charset targetCharset) {
        File folder = new File(dir);
        File[] files = folder.listFiles((d, name) -> 
            name.endsWith(".txt") || name.endsWith(".csv"));
        
        if (files == null) return;
        
        for (File file : files) {
            Charset sourceCharset = EncodingUtil.getFileEncoding(file);
            
            if (!sourceCharset.equals(targetCharset)) {
                try {
                    String content = new String(
                        Files.readAllBytes(file.toPath()), sourceCharset);
                    Files.write(file.toPath(), content.getBytes(targetCharset));
                    System.out.printf("已转换: %s (%s -> %s)%n",
                        file.getName(), sourceCharset.name(), targetCharset.name());
                } catch (IOException e) {
                    System.err.printf("转换失败: %s - %s%n", file.getName(), e.getMessage());
                }
            }
        }
    }
}

10.4 SortUtil - 自然排序

10.4.1 类定义

/**
 * 自然排序工具类
 * 正确处理包含数字的字符串排序
 */
public class SortUtil {
    
    /**
     * 比较两个字符串(自然排序)
     * @return 负数表示s1<s2,0表示相等,正数表示s1>s2
     */
    public static int compareString(String s1, String s2);
}

10.4.2 问题背景

普通字符串排序的问题:

// 普通排序
List<String> files = Arrays.asList(
    "第1章", "第10章", "第2章", "第20章", "第3章");
Collections.sort(files);
// 结果: [第1章, 第10章, 第2章, 第20章, 第3章]  // 错误!

// 自然排序
files.sort(SortUtil::compareString);
// 结果: [第1章, 第2章, 第3章, 第10章, 第20章]  // 正确!

10.4.3 基本使用

import com.znlgis.ogu4j.utils.SortUtil;

// 比较两个字符串
int result = SortUtil.compareString("第5章", "第10章");
System.out.println(result);  // 负数,因为5 < 10

// 排序列表
List<String> items = Arrays.asList(
    "文件1", "文件10", "文件2", "文件100", "文件20");
items.sort(SortUtil::compareString);
// 结果: [文件1, 文件2, 文件10, 文件20, 文件100]

// 排序文件名
List<String> fileNames = Arrays.asList(
    "layer_1.shp", "layer_10.shp", "layer_2.shp", "layer_3.shp");
fileNames.sort(SortUtil::compareString);
// 结果: [layer_1.shp, layer_2.shp, layer_3.shp, layer_10.shp]

10.4.4 对象排序

public class FeatureSorter {
    
    /**
     * 按名称自然排序要素
     */
    public static void sortByName(List<OguFeature> features, String fieldName) {
        features.sort((f1, f2) -> {
            String name1 = Optional.ofNullable(f1.getValue(fieldName))
                .map(Object::toString).orElse("");
            String name2 = Optional.ofNullable(f2.getValue(fieldName))
                .map(Object::toString).orElse("");
            return SortUtil.compareString(name1, name2);
        });
    }
    
    /**
     * 按FID自然排序
     */
    public static void sortByFid(List<OguFeature> features) {
        features.sort((f1, f2) -> SortUtil.compareString(f1.getFid(), f2.getFid()));
    }
}

// 使用示例
List<OguFeature> features = layer.getFeatures();
FeatureSorter.sortByName(features, "NAME");
FeatureSorter.sortByFid(features);

10.4.5 自定义比较器

public class NaturalOrderComparator implements Comparator<String> {
    
    @Override
    public int compare(String s1, String s2) {
        return SortUtil.compareString(s1, s2);
    }
}

// 使用
List<String> items = new ArrayList<>(Arrays.asList("a10", "a2", "a1"));
items.sort(new NaturalOrderComparator());

// 或者使用TreeSet
TreeSet<String> sortedSet = new TreeSet<>(new NaturalOrderComparator());
sortedSet.addAll(items);

10.5 NumUtil - 数字格式化

10.5.1 类定义

/**
 * 数字格式化工具类
 */
public class NumUtil {
    
    /**
     * 去除科学计数法显示
     * @param value 数值
     * @return 普通数字字符串
     */
    public static String getPlainString(double value);
}

10.5.2 问题背景

Java默认会用科学计数法显示大数或小数:

double large = 12340000000.0;
System.out.println(large);  // 1.234E10

double small = 0.00000123;
System.out.println(small);  // 1.23E-6

10.5.3 基本使用

import com.znlgis.ogu4j.utils.NumUtil;

// 大数
double large = 12340000000.0;
String result = NumUtil.getPlainString(large);
System.out.println(result);  // 12340000000

// 小数
double small = 0.00000123;
String result2 = NumUtil.getPlainString(small);
System.out.println(result2);  // 0.00000123

// 坐标值
double x = 500000.123456789;
System.out.println(NumUtil.getPlainString(x));  // 500000.123456789

10.5.4 坐标格式化

public class CoordinateFormatter {
    
    /**
     * 格式化WKT中的坐标值
     */
    public static String formatWktCoordinates(String wkt, int decimals) {
        // 简化示例:格式化WKT中的数字
        Pattern pattern = Pattern.compile("-?\\d+\\.\\d+");
        Matcher matcher = pattern.matcher(wkt);
        StringBuffer sb = new StringBuffer();
        
        while (matcher.find()) {
            double value = Double.parseDouble(matcher.group());
            String formatted = String.format("%." + decimals + "f", value);
            matcher.appendReplacement(sb, formatted);
        }
        matcher.appendTail(sb);
        
        return sb.toString();
    }
    
    /**
     * 格式化坐标
     */
    public static String formatCoordinate(double x, double y, int decimals) {
        String format = "%." + decimals + "f";
        return String.format("(%s, %s)", 
            String.format(format, x),
            String.format(format, y));
    }
}

10.6 GdalCmdUtil - GDAL命令行工具

10.6.1 类定义

/**
 * GDAL命令行工具封装
 */
public class GdalCmdUtil {
    
    /**
     * 获取FileGDB的数据结构
     * @param gdbPath GDB文件路径
     * @return GDB数据结构模型
     */
    public static GdbGroupModel getGdbDataStructure(String gdbPath);
}

10.6.2 获取GDB结构

import com.znlgis.ogu4j.engine.util.GdalCmdUtil;
import com.znlgis.ogu4j.engine.model.GdbGroupModel;

// 获取GDB数据结构
GdbGroupModel structure = GdalCmdUtil.getGdbDataStructure("D:/data/sample.gdb");

// 遍历要素数据集
System.out.println("=== 要素数据集 ===");
for (String dataset : structure.getFeatureDatasets()) {
    System.out.println("  " + dataset);
}

// 遍历图层
System.out.println("=== 图层 ===");
for (String layer : structure.getLayers()) {
    System.out.println("  " + layer);
}

10.6.3 GdbGroupModel

/**
 * GDB数据结构模型
 */
@Data
public class GdbGroupModel {
    
    /**
     * 要素数据集列表
     */
    private List<String> featureDatasets;
    
    /**
     * 图层(要素类)列表
     */
    private List<String> layers;
    
    /**
     * 表列表
     */
    private List<String> tables;
}

10.7 ShpUtil - Shapefile工具

10.7.1 常用方法

/**
 * Shapefile工具类
 */
public class ShpUtil {
    
    /**
     * 获取Shapefile的字段列表
     */
    public static List<OguField> getFields(String shpPath);
    
    /**
     * 获取Shapefile的范围
     */
    public static Envelope getBounds(String shpPath);
    
    /**
     * 获取Shapefile的要素数量
     */
    public static int getFeatureCount(String shpPath);
    
    /**
     * 创建空的Shapefile
     */
    public static void createEmptyShapefile(String shpPath, 
            SimpleFeatureType featureType);
}

10.7.2 使用示例

import com.znlgis.ogu4j.engine.util.ShpUtil;

// 获取字段信息
List<OguField> fields = ShpUtil.getFields("D:/data/cities.shp");
for (OguField field : fields) {
    System.out.printf("%s (%s)%n", field.getName(), field.getDataType());
}

// 获取范围
Envelope bounds = ShpUtil.getBounds("D:/data/cities.shp");
System.out.printf("范围: (%.4f, %.4f) - (%.4f, %.4f)%n",
    bounds.getMinX(), bounds.getMinY(),
    bounds.getMaxX(), bounds.getMaxY());

// 获取要素数量
int count = ShpUtil.getFeatureCount("D:/data/cities.shp");
System.out.println("要素数量: " + count);

10.8 PostgisUtil - PostGIS工具

10.8.1 常用方法

/**
 * PostGIS工具类
 */
public class PostgisUtil {
    
    /**
     * 解析连接字符串
     */
    public static DbConnBaseModel parseConnectionString(String connStr);
    
    /**
     * 构建连接字符串
     */
    public static String buildConnectionString(String host, int port,
            String database, String user, String password, String schema);
    
    /**
     * 测试连接
     */
    public static boolean testConnection(DbConnBaseModel conn);
}

10.8.2 使用示例

import com.znlgis.ogu4j.engine.util.PostgisUtil;
import com.znlgis.ogu4j.engine.model.DbConnBaseModel;

// 解析连接字符串
String connStr = "PG: host=localhost port=5432 dbname=gisdb " +
                 "user=postgres password=123456 active_schema=public";
DbConnBaseModel conn = PostgisUtil.parseConnectionString(connStr);

System.out.println("Host: " + conn.getHost());
System.out.println("Port: " + conn.getPort());
System.out.println("Database: " + conn.getDatabase());
System.out.println("User: " + conn.getUser());
System.out.println("Schema: " + conn.getSchema());

// 构建连接字符串
String newConnStr = PostgisUtil.buildConnectionString(
    "192.168.1.100", 5432, "production", "admin", "secret", "gis");
System.out.println(newConnStr);

// 测试连接
if (PostgisUtil.testConnection(conn)) {
    System.out.println("连接成功");
} else {
    System.out.println("连接失败");
}

10.8.3 DbConnBaseModel

/**
 * 数据库连接模型
 */
@Data
public class DbConnBaseModel {
    
    private String host;
    private int port;
    private String database;
    private String user;
    private String password;
    private String schema;
}

10.9 综合案例

10.9.1 数据打包下载服务

public class DataPackageService {
    
    /**
     * 打包指定图层供下载
     */
    public String packageForDownload(String shpPath, String outputDir) {
        // 1. 创建临时目录
        String tempDir = System.getProperty("java.io.tmpdir") + 
            "/download_" + System.currentTimeMillis();
        new File(tempDir).mkdirs();
        
        try {
            // 2. 读取图层
            OguLayer layer = OguLayerUtil.readLayer(
                DataFormatType.SHP,
                shpPath,
                null, null, null,
                GisEngineType.GEOTOOLS
            );
            
            // 3. 写入临时目录
            String tempShp = tempDir + "/" + layer.getName() + ".shp";
            OguLayerUtil.writeLayer(
                DataFormatType.SHP,
                layer,
                tempShp,
                null, null,
                GisEngineType.GEOTOOLS
            );
            
            // 4. 创建cpg文件(确保中文正确)
            String cpgPath = tempShp.replace(".shp", ".cpg");
            Files.write(Paths.get(cpgPath), "UTF-8".getBytes());
            
            // 5. 压缩
            String zipPath = outputDir + "/" + layer.getName() + ".zip";
            ZipUtil.zip(new File(tempDir), zipPath, StandardCharsets.UTF_8);
            
            return zipPath;
            
        } finally {
            // 6. 清理临时目录
            deleteDirectory(new File(tempDir));
        }
    }
}

10.9.2 批量数据处理

public class BatchProcessor {
    
    /**
     * 批量处理目录下的所有Shapefile
     */
    public void processDirectory(String inputDir, String outputDir) {
        File dir = new File(inputDir);
        File[] shpFiles = dir.listFiles((d, name) -> name.endsWith(".shp"));
        
        if (shpFiles == null || shpFiles.length == 0) {
            System.out.println("未找到Shapefile");
            return;
        }
        
        // 按自然顺序处理
        Arrays.sort(shpFiles, (f1, f2) -> 
            SortUtil.compareString(f1.getName(), f2.getName()));
        
        new File(outputDir).mkdirs();
        
        int success = 0;
        int failed = 0;
        
        for (File shpFile : shpFiles) {
            System.out.println("处理: " + shpFile.getName());
            
            try {
                // 检测编码
                Charset charset = EncodingUtil.getFileEncoding(
                    new File(shpFile.getPath().replace(".shp", ".dbf")));
                System.out.println("  编码: " + charset.name());
                
                // 读取
                OguLayer layer = OguLayerUtil.readLayer(
                    DataFormatType.SHP,
                    shpFile.getAbsolutePath(),
                    null, null, null,
                    GisEngineType.GEOTOOLS
                );
                
                // 处理...
                processLayer(layer);
                
                // 保存
                String outputPath = outputDir + "/" + 
                    shpFile.getName().replace(".shp", ".geojson");
                OguLayerUtil.writeLayer(
                    DataFormatType.GEOJSON,
                    layer,
                    outputPath,
                    null, null,
                    GisEngineType.GEOTOOLS
                );
                
                success++;
                
            } catch (Exception e) {
                System.err.println("  失败: " + e.getMessage());
                failed++;
            }
        }
        
        System.out.printf("%n处理完成: 成功 %d, 失败 %d%n", success, failed);
    }
    
    private void processLayer(OguLayer layer) {
        // 自定义处理逻辑
    }
}

← 上一章:异常处理体系 | 下一章:开发实战案例 →

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