第12章 - 扩展开发指南

第12章 - 扩展开发指南

12.1 扩展点概述

OGU4J提供了多个扩展点,允许开发者根据需求进行定制:

扩展点 扩展方式 典型场景
新GIS引擎 实现GisEngine接口 支持新的GIS库
新数据格式 扩展Reader/Writer 支持新的文件格式
新几何操作 扩展GeometryUtil 添加新的空间分析功能
新坐标系 扩展CrsUtil 支持自定义坐标系
新异常类型 继承OguException 业务特定异常

12.2 添加新的GIS引擎

12.2.1 实现步骤

  1. 实现GisEngine接口
  2. 实现LayerReader接口
  3. 实现LayerWriter接口
  4. GisEngineFactory中注册

12.2.2 引擎接口

/**
 * GIS引擎接口
 */
public interface GisEngine {
    
    /**
     * 获取图层读取器
     */
    LayerReader getReader();
    
    /**
     * 获取图层写入器
     */
    LayerWriter getWriter();
    
    /**
     * 获取引擎类型
     */
    GisEngineType getEngineType();
    
    /**
     * 检查引擎是否可用
     */
    boolean isAvailable();
}

12.2.3 示例:MapBox引擎

假设我们要添加一个基于MapBox Vector Tile的引擎:

/**
 * MapBox引擎类型
 */
public enum GisEngineType {
    GEOTOOLS,
    GDAL,
    MAPBOX  // 新增
}

/**
 * MapBox引擎实现
 */
public class MapBoxEngine implements GisEngine {
    
    private final MapBoxLayerReader reader;
    private final MapBoxLayerWriter writer;
    private boolean available;
    
    public MapBoxEngine() {
        this.available = checkDependencies();
        if (available) {
            this.reader = new MapBoxLayerReader();
            this.writer = new MapBoxLayerWriter();
        } else {
            this.reader = null;
            this.writer = null;
        }
    }
    
    private boolean checkDependencies() {
        try {
            // 检查MapBox依赖是否可用
            Class.forName("com.mapbox.geojson.GeoJson");
            return true;
        } catch (ClassNotFoundException e) {
            return false;
        }
    }
    
    @Override
    public LayerReader getReader() {
        checkAvailable();
        return reader;
    }
    
    @Override
    public LayerWriter getWriter() {
        checkAvailable();
        return writer;
    }
    
    @Override
    public GisEngineType getEngineType() {
        return GisEngineType.MAPBOX;
    }
    
    @Override
    public boolean isAvailable() {
        return available;
    }
    
    private void checkAvailable() {
        if (!available) {
            throw new EngineNotSupportedException("MapBox引擎不可用");
        }
    }
}

12.2.4 实现Reader

/**
 * MapBox图层读取器
 */
public class MapBoxLayerReader implements LayerReader {
    
    @Override
    public OguLayer read(DataFormatType format, String path,
            String layerName, String attrFilter, String spatialFilter) {
        
        switch (format) {
            case MVT:  // 假设添加了MVT格式
                return readMvt(path, layerName);
            case MBTILES:
                return readMbTiles(path, layerName);
            default:
                throw new EngineNotSupportedException(
                    "MapBox引擎不支持格式: " + format);
        }
    }
    
    private OguLayer readMvt(String path, String layerName) {
        // MVT读取实现
        OguLayer layer = new OguLayer();
        // ... 读取逻辑
        return layer;
    }
    
    private OguLayer readMbTiles(String path, String layerName) {
        // MBTiles读取实现
        OguLayer layer = new OguLayer();
        // ... 读取逻辑
        return layer;
    }
}

12.2.5 注册引擎

/**
 * 扩展后的工厂类
 */
public class GisEngineFactory {
    
    private static final Map<GisEngineType, GisEngine> engines = 
        new ConcurrentHashMap<>();
    
    public static GisEngine getEngine(GisEngineType engineType) {
        return engines.computeIfAbsent(engineType, type -> {
            switch (type) {
                case GEOTOOLS:
                    return new GeoToolsEngine();
                case GDAL:
                    return new GdalEngine();
                case MAPBOX:  // 新增
                    return new MapBoxEngine();
                default:
                    throw new EngineNotSupportedException("未知引擎: " + type);
            }
        });
    }
}

12.3 添加新的数据格式

12.3.1 扩展步骤

  1. DataFormatType中添加新格式
  2. 在相应引擎的Reader/Writer中实现处理逻辑
  3. 编写格式解析和生成代码

12.3.2 示例:添加KML格式支持

/**
 * 扩展格式枚举
 */
public enum DataFormatType {
    SHP, GEOJSON, FILEGDB, POSTGIS,
    KML  // 新增
}

/**
 * KML读取实现(在GeoToolsLayerReader中)
 */
public class GeoToolsLayerReader implements LayerReader {
    
    @Override
    public OguLayer read(DataFormatType format, String path,
            String layerName, String attrFilter, String spatialFilter) {
        
        switch (format) {
            case SHP:
                return readShapefile(path, attrFilter, spatialFilter);
            case GEOJSON:
                return readGeoJson(path, attrFilter, spatialFilter);
            case KML:  // 新增
                return readKml(path);
            default:
                throw new EngineNotSupportedException("不支持: " + format);
        }
    }
    
    /**
     * 读取KML文件
     */
    private OguLayer readKml(String path) {
        OguLayer layer = new OguLayer();
        layer.setName("kml_layer");
        layer.setWkid(4326);  // KML使用WGS84
        
        try {
            // 使用DOM解析KML
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document doc = builder.parse(new File(path));
            
            // 解析Placemarks
            NodeList placemarks = doc.getElementsByTagName("Placemark");
            List<OguFeature> features = new ArrayList<>();
            
            for (int i = 0; i < placemarks.getLength(); i++) {
                Element placemark = (Element) placemarks.item(i);
                OguFeature feature = parseKmlPlacemark(placemark, i);
                if (feature != null) {
                    features.add(feature);
                }
            }
            
            layer.setFeatures(features);
            
            // 推断字段
            if (!features.isEmpty()) {
                layer.setFields(inferFields(features));
                layer.setGeometryType(inferGeometryType(features));
            }
            
            return layer;
            
        } catch (Exception e) {
            throw new DataSourceException("读取KML失败: " + e.getMessage(), e);
        }
    }
    
    /**
     * 解析KML Placemark
     */
    private OguFeature parseKmlPlacemark(Element placemark, int index) {
        OguFeature feature = new OguFeature();
        feature.setFid(String.valueOf(index + 1));
        
        // 获取名称
        NodeList names = placemark.getElementsByTagName("name");
        if (names.getLength() > 0) {
            feature.setValue("name", names.item(0).getTextContent());
        }
        
        // 获取描述
        NodeList descriptions = placemark.getElementsByTagName("description");
        if (descriptions.getLength() > 0) {
            feature.setValue("description", descriptions.item(0).getTextContent());
        }
        
        // 解析几何
        String wkt = parseKmlGeometry(placemark);
        if (wkt != null) {
            feature.setWkt(wkt);
        }
        
        return feature;
    }
    
    /**
     * 解析KML几何
     */
    private String parseKmlGeometry(Element placemark) {
        // 点
        NodeList points = placemark.getElementsByTagName("Point");
        if (points.getLength() > 0) {
            Element point = (Element) points.item(0);
            String coords = point.getElementsByTagName("coordinates")
                .item(0).getTextContent().trim();
            String[] parts = coords.split(",");
            return String.format("POINT(%s %s)", parts[0], parts[1]);
        }
        
        // 线
        NodeList lines = placemark.getElementsByTagName("LineString");
        if (lines.getLength() > 0) {
            Element line = (Element) lines.item(0);
            String coords = line.getElementsByTagName("coordinates")
                .item(0).getTextContent().trim();
            return "LINESTRING(" + convertKmlCoords(coords) + ")";
        }
        
        // 面
        NodeList polygons = placemark.getElementsByTagName("Polygon");
        if (polygons.getLength() > 0) {
            Element polygon = (Element) polygons.item(0);
            NodeList outer = polygon.getElementsByTagName("outerBoundaryIs");
            if (outer.getLength() > 0) {
                String coords = ((Element) outer.item(0))
                    .getElementsByTagName("coordinates")
                    .item(0).getTextContent().trim();
                return "POLYGON((" + convertKmlCoords(coords) + "))";
            }
        }
        
        return null;
    }
    
    /**
     * 转换KML坐标格式到WKT格式
     */
    private String convertKmlCoords(String kmlCoords) {
        StringBuilder sb = new StringBuilder();
        String[] coordPairs = kmlCoords.split("\\s+");
        
        for (int i = 0; i < coordPairs.length; i++) {
            if (i > 0) sb.append(", ");
            String[] parts = coordPairs[i].split(",");
            sb.append(parts[0]).append(" ").append(parts[1]);
        }
        
        return sb.toString();
    }
}

12.4 扩展几何操作

12.4.1 添加新方法

/**
 * 扩展GeometryUtil
 */
public class GeometryUtilExtended {
    
    /**
     * 计算几何的紧凑度
     * 紧凑度 = 4π * 面积 / 周长²
     * 圆形的紧凑度为1,越接近1说明形状越接近圆形
     */
    public static double compactness(Geometry geom) {
        if (geom == null || geom.isEmpty()) {
            return 0;
        }
        
        double area = GeometryUtil.area(geom);
        double perimeter = GeometryUtil.length(geom);
        
        if (perimeter == 0) {
            return 0;
        }
        
        return (4 * Math.PI * area) / (perimeter * perimeter);
    }
    
    /**
     * 计算几何的伸长度
     * 基于最小外接矩形
     */
    public static double elongation(Geometry geom) {
        if (geom == null || geom.isEmpty()) {
            return 0;
        }
        
        // 获取最小外接矩形
        Geometry mbr = new MinimumBoundingCircle(geom).getDiameter();
        Envelope env = geom.getEnvelopeInternal();
        
        double width = env.getWidth();
        double height = env.getHeight();
        
        if (width == 0 || height == 0) {
            return 0;
        }
        
        return Math.max(width, height) / Math.min(width, height);
    }
    
    /**
     * 计算两个几何之间的Hausdorff距离
     */
    public static double hausdorffDistance(Geometry g1, Geometry g2) {
        DiscreteHausdorffDistance hausdorff = 
            new DiscreteHausdorffDistance(g1, g2);
        return hausdorff.distance();
    }
    
    /**
     * 计算几何的凹陷程度
     * 凹陷程度 = 1 - (面积 / 凸包面积)
     */
    public static double concavity(Geometry geom) {
        if (geom == null || geom.isEmpty()) {
            return 0;
        }
        
        double area = GeometryUtil.area(geom);
        Geometry convexHull = GeometryUtil.convexHull(geom);
        double hullArea = GeometryUtil.area(convexHull);
        
        if (hullArea == 0) {
            return 0;
        }
        
        return 1 - (area / hullArea);
    }
    
    /**
     * 沿线等距采样点
     */
    public static List<Point> samplePointsAlongLine(Geometry line, double interval) {
        List<Point> points = new ArrayList<>();
        
        if (line == null || !(line instanceof LineString)) {
            return points;
        }
        
        GeometryFactory factory = new GeometryFactory();
        LengthIndexedLine indexedLine = new LengthIndexedLine(line);
        double length = line.getLength();
        
        for (double d = 0; d <= length; d += interval) {
            Coordinate coord = indexedLine.extractPoint(d);
            points.add(factory.createPoint(coord));
        }
        
        return points;
    }
    
    /**
     * 骨架化(中轴提取)
     */
    public static Geometry skeletonize(Geometry polygon) {
        if (polygon == null || !(polygon instanceof Polygon)) {
            return null;
        }
        
        // 使用Voronoi图方法
        VoronoiDiagramBuilder builder = new VoronoiDiagramBuilder();
        builder.setSites(polygon);
        Geometry voronoi = builder.getDiagram(polygon.getFactory());
        
        // 裁剪到原多边形内部
        return voronoi.intersection(polygon);
    }
}

12.4.2 使用示例

public class ExtendedGeometryDemo {
    
    public static void main(String[] args) {
        String wkt = "POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))";
        Geometry geom = GeometryUtil.wkt2Geometry(wkt);
        
        // 使用扩展方法
        double compactness = GeometryUtilExtended.compactness(geom);
        System.out.println("紧凑度: " + compactness);  // 正方形约0.785
        
        double elongation = GeometryUtilExtended.elongation(geom);
        System.out.println("伸长度: " + elongation);  // 正方形为1
        
        double concavity = GeometryUtilExtended.concavity(geom);
        System.out.println("凹陷度: " + concavity);  // 凸多边形为0
    }
}

12.5 自定义坐标系支持

12.5.1 添加自定义WKID

/**
 * 自定义坐标系注册器
 */
public class CustomCrsRegistry {
    
    private static final Map<Integer, CoordinateReferenceSystem> customCRS = 
        new ConcurrentHashMap<>();
    
    /**
     * 注册自定义坐标系
     */
    public static void register(int wkid, String wkt) {
        try {
            CoordinateReferenceSystem crs = CRS.parseWKT(wkt);
            customCRS.put(wkid, crs);
        } catch (Exception e) {
            throw new OguException("注册坐标系失败: " + e.getMessage(), e);
        }
    }
    
    /**
     * 获取自定义坐标系
     */
    public static CoordinateReferenceSystem get(int wkid) {
        return customCRS.get(wkid);
    }
    
    /**
     * 检查是否存在
     */
    public static boolean exists(int wkid) {
        return customCRS.containsKey(wkid);
    }
    
    /**
     * 初始化常用自定义坐标系
     */
    public static void initCommonCRS() {
        // 北京54坐标系示例
        register(2401, 
            "PROJCS[\"Beijing 1954 / 3-degree Gauss-Kruger zone 25\"," +
            "GEOGCS[\"Beijing 1954\",...]," +
            "...]");
        
        // 西安80坐标系示例
        register(2349,
            "PROJCS[\"Xian 1980 / 3-degree Gauss-Kruger zone 25\"," +
            "GEOGCS[\"Xian 1980\",...]," +
            "...]");
    }
}

/**
 * 扩展CrsUtil以支持自定义坐标系
 */
public class CrsUtilExtended {
    
    public static CoordinateReferenceSystem getCRS(int wkid) {
        // 首先检查自定义坐标系
        if (CustomCrsRegistry.exists(wkid)) {
            return CustomCrsRegistry.get(wkid);
        }
        
        // 然后使用默认方法
        return CrsUtil.getCRS(wkid);
    }
}

12.5.2 使用示例

public class CustomCrsDemo {
    
    public static void main(String[] args) {
        // 注册自定义坐标系
        String customWkt = "PROJCS[\"Custom Projection\"," +
            "GEOGCS[\"GCS_Custom\"," +
            "DATUM[\"D_Custom\",SPHEROID[\"Custom\",6378137,298.257223563]]," +
            "PRIMEM[\"Greenwich\",0]," +
            "UNIT[\"Degree\",0.0174532925199433]]," +
            "PROJECTION[\"Transverse_Mercator\"]," +
            "PARAMETER[\"False_Easting\",500000]," +
            "PARAMETER[\"False_Northing\",0]," +
            "PARAMETER[\"Central_Meridian\",117]," +
            "PARAMETER[\"Scale_Factor\",1]," +
            "PARAMETER[\"Latitude_Of_Origin\",0]," +
            "UNIT[\"Meter\",1]]";
        
        CustomCrsRegistry.register(99999, customWkt);
        
        // 使用自定义坐标系
        CoordinateReferenceSystem crs = CrsUtilExtended.getCRS(99999);
        System.out.println("坐标系名称: " + crs.getName());
    }
}

12.6 自定义异常

12.6.1 业务异常

/**
 * 业务异常基类
 */
public class GisBusinessException extends OguException {
    
    private final ErrorCode errorCode;
    
    public GisBusinessException(ErrorCode errorCode, String message) {
        super(message);
        this.errorCode = errorCode;
    }
    
    public GisBusinessException(ErrorCode errorCode, String message, Throwable cause) {
        super(message, cause);
        this.errorCode = errorCode;
    }
    
    public ErrorCode getErrorCode() {
        return errorCode;
    }
    
    public enum ErrorCode {
        DATA_NOT_FOUND(1001, "数据未找到"),
        INVALID_GEOMETRY(1002, "无效几何"),
        COORDINATE_MISMATCH(1003, "坐标系不匹配"),
        PERMISSION_DENIED(1004, "权限不足"),
        QUOTA_EXCEEDED(1005, "配额超限");
        
        private final int code;
        private final String message;
        
        ErrorCode(int code, String message) {
            this.code = code;
            this.message = message;
        }
        
        public int getCode() { return code; }
        public String getMessage() { return message; }
    }
}

/**
 * 坐标系不匹配异常
 */
public class CoordinateMismatchException extends GisBusinessException {
    
    private final Integer sourceWkid;
    private final Integer targetWkid;
    
    public CoordinateMismatchException(Integer sourceWkid, Integer targetWkid) {
        super(ErrorCode.COORDINATE_MISMATCH, 
            String.format("坐标系不匹配: %d != %d", sourceWkid, targetWkid));
        this.sourceWkid = sourceWkid;
        this.targetWkid = targetWkid;
    }
    
    public Integer getSourceWkid() { return sourceWkid; }
    public Integer getTargetWkid() { return targetWkid; }
}

/**
 * 使用示例
 */
public class ExceptionDemo {
    
    public void processLayers(OguLayer layer1, OguLayer layer2) {
        if (!Objects.equals(layer1.getWkid(), layer2.getWkid())) {
            throw new CoordinateMismatchException(layer1.getWkid(), layer2.getWkid());
        }
        
        // 处理逻辑...
    }
}

12.7 插件机制设计

12.7.1 插件接口

/**
 * OGU4J插件接口
 */
public interface OguPlugin {
    
    /**
     * 插件名称
     */
    String getName();
    
    /**
     * 插件版本
     */
    String getVersion();
    
    /**
     * 初始化插件
     */
    void initialize();
    
    /**
     * 销毁插件
     */
    void destroy();
}

/**
 * 引擎插件接口
 */
public interface EnginePlugin extends OguPlugin {
    
    /**
     * 获取支持的引擎类型
     */
    GisEngineType getEngineType();
    
    /**
     * 创建引擎实例
     */
    GisEngine createEngine();
}

/**
 * 格式插件接口
 */
public interface FormatPlugin extends OguPlugin {
    
    /**
     * 获取支持的格式类型
     */
    DataFormatType getFormatType();
    
    /**
     * 获取文件扩展名
     */
    String getFileExtension();
    
    /**
     * 创建读取器
     */
    LayerReader createReader();
    
    /**
     * 创建写入器
     */
    LayerWriter createWriter();
}

12.7.2 插件管理器

/**
 * 插件管理器
 */
public class PluginManager {
    
    private static final PluginManager INSTANCE = new PluginManager();
    private final Map<String, OguPlugin> plugins = new ConcurrentHashMap<>();
    
    public static PluginManager getInstance() {
        return INSTANCE;
    }
    
    /**
     * 注册插件
     */
    public void register(OguPlugin plugin) {
        plugin.initialize();
        plugins.put(plugin.getName(), plugin);
        
        // 如果是引擎插件,注册到工厂
        if (plugin instanceof EnginePlugin) {
            EnginePlugin ep = (EnginePlugin) plugin;
            // 注册逻辑...
        }
        
        // 如果是格式插件,注册格式支持
        if (plugin instanceof FormatPlugin) {
            FormatPlugin fp = (FormatPlugin) plugin;
            // 注册逻辑...
        }
    }
    
    /**
     * 卸载插件
     */
    public void unregister(String name) {
        OguPlugin plugin = plugins.remove(name);
        if (plugin != null) {
            plugin.destroy();
        }
    }
    
    /**
     * 获取所有插件
     */
    public Collection<OguPlugin> getAllPlugins() {
        return plugins.values();
    }
    
    /**
     * 通过SPI加载插件
     */
    public void loadPlugins() {
        ServiceLoader<OguPlugin> loader = ServiceLoader.load(OguPlugin.class);
        for (OguPlugin plugin : loader) {
            register(plugin);
        }
    }
}

12.7.3 示例插件实现

/**
 * GeoPackage格式插件
 */
public class GeoPackagePlugin implements FormatPlugin {
    
    @Override
    public String getName() {
        return "GeoPackage";
    }
    
    @Override
    public String getVersion() {
        return "1.0.0";
    }
    
    @Override
    public void initialize() {
        System.out.println("GeoPackage插件已加载");
    }
    
    @Override
    public void destroy() {
        System.out.println("GeoPackage插件已卸载");
    }
    
    @Override
    public DataFormatType getFormatType() {
        return DataFormatType.GPKG;  // 需要添加枚举值
    }
    
    @Override
    public String getFileExtension() {
        return ".gpkg";
    }
    
    @Override
    public LayerReader createReader() {
        return new GeoPackageReader();
    }
    
    @Override
    public LayerWriter createWriter() {
        return new GeoPackageWriter();
    }
}

12.8 最佳实践

12.8.1 扩展开发原则

  1. 遵循接口契约:实现所有接口方法
  2. 保持向后兼容:不修改现有公共API
  3. 完善错误处理:使用合适的异常类型
  4. 添加单元测试:覆盖主要功能
  5. 更新文档:说明新增功能

12.8.2 性能考虑

// 使用缓存避免重复创建
private static final Map<Integer, MathTransform> transformCache = 
    new ConcurrentHashMap<>();

// 使用对象池
private static final ObjectPool<GeometryFactory> factoryPool = 
    new GenericObjectPool<>(new GeometryFactoryFactory());

// 批量处理时复用对象
public void batchProcess(List<OguFeature> features) {
    GeometryFactory factory = factoryPool.borrowObject();
    try {
        for (OguFeature feature : features) {
            // 使用同一个factory
            processFeature(feature, factory);
        }
    } finally {
        factoryPool.returnObject(factory);
    }
}

12.8.3 线程安全

// 使用线程安全的集合
private final ConcurrentHashMap<String, Geometry> cache = 
    new ConcurrentHashMap<>();

// 使用读写锁
private final ReadWriteLock lock = new ReentrantReadWriteLock();

public Geometry getFromCache(String key) {
    lock.readLock().lock();
    try {
        return cache.get(key);
    } finally {
        lock.readLock().unlock();
    }
}

public void putToCache(String key, Geometry geom) {
    lock.writeLock().lock();
    try {
        cache.put(key, geom);
    } finally {
        lock.writeLock().unlock();
    }
}

← 上一章:开发实战案例 | 返回首页 →

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