springboot中使用geotools光滑shp矢量图形【拉普拉斯(Laplacian)光滑算法】
摘要 本文介绍了在SpringBoot中使用GeoTools实现SHP矢量图形平滑处理的方法,重点采用拉普拉斯(Laplacian)光滑算法消除锯齿并优化折线。通过调整迭代次数(iterations)和移动系数(lambda),可适应不同场景需求(如普通道路平滑、保形处理或强力去锯齿)。核心代码展示了如何批量处理SHP文件中的几何要素,包括线状与面状要素的平滑实现,其中线状要素通过坐标序列迭代调整
·
springboot中使用geotools光滑shp矢量图形【拉普拉斯(Laplacian)光滑算法】
对shp文件中的每个要素进行矢量光滑,也就是消除锯齿,减少折线,让边线变得平滑。使用拉普拉斯(Laplacian)光滑算法。下面的示例代码是对任意 Geometry 类型进行平滑。
| 场景 | iterations | lambda | 说明 |
|---|---|---|---|
| 普通道路线平滑 | 3 ~ 5 | 0.3 ~ 0.5 | 平滑明显但不过度 |
| 需要保形(不能动太多) | 2 ~ 3 | 0.2 ~ 0.3 | 更保守 |
| 极度锯齿(比如 AI 输出图层) | 5 ~ 8 | 0.4 ~ 0.6 | 强力平滑 |
效果:
/**
* TODO 矢量光滑
* 建议使用配置:
* 场景 iterations lambda 说明
* 普通道路线平滑 3 ~ 5 0.3 ~ 0.5 平滑明显但不过度
* 需要保形(不能动太多) 2 ~ 3 0.2 ~ 0.3 更保守
* 极度锯齿(比如 AI 输出图层) 5 ~ 8 0.4 ~ 0.6 强力平滑
*/
private void trimZnjySmoothIterations(znjyjyParaModelJsonVo paraModelJsonVo, ZnjyInterpretationTask task) throws IOException {
long startTime = System.currentTimeMillis();
// 平滑的迭代次数,整数,例如 2、3、5
Integer iterations = paraModelJsonVo.getSmoothIterations();
// 每次迭代中顶点的移动程度,0~1 的小数,例如 0.3 表示移动到邻域平均位置的 30% 距离
double lambda = paraModelJsonVo.getLambda();
if (iterations == null || iterations <= 0 || lambda <= 0) {
OpLogUtil.info("无效的矢量光滑参数,跳过处理");
return;
}
FileDataStore store = null;
FeatureIterator<SimpleFeature> features = null;
try {
store = getFileDataStoreStore(task);
if (store == null) throw new IOException("无法打开SHP文件: " + task.getResultPath());
SimpleFeatureStore featureStore = (SimpleFeatureStore) store.getFeatureSource();
String geomName = featureStore.getSchema().getGeometryDescriptor().getLocalName();
features = featureStore.getFeatures().features();
List<FeatureId> fids = new ArrayList<>();
List<Geometry> smoothGeoms = new ArrayList<>();
int count = 0;
while (features.hasNext()) {
SimpleFeature f = features.next();
Geometry g = (Geometry) f.getDefaultGeometry();
Geometry sm = ShpFileStoreUtil.smoothGeometryLaplace(g, iterations, lambda);
if (sm == null || sm.isEmpty()) sm = g;
fids.add(ShpFileStoreUtil.FF.featureId(f.getID()));
smoothGeoms.add(sm.copy());
if (++count % 100 == 0) {
OpLogUtil.info("已光滑 {} 个要素", count);
}
}
// 批量更新
Map<String, Geometry> map = new HashMap<>();
for (int i = 0; i < fids.size(); i++) {
map.put(fids.get(i).getID(), smoothGeoms.get(i));
}
Transaction tx = new DefaultTransaction("laplace-smooth");
featureStore.setTransaction(tx);
FeatureWriter<SimpleFeatureType, SimpleFeature> writer =
((DataStore)featureStore.getDataStore())
.getFeatureWriter(featureStore.getSchema().getTypeName(), tx);
int updated = 0;
while (writer.hasNext()) {
SimpleFeature f = writer.next();
String id = f.getIdentifier().getID();
if (map.containsKey(id)) {
f.setAttribute(geomName, map.get(id));
writer.write();
if (++updated % 100 == 0) {
OpLogUtil.info("已更新 {} 个要素", updated);
}
}
}
tx.commit();
writer.close();
tx.close();
OpLogUtil.info("光滑完成,共处理 {} 个要素,耗时 {} ms",
count, System.currentTimeMillis() - startTime);
} catch (Exception e) {
throw new IOException("光滑处理失败", e);
} finally {
if (features != null) features.close();
if (store != null) store.dispose();
System.gc();
}
}
ShpFileStoreUtil:
/**
* 拉普拉斯(Laplacian)光滑
* 对任意 Geometry 类型进行平滑
*/
public static Geometry smoothGeometryLaplace(Geometry geometry, int iterations, double lambda) {
if (geometry == null || geometry.isEmpty()) return geometry;
GeometryFactory gf = geometry.getFactory();
if (geometry instanceof LineString) {
return laplaceSmoothLine((LineString) geometry, iterations, lambda);
}
if (geometry instanceof Polygon) {
Polygon p = (Polygon) geometry;
// 对外环做拉普拉斯平滑,得到 LineString
LineString smoothedExteriorLS = laplaceSmoothLine(p.getExteriorRing(), iterations, lambda);
// 用它的坐标序列创建 LinearRing
LinearRing extRing = gf.createLinearRing(smoothedExteriorLS.getCoordinateSequence());
// 如果有内环,也同样平滑并重建
int n = p.getNumInteriorRing();
LinearRing[] innerRings = new LinearRing[n];
for (int i = 0; i < n; i++) {
LineString smoothedInnerLS = laplaceSmoothLine(p.getInteriorRingN(i), iterations, lambda);
innerRings[i] = gf.createLinearRing(smoothedInnerLS.getCoordinateSequence());
}
return gf.createPolygon(extRing, innerRings);
}
if (geometry instanceof MultiLineString) {
MultiLineString mls = (MultiLineString) geometry;
LineString[] arr = new LineString[mls.getNumGeometries()];
for (int i = 0; i < arr.length; i++) {
arr[i] = (LineString) laplaceSmoothLine((LineString) mls.getGeometryN(i), iterations, lambda);
}
return gf.createMultiLineString(arr);
}
if (geometry instanceof MultiPolygon) {
MultiPolygon mp = (MultiPolygon) geometry;
Polygon[] arr = new Polygon[mp.getNumGeometries()];
for (int i = 0; i < arr.length; i++) {
arr[i] = (Polygon) smoothGeometryLaplace(mp.getGeometryN(i), iterations, lambda);
}
return gf.createMultiPolygon(arr);
}
if (geometry instanceof GeometryCollection) {
GeometryCollection gc = (GeometryCollection) geometry;
Geometry[] geoms = new Geometry[gc.getNumGeometries()];
for (int i = 0; i < geoms.length; i++) {
geoms[i] = smoothGeometryLaplace(gc.getGeometryN(i), iterations, lambda);
}
return gf.createGeometryCollection(geoms);
}
// 其他类型不处理
return geometry;
}
// 拉普拉斯光滑:只修改中间节点,端点保持不变
public static LineString laplaceSmoothLine(LineString line, int iterations, double lambda) {
Coordinate[] coords = line.getCoordinates();
int n = coords.length;
if (n < 3) return line;
// 工作数组
Coordinate[] curr = coords.clone();
Coordinate[] next = new Coordinate[n];
for (int it = 0; it < iterations; it++) {
// 端点直接拷贝
next[0] = new Coordinate(curr[0]);
next[n-1] = new Coordinate(curr[n-1]);
// 对每个中间点做 Laplacian 平滑
for (int i = 1; i < n - 1; i++) {
double x = curr[i].x + lambda * ((curr[i-1].x + curr[i+1].x) / 2 - curr[i].x);
double y = curr[i].y + lambda * ((curr[i-1].y + curr[i+1].y) / 2 - curr[i].y);
next[i] = new Coordinate(x, y);
}
// 交换数组
Coordinate[] tmp = curr;
curr = next;
next = tmp;
}
return line.getFactory().createLineString(curr);
}
// 假设 FF 是 FilterFactory2 的实例
public static final FilterFactory2 FF = CommonFactoryFinder.getFilterFactory2();
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐



所有评论(0)