geojs 1.8.8 → 1.9.0

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.
@@ -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
+ };
@@ -136,7 +136,6 @@ var webgl_lineFeature = function (arg) {
136
136
  v = vert[1],
137
137
  target_gcs = m_this.gcs(),
138
138
  map_gcs = m_this.layer().map().gcs(),
139
- simpleInverse = transform.onlyInvertedY(target_gcs, map_gcs),
140
139
  pos, posIdx3, firstpos, firstPosIdx3,
141
140
  lineFunc = m_this.line(),
142
141
  strokeWidthFunc = m_this.style.get('strokeWidth'), strokeWidthVal,
@@ -147,6 +146,7 @@ var webgl_lineFeature = function (arg) {
147
146
  strokeOffsetFunc = m_this.style.get('strokeOffset'), strokeOffsetVal,
148
147
  miterLimit = m_this.style.get('miterLimit')(data),
149
148
  antialiasing = m_this.style.get('antialiasing')(data) || 0,
149
+ uniformFunc = m_this.style.get('uniformLine'), uniformVal, uniform,
150
150
  order = m_this.featureVertices(), orderk0, prevkey, nextkey, offkey,
151
151
  orderLen = order.length,
152
152
  // webgl buffers; see _init for details
@@ -165,6 +165,7 @@ var webgl_lineFeature = function (arg) {
165
165
  strokeOffsetVal = util.isFunction(m_this.style('strokeOffset')) ? undefined : (strokeOffsetFunc() || 0);
166
166
  strokeOpacityVal = util.isFunction(m_this.style('strokeOpacity')) ? undefined : strokeOpacityFunc();
167
167
  strokeWidthVal = util.isFunction(m_this.style('strokeWidth')) ? undefined : strokeWidthFunc();
168
+ uniformVal = util.isFunction(m_this.style('uniformLine')) ? undefined : uniformFunc();
168
169
 
169
170
  if (miterLimit !== undefined) {
170
171
  /* We impose a limit no matter what, since otherwise the growth is
@@ -192,7 +193,7 @@ var webgl_lineFeature = function (arg) {
192
193
  for (j = 0; j < lineItem.length; j += 1) {
193
194
  pos = posFunc ? posFunc(lineItem[j], j, d, i) : lineItem[j];
194
195
  position.push(pos.x);
195
- position.push(simpleInverse ? -pos.y : pos.y);
196
+ position.push(pos.y);
196
197
  position.push(pos.z || 0.0);
197
198
  if (!j) {
198
199
  firstpos = pos;
@@ -212,9 +213,7 @@ var webgl_lineFeature = function (arg) {
212
213
  }
213
214
  }
214
215
 
215
- if (!simpleInverse) {
216
- position = transform.transformCoordinates(target_gcs, map_gcs, position, 3);
217
- }
216
+ position = transform.transformCoordinates(target_gcs, map_gcs, position, 3);
218
217
  m_origin = new Float32Array(m_this.style.get('origin')(position));
219
218
  if (m_origin[0] || m_origin[1] || m_origin[2]) {
220
219
  for (i = 0; i < position.length; i += 3) {
@@ -265,6 +264,7 @@ var webgl_lineFeature = function (arg) {
265
264
  if (lineItem.length < 2) {
266
265
  continue;
267
266
  }
267
+ uniform = uniformVal === undefined ? uniformFunc(lineItem, i) : uniformVal;
268
268
  d = data[i];
269
269
  closedVal = closed[i];
270
270
  firstPosIdx3 = posIdx3;
@@ -291,12 +291,26 @@ var webgl_lineFeature = function (arg) {
291
291
  (j !== lidx ? firstPosIdx3 + 3 : firstPosIdx3 + 6 - closedVal * 3) :
292
292
  posIdx3);
293
293
  }
294
- v.strokeWidth = strokeWidthVal === undefined ? strokeWidthFunc(lineItemData, lidx, d, i) : strokeWidthVal;
295
- v.strokeColor = strokeColorVal === undefined ? strokeColorFunc(lineItemData, lidx, d, i) : strokeColorVal;
296
- v.strokeOpacity = strokeOpacityVal === undefined ? strokeOpacityFunc(lineItemData, lidx, d, i) : strokeOpacityVal;
294
+ if (uniform && j > 0) {
295
+ if (j === 1) {
296
+ v.strokeWidth = vert[0].strokeWidth;
297
+ v.strokeColor = vert[0].strokeColor;
298
+ v.strokeOpacity = vert[0].strokeOpacity;
299
+ }
300
+ } else {
301
+ v.strokeWidth = strokeWidthVal === undefined ? strokeWidthFunc(lineItemData, lidx, d, i) : strokeWidthVal;
302
+ v.strokeColor = strokeColorVal === undefined ? strokeColorFunc(lineItemData, lidx, d, i) : strokeColorVal;
303
+ v.strokeOpacity = strokeOpacityVal === undefined ? strokeOpacityFunc(lineItemData, lidx, d, i) : strokeOpacityVal;
304
+ }
297
305
  if (updateFlags) {
298
306
  if (strokeOffsetVal !== 0) {
299
- v.strokeOffset = (strokeOffsetVal === undefined ? strokeOffsetFunc(lineItemData, lidx, d, i) : strokeOffsetVal) || 0;
307
+ if (uniform && j > 0) {
308
+ if (j === 1) {
309
+ v.strokeOffset = vert[0].strokeOffset;
310
+ }
311
+ } else {
312
+ v.strokeOffset = (strokeOffsetVal === undefined ? strokeOffsetFunc(lineItemData, lidx, d, i) : strokeOffsetVal) || 0;
313
+ }
300
314
  if (v.strokeOffset) {
301
315
  /* we use 11 bits to store the offset, and we want to store values
302
316
  * from -1 to 1, so multiply our values by 1023, and use some bit
@@ -77,12 +77,11 @@ var webgl_polygonFeature = function (arg) {
77
77
  fillColor, fillColorFunc, fillColorVal,
78
78
  fillOpacity, fillOpacityFunc, fillOpacityVal,
79
79
  fillFunc, fillVal,
80
- uniformPolyFunc, uniform,
80
+ uniformFunc, uniformVal, uniform,
81
81
  indices,
82
82
  items = [],
83
83
  target_gcs = m_this.gcs(),
84
84
  map_gcs = m_this.layer().map().gcs(),
85
- simpleInverse = transform.onlyInvertedY(target_gcs, map_gcs),
86
85
  numPts = 0,
87
86
  geom = m_mapper.geometryData(),
88
87
  color, opacity, fill, d, d3, vertices, i, j, k, n,
@@ -94,7 +93,8 @@ var webgl_polygonFeature = function (arg) {
94
93
  fillOpacityVal = util.isFunction(m_this.style('fillOpacity')) ? undefined : fillOpacityFunc();
95
94
  fillFunc = m_this.style.get('fill');
96
95
  fillVal = util.isFunction(m_this.style('fill')) ? undefined : fillFunc();
97
- uniformPolyFunc = m_this.style.get('uniformPolygon');
96
+ uniformFunc = m_this.style.get('uniformPolygon');
97
+ uniformVal = util.isFunction(m_this.style('uniformPolygon')) ? undefined : uniformFunc();
98
98
 
99
99
  if (!onlyStyle) {
100
100
  posFunc = m_this.style.get('position');
@@ -120,7 +120,7 @@ var webgl_polygonFeature = function (arg) {
120
120
  for (i = d3 = 0; i < outer.length; i += 1, d3 += 3) {
121
121
  c = posFunc ? posFunc(outer[i], i, item, itemIndex) : outer[i];
122
122
  geometry[d3] = c.x;
123
- geometry[d3 + 1] = simpleInverse ? -c.y : c.y;
123
+ geometry[d3 + 1] = c.y;
124
124
  // ignore the z values until we support them
125
125
  geometry[d3 + 2] = 0; // c.z || 0;
126
126
  }
@@ -137,7 +137,7 @@ var webgl_polygonFeature = function (arg) {
137
137
  for (i = 0; i < hole.length; i += 1, d3 += 3) {
138
138
  c = posFunc ? posFunc(hole[i], i, item, itemIndex) : hole[i];
139
139
  geometry.vertices[d3] = c.x;
140
- geometry.vertices[d3 + 1] = simpleInverse ? -c.y : c.y;
140
+ geometry.vertices[d3 + 1] = c.y;
141
141
  // ignore the z values until we support them
142
142
  geometry.vertices[d3 + 2] = 0; // c.z || 0;
143
143
  }
@@ -145,14 +145,12 @@ var webgl_polygonFeature = function (arg) {
145
145
  }
146
146
 
147
147
  // transform to map gcs
148
- if (!simpleInverse) {
149
- geometry.vertices = transform.transformCoordinates(
150
- target_gcs,
151
- map_gcs,
152
- geometry.vertices,
153
- geometry.dimensions
154
- );
155
- }
148
+ geometry.vertices = transform.transformCoordinates(
149
+ target_gcs,
150
+ map_gcs,
151
+ geometry.vertices,
152
+ geometry.dimensions
153
+ );
156
154
 
157
155
  record = {
158
156
  // triangulate
@@ -191,7 +189,7 @@ var webgl_polygonFeature = function (arg) {
191
189
  item = items[k].item;
192
190
  itemIndex = items[k].itemIndex;
193
191
  original = items[k].original;
194
- uniform = uniformPolyFunc(item, itemIndex);
192
+ uniform = uniformVal === undefined ? uniformFunc(item, itemIndex) : uniformVal;
195
193
  opacity = fillOpacityVal;
196
194
  if (uniform) {
197
195
  if (fillColorVal === undefined) {