第12章 - 扩展开发指南
第12章 - 扩展开发指南
12.1 扩展点概述
OGU4J提供了多个扩展点,允许开发者根据需求进行定制:
| 扩展点 | 扩展方式 | 典型场景 |
|---|---|---|
| 新GIS引擎 | 实现GisEngine接口 | 支持新的GIS库 |
| 新数据格式 | 扩展Reader/Writer | 支持新的文件格式 |
| 新几何操作 | 扩展GeometryUtil | 添加新的空间分析功能 |
| 新坐标系 | 扩展CrsUtil | 支持自定义坐标系 |
| 新异常类型 | 继承OguException | 业务特定异常 |
12.2 添加新的GIS引擎
12.2.1 实现步骤
- 实现
GisEngine接口 - 实现
LayerReader接口 - 实现
LayerWriter接口 - 在
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 扩展步骤
- 在
DataFormatType中添加新格式 - 在相应引擎的Reader/Writer中实现处理逻辑
- 编写格式解析和生成代码
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 扩展开发原则
- 遵循接口契约:实现所有接口方法
- 保持向后兼容:不修改现有公共API
- 完善错误处理:使用合适的异常类型
- 添加单元测试:覆盖主要功能
- 更新文档:说明新增功能
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();
}
}

浙公网安备 33010602011771号