├── README.md ├── clip.js ├── index.html └── style.css /README.md: -------------------------------------------------------------------------------- 1 | ## 简述 2 | 在gis系统中 经常会用到一些裁剪的方法,首先推荐一个非常好用的空间分析JavaScript库--Turf.js,不仅功能强大、使用简单,同时处理速度也很快。 3 | Turf.js中提供了一中多边形的裁剪方法是使用多边形去裁剪多边形,但是如果实际工作中需要使用到线去裁剪多边形却无法满足。 4 | [http://turfjs.org/docs#bboxClip](http://turfjs.org/docs#bboxClip) 5 | 这边文章使用turf.js的基本方法,在此基础上构建了线裁剪多边形的方法。 6 | [点击可查看在线demo](https://fwc1994.github.io/clip-polygon/) 7 | ## 算法原理 8 | ### (一)单个polygon的裁剪 9 | `相交要求:线与多边形有且只有两个交点,且可以将多边形分成两部分` 10 | 1. 计算多边形与线的两个交点并根据交点将多边形分割成两条线 11 |  12 | 2. 将分割的两条线根据切割点与切割线进行拼接,分别组成两个多边形,(需要注意的是线的方向性问题) 13 |  14 | ### (二)环多边形的裁剪 15 | `相交要求:线与多边形有且只有两个交点,且可以将多边形分成两部分,同时切割线不可与内环相交` 16 | `注:在geojson数据中外部多边形的顺序为顺时针,环内部多边形顺序为逆时针` 17 | 1. 将环多边形拆分成内环和外环 18 |  19 | 2. 对外环多边形通过切割线进行裁剪 方法同(一) 20 | 21 | 3. 组合切割后的外环多边形和内环多边形:(通过判断内环多边形在那一个切割多边形内部从而判断如何进行组合还原) 22 |  23 | ### (三)MultiPolygon多边形的裁剪 24 | `相交要求:切割线只能与MultiPolygon中的一个Polygon有两个交点` 25 | 1. 拆分MultiPolygon分割为多个Polygon 26 | 2. 根据切割线与多边形的相交情况,对有两个交点的多边形进行进行切割 27 |  28 | 3. 将分割后的多边形与不参与切割的多边形合并组成要素集进行返回即可 29 | ## 项目地址 30 | github:[https://github.com/FWC1994/clip-polygon](https://github.com/FWC1994/clip-polygon) -------------------------------------------------------------------------------- /clip.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: fengwc 3 | * geoJson数据处理模块(需要引入turf.js) 4 | * 输入输出数据均为标准geoJson格式 5 | */ 6 | var geoUtil = { 7 | /** 8 | * 合并多边形 9 | */ 10 | unionPolygon: function(polygons){ 11 | var polygon = polygons[0]; 12 | for (var i = 0; i < polygons.length; i++){ 13 | polygon = turf.union(polygon,polygons[i]); 14 | }; 15 | return polygon; 16 | }, 17 | /** 18 | * 线分割面 19 | * 面类型只能是polygon 但可以是环 20 | * 注:线与多边形必须有两个交点 21 | */ 22 | polygonClipByLine: function (polygon, clipLine) { 23 | if(polygon.geometry.type === 'Polygon'){ 24 | var polyLine = turf.polygonToLine(polygon) 25 | if(polyLine.geometry.type === 'LineString'){ // 切割普通多边形 26 | return this._singlePolygonClip(polyLine, clipLine); 27 | }else if (polyLine.geometry.type === 'MultiLineString'){ //切割环 28 | return this._multiPolygonClip(polyLine, clipLine); 29 | } 30 | }else if(polygon.geometry.type === 'MultiPolygon'){ 31 | // 若输入的多边形类型为Multipolygon则拆分成多个Polygon 32 | var polygons = this.multiPolygon2polygons(polygon) 33 | var clipPolygon = null; 34 | var clipPolygonIndex = -1 35 | // 获取MultiPolygon中与切割线相交的多边形(有且只能有一个多边形相交2个交点) 36 | polygons.forEach(function(polygon, index){ 37 | var polyLine = turf.polygonToLine(polygon) 38 | if(turf.lineIntersect(polyLine, clipLine).features.length === 2){ 39 | if(!clipPolygon){ 40 | clipPolygon = polygon 41 | clipPolygonIndex = index 42 | }else{ 43 | throw {state : '裁剪失败',message : 'MultiPolygon只能有一个多边形与切割线存在交点'}; 44 | } 45 | } 46 | }) 47 | if(clipPolygonIndex !== -1){ 48 | polygons.splice(clipPolygonIndex,1) 49 | return turf.featureCollection(polygons.concat(this.polygonClipByLine(clipPolygon,clipLine).features)); 50 | }else{ 51 | throw {state : '裁剪失败',message : 'MultiPolygon与切割线无交点'}; 52 | } 53 | 54 | }else{ 55 | throw {state : '裁剪失败',message : '输入的多边形类型为错误'}; 56 | } 57 | }, 58 | 59 | _singlePolygonClip: function(polyLine, clipLine){ 60 | // 获得裁切点 61 | var intersects = turf.lineIntersect(polyLine, clipLine); 62 | if (intersects.features.length !== 2){ 63 | throw {state : '裁剪失败',message : '切割线与多边形交点应该为2个,当前交点个数为'+intersects.features.length}; 64 | } 65 | // 检查切割线与多边形的位置关系 (切割线的起点和终点不能落在多边形内部) 66 | var clipLineLength = clipLine.geometry.coordinates.length; 67 | var clipLineStartPoint = turf.point(clipLine.geometry.coordinates[0]) 68 | var clipLineEndPoint = turf.point(clipLine.geometry.coordinates[clipLineLength-1]) 69 | var polygon = turf.polygon([polyLine.geometry.coordinates]) 70 | if(turf.booleanPointInPolygon(clipLineStartPoint, polygon) || turf.booleanPointInPolygon(clipLineEndPoint, polygon)){ 71 | throw {state : '裁剪失败',message : '切割线起点或终点不能在 裁剪多边形内部'}; 72 | } 73 | // 通过裁切点 分割多边形(只能获得多边形的一部分) 74 | var slicedPolyLine = turf.lineSlice(intersects.features[0], intersects.features[1], polyLine); 75 | // 裁剪线分割 保留多边形内部部分 76 | var slicedClipLine = turf.lineSlice(intersects.features[0], intersects.features[1], clipLine); 77 | // 重新拼接多边形 存在 对接的问题 所以先进行判断 如何对接裁剪的多边形和裁剪线 78 | var resultPolyline1 = this.connectLine(slicedPolyLine, slicedClipLine) 79 | // 闭合线 来构造多边形 80 | resultPolyline1.geometry.coordinates.push(resultPolyline1.geometry.coordinates[0]) 81 | var resultPolygon1 = turf.lineToPolygon(resultPolyline1); 82 | // 构造切割的另一面多边形 83 | var firstPointOnLine = this.isOnLine(turf.point(polyLine.geometry.coordinates[0]),slicedPolyLine); 84 | var pointList = []; 85 | if(firstPointOnLine){ 86 | for (var i = 0; i < polyLine.geometry.coordinates.length; i++){ 87 | var coordinate = polyLine.geometry.coordinates[i]; 88 | if(!this.isOnLine(turf.point(coordinate), slicedPolyLine)){ 89 | pointList.push(coordinate) 90 | } 91 | }; 92 | }else{ 93 | var skipNum = 0; // 记录前面被跳过的点的个数 94 | var isStartPush = false; 95 | for (var i = 0; i < polyLine.geometry.coordinates.length; i++){ 96 | var coordinate = polyLine.geometry.coordinates[i]; 97 | if(!this.isOnLine(turf.point(coordinate), slicedPolyLine)){ 98 | if(isStartPush){ 99 | pointList.push(coordinate) 100 | }else{ 101 | skipNum++ 102 | } 103 | 104 | }else{ 105 | isStartPush = true; 106 | } 107 | }; 108 | // 将前面跳过的点补充到 点数组中 109 | for (var i = 0; i < skipNum; i++){ 110 | pointList.push(polyLine.geometry.coordinates[i]) 111 | } 112 | } 113 | var slicedPolyLine_2 = turf.lineString(pointList); 114 | var resultPolyline2 = this.connectLine(slicedPolyLine_2, slicedClipLine) 115 | // 闭合线 来构造多边形 116 | resultPolyline2.geometry.coordinates.push(resultPolyline2.geometry.coordinates[0]) 117 | var resultPolygon2 = turf.lineToPolygon(resultPolyline2); 118 | // 返回面要素集 119 | return turf.featureCollection([ 120 | resultPolygon1, 121 | resultPolygon2 122 | ]); 123 | 124 | }, 125 | 126 | _multiPolygonClip: function(polyLine, clipLine){ 127 | // 将环 多边形分割成 内部逆时针多边形+外部多边形 128 | var outPolyline,insidePolylineList = []; 129 | for(var i=0; i < polyLine.geometry.coordinates.length; i++){ 130 | var splitPolyline = turf.lineString(polyLine.geometry.coordinates[i]); 131 | if(turf.booleanClockwise(splitPolyline)){ 132 | if(outPolyline){ 133 | throw {state : '裁剪失败',message : '出现了两个外部多边形无法处理'}; 134 | }else{ 135 | outPolyline = splitPolyline 136 | } 137 | }else{ 138 | var intersects = turf.lineIntersect(splitPolyline, clipLine); 139 | if(intersects.features.length > 0){ 140 | throw {state : '裁剪失败',message : '切割线不能与内环有交点'}; 141 | } 142 | insidePolylineList.push(splitPolyline) 143 | } 144 | } 145 | var resultCollection = this._singlePolygonClip(outPolyline, clipLine) 146 | 147 | for(var i=0; i < resultCollection.features.length; i++){ 148 | for(var j = 0; j < insidePolylineList.length; j++){ 149 | var startPoint = turf.point(insidePolylineList[j].geometry.coordinates[0]); 150 | if(turf.booleanPointInPolygon(startPoint, resultCollection.features[i])){ 151 | resultCollection.features[i] = turf.mask(resultCollection.features[i], turf.lineToPolygon(insidePolylineList[j])); 152 | } 153 | } 154 | } 155 | return resultCollection 156 | }, 157 | 158 | /** 159 | * 连接两条线 160 | * 方法会将两条线段最近的一段直接连接 161 | */ 162 | connectLine: function(line1, line2){ 163 | var line2_length = line2.geometry.coordinates.length; 164 | var line1_startPoint = line1.geometry.coordinates[0] 165 | var line2_startPoint = line2.geometry.coordinates[0] 166 | var line2_endPoint = line2.geometry.coordinates[line2_length-1] 167 | var pointList = []; 168 | // 获取line1 所有点坐标 169 | for (var i = 0; i < line1.geometry.coordinates.length; i++){ 170 | var coordinate = line1.geometry.coordinates[i]; 171 | pointList.push(coordinate) 172 | }; 173 | 174 | // 判断两条线的 起点是否接近,如果接近 逆转line2线 进行连接 175 | if(turf.distance(line1_startPoint, line2_startPoint) < turf.distance(line1_startPoint, line2_endPoint)){ 176 | line2.geometry.coordinates = line2.geometry.coordinates.reverse(); 177 | } 178 | for (var i = 0; i < line2.geometry.coordinates.length; i++){ 179 | var coordinate = line2.geometry.coordinates[i]; 180 | pointList.push(coordinate) 181 | }; 182 | return turf.lineString(pointList); 183 | }, 184 | 185 | /** 186 | * 判断点是否在线里面 187 | * 注:线组成的坐标对比 188 | */ 189 | isOnLine: function(point, line) { 190 | for (var i = 0; i < line.geometry.coordinates.length; i++){ 191 | var coordinate = line.geometry.coordinates[i]; 192 | if(point.geometry.coordinates[0] === coordinate[0] && point.geometry.coordinates[1] === coordinate[1]){ 193 | return true; 194 | } 195 | }; 196 | return false; 197 | }, 198 | 199 | /** 200 | * 获得两条线交点 201 | */ 202 | getIntersectPoints: function(line1, line2){ 203 | return turf.lineIntersect(line1, line2); 204 | }, 205 | /** 206 | * multiPolygon转polygons,不涉及属性 207 | */ 208 | multiPolygon2polygons: function(multiPolygon){ 209 | if(multiPolygon.geometry.type !== 'MultiPolygon'){ 210 | return 211 | } 212 | var polygons = []; 213 | multiPolygon.geometry.coordinates.forEach((item)=>{ 214 | var polygon = { 215 | type:'Feature', 216 | properties: {}, 217 | geometry: { 218 | type:'Polygon', 219 | coordinates:[] 220 | } 221 | }; 222 | polygon.geometry.coordinates = item; 223 | polygons.push(polygon) 224 | }); 225 | return polygons; 226 | }, 227 | /** 228 | * polygons转multiPolygon,不涉及属性,只输出属性为{} 229 | * 考虑polygons中就存在多面的情况 230 | */ 231 | polygons2MultiPolygon: function (geoJson) { 232 | var newGeoJson = { 233 | type: "FeatureCollection", 234 | features: [{geometry: {coordinates: [], type: "MultiPolygon"}, type: "Feature", properties: {}}] 235 | }; 236 | geoJson.features.forEach((item) => { 237 | if(item.geometry.type==="Polygon"){ 238 | newGeoJson.features[0].geometry.coordinates.push(item.geometry.coordinates); 239 | }else{ 240 | item.geometry.coordinates.forEach((item)=>{ 241 | newGeoJson.features[0].geometry.coordinates.push(item); 242 | }) 243 | } 244 | }) 245 | return newGeoJson; 246 | }, 247 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 |