@dra2020/baseclient 1.0.167 → 1.0.168
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 +87 -15
- package/dist/baseclient.js.map +1 -1
- package/dist/geo/geo.d.ts +9 -3
- package/dist/poly/topovalidate.d.ts +146 -0
- package/lib/geo/geo.ts +65 -16
- package/lib/poly/polybin.ts +27 -0
- package/lib/poly/topovalidate.ts +389 -0
- package/package.json +2 -2
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
type Point = [number, number];
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Minimal shape this validator needs. A real topology from
|
|
5
|
+
* `topojson-specification` satisfies this. We require both `packed.arcs`
|
|
6
|
+
* (the segment data) and `packed.arcindices` + `objects` (so we can walk
|
|
7
|
+
* only the arcs reachable from the live object set, matching
|
|
8
|
+
* topojson-client's `validateneighbors.js` semantics via `forAllArcPoints`
|
|
9
|
+
* with `onlyOnce: true`).
|
|
10
|
+
*/
|
|
11
|
+
export interface PackedTopologyLike {
|
|
12
|
+
objects: { [id: string]: PackedObject };
|
|
13
|
+
packed: {
|
|
14
|
+
arcs: Float64Array | number[];
|
|
15
|
+
arcindices: Int32Array | number[];
|
|
16
|
+
/** Per-topology map from object reference to its offset into `packed.arcindices`.
|
|
17
|
+
* Kept here (not on the object) so the same object can appear in more than one
|
|
18
|
+
* packed topology — see topojson-client/src/packarcindices.js. */
|
|
19
|
+
objectArcs: WeakMap<object, number>;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface PackedObject {
|
|
24
|
+
type: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A line segment found in more than one arc. Endpoints are reported in their
|
|
28
|
+
* lexicographic-canonical order (smaller endpoint first). */
|
|
29
|
+
export interface DuplicateArcSegment {
|
|
30
|
+
segment: { s: Point; e: Point };
|
|
31
|
+
/** Arc indices (into `topology.packed.arcs`) that contain this segment, ascending. */
|
|
32
|
+
arcs: number[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ValidateArcSegmentsOptions {
|
|
36
|
+
/**
|
|
37
|
+
* Subset of object IDs to validate. If undefined, every object in
|
|
38
|
+
* `topology.objects` is walked (matching `validateneighbors.js` default,
|
|
39
|
+
* which uses `params.topology.objects` when no `objects` field is given).
|
|
40
|
+
*
|
|
41
|
+
* Accepts either a hash whose keys are the object IDs to include (same
|
|
42
|
+
* shape as `topology.objects`), or a Set of IDs.
|
|
43
|
+
*/
|
|
44
|
+
objects?: { [id: string]: unknown } | Set<string>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ValidateArcSegmentsResult {
|
|
48
|
+
ok: boolean;
|
|
49
|
+
duplicates: DuplicateArcSegment[];
|
|
50
|
+
/** Number of unique arcs actually inspected (reachable from the object set). */
|
|
51
|
+
arcsInspected: number;
|
|
52
|
+
/** Total (arc, consecutive-point-pair) segments inspected across reachable arcs. */
|
|
53
|
+
totalSegments: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Verify that no two arcs of a packed TopoJSON topology contain the same line
|
|
58
|
+
* segment — i.e. the same pair of consecutive points, in either direction.
|
|
59
|
+
*
|
|
60
|
+
* Semantics mirror topojson-client's `validateneighbors.js`:
|
|
61
|
+
* - Only arcs **reachable from the current object set** are inspected;
|
|
62
|
+
* orphaned arcs in the packed buffer are ignored.
|
|
63
|
+
* - Each reachable arc is visited at most once (the `onlyOnce: true` flag
|
|
64
|
+
* in the upstream `forAllArcPoints` walker).
|
|
65
|
+
* - Equality is exact Float64 (no epsilon), matching `splice.js`'s
|
|
66
|
+
* `dedup`/`equalArcs`. Endpoint order is normalized lexicographically
|
|
67
|
+
* before keying, so segment `(P→Q)` in one arc and `(Q→P)` in another
|
|
68
|
+
* are treated as the same segment.
|
|
69
|
+
* - Within-arc segment repeats (the same arc containing the same segment
|
|
70
|
+
* twice) are ignored — only cross-arc duplicates are reported.
|
|
71
|
+
*
|
|
72
|
+
* Expected packed layout (same as topojson-client's `forAllArcPoints` / `splice`):
|
|
73
|
+
* topology.packed.arcs (Float64Array)
|
|
74
|
+
* [0] = narcs
|
|
75
|
+
* [1 + 2*a] = npoints for arc `a`
|
|
76
|
+
* [1 + 2*a + 1] = pointoffset for arc `a` (index into the same buffer)
|
|
77
|
+
* at pointoffset: interleaved x, y, x, y, ... (2 floats per point)
|
|
78
|
+
* topology.packed.arcindices (Int32Array)
|
|
79
|
+
* starting at object.packedarcs:
|
|
80
|
+
* Polygon : [nring][narc][arc...]...
|
|
81
|
+
* MultiPolygon : [npoly][nring][narc][arc...]...
|
|
82
|
+
*
|
|
83
|
+
* O(total reachable points) time, O(total reachable unique segments) space.
|
|
84
|
+
*/
|
|
85
|
+
export function validateNoDuplicateArcSegments(
|
|
86
|
+
topology: PackedTopologyLike,
|
|
87
|
+
options: ValidateArcSegmentsOptions = {},
|
|
88
|
+
): ValidateArcSegmentsResult {
|
|
89
|
+
const arcs = topology.packed.arcs;
|
|
90
|
+
const arcindices = topology.packed.arcindices;
|
|
91
|
+
|
|
92
|
+
// -- Phase 1: walk topology.objects to collect the set of reachable arcs ---
|
|
93
|
+
// Mirrors forAllArcPoints({ topology, objects, onlyOnce: true }) which is
|
|
94
|
+
// what validateneighbors.js uses. We only need each reachable arc once,
|
|
95
|
+
// so dedup with a Set as we go.
|
|
96
|
+
const seen = new Set<number>();
|
|
97
|
+
|
|
98
|
+
function walkRing(z: number): number {
|
|
99
|
+
const narc = arcindices[z++];
|
|
100
|
+
for (let i = 0; i < narc; i++, z++)
|
|
101
|
+
{
|
|
102
|
+
let arc = arcindices[z];
|
|
103
|
+
if (arc < 0) arc = ~arc; // negative encodes reversed direction; same arc index after ~
|
|
104
|
+
if (!seen.has(arc)) seen.add(arc);
|
|
105
|
+
}
|
|
106
|
+
return z;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function walkPolygon(z: number): number {
|
|
110
|
+
const nring = arcindices[z++];
|
|
111
|
+
for (let i = 0; i < nring; i++) z = walkRing(z);
|
|
112
|
+
return z;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function walkMultiPolygon(z: number): number {
|
|
116
|
+
const npoly = arcindices[z++];
|
|
117
|
+
for (let i = 0; i < npoly; i++) z = walkPolygon(z);
|
|
118
|
+
return z;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Resolve the object-id list to walk. A user-supplied Set is converted to
|
|
122
|
+
// an iterable of keys; a user-supplied hash is treated as id→anything (same
|
|
123
|
+
// shape as `topology.objects`); falling back to all of `topology.objects`.
|
|
124
|
+
let objectIds: Iterable<string>;
|
|
125
|
+
if (options.objects instanceof Set) objectIds = options.objects;
|
|
126
|
+
else if (options.objects) objectIds = Object.keys(options.objects);
|
|
127
|
+
else objectIds = Object.keys(topology.objects);
|
|
128
|
+
|
|
129
|
+
const objectArcs = topology.packed.objectArcs;
|
|
130
|
+
for (const id of objectIds)
|
|
131
|
+
{
|
|
132
|
+
const obj = topology.objects[id];
|
|
133
|
+
if (!obj) continue;
|
|
134
|
+
const z = objectArcs ? objectArcs.get(obj) : undefined;
|
|
135
|
+
if (z === undefined) continue;
|
|
136
|
+
if (obj.type === "Polygon") walkPolygon(z);
|
|
137
|
+
else if (obj.type === "MultiPolygon") walkMultiPolygon(z);
|
|
138
|
+
// Other geometry types don't contribute polygon arcs; ignore.
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// -- Phase 2: scan segments only on reachable arcs --------------------------
|
|
142
|
+
|
|
143
|
+
/** Canonical key for a segment, with endpoints lex-sorted (smaller first).
|
|
144
|
+
* Number.prototype.toString round-trips finite Float64s reversibly, so
|
|
145
|
+
* byte-equal floats produce byte-equal strings — same coordinate equality
|
|
146
|
+
* as `splice.js`'s `equalArcs`. */
|
|
147
|
+
function segKey(p1x: number, p1y: number, p2x: number, p2y: number): string {
|
|
148
|
+
const smallerFirst = p1x < p2x || (p1x === p2x && p1y < p2y);
|
|
149
|
+
return smallerFirst
|
|
150
|
+
? `${p1x},${p1y};${p2x},${p2y}`
|
|
151
|
+
: `${p2x},${p2y};${p1x},${p1y}`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const firstArcByKey = new Map<string, number>();
|
|
155
|
+
const dupByKey = new Map<string, DuplicateArcSegment>();
|
|
156
|
+
let totalSegments = 0;
|
|
157
|
+
|
|
158
|
+
seen.forEach((arc) => {
|
|
159
|
+
const hdr = 1 + arc * 2;
|
|
160
|
+
const npoints = arcs[hdr] | 0;
|
|
161
|
+
let zp = arcs[hdr + 1] | 0;
|
|
162
|
+
if (npoints < 2) return;
|
|
163
|
+
|
|
164
|
+
let px = arcs[zp++];
|
|
165
|
+
let py = arcs[zp++];
|
|
166
|
+
for (let i = 1; i < npoints; i++)
|
|
167
|
+
{
|
|
168
|
+
const qx = arcs[zp++];
|
|
169
|
+
const qy = arcs[zp++];
|
|
170
|
+
totalSegments++;
|
|
171
|
+
|
|
172
|
+
const key = segKey(px, py, qx, qy);
|
|
173
|
+
const firstArc = firstArcByKey.get(key);
|
|
174
|
+
if (firstArc === undefined)
|
|
175
|
+
{
|
|
176
|
+
firstArcByKey.set(key, arc);
|
|
177
|
+
}
|
|
178
|
+
else if (firstArc !== arc)
|
|
179
|
+
{
|
|
180
|
+
let entry = dupByKey.get(key);
|
|
181
|
+
if (!entry)
|
|
182
|
+
{
|
|
183
|
+
const pIsSmaller = px < qx || (px === qx && py < qy);
|
|
184
|
+
const s: Point = pIsSmaller ? [px, py] : [qx, qy];
|
|
185
|
+
const e: Point = pIsSmaller ? [qx, qy] : [px, py];
|
|
186
|
+
entry = { segment: { s, e }, arcs: [firstArc] };
|
|
187
|
+
dupByKey.set(key, entry);
|
|
188
|
+
}
|
|
189
|
+
// Sorted append — `seen.forEach` runs arcs in insertion order
|
|
190
|
+
// (object-walk order), so duplicates land in a useful sequence.
|
|
191
|
+
if (entry.arcs[entry.arcs.length - 1] !== arc) entry.arcs.push(arc);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
px = qx;
|
|
195
|
+
py = qy;
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const duplicates: DuplicateArcSegment[] = [];
|
|
200
|
+
dupByKey.forEach((d) => {
|
|
201
|
+
d.arcs.sort((a, b) => a - b);
|
|
202
|
+
duplicates.push(d);
|
|
203
|
+
});
|
|
204
|
+
duplicates.sort((a, b) => a.arcs[0] - b.arcs[0]);
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
ok: duplicates.length === 0,
|
|
208
|
+
duplicates,
|
|
209
|
+
arcsInspected: seen.size,
|
|
210
|
+
totalSegments,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// =============================================================================
|
|
215
|
+
// Cross-arc interior-point-sharing diagnostic
|
|
216
|
+
// =============================================================================
|
|
217
|
+
|
|
218
|
+
/** One offending point: shared between arcs in a way that violates
|
|
219
|
+
* `ptsToArcs`'s "interior set once" invariant. */
|
|
220
|
+
export interface InteriorPointSharing {
|
|
221
|
+
point: Point;
|
|
222
|
+
/** Arcs in which this point is INTERIOR (not the first or last point). */
|
|
223
|
+
interiorInArcs: number[];
|
|
224
|
+
/** Arcs in which this point is an ENDPOINT (first or last point). */
|
|
225
|
+
endpointInArcs: number[];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export interface InteriorPointSharingResult {
|
|
229
|
+
ok: boolean;
|
|
230
|
+
/** Points that, in this single topology, are shared across arcs in a way that
|
|
231
|
+
* causes `ptsToArcs` (inside topojson-client's splice) to silently overwrite
|
|
232
|
+
* its `map.set(here, …)` record for that point.
|
|
233
|
+
*
|
|
234
|
+
* Two failure modes are flagged:
|
|
235
|
+
*
|
|
236
|
+
* a) **Interior in 2+ arcs.** `ptsToArcs` says "Interior points will by
|
|
237
|
+
* definition only be set once." If that invariant doesn't hold,
|
|
238
|
+
* whichever arc walks last wins the `map.set`; the earlier arc's
|
|
239
|
+
* record at that point is lost. `newJunctions` then can't detect that
|
|
240
|
+
* the lost arc needed cutting against another topology, so it stays
|
|
241
|
+
* whole and may produce duplicate segments after splice.
|
|
242
|
+
*
|
|
243
|
+
* b) **Interior in one arc AND endpoint in another.** The same overwrite
|
|
244
|
+
* happens regardless of which one walks last:
|
|
245
|
+
* - If the endpoint write lands last, the interior record is lost
|
|
246
|
+
* and the interior arc never gets its junction check.
|
|
247
|
+
* - If the interior write lands last, the endpoint's
|
|
248
|
+
* junction-forcing role at that point is lost.
|
|
249
|
+
* The `ptsToArcs` comment says endpoint overwrites are OK "since ANY
|
|
250
|
+
* endpoint forces a junction" — that only holds when *all* the
|
|
251
|
+
* overwrites are endpoint-on-endpoint. As soon as an interior is in
|
|
252
|
+
* the mix, the invariant is broken.
|
|
253
|
+
*/
|
|
254
|
+
offenses: InteriorPointSharing[];
|
|
255
|
+
/** Number of unique arcs actually inspected (reachable from objects). */
|
|
256
|
+
arcsInspected: number;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Diagnose whether a single input topology to `splice` violates the
|
|
261
|
+
* `ptsToArcs` "interior set once" invariant. This is a candidate explanation
|
|
262
|
+
* for the case where the input topologies report no duplicated arc segments
|
|
263
|
+
* (per `validateNoDuplicateArcSegments`) but the spliced output does — i.e.
|
|
264
|
+
* the inputs share an interior point across arcs, so within ONE topology the
|
|
265
|
+
* pointMap built by `ptsToArcs` silently loses the loser of the overwrite,
|
|
266
|
+
* `newJunctions` then can't find every cut it needs, arcs aren't broken at
|
|
267
|
+
* the right places, and `dedup` (which only catches full-arc matches) can't
|
|
268
|
+
* fuse the resulting partial overlaps.
|
|
269
|
+
*
|
|
270
|
+
* Implementation mirrors `validateNoDuplicateArcSegments`: walk reachable
|
|
271
|
+
* arcs only (matching `forAllArcPoints({onlyOnce: true})`), then for each
|
|
272
|
+
* point of each reachable arc classify it as start, interior, or end and
|
|
273
|
+
* accumulate the per-point arc sets.
|
|
274
|
+
*
|
|
275
|
+
* Exact float equality (no epsilon) — same coordinate semantics as
|
|
276
|
+
* `splice.js`'s `equalArcs`/`pointEqual`.
|
|
277
|
+
*/
|
|
278
|
+
export function findCrossArcInteriorPointSharing(
|
|
279
|
+
topology: PackedTopologyLike,
|
|
280
|
+
options: ValidateArcSegmentsOptions = {},
|
|
281
|
+
): InteriorPointSharingResult {
|
|
282
|
+
const arcs = topology.packed.arcs;
|
|
283
|
+
const arcindices = topology.packed.arcindices;
|
|
284
|
+
|
|
285
|
+
// -- Phase 1: walk objects → reachable arcs (identical to the segment validator).
|
|
286
|
+
const seen = new Set<number>();
|
|
287
|
+
|
|
288
|
+
function walkRing(z: number): number {
|
|
289
|
+
const narc = arcindices[z++];
|
|
290
|
+
for (let i = 0; i < narc; i++, z++)
|
|
291
|
+
{
|
|
292
|
+
let arc = arcindices[z];
|
|
293
|
+
if (arc < 0) arc = ~arc;
|
|
294
|
+
if (!seen.has(arc)) seen.add(arc);
|
|
295
|
+
}
|
|
296
|
+
return z;
|
|
297
|
+
}
|
|
298
|
+
function walkPolygon(z: number): number {
|
|
299
|
+
const nring = arcindices[z++];
|
|
300
|
+
for (let i = 0; i < nring; i++) z = walkRing(z);
|
|
301
|
+
return z;
|
|
302
|
+
}
|
|
303
|
+
function walkMultiPolygon(z: number): number {
|
|
304
|
+
const npoly = arcindices[z++];
|
|
305
|
+
for (let i = 0; i < npoly; i++) z = walkPolygon(z);
|
|
306
|
+
return z;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
let objectIds: Iterable<string>;
|
|
310
|
+
if (options.objects instanceof Set) objectIds = options.objects;
|
|
311
|
+
else if (options.objects) objectIds = Object.keys(options.objects);
|
|
312
|
+
else objectIds = Object.keys(topology.objects);
|
|
313
|
+
|
|
314
|
+
const objectArcs2 = topology.packed.objectArcs;
|
|
315
|
+
for (const id of objectIds)
|
|
316
|
+
{
|
|
317
|
+
const obj = topology.objects[id];
|
|
318
|
+
if (!obj) continue;
|
|
319
|
+
const z = objectArcs2 ? objectArcs2.get(obj) : undefined;
|
|
320
|
+
if (z === undefined) continue;
|
|
321
|
+
if (obj.type === "Polygon") walkPolygon(z);
|
|
322
|
+
else if (obj.type === "MultiPolygon") walkMultiPolygon(z);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// -- Phase 2: classify each point of each reachable arc as endpoint or interior,
|
|
326
|
+
// accumulate per-point arc sets. Exact float equality via string key.
|
|
327
|
+
function ptKey(x: number, y: number): string { return `${x},${y}`; }
|
|
328
|
+
|
|
329
|
+
const interiorByKey = new Map<string, Set<number>>();
|
|
330
|
+
const endpointByKey = new Map<string, Set<number>>();
|
|
331
|
+
const pointByKey = new Map<string, Point>();
|
|
332
|
+
|
|
333
|
+
seen.forEach((arc) => {
|
|
334
|
+
const hdr = 1 + arc * 2;
|
|
335
|
+
const npoints = arcs[hdr] | 0;
|
|
336
|
+
let zp = arcs[hdr + 1] | 0;
|
|
337
|
+
if (npoints < 1) return;
|
|
338
|
+
const lastIdx = npoints - 1;
|
|
339
|
+
for (let i = 0; i < npoints; i++)
|
|
340
|
+
{
|
|
341
|
+
const x = arcs[zp++];
|
|
342
|
+
const y = arcs[zp++];
|
|
343
|
+
const k = ptKey(x, y);
|
|
344
|
+
if (!pointByKey.has(k)) pointByKey.set(k, [x, y]);
|
|
345
|
+
const isEndpoint = (i === 0 || i === lastIdx);
|
|
346
|
+
const bucket = isEndpoint ? endpointByKey : interiorByKey;
|
|
347
|
+
let s = bucket.get(k);
|
|
348
|
+
if (!s) { s = new Set<number>(); bucket.set(k, s); }
|
|
349
|
+
s.add(arc);
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
// -- Phase 3: flag any point that's interior in 2+ arcs, OR interior in one
|
|
354
|
+
// arc AND endpoint in another arc. Either case breaks the
|
|
355
|
+
// ptsToArcs "interior set once" invariant.
|
|
356
|
+
const offenses: InteriorPointSharing[] = [];
|
|
357
|
+
interiorByKey.forEach((interiorArcs, key) => {
|
|
358
|
+
const endpointArcs = endpointByKey.get(key);
|
|
359
|
+
const interiorCount = interiorArcs.size;
|
|
360
|
+
const endpointCount = endpointArcs ? endpointArcs.size : 0;
|
|
361
|
+
if (interiorCount >= 2 || (interiorCount >= 1 && endpointCount >= 1))
|
|
362
|
+
{
|
|
363
|
+
// For the endpoint set, exclude any arc that's *also* interior at this
|
|
364
|
+
// point (would be a self-touching arc — same arc appearing in both
|
|
365
|
+
// buckets is a degenerate within-arc issue, not the cross-arc problem
|
|
366
|
+
// we're hunting).
|
|
367
|
+
const endpointArcsOther: number[] = endpointArcs
|
|
368
|
+
? Array.from(endpointArcs).filter(a => !interiorArcs.has(a)).sort((a, b) => a - b)
|
|
369
|
+
: [];
|
|
370
|
+
// Re-check the condition after filtering — if interior is just 1 and
|
|
371
|
+
// the only endpoint occurrences were the same arc self-touching, drop
|
|
372
|
+
// this as a false positive.
|
|
373
|
+
if (interiorCount >= 2 || (interiorCount >= 1 && endpointArcsOther.length >= 1))
|
|
374
|
+
{
|
|
375
|
+
offenses.push({
|
|
376
|
+
point: pointByKey.get(key)!,
|
|
377
|
+
interiorInArcs: Array.from(interiorArcs).sort((a, b) => a - b),
|
|
378
|
+
endpointInArcs: endpointArcsOther,
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
return {
|
|
385
|
+
ok: offenses.length === 0,
|
|
386
|
+
offenses,
|
|
387
|
+
arcsInspected: seen.size,
|
|
388
|
+
};
|
|
389
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dra2020/baseclient",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.168",
|
|
4
4
|
"description": "Utility functions for Javascript projects.",
|
|
5
5
|
"main": "dist/baseclient.js",
|
|
6
6
|
"types": "./dist/all/all.d.ts",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"webpack-cli": "^5.1.4"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@dra2020/topojson-client": "^3.2.
|
|
47
|
+
"@dra2020/topojson-client": "^3.2.17",
|
|
48
48
|
"@dra2020/topojson-server": "^3.0.103",
|
|
49
49
|
"@dra2020/topojson-simplify": "^3.0.102",
|
|
50
50
|
"diff-match-patch": "^1.0.5",
|