JTS + Proj4j 实现单侧线缓冲区生成
前言在 GIS 几何计算中“按距离扩面”是一个非常常见的需求比如道路红线生成、管线防护区划分等。直觉上很简单直接调用 Geometry.buffer()就行但如果你用的是 WGS84经纬度结果往往是不准的。根本原因很简单JTS 的 buffer 是平面欧氏运算经纬度不是平面坐标系。凡是涉及“米”的距离运算都必须先投影。本文将封装一个工具类 JtsUtmBufferUtil结合 JTS Proj4j实现米级精度、支持左右单侧缓冲、自动处理 UTM 投影的工程级解决方案。一、核心依赖dependencygroupIdorg.locationtech.jts/groupIdartifactIdjts-core/artifactIdversion1.19.0/version/dependencydependencygroupIdorg.osgeo/groupIdartifactIdproj4j/artifactIdversion0.1.0/version/dependency二、工具类整体结构publicfinalclassJtsUtmBufferUtil{privatestaticfinalGeometryFactoryGFnewGeometryFactory();privatestaticfinalWKTReaderWKTnewWKTReader(GF);privatestaticfinalCRSFactoryCRS_FACTORYnewCRSFactory();privatestaticfinalCoordinateTransformFactoryCTFnewCoordinateTransformFactory();privatestaticfinalCoordinateReferenceSystemWGS84CRS_FACTORY.createFromName(EPSG:4326);privateJtsUtmBufferUtil(){}}三、UTM 投影与坐标转换WGS84 → UTMUTM → WGS84/* 投影 */privatestaticLineStringtoUtm(LineStringg,CoordinateTransformct){Coordinate[]srcg.getCoordinates();Coordinate[]dstnewCoordinate[src.length];ProjCoordinateinnewProjCoordinate();ProjCoordinateoutnewProjCoordinate();for(inti0;isrc.length;i){in.setValue(src[i].x,src[i].y);ct.transform(in,out);dst[i]newCoordinate(out.x,out.y);}returnGF.createLineString(dst);}privatestaticPolygontoWgs84(Polygong,CoordinateTransformct){Coordinate[]srcg.getCoordinates();Coordinate[]dstnewCoordinate[src.length];ProjCoordinateinnewProjCoordinate();ProjCoordinateoutnewProjCoordinate();for(inti0;isrc.length;i){in.setValue(src[i].x,src[i].y);ct.transform(in,out);dst[i]newCoordinate(out.x,out.y);}returnGF.createPolygon(dst);}privatestaticintdetectZone(LineStringline){Coordinate[]csline.getCoordinates();SetIntegerzonesnewHashSet();doublelonSum0;for(Coordinatec:cs){intzutmZone(c.x);zones.add(z);lonSumc.x;}if(zones.size()1){thrownewIllegalArgumentException(LineString crosses UTM zones zones, please split it before buffering.);}returnutmZone(lonSum/cs.length);}privatestaticintutmZone(doublelon){if(lon-180||lon180)thrownewIllegalArgumentException(lon must be in [-180,180]);return(int)Math.floor((lon180)/6)1;}四、Buffer 参数/* BufferParams */privatestaticBufferParametersparams(){BufferParameterspnewBufferParameters();p.setEndCapStyle(BufferParameters.CAP_FLAT);p.setJoinStyle(BufferParameters.JOIN_ROUND);p.setQuadrantSegments(16);returnp;}privatestaticBufferParameterscopy(BufferParameterssrc){BufferParametersdnewBufferParameters();d.setEndCapStyle(src.getEndCapStyle());d.setJoinStyle(src.getJoinStyle());d.setQuadrantSegments(src.getQuadrantSegments());d.setMitreLimit(src.getMitreLimit());d.setSingleSided(src.isSingleSided());returnd;}参数一EndCapStyle端点样式决定 线段两端 的缓冲形状常用于道路、管线的起终点处理参数二JoinStyle拐角样式决定 两条线段连接处 的缓冲形状影响转弯效果JOIN_MITRE 的特殊参数setMitreLimitp.setJoinStyle(BufferParameters.JOIN_MITRE);p.setMitreLimit(5.0);控制尖角的“尖锐程度”超过限制会自动退化为 BEVELAI给出的建议道路 / 自然曲线 → JOIN_ROUND建筑 / 规划红线 → JOIN_MITRE参数三QuadrantSegments象限分段数决定圆角部分的精细程度是影响性能和效果的关键参数JTS把一个圆分成 4 × quadrantSegments条边值越大圆弧越平滑顶点越多quadrantSegments 翻倍 ≈ 顶点数量翻倍不建议超过 16参数四SingleSided单侧缓冲开关这是本工具类最核心的参数之一配合正负距离使用doubledleft?meters:-meters;BufferOp.bufferOp(line,d,p);五、单侧 Buffer 关键代码这里给出左侧的代码右侧思路类似publicstaticPolygonleftSideBuffer(LineStringline,doublemeters){returnleftSideBuffer(line,meters,params());}publicstaticPolygonleftSideBuffer(LineStringline,doublemeters,BufferParametersp){returnsingleSide(line,meters,true,p);}privatestaticPolygonsingleSide(LineStringline,doublemeters,booleanleft,BufferParametersp){if(linenull||line.isEmpty()||meters0)returnnull;intzonedetectZone(line);CoordinateReferenceSystemutmCRS_FACTORY.createFromName(EPSG:326zone);CoordinateTransformtoUtmCTF.createTransform(WGS84,utm);CoordinateTransformtoWgsCTF.createTransform(utm,WGS84);LineStringutmLinetoUtm(line,toUtm);BufferParametersbpcopy(p);bp.setSingleSided(true);doubledleft?meters:-meters;GeometrybufBufferOp.bufferOp(utmLine,d,bp);returntoWgs84((Polygon)buf,toWgs);}privatestaticBufferParameterscopy(BufferParameterssrc){BufferParametersdnewBufferParameters();d.setEndCapStyle(src.getEndCapStyle());d.setJoinStyle(src.getJoinStyle());d.setQuadrantSegments(src.getQuadrantSegments());d.setMitreLimit(src.getMitreLimit());d.setSingleSided(src.isSingleSided());returnd;}六、测试publicstaticvoidmain(String[]args)throwsException{doubled50;LineStringbj(LineString)WKT.read(LINESTRING(116.32672 39.95624,116.456342 39.955656,116.505623 39.926232));LineStringzj(LineString)WKT.read(LINESTRING(117.32672 26.95624,117.456342 26.955656,117.505623 26.926232));System.out.println(BJ left leftSideBuffer(bj,d));System.out.println(BJ right rightSideBuffer(bj,d));System.out.println(ZJ left leftSideBuffer(zj,d));System.out.println(ZJ right rightSideBuffer(zj,d));}将结果在QGIS中可视化可以看到精度没问题总结今天介绍了下用jts生成单侧缓冲区的方法包括坐标转换缓冲区参数这些坐标转换决定生成的缓冲区其位置是否正确以及缓冲区的实际距离是否正确缓冲区参数决定了缓冲区的样式。