@dra2020/baseclient 1.0.48 → 1.0.51
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/dist/baseclient.js +166 -50
- package/dist/baseclient.js.map +1 -1
- package/dist/geo/geo.d.ts +8 -1
- package/dist/poly/topo.d.ts +1 -0
- package/lib/geo/geo.ts +116 -4
- package/lib/poly/polylabel.ts +66 -39
- package/lib/poly/topo.ts +9 -8
- package/package.json +1 -1
package/dist/geo/geo.d.ts
CHANGED
|
@@ -10,7 +10,14 @@ export declare type GeoCentroidMap = {
|
|
|
10
10
|
y: number;
|
|
11
11
|
};
|
|
12
12
|
};
|
|
13
|
-
export
|
|
13
|
+
export interface NormalizeOptions {
|
|
14
|
+
joinPolygons?: boolean;
|
|
15
|
+
checkRewind?: boolean;
|
|
16
|
+
ensureID?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare function geoEnsureID(col: GeoFeatureCollection, options?: NormalizeOptions): void;
|
|
19
|
+
export declare function geoNormalizeFeature(f: any, options?: NormalizeOptions): GeoFeature;
|
|
20
|
+
export declare function geoNormalizeCollection(col: GeoFeatureCollection, options?: NormalizeOptions): GeoFeatureCollection;
|
|
14
21
|
export declare function geoCollectionToMap(col: GeoFeatureCollection): GeoFeatureMap;
|
|
15
22
|
export declare function geoMapToCollection(map: GeoFeatureMap): GeoFeatureCollection;
|
|
16
23
|
export declare function geoMapToCollectionNonNull(map: GeoFeatureMap): GeoFeatureCollection;
|
package/dist/poly/topo.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export declare function topoToFeature(topo: Topo, geoid: string): any;
|
|
|
8
8
|
export declare function topoToCollection(topo: Topo): any;
|
|
9
9
|
export interface SimplifyOptions {
|
|
10
10
|
minArea?: number;
|
|
11
|
+
log?: boolean;
|
|
11
12
|
}
|
|
12
13
|
export declare function topoSimplifyCollection(col: any, options?: SimplifyOptions): any;
|
|
13
14
|
export declare function topoMerge(topo: Topo, geoids: string[]): any;
|
package/lib/geo/geo.ts
CHANGED
|
@@ -8,16 +8,40 @@ export type GeoFeatureArray = GeoFeature[];
|
|
|
8
8
|
export type GeoFeatureCollection = geojson.FeatureCollection;
|
|
9
9
|
export type GeoCentroidMap = { [geoid: string]: { x: number, y: number } };
|
|
10
10
|
|
|
11
|
-
export
|
|
11
|
+
export interface NormalizeOptions
|
|
12
12
|
{
|
|
13
|
+
joinPolygons?: boolean,
|
|
14
|
+
checkRewind?: boolean,
|
|
15
|
+
ensureID?: boolean,
|
|
16
|
+
}
|
|
17
|
+
const NormalizeAll: NormalizeOptions = { joinPolygons: true, checkRewind: true, ensureID: true };
|
|
18
|
+
|
|
19
|
+
// set the canonical 'id' property from the best property value.
|
|
20
|
+
// if joinPolygons is true, we do not enforce uniqueness.
|
|
21
|
+
//
|
|
22
|
+
|
|
23
|
+
export function geoEnsureID(col: GeoFeatureCollection, options?: NormalizeOptions): void
|
|
24
|
+
{
|
|
25
|
+
options = Util.shallowAssignImmutable({}, options);
|
|
26
|
+
|
|
13
27
|
let prop: string;
|
|
14
|
-
const props = ['id', 'GEOID', 'GEOID10', 'GEOID20', 'GEOID30' ];
|
|
28
|
+
const props = [ 'id', 'GEOID', 'GEOID10', 'GEOID20', 'GEOID30', 'DISTRICT', 'DISTRICTNO', 'DISTRICTNAME' ];
|
|
15
29
|
|
|
16
30
|
if (col && col.features && col.features.length > 0)
|
|
17
31
|
{
|
|
18
32
|
let f = col.features[0];
|
|
19
|
-
if (f.properties.id !== undefined) return;
|
|
20
|
-
props.forEach(p => {
|
|
33
|
+
if (f.properties.id !== undefined) return; // short-cut - assume if 'id' is set, we're all good.
|
|
34
|
+
props.forEach(p => {
|
|
35
|
+
if (prop === undefined)
|
|
36
|
+
if (f.properties[p] !== undefined)
|
|
37
|
+
prop = p;
|
|
38
|
+
else
|
|
39
|
+
{
|
|
40
|
+
p = p.toLowerCase();
|
|
41
|
+
if (f.properties[p] !== undefined)
|
|
42
|
+
prop = p
|
|
43
|
+
}
|
|
44
|
+
});
|
|
21
45
|
if (prop)
|
|
22
46
|
col.features.forEach(f => { f.properties.id = f.properties[prop] });
|
|
23
47
|
else
|
|
@@ -28,6 +52,94 @@ export function geoEnsureID(col: GeoFeatureCollection): void
|
|
|
28
52
|
}
|
|
29
53
|
}
|
|
30
54
|
|
|
55
|
+
export function geoNormalizeFeature(f: any, options?: NormalizeOptions): GeoFeature
|
|
56
|
+
{
|
|
57
|
+
options = Util.shallowAssignImmutable({}, options);
|
|
58
|
+
|
|
59
|
+
if (f && f.geometry && f.geometry.type === 'MultiPolygon' && f.geometry.coordinates)
|
|
60
|
+
{
|
|
61
|
+
let multiPoly = f.geometry.coordinates;
|
|
62
|
+
|
|
63
|
+
// Convert degenerate MultiPolygon to Polygon
|
|
64
|
+
if (multiPoly.length == 1)
|
|
65
|
+
{
|
|
66
|
+
f.geometry.type = 'Polygon';
|
|
67
|
+
f.geometry.coordinates = multiPoly[0];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else if (f && f.geometry && f.geometry.type === 'Point' && f.geometry.coordinates)
|
|
71
|
+
{
|
|
72
|
+
while (Array.isArray(f.geometry.coordinates[0]))
|
|
73
|
+
f.geometry.coordinates = f.geometry.coordinates[0];
|
|
74
|
+
}
|
|
75
|
+
else if (options.checkRewind)
|
|
76
|
+
// Various tools do not guarantee valid GeoJSON winding rules. Verify it since internal processing
|
|
77
|
+
// assumes it is correct.
|
|
78
|
+
Poly.featureRewind(f);
|
|
79
|
+
|
|
80
|
+
return f;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function onlyPolygons(col: GeoFeatureCollection): boolean
|
|
84
|
+
{
|
|
85
|
+
if (col && Array.isArray(col.features))
|
|
86
|
+
for (let i = 0; i < col.features.length; i++)
|
|
87
|
+
{
|
|
88
|
+
let f = col.features[i];
|
|
89
|
+
if (f.geometry && f.geometry.type === 'MultiPolygon')
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function mergePolygon(f1: any, f2: any): any
|
|
97
|
+
{
|
|
98
|
+
if (!f1) return f2;
|
|
99
|
+
if (!f2) return f1;
|
|
100
|
+
if (f1.geometry.type !== 'Polygon' && f1.geometry.type !== 'MultiPolygon')
|
|
101
|
+
return f1;
|
|
102
|
+
if (f2.geometry.type !== 'Polygon' && f2.geometry.type !== 'MultiPolygon')
|
|
103
|
+
return f2;
|
|
104
|
+
if (f1.geometry.type === 'Polygon')
|
|
105
|
+
{
|
|
106
|
+
f1.geometry.type = 'MultiPolygon';
|
|
107
|
+
if (f2.geometry.type === 'Polygon')
|
|
108
|
+
f1.geometry.coordinates = [ f1.geometry.coordinates, f2.geometry.coordinates ];
|
|
109
|
+
else
|
|
110
|
+
f1.geometry.coordinates = [ f1.geometry.coordinates, ...f2.geometry.coordinates ];
|
|
111
|
+
}
|
|
112
|
+
else
|
|
113
|
+
{
|
|
114
|
+
if (f2.geometry.type === 'Polygon')
|
|
115
|
+
f1.geometry.coordinates.push(f2.geometry.coordinates);
|
|
116
|
+
else
|
|
117
|
+
f1.geometry.coordinates = [...f1.geometry.coordinates, ...f2.geometry.coordinates];
|
|
118
|
+
}
|
|
119
|
+
return f1;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function geoNormalizeCollection(col: GeoFeatureCollection, options?: NormalizeOptions): GeoFeatureCollection
|
|
123
|
+
{
|
|
124
|
+
options = Util.shallowAssignImmutable(NormalizeAll, options);
|
|
125
|
+
|
|
126
|
+
// Normalize individual features
|
|
127
|
+
if (col && Array.isArray(col.features)) col.features.forEach((f: GeoFeature) => geoNormalizeFeature(f, options));
|
|
128
|
+
|
|
129
|
+
// Ensure ID
|
|
130
|
+
if (options.ensureID)
|
|
131
|
+
geoEnsureID(col, options);
|
|
132
|
+
|
|
133
|
+
// Merge polygons into multi-polygons based on id?
|
|
134
|
+
if (options.ensureID && options.joinPolygons && onlyPolygons(col))
|
|
135
|
+
{
|
|
136
|
+
let map: GeoFeatureMap = {};
|
|
137
|
+
col.features.forEach(f => { let id = f.properties.id; map[id] = mergePolygon(map[id], f) });
|
|
138
|
+
col.features = Object.values(map);
|
|
139
|
+
}
|
|
140
|
+
return col;
|
|
141
|
+
}
|
|
142
|
+
|
|
31
143
|
export function geoCollectionToMap(col: GeoFeatureCollection): GeoFeatureMap
|
|
32
144
|
{
|
|
33
145
|
if (col == null) return null;
|
package/lib/poly/polylabel.ts
CHANGED
|
@@ -21,16 +21,66 @@ export function polyDistance(poly: any, x: number, y: number): number
|
|
|
21
21
|
let pp = P.polyNormalize(poly);
|
|
22
22
|
if (pp == null) return 0;
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
24
|
+
// First find if it is contained in one of the outer polygons, or outside all outer polygons
|
|
25
|
+
let iContaining = -1;
|
|
26
|
+
let maxOutside = - Infinity;
|
|
27
|
+
let minInside = Infinity;
|
|
28
|
+
let noholes = true;
|
|
29
|
+
PP.polyPackEachRing(pp, (b: Float64Array, iPoly: number, iRing: number, iOffset: number, nPoints: number) => {
|
|
30
|
+
// If we have determined we are inside this polygon, keep track of whether it has holes
|
|
31
|
+
if (iContaining == iPoly && iRing > 0)
|
|
32
|
+
noholes = false;
|
|
33
|
+
// Don't process rings
|
|
34
|
+
if (iRing > 0) return;
|
|
35
|
+
// OK, get distance
|
|
36
|
+
let forEachPointPair = (iter: any) => {
|
|
37
|
+
PP.polyPackEachRing(pp, (b: Float64Array, iInteriorPoly: number, iRing: number, iOffset: number, nPoints: number) => {
|
|
38
|
+
if (iRing || iInteriorPoly != iPoly) return;
|
|
39
|
+
let iFirst = iOffset;
|
|
40
|
+
let iLast = iOffset + nPoints * 2;
|
|
41
|
+
let iSecond = iLast - 2;
|
|
42
|
+
for (; iFirst < iLast; iSecond = iFirst, iFirst += 2)
|
|
43
|
+
iter(b[iFirst], b[iFirst+1], b[iSecond], b[iSecond+1]);
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
let dist = pointToPolygonDist(x, y, forEachPointPair);
|
|
47
|
+
// If inside, is it closest inside (deal with multipolygons that self-contain - think filled donut)
|
|
48
|
+
if (dist > 0 && dist < minInside)
|
|
49
|
+
{
|
|
50
|
+
iContaining = iPoly;
|
|
51
|
+
minInside = dist;
|
|
52
|
+
noholes = true;
|
|
53
|
+
}
|
|
54
|
+
else if (dist < 0)
|
|
55
|
+
maxOutside = Math.max(maxOutside, dist);
|
|
56
|
+
});
|
|
57
|
+
if (iContaining < 0)
|
|
58
|
+
return maxOutside;
|
|
59
|
+
if (noholes)
|
|
60
|
+
return minInside;
|
|
61
|
+
|
|
62
|
+
// OK, now need to worry about holes in the polygon it is contained in
|
|
63
|
+
PP.polyPackEachRing(pp, (b: Float64Array, iPoly: number, iRing: number, iOffset: number, nPoints: number) => {
|
|
64
|
+
// Only want to look at the holes for the containing polygon
|
|
65
|
+
if (iPoly != iContaining || iRing == 0) return;
|
|
66
|
+
// Compute distance to those holes
|
|
67
|
+
let forEachPointPair = (iter: any) => {
|
|
68
|
+
PP.polyPackEachRing(pp, (b: Float64Array, iInteriorPoly: number, iRing: number, iOffset: number, nPoints: number) => {
|
|
69
|
+
if (iInteriorPoly != iContaining || iRing == 0) return;
|
|
70
|
+
let iFirst = iOffset;
|
|
71
|
+
let iLast = iOffset + nPoints * 2;
|
|
72
|
+
let iSecond = iLast - 2;
|
|
73
|
+
for (; iFirst < iLast; iSecond = iFirst, iFirst += 2)
|
|
74
|
+
iter(b[iFirst], b[iFirst+1], b[iSecond], b[iSecond+1]);
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
// Negate distance since dealing with holes
|
|
78
|
+
let dist = - pointToPolygonDist(x, y, forEachPointPair);
|
|
79
|
+
// We take the min to either get a negative value (inside hole) or a smaller positive value
|
|
80
|
+
// (outside hole but close to hole boundary).
|
|
81
|
+
minInside = Math.min(minInside, dist);
|
|
82
|
+
});
|
|
83
|
+
return minInside;
|
|
34
84
|
}
|
|
35
85
|
|
|
36
86
|
//
|
|
@@ -69,7 +119,7 @@ export function polyLabel(poly: any, precision?: number, debug?: boolean): PolyL
|
|
|
69
119
|
let iLast = iOffset + nPoints * 2;
|
|
70
120
|
let iSecond = iLast - 2;
|
|
71
121
|
for (; iFirst < iLast; iSecond = iFirst, iFirst += 2)
|
|
72
|
-
iter(
|
|
122
|
+
iter(b[iFirst], b[iFirst+1], b[iSecond], b[iSecond+1]);
|
|
73
123
|
});
|
|
74
124
|
};
|
|
75
125
|
|
|
@@ -164,38 +214,15 @@ class Cell
|
|
|
164
214
|
// signed distance from point to polygon outline (negative if point is outside)
|
|
165
215
|
function pointToPolygonDist(x: number, y: number, forEachPointPair: any): number
|
|
166
216
|
{
|
|
167
|
-
let iPolyLast = -1;
|
|
168
|
-
let iRingLast = -1;
|
|
169
217
|
let inside = false;
|
|
170
|
-
let
|
|
171
|
-
let useInside = false;
|
|
172
|
-
let isHole = false;
|
|
173
|
-
let minDistSq: number = Infinity;
|
|
218
|
+
let minDistSq = Infinity;
|
|
174
219
|
|
|
175
|
-
forEachPointPair((
|
|
176
|
-
if (iPoly != iPolyLast || iRing != iRingLast)
|
|
177
|
-
{
|
|
178
|
-
if (useInside)
|
|
179
|
-
inside = isHole ? ! thisInside : thisInside;
|
|
180
|
-
iPolyLast = iPoly;
|
|
181
|
-
iRingLast = iRing;
|
|
182
|
-
thisInside = false;
|
|
183
|
-
useInside = false;
|
|
184
|
-
isHole = false;
|
|
185
|
-
}
|
|
220
|
+
forEachPointPair((ax: number, ay: number, bx: number, by: number) => {
|
|
186
221
|
if ((ay > y !== by > y) && (x < (bx - ax) * (y - ay) / (by - ay) + ax))
|
|
187
|
-
|
|
222
|
+
inside = !inside;
|
|
188
223
|
|
|
189
|
-
|
|
190
|
-
if (thisDistSq < minDistSq)
|
|
191
|
-
{
|
|
192
|
-
minDistSq = thisDistSq;
|
|
193
|
-
useInside = true;
|
|
194
|
-
isHole = iRing != 0;
|
|
195
|
-
}
|
|
224
|
+
minDistSq = Math.min(minDistSq, getSegDistSq(x, y, ax, ay, bx, by));
|
|
196
225
|
});
|
|
197
|
-
if (useInside)
|
|
198
|
-
inside = isHole ? ! thisInside : thisInside;
|
|
199
226
|
|
|
200
227
|
return (inside ? 1 : -1) * Math.sqrt(minDistSq);
|
|
201
228
|
}
|
|
@@ -209,7 +236,7 @@ function getCentroidCell(forEachPointPair: any): Cell
|
|
|
209
236
|
let fx: number;
|
|
210
237
|
let fy: number;
|
|
211
238
|
|
|
212
|
-
forEachPointPair((
|
|
239
|
+
forEachPointPair((ax: number, ay: number, bx: number, by: number) => {
|
|
213
240
|
if (fx === undefined) fx = ax, fy = ay;
|
|
214
241
|
let f: number = ax * by - bx * ay;
|
|
215
242
|
x += (ax + bx) * f;
|
package/lib/poly/topo.ts
CHANGED
|
@@ -215,13 +215,14 @@ function intpt(f: any): { x: number, y: number }
|
|
|
215
215
|
export interface SimplifyOptions
|
|
216
216
|
{
|
|
217
217
|
minArea?: number,
|
|
218
|
+
log?: boolean,
|
|
218
219
|
}
|
|
219
220
|
|
|
220
221
|
const DefaultSimplifyOptions: SimplifyOptions = { minArea: 500 };
|
|
221
222
|
|
|
222
|
-
function log(s: string): void
|
|
223
|
+
function log(emitlog: boolean, s: string): void
|
|
223
224
|
{
|
|
224
|
-
console.log(s);
|
|
225
|
+
if (emitlog) console.log(s);
|
|
225
226
|
}
|
|
226
227
|
|
|
227
228
|
//
|
|
@@ -248,10 +249,10 @@ export function topoSimplifyCollection(col: any, options?: SimplifyOptions): any
|
|
|
248
249
|
let elapsedTotal = new Util.Elapsed();
|
|
249
250
|
let elapsed = new Util.Elapsed();
|
|
250
251
|
let topo = topoFromCollection(col);
|
|
251
|
-
log(`topoSimplifyCollection: fromCollection: ${Math.round(elapsed.ms())}ms`);
|
|
252
|
+
log(options.log, `topoSimplifyCollection: fromCollection: ${Math.round(elapsed.ms())}ms`);
|
|
252
253
|
elapsed.start();
|
|
253
254
|
topo = TopoSimplify.presimplify(topo, TopoSimplify['sphericalTriangleArea']);
|
|
254
|
-
log(`topoSimplifyCollection: presimplify: ${Math.round(elapsed.ms())}ms`);
|
|
255
|
+
log(options.log, `topoSimplifyCollection: presimplify: ${Math.round(elapsed.ms())}ms`);
|
|
255
256
|
elapsed.start();
|
|
256
257
|
|
|
257
258
|
// Keep iterating on removing simplification from degenerate shapes
|
|
@@ -328,19 +329,19 @@ export function topoSimplifyCollection(col: any, options?: SimplifyOptions): any
|
|
|
328
329
|
keepTiny.set(f, f);
|
|
329
330
|
keepArcs(topo, oOld.arcs, 0); // keeps all points to avoid reprocessing these tiny shapes
|
|
330
331
|
nBad++, nTiny++;
|
|
331
|
-
log(`topoSimplifyCollection: ${f.properties.id}: increasing feature fidelity because intpt dist is ${d}`);
|
|
332
|
+
log(options.log, `topoSimplifyCollection: ${f.properties.id}: increasing feature fidelity because intpt dist is ${d}`);
|
|
332
333
|
}
|
|
333
334
|
}
|
|
334
335
|
}
|
|
335
336
|
}
|
|
336
337
|
});
|
|
337
|
-
log(`topoSimplifyCollection: pass ${nTries}: ${nBad} (${nTiny} tiny) of ${col.features.length} features are degenerate`);
|
|
338
|
+
log(options.log, `topoSimplifyCollection: pass ${nTries}: ${nBad} (${nTiny} tiny) of ${col.features.length} features are degenerate`);
|
|
338
339
|
|
|
339
340
|
// If not making progress, keep more points
|
|
340
341
|
if (nBad >= nBadLast)
|
|
341
342
|
{
|
|
342
343
|
keepweight /= 10;
|
|
343
|
-
log(`topoSimplifyCollection: pass ${nTries}: reducing weight limit to ${keepweight}`);
|
|
344
|
+
log(options.log, `topoSimplifyCollection: pass ${nTries}: reducing weight limit to ${keepweight}`);
|
|
344
345
|
}
|
|
345
346
|
nBadLast = nBad;
|
|
346
347
|
|
|
@@ -356,7 +357,7 @@ export function topoSimplifyCollection(col: any, options?: SimplifyOptions): any
|
|
|
356
357
|
nTries++;
|
|
357
358
|
}
|
|
358
359
|
|
|
359
|
-
log(`topoSimplifyCollection: total elapsed time: ${bigTimeString(elapsedTotal.ms())}`);
|
|
360
|
+
log(options.log, `topoSimplifyCollection: total elapsed time: ${bigTimeString(elapsedTotal.ms())}`);
|
|
360
361
|
|
|
361
362
|
return col;
|
|
362
363
|
}
|