svg-path-simplify 0.3.0 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import { rad2Deg } from "../constants";
2
- import { simplifyRDP } from "../simplify_poly_RDP";
3
- import { simplifyRD } from "../simplify_poly_radial_distance";
2
+ //import { simplifyPolyRDP } from "../simplify_poly_RDP";
3
+ //import { simplifyRD } from "../simplify_poly_radial_distance";
4
4
  import { checkLineIntersection, getAngle, getDeltaAngle, getDeltaAngle2, getDistManhattan, getPointOnEllipse, getSquareDistance, pointAtT, reducePoints, rotatePoint } from "./geometry";
5
5
  import { getPolygonArea } from "./geometry_area";
6
6
  import { getPolyBBox } from "./geometry_bbox";
@@ -9,6 +9,7 @@ import { pathDataToD } from "./pathData_stringify";
9
9
  import { renderPath, renderPoint, renderPoly } from "./visualize";
10
10
 
11
11
 
12
+
12
13
  export function analyzePoly(pts, {
13
14
  x = 0,
14
15
  y = 0,
@@ -18,9 +19,6 @@ export function analyzePoly(pts, {
18
19
  } = {}) {
19
20
 
20
21
  let l = pts.length;
21
- //let polyArea = getPolygonArea(pts, true)
22
-
23
- //let bb0 = {x:0, y:0, width:0, height:0}
24
22
 
25
23
  // bounding box of this sub poly
26
24
  let bb0 = getPolyBBox(pts);
@@ -35,7 +33,7 @@ export function analyzePoly(pts, {
35
33
 
36
34
  //console.log(thresh);
37
35
 
38
- // get areas
36
+ // get areas an distances
39
37
  for (let i = 0; i < l; i++) {
40
38
  let p0 = i > 0 ? pts[i - 1] : pts[l - 1];
41
39
  let p1 = pts[i];
@@ -44,12 +42,11 @@ export function analyzePoly(pts, {
44
42
  let area = getPolygonArea([p0, p1, p2], false);
45
43
  p1.area = area;
46
44
  p1.dist = getDistManhattan(p0, p1)
45
+ p1.idx = i
47
46
  //pts[i] = p1
48
47
  }
49
48
 
50
- // pts= pts.reverse();
51
49
 
52
- let remove = []
53
50
 
54
51
  for (let i = 0; i < l; i++) {
55
52
  let p02 = i > 1 ? pts[i - 2] : pts[l - 1];
@@ -58,7 +55,6 @@ export function analyzePoly(pts, {
58
55
  let p2 = i < l - 1 ? pts[i + 1] : pts[l - 1];
59
56
 
60
57
 
61
- let bb = getPolyBBox([p0, p2]);
62
58
  let max = getSquareDistance(p0, p2) * 0.01
63
59
 
64
60
  let area0 = Math.abs(p0.area)
@@ -72,157 +68,97 @@ export function analyzePoly(pts, {
72
68
 
73
69
 
74
70
  //console.log(bb);
75
- let { left, right, top, bottom } = bb;
76
-
77
- // is local or absolute extreme
78
- let extremeLocal = (p1.x <= left || p1.x >= right || p1.y <= top || p1.y >= bottom)
79
-
80
-
81
- let isExtreme = (
82
- // extremeLocal ||
83
- (p1.x === bb0.left || p1.x === bb0.right || p1.y === bb0.top || p1.y === bb0.bottom)
84
- );
85
-
71
+ //let extremeLocal = (p1.x < left || p1.x > right || p1.y < top || p1.y > bottom)
86
72
 
87
73
  let dist = getDistManhattan(p1, p0)
88
74
  let isNear = dist < thresh * 5
89
75
 
90
76
 
91
77
 
92
- //let isHorizontal = !pt0.isHorizontal ? pt1.y === pt0.y && pt1.x!== pt0.x : false;
93
- let isHorizontal = p1.y === p0.y && p1.x !== p0.x;
94
- let isVertical = p1.x === p0.x && p1.y !== p0.y
78
+ /**
79
+ * check extremes
80
+ */
95
81
 
82
+ let isExtreme = false;
96
83
 
97
- //!isCloseExtreme &&
98
- if ((isHorizontal || isVertical)) {
84
+ // 1. total extreme
85
+ if ((p1.x === bb0.left || p1.x === bb0.right || p1.y === bb0.top || p1.y === bb0.bottom)) {
99
86
  isExtreme = true
100
87
  }
101
88
 
102
- //let area0 = p02.area + p0.area
103
- //let area1 = p1.area + p2.area
104
-
105
- let signChange = (p0.area < 0 && p1.area > 0) || (p0.area > 0 && p1.area < 0)
106
- let isDirChange = signChange && !flat
107
- isCorner = isDirChange
108
-
109
- // isDirChange = signChange && signChange3
89
+ // 1.2 horizontal or vertical
90
+ let isHorizontal = p1.y === p0.y && p1.x !== p0.x;
91
+ let isVertical = p1.x === p0.x && p1.y !== p0.y
110
92
 
111
- if (extremeLocal && !p0.isDirChange) {
112
- //renderPoint(markers, p0, 'red', '0.75%', '1')
113
- //renderPoint(markers, p1, 'blue', '0.75%', '1')
114
- //p1.isDirChange=false
115
- isExtreme = true;
93
+ if ((isHorizontal || isVertical)) {
94
+ p0.isExtreme = true
95
+ isExtreme = true
116
96
  }
117
97
 
118
98
 
99
+ // 1.3 is local or absolute extreme
100
+ let bb = getPolyBBox([p0, p2]); // local bb
101
+ let { left, right, top, bottom } = bb;
119
102
 
120
- //|| (p0.isVertical || p1.isHorizontal )
121
- //|| (p0.isVertical || isHorizontal )
122
- if ((isVertical && p0.isHorizontal)) {
123
- //renderPoint(markers, p0, 'red', '2%', '1')
124
- p0.isCorner = true
125
- p0.isExtreme = false
126
- isExtreme = false
103
+ //let extremeLocal = (p1.x <= left || p1.x >= right || p1.y <= top || p1.y >= bottom)
104
+ let extremeLocal = (p1.x < left || p1.x > right || p1.y < top || p1.y > bottom)
105
+ if (!isExtreme && extremeLocal) {
106
+ isExtreme = true
107
+ //renderPoint(markers, p1, 'blue', '2%', '0.5')
127
108
  }
128
109
 
129
110
 
130
- /*
131
- if (isExtreme && p0.isExtreme && signChange && isNear) {
132
- //console.log(p0);
133
- //renderPoint(markers, p1, 'red', '2%', '1')
134
- isCorner = true
135
- }
136
- */
137
111
 
138
- /*
139
- //nearby extremes
140
- if (isExtreme && p0.isExtreme) {
141
- let dist = getDistManhattan(p1, p0)
142
- if (dist < thresh * 5) {
143
- //p0.isExtreme = true
144
- p1.isExtreme = false
145
- isExtreme=false
146
- isCloseExtreme = true
147
- remove.push(i)
148
- }
112
+ /**
113
+ * 2. sign changes
114
+ */
115
+ let signChange = (p0.area < 0 && p1.area > 0) || (p0.area > 0 && p1.area < 0)
116
+ let isDirChange = signChange && !flat && !p0.isDirChange
149
117
 
150
- }
151
- */
152
118
 
153
119
 
154
- // reset if 2 in sequence
155
- if (isDirChange && (p0.isDirChange || p0.isExtreme)) {
156
- isDirChange = false
157
- p1.isDirChange = false
158
- }
120
+ /**
121
+ * 3. corners
122
+ */
159
123
 
124
+ //isDirChange &&
125
+ if (isExtreme) {
160
126
 
127
+ let delta = getDeltaAngle(p1, p2, p0)
128
+ let { deltaAngleDeg } = delta
129
+ deltaAngleDeg = Math.abs(deltaAngleDeg)
161
130
 
131
+ let isCornerDelta = deltaAngleDeg > 10 && deltaAngleDeg < 160
132
+ if (isCornerDelta) {
133
+ //console.log(deltaAngleDeg);
134
+ isCorner = true;
135
+ }
162
136
 
163
- /*
164
- let areaChange = area2*1.5<area1 && area0*1.5<area1;
165
- if(areaChange){
166
- //renderPoint(markers, p1, 'red', '1.75%', '1')
167
137
  }
168
- */
169
-
170
138
 
171
139
 
172
140
  /*
173
- let thresh2 = (width + height) * 0.01
174
- let isLong = p1.dist > thresh2
175
- console.log('thresh2', p1.dist, thresh2, thresh);
176
-
177
- if( isLong && isDirChange){
178
- renderPoint(markers, p0, 'red', '1.75%', '1')
179
- renderPoint(markers, p1, 'green', '1.75%', '1')
180
- }
181
- */
182
-
183
- // corner after extreme
184
- if (p0.isExtreme && area1 > area2 && !isDirChange && !p1.isHorizontal) {
185
- }
186
-
141
+ let debug = true
142
+ if (debug) {
187
143
 
188
- // refine corner check
189
- /*
190
- if (isCorner) {
191
- let delta = getDeltaAngle(p1, p0, p2)
192
- delta = Math.abs(delta.deltaAngleDeg)
193
- if (delta > 160) {
194
- //renderPoint(markers, p1, 'blue', '2.75%', '1')
195
- isCorner = false;
196
- } else {
197
- isCorner = true;
144
+ if ((isHorizontal || isVertical)) {
145
+ renderPoint(markers, p1, 'blue', '2%', '0.5')
146
+ renderPoint(markers, p0, 'blue', '2%', '0.5')
198
147
  }
199
- console.log(delta);
200
- }
201
- */
202
-
203
148
 
204
- if ((isExtreme || signChange || area1 > area0)) {
205
-
206
- let delta = getDeltaAngle(p1, p2, p0)
207
- let { deltaAngleDeg } = delta
208
- deltaAngleDeg = Math.abs(deltaAngleDeg)
209
-
210
-
211
- // not a corner
212
- if (deltaAngleDeg < 3 || deltaAngleDeg > 160) {
213
- //renderPoint(markers, p1, 'blue', '2.75%', '1')
214
- isCorner = false;
215
- }
216
- else {
217
- //console.log(deltaAngleDeg, delta);
218
- isCorner = true;
149
+ if (isExtreme) {
150
+ renderPoint(markers, p1, 'cyan', '1.5%', '0.5')
219
151
  }
220
- }
221
152
 
222
- if (isHorizontal) {
223
- //renderPoint(markers, p1, 'blue', '2.75%', '1')
153
+ if (isDirChange) {
154
+ renderPoint(markers, p1, 'orange', '0.75%', '0.5')
155
+ }
224
156
 
157
+ if (isCorner) {
158
+ renderPoint(markers, p1, 'magenta', '2.75%', '0.5')
159
+ }
225
160
  }
161
+ */
226
162
 
227
163
 
228
164
  p1.isCorner = isCorner;
@@ -231,165 +167,85 @@ export function analyzePoly(pts, {
231
167
  p1.isVertical = isVertical;
232
168
  p1.isDirChange = isDirChange;
233
169
 
234
- }
170
+ //renderPoint(markers, p1, 'red', '2%', '0.5')
235
171
 
236
172
 
173
+ }
174
+
237
175
 
238
176
  // filter adjacent extremes
239
177
  let pts1 = []
240
178
  let exclude = []
241
-
242
- for (let i = 0; i < pts.length; i++) {
243
- let p = pts[i]
244
- let p1 = pts[i + 1] || null
245
- let p2 = pts[i + 2] || null
246
-
247
- let extremes = []
248
-
249
-
250
- if (p1 && p1.isExtreme && p.isExtreme && !p.isCorner) {
251
- let has2nd = p1.dist < thresh * 2 && !p1.isCorner
252
- let has3rd = p2 && p2.isExtreme && p2.dist < thresh * 2 && !p2.isCorner
253
- let lastExt = p1
254
-
255
- if (has2nd && !has3rd) {
256
- extremes.push(p, p1)
257
- //renderPoint(markers, p1, 'magenta', '1%', '0.5')
258
- } else if (has3rd) {
259
- extremes.push(p, p1, p2)
260
- /*
261
- renderPoint(markers, p, 'green', '1%', '0.5')
262
- renderPoint(markers, p1, 'red', '1%', '0.5')
263
- renderPoint(markers, p2, 'blue', '1%', '0.5')
264
- */
179
+ let filterExtremes = true;
180
+
181
+ if (filterExtremes) {
182
+ for (let i = 0; i < pts.length; i++) {
183
+ let p = pts[i]
184
+ let p1 = pts[i + 1] || null
185
+ let p2 = pts[i + 2] || null
186
+
187
+ let extremes = []
188
+
189
+ if (p1 && p1.isExtreme && p.isExtreme && !p.isCorner) {
190
+ let has2nd = p1.dist < thresh * 2 && !p1.isCorner
191
+ let has3rd = p2 && p2.isExtreme && p2.dist < thresh * 2 && !p2.isCorner
192
+ let lastExt = p1
193
+
194
+ if (has2nd && !has3rd) {
195
+ extremes.push(p, p1)
196
+ //renderPoint(markers, p1, 'magenta', '1%', '0.5')
197
+ } else if (has3rd) {
198
+ extremes.push(p, p1, p2)
199
+ /*
200
+ renderPoint(markers, p, 'green', '1%', '0.5')
201
+ renderPoint(markers, p1, 'red', '1%', '0.5')
202
+ renderPoint(markers, p2, 'blue', '1%', '0.5')
203
+ */
204
+ }
205
+
206
+ if (extremes.length) {
207
+ // average extreme
208
+ //console.log(extremes);
209
+ let x = extremes.reduce((a, b) => a + b.x, 0) / extremes.length
210
+ let y = extremes.reduce((a, b) => a + b.y, 0) / extremes.length
211
+
212
+ ///extremes.length
213
+ p.x = x
214
+ p.y = y
215
+ //console.log(x);
216
+ i += extremes.length - 1
217
+ }
265
218
  }
266
219
 
267
- if (extremes.length) {
268
- // average extreme
269
- //console.log(extremes);
270
- let x = extremes.reduce((a, b) => a + b.x, 0) / extremes.length
271
- let y = extremes.reduce((a, b) => a + b.y, 0) / extremes.length
272
-
273
- ///extremes.length
274
- p.x = x
275
- p.y = y
276
- //console.log(x);
277
- i += extremes.length - 1
220
+ if (p.isExtreme || p.isCorner || p.isDirChange) {
221
+ exclude.push(pts1.length)
278
222
  }
279
- }
280
-
281
- if (p.isExtreme || p.isCorner || p.isDirChange) {
282
- exclude.push(pts1.length)
283
- }
284
223
 
285
224
 
286
- pts1.push(p)
287
- }
288
-
289
- // remove last nearby extreme
290
- let l2 = pts1.length;
291
- let p0 = pts1[0]
292
- let pL = pts1[l2 - 1]
293
- let near0 = getDistManhattan(p0, pL) < thresh * 2
294
- if (p0.isExtreme && pL.isExtreme && near0) {
295
- pL.x = p0.x
296
- pL.y = p0.y
297
- }
298
-
299
-
300
- //console.log(exclude);
301
- //pts1 = simplifyRD(pts1, {quality:0.7, exclude})
302
-
303
- // simplify via RDP - exclude extremes
304
- //pts1 = simplifyRDP(pts1, {quality:0.85, exclude})
305
-
306
- /*
307
- pts1.forEach(pt=>{
308
- renderPoint(markers, pt, 'green', '1%', '0.5')
309
- })
310
- */
311
-
312
- pts = pts1
313
- //console.log(pts1);
314
-
315
- // test render
316
- //renderPolyTopology(pts)
317
- //return pathDataFromPoly(pts)
318
-
319
- return pts
320
- }
321
-
225
+ pts1.push(p)
322
226
 
323
- // split in chunks based on significant points
324
-
325
- export function getPolyChunks(pts,
326
- { closed = true,
327
- keepCorners = true,
328
- keepExtremes = true,
329
- keepInflections = false
330
- } = {}
331
- ) {
332
- let chunks = [];
333
- let chunk = [pts[0]];
334
-
335
- let l = pts.length
336
-
337
- // render
338
- for (let i = 1; i < l; i++) {
339
- let p0 = i > 0 ? pts[i - 1] : pts[l - 1];
340
- let p1 = pts[i];
341
- let p2 = i < l - 1 ? pts[i + 1] : pts[l - 1];
342
-
343
- chunk.push(p1)
344
-
345
- // start new chunk
346
- if (i > 0 &&
347
- (keepExtremes && p2.isExtreme || keepCorners && p2.isCorner)
348
- ) {
349
- chunks.push(chunk)
350
- chunk = []
227
+ // remove last nearby extreme
228
+ let l2 = pts1.length;
229
+ let p0 = pts1[0]
230
+ let pL = pts1[l2 - 1]
231
+ let near0 = getDistManhattan(p0, pL) < thresh * 2
232
+ if (p0.isExtreme && pL.isExtreme && near0) {
233
+ pL.x = p0.x
234
+ pL.y = p0.y
235
+ }
351
236
  }
352
237
 
353
- //else if(!keepExtremes && !keepExtremes){}
238
+ pts = pts1
354
239
  }
355
240
 
356
- // chunk is empty - extremes or corners
357
- if(!chunks.length && pts.length>1){
358
- chunks = [pts]
359
- }
360
241
 
361
- //console.log(keepExtremes, keepCorners, chunks, pts);
362
-
363
- // test render
364
- //renderchunks(chunks)
365
-
366
- return chunks;
242
+ return pts
367
243
  }
368
244
 
369
- function renderchunks(chunks) {
370
-
371
- //console.log('renderchunks', chunks);
372
-
373
- chunks.forEach((chunk, i) => {
374
-
375
- let stroke = i % 2 === 0 ? 'orange' : 'blue';
376
- let pathData = [{ type: 'M', values: [chunk[0].x, chunk[0].y] }]
377
- let d = `M`
378
-
379
- chunk.forEach(pt => {
380
245
 
381
- pathData.push({ type: 'L', values: [pt.x, pt.y] })
382
- d += ` ${[pt.x, pt.y].join(' ')}`
383
246
 
384
- })
385
247
 
386
- d = pathDataToD(pathData)
387
- //console.log(d);
388
248
 
389
- renderPath(markers, d, stroke, '2%', '0.5')
390
- })
391
-
392
- }
393
249
 
394
250
  // just for visualization
395
251
  function renderPolyTopology(pts) {
@@ -423,7 +279,6 @@ function renderPolyTopology(pts) {
423
279
  }
424
280
 
425
281
 
426
-
427
282
  if (p1.isCorner) {
428
283
  renderPoint(markers, p1, 'magenta', '1%', '1')
429
284
  }
@@ -433,182 +288,6 @@ function renderPolyTopology(pts) {
433
288
 
434
289
  }
435
290
 
436
- export function analyzePoly2(pts, debug = false) {
437
-
438
- let l = pts.length;
439
- let polyArea = getPolygonArea(pts, true)
440
- let bb0 = getPolyBBox(pts)
441
- //console.log(polyArea);
442
-
443
-
444
- // get areas
445
- for (let i = 0; i < l; i++) {
446
- let pt0 = i > 0 ? pts[i - 1] : null;
447
- let pt1 = pts[i];
448
- let pt2 = i < l - 1 ? pts[i + 1] : null;
449
-
450
- if (!pt0 || !pt2) continue
451
-
452
- let area = getPolygonArea([pt0, pt1, pt2], false);
453
- let ang1 = getAngle(pt1, pt0, true);
454
- let ang2 = getAngle(pt1, pt2, true);
455
- let delta = Math.abs(ang1 - ang2);
456
- let deltaDeg = delta * 180 / Math.PI;
457
-
458
- //console.log(bb0);
459
-
460
- /**
461
- * get local extremes
462
- * my coincide with corners or
463
- * direction changes
464
- */
465
- let { left, right, top, bottom } = getPolyBBox([pt0, pt2]);
466
- let isExtreme = ((pt1.x < left || pt1.x > right || pt1.y < top || pt1.y > bottom) ||
467
- (pt1.x === bb0.left || pt1.x === bb0.right || pt1.y === bb0.top || pt1.y === bb0.bottom)
468
- );
469
- //let isHorizontal = !pt0.isHorizontal ? pt1.y === pt0.y && pt1.x!== pt0.x : false;
470
- let isHorizontal = pt1.y === pt0.y && pt1.x !== pt0.x;
471
- let isVertical = pt1.x === pt0.x && pt1.y !== pt0.y
472
-
473
- if (pt0.isHorizontal) {
474
- //isHorizontal=false
475
- //renderPoint(markers, pt0)
476
- }
477
-
478
-
479
- /**
480
- * check corners by
481
- * adjacent angle differences
482
- */
483
- let isCorner = deltaDeg < 120 || deltaDeg > 270;
484
-
485
-
486
- /**
487
- * get direction changes
488
- * e.g the spine of a "S" shape
489
- */
490
- let directionChange = pt0.isCorner === false && ((pt0.area < 0 && area > 0) || (pt0.area > 0 && area < 0));
491
-
492
-
493
-
494
- if (pt0.isExtreme &&
495
- (pt1.y === pt0.y || pt1.x === pt0.x)
496
- ) {
497
- isExtreme = true;
498
- }
499
-
500
-
501
- if (directionChange && isExtreme) {
502
- isCorner = true;
503
- }
504
-
505
- // if segment is too large relative to total area - don't interpret as corner
506
- let areaRat = Math.abs(area / polyArea);
507
-
508
- if (areaRat > 0.2) {
509
- isCorner = false;
510
- }
511
-
512
-
513
- /**
514
- * visualize significant points for
515
- * debugging
516
- */
517
-
518
- /*
519
- */
520
-
521
- if (debug) {
522
-
523
- if ((isExtreme && isCorner)) {
524
- //isExtreme = false;
525
- directionChange = false;
526
- //isCorner = false;
527
- }
528
-
529
- if (isHorizontal) {
530
- renderPoint(markers, pt0, 'blue', '1%', '0.5');
531
- renderPoint(markers, pt1, 'red', '1%', '0.5');
532
- }
533
-
534
-
535
- if (isVertical) {
536
- //renderPoint(markers, pt1, 'blue', '2%', '0.5');
537
- }
538
-
539
-
540
- if (isExtreme) {
541
- renderPoint(markers, pt1, 'cyan', '2%');
542
- }
543
-
544
- if (isCorner) {
545
- renderPoint(markers, pt1, 'purple', '0.5%');
546
- }
547
-
548
- if (directionChange) {
549
- //renderPoint(markers, pt1, 'orange', '1.5%', '0.5');
550
- }
551
-
552
- }
553
-
554
-
555
- /**
556
- * save point analysis properties
557
- * to point objects
558
- */
559
- pt1.isHorizontal = isHorizontal;
560
- pt1.isVertical = isVertical;
561
- pt1.isExtreme = isExtreme;
562
- pt1.isCorner = isCorner;
563
- pt1.directionChange = directionChange;
564
-
565
- pt1.area = area;
566
- pt1.delta = delta;
567
- pt1.deltaDeg = deltaDeg;
568
-
569
- }
570
-
571
-
572
- //getControlPoints(pts)
573
-
574
-
575
- return pts
576
- }
577
-
578
-
579
-
580
-
581
-
582
-
583
-
584
-
585
- export function getPathDataChunks(pathData) {
586
-
587
- let chunks = [[]];
588
- let lastType = 'M'
589
- let ind = 0;
590
- let wasExtreme, wasCorner, wasDirectionchange;
591
-
592
- pathData.forEach(com => {
593
-
594
- let { isCorner, isExtreme, directionChange, type } = com;
595
-
596
- if (type !== lastType || wasExtreme || wasCorner || directionChange || wasDirectionchange) {
597
- chunks.push([])
598
- ind++
599
- }
600
- chunks[ind].push(com)
601
-
602
- wasExtreme = isExtreme
603
- wasCorner = isCorner
604
- wasDirectionchange = directionChange;
605
- lastType = type
606
- })
607
-
608
-
609
- return chunks;
610
-
611
- }
612
291
 
613
292
 
614
293