第12章 - 扩展开发指南

第12章 - 扩展开发指南

12.1 源码结构分析

12.1.1 包结构

com.esri.core.geometry
├── 核心几何类
│   ├── Geometry.java              # 几何抽象基类
│   ├── Point.java                 # 点
│   ├── MultiPoint.java            # 多点
│   ├── Polyline.java              # 折线
│   ├── Polygon.java               # 多边形
│   ├── Envelope.java              # 包围盒
│   ├── Line.java                  # 线段
│   └── Segment.java               # 线段基类
├── 内部实现类
│   ├── MultiVertexGeometryImpl.java    # 多顶点实现
│   ├── MultiPathImpl.java              # 多路径实现
│   └── VertexDescriptionDesignerImpl.java # 顶点描述
├── 算子类
│   ├── Operator.java              # 算子基类
│   ├── OperatorFactory.java       # 算子工厂接口
│   ├── OperatorFactoryLocal.java  # 本地算子工厂
│   └── Operator*.java             # 各种算子
├── 工具类
│   ├── GeometryEngine.java        # 便捷 API
│   ├── NumberUtils.java           # 数值工具
│   ├── Point2D.java               # 2D 坐标
│   ├── Point3D.java               # 3D 坐标
│   └── Envelope2D.java            # 2D 包围盒
├── 空间索引
│   ├── QuadTree.java              # 四叉树
│   ├── QuadTreeImpl.java          # 四叉树实现
│   ├── Envelope2DIntersector.java # 包围盒相交器
│   └── IntervalTree.java          # 区间树
├── 格式处理
│   ├── OperatorImport*.java       # 导入算子
│   ├── OperatorExport*.java       # 导出算子
│   ├── JsonReader.java            # JSON 读取
│   └── WktParser.java             # WKT 解析
└── ogc                            # OGC 兼容类
    ├── OGCGeometry.java
    └── OGC*.java

12.1.2 核心设计模式

1. 工厂模式 (Factory Pattern)

// 算子工厂
public abstract class OperatorFactory {
    public abstract Operator getOperator(Operator.Type type);
    public abstract boolean isOperatorSupported(Operator.Type type);
}

// 本地实现
public class OperatorFactoryLocal extends OperatorFactory {
    private static final OperatorFactoryLocal INSTANCE = new OperatorFactoryLocal();
    private static final HashMap<Type, Operator> st_supportedOperators;
    
    static {
        st_supportedOperators.put(Type.Buffer, new OperatorBufferLocal());
        // ...
    }
    
    public static OperatorFactoryLocal getInstance() {
        return INSTANCE;
    }
}

2. 迭代器模式 (Iterator Pattern)

// GeometryCursor 作为迭代器
public abstract class GeometryCursor {
    public abstract Geometry next();
    public abstract int getGeometryID();
}

// 使用示例
GeometryCursor cursor = operator.execute(input, sr, params);
Geometry geom;
while ((geom = cursor.next()) != null) {
    // 处理几何
}

3. 模板方法模式 (Template Method)

// Operator 基类定义模板
public abstract class Operator {
    public abstract Type getType();
    
    // 可选的加速方法
    public boolean accelerateGeometry(...) { return false; }
    public boolean canAccelerateGeometry(...) { return false; }
}

// 具体实现
public class OperatorContainsLocal extends OperatorContains {
    @Override
    public boolean execute(...) {
        // 具体实现
    }
    
    @Override
    public boolean accelerateGeometry(...) {
        // 实现加速
    }
}

4. 策略模式 (Strategy Pattern)

// 不同的简化策略
public class OperatorSimplify extends Operator { ... }
public class OperatorSimplifyOGC extends Operator { ... }
public class OperatorGeneralize extends Operator { ... }

12.1.3 内存管理

// 几何对象的内存估算
public abstract class Geometry {
    public abstract long estimateMemorySize();
}

// 实现示例(Point)
@Override
public long estimateMemorySize() {
    return SIZE_OF_POINT + estimateMemorySize(m_attributes);
}

// 尺寸常量定义
public class SizeOf {
    public static final int SIZE_OF_POINT = 40;
    public static final int SIZE_OF_ENVELOPE = 80;
    public static final int SIZE_OF_POLYLINE = 24;
    public static final int SIZE_OF_POLYGON = 24;
}

12.2 自定义算子开发

12.2.1 算子开发步骤

  1. 继承 Operator 基类
  2. 实现抽象方法
  3. OperatorFactoryLocal 中注册
  4. 提供便捷的静态方法

12.2.2 示例:实现中心线提取算子

/**
 * 中心线提取算子
 */
public abstract class OperatorCenterLine extends Operator {
    
    @Override
    public Type getType() {
        return Type.CenterLine; // 需要在 Type 枚举中添加
    }
    
    /**
     * 从多边形提取中心线
     */
    public abstract Polyline execute(Polygon polygon, 
            SpatialReference sr, ProgressTracker progress);
    
    /**
     * 批量处理
     */
    public abstract GeometryCursor execute(GeometryCursor polygons,
            SpatialReference sr, ProgressTracker progress);
    
    /**
     * 获取本地实例
     */
    public static OperatorCenterLine local() {
        return (OperatorCenterLine) OperatorFactoryLocal.getInstance()
            .getOperator(Type.CenterLine);
    }
}

/**
 * 中心线提取算子本地实现
 */
public class OperatorCenterLineLocal extends OperatorCenterLine {
    
    @Override
    public Polyline execute(Polygon polygon, SpatialReference sr, 
            ProgressTracker progress) {
        
        if (polygon.isEmpty()) {
            return new Polyline();
        }
        
        // 简化实现:使用骨架化算法
        // 实际实现需要更复杂的算法
        
        // 1. 获取多边形边界
        Polyline boundary = (Polyline) polygon.getBoundary();
        
        // 2. 计算 Voronoi 图或使用其他算法
        // ... 算法实现 ...
        
        // 3. 返回中心线
        return computeCenterLine(polygon, sr);
    }
    
    @Override
    public GeometryCursor execute(GeometryCursor polygons, 
            SpatialReference sr, ProgressTracker progress) {
        return new CenterLineCursor(polygons, sr, progress);
    }
    
    private Polyline computeCenterLine(Polygon polygon, SpatialReference sr) {
        // 简化实现:连接质心
        Polyline result = new Polyline();
        
        // 获取各环的质心
        for (int i = 0; i < polygon.getPathCount(); i++) {
            if (polygon.isExteriorRing(i)) {
                Point2D centroid = computeRingCentroid(polygon, i);
                if (result.isEmpty()) {
                    result.startPath(centroid.x, centroid.y);
                } else {
                    result.lineTo(centroid.x, centroid.y);
                }
            }
        }
        
        return result;
    }
    
    private Point2D computeRingCentroid(Polygon polygon, int ringIndex) {
        // 计算环的质心
        int start = polygon.getPathStart(ringIndex);
        int end = polygon.getPathEnd(ringIndex);
        
        double sumX = 0, sumY = 0;
        for (int i = start; i < end; i++) {
            Point2D pt = polygon.getXY(i);
            sumX += pt.x;
            sumY += pt.y;
        }
        
        int count = end - start;
        return new Point2D(sumX / count, sumY / count);
    }
    
    /**
     * 中心线游标
     */
    private class CenterLineCursor extends GeometryCursor {
        private GeometryCursor inputCursor;
        private SpatialReference sr;
        private ProgressTracker progress;
        
        public CenterLineCursor(GeometryCursor input, SpatialReference sr,
                ProgressTracker progress) {
            this.inputCursor = input;
            this.sr = sr;
            this.progress = progress;
        }
        
        @Override
        public Geometry next() {
            Geometry geom = inputCursor.next();
            if (geom == null) {
                return null;
            }
            
            if (!(geom instanceof Polygon)) {
                return new Polyline(); // 非多边形返回空
            }
            
            return execute((Polygon) geom, sr, progress);
        }
        
        @Override
        public int getGeometryID() {
            return inputCursor.getGeometryID();
        }
    }
}

12.2.3 注册自定义算子

// 方式一:修改 OperatorFactoryLocal(需要修改源码)
static {
    st_supportedOperators.put(Type.CenterLine, new OperatorCenterLineLocal());
}

// 方式二:创建自定义工厂
public class CustomOperatorFactory extends OperatorFactoryLocal {
    private static final CustomOperatorFactory INSTANCE = new CustomOperatorFactory();
    
    private final Map<Operator.Type, Operator> customOperators = new HashMap<>();
    
    public CustomOperatorFactory() {
        customOperators.put(Type.CenterLine, new OperatorCenterLineLocal());
    }
    
    public static CustomOperatorFactory getInstance() {
        return INSTANCE;
    }
    
    @Override
    public Operator getOperator(Operator.Type type) {
        if (customOperators.containsKey(type)) {
            return customOperators.get(type);
        }
        return super.getOperator(type);
    }
}

12.3 自定义几何类型

12.3.1 扩展 Geometry 类

/**
 * 圆形几何类型
 */
public class Circle extends Geometry {
    
    private Point center;
    private double radius;
    private VertexDescription description;
    
    public Circle() {
        this.description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D();
        this.center = new Point();
        this.radius = 0;
    }
    
    public Circle(Point center, double radius) {
        this();
        this.center = (Point) center.copy();
        this.radius = radius;
    }
    
    public Circle(double x, double y, double radius) {
        this(new Point(x, y), radius);
    }
    
    // Getters/Setters
    public Point getCenter() { return center; }
    public double getRadius() { return radius; }
    
    public void setCenter(Point center) {
        this.center = (Point) center.copy();
    }
    
    public void setRadius(double radius) {
        this.radius = radius;
    }
    
    // 实现抽象方法
    @Override
    public Type getType() {
        return Type.Unknown; // 自定义类型
    }
    
    @Override
    public int getDimension() {
        return 2; // 面状
    }
    
    @Override
    public boolean isEmpty() {
        return center.isEmpty() || radius <= 0;
    }
    
    @Override
    public void setEmpty() {
        center.setEmpty();
        radius = 0;
    }
    
    @Override
    public void queryEnvelope(Envelope env) {
        if (isEmpty()) {
            env.setEmpty();
            return;
        }
        env.setCoords(
            center.getX() - radius, center.getY() - radius,
            center.getX() + radius, center.getY() + radius);
    }
    
    @Override
    public void queryEnvelope2D(Envelope2D env) {
        if (isEmpty()) {
            env.setEmpty();
            return;
        }
        env.setCoords(
            center.getX() - radius, center.getY() - radius,
            center.getX() + radius, center.getY() + radius);
    }
    
    @Override
    void queryEnvelope3D(Envelope3D env) {
        if (isEmpty()) {
            env.setEmpty();
            return;
        }
        double z = center.hasZ() ? center.getZ() : 0;
        env.setCoords(
            center.getX() - radius, center.getY() - radius, z,
            center.getX() + radius, center.getY() + radius, z);
    }
    
    @Override
    public Envelope1D queryInterval(int semantics, int ordinate) {
        Envelope1D env = new Envelope1D();
        if (isEmpty()) {
            env.setEmpty();
            return env;
        }
        
        if (semantics == VertexDescription.Semantics.POSITION) {
            if (ordinate == 0) {
                env.setCoords(center.getX() - radius, center.getX() + radius);
            } else {
                env.setCoords(center.getY() - radius, center.getY() + radius);
            }
        } else {
            double val = center.getAttributeAsDbl(semantics, ordinate);
            env.setCoords(val, val);
        }
        return env;
    }
    
    @Override
    public Geometry createInstance() {
        return new Circle();
    }
    
    @Override
    public void copyTo(Geometry dst) {
        if (!(dst instanceof Circle)) {
            throw new IllegalArgumentException();
        }
        Circle other = (Circle) dst;
        other.center = (Point) center.copy();
        other.radius = radius;
        other.description = description;
    }
    
    @Override
    public long estimateMemorySize() {
        return 64 + center.estimateMemorySize();
    }
    
    @Override
    public Geometry getBoundary() {
        // 圆的边界是圆周
        return toPolygon(64).getBoundary();
    }
    
    @Override
    public void applyTransformation(Transformation2D transform) {
        center.applyTransformation(transform);
        // 注意:缩放会影响半径
    }
    
    @Override
    protected void _assignVertexDescriptionImpl(VertexDescription newDescription) {
        center.assignVertexDescription(newDescription);
        this.description = newDescription;
    }
    
    // 自定义方法
    
    /**
     * 转换为多边形(近似)
     */
    public Polygon toPolygon(int numPoints) {
        Polygon polygon = new Polygon();
        
        if (isEmpty()) {
            return polygon;
        }
        
        double angleStep = 2 * Math.PI / numPoints;
        
        polygon.startPath(
            center.getX() + radius,
            center.getY());
        
        for (int i = 1; i < numPoints; i++) {
            double angle = i * angleStep;
            polygon.lineTo(
                center.getX() + radius * Math.cos(angle),
                center.getY() + radius * Math.sin(angle));
        }
        
        polygon.closePathWithLine();
        return polygon;
    }
    
    /**
     * 计算面积
     */
    public double calculateArea() {
        return Math.PI * radius * radius;
    }
    
    /**
     * 计算周长
     */
    public double calculatePerimeter() {
        return 2 * Math.PI * radius;
    }
    
    /**
     * 判断点是否在圆内
     */
    public boolean contains(Point point) {
        double dx = point.getX() - center.getX();
        double dy = point.getY() - center.getY();
        return dx * dx + dy * dy <= radius * radius;
    }
    
    /**
     * 判断两圆是否相交
     */
    public boolean intersects(Circle other) {
        double dx = other.center.getX() - center.getX();
        double dy = other.center.getY() - center.getY();
        double dist = Math.sqrt(dx * dx + dy * dy);
        return dist <= radius + other.radius;
    }
}

12.4 格式扩展

12.4.1 自定义格式导入

/**
 * 自定义格式导入器(示例:简化的 CSV 格式)
 */
public class OperatorImportFromCSV {
    
    /**
     * 从 CSV 导入点几何
     * 格式: id,x,y[,z][,m]
     */
    public static List<Point> importPoints(String csv) {
        List<Point> points = new ArrayList<>();
        
        String[] lines = csv.split("\n");
        for (String line : lines) {
            if (line.trim().isEmpty() || line.startsWith("#")) {
                continue;
            }
            
            String[] parts = line.split(",");
            if (parts.length < 3) {
                continue;
            }
            
            try {
                double x = Double.parseDouble(parts[1].trim());
                double y = Double.parseDouble(parts[2].trim());
                
                Point point;
                if (parts.length >= 4) {
                    double z = Double.parseDouble(parts[3].trim());
                    point = new Point(x, y, z);
                } else {
                    point = new Point(x, y);
                }
                
                points.add(point);
            } catch (NumberFormatException e) {
                // 跳过格式错误的行
            }
        }
        
        return points;
    }
    
    /**
     * 从 CSV 导入折线
     * 格式: path_id,point_seq,x,y
     */
    public static Polyline importPolyline(String csv) {
        Polyline polyline = new Polyline();
        
        String[] lines = csv.split("\n");
        int currentPath = -1;
        
        for (String line : lines) {
            if (line.trim().isEmpty() || line.startsWith("#")) {
                continue;
            }
            
            String[] parts = line.split(",");
            if (parts.length < 4) {
                continue;
            }
            
            try {
                int pathId = Integer.parseInt(parts[0].trim());
                double x = Double.parseDouble(parts[2].trim());
                double y = Double.parseDouble(parts[3].trim());
                
                if (pathId != currentPath) {
                    polyline.startPath(x, y);
                    currentPath = pathId;
                } else {
                    polyline.lineTo(x, y);
                }
            } catch (NumberFormatException e) {
                // 跳过
            }
        }
        
        return polyline;
    }
}

12.4.2 自定义格式导出

/**
 * 自定义格式导出器
 */
public class OperatorExportToCSV {
    
    /**
     * 导出点为 CSV
     */
    public static String exportPoints(List<Point> points, boolean includeZ) {
        StringBuilder sb = new StringBuilder();
        sb.append("# id,x,y");
        if (includeZ) {
            sb.append(",z");
        }
        sb.append("\n");
        
        for (int i = 0; i < points.size(); i++) {
            Point p = points.get(i);
            sb.append(i).append(",");
            sb.append(p.getX()).append(",");
            sb.append(p.getY());
            if (includeZ && p.hasZ()) {
                sb.append(",").append(p.getZ());
            }
            sb.append("\n");
        }
        
        return sb.toString();
    }
    
    /**
     * 导出多边形为 CSV(边界点)
     */
    public static String exportPolygon(Polygon polygon) {
        StringBuilder sb = new StringBuilder();
        sb.append("# ring_id,point_seq,x,y\n");
        
        for (int ring = 0; ring < polygon.getPathCount(); ring++) {
            int start = polygon.getPathStart(ring);
            int end = polygon.getPathEnd(ring);
            
            for (int i = start; i < end; i++) {
                Point2D pt = polygon.getXY(i);
                sb.append(ring).append(",");
                sb.append(i - start).append(",");
                sb.append(pt.x).append(",");
                sb.append(pt.y).append("\n");
            }
        }
        
        return sb.toString();
    }
}

12.5 性能监控扩展

12.5.1 操作统计

/**
 * 几何操作统计器
 */
public class GeometryOperationStats {
    
    private static final GeometryOperationStats INSTANCE = new GeometryOperationStats();
    
    private final Map<String, AtomicLong> operationCounts = new ConcurrentHashMap<>();
    private final Map<String, AtomicLong> operationTimes = new ConcurrentHashMap<>();
    
    public static GeometryOperationStats getInstance() {
        return INSTANCE;
    }
    
    /**
     * 记录操作
     */
    public void record(String operation, long durationNanos) {
        operationCounts.computeIfAbsent(operation, k -> new AtomicLong())
            .incrementAndGet();
        operationTimes.computeIfAbsent(operation, k -> new AtomicLong())
            .addAndGet(durationNanos);
    }
    
    /**
     * 获取统计报告
     */
    public String getReport() {
        StringBuilder sb = new StringBuilder();
        sb.append("几何操作统计报告\n");
        sb.append("================\n");
        
        for (String op : operationCounts.keySet()) {
            long count = operationCounts.get(op).get();
            long totalTime = operationTimes.get(op).get();
            double avgTime = count > 0 ? totalTime / (double) count / 1_000_000 : 0;
            
            sb.append(String.format("%s: 调用 %d 次, 平均 %.2f ms%n",
                op, count, avgTime));
        }
        
        return sb.toString();
    }
    
    /**
     * 重置统计
     */
    public void reset() {
        operationCounts.clear();
        operationTimes.clear();
    }
}

/**
 * 带统计的几何引擎
 */
public class InstrumentedGeometryEngine {
    
    private static final GeometryOperationStats stats = 
        GeometryOperationStats.getInstance();
    
    public static Polygon buffer(Geometry geometry, SpatialReference sr, 
            double distance) {
        long start = System.nanoTime();
        try {
            return GeometryEngine.buffer(geometry, sr, distance);
        } finally {
            stats.record("buffer", System.nanoTime() - start);
        }
    }
    
    public static boolean contains(Geometry container, Geometry contained,
            SpatialReference sr) {
        long start = System.nanoTime();
        try {
            return GeometryEngine.contains(container, contained, sr);
        } finally {
            stats.record("contains", System.nanoTime() - start);
        }
    }
    
    // ... 其他方法类似
}

12.6 与其他库集成

12.6.1 与 JTS 互操作

/**
 * geometry-api-java 与 JTS 互操作工具
 */
public class JtsInterop {
    
    private static final GeometryFactory jtsFactory = new GeometryFactory();
    
    /**
     * ESRI Geometry → JTS Geometry
     */
    public static org.locationtech.jts.geom.Geometry toJts(Geometry esriGeom) {
        String wkt = GeometryEngine.geometryToWkt(esriGeom, 0);
        WKTReader reader = new WKTReader(jtsFactory);
        try {
            return reader.read(wkt);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * JTS Geometry → ESRI Geometry
     */
    public static Geometry fromJts(org.locationtech.jts.geom.Geometry jtsGeom) {
        WKTWriter writer = new WKTWriter();
        String wkt = writer.write(jtsGeom);
        return GeometryEngine.geometryFromWkt(wkt, 0, Geometry.Type.Unknown);
    }
    
    /**
     * 使用 JTS 进行复杂操作
     */
    public static Geometry jtsOperation(Geometry esriGeom, 
            Function<org.locationtech.jts.geom.Geometry, 
                     org.locationtech.jts.geom.Geometry> operation) {
        org.locationtech.jts.geom.Geometry jtsGeom = toJts(esriGeom);
        org.locationtech.jts.geom.Geometry result = operation.apply(jtsGeom);
        return fromJts(result);
    }
}

// 使用示例
Polygon esriPolygon = createPolygon();

// 使用 JTS 计算 Voronoi 图
Geometry voronoi = JtsInterop.jtsOperation(esriPolygon, jtsGeom -> {
    VoronoiDiagramBuilder builder = new VoronoiDiagramBuilder();
    builder.setSites(jtsGeom);
    return builder.getDiagram(jtsFactory);
});

12.6.2 与 GeoTools 集成

/**
 * GeoTools 集成工具
 */
public class GeoToolsIntegration {
    
    /**
     * 从 Shapefile 读取几何
     */
    public static List<Geometry> readShapefile(File shpFile) throws Exception {
        List<Geometry> geometries = new ArrayList<>();
        
        FileDataStore store = FileDataStoreFinder.getDataStore(shpFile);
        SimpleFeatureCollection collection = store.getFeatureSource().getFeatures();
        
        try (SimpleFeatureIterator iterator = collection.features()) {
            while (iterator.hasNext()) {
                SimpleFeature feature = iterator.next();
                org.locationtech.jts.geom.Geometry jtsGeom = 
                    (org.locationtech.jts.geom.Geometry) feature.getDefaultGeometry();
                
                Geometry esriGeom = JtsInterop.fromJts(jtsGeom);
                geometries.add(esriGeom);
            }
        }
        
        store.dispose();
        return geometries;
    }
    
    /**
     * 写入 Shapefile
     */
    public static void writeShapefile(File shpFile, List<Geometry> geometries,
            int srid) throws Exception {
        // GeoTools Shapefile 写入实现
        // ...
    }
}

12.7 本章小结

本章介绍了 geometry-api-java 的扩展开发:

  1. 源码结构:包组织和核心设计模式
  2. 自定义算子:开发和注册新的空间操作
  3. 自定义几何:扩展几何类型(如 Circle)
  4. 格式扩展:自定义导入/导出格式
  5. 性能监控:操作统计和性能分析
  6. 库集成:与 JTS、GeoTools 互操作

扩展开发要点

  • 遵循现有的设计模式
  • 保持 API 风格一致性
  • 注意线程安全和内存管理
  • 提供完整的单元测试

← 上一章:开发实战案例 | 返回目录 →

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