@dra2020/baseclient 1.0.13 → 1.0.14
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/LICENSE +21 -0
- package/README.md +37 -0
- package/dist/all/all.d.ts +36 -0
- package/dist/all/allclient.d.ts +18 -0
- package/dist/base.js +33010 -0
- package/dist/base.js.map +1 -0
- package/dist/baseclient.js +8991 -0
- package/dist/baseclient.js.map +1 -0
- package/dist/context/all.d.ts +1 -0
- package/dist/context/context.d.ts +13 -0
- package/dist/dbabstract/all.d.ts +1 -0
- package/dist/dbabstract/db.d.ts +83 -0
- package/dist/dbdynamo/all.d.ts +1 -0
- package/dist/dbdynamo/dbdynamo.d.ts +190 -0
- package/dist/filterexpr/all.d.ts +1 -0
- package/dist/filterexpr/filterexpr.d.ts +64 -0
- package/dist/fsm/all.d.ts +1 -0
- package/dist/fsm/fsm.d.ts +118 -0
- package/dist/fsmfile/all.d.ts +1 -0
- package/dist/fsmfile/fsmfile.d.ts +47 -0
- package/dist/jsonstream/all.d.ts +1 -0
- package/dist/jsonstream/jsonstream.d.ts +130 -0
- package/dist/lambda/all.d.ts +1 -0
- package/dist/lambda/env.d.ts +10 -0
- package/dist/lambda/lambda.d.ts +18 -0
- package/dist/logabstract/all.d.ts +1 -0
- package/dist/logabstract/log.d.ts +26 -0
- package/dist/logclient/all.d.ts +1 -0
- package/dist/logclient/log.d.ts +6 -0
- package/dist/logserver/all.d.ts +5 -0
- package/dist/logserver/log.d.ts +11 -0
- package/dist/logserver/logaccum.d.ts +154 -0
- package/dist/logserver/logblob.d.ts +24 -0
- package/dist/logserver/logconcat.d.ts +55 -0
- package/dist/logserver/logkey.d.ts +28 -0
- package/dist/memsqs/all.d.ts +4 -0
- package/dist/memsqs/client.d.ts +13 -0
- package/dist/memsqs/loopback.d.ts +11 -0
- package/dist/memsqs/orderedlist.d.ts +19 -0
- package/dist/memsqs/queue.d.ts +84 -0
- package/dist/memsqs/server.d.ts +37 -0
- package/dist/ot-editutil/all.d.ts +2 -0
- package/dist/ot-editutil/oteditutil.d.ts +14 -0
- package/dist/ot-editutil/otmaputil.d.ts +21 -0
- package/dist/ot-js/all.d.ts +9 -0
- package/dist/ot-js/otarray.d.ts +111 -0
- package/dist/ot-js/otclientengine.d.ts +38 -0
- package/dist/ot-js/otcomposite.d.ts +37 -0
- package/dist/ot-js/otcounter.d.ts +17 -0
- package/dist/ot-js/otengine.d.ts +22 -0
- package/dist/ot-js/otmap.d.ts +19 -0
- package/dist/ot-js/otserverengine.d.ts +38 -0
- package/dist/ot-js/otsession.d.ts +111 -0
- package/dist/ot-js/ottypes.d.ts +29 -0
- package/dist/poly/all.d.ts +15 -0
- package/dist/poly/blend.d.ts +1 -0
- package/dist/poly/boundbox.d.ts +16 -0
- package/dist/poly/cartesian.d.ts +5 -0
- package/dist/poly/graham-scan.d.ts +8 -0
- package/dist/poly/hash.d.ts +1 -0
- package/dist/poly/matrix.d.ts +24 -0
- package/dist/poly/minbound.d.ts +1 -0
- package/dist/poly/poly.d.ts +52 -0
- package/dist/poly/polybin.d.ts +5 -0
- package/dist/poly/polylabel.d.ts +7 -0
- package/dist/poly/polypack.d.ts +30 -0
- package/dist/poly/polyround.d.ts +1 -0
- package/dist/poly/polysimplify.d.ts +1 -0
- package/dist/poly/quad.d.ts +48 -0
- package/dist/poly/selfintersect.d.ts +1 -0
- package/dist/poly/shamos.d.ts +1 -0
- package/dist/poly/simplify.d.ts +2 -0
- package/dist/poly/topo.d.ts +46 -0
- package/dist/poly/union.d.ts +48 -0
- package/dist/storage/all.d.ts +4 -0
- package/dist/storage/datablob.d.ts +9 -0
- package/dist/storage/env.d.ts +10 -0
- package/dist/storage/splitsblob.d.ts +13 -0
- package/dist/storage/storage.d.ts +166 -0
- package/dist/storages3/all.d.ts +1 -0
- package/dist/storages3/s3.d.ts +62 -0
- package/dist/util/all.d.ts +5 -0
- package/dist/util/bintrie.d.ts +93 -0
- package/dist/util/countedhash.d.ts +19 -0
- package/dist/util/gradient.d.ts +15 -0
- package/dist/util/indexedarray.d.ts +15 -0
- package/dist/util/util.d.ts +68 -0
- package/docs/context.md +2 -0
- package/docs/dbabstract.md +2 -0
- package/docs/dbdynamo.md +2 -0
- package/docs/fsm.md +243 -0
- package/docs/fsmfile.md +2 -0
- package/docs/jsonstream.md +44 -0
- package/docs/lambda.md +2 -0
- package/docs/logabstract.md +2 -0
- package/docs/logclient.md +2 -0
- package/docs/logserver.md +2 -0
- package/docs/ot-editutil.md +2 -0
- package/docs/ot-js.md +95 -0
- package/docs/poly.md +103 -0
- package/docs/storage.md +2 -0
- package/docs/storages3.md +2 -0
- package/docs/util.md +2 -0
- package/lib/all/all.ts +41 -0
- package/lib/all/allclient.ts +19 -0
- package/lib/context/all.ts +1 -0
- package/lib/context/context.ts +82 -0
- package/lib/dbabstract/all.ts +1 -0
- package/lib/dbabstract/db.ts +246 -0
- package/lib/dbdynamo/all.ts +1 -0
- package/lib/dbdynamo/dbdynamo.ts +1551 -0
- package/lib/filterexpr/all.ts +1 -0
- package/lib/filterexpr/filterexpr.ts +625 -0
- package/lib/fsm/all.ts +1 -0
- package/lib/fsm/fsm.ts +549 -0
- package/lib/fsmfile/all.ts +1 -0
- package/lib/fsmfile/fsmfile.ts +236 -0
- package/lib/jsonstream/all.ts +1 -0
- package/lib/jsonstream/jsonstream.ts +940 -0
- package/lib/lambda/all.ts +1 -0
- package/lib/lambda/env.ts +13 -0
- package/lib/lambda/lambda.ts +120 -0
- package/lib/logabstract/all.ts +1 -0
- package/lib/logabstract/log.ts +55 -0
- package/lib/logclient/all.ts +1 -0
- package/lib/logclient/log.ts +105 -0
- package/lib/logserver/all.ts +5 -0
- package/lib/logserver/log.ts +565 -0
- package/lib/logserver/logaccum.ts +1445 -0
- package/lib/logserver/logblob.ts +84 -0
- package/lib/logserver/logconcat.ts +313 -0
- package/lib/logserver/logkey.ts +125 -0
- package/lib/memsqs/all.ts +4 -0
- package/lib/memsqs/client.ts +268 -0
- package/lib/memsqs/loopback.ts +64 -0
- package/lib/memsqs/orderedlist.ts +74 -0
- package/lib/memsqs/queue.ts +395 -0
- package/lib/memsqs/server.ts +262 -0
- package/lib/ot-editutil/all.ts +2 -0
- package/lib/ot-editutil/oteditutil.ts +180 -0
- package/lib/ot-editutil/otmaputil.ts +209 -0
- package/lib/ot-js/all.ts +9 -0
- package/lib/ot-js/otarray.ts +1168 -0
- package/lib/ot-js/otclientengine.ts +327 -0
- package/lib/ot-js/otcomposite.ts +247 -0
- package/lib/ot-js/otcounter.ts +145 -0
- package/lib/ot-js/otengine.ts +71 -0
- package/lib/ot-js/otmap.ts +144 -0
- package/lib/ot-js/otserverengine.ts +329 -0
- package/lib/ot-js/otsession.ts +199 -0
- package/lib/ot-js/ottypes.ts +98 -0
- package/lib/poly/all.ts +15 -0
- package/lib/poly/blend.ts +27 -0
- package/lib/poly/boundbox.ts +102 -0
- package/lib/poly/cartesian.ts +130 -0
- package/lib/poly/graham-scan.ts +401 -0
- package/lib/poly/hash.ts +15 -0
- package/lib/poly/matrix.ts +309 -0
- package/lib/poly/minbound.ts +211 -0
- package/lib/poly/poly.ts +767 -0
- package/lib/poly/polybin.ts +218 -0
- package/lib/poly/polylabel.ts +204 -0
- package/lib/poly/polypack.ts +458 -0
- package/lib/poly/polyround.ts +30 -0
- package/lib/poly/polysimplify.ts +24 -0
- package/lib/poly/quad.ts +272 -0
- package/lib/poly/selfintersect.ts +87 -0
- package/lib/poly/shamos.ts +297 -0
- package/lib/poly/simplify.ts +119 -0
- package/lib/poly/topo.ts +525 -0
- package/lib/poly/union.ts +371 -0
- package/lib/storage/all.ts +4 -0
- package/lib/storage/datablob.ts +36 -0
- package/lib/storage/env.ts +14 -0
- package/lib/storage/splitsblob.ts +63 -0
- package/lib/storage/storage.ts +604 -0
- package/lib/storages3/all.ts +1 -0
- package/lib/storages3/s3.ts +576 -0
- package/lib/util/all.ts +5 -0
- package/lib/util/bintrie.ts +603 -0
- package/lib/util/countedhash.ts +83 -0
- package/lib/util/gradient.ts +108 -0
- package/lib/util/indexedarray.ts +80 -0
- package/lib/util/util.ts +695 -0
- package/package.json +8 -8
package/lib/poly/poly.ts
ADDED
|
@@ -0,0 +1,767 @@
|
|
|
1
|
+
import * as Util from '../util/all';
|
|
2
|
+
|
|
3
|
+
import * as PP from './polypack';
|
|
4
|
+
import { makeConvexHullGrahamScan } from './graham-scan';
|
|
5
|
+
|
|
6
|
+
// For incremental poly interface
|
|
7
|
+
export interface TickOptions
|
|
8
|
+
{
|
|
9
|
+
maxLeafCount?: number;
|
|
10
|
+
maxDepth?: number;
|
|
11
|
+
tickStep?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const DefaultTickOptions = { maxLeafCount: 256, maxDepth: 20, tickStep: 1 };
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
// Internal utilities
|
|
18
|
+
|
|
19
|
+
export const EARTH_RADIUS = 6371000; // Radius of earth in meters
|
|
20
|
+
|
|
21
|
+
export interface PolyOptions
|
|
22
|
+
{
|
|
23
|
+
noLatitudeCorrection?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export const DefaultOptions: PolyOptions = { };
|
|
26
|
+
|
|
27
|
+
// Return geographic polygon area in meters^2
|
|
28
|
+
export function polySimpleArea(b: Float64Array, iOffset: number, n: number): number
|
|
29
|
+
{
|
|
30
|
+
let p1x: number;
|
|
31
|
+
let p1y: number;
|
|
32
|
+
let p2x: number;
|
|
33
|
+
let p2y: number;
|
|
34
|
+
let p3x: number;
|
|
35
|
+
let p3y: number;
|
|
36
|
+
let dx: number;
|
|
37
|
+
let l: number;
|
|
38
|
+
let m: number;
|
|
39
|
+
let u: number;
|
|
40
|
+
let i: number;
|
|
41
|
+
let total: number = 0;
|
|
42
|
+
if (n > 2)
|
|
43
|
+
{
|
|
44
|
+
for (i = 0; i < n; i++)
|
|
45
|
+
{
|
|
46
|
+
if (i === n - 2)
|
|
47
|
+
{
|
|
48
|
+
l = n - 2;
|
|
49
|
+
m = n - 1;
|
|
50
|
+
u = 0;
|
|
51
|
+
}
|
|
52
|
+
else if (i === n - 1)
|
|
53
|
+
{
|
|
54
|
+
l = n - 1;
|
|
55
|
+
m = 0;
|
|
56
|
+
u = 1;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
l = i;
|
|
60
|
+
m = i + 1;
|
|
61
|
+
u = i + 2;
|
|
62
|
+
}
|
|
63
|
+
p1x = b[iOffset+l*2];
|
|
64
|
+
p1y = b[iOffset+l*2+1];
|
|
65
|
+
p2x = b[iOffset+m*2];
|
|
66
|
+
p2y = b[iOffset+m*2+1];
|
|
67
|
+
p3x = b[iOffset+u*2];
|
|
68
|
+
p3y = b[iOffset+u*2+1];
|
|
69
|
+
dx = (Util.deg2rad(p3x) - Util.deg2rad(p1x));
|
|
70
|
+
dx *= Math.sin(Util.deg2rad(p2y));
|
|
71
|
+
total += dx;
|
|
72
|
+
}
|
|
73
|
+
total *= EARTH_RADIUS * EARTH_RADIUS / 2;
|
|
74
|
+
}
|
|
75
|
+
return Math.abs(total);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Allow bare polygon coordinates array or GeoJSON feature.
|
|
79
|
+
// Normalize to multipolygon points array.
|
|
80
|
+
|
|
81
|
+
export function polyNormalize(poly: any): PP.PolyPack
|
|
82
|
+
{
|
|
83
|
+
return PP.polyPack(poly);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function polyNull(poly: any): boolean
|
|
87
|
+
{
|
|
88
|
+
let pp = PP.polyPack(poly);
|
|
89
|
+
return pp == null || pp.length == 2;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Area of geodesic polygon
|
|
93
|
+
export function polyArea(poly: any): number
|
|
94
|
+
{
|
|
95
|
+
let pp = polyNormalize(poly);
|
|
96
|
+
let a: number = 0;
|
|
97
|
+
|
|
98
|
+
// MultiPolygon is a set of polygons
|
|
99
|
+
PP.polyPackEachRing(pp, (b: Float64Array, iPoly: number, iRing: number, iOffset: number, nPoints: number) => {
|
|
100
|
+
a += polySimpleArea(b, iOffset, nPoints) * (iRing == 0 ? 1 : -1);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return a;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// NOTE - COMPACTNESS: Added the diameter of the geodesic polygon in meters
|
|
107
|
+
export function polyDiameter(poly: any, options?: PolyOptions): number
|
|
108
|
+
{
|
|
109
|
+
if (options === undefined) options = DefaultOptions;
|
|
110
|
+
|
|
111
|
+
const ch: any = polyConvexHull(poly);
|
|
112
|
+
const chCircle: Circle = polyToCircle(ch);
|
|
113
|
+
|
|
114
|
+
const lon: number = chCircle.x;
|
|
115
|
+
const lat: number = chCircle.y;
|
|
116
|
+
const r: number = chCircle.r;
|
|
117
|
+
|
|
118
|
+
const lonDistance: number = haversine(lon - r, lat, lon + r, lat, options);
|
|
119
|
+
const latDistance: number = haversine(lon, lat - r, lon, lat + r, options);
|
|
120
|
+
|
|
121
|
+
const diameter: number = Math.max(lonDistance, latDistance);
|
|
122
|
+
|
|
123
|
+
return diameter;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Return distance in meters given two lat/lon points
|
|
127
|
+
function haversine(x1: number, y1: number, x2: number, y2: number, options: PolyOptions): number
|
|
128
|
+
{
|
|
129
|
+
let dLat = Util.deg2rad(y2 - y1);
|
|
130
|
+
let dLon = Util.deg2rad(x2 - x1);
|
|
131
|
+
let c: number;
|
|
132
|
+
|
|
133
|
+
// Short circuit for using simple cartesian algorithm instead of haversine
|
|
134
|
+
if (options.noLatitudeCorrection)
|
|
135
|
+
c = Math.sqrt((dLat*dLat)+(dLon*dLon));
|
|
136
|
+
else
|
|
137
|
+
{
|
|
138
|
+
let lat1 = Util.deg2rad(y1);
|
|
139
|
+
let lat2 = Util.deg2rad(y2);
|
|
140
|
+
|
|
141
|
+
let a = Math.sin(dLat/2) * Math.sin(dLat/2) +
|
|
142
|
+
Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2)
|
|
143
|
+
c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a))
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return EARTH_RADIUS * c;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// The perimeter of geodesic polygon in meters
|
|
150
|
+
export function polyPerimeter(poly: any, options?: PolyOptions): number
|
|
151
|
+
{
|
|
152
|
+
if (options === undefined) options = DefaultOptions;
|
|
153
|
+
|
|
154
|
+
let pp = polyNormalize(poly);
|
|
155
|
+
let perimeter: number = 0;
|
|
156
|
+
|
|
157
|
+
PP.polyPackEachRing(pp, (b: Float64Array, iPoly: number, iRing: number, iOffset: number, nPoints: number) => {
|
|
158
|
+
if (iRing > 0) return; // skip holes
|
|
159
|
+
let iStart = iOffset;
|
|
160
|
+
let iEnd = (iOffset + nPoints * 2) - 2; // index to last point, not past last point
|
|
161
|
+
for (; iOffset < iEnd; iOffset += 2)
|
|
162
|
+
perimeter += haversine(b[iOffset], b[iOffset+1], b[iOffset+2], b[iOffset+3], options);
|
|
163
|
+
|
|
164
|
+
// Close ring if necessary
|
|
165
|
+
if (nPoints > 2 && (b[iStart] != b[iEnd] || b[iStart+1] != b[iEnd+1]))
|
|
166
|
+
perimeter += haversine(b[iStart], b[iStart+1], b[iEnd], b[iEnd+1], options);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
return perimeter;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
class Point {
|
|
173
|
+
public constructor(
|
|
174
|
+
public x: number,
|
|
175
|
+
public y: number) {}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export class Circle {
|
|
179
|
+
public constructor(
|
|
180
|
+
public x: number,
|
|
181
|
+
public y: number,
|
|
182
|
+
public r: number) {}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
//
|
|
186
|
+
// Returns the smallest circle that encloses the given polygon.
|
|
187
|
+
// Runs in expected O(n) time, randomized.
|
|
188
|
+
// Note: If 0 points are given, null is returned.
|
|
189
|
+
// If 1 point is given, a circle of radius 0 is returned.
|
|
190
|
+
//
|
|
191
|
+
|
|
192
|
+
export function polyToCircle(poly: any): Circle|null
|
|
193
|
+
{
|
|
194
|
+
return makeCircle(polyToExteriorPoints(poly));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
//
|
|
198
|
+
// Returns the circle whose perimeter is equal to the perimeter of the bounding perimeter
|
|
199
|
+
// of the polygon. Use binary search to find an approximation.
|
|
200
|
+
//
|
|
201
|
+
|
|
202
|
+
export function polyToPolsbyPopperCircle(poly: any, options?: PolyOptions): Circle|null
|
|
203
|
+
{
|
|
204
|
+
let pp = polyNormalize(poly);
|
|
205
|
+
|
|
206
|
+
let c = polyToCircle(poly);
|
|
207
|
+
if (c == null) return c;
|
|
208
|
+
let p: number = polyPerimeter(poly, options);
|
|
209
|
+
c.r = (p / (2 * Math.PI)) / 111139;
|
|
210
|
+
return c;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function makeCircle(points: Array<Point>): Circle|null
|
|
214
|
+
{
|
|
215
|
+
if (points == null) return null;
|
|
216
|
+
|
|
217
|
+
// Clone list to preserve the caller's data, do Durstenfeld shuffle
|
|
218
|
+
let shuffled: Array<Point> = points.slice();
|
|
219
|
+
for (let i = points.length - 1; i >= 0; i--) {
|
|
220
|
+
let j = Math.floor(Math.random() * (i + 1));
|
|
221
|
+
j = Math.max(Math.min(j, i), 0);
|
|
222
|
+
const temp: Point = shuffled[i];
|
|
223
|
+
shuffled[i] = shuffled[j];
|
|
224
|
+
shuffled[j] = temp;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Progressively add points to circle or recompute circle
|
|
228
|
+
let c: Circle|null = null;
|
|
229
|
+
shuffled.forEach((p: Point, i: number) => {
|
|
230
|
+
if (c === null || !isInCircle(c, p))
|
|
231
|
+
c = makeCircleOnePoint(shuffled.slice(0, i + 1), p);
|
|
232
|
+
});
|
|
233
|
+
return c;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// One boundary point known
|
|
237
|
+
function makeCircleOnePoint(points: Array<Point>, p: Point): Circle
|
|
238
|
+
{
|
|
239
|
+
let c: Circle = new Circle(p.x, p.y, 0);
|
|
240
|
+
points.forEach((q: Point, i: number) => {
|
|
241
|
+
if (!isInCircle(c, q)) {
|
|
242
|
+
if (c.r == 0)
|
|
243
|
+
c = makeDiameter(p, q);
|
|
244
|
+
else
|
|
245
|
+
c = makeCircleTwoPoints(points.slice(0, i + 1), p, q);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
return c;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Two boundary points known
|
|
252
|
+
function makeCircleTwoPoints(points: Array<Point>, p: Point, q: Point): Circle
|
|
253
|
+
{
|
|
254
|
+
const circ: Circle = makeDiameter(p, q);
|
|
255
|
+
let left : Circle|null = null;
|
|
256
|
+
let right: Circle|null = null;
|
|
257
|
+
|
|
258
|
+
// For each point not in the two-point circle
|
|
259
|
+
for (const r of points) {
|
|
260
|
+
if (isInCircle(circ, r))
|
|
261
|
+
continue;
|
|
262
|
+
|
|
263
|
+
// Form a circumcircle and classify it on left or right side
|
|
264
|
+
const cross: number = crossProduct(p.x, p.y, q.x, q.y, r.x, r.y);
|
|
265
|
+
const c: Circle|null = makeCircumcircle(p, q, r);
|
|
266
|
+
if (c === null)
|
|
267
|
+
continue;
|
|
268
|
+
else if (cross > 0 && (left === null || crossProduct(p.x, p.y, q.x, q.y, c.x, c.y) > crossProduct(p.x, p.y, q.x, q.y, left.x, left.y)))
|
|
269
|
+
left = c;
|
|
270
|
+
else if (cross < 0 && (right === null || crossProduct(p.x, p.y, q.x, q.y, c.x, c.y) < crossProduct(p.x, p.y, q.x, q.y, right.x, right.y)))
|
|
271
|
+
right = c;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Select which circle to return
|
|
275
|
+
if (left === null && right === null)
|
|
276
|
+
return circ;
|
|
277
|
+
else if (left === null && right !== null)
|
|
278
|
+
return right;
|
|
279
|
+
else if (left !== null && right === null)
|
|
280
|
+
return left;
|
|
281
|
+
else if (left !== null && right !== null)
|
|
282
|
+
return left.r <= right.r ? left : right;
|
|
283
|
+
else
|
|
284
|
+
throw "Assertion error";
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function makeDiameter(a: Point, b: Point): Circle
|
|
288
|
+
{
|
|
289
|
+
const cx: number = (a.x + b.x) / 2;
|
|
290
|
+
const cy: number = (a.y + b.y) / 2;
|
|
291
|
+
const r0: number = Util.distance(cx, cy, a.x, a.y);
|
|
292
|
+
const r1: number = Util.distance(cx, cy, b.x, b.y);
|
|
293
|
+
return new Circle(cx, cy, Math.max(r0, r1));
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function makeCircumcircle(a: Point, b: Point, c: Point): Circle|null
|
|
297
|
+
{
|
|
298
|
+
// Mathematical algorithm from Wikipedia: Circumscribed circle
|
|
299
|
+
const ox: number = (Math.min(a.x, b.x, c.x) + Math.max(a.x, b.x, c.x)) / 2;
|
|
300
|
+
const oy: number = (Math.min(a.y, b.y, c.y) + Math.max(a.y, b.y, c.y)) / 2;
|
|
301
|
+
const ax: number = a.x - ox; const ay: number = a.y - oy;
|
|
302
|
+
const bx: number = b.x - ox; const by: number = b.y - oy;
|
|
303
|
+
const cx: number = c.x - ox; const cy: number = c.y - oy;
|
|
304
|
+
const d: number = (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by)) * 2;
|
|
305
|
+
if (d == 0)
|
|
306
|
+
return null;
|
|
307
|
+
const x: number = ox + ((ax*ax + ay*ay) * (by - cy) + (bx*bx + by*by) * (cy - ay) + (cx*cx + cy*cy) * (ay - by)) / d;
|
|
308
|
+
const y: number = oy + ((ax*ax + ay*ay) * (cx - bx) + (bx*bx + by*by) * (ax - cx) + (cx*cx + cy*cy) * (bx - ax)) / d;
|
|
309
|
+
const ra: number = Util.distance(x, y, a.x, a.y);
|
|
310
|
+
const rb: number = Util.distance(x, y, b.x, b.y);
|
|
311
|
+
const rc: number = Util.distance(x, y, c.x, c.y);
|
|
312
|
+
return new Circle(x, y, Math.max(ra, rb, rc));
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/* Simple mathematical functions */
|
|
316
|
+
|
|
317
|
+
const MULTIPLICATIVE_EPSILON: number = 1 + 1e-14;
|
|
318
|
+
|
|
319
|
+
function isInCircle(c: Circle|null, p: Point): boolean
|
|
320
|
+
{
|
|
321
|
+
return c !== null && Util.distance(p.x, p.y, c.x, c.y) <= c.r * MULTIPLICATIVE_EPSILON;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Returns twice the signed area of the triangle defined by (x0, y0), (x1, y1), (x2, y2).
|
|
325
|
+
function crossProduct(x0: number, y0: number, x1: number, y1: number, x2: number, y2: number): number
|
|
326
|
+
{
|
|
327
|
+
return (x1 - x0) * (y2 - y0) - (y1 - y0) * (x2 - x0);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// cache x,y circle offsets for one quadrant indexed by number of segments
|
|
331
|
+
let circleOffsets: any = {};
|
|
332
|
+
|
|
333
|
+
function getCircleOffsets(n: number): any
|
|
334
|
+
{
|
|
335
|
+
if (circleOffsets[n] === undefined)
|
|
336
|
+
{
|
|
337
|
+
let a: number[] = [];
|
|
338
|
+
let incr = (Math.PI / 2) / n;
|
|
339
|
+
let theta: number = 0;
|
|
340
|
+
let i: number, j: number;
|
|
341
|
+
|
|
342
|
+
// Compute NE quadrant
|
|
343
|
+
for (i = 0; i < n; i++, theta += incr)
|
|
344
|
+
{
|
|
345
|
+
a.push(Math.sin(theta));
|
|
346
|
+
a.push(Math.cos(theta));
|
|
347
|
+
}
|
|
348
|
+
// Add top of circle
|
|
349
|
+
a.push(0);
|
|
350
|
+
a.push(1);
|
|
351
|
+
// Now flip X and replicate to NW quadrant
|
|
352
|
+
for (i = 0; i < n; i++)
|
|
353
|
+
{
|
|
354
|
+
j = (n-i)*2;
|
|
355
|
+
a.push(- a[j-1]);
|
|
356
|
+
a.push(a[j-2]);
|
|
357
|
+
}
|
|
358
|
+
// Now flip X and Y and replicate to SW quadrant
|
|
359
|
+
for (i = 0; i < n; i++)
|
|
360
|
+
{
|
|
361
|
+
j = (n-i)*2;
|
|
362
|
+
a.push(- a[j-1]);
|
|
363
|
+
a.push(- a[j-2]);
|
|
364
|
+
}
|
|
365
|
+
// Add bottom of circle
|
|
366
|
+
a.push(0);
|
|
367
|
+
a.push(-1);
|
|
368
|
+
// Now flip Y and replicate to SE quadrant
|
|
369
|
+
for (i = 0; i < n; i++)
|
|
370
|
+
{
|
|
371
|
+
j = (n-i)*2;
|
|
372
|
+
a.push(a[j-1]);
|
|
373
|
+
a.push(- a[j-2]);
|
|
374
|
+
}
|
|
375
|
+
// Duplicate final point per GeoJSON spec
|
|
376
|
+
a.push(a[0]);
|
|
377
|
+
a.push(a[1]);
|
|
378
|
+
|
|
379
|
+
circleOffsets[n] = a;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return circleOffsets[n];
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Note that this is essentially an inversion of polyToCircle which computes a mathematical
|
|
386
|
+
// circle given the point values, ignoring latitude correction. This inversion also
|
|
387
|
+
// ignores that correction which means when plotted on a 2D map looks circular but the edge of
|
|
388
|
+
// the circle is not a fixed distance (in physical units, e.g. meters) from the center.
|
|
389
|
+
//
|
|
390
|
+
|
|
391
|
+
export function polyFromCircle(c: Circle, nSegments?: number): any
|
|
392
|
+
{
|
|
393
|
+
if (c === null || c.r == 0) return null;
|
|
394
|
+
if (!nSegments || nSegments < 8) nSegments = 8;
|
|
395
|
+
let poly: any = [];
|
|
396
|
+
|
|
397
|
+
let offsets: any = getCircleOffsets(nSegments);
|
|
398
|
+
const n: number = offsets.length;
|
|
399
|
+
|
|
400
|
+
for (let i: number = 0; i < n; i += 2)
|
|
401
|
+
poly.push([ c.x + (c.r * offsets[i]), c.y + (c.r * offsets[i+1]) ]);
|
|
402
|
+
|
|
403
|
+
// return multi-polygon
|
|
404
|
+
return [ [ poly ] ];
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// isLeft(): tests if a point is Left|On|Right of an infinite line.
|
|
408
|
+
// Input: three points P0, P1, and P2
|
|
409
|
+
// Return: >0 for P2 left of the line through P0 and P1
|
|
410
|
+
// =0 for P2 on the line
|
|
411
|
+
// <0 for P2 right of the line
|
|
412
|
+
|
|
413
|
+
function sortPointX(a: Point, b: Point): number { return a.x - b.x; }
|
|
414
|
+
function sortPointY(a: Point, b: Point): number { return a.y - b.y; }
|
|
415
|
+
function sortPoint(a: Point, b: Point): number { return a.x === b.x ? a.y - b.y : a.x - b.x; }
|
|
416
|
+
function isLeft(p0: Point, p1: Point, p2: Point): number { return (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y); }
|
|
417
|
+
|
|
418
|
+
function pointsToPoly(points: Point[]): any
|
|
419
|
+
{
|
|
420
|
+
let p: any = [];
|
|
421
|
+
|
|
422
|
+
for (let i: number = 0; i < points.length; i++)
|
|
423
|
+
p.push( [ points[i].x, points[i].y ] );
|
|
424
|
+
|
|
425
|
+
return [ p ];
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export function polyToExteriorPoints(poly: any): any
|
|
429
|
+
{
|
|
430
|
+
let pp = polyNormalize(poly);
|
|
431
|
+
if (pp == null) return null;
|
|
432
|
+
|
|
433
|
+
let points: Point[] = [];
|
|
434
|
+
|
|
435
|
+
PP.polyPackEachPoint(pp, (b: Float64Array, iPoly: number, iRing: number, iOffset: number) => {
|
|
436
|
+
if (iRing > 0) return; // skip holes
|
|
437
|
+
points.push(new Point(b[iOffset], b[iOffset+1]));
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
return points.length > 0 ? points : null;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
//
|
|
444
|
+
// polyConvexHull() - Two algorithms:
|
|
445
|
+
// * Gram Scan (default) - in graham-scan.ts
|
|
446
|
+
// * A.M.Andrew's monotone chain 2D convex hull algorithm - below
|
|
447
|
+
//
|
|
448
|
+
|
|
449
|
+
export function polyConvexHull(poly: any, altAlgorithm?: any): any
|
|
450
|
+
{
|
|
451
|
+
if (altAlgorithm !== undefined)
|
|
452
|
+
return makeConvexHullMonotoneChain2D(poly);
|
|
453
|
+
|
|
454
|
+
return makeConvexHullGrahamScan(poly);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export function makeConvexHullMonotoneChain2D(poly: any): any
|
|
458
|
+
{
|
|
459
|
+
// Normalize input
|
|
460
|
+
let points = polyToExteriorPoints(poly);
|
|
461
|
+
if (points == null) return null;
|
|
462
|
+
|
|
463
|
+
points.sort(sortPoint);
|
|
464
|
+
|
|
465
|
+
let H: Point[] = [];
|
|
466
|
+
|
|
467
|
+
// the output array H[] will be used as the stack
|
|
468
|
+
let n: number = points.length;
|
|
469
|
+
let i: number;
|
|
470
|
+
let bot: number = 0;
|
|
471
|
+
let top: number = -1; // indices for bottom and top of the stack
|
|
472
|
+
|
|
473
|
+
// Get the indices of points with min x-coord and min|max y-coord
|
|
474
|
+
let minmin: number = 0, minmax: number;
|
|
475
|
+
|
|
476
|
+
let xmin = points[0].x;
|
|
477
|
+
for (i = 1; i < n; i++)
|
|
478
|
+
if (points[i].x != xmin)
|
|
479
|
+
break;
|
|
480
|
+
|
|
481
|
+
minmax = i - 1;
|
|
482
|
+
if (minmax == n - 1) // degenerate case: all x-coords == xmin
|
|
483
|
+
{
|
|
484
|
+
H[++top] = points[minmin];
|
|
485
|
+
if (points[minmax].y != points[minmin].y) // a nontrivial segment
|
|
486
|
+
H[++top] = points[minmax];
|
|
487
|
+
H[++top] = points[minmin]; // add polygon endpoint
|
|
488
|
+
return pointsToPoly(H);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Get the indices of points with max x-coord and min|max y-coord
|
|
492
|
+
let maxmin: number, maxmax: number = n - 1;
|
|
493
|
+
let xmax: number = points[n - 1].x;
|
|
494
|
+
for (i = n - 2; i >= 0; i--)
|
|
495
|
+
if (points[i].x != xmax)
|
|
496
|
+
break;
|
|
497
|
+
maxmin = i + 1;
|
|
498
|
+
|
|
499
|
+
// Compute the lower hull on the stack H
|
|
500
|
+
H[++top] = points[minmin]; // push minmin point onto stack
|
|
501
|
+
i = minmax;
|
|
502
|
+
while (++i <= maxmin)
|
|
503
|
+
{
|
|
504
|
+
// the lower line joins points[minmin] with points[maxmin]
|
|
505
|
+
if (isLeft(points[minmin], points[maxmin], points[i]) >= 0 && i < maxmin)
|
|
506
|
+
continue; // ignore points[i] above or on the lower line
|
|
507
|
+
|
|
508
|
+
while (top > 0) // there are at least 2 points on the stack
|
|
509
|
+
{
|
|
510
|
+
// test if points[i] is left of the line at the stack top
|
|
511
|
+
if (isLeft(H[top - 1], H[top], points[i]) > 0)
|
|
512
|
+
break; // points[i] is a new hull vertex
|
|
513
|
+
else
|
|
514
|
+
top--; // pop top point off stack
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
H[++top] = points[i]; // push points[i] onto stack
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Next, compute the upper hull on the stack H above the bottom hull
|
|
521
|
+
if (maxmax != maxmin) // if distinct xmax points
|
|
522
|
+
H[++top] = points[maxmax]; // push maxmax point onto stack
|
|
523
|
+
|
|
524
|
+
bot = top; // the bottom point of the upper hull stack
|
|
525
|
+
i = maxmin;
|
|
526
|
+
while (--i >= minmax)
|
|
527
|
+
{
|
|
528
|
+
// the upper line joins points[maxmax] with points[minmax]
|
|
529
|
+
if (isLeft(points[maxmax], points[minmax], points[i]) >= 0 && i > minmax)
|
|
530
|
+
continue; // ignore points[i] below or on the upper line
|
|
531
|
+
|
|
532
|
+
while (top > bot) // at least 2 points on the upper stack
|
|
533
|
+
{
|
|
534
|
+
// test if points[i] is left of the line at the stack top
|
|
535
|
+
if (isLeft(H[top - 1], H[top], points[i]) > 0)
|
|
536
|
+
break; // points[i] is a new hull vertex
|
|
537
|
+
else
|
|
538
|
+
top--; // pop top point off stack
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (points[i].x == H[0].x && points[i].y == H[0].y)
|
|
542
|
+
return pointsToPoly(H); // special case (mgomes)
|
|
543
|
+
|
|
544
|
+
H[++top] = points[i]; // push points[i] onto stack
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (minmax != minmin)
|
|
548
|
+
H[++top] = points[minmin]; // push joining endpoint onto stack
|
|
549
|
+
|
|
550
|
+
return pointsToPoly(H);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// NOTE - COMPACTNESS: Removed all the compactness notes.
|
|
554
|
+
|
|
555
|
+
// TODO - COMPACTNESS: Ultimately remove this interface & polyCompactness(),
|
|
556
|
+
// when dra-client no longer uses them.
|
|
557
|
+
export interface CompactnessDescription
|
|
558
|
+
{
|
|
559
|
+
value: number,
|
|
560
|
+
reock: number,
|
|
561
|
+
polsby_popper: number,
|
|
562
|
+
convex_hull: number,
|
|
563
|
+
schwartzberg: number,
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
export function polyCompactness(poly: any, options?: PolyOptions): CompactnessDescription
|
|
567
|
+
{
|
|
568
|
+
if (options === undefined) options = DefaultOptions;
|
|
569
|
+
|
|
570
|
+
let pp = polyNormalize(poly);
|
|
571
|
+
|
|
572
|
+
let area: number = polyArea(pp);
|
|
573
|
+
let perimeter: number = polyPerimeter(pp, options);
|
|
574
|
+
|
|
575
|
+
let circle: Circle = polyToCircle(pp);
|
|
576
|
+
let circleArea: number = polyArea(polyFromCircle(circle));
|
|
577
|
+
let ppcircleArea: number = polyArea(polyFromCircle(polyToPolsbyPopperCircle(poly)));
|
|
578
|
+
let hullArea: number = polyArea(polyConvexHull(pp));
|
|
579
|
+
|
|
580
|
+
let result: CompactnessDescription = { value: 0, reock: 0, polsby_popper: 0, convex_hull: 0, schwartzberg: 0 };
|
|
581
|
+
if (area == 0 || circle == null || circle.r == 0) return result;
|
|
582
|
+
|
|
583
|
+
result.reock = area / circleArea;
|
|
584
|
+
result.polsby_popper = area / ppcircleArea;
|
|
585
|
+
result.convex_hull = area / hullArea;
|
|
586
|
+
result.schwartzberg = ((2 * Math.PI) * Math.sqrt(area / Math.PI)) / perimeter;
|
|
587
|
+
|
|
588
|
+
// Weight Reock and Polsby-Popper equally, normalize in 0-100 range
|
|
589
|
+
result.value = Math.trunc(((result.reock + result.polsby_popper) / 2) * 100);
|
|
590
|
+
return result;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
export interface PolyDescription
|
|
595
|
+
{
|
|
596
|
+
npoly: number,
|
|
597
|
+
nhole: number,
|
|
598
|
+
npoint: number
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const EmptyPolyDescription = { npoly: 0, nhole: 0, npoint: 0 };
|
|
602
|
+
|
|
603
|
+
export function polyDescribe(poly: any): PolyDescription
|
|
604
|
+
{
|
|
605
|
+
let pp = polyNormalize(poly);
|
|
606
|
+
let d = Util.shallowCopy(EmptyPolyDescription);
|
|
607
|
+
|
|
608
|
+
PP.polyPackEachRing(pp, (b: Float64Array, iPoly: number, iRing: number, iOffset: number, nPoints: number) => {
|
|
609
|
+
d.npoint += nPoints;
|
|
610
|
+
if (iRing == 0)
|
|
611
|
+
d.npoly++;
|
|
612
|
+
else
|
|
613
|
+
d.nhole++;
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
return d;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
export function npoints(poly: any): number
|
|
620
|
+
{
|
|
621
|
+
let d = polyDescribe(poly);
|
|
622
|
+
return d.npoint;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
//
|
|
626
|
+
// polyTransform: transform each point. Called for all polygons and all rings.
|
|
627
|
+
// point, by point. Returns a packed structure.
|
|
628
|
+
//
|
|
629
|
+
export function polyTransform(poly: any, transformFn: any): any
|
|
630
|
+
{
|
|
631
|
+
// Get all the points -- DON'T skip holes -- and transform them.
|
|
632
|
+
// Don't alter input structure so copy if polyNormalize did not.
|
|
633
|
+
let pp = polyNormalize(poly);
|
|
634
|
+
if (! PP.polyPacked(poly))
|
|
635
|
+
pp = PP.polyPackCopy(pp);
|
|
636
|
+
|
|
637
|
+
PP.polyPackEachPoint(pp, (b: Float64Array, iPoly: number, iRing: number, iOffset: number) =>
|
|
638
|
+
{
|
|
639
|
+
const [newX, newY] = transformFn([b[iOffset], b[iOffset+1]]);
|
|
640
|
+
b[iOffset] = newX;
|
|
641
|
+
b[iOffset+1] = newY;
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
return pp;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function identityTransform(pt: number[]): number[]
|
|
648
|
+
{
|
|
649
|
+
return [pt[0], pt[1]];
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function closeRing(ring: any): any
|
|
653
|
+
{
|
|
654
|
+
if (ring.length > 1)
|
|
655
|
+
{
|
|
656
|
+
let p1 = ring[0];
|
|
657
|
+
let p2 = ring[ring.length-1];
|
|
658
|
+
if (p1[0] !== p2[0] || p1[1] !== p2[1])
|
|
659
|
+
ring.push(p1);
|
|
660
|
+
}
|
|
661
|
+
return ring;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function closePoly(poly: any): any
|
|
665
|
+
{
|
|
666
|
+
poly.forEach(closeRing);
|
|
667
|
+
return poly;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// This mutates the passed in feature to ensure it is correctly wound
|
|
671
|
+
// For convenience, passed back the value provided.
|
|
672
|
+
export function featureRewind(poly: any): any
|
|
673
|
+
{
|
|
674
|
+
let pp = polyNormalize(poly);
|
|
675
|
+
if (pp == null) return null;
|
|
676
|
+
polyRewindRings(pp);
|
|
677
|
+
if (poly.type === 'Feature')
|
|
678
|
+
{
|
|
679
|
+
if (poly.geometry.packed !== pp)
|
|
680
|
+
{
|
|
681
|
+
poly.geometry.coordinates = PP.polyUnpack(pp);
|
|
682
|
+
// Also make sure first === last coordinate
|
|
683
|
+
let d = Util.depthof(poly.geometry.coordinates);
|
|
684
|
+
if (d === 4) closePoly(poly.geometry.coordinates);
|
|
685
|
+
else if (d === 5) poly.geometry.coordinates.forEach(closePoly);
|
|
686
|
+
}
|
|
687
|
+
return poly;
|
|
688
|
+
}
|
|
689
|
+
else if (poly === pp)
|
|
690
|
+
return pp;
|
|
691
|
+
else
|
|
692
|
+
return PP.polyUnpack(pp);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
//
|
|
696
|
+
// polyRewindRings: Check the winding order of the polygon's ring. Rewind them,
|
|
697
|
+
// if necessary. Return a packed polygon.
|
|
698
|
+
//
|
|
699
|
+
export function polyRewindRings(pp: any, bLog: boolean = false): any
|
|
700
|
+
{
|
|
701
|
+
if (pp == null || pp.buffer == null) return;
|
|
702
|
+
|
|
703
|
+
if (pp.offset === undefined) throw 'oops: not a PolyPack';
|
|
704
|
+
|
|
705
|
+
PP.polyPackEachRing(pp, (b: Float64Array, iPoly: number, iRing: number, iOffset: number, nPoints: number) =>
|
|
706
|
+
{
|
|
707
|
+
const iStart = iOffset;
|
|
708
|
+
const iEnd = iStart + (nPoints * 2) - 2;
|
|
709
|
+
|
|
710
|
+
// Determine the winding order of the ring
|
|
711
|
+
let direction = 0;
|
|
712
|
+
|
|
713
|
+
// Start at the second point
|
|
714
|
+
iOffset += 2;
|
|
715
|
+
for (; iOffset <= iEnd; iOffset += 2)
|
|
716
|
+
{
|
|
717
|
+
// The previous point
|
|
718
|
+
let jOffset = iOffset - 2;
|
|
719
|
+
|
|
720
|
+
// Sum over the edges
|
|
721
|
+
direction += twoTimesArea(b[iOffset], b[jOffset], b[iOffset+1], b[jOffset+1]);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// Implicitly close the ring, if necessary
|
|
725
|
+
if (nPoints > 2 && (b[iStart] != b[iEnd] || b[iStart + 1] != b[iEnd + 1]))
|
|
726
|
+
{
|
|
727
|
+
if (bLog) console.log(`Implicitly closing polygon ${iPoly}, ring ${iRing}.`);
|
|
728
|
+
|
|
729
|
+
direction += twoTimesArea(b[iStart], b[iEnd], b[iStart + 1], b[iEnd + 1]);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// If the winding order is wrong, reverse it
|
|
733
|
+
if (((iRing == 0) && (direction > 0)) || ((iRing > 0) && (direction < 0)))
|
|
734
|
+
{
|
|
735
|
+
if (bLog) console.log(`Rewinding polygon ${iPoly}, ring ${iRing}.`);
|
|
736
|
+
|
|
737
|
+
let iFront = iStart;
|
|
738
|
+
let iBack = iEnd;
|
|
739
|
+
for (; iFront < iBack; iFront += 2, iBack -= 2)
|
|
740
|
+
{
|
|
741
|
+
let tmpX = b[iFront];
|
|
742
|
+
let tmpY = b[iFront+1];
|
|
743
|
+
b[iFront] = b[iBack];
|
|
744
|
+
b[iFront+1] = b[iBack+1];
|
|
745
|
+
b[iBack] = tmpX;
|
|
746
|
+
b[iBack+1] = tmpY;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
return pp;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
//
|
|
755
|
+
// To figure out which way a ring is wound:
|
|
756
|
+
//
|
|
757
|
+
// Sum over the edges, (x2 − x1) (y2 + y1). If the result is positive the curve
|
|
758
|
+
// is clockwise, if it's negative the curve is counterclockwise. (The result is
|
|
759
|
+
// twice the enclosed area, with a + /- convention.)
|
|
760
|
+
//
|
|
761
|
+
// Source: https://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order#1165943
|
|
762
|
+
//
|
|
763
|
+
function twoTimesArea(x2: number, x1: number, y2: number, y1: number): number
|
|
764
|
+
{
|
|
765
|
+
return (x2 - x1) * (y2 + y1);
|
|
766
|
+
}
|
|
767
|
+
|