第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 的实际应用:
- 地理围栏服务:位置监控和事件触发
- 空间分析服务:REST API 封装空间操作
- POI 空间查询:范围查询和最近搜索
- 轨迹分析:长度计算、停留检测、轨迹简化
- 数据校验:几何有效性和拓扑规则检查
关键技术点:
- 使用空间索引加速查询
- 使用几何加速优化重复判断
- 合理的错误处理和日志记录
- REST API 设计模式

浙公网安备 33010602011771号