第11章 - 开发实战案例

第11章 - 开发实战案例

11.1 案例一:地理围栏服务

11.1.1 需求描述

实现一个地理围栏服务,用于:

  • 判断用户位置是否在指定区域内
  • 支持多个围栏区域的快速查询
  • 记录进入/离开围栏的事件

11.1.2 核心实现

/**
 * 地理围栏服务
 */
public class GeofenceService {
    
    private final Map<String, Polygon> fences = new ConcurrentHashMap<>();
    private final Map<String, Boolean> userStates = new ConcurrentHashMap<>();
    private final SpatialReference sr;
    private final QuadTree spatialIndex;
    private final Envelope2D worldExtent;
    
    public GeofenceService() {
        this.sr = SpatialReference.create(4326);
        this.worldExtent = new Envelope2D();
        this.worldExtent.setCoords(-180, -90, 180, 90);
        this.spatialIndex = new QuadTree(worldExtent, 8);
    }
    
    /**
     * 添加围栏
     */
    public void addFence(String fenceId, Polygon polygon) {
        fences.put(fenceId, polygon);
        
        // 添加到空间索引
        Envelope2D env = new Envelope2D();
        polygon.queryEnvelope2D(env);
        spatialIndex.insert(fences.size() - 1, env);
        
        // 加速几何
        OperatorContains.local().accelerateGeometry(
            polygon, sr, Geometry.GeometryAccelerationDegree.enumMedium);
    }
    
    /**
     * 添加围栏(从 GeoJSON)
     */
    public void addFenceFromGeoJson(String fenceId, String geoJson) {
        MapGeometry mg = GeometryEngine.geoJsonToGeometry(
            geoJson, 0, Geometry.Type.Polygon);
        Polygon polygon = (Polygon) mg.getGeometry();
        addFence(fenceId, polygon);
    }
    
    /**
     * 检查用户位置并触发事件
     */
    public GeofenceEvent checkLocation(String userId, double longitude, 
            double latitude) {
        
        Point userLocation = new Point(longitude, latitude);
        
        // 查找包含用户位置的围栏
        String currentFence = null;
        Envelope2D pointEnv = new Envelope2D();
        userLocation.queryEnvelope2D(pointEnv);
        
        // 使用空间索引快速过滤
        QuadTree.QuadTreeIterator iter = spatialIndex.getIterator(pointEnv, 0);
        int handle;
        while ((handle = iter.next()) != -1) {
            int fenceIndex = spatialIndex.getElement(handle);
            String fenceId = (String) fences.keySet().toArray()[fenceIndex];
            Polygon fence = fences.get(fenceId);
            
            if (GeometryEngine.contains(fence, userLocation, sr)) {
                currentFence = fenceId;
                break;
            }
        }
        
        // 获取用户之前的状态
        String stateKey = userId + "_currentFence";
        String previousFence = userStates.containsKey(stateKey) ? 
            userStates.get(stateKey).toString() : null;
        
        // 判断事件类型
        GeofenceEvent event = null;
        
        if (previousFence == null && currentFence != null) {
            // 进入围栏
            event = new GeofenceEvent(userId, currentFence, 
                GeofenceEventType.ENTER, longitude, latitude);
        } else if (previousFence != null && currentFence == null) {
            // 离开围栏
            event = new GeofenceEvent(userId, previousFence, 
                GeofenceEventType.EXIT, longitude, latitude);
        } else if (previousFence != null && currentFence != null && 
                   !previousFence.equals(currentFence)) {
            // 从一个围栏到另一个围栏
            event = new GeofenceEvent(userId, currentFence, 
                GeofenceEventType.TRANSITION, longitude, latitude);
        }
        
        // 更新状态
        if (currentFence != null) {
            userStates.put(stateKey, true);
        } else {
            userStates.remove(stateKey);
        }
        
        return event;
    }
    
    /**
     * 移除围栏
     */
    public void removeFence(String fenceId) {
        Polygon polygon = fences.remove(fenceId);
        if (polygon != null) {
            Operator.deaccelerateGeometry(polygon);
        }
        // 注意:简化实现中未从 QuadTree 移除
    }
    
    // 内部类
    public enum GeofenceEventType {
        ENTER, EXIT, TRANSITION
    }
    
    public static class GeofenceEvent {
        public final String userId;
        public final String fenceId;
        public final GeofenceEventType type;
        public final double longitude;
        public final double latitude;
        public final long timestamp;
        
        public GeofenceEvent(String userId, String fenceId, 
                GeofenceEventType type, double longitude, double latitude) {
            this.userId = userId;
            this.fenceId = fenceId;
            this.type = type;
            this.longitude = longitude;
            this.latitude = latitude;
            this.timestamp = System.currentTimeMillis();
        }
    }
}

11.1.3 使用示例

public class GeofenceExample {
    
    public static void main(String[] args) {
        GeofenceService service = new GeofenceService();
        
        // 添加办公区围栏
        String officeGeoJson = "{\"type\":\"Polygon\",\"coordinates\":[" +
            "[[116.39,39.90],[116.41,39.90],[116.41,39.92],[116.39,39.92],[116.39,39.90]]]}";
        service.addFenceFromGeoJson("office", officeGeoJson);
        
        // 添加家庭区围栏
        String homeGeoJson = "{\"type\":\"Polygon\",\"coordinates\":[" +
            "[[116.30,39.95],[116.32,39.95],[116.32,39.97],[116.30,39.97],[116.30,39.95]]]}";
        service.addFenceFromGeoJson("home", homeGeoJson);
        
        // 模拟用户位置更新
        GeofenceService.GeofenceEvent event;
        
        // 用户进入办公区
        event = service.checkLocation("user001", 116.40, 39.91);
        if (event != null) {
            System.out.println("事件: " + event.type + " - " + event.fenceId);
        }
        
        // 用户移动到办公区外
        event = service.checkLocation("user001", 116.35, 39.88);
        if (event != null) {
            System.out.println("事件: " + event.type + " - " + event.fenceId);
        }
    }
}

11.2 案例二:空间数据分析服务

11.2.1 需求描述

实现一个 REST API 空间数据分析服务,支持:

  • 缓冲区分析
  • 叠加分析(交集、合并、差集)
  • 距离计算
  • 空间关系判断

11.2.2 核心实现

/**
 * 空间分析服务
 */
@RestController
@RequestMapping("/api/spatial")
public class SpatialAnalysisController {
    
    private final SpatialReference sr = SpatialReference.create(4326);
    
    /**
     * 缓冲区分析
     */
    @PostMapping("/buffer")
    public ResponseEntity<String> buffer(
            @RequestBody BufferRequest request) {
        
        try {
            Geometry geometry = parseGeometry(request.getGeometry());
            Polygon buffer = GeometryEngine.buffer(geometry, sr, request.getDistance());
            String result = GeometryEngine.geometryToGeoJson(sr, buffer);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            return ResponseEntity.badRequest().body(e.getMessage());
        }
    }
    
    /**
     * 交集分析
     */
    @PostMapping("/intersection")
    public ResponseEntity<String> intersection(
            @RequestBody OverlayRequest request) {
        
        try {
            Geometry g1 = parseGeometry(request.getGeometry1());
            Geometry g2 = parseGeometry(request.getGeometry2());
            Geometry result = GeometryEngine.intersect(g1, g2, sr);
            return ResponseEntity.ok(GeometryEngine.geometryToGeoJson(sr, result));
        } catch (Exception e) {
            return ResponseEntity.badRequest().body(e.getMessage());
        }
    }
    
    /**
     * 合并分析
     */
    @PostMapping("/union")
    public ResponseEntity<String> union(
            @RequestBody UnionRequest request) {
        
        try {
            List<Geometry> geometries = new ArrayList<>();
            for (String geomStr : request.getGeometries()) {
                geometries.add(parseGeometry(geomStr));
            }
            Geometry[] geomArray = geometries.toArray(new Geometry[0]);
            Geometry result = GeometryEngine.union(geomArray, sr);
            return ResponseEntity.ok(GeometryEngine.geometryToGeoJson(sr, result));
        } catch (Exception e) {
            return ResponseEntity.badRequest().body(e.getMessage());
        }
    }
    
    /**
     * 差集分析
     */
    @PostMapping("/difference")
    public ResponseEntity<String> difference(
            @RequestBody OverlayRequest request) {
        
        try {
            Geometry g1 = parseGeometry(request.getGeometry1());
            Geometry g2 = parseGeometry(request.getGeometry2());
            Geometry result = GeometryEngine.difference(g1, g2, sr);
            return ResponseEntity.ok(GeometryEngine.geometryToGeoJson(sr, result));
        } catch (Exception e) {
            return ResponseEntity.badRequest().body(e.getMessage());
        }
    }
    
    /**
     * 距离计算
     */
    @PostMapping("/distance")
    public ResponseEntity<DistanceResult> distance(
            @RequestBody DistanceRequest request) {
        
        try {
            Geometry g1 = parseGeometry(request.getGeometry1());
            Geometry g2 = parseGeometry(request.getGeometry2());
            
            // 平面距离(度)
            double planarDistance = GeometryEngine.distance(g1, g2, sr);
            
            // 测地距离(米)- 仅适用于点
            Double geodesicDistance = null;
            if (g1 instanceof Point && g2 instanceof Point) {
                geodesicDistance = GeometryEngine.geodesicDistanceOnWGS84(
                    (Point) g1, (Point) g2);
            }
            
            return ResponseEntity.ok(
                new DistanceResult(planarDistance, geodesicDistance));
        } catch (Exception e) {
            return ResponseEntity.badRequest().body(null);
        }
    }
    
    /**
     * 空间关系判断
     */
    @PostMapping("/relate")
    public ResponseEntity<RelationResult> relate(
            @RequestBody RelateRequest request) {
        
        try {
            Geometry g1 = parseGeometry(request.getGeometry1());
            Geometry g2 = parseGeometry(request.getGeometry2());
            
            RelationResult result = new RelationResult();
            result.setEquals(GeometryEngine.equals(g1, g2, sr));
            result.setDisjoint(GeometryEngine.disjoint(g1, g2, sr));
            result.setIntersects(!result.isDisjoint());
            result.setContains(GeometryEngine.contains(g1, g2, sr));
            result.setWithin(GeometryEngine.within(g1, g2, sr));
            result.setTouches(GeometryEngine.touches(g1, g2, sr));
            result.setOverlaps(GeometryEngine.overlaps(g1, g2, sr));
            result.setCrosses(GeometryEngine.crosses(g1, g2, sr));
            
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            return ResponseEntity.badRequest().body(null);
        }
    }
    
    /**
     * 凸包计算
     */
    @PostMapping("/convexhull")
    public ResponseEntity<String> convexHull(
            @RequestBody GeometryRequest request) {
        
        try {
            Geometry geometry = parseGeometry(request.getGeometry());
            Geometry hull = GeometryEngine.convexHull(geometry);
            return ResponseEntity.ok(GeometryEngine.geometryToGeoJson(sr, hull));
        } catch (Exception e) {
            return ResponseEntity.badRequest().body(e.getMessage());
        }
    }
    
    /**
     * 简化几何
     */
    @PostMapping("/simplify")
    public ResponseEntity<String> simplify(
            @RequestBody SimplifyRequest request) {
        
        try {
            Geometry geometry = parseGeometry(request.getGeometry());
            Geometry simplified;
            
            if (request.getTolerance() != null) {
                // 使用泛化
                simplified = OperatorGeneralize.local().execute(
                    geometry, request.getTolerance(), true, null);
            } else {
                // 使用简化
                simplified = GeometryEngine.simplify(geometry, sr);
            }
            
            return ResponseEntity.ok(GeometryEngine.geometryToGeoJson(sr, simplified));
        } catch (Exception e) {
            return ResponseEntity.badRequest().body(e.getMessage());
        }
    }
    
    // 辅助方法
    private Geometry parseGeometry(String geomStr) {
        // 尝试 GeoJSON
        if (geomStr.trim().startsWith("{")) {
            MapGeometry mg = GeometryEngine.geoJsonToGeometry(
                geomStr, 0, Geometry.Type.Unknown);
            return mg.getGeometry();
        }
        // 尝试 WKT
        return GeometryEngine.geometryFromWkt(geomStr, 0, Geometry.Type.Unknown);
    }
}

11.2.3 请求/响应类

// 请求类
public class BufferRequest {
    private String geometry;  // GeoJSON 或 WKT
    private double distance;
    // getters/setters
}

public class OverlayRequest {
    private String geometry1;
    private String geometry2;
    // getters/setters
}

public class UnionRequest {
    private List<String> geometries;
    // getters/setters
}

// 响应类
public class DistanceResult {
    private double planarDistance;
    private Double geodesicDistanceMeters;
    
    public DistanceResult(double planar, Double geodesic) {
        this.planarDistance = planar;
        this.geodesicDistanceMeters = geodesic;
    }
    // getters/setters
}

public class RelationResult {
    private boolean equals;
    private boolean disjoint;
    private boolean intersects;
    private boolean contains;
    private boolean within;
    private boolean touches;
    private boolean overlaps;
    private boolean crosses;
    // getters/setters
}

11.3 案例三:POI 空间查询

11.3.1 需求描述

实现一个 POI(兴趣点)空间查询服务:

  • 查询范围内的 POI
  • 查询最近的 POI
  • 按距离排序返回 POI

11.3.2 核心实现

/**
 * POI 空间查询服务
 */
public class POIQueryService {
    
    private final List<POI> pois = new ArrayList<>();
    private final QuadTree spatialIndex;
    private final SpatialReference sr;
    private final Envelope2D extent;
    
    public POIQueryService(double minX, double minY, double maxX, double maxY) {
        this.sr = SpatialReference.create(4326);
        this.extent = new Envelope2D();
        this.extent.setCoords(minX, minY, maxX, maxY);
        this.spatialIndex = new QuadTree(extent, 10);
    }
    
    /**
     * 添加 POI
     */
    public void addPOI(POI poi) {
        int index = pois.size();
        pois.add(poi);
        
        Envelope2D env = new Envelope2D();
        env.setCoords(poi.getLongitude(), poi.getLatitude(),
                      poi.getLongitude(), poi.getLatitude());
        spatialIndex.insert(index, env);
    }
    
    /**
     * 矩形范围查询
     */
    public List<POI> queryByBounds(double minX, double minY, 
            double maxX, double maxY) {
        
        Envelope2D queryEnv = new Envelope2D();
        queryEnv.setCoords(minX, minY, maxX, maxY);
        
        List<POI> results = new ArrayList<>();
        QuadTree.QuadTreeIterator iter = spatialIndex.getIterator(queryEnv, 0);
        
        int handle;
        while ((handle = iter.next()) != -1) {
            int index = spatialIndex.getElement(handle);
            results.add(pois.get(index));
        }
        
        return results;
    }
    
    /**
     * 圆形范围查询
     */
    public List<POI> queryByRadius(double centerLon, double centerLat, 
            double radiusMeters) {
        
        Point center = new Point(centerLon, centerLat);
        
        // 计算包围盒(粗略估算)
        double radiusDeg = radiusMeters / 111000.0;
        Envelope2D queryEnv = new Envelope2D();
        queryEnv.setCoords(centerLon - radiusDeg, centerLat - radiusDeg,
                          centerLon + radiusDeg, centerLat + radiusDeg);
        
        List<POI> results = new ArrayList<>();
        QuadTree.QuadTreeIterator iter = spatialIndex.getIterator(queryEnv, 0);
        
        int handle;
        while ((handle = iter.next()) != -1) {
            int index = spatialIndex.getElement(handle);
            POI poi = pois.get(index);
            
            // 精确距离检查
            Point poiPoint = new Point(poi.getLongitude(), poi.getLatitude());
            double distance = GeometryEngine.geodesicDistanceOnWGS84(center, poiPoint);
            
            if (distance <= radiusMeters) {
                poi.setDistance(distance);
                results.add(poi);
            }
        }
        
        // 按距离排序
        results.sort(Comparator.comparingDouble(POI::getDistance));
        
        return results;
    }
    
    /**
     * 多边形范围查询
     */
    public List<POI> queryByPolygon(Polygon polygon) {
        Envelope2D queryEnv = new Envelope2D();
        polygon.queryEnvelope2D(queryEnv);
        
        // 加速多边形
        OperatorContains.local().accelerateGeometry(
            polygon, sr, Geometry.GeometryAccelerationDegree.enumMedium);
        
        List<POI> results = new ArrayList<>();
        QuadTree.QuadTreeIterator iter = spatialIndex.getIterator(queryEnv, 0);
        
        int handle;
        while ((handle = iter.next()) != -1) {
            int index = spatialIndex.getElement(handle);
            POI poi = pois.get(index);
            
            Point poiPoint = new Point(poi.getLongitude(), poi.getLatitude());
            if (GeometryEngine.contains(polygon, poiPoint, sr)) {
                results.add(poi);
            }
        }
        
        Operator.deaccelerateGeometry(polygon);
        
        return results;
    }
    
    /**
     * 查询最近的 N 个 POI
     */
    public List<POI> queryNearest(double centerLon, double centerLat, int n) {
        Point center = new Point(centerLon, centerLat);
        
        // 使用优先队列保持 N 个最近的
        PriorityQueue<POI> heap = new PriorityQueue<>(
            Comparator.comparingDouble(POI::getDistance).reversed());
        
        for (POI poi : pois) {
            Point poiPoint = new Point(poi.getLongitude(), poi.getLatitude());
            double distance = GeometryEngine.geodesicDistanceOnWGS84(center, poiPoint);
            poi.setDistance(distance);
            
            heap.offer(poi);
            if (heap.size() > n) {
                heap.poll();
            }
        }
        
        List<POI> results = new ArrayList<>(heap);
        results.sort(Comparator.comparingDouble(POI::getDistance));
        
        return results;
    }
    
    // POI 类
    public static class POI {
        private String id;
        private String name;
        private String category;
        private double longitude;
        private double latitude;
        private transient double distance;
        
        // getters/setters
        public String getId() { return id; }
        public void setId(String id) { this.id = id; }
        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
        public String getCategory() { return category; }
        public void setCategory(String category) { this.category = category; }
        public double getLongitude() { return longitude; }
        public void setLongitude(double longitude) { this.longitude = longitude; }
        public double getLatitude() { return latitude; }
        public void setLatitude(double latitude) { this.latitude = latitude; }
        public double getDistance() { return distance; }
        public void setDistance(double distance) { this.distance = distance; }
    }
}

11.4 案例四:轨迹分析

11.4.1 需求描述

实现一个轨迹分析服务:

  • 计算轨迹长度
  • 检测停留点
  • 轨迹简化
  • 轨迹平滑

11.4.2 核心实现

/**
 * 轨迹分析服务
 */
public class TrajectoryAnalysisService {
    
    private final SpatialReference sr = SpatialReference.create(4326);
    
    /**
     * 轨迹点
     */
    public static class TrajectoryPoint {
        public double longitude;
        public double latitude;
        public long timestamp;
        public Double speed;
        
        public TrajectoryPoint(double lon, double lat, long ts) {
            this.longitude = lon;
            this.latitude = lat;
            this.timestamp = ts;
        }
    }
    
    /**
     * 创建轨迹折线
     */
    public Polyline createTrajectory(List<TrajectoryPoint> points) {
        Polyline trajectory = new Polyline();
        
        if (points.isEmpty()) {
            return trajectory;
        }
        
        TrajectoryPoint first = points.get(0);
        trajectory.startPath(first.longitude, first.latitude);
        
        for (int i = 1; i < points.size(); i++) {
            TrajectoryPoint p = points.get(i);
            trajectory.lineTo(p.longitude, p.latitude);
        }
        
        return trajectory;
    }
    
    /**
     * 计算轨迹长度(米)
     */
    public double calculateLength(Polyline trajectory) {
        OperatorGeodeticLength op = (OperatorGeodeticLength) OperatorFactoryLocal
            .getInstance().getOperator(Operator.Type.GeodeticLength);
        
        return op.execute(trajectory, sr, GeodeticCurveType.Geodesic, null);
    }
    
    /**
     * 检测停留点
     */
    public List<StayPoint> detectStayPoints(List<TrajectoryPoint> points,
            double radiusMeters, long minDurationMs) {
        
        List<StayPoint> stayPoints = new ArrayList<>();
        
        int i = 0;
        while (i < points.size()) {
            TrajectoryPoint anchor = points.get(i);
            Point anchorPoint = new Point(anchor.longitude, anchor.latitude);
            
            int j = i + 1;
            while (j < points.size()) {
                TrajectoryPoint current = points.get(j);
                Point currentPoint = new Point(current.longitude, current.latitude);
                
                double distance = GeometryEngine.geodesicDistanceOnWGS84(
                    anchorPoint, currentPoint);
                
                if (distance > radiusMeters) {
                    break;
                }
                j++;
            }
            
            // 检查停留时间
            if (j > i + 1) {
                long duration = points.get(j - 1).timestamp - anchor.timestamp;
                if (duration >= minDurationMs) {
                    // 计算停留区域中心
                    double sumLon = 0, sumLat = 0;
                    for (int k = i; k < j; k++) {
                        sumLon += points.get(k).longitude;
                        sumLat += points.get(k).latitude;
                    }
                    
                    StayPoint sp = new StayPoint();
                    sp.longitude = sumLon / (j - i);
                    sp.latitude = sumLat / (j - i);
                    sp.startTime = anchor.timestamp;
                    sp.endTime = points.get(j - 1).timestamp;
                    sp.pointCount = j - i;
                    stayPoints.add(sp);
                    
                    i = j;
                    continue;
                }
            }
            i++;
        }
        
        return stayPoints;
    }
    
    /**
     * 轨迹简化
     */
    public Polyline simplifyTrajectory(Polyline trajectory, double toleranceMeters) {
        // 转换容差(简化处理)
        double toleranceDeg = toleranceMeters / 111000.0;
        
        return (Polyline) OperatorGeneralize.local().execute(
            trajectory, toleranceDeg, true, null);
    }
    
    /**
     * 计算轨迹速度
     */
    public List<TrajectoryPoint> calculateSpeeds(List<TrajectoryPoint> points) {
        for (int i = 1; i < points.size(); i++) {
            TrajectoryPoint prev = points.get(i - 1);
            TrajectoryPoint curr = points.get(i);
            
            Point prevPoint = new Point(prev.longitude, prev.latitude);
            Point currPoint = new Point(curr.longitude, curr.latitude);
            
            double distance = GeometryEngine.geodesicDistanceOnWGS84(
                prevPoint, currPoint);
            double timeDiff = (curr.timestamp - prev.timestamp) / 1000.0;
            
            if (timeDiff > 0) {
                curr.speed = distance / timeDiff;  // 米/秒
            }
        }
        
        return points;
    }
    
    /**
     * 轨迹平滑(移动平均)
     */
    public List<TrajectoryPoint> smoothTrajectory(List<TrajectoryPoint> points, 
            int windowSize) {
        
        List<TrajectoryPoint> smoothed = new ArrayList<>();
        
        for (int i = 0; i < points.size(); i++) {
            int start = Math.max(0, i - windowSize / 2);
            int end = Math.min(points.size(), i + windowSize / 2 + 1);
            
            double sumLon = 0, sumLat = 0;
            for (int j = start; j < end; j++) {
                sumLon += points.get(j).longitude;
                sumLat += points.get(j).latitude;
            }
            
            int count = end - start;
            TrajectoryPoint sp = new TrajectoryPoint(
                sumLon / count, sumLat / count, points.get(i).timestamp);
            smoothed.add(sp);
        }
        
        return smoothed;
    }
    
    /**
     * 停留点
     */
    public static class StayPoint {
        public double longitude;
        public double latitude;
        public long startTime;
        public long endTime;
        public int pointCount;
        
        public long getDurationMs() {
            return endTime - startTime;
        }
    }
}

11.5 案例五:空间数据校验

11.5.1 需求描述

实现一个空间数据校验服务:

  • 几何有效性检查
  • 拓扑规则检查
  • 数据质量报告

11.5.2 核心实现

/**
 * 空间数据校验服务
 */
public class SpatialDataValidator {
    
    private final SpatialReference sr;
    
    public SpatialDataValidator(int srid) {
        this.sr = SpatialReference.create(srid);
    }
    
    /**
     * 校验几何
     */
    public ValidationReport validate(Geometry geometry) {
        ValidationReport report = new ValidationReport();
        
        // 基本检查
        validateBasic(geometry, report);
        
        // 几何有效性检查
        validateGeometry(geometry, report);
        
        // 特定类型检查
        if (geometry instanceof Polygon) {
            validatePolygon((Polygon) geometry, report);
        } else if (geometry instanceof Polyline) {
            validatePolyline((Polyline) geometry, report);
        }
        
        return report;
    }
    
    private void validateBasic(Geometry geometry, ValidationReport report) {
        // 检查是否为空
        if (geometry.isEmpty()) {
            report.addWarning("几何为空");
        }
        
        // 检查边界
        Envelope2D env = new Envelope2D();
        geometry.queryEnvelope2D(env);
        
        if (Double.isNaN(env.xmin) || Double.isNaN(env.xmax) ||
            Double.isNaN(env.ymin) || Double.isNaN(env.ymax)) {
            report.addError("包围盒包含 NaN 值");
        }
        
        // 检查坐标范围(假设 WGS84)
        if (sr.getID() == 4326) {
            if (env.xmin < -180 || env.xmax > 180) {
                report.addError("经度超出范围 [-180, 180]");
            }
            if (env.ymin < -90 || env.ymax > 90) {
                report.addError("纬度超出范围 [-90, 90]");
            }
        }
    }
    
    private void validateGeometry(Geometry geometry, ValidationReport report) {
        // 使用 ESRI 规则检查
        NonSimpleResult result = new NonSimpleResult();
        boolean isSimple = OperatorSimplify.local().isSimpleAsFeature(
            geometry, sr, true, result, null);
        
        if (!isSimple) {
            String reason = describeNonSimpleReason(result.m_reason);
            report.addError("几何不简单: " + reason);
        }
        
        // 使用 OGC 规则检查
        boolean isSimpleOGC = OperatorSimplifyOGC.local().isSimpleOGC(
            geometry, sr, true, null, null);
        
        if (!isSimpleOGC) {
            report.addWarning("几何不符合 OGC Simple Feature 规范");
        }
    }
    
    private void validatePolygon(Polygon polygon, ValidationReport report) {
        // 检查面积
        double area = polygon.calculateArea2D();
        if (area == 0) {
            report.addError("多边形面积为零");
        } else if (area < 0) {
            report.addWarning("多边形面积为负(顶点顺序可能反向)");
        }
        
        // 检查环数
        if (polygon.getPathCount() == 0) {
            report.addError("多边形没有环");
        }
        
        // 检查外环方向
        for (int i = 0; i < polygon.getPathCount(); i++) {
            double ringArea = polygon.calculateRingArea2D(i);
            boolean isExterior = polygon.isExteriorRing(i);
            
            if (isExterior && ringArea < 0) {
                report.addWarning("外环 " + i + " 方向应为逆时针");
            } else if (!isExterior && ringArea > 0) {
                report.addWarning("内环 " + i + " 方向应为顺时针");
            }
        }
        
        // 检查自相交
        // 使用简化操作检测
        try {
            Geometry simplified = OperatorSimplifyOGC.local().execute(
                polygon, sr, true, null);
            if (simplified.isEmpty()) {
                report.addError("简化后几何为空,可能存在严重的自相交");
            }
        } catch (Exception e) {
            report.addError("简化操作失败: " + e.getMessage());
        }
    }
    
    private void validatePolyline(Polyline polyline, ValidationReport report) {
        // 检查长度
        double length = polyline.calculateLength2D();
        if (length == 0) {
            report.addWarning("折线长度为零");
        }
        
        // 检查路径数
        if (polyline.getPathCount() == 0) {
            report.addError("折线没有路径");
        }
        
        // 检查每条路径的点数
        for (int i = 0; i < polyline.getPathCount(); i++) {
            int pathSize = polyline.getPathSize(i);
            if (pathSize < 2) {
                report.addError("路径 " + i + " 点数不足(至少需要2个点)");
            }
        }
    }
    
    private String describeNonSimpleReason(int reason) {
        switch (reason) {
            case 1: return "重复点";
            case 2: return "自相交";
            case 3: return "环方向错误";
            case 4: return "悬挂边";
            case 5: return "短线段";
            default: return "未知原因 (" + reason + ")";
        }
    }
    
    /**
     * 校验报告
     */
    public static class ValidationReport {
        private final List<String> errors = new ArrayList<>();
        private final List<String> warnings = new ArrayList<>();
        
        public void addError(String message) {
            errors.add(message);
        }
        
        public void addWarning(String message) {
            warnings.add(message);
        }
        
        public boolean isValid() {
            return errors.isEmpty();
        }
        
        public List<String> getErrors() { return errors; }
        public List<String> getWarnings() { return warnings; }
        
        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("校验结果: ").append(isValid() ? "通过" : "失败").append("\n");
            
            if (!errors.isEmpty()) {
                sb.append("错误:\n");
                for (String error : errors) {
                    sb.append("  - ").append(error).append("\n");
                }
            }
            
            if (!warnings.isEmpty()) {
                sb.append("警告:\n");
                for (String warning : warnings) {
                    sb.append("  - ").append(warning).append("\n");
                }
            }
            
            return sb.toString();
        }
    }
}

11.6 本章小结

本章通过五个实战案例展示了 geometry-api-java 的实际应用:

  1. 地理围栏服务:位置监控和事件触发
  2. 空间分析服务:REST API 封装空间操作
  3. POI 空间查询:范围查询和最近搜索
  4. 轨迹分析:长度计算、停留检测、轨迹简化
  5. 数据校验:几何有效性和拓扑规则检查

关键技术点

  • 使用空间索引加速查询
  • 使用几何加速优化重复判断
  • 合理的错误处理和日志记录
  • REST API 设计模式

← 上一章:大数据集成 | 下一章:扩展开发指南 →

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