1
1
import { Position } from "geojson" ;
2
2
import { feature } from "@turf/helpers" ;
3
3
import { getCoords , getType } from "@turf/invariant" ;
4
+ import { booleanPointOnLine } from "@turf/boolean-point-on-line" ;
5
+ import { lineString } from "@turf/helpers" ;
4
6
5
7
// To-Do => Improve Typescript GeoJSON handling
6
8
@@ -99,62 +101,71 @@ function cleanCoords(
99
101
* @returns {Array<number> } Cleaned coordinates
100
102
*/
101
103
function cleanLine ( line : Position [ ] , type : string ) {
102
- var points = getCoords ( line ) ;
104
+ const points = getCoords ( line ) ;
103
105
// handle "clean" segment
104
106
if ( points . length === 2 && ! equals ( points [ 0 ] , points [ 1 ] ) ) return points ;
105
107
106
- var newPoints = [ ] ;
107
- var secondToLast = points . length - 1 ;
108
- var newPointsLength = newPoints . length ;
109
-
110
- newPoints . push ( points [ 0 ] ) ;
111
- for ( var i = 1 ; i < secondToLast ; i ++ ) {
112
- var prevAddedPoint = newPoints [ newPoints . length - 1 ] ;
113
- if (
114
- points [ i ] [ 0 ] === prevAddedPoint [ 0 ] &&
115
- points [ i ] [ 1 ] === prevAddedPoint [ 1 ]
116
- )
117
- continue ;
118
- else {
119
- newPoints . push ( points [ i ] ) ;
120
- newPointsLength = newPoints . length ;
121
- if ( newPointsLength > 2 ) {
122
- if (
123
- isPointOnLineSegment (
124
- newPoints [ newPointsLength - 3 ] ,
125
- newPoints [ newPointsLength - 1 ] ,
126
- newPoints [ newPointsLength - 2 ]
127
- )
128
- )
129
- newPoints . splice ( newPoints . length - 2 , 1 ) ;
130
- }
108
+ const newPoints = [ ] ;
109
+
110
+ // Segments based approach. With initial segment a-b, keep comparing to a
111
+ // longer segment a-c and as long as b is still on a-c, b is a redundant
112
+ // point.
113
+ let a = 0 ,
114
+ b = 1 ,
115
+ c = 2 ;
116
+
117
+ // Guaranteed we'll use the first point.
118
+ newPoints . push ( points [ a ] ) ;
119
+ // While there is still room to extend the segment ...
120
+ while ( c < points . length ) {
121
+ if ( booleanPointOnLine ( points [ b ] , lineString ( [ points [ a ] , points [ c ] ] ) ) ) {
122
+ // b is on a-c, so we can discard point b, and extend a-b to be the same
123
+ // as a-c as the basis for comparison during the next iteration.
124
+ b = c ;
125
+ } else {
126
+ // b is NOT on a-c, suggesting a-c is not an extension of a-b. Commit a-b
127
+ // as a necessary segment.
128
+ newPoints . push ( points [ b ] ) ;
129
+
130
+ // Make our a-b for the next iteration start from the end of the segment
131
+ // that was just locked in i.e. next a-b should be the current b-(b+1).
132
+ a = b ;
133
+ b ++ ;
134
+ c = b ;
131
135
}
136
+ // Plan to look at the next point during the next iteration.
137
+ c ++ ;
132
138
}
133
- newPoints . push ( points [ points . length - 1 ] ) ;
134
- newPointsLength = newPoints . length ;
135
-
136
- // (Multi)Polygons must have at least 4 points, but a closed LineString with only 3 points is acceptable
137
- if (
138
- ( type === "Polygon" || type === "MultiPolygon" ) &&
139
- equals ( points [ 0 ] , points [ points . length - 1 ] ) &&
140
- newPointsLength < 4
141
- ) {
142
- throw new Error ( "invalid polygon" ) ;
143
- }
139
+ // No remaining points, so commit the current a-b segment.
140
+ newPoints . push ( points [ b ] ) ;
141
+
142
+ if ( type === "Polygon" || type === "MultiPolygon" ) {
143
+ // For polygons need to make sure the start / end point wasn't one of the
144
+ // points that needed to be cleaned.
145
+ // https://github.com/Turfjs/turf/issues/2406
146
+ // For points [a, b, c, ..., z, a]
147
+ // if a is on line b-z, it too can be removed. New array becomes
148
+ // [b, c, ..., z, b]
149
+ if (
150
+ booleanPointOnLine (
151
+ newPoints [ 0 ] ,
152
+ lineString ( [ newPoints [ 1 ] , newPoints [ newPoints . length - 2 ] ] )
153
+ )
154
+ ) {
155
+ newPoints . shift ( ) ; // Discard starting point.
156
+ newPoints . pop ( ) ; // Discard closing point.
157
+ newPoints . push ( newPoints [ 0 ] ) ; // Duplicate the new closing point to end of array.
158
+ }
144
159
145
- if ( type === "LineString" && newPointsLength < 3 ) {
146
- return newPoints ;
160
+ // (Multi)Polygons must have at least 4 points and be closed.
161
+ if ( newPoints . length < 4 ) {
162
+ throw new Error ( "invalid polygon, fewer than 4 points" ) ;
163
+ }
164
+ if ( ! equals ( newPoints [ 0 ] , newPoints [ newPoints . length - 1 ] ) ) {
165
+ throw new Error ( "invalid polygon, first and last points not equal" ) ;
166
+ }
147
167
}
148
168
149
- if (
150
- isPointOnLineSegment (
151
- newPoints [ newPointsLength - 3 ] ,
152
- newPoints [ newPointsLength - 1 ] ,
153
- newPoints [ newPointsLength - 2 ]
154
- )
155
- )
156
- newPoints . splice ( newPoints . length - 2 , 1 ) ;
157
-
158
169
return newPoints ;
159
170
}
160
171
@@ -170,35 +181,5 @@ function equals(pt1: Position, pt2: Position) {
170
181
return pt1 [ 0 ] === pt2 [ 0 ] && pt1 [ 1 ] === pt2 [ 1 ] ;
171
182
}
172
183
173
- /**
174
- * Returns if `point` is on the segment between `start` and `end`.
175
- * Borrowed from `@turf/boolean-point-on-line` to speed up the evaluation (instead of using the module as dependency)
176
- *
177
- * @private
178
- * @param {Position } start coord pair of start of line
179
- * @param {Position } end coord pair of end of line
180
- * @param {Position } point coord pair of point to check
181
- * @returns {boolean } true/false
182
- */
183
- function isPointOnLineSegment ( start : Position , end : Position , point : Position ) {
184
- var x = point [ 0 ] ,
185
- y = point [ 1 ] ;
186
- var startX = start [ 0 ] ,
187
- startY = start [ 1 ] ;
188
- var endX = end [ 0 ] ,
189
- endY = end [ 1 ] ;
190
-
191
- var dxc = x - startX ;
192
- var dyc = y - startY ;
193
- var dxl = endX - startX ;
194
- var dyl = endY - startY ;
195
- var cross = dxc * dyl - dyc * dxl ;
196
-
197
- if ( cross !== 0 ) return false ;
198
- else if ( Math . abs ( dxl ) >= Math . abs ( dyl ) )
199
- return dxl > 0 ? startX <= x && x <= endX : endX <= x && x <= startX ;
200
- else return dyl > 0 ? startY <= y && y <= endY : endY <= y && y <= startY ;
201
- }
202
-
203
184
export { cleanCoords } ;
204
185
export default cleanCoords ;
0 commit comments