11import { Position } from "geojson" ;
22import { feature } from "@turf/helpers" ;
33import { getCoords , getType } from "@turf/invariant" ;
4+ import { booleanPointOnLine } from "@turf/boolean-point-on-line" ;
5+ import { lineString } from "@turf/helpers" ;
46
57// To-Do => Improve Typescript GeoJSON handling
68
@@ -99,62 +101,71 @@ function cleanCoords(
99101 * @returns {Array<number> } Cleaned coordinates
100102 */
101103function cleanLine ( line : Position [ ] , type : string ) {
102- var points = getCoords ( line ) ;
104+ const points = getCoords ( line ) ;
103105 // handle "clean" segment
104106 if ( points . length === 2 && ! equals ( points [ 0 ] , points [ 1 ] ) ) return points ;
105107
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 ;
131135 }
136+ // Plan to look at the next point during the next iteration.
137+ c ++ ;
132138 }
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+ }
144159
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+ }
147167 }
148168
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-
158169 return newPoints ;
159170}
160171
@@ -170,35 +181,5 @@ function equals(pt1: Position, pt2: Position) {
170181 return pt1 [ 0 ] === pt2 [ 0 ] && pt1 [ 1 ] === pt2 [ 1 ] ;
171182}
172183
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-
203184export { cleanCoords } ;
204185export default cleanCoords ;
0 commit comments