geojs 1.8.10 → 1.9.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/.github/workflows/main.yml +1 -1
- package/CHANGELOG.md +7 -0
- package/geo.js +3057 -176
- package/geo.lean.js +3056 -175
- package/geo.lean.min.js +10 -4
- package/geo.min.js +11 -5
- package/package.json +6 -5
- package/src/annotation/annotation.js +19 -2
- package/src/annotation/circleAnnotation.js +8 -1
- package/src/annotation/ellipseAnnotation.js +51 -1
- package/src/annotation/lineAnnotation.js +29 -20
- package/src/annotation/pointAnnotation.js +28 -23
- package/src/annotation/polygonAnnotation.js +52 -22
- package/src/annotation/rectangleAnnotation.js +42 -23
- package/src/annotation/squareAnnotation.js +8 -1
- package/src/annotationLayer.js +188 -2
- package/src/css/cursor-crosshair-difference.svg +4 -0
- package/src/css/cursor-crosshair-intersect.svg +4 -0
- package/src/css/cursor-crosshair-union.svg +4 -0
- package/src/css/cursor-crosshair-xor.svg +4 -0
- package/src/event.js +14 -0
- package/src/lineFeature.js +1 -1
- package/src/main.styl +8 -0
- package/src/polygonFeature.js +50 -0
- package/src/typedef.js +11 -0
- package/src/util/index.js +2 -1
- package/src/util/polyops.js +449 -0
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
var PolyBool = require('polybooljs');
|
|
2
|
+
var geo_map = require('../map');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A list of flat polygon lists.
|
|
6
|
+
*
|
|
7
|
+
* @typedef {Array.<Array.<Array.<geo.geoPositionFlat>>>} geo.polygonList
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A polygon in any of a variety of formats.
|
|
12
|
+
*
|
|
13
|
+
* This can be any object with a ``toPolygonList`` and ``fromPolygonList``
|
|
14
|
+
* method.
|
|
15
|
+
*
|
|
16
|
+
* @typedef {geo.polygonFlat|Array.<geo.polygonFlat>|Array.<Array.<geo.polygonFlat>>|geo.polygonObject|Array.<geo.polygonObject>|Array.<geo.geoPositionFlat>|Array.<Array.<geo.geoPositionFlat>>|geo.polygonList|object} geo.polygonAny
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Object specification for polygon operation options.
|
|
21
|
+
*
|
|
22
|
+
* @typedef {object} geo.util.polyop.spec
|
|
23
|
+
* @property {geo.polygonAny} [poly1] The first polygon set to operate on.
|
|
24
|
+
* @property {geo.polygonAny} [poly2] The second polygon set to operate on.
|
|
25
|
+
* @property {number} [epsilon1] A precision value to use when computing the
|
|
26
|
+
* operation. If not specified, this is computed from the range of values in
|
|
27
|
+
* ``poly1``. It is used for processing ``poly1`` and the general operation.
|
|
28
|
+
* @property {number} [epsilon1] A precision value to use when processing
|
|
29
|
+
* ``poly2``. If not specified, this is computed from the range of values in
|
|
30
|
+
* ``poly2``.
|
|
31
|
+
* @property {string|object} [style] If specified, the preferred output style.
|
|
32
|
+
* This can be (flat|object)[-list[list[-outer[-list]]]]. If an object,
|
|
33
|
+
* the object must have a method ``fromPolygonList``.
|
|
34
|
+
* @property {string|geo.transform} [ingcs] The default coordinate
|
|
35
|
+
* system of the source polygon coordinates. If not specified , this is
|
|
36
|
+
* taken from the feature first or the map second if either is available.
|
|
37
|
+
* @property {string|geo.transform} [gcs] The default coordinate system to use
|
|
38
|
+
* for the actual operations. This is where the epsilon and rdp values are
|
|
39
|
+
* applied. If not specified , this is taken from the map second if
|
|
40
|
+
* available.
|
|
41
|
+
* @property {geo.map} [map] Used for ``ingcs`` and ``gcs`` if needed.
|
|
42
|
+
* @property {string} [innerOperation="union"] one of union, intersect, xor.
|
|
43
|
+
* Used to combine individual polygons in each of ``poly1`` and ``poly2``
|
|
44
|
+
* before the main operation is carried out.
|
|
45
|
+
* @property {object} [correspond] If present, information about the
|
|
46
|
+
* correspondence of the input and output polygons is added to this object.
|
|
47
|
+
* This is of the form {poly1: [idx...], poly2: [idx...], exact1: [idx...],
|
|
48
|
+
* exact2: [idx...]}, where the [idx...] array is the length of the input
|
|
49
|
+
* polygon array and each entry is either undefined or contains a list of
|
|
50
|
+
* indices of corresponding output polygons. Exact means that all of the
|
|
51
|
+
* points in the input polygon are in the output polygon. Multiple inputs
|
|
52
|
+
* can refer to the same output.
|
|
53
|
+
* @property {number} [tolerance] A tolerance value to pass to features,
|
|
54
|
+
* annotations, or other object-based polygon generators to specify how
|
|
55
|
+
* closely non-polygons are converted to polygons. This is in the feature's
|
|
56
|
+
* gcs coordinate system.
|
|
57
|
+
* @property {number} [pixelTolerance] A tolerance value to pass to features,
|
|
58
|
+
* annotations, or other object-based polygon generators to specify how
|
|
59
|
+
* closely non-polygons are converted to polygons. This is interpreted in
|
|
60
|
+
* the feature's map's display coordinates.
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
const AlternateOpNames = {
|
|
64
|
+
'+': 'union',
|
|
65
|
+
'|': 'union',
|
|
66
|
+
add: 'union',
|
|
67
|
+
'-': 'difference',
|
|
68
|
+
sub: 'difference',
|
|
69
|
+
subtract: 'difference',
|
|
70
|
+
minus: 'difference',
|
|
71
|
+
'*': 'intersect',
|
|
72
|
+
'&': 'intersect',
|
|
73
|
+
mul: 'intersect',
|
|
74
|
+
multiply: 'intersect',
|
|
75
|
+
x: 'xor',
|
|
76
|
+
'^': 'xor'
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Convert a segment list to a polygon list.
|
|
81
|
+
*
|
|
82
|
+
* @param {object[]} seglist A PolyBool segment list.
|
|
83
|
+
* @returns {geo.polygonList} A polygon list.
|
|
84
|
+
*/
|
|
85
|
+
function seglistToPolygonList(seglist) {
|
|
86
|
+
// This single line doesn't arrange holes correctly
|
|
87
|
+
// return seglist.map((s) => PolyBool.polygon(s).regions);
|
|
88
|
+
const polys = [];
|
|
89
|
+
seglist.forEach((s) => {
|
|
90
|
+
const geojson = PolyBool.polygonToGeoJSON(PolyBool.polygon(s));
|
|
91
|
+
if (geojson.type === 'MultiPolygon') {
|
|
92
|
+
geojson.coordinates.forEach((p) => {
|
|
93
|
+
polys.push(p.map((h) => h.slice(0, h.length - 1)));
|
|
94
|
+
});
|
|
95
|
+
} else if (geojson.type === 'Polygon') {
|
|
96
|
+
polys.push(geojson.coordinates.map((h) => h.slice(0, h.length - 1)));
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
return polys;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Perform an boolean operation on a seglist from polygons.
|
|
104
|
+
*
|
|
105
|
+
* @param {string} op One of 'union', 'intersect', or other value PolyBool
|
|
106
|
+
* supports.
|
|
107
|
+
* @param {number} epsilon Precision for calculations. In degrees, 1e-9 is
|
|
108
|
+
* around 0.11 mm in ground distance.
|
|
109
|
+
* @param {object} seglist A seglist array as used by the polybool library.
|
|
110
|
+
* @returns {object} A seglist array.
|
|
111
|
+
*/
|
|
112
|
+
function polygonOperationSeglist(op, epsilon, seglist) {
|
|
113
|
+
op = 'select' + op.charAt(0).toUpperCase() + op.slice(1);
|
|
114
|
+
PolyBool.epsilon(epsilon);
|
|
115
|
+
|
|
116
|
+
while (seglist.length > 1) {
|
|
117
|
+
const newlist = [];
|
|
118
|
+
const half = Math.ceil(seglist.length / 2);
|
|
119
|
+
for (let i = 0; i < half; i += 1) {
|
|
120
|
+
let segments = seglist[i];
|
|
121
|
+
if (i + half < seglist.length) {
|
|
122
|
+
let nextseg = seglist[i + half];
|
|
123
|
+
try {
|
|
124
|
+
segments = PolyBool.combine(segments, nextseg);
|
|
125
|
+
} catch (err) {
|
|
126
|
+
segments = PolyBool.segments(PolyBool.polygon(segments));
|
|
127
|
+
nextseg = PolyBool.segments(PolyBool.polygon(nextseg));
|
|
128
|
+
for (let j = 20; j >= 6; j -= 1) {
|
|
129
|
+
PolyBool.epsilon(Math.pow(0.1, j));
|
|
130
|
+
try {
|
|
131
|
+
segments = PolyBool.combine(segments, nextseg);
|
|
132
|
+
break;
|
|
133
|
+
} catch (err) {}
|
|
134
|
+
}
|
|
135
|
+
PolyBool.epsilon(epsilon);
|
|
136
|
+
}
|
|
137
|
+
if (segments.combined) {
|
|
138
|
+
segments.combined = segments.combined.filter(s => Math.abs(s.start[0] - s.end[0]) > epsilon || Math.abs(s.start[1] - s.end[1]) > epsilon);
|
|
139
|
+
segments = PolyBool[op](segments);
|
|
140
|
+
} else {
|
|
141
|
+
console.warn('Failed in polygon functions.');
|
|
142
|
+
}
|
|
143
|
+
segments.segments = segments.segments.filter(s => Math.abs(s.start[0] - s.end[0]) > epsilon || Math.abs(s.start[1] - s.end[1]) > epsilon);
|
|
144
|
+
}
|
|
145
|
+
newlist.push(segments);
|
|
146
|
+
}
|
|
147
|
+
seglist = newlist;
|
|
148
|
+
}
|
|
149
|
+
return seglist;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Convert a polygon in any of several formats to a polygon list. Each polygon
|
|
154
|
+
* in the list is a list of polylines. Each polyline is a list of points.
|
|
155
|
+
* Each point is a list of two coordinates. The input can be a variety of
|
|
156
|
+
* formats:
|
|
157
|
+
* - object, flat: a list of {x, y} or [x, y]
|
|
158
|
+
* - object-list, flat-list: a list of lists of {x, y} or [x, y]
|
|
159
|
+
* - object-listlist, flat-listlist: a list if lists of lists of {x, y} or
|
|
160
|
+
* [x, y]
|
|
161
|
+
* - object-listlist-outer, flat-listlist-outer: {outer: {x, y}, inner:
|
|
162
|
+
* [{x, y}]} or {outer: [x, y], inner: [[x, y]]}
|
|
163
|
+
* - object-listlist-outer-list, flat-listlist-outer-list: a list of objects
|
|
164
|
+
* in outer/inner object or outer/inner list form
|
|
165
|
+
* The actual format is stored in the mode object.
|
|
166
|
+
*
|
|
167
|
+
* @param {geo.polygonAny} poly A polygon in one of the above formats to
|
|
168
|
+
* convert.
|
|
169
|
+
* @param {object} mode An object that is modified with information about
|
|
170
|
+
* the conversion. This includes style: the input polygon format; min: a
|
|
171
|
+
* 2-item list with minimum values in x, y; max: a 2-item list with
|
|
172
|
+
* maximum values in x, y; epsilon: a recommended value for epsilon in other
|
|
173
|
+
* functions.
|
|
174
|
+
* @param {geo.util.polyop.spec} [opts] Options for the operation. Only used
|
|
175
|
+
* if poly is an object with a toPolygonList method.
|
|
176
|
+
* @returns {geo.polygonList} A list of polygons.
|
|
177
|
+
*/
|
|
178
|
+
function toPolygonList(poly, mode, opts) {
|
|
179
|
+
mode = mode || {};
|
|
180
|
+
|
|
181
|
+
if (poly.toPolygonList) {
|
|
182
|
+
mode.style = poly;
|
|
183
|
+
poly = poly.toPolygonList(opts);
|
|
184
|
+
if (!poly.length) {
|
|
185
|
+
mode.min = mode.max = [0, 0];
|
|
186
|
+
mode.epsilon = 1e-10;
|
|
187
|
+
return poly;
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
mode.style = '';
|
|
191
|
+
if (poly.outer) {
|
|
192
|
+
mode.style = '-outer';
|
|
193
|
+
poly = [[poly.outer, ...(poly.inner || [])]];
|
|
194
|
+
} else if (poly.length && poly[0].outer) {
|
|
195
|
+
mode.style = '-outer-list';
|
|
196
|
+
poly = poly.map((p) => [p.outer, ...(p.inner || [])]);
|
|
197
|
+
}
|
|
198
|
+
if (!poly.length) {
|
|
199
|
+
mode.style = 'flat-listlist' + mode.style;
|
|
200
|
+
mode.min = mode.max = [0, 0];
|
|
201
|
+
mode.epsilon = 1e-10;
|
|
202
|
+
return poly;
|
|
203
|
+
}
|
|
204
|
+
if (poly[0].x !== undefined) {
|
|
205
|
+
mode.style = 'object';
|
|
206
|
+
poly = [[poly.map((pt) => [pt.x, pt.y])]];
|
|
207
|
+
} else if (poly[0][0].x !== undefined) {
|
|
208
|
+
mode.style = 'object-list' + mode.style;
|
|
209
|
+
poly = [poly.map((p) => p.map((pt) => [pt.x, pt.y]))];
|
|
210
|
+
} else if (poly[0][0][0] === undefined) {
|
|
211
|
+
mode.style = 'flat';
|
|
212
|
+
poly = [[poly]];
|
|
213
|
+
} else if (poly[0][0][0].x !== undefined) {
|
|
214
|
+
mode.style = 'object-listlist' + mode.style;
|
|
215
|
+
poly = poly.map((p) => p.map((l) => l.map((pt) => [pt.x, pt.y])));
|
|
216
|
+
} else if (poly[0][0][0][0] === undefined) {
|
|
217
|
+
mode.style = 'flat-list';
|
|
218
|
+
poly = [poly];
|
|
219
|
+
} else {
|
|
220
|
+
mode.style = 'flat-listlist' + mode.style;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
mode.min = [poly[0][0][0][0], poly[0][0][0][1]];
|
|
224
|
+
mode.max = [poly[0][0][0][0], poly[0][0][0][1]];
|
|
225
|
+
poly.forEach((p) => p.forEach((l) => l.forEach((pt) => {
|
|
226
|
+
if (pt[0] < mode.min[0]) { mode.min[0] = pt[0]; }
|
|
227
|
+
if (pt[0] > mode.max[0]) { mode.max[0] = pt[0]; }
|
|
228
|
+
if (pt[1] < mode.min[1]) { mode.min[1] = pt[1]; }
|
|
229
|
+
if (pt[1] > mode.max[1]) { mode.max[1] = pt[1]; }
|
|
230
|
+
})));
|
|
231
|
+
mode.epsilon = Math.max(Math.max(mode.max[0], mode.max[1]), Math.max(Math.abs(mode.min[0]), Math.abs(mode.min[1]))) * 1e-10;
|
|
232
|
+
return poly;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Convert a polygon list to another format.
|
|
237
|
+
*
|
|
238
|
+
* @param {geo.polygonList} poly A list of polygons.
|
|
239
|
+
* @param {object} mode An object that is modified with information about the
|
|
240
|
+
* conversion. This includes style: the input polygon format; min: a 2-item
|
|
241
|
+
* list with minimum values in x, y; max: a 2-item list with maximum values
|
|
242
|
+
* in x, y; epsilon: a recommended value for epsilon in other functions.
|
|
243
|
+
* @param {geo.util.polyop.spec} [opts] Options for the operation. Only used
|
|
244
|
+
* if ``mode.style`` is an object with a fromPolygonList method.
|
|
245
|
+
* @returns {geo.polygonAny} A polygon in one of several formats.
|
|
246
|
+
*/
|
|
247
|
+
function fromPolygonList(poly, mode, opts) {
|
|
248
|
+
if (mode.style.fromPolygonList) {
|
|
249
|
+
return mode.style.fromPolygonList(poly, opts);
|
|
250
|
+
}
|
|
251
|
+
if (mode.style.includes('object')) {
|
|
252
|
+
poly = poly.map((p) => p.map((h) => h.map((pt) => ({x: pt[0], y: pt[1]}))));
|
|
253
|
+
}
|
|
254
|
+
if (mode.style.includes('outer')) {
|
|
255
|
+
poly = [poly.map((p) => ({outer: p[0], inner: p.slice(1)}))];
|
|
256
|
+
}
|
|
257
|
+
if (!mode.style.endsWith('listlist')) {
|
|
258
|
+
if (mode.style.endsWith('list')) {
|
|
259
|
+
poly = poly[0];
|
|
260
|
+
} else {
|
|
261
|
+
poly = poly[0][0];
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return poly;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Use a minimum style for output to include all of the results.
|
|
269
|
+
*
|
|
270
|
+
* @param {geo.polygonList} polylist A list of polygons.
|
|
271
|
+
* @param {string} style the proposed style.
|
|
272
|
+
* @returns {string} The preferred style.
|
|
273
|
+
*/
|
|
274
|
+
function minimumPolygonStyle(polylist, style) {
|
|
275
|
+
if (style.fromPolygonList) {
|
|
276
|
+
return style;
|
|
277
|
+
}
|
|
278
|
+
if (polylist.length > 1) {
|
|
279
|
+
if (style.includes('outer')) {
|
|
280
|
+
return style + (style.endsWith('list') ? '' : '-list');
|
|
281
|
+
}
|
|
282
|
+
if (style.endsWith('listlist')) {
|
|
283
|
+
return style;
|
|
284
|
+
}
|
|
285
|
+
return style + (style.endsWith('list') ? 'list' : '-listlist');
|
|
286
|
+
}
|
|
287
|
+
if (polylist.length === 1 && polylist[0].length > 1) {
|
|
288
|
+
if (style.includes('outer') || style.endsWith('list')) {
|
|
289
|
+
return style;
|
|
290
|
+
}
|
|
291
|
+
return style + '-list';
|
|
292
|
+
}
|
|
293
|
+
return style;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Generate the correspondence between the source polygons and the output
|
|
298
|
+
* polygons. A polygon corresponds if it has at least two points in common.
|
|
299
|
+
*
|
|
300
|
+
* @param {geo.polygonList} poly1 First set of source polygons.
|
|
301
|
+
* @param {geo.polygonList} poly2 Second set of source polygons.
|
|
302
|
+
* @param {geo.polygonList} newpoly Output polygons.
|
|
303
|
+
* @param {object} results An object to add results to.
|
|
304
|
+
*/
|
|
305
|
+
function generateCorrespondence(poly1, poly2, newpoly, results) {
|
|
306
|
+
const pts = {};
|
|
307
|
+
const counts = [];
|
|
308
|
+
|
|
309
|
+
newpoly.forEach((p, idx) => p.forEach((h) => h.forEach((pt) => {
|
|
310
|
+
const key = '_' + pt[0] + '_' + pt[1];
|
|
311
|
+
if (pts[key]) {
|
|
312
|
+
if (pts[key].indexOf(idx) === -1) {
|
|
313
|
+
pts[key].push(idx);
|
|
314
|
+
}
|
|
315
|
+
} else {
|
|
316
|
+
pts[key] = [idx];
|
|
317
|
+
}
|
|
318
|
+
if (counts.length <= idx) {
|
|
319
|
+
counts.push(0);
|
|
320
|
+
}
|
|
321
|
+
counts[idx] += 1;
|
|
322
|
+
})));
|
|
323
|
+
['poly1', 'poly2'].forEach((pkey) => {
|
|
324
|
+
const poly = pkey === 'poly1' ? poly1 : poly2;
|
|
325
|
+
const ekey = pkey === 'poly1' ? 'exact1' : 'exact2';
|
|
326
|
+
results[pkey] = Array(poly.length);
|
|
327
|
+
results[ekey] = Array(poly.length);
|
|
328
|
+
poly.forEach((p, idx) => {
|
|
329
|
+
const found = {};
|
|
330
|
+
p.forEach((h) => h.forEach((pt) => {
|
|
331
|
+
const key = '_' + pt[0] + '_' + pt[1];
|
|
332
|
+
if (pts[key]) {
|
|
333
|
+
pts[key].forEach((val) => {
|
|
334
|
+
found[val] = (found[val] || 0) + 1;
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}));
|
|
338
|
+
Object.keys(found).forEach((nidx) => {
|
|
339
|
+
if (found[nidx] === counts[+nidx]) {
|
|
340
|
+
if (!results[ekey][idx]) {
|
|
341
|
+
results[ekey][idx] = [];
|
|
342
|
+
}
|
|
343
|
+
results[ekey][idx].push(+nidx);
|
|
344
|
+
}
|
|
345
|
+
if (found[nidx] >= 2) {
|
|
346
|
+
if (!results[pkey][idx]) {
|
|
347
|
+
results[pkey][idx] = [];
|
|
348
|
+
}
|
|
349
|
+
results[pkey][idx].push(+nidx);
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Perform a general operation of a set of polygons.
|
|
358
|
+
*
|
|
359
|
+
* @param {string} op The operation to handle. One of union, intersect,
|
|
360
|
+
* difference, or xor.
|
|
361
|
+
* @param {geo.polygonAny|geo.util.polyop.spec} poly1 Either the first polygon
|
|
362
|
+
* set or an object containing all parameters for the function.
|
|
363
|
+
* @param {geo.polygonAny} [poly2] If the poly1 parameter is not a complete
|
|
364
|
+
* options object, the second polygon set for the operation.
|
|
365
|
+
* @param {geo.util.polyop.spec} [opts] If the poly1 parameter is not a
|
|
366
|
+
* complete options object, the options for the operation.
|
|
367
|
+
* @returns {geo.polygonAny} A polygon set in the same style as poly1.
|
|
368
|
+
*/
|
|
369
|
+
function generalOperationProcess(op, poly1, poly2, opts) {
|
|
370
|
+
var transform = require('../transform');
|
|
371
|
+
|
|
372
|
+
op = AlternateOpNames[op] || op;
|
|
373
|
+
if (poly1.poly1) {
|
|
374
|
+
opts = poly1;
|
|
375
|
+
poly1 = opts.poly1;
|
|
376
|
+
poly2 = opts.poly2;
|
|
377
|
+
}
|
|
378
|
+
opts = opts || {};
|
|
379
|
+
const ingcs1 = opts.ingcs || (
|
|
380
|
+
opts.map ? opts.map.ingcs() : (
|
|
381
|
+
poly1.gcs ? poly1.gcs() : (
|
|
382
|
+
poly1.layer && poly1.layer().gcs ? poly1.layer().gcs() : (
|
|
383
|
+
poly1.layer ? poly1.layer().map().ingcs() : (
|
|
384
|
+
poly1.map instanceof geo_map ? poly1.map().ingcs() : undefined)))));
|
|
385
|
+
const ingcs2 = opts.ingcs || (
|
|
386
|
+
opts.map ? opts.map.ingcs() : (
|
|
387
|
+
poly2.gcs ? poly2.gcs() : (
|
|
388
|
+
poly2.layer && poly2.layer().gcs ? poly2.layer().gcs() : (
|
|
389
|
+
poly2.layer ? poly2.layer().map().ingcs() : (
|
|
390
|
+
poly2.map instanceof geo_map ? poly2.map().ingcs() : ingcs1)))));
|
|
391
|
+
const gcs = opts.gcs || (
|
|
392
|
+
opts.map ? opts.map.gcs() : (
|
|
393
|
+
poly1.layer ? poly1.layer().map().gcs() : (
|
|
394
|
+
poly1.map instanceof geo_map ? poly1.map().gcs() : undefined)));
|
|
395
|
+
const mode1 = {};
|
|
396
|
+
const mode2 = {};
|
|
397
|
+
poly1 = toPolygonList(poly1, mode1, opts);
|
|
398
|
+
poly2 = toPolygonList(poly2, mode2, opts);
|
|
399
|
+
mode1.epsilon = opts.epsilon1 || mode1.epsilon;
|
|
400
|
+
mode2.epsilon = opts.epsilon2 || mode1.epsilon;
|
|
401
|
+
if (ingcs1 && gcs && ingcs1 !== gcs) {
|
|
402
|
+
poly1 = poly1.map((p) => p.map((h) => transform.transformCoordinates(ingcs1, gcs, h)));
|
|
403
|
+
}
|
|
404
|
+
if (ingcs2 && gcs && ingcs2 !== gcs) {
|
|
405
|
+
poly2 = poly2.map((p) => p.map((h) => transform.transformCoordinates(ingcs2, gcs, h)));
|
|
406
|
+
}
|
|
407
|
+
let seglist1 = poly1.map(p => PolyBool.segments({regions: p}));
|
|
408
|
+
let seglist2 = poly2.map(p => PolyBool.segments({regions: p}));
|
|
409
|
+
seglist1 = polygonOperationSeglist(opts.innerOperation || 'union', mode1.epsilon, seglist1);
|
|
410
|
+
seglist2 = polygonOperationSeglist(opts.innerOperation || 'union', mode2.epsilon, seglist2);
|
|
411
|
+
let seglist = seglist1;
|
|
412
|
+
if (seglist1[0] && seglist2[0]) {
|
|
413
|
+
/* We need to do the main operation with the same inversion flags */
|
|
414
|
+
seglist1[0].inverted = false;
|
|
415
|
+
seglist2[0].inverted = false;
|
|
416
|
+
seglist = polygonOperationSeglist(op, Math.max(mode1.epsilon, mode2.epsilon), [seglist1[0], seglist2[0]]);
|
|
417
|
+
} else if (seglist2[0]) {
|
|
418
|
+
seglist = seglist2;
|
|
419
|
+
}
|
|
420
|
+
let newpoly = seglistToPolygonList(seglist);
|
|
421
|
+
if (opts.correspond) {
|
|
422
|
+
generateCorrespondence(poly1, poly2, newpoly, opts.correspond);
|
|
423
|
+
}
|
|
424
|
+
if (ingcs1 && gcs && ingcs1 !== gcs) {
|
|
425
|
+
newpoly = newpoly.map((p) => p.map((h) => transform.transformCoordinates(gcs, ingcs1, h)));
|
|
426
|
+
}
|
|
427
|
+
const mode = {style: opts.style || minimumPolygonStyle(newpoly, mode1.style)};
|
|
428
|
+
return fromPolygonList(newpoly, mode, opts);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Generate a polygon function for a specific operation.
|
|
433
|
+
*
|
|
434
|
+
* @param {string} op The operation to handle. One of union, intersect,
|
|
435
|
+
* difference, or xor.
|
|
436
|
+
* @returns {function} a function for the polygons.
|
|
437
|
+
*/
|
|
438
|
+
function generalOperation(op) {
|
|
439
|
+
return (poly1, poly2, opts) => generalOperationProcess(op, poly1, poly2, opts);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
module.exports = {
|
|
443
|
+
union: generalOperation('union'),
|
|
444
|
+
intersect: generalOperation('intersect'),
|
|
445
|
+
difference: generalOperation('difference'),
|
|
446
|
+
xor: generalOperation('xor'),
|
|
447
|
+
toPolygonList,
|
|
448
|
+
fromPolygonList
|
|
449
|
+
};
|