svg-path-simplify 0.0.1 → 0.0.2
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.
- package/README.md +28 -1
- package/dist/svg-path-simplify.esm.js +4040 -0
- package/dist/svg-path-simplify.esm.min.js +1 -0
- package/dist/svg-path-simplify.js +4065 -0
- package/dist/svg-path-simplify.min.js +1 -0
- package/dist/svg-path-simplify.node.js +4062 -0
- package/dist/svg-path-simplify.node.min.js +1 -0
- package/index.html +222 -0
- package/package.json +2 -2
- package/src/constants.js +4 -0
- package/src/index.js +18 -3
- package/src/pathData_simplify_cubic.js +324 -0
- package/src/pathData_simplify_cubic_arr.js +50 -0
- package/src/pathData_simplify_cubic_extrapolate.js +220 -0
- package/src/pathSimplify-main.js +294 -0
- package/src/svgii/...parse.js +402 -0
- package/src/svgii/geometry.js +1096 -0
- package/src/svgii/geometry_area.js +265 -0
- package/src/svgii/geometry_bbox.js +223 -0
- package/src/svgii/pathData_analyze.js +896 -0
- package/src/svgii/pathData_convert.js +1180 -0
- package/src/svgii/pathData_parse.js +487 -0
- package/src/svgii/pathData_remove_collinear.js +85 -0
- package/src/svgii/pathData_remove_zerolength.js +28 -0
- package/src/svgii/pathData_reorder.js +204 -0
- package/src/svgii/pathData_reverse.js +124 -0
- package/src/svgii/pathData_scale.js +42 -0
- package/src/svgii/pathData_split.js +449 -0
- package/src/svgii/pathData_stringify.js +146 -0
- package/src/svgii/pathData_toPolygon.js +92 -0
- package/src/svgii/pathdata_cleanup.js +363 -0
- package/src/svgii/poly_analyze.js +172 -0
- package/src/svgii/poly_to_pathdata.js +185 -0
- package/src/svgii/rounding.js +154 -0
- package/src/svgii/simplify.js +248 -0
- package/src/svgii/simplify_bezier.js +470 -0
- package/src/svgii/simplify_linetos.js +93 -0
- package/src/svgii/simplify_polygon.js +135 -0
- package/src/svgii/stringify.js +103 -0
- package/src/svgii/svg_cleanup.js +80 -0
- package/src/svgii/visualize.js +317 -0
- package/LICENSE +0 -21
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
//import { quadratic2Cubic } from './convert.js';
|
|
2
|
+
//import { splitSubpaths, shiftSvgStartingPoint } from './convert_segments.js';
|
|
3
|
+
import { shiftSvgStartingPoint, reorderPathData } from './pathData_reorder.js';
|
|
4
|
+
import { splitSubpaths, addExtemesToCommand } from './pathData_split.js';
|
|
5
|
+
import { getComThresh, commandIsFlat, getPathDataVertices } from './geometry.js';
|
|
6
|
+
|
|
7
|
+
import { getPolyBBox } from './geometry_bbox.js';
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* remove zero length commands
|
|
14
|
+
* replace flat beziers with lintos
|
|
15
|
+
* replace closing lines with z
|
|
16
|
+
* rearrange commands to avoid unnessessary linetos
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
export function cleanUpPathData(pathData, addExtremes = false, removeClosingLines = true, startToTop = true, debug = false) {
|
|
21
|
+
|
|
22
|
+
//collect logs
|
|
23
|
+
let simplyfy_debug_log = [];
|
|
24
|
+
|
|
25
|
+
pathData = JSON.parse(JSON.stringify(pathData));
|
|
26
|
+
let pathDataNew = [pathData[0]];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* get poly bbox to define
|
|
30
|
+
* an appropriate relative threshold
|
|
31
|
+
* for flat or short segment detection
|
|
32
|
+
*/
|
|
33
|
+
let pathPoly = getPathDataVertices(pathData);
|
|
34
|
+
let bb = getPolyBBox(pathPoly)
|
|
35
|
+
let { width, height } = bb;
|
|
36
|
+
let tolerance = (width + height) / 2 * 0.001
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
// previous on path point
|
|
40
|
+
let p0 = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
41
|
+
let M = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
42
|
+
|
|
43
|
+
let addedExtremes = false;
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
for (let c = 1, len = pathData.length; len && c < len; c++) {
|
|
47
|
+
let com = pathData[c];
|
|
48
|
+
//let comPrev = pathData[c - 1];
|
|
49
|
+
let comN = pathData[c + 1] ? pathData[c + 1] : '';
|
|
50
|
+
let { type, values } = com;
|
|
51
|
+
//let typeRel = type.toLowerCase();
|
|
52
|
+
let valsL = values.slice(-2);
|
|
53
|
+
let p = { x: valsL[0], y: valsL[1] };
|
|
54
|
+
|
|
55
|
+
// segment command points - including previous final on-path
|
|
56
|
+
let pts = [p0, p]
|
|
57
|
+
if (type === 'C' || type === 'Q') pts.push({ x: values[0], y: values[1] })
|
|
58
|
+
if (type === 'C') pts.push({ x: values[2], y: values[3] })
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
// get relative threshold based on averaged command dimensions
|
|
62
|
+
let xArr = pts.map(pt => { return pt.x });
|
|
63
|
+
let yArr = pts.map(pt => { return pt.y });
|
|
64
|
+
let xMax = Math.max(...xArr)
|
|
65
|
+
let xMin = Math.min(...xArr)
|
|
66
|
+
let yMax = Math.max(...yArr)
|
|
67
|
+
let yMin = Math.min(...yArr)
|
|
68
|
+
|
|
69
|
+
let w = xMax - xMin
|
|
70
|
+
let h = yMax - yMin
|
|
71
|
+
let dimA = (w + h) / 2 || 0;
|
|
72
|
+
|
|
73
|
+
if (type.toLowerCase() !== 'z') {
|
|
74
|
+
|
|
75
|
+
// zero length
|
|
76
|
+
//|| (type==='L' && dimA<tolerance)
|
|
77
|
+
if ((p.x === p0.x && p.y === p0.y) || (type === 'L' && dimA < tolerance)) {
|
|
78
|
+
//console.log('zero', com, dimA, tolerance, w, h);
|
|
79
|
+
if (debug) simplyfy_debug_log.push(`removed zero length ${type}`)
|
|
80
|
+
continue
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* simplify adjacent linetos
|
|
85
|
+
* based on their flatness
|
|
86
|
+
*/
|
|
87
|
+
else if (type === 'L') {
|
|
88
|
+
|
|
89
|
+
//unnessecary closing linto
|
|
90
|
+
if (removeClosingLines && p.x === M.x && p.y === M.y && comN.type.toLowerCase() === 'z') {
|
|
91
|
+
if (debug) simplyfy_debug_log.push(`unnessecary closing linto`)
|
|
92
|
+
continue
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
if (comN.type === 'L') {
|
|
97
|
+
|
|
98
|
+
let valuesNL = comN.values.slice(-2)
|
|
99
|
+
let pN = { x: valuesNL[0], y: valuesNL[1] }
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
// check if adjacent linetos are flat
|
|
103
|
+
//let flatness = commandIsFlat([p0, p, pN], tolerance)
|
|
104
|
+
let flatness = commandIsFlat([p0, p, pN], tolerance)
|
|
105
|
+
let isFlatN = flatness.flat;
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
// next lineto is flat – don't add command
|
|
109
|
+
if (isFlatN) {
|
|
110
|
+
//console.log('flat', flatness, [p0, p, pN]);
|
|
111
|
+
if (debug) simplyfy_debug_log.push(`remove flat linetos`)
|
|
112
|
+
continue
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
if (type === 'C') {
|
|
119
|
+
/**
|
|
120
|
+
* detect flat beziers
|
|
121
|
+
* often used for morphing
|
|
122
|
+
* animation
|
|
123
|
+
*/
|
|
124
|
+
|
|
125
|
+
let cp1 = { x: values[0], y: values[1] }
|
|
126
|
+
let cp2 = { x: values[2], y: values[3] }
|
|
127
|
+
let pts = [p0, cp1, cp2, p];
|
|
128
|
+
|
|
129
|
+
let flatness = commandIsFlat(pts, tolerance)
|
|
130
|
+
let isFlat = flatness.flat
|
|
131
|
+
let ratio = flatness.ratio;
|
|
132
|
+
//console.log('flatness', flatness);
|
|
133
|
+
|
|
134
|
+
/*
|
|
135
|
+
// zero length control points
|
|
136
|
+
let zeroCpts = (cp1.x === p0.x && cp1.y === p0.y && ratio<0.2 ) || (cp2.x === p.x && cp2.y === p.y && ratio<0.2 );
|
|
137
|
+
if(zeroCpts){
|
|
138
|
+
console.log('flat cpts');
|
|
139
|
+
//com = { type: 'L', values: [p.x, p.y] };
|
|
140
|
+
}
|
|
141
|
+
*/
|
|
142
|
+
|
|
143
|
+
if(isFlat){
|
|
144
|
+
//console.log('!!!flat cpts');
|
|
145
|
+
//com = { type: 'L', values: [p.x, p.y] };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
let valuesNL = comN ? comN.values.slice(-2) : '';
|
|
151
|
+
let pN = valuesNL.length ? { x: valuesNL[0], y: valuesNL[1] } : ''
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
//check adjacent flat C - convert to linetos
|
|
155
|
+
if (isFlat) {
|
|
156
|
+
|
|
157
|
+
let flatnessN, isFlatN=false;
|
|
158
|
+
|
|
159
|
+
if (comN.type === 'C') {
|
|
160
|
+
|
|
161
|
+
// check if adjacent curves are also flat
|
|
162
|
+
flatnessN = commandIsFlat([p0, p, pN], tolerance)
|
|
163
|
+
isFlatN = flatnessN.flat;
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
if (isFlatN) {
|
|
167
|
+
//console.log('is flat');
|
|
168
|
+
//console.log(flatnessN);
|
|
169
|
+
if (debug) simplyfy_debug_log.push(`skip cubic - actually a lineto: area-ratio: ${ratio}, flatness next:${flatnessN}`)
|
|
170
|
+
|
|
171
|
+
//com = { type: 'L', values: [p.x, p.y] };
|
|
172
|
+
//continue
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (ratio < 0.1 ) {
|
|
177
|
+
//console.log('simplify cubic to lineto');
|
|
178
|
+
simplyfy_debug_log.push(`simplify cubic to lineto`)
|
|
179
|
+
//com = { type: 'L', values: [p.x, p.y] };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
}
|
|
184
|
+
// not flat
|
|
185
|
+
else {
|
|
186
|
+
// add extremes
|
|
187
|
+
if (addExtremes) {
|
|
188
|
+
addedExtremes = addExtemesToCommand(p0, values);
|
|
189
|
+
com = addedExtremes.pathData
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
//add extremes
|
|
193
|
+
if (addExtremes && addedExtremes.count) simplyfy_debug_log.push(`added extremes: ${addedExtremes.count}`)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// add new commands
|
|
200
|
+
if (com.length) {
|
|
201
|
+
pathDataNew.push(...com);
|
|
202
|
+
} else {
|
|
203
|
+
pathDataNew.push(com);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
if (type.toLowerCase() === "z") {
|
|
208
|
+
p0 = M;
|
|
209
|
+
} else if (type === "M") {
|
|
210
|
+
M = { x: valsL[0], y: valsL[1] };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// new previous point
|
|
214
|
+
p0 = { x: valsL[0], y: valsL[1] };
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
}//end for
|
|
219
|
+
|
|
220
|
+
//optimize starting point
|
|
221
|
+
pathDataNew = optimizeStartingPoints(pathDataNew, removeClosingLines, startToTop);
|
|
222
|
+
|
|
223
|
+
simplyfy_debug_log.push(`original command count: ${pathData.length}; removed:${pathData.length - pathDataNew.length} `)
|
|
224
|
+
|
|
225
|
+
if (debug) console.log(simplyfy_debug_log);
|
|
226
|
+
|
|
227
|
+
//console.log(pathData.length, pathDataNew.length)
|
|
228
|
+
return pathDataNew;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* avoids starting points in the middle of 2 smooth curves
|
|
234
|
+
* can replace linetos with closepaths
|
|
235
|
+
*/
|
|
236
|
+
|
|
237
|
+
export function optimizeStartingPoints(pathData, removeFinalLineto = false, startToTop = false) {
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
let pathDataArr = splitSubpaths(pathData);
|
|
241
|
+
//console.log(pathDataArr);
|
|
242
|
+
|
|
243
|
+
let pathDataNew = [];
|
|
244
|
+
let len = pathDataArr.length;
|
|
245
|
+
|
|
246
|
+
for (let i = 0; i < len; i++) {
|
|
247
|
+
let pathData = pathDataArr[i]
|
|
248
|
+
|
|
249
|
+
// move starting point to first lineto
|
|
250
|
+
let firstLIndex = pathData.findIndex(cmd => cmd.type === 'L');
|
|
251
|
+
let firstBezierIndex = pathData.findIndex(cmd => cmd.type === 'C' || cmd.type === 'Q');
|
|
252
|
+
let commands = new Set([...pathData.map(com => com.type)]);
|
|
253
|
+
let hasLinetos = commands.has('L')
|
|
254
|
+
let hasBeziers = commands.has('C') || commands.has('Q')
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
let len = pathData.length
|
|
258
|
+
let isClosed = pathData[len - 1].type.toLowerCase() === 'z'
|
|
259
|
+
|
|
260
|
+
if (!isClosed) {
|
|
261
|
+
pathDataNew.push(...pathData);
|
|
262
|
+
continue
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (isClosed) {
|
|
266
|
+
|
|
267
|
+
let extremeIndex = -1;
|
|
268
|
+
let newIndex = 0
|
|
269
|
+
|
|
270
|
+
if (startToTop) {
|
|
271
|
+
//get top most index
|
|
272
|
+
let indices = [];
|
|
273
|
+
for (let i = 0, len = pathData.length; i < len; i++) {
|
|
274
|
+
let com = pathData[i];
|
|
275
|
+
let { type, values } = com;
|
|
276
|
+
if (values.length) {
|
|
277
|
+
|
|
278
|
+
let valsL = values.slice(-2)
|
|
279
|
+
let p = { x: valsL[0], y: valsL[1], index: i }
|
|
280
|
+
indices.push(p)
|
|
281
|
+
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
// find top most
|
|
287
|
+
indices = indices.sort((a, b) => {
|
|
288
|
+
a.y - b.y
|
|
289
|
+
//a.x - b.x || a.y - b.y
|
|
290
|
+
/*
|
|
291
|
+
let n = b.y - a.y;
|
|
292
|
+
if (n !== 0) {
|
|
293
|
+
return n;
|
|
294
|
+
}
|
|
295
|
+
return b.x - a.x;
|
|
296
|
+
*/
|
|
297
|
+
});
|
|
298
|
+
newIndex = indices[0].index
|
|
299
|
+
|
|
300
|
+
} else {
|
|
301
|
+
//find extreme
|
|
302
|
+
let pathPoly = getPathDataVertices(pathData);
|
|
303
|
+
let bb = getPolyBBox(pathPoly)
|
|
304
|
+
let { left, right, top, bottom, width, height } = bb;
|
|
305
|
+
let minX = Infinity;
|
|
306
|
+
let minY = Infinity;
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
// get extreme
|
|
310
|
+
if (hasBeziers) {
|
|
311
|
+
|
|
312
|
+
for (let i = 0, len = pathData.length; i < len; i++) {
|
|
313
|
+
let com = pathData[i];
|
|
314
|
+
let { type, values } = com;
|
|
315
|
+
if (type === 'C' || type === 'Q') {
|
|
316
|
+
|
|
317
|
+
let valsL = values.slice(-2)
|
|
318
|
+
let p = { x: valsL[0], y: valsL[1] }
|
|
319
|
+
// is extreme relative to bounding box
|
|
320
|
+
if (p.x === left || p.y === top || p.x === right || p.y === bottom) {
|
|
321
|
+
if (p.x < minX && p.y < minY) {
|
|
322
|
+
extremeIndex = i;
|
|
323
|
+
minX = p.x;
|
|
324
|
+
minY = p.y
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
// set to first bezier extreme or first L
|
|
333
|
+
firstBezierIndex = extremeIndex > -1 ? extremeIndex : firstBezierIndex
|
|
334
|
+
newIndex = hasLinetos ? firstLIndex : firstBezierIndex;
|
|
335
|
+
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
// reorder
|
|
340
|
+
pathData = shiftSvgStartingPoint(pathData, newIndex)
|
|
341
|
+
len = pathData.length
|
|
342
|
+
|
|
343
|
+
// remove last lineto
|
|
344
|
+
let M = { x: pathData[0].values[0], y: pathData[0].values[1] }
|
|
345
|
+
let penultimateCom = pathData[len - 2];
|
|
346
|
+
let penultimateType = penultimateCom.type;
|
|
347
|
+
let penultimateComCoords = penultimateCom.values.slice(-2)
|
|
348
|
+
|
|
349
|
+
let isClosingCommand = penultimateType === 'L' && penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y
|
|
350
|
+
|
|
351
|
+
if (removeFinalLineto && isClosingCommand) {
|
|
352
|
+
//console.log('remove l');
|
|
353
|
+
pathData.splice(len - 2, 1)
|
|
354
|
+
}
|
|
355
|
+
pathDataNew.push(...pathData);
|
|
356
|
+
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return pathDataNew
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { checkLineIntersection, getAngle, getPointOnEllipse, getSquareDistance, pointAtT, reducePoints } from "./geometry";
|
|
2
|
+
import { getPolygonArea } from "./geometry_area";
|
|
3
|
+
import { getPolyBBox } from "./geometry_bbox";
|
|
4
|
+
import { renderPoint } from "./visualize";
|
|
5
|
+
|
|
6
|
+
export function analyzePoly(pts) {
|
|
7
|
+
|
|
8
|
+
let l = pts.length;
|
|
9
|
+
let polyArea = getPolygonArea(pts, true)
|
|
10
|
+
//console.log(polyArea);
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
// get areas
|
|
14
|
+
for (let i = 0; i < l; i++) {
|
|
15
|
+
let pt0 = i > 0 ? pts[i - 1] : pts[l - 1];
|
|
16
|
+
let pt1 = pts[i];
|
|
17
|
+
let pt2 = i < l - 1 ? pts[i + 1] : pts[0];
|
|
18
|
+
|
|
19
|
+
let area = getPolygonArea([pt0, pt1, pt2], false);
|
|
20
|
+
let ang1 = getAngle(pt1, pt0, true);
|
|
21
|
+
let ang2 = getAngle(pt1, pt2, true);
|
|
22
|
+
let delta = Math.abs(ang1 - ang2);
|
|
23
|
+
let deltaDeg = delta * 180 / Math.PI;
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* get local extremes
|
|
29
|
+
* my coincide with corners or
|
|
30
|
+
* direction changes
|
|
31
|
+
*/
|
|
32
|
+
let { left, right, top, bottom } = getPolyBBox([pt0, pt2]);
|
|
33
|
+
let isExtreme = (pt1.x < left || pt1.x > right || pt1.y < top || pt1.y > bottom);
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* check corners by
|
|
38
|
+
* adjacent angle differences
|
|
39
|
+
*/
|
|
40
|
+
let isCorner = deltaDeg < 120 || deltaDeg > 270;
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* get direction changes
|
|
45
|
+
* e.g the spine of a "S" shape
|
|
46
|
+
*/
|
|
47
|
+
let directionChange = pt0.isCorner === false && ((pt0.area < 0 && area > 0) || (pt0.area > 0 && area < 0));
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
if (pt0.isExtreme &&
|
|
52
|
+
(pt1.y === pt0.y || pt1.x === pt0.x)
|
|
53
|
+
) {
|
|
54
|
+
isExtreme = true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
if (directionChange && isExtreme) {
|
|
59
|
+
isCorner = true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// if segment is too large relative to total area - don't interpret as corner
|
|
63
|
+
let areaRat = Math.abs(area / polyArea);
|
|
64
|
+
|
|
65
|
+
if (areaRat > 0.2) {
|
|
66
|
+
isCorner = false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* visualize significant points for
|
|
72
|
+
* debugging
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
/*
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
if ((isExtreme && isCorner)) {
|
|
79
|
+
isExtreme = false;
|
|
80
|
+
directionChange = false;
|
|
81
|
+
//isCorner = false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (isExtreme) {
|
|
85
|
+
renderPoint(markers, pt1, 'cyan', '1%');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (isCorner) {
|
|
89
|
+
renderPoint(markers, pt1, 'purple', '0.5%');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (directionChange) {
|
|
93
|
+
renderPoint(markers, pt1, 'orange', '1.5%', '0.5');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* save point analysis properties
|
|
99
|
+
* to point objects
|
|
100
|
+
*/
|
|
101
|
+
pt1.isExtreme = isExtreme;
|
|
102
|
+
pt1.isCorner = isCorner;
|
|
103
|
+
pt1.directionChange = directionChange;
|
|
104
|
+
|
|
105
|
+
pt1.area = area;
|
|
106
|
+
pt1.delta = delta;
|
|
107
|
+
pt1.deltaDeg = deltaDeg;
|
|
108
|
+
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
//getControlPoints(pts)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
return pts
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
export function getPathDataChunks(pathData) {
|
|
126
|
+
|
|
127
|
+
let chunks = [[]];
|
|
128
|
+
let lastType = 'M'
|
|
129
|
+
let ind = 0;
|
|
130
|
+
let wasExtreme, wasCorner, wasDirectionchange;
|
|
131
|
+
|
|
132
|
+
pathData.forEach(com => {
|
|
133
|
+
|
|
134
|
+
let { isCorner, isExtreme, directionChange, type } = com;
|
|
135
|
+
|
|
136
|
+
if (type !== lastType || wasExtreme || wasCorner || directionChange || wasDirectionchange) {
|
|
137
|
+
chunks.push([])
|
|
138
|
+
ind++
|
|
139
|
+
}
|
|
140
|
+
chunks[ind].push(com)
|
|
141
|
+
|
|
142
|
+
wasExtreme = isExtreme
|
|
143
|
+
wasCorner = isCorner
|
|
144
|
+
wasDirectionchange = directionChange;
|
|
145
|
+
lastType = type
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
return chunks;
|
|
150
|
+
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* check whether a polygon is likely
|
|
158
|
+
* to be closed
|
|
159
|
+
* or an open polyline
|
|
160
|
+
*/
|
|
161
|
+
export function isClosedPolygon(pts, reduce = 24) {
|
|
162
|
+
|
|
163
|
+
let ptsR = reducePoints(pts, reduce);
|
|
164
|
+
let { width, height } = getPolyBBox(ptsR);
|
|
165
|
+
//let dimAvg = Math.max(width, height);
|
|
166
|
+
let dimAvg = (width + height) / 2;
|
|
167
|
+
//let closingThresh = (dimAvg / pts.length) ** 2
|
|
168
|
+
let closingThresh = (dimAvg) ** 2
|
|
169
|
+
let closingDist = getSquareDistance(pts[0], pts[pts.length - 1]);
|
|
170
|
+
|
|
171
|
+
return closingDist < closingThresh;
|
|
172
|
+
}
|