@reearth/core 0.0.2 → 0.0.4

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,459 @@
1
+ import type {
2
+ GeometryObject,
3
+ Feature as GeoJSONFeature,
4
+ MultiPoint,
5
+ Polygon,
6
+ MultiLineString,
7
+ } from "geojson";
8
+ import proj4 from "proj4";
9
+
10
+ import { generateRandomString } from "../utils";
11
+
12
+ export function parseShp(
13
+ buffer: ArrayBuffer,
14
+ trans?: proj4.Converter | false,
15
+ ): GeoJSONFeature<GeometryObject>[] {
16
+ const headers = parseHeader(buffer);
17
+ const parseFunc = getParseFunction(headers.shpCode, trans);
18
+ const rows = getRows(buffer, headers, parseFunc);
19
+ return rows;
20
+ }
21
+
22
+ function parseHeader(buffer: ArrayBuffer): {
23
+ length: number;
24
+ version: number;
25
+ shpCode: number;
26
+ bbox: number[];
27
+ } {
28
+ const view = new DataView(buffer, 0, 100);
29
+ return {
30
+ length: view.getInt32(6 << 2, false) << 1,
31
+ version: view.getInt32(7 << 2, true),
32
+ shpCode: view.getInt32(8 << 2, true),
33
+ bbox: [
34
+ view.getFloat64(9 << 2, true),
35
+ view.getFloat64(11 << 2, true),
36
+ view.getFloat64(13 << 2, true),
37
+ view.getFloat64(15 << 2, true),
38
+ ],
39
+ };
40
+ }
41
+
42
+ function makeParseCoord(
43
+ trans?: proj4.Converter | false,
44
+ ): (data: DataView, offset: number) => number[] {
45
+ if (trans) {
46
+ return (data: DataView, offset: number) => {
47
+ const x = data.getFloat64(offset, true);
48
+ const y = data.getFloat64(offset + 8, true);
49
+ return trans.inverse([x, y]);
50
+ };
51
+ } else {
52
+ return (data: DataView, offset: number) => [
53
+ data.getFloat64(offset, true),
54
+ data.getFloat64(offset + 8, true),
55
+ ];
56
+ }
57
+ }
58
+
59
+ function getParseFunction(
60
+ shpCode: number,
61
+ trans?: proj4.Converter | false,
62
+ ): (data: ArrayBuffer) => GeoJSONFeature<GeometryObject> | null {
63
+ const num = shpCode > 20 ? shpCode - 20 : shpCode;
64
+ const shpFuncObj: { [key: number]: keyof typeof parseFunctions } = {
65
+ 1: "parsePoint",
66
+ 3: "parsePolyline",
67
+ 5: "parsePolygon",
68
+ 8: "parseMultiPoint",
69
+ 11: "parseZPoint",
70
+ 13: "parseZPolyline",
71
+ 15: "parseZPolygon",
72
+ };
73
+ const funcName = shpFuncObj[num];
74
+ if (!funcName) {
75
+ throw new Error("Unsupported shape type");
76
+ }
77
+ const parseCoord = makeParseCoord(trans);
78
+
79
+ switch (funcName) {
80
+ case "parsePoint":
81
+ case "parseZPoint":
82
+ case "parseMultiPoint":
83
+ case "parsePolyline":
84
+ case "parseZPolyline":
85
+ case "parsePolygon":
86
+ case "parseZPolygon":
87
+ return (data: ArrayBuffer) => parseFunctions[funcName](new DataView(data), parseCoord);
88
+ case "parsePointArray":
89
+ return (data: ArrayBuffer) =>
90
+ parseFunctions[funcName](
91
+ new DataView(data),
92
+ 0,
93
+ new DataView(data).getInt32(32, true),
94
+ parseCoord,
95
+ );
96
+ case "parseArrayGroup":
97
+ return (data: ArrayBuffer) =>
98
+ parseFunctions[funcName](
99
+ new DataView(data),
100
+ 44,
101
+ 40,
102
+ new DataView(data).getInt32(32, true),
103
+ new DataView(data).getInt32(36, true),
104
+ parseCoord,
105
+ );
106
+ default:
107
+ throw new Error("Unsupported shape type");
108
+ }
109
+ }
110
+
111
+ function getRows(
112
+ buffer: ArrayBuffer,
113
+ headers: ReturnType<typeof parseHeader>,
114
+ parseFunc: ReturnType<typeof getParseFunction>,
115
+ ): GeoJSONFeature<GeometryObject>[] {
116
+ const rows: GeoJSONFeature<GeometryObject>[] = [];
117
+ let offset = 100;
118
+ const bufferLength = buffer.byteLength;
119
+ while (offset + 8 <= bufferLength) {
120
+ const record = getRow(buffer, offset, bufferLength);
121
+ if (!record) {
122
+ break;
123
+ }
124
+ offset += 8 + record.len;
125
+ const feature = parseFunc(record.data);
126
+ if (feature) {
127
+ feature.id = generateRandomString(12);
128
+ rows.push(feature);
129
+ }
130
+ }
131
+ return rows;
132
+ }
133
+
134
+ function getRow(
135
+ buffer: ArrayBuffer,
136
+ offset: number,
137
+ bufferLength: number,
138
+ ):
139
+ | {
140
+ id: number;
141
+ len: number;
142
+ data: ArrayBuffer;
143
+ type: number;
144
+ }
145
+ | undefined {
146
+ const view = new DataView(buffer, offset, 12);
147
+ const len = view.getInt32(4, false) << 1;
148
+ const id = view.getInt32(0, false);
149
+ if (len === 0) {
150
+ return {
151
+ id,
152
+ len,
153
+ data: new ArrayBuffer(0),
154
+ type: 0,
155
+ };
156
+ }
157
+ if (offset + len + 8 > bufferLength) {
158
+ return undefined;
159
+ }
160
+ return {
161
+ id,
162
+ len,
163
+ data: buffer.slice(offset + 12, offset + len + 8),
164
+ type: view.getInt32(8, true),
165
+ };
166
+ }
167
+
168
+ const parseFunctions = {
169
+ parsePoint(
170
+ data: DataView,
171
+ parseCoord: ReturnType<typeof makeParseCoord>,
172
+ ): GeoJSONFeature<GeometryObject> {
173
+ return {
174
+ type: "Feature",
175
+ geometry: {
176
+ type: "Point",
177
+ coordinates: parseCoord(data, 0),
178
+ },
179
+ properties: {},
180
+ };
181
+ },
182
+
183
+ parseZPoint(
184
+ data: DataView,
185
+ parseCoord: ReturnType<typeof makeParseCoord>,
186
+ ): GeoJSONFeature<GeometryObject> {
187
+ const pointXY = parseFunctions.parsePoint(data, parseCoord);
188
+ (pointXY.geometry as any).coordinates.push(data.getFloat64(16, true));
189
+ return pointXY;
190
+ },
191
+
192
+ parsePointArray(
193
+ data: DataView,
194
+ offset: number,
195
+ num: number,
196
+ parseCoord: ReturnType<typeof makeParseCoord>,
197
+ ): GeoJSONFeature<GeometryObject> {
198
+ const coordinates: number[][] = [];
199
+ for (let i = 0; i < num; i++) {
200
+ coordinates.push(parseCoord(data, offset));
201
+ offset += 16;
202
+ }
203
+ return {
204
+ type: "Feature",
205
+ geometry: {
206
+ type: "MultiPoint",
207
+ coordinates,
208
+ },
209
+ properties: {},
210
+ };
211
+ },
212
+
213
+ parseZPointArray(
214
+ data: DataView,
215
+ zOffset: number,
216
+ num: number,
217
+ coordinates: number[][],
218
+ ): number[][] {
219
+ for (let i = 0; i < num; i++) {
220
+ coordinates[i].push(data.getFloat64(zOffset, true));
221
+ zOffset += 8;
222
+ }
223
+ return coordinates;
224
+ },
225
+
226
+ parseArrayGroup(
227
+ data: DataView,
228
+ offset: number,
229
+ partOffset: number,
230
+ num: number,
231
+ tot: number,
232
+ parseCoord: ReturnType<typeof makeParseCoord>,
233
+ ): GeoJSONFeature<Polygon> {
234
+ const coordinates: number[][][] = [];
235
+ let pointOffset = offset;
236
+ for (let i = 0; i < num; i++) {
237
+ const pointNum =
238
+ i === num - 1
239
+ ? tot - data.getInt32(partOffset, true)
240
+ : data.getInt32(partOffset + 4, true) - data.getInt32(partOffset, true);
241
+ partOffset += 4;
242
+ if (pointNum === 0) {
243
+ continue;
244
+ }
245
+ const feature = parseFunctions.parsePointArray(data, pointOffset, pointNum, parseCoord);
246
+ coordinates.push((feature.geometry as MultiPoint).coordinates);
247
+ pointOffset += pointNum << 4;
248
+ }
249
+ return {
250
+ type: "Feature",
251
+ geometry: {
252
+ type: "Polygon",
253
+ coordinates,
254
+ },
255
+ properties: {},
256
+ };
257
+ },
258
+
259
+ parseZArrayGroup(
260
+ data: DataView,
261
+ zOffset: number,
262
+ num: number,
263
+ coordinates: number[][][],
264
+ ): number[][][] {
265
+ for (let i = 0; i < num; i++) {
266
+ coordinates[i] = parseFunctions.parseZPointArray(
267
+ data,
268
+ zOffset,
269
+ coordinates[i].length,
270
+ coordinates[i],
271
+ );
272
+ zOffset += coordinates[i].length << 3;
273
+ }
274
+ return coordinates;
275
+ },
276
+
277
+ parseMultiPoint(
278
+ data: DataView,
279
+ parseCoord: ReturnType<typeof makeParseCoord>,
280
+ ): GeoJSONFeature<MultiPoint> {
281
+ const num = data.getInt32(32, true);
282
+ if (num === 0) {
283
+ return {
284
+ type: "Feature",
285
+ geometry: {
286
+ type: "MultiPoint",
287
+ coordinates: [],
288
+ },
289
+ properties: {},
290
+ };
291
+ }
292
+ const bounds = [parseCoord(data, 0), parseCoord(data, 16)];
293
+ const pointOffset = 36;
294
+ const feature = parseFunctions.parsePointArray(data, pointOffset, num, parseCoord);
295
+ const coordinates = (feature.geometry as MultiPoint).coordinates;
296
+ return {
297
+ type: "Feature",
298
+ geometry: {
299
+ type: "MultiPoint",
300
+ coordinates,
301
+ },
302
+ properties: {},
303
+ bbox: [bounds[0][0], bounds[0][1], bounds[1][0], bounds[1][1]],
304
+ };
305
+ },
306
+
307
+ parsePolyline(
308
+ data: DataView,
309
+ parseCoord: ReturnType<typeof makeParseCoord>,
310
+ ): GeoJSONFeature<MultiLineString> {
311
+ const numParts = data.getInt32(32, true);
312
+ if (numParts === 0) {
313
+ return {
314
+ type: "Feature",
315
+ geometry: {
316
+ type: "MultiLineString",
317
+ coordinates: [],
318
+ },
319
+ properties: {},
320
+ };
321
+ }
322
+ const bounds = [parseCoord(data, 0), parseCoord(data, 16)];
323
+ const num = data.getInt32(36, true);
324
+ const partOffset = 40;
325
+ const pointOffset = 40 + (numParts << 2);
326
+ const feature = parseFunctions.parseArrayGroup(
327
+ data,
328
+ pointOffset,
329
+ partOffset,
330
+ numParts,
331
+ num,
332
+ parseCoord,
333
+ );
334
+ const coordinates = (feature.geometry as Polygon).coordinates;
335
+ return {
336
+ type: "Feature",
337
+ geometry: {
338
+ type: "MultiLineString",
339
+ coordinates,
340
+ },
341
+ properties: {},
342
+ bbox: [bounds[0][0], bounds[0][1], bounds[1][0], bounds[1][1]],
343
+ };
344
+ },
345
+
346
+ parseZPolyline(
347
+ data: DataView,
348
+ parseCoord: ReturnType<typeof makeParseCoord>,
349
+ ): GeoJSONFeature<GeometryObject> {
350
+ const feature = parseFunctions.parsePolyline(data, parseCoord);
351
+ const numParts = data.getInt32(32, true);
352
+ const num = data.getInt32(36, true);
353
+ const zOffset = 56 + (num << 4) + (numParts << 2);
354
+ (feature.geometry as any).coordinates = parseFunctions.parseZArrayGroup(
355
+ data,
356
+ zOffset,
357
+ numParts,
358
+ (feature.geometry as any).coordinates,
359
+ );
360
+ return feature;
361
+ },
362
+
363
+ parsePolygon(
364
+ data: DataView,
365
+ parseCoord: ReturnType<typeof makeParseCoord>,
366
+ ): GeoJSONFeature<GeometryObject> {
367
+ const feature = parseFunctions.parsePolyline(data, parseCoord);
368
+ (feature.geometry as any).type = "MultiPolygon";
369
+ (feature.geometry as any).coordinates = handleRings((feature.geometry as any).coordinates);
370
+ return feature;
371
+ },
372
+
373
+ parseZPolygon(
374
+ data: DataView,
375
+ parseCoord: ReturnType<typeof makeParseCoord>,
376
+ ): GeoJSONFeature<GeometryObject> {
377
+ const feature = parseFunctions.parseZPolyline(data, parseCoord);
378
+ (feature.geometry as any).type = "MultiPolygon";
379
+ (feature.geometry as any).coordinates = handleRings((feature.geometry as any).coordinates);
380
+ return feature;
381
+ },
382
+ };
383
+
384
+ function handleRings(rings: number[][][]): number[][][][] {
385
+ const clockwiseRings: {
386
+ ring: number[][];
387
+ bbox: number[];
388
+ children: number[][][];
389
+ }[] = [];
390
+ const counterClockwiseRings: {
391
+ ring: number[][];
392
+ bbox: number[];
393
+ children: number[][][];
394
+ }[] = [];
395
+
396
+ for (const ring of rings) {
397
+ const { ring: coordinates, bbox, clockwise } = isClockwise(ring);
398
+ if (clockwise) {
399
+ clockwiseRings.push({ ring: coordinates, bbox, children: [] });
400
+ } else {
401
+ counterClockwiseRings.push({ ring: coordinates, bbox, children: [] });
402
+ }
403
+ }
404
+
405
+ for (const counterClockwiseRing of counterClockwiseRings) {
406
+ let parent: (typeof clockwiseRings)[number] | undefined;
407
+ for (const clockwiseRing of clockwiseRings) {
408
+ if (contains(clockwiseRing.bbox, counterClockwiseRing.bbox)) {
409
+ parent = clockwiseRing;
410
+ break;
411
+ }
412
+ }
413
+ if (parent) {
414
+ parent.children.push(counterClockwiseRing.ring);
415
+ }
416
+ }
417
+
418
+ return clockwiseRings.map(({ ring, children }) => [ring, ...children]);
419
+ }
420
+
421
+ function isClockwise(ring: number[][]): { ring: number[][]; bbox: number[]; clockwise: boolean } {
422
+ const [firstPoint, ...otherPoints] = ring;
423
+ let minX = firstPoint[0];
424
+ let minY = firstPoint[1];
425
+ let maxX = firstPoint[0];
426
+ let maxY = firstPoint[1];
427
+
428
+ let signedArea = 0;
429
+ let previousPoint = firstPoint;
430
+
431
+ for (const point of otherPoints) {
432
+ const [x, y] = point;
433
+ signedArea += (point[0] - previousPoint[0]) * (point[1] + previousPoint[1]);
434
+ previousPoint = point;
435
+
436
+ if (x < minX) minX = x;
437
+ if (y < minY) minY = y;
438
+ if (x > maxX) maxX = x;
439
+ if (y > maxY) maxY = y;
440
+ }
441
+
442
+ return {
443
+ ring,
444
+ bbox: [minX, minY, maxX, maxY],
445
+ clockwise: signedArea >= 0,
446
+ };
447
+ }
448
+
449
+ function contains(outerBbox: number[], innerBbox: number[]): boolean {
450
+ const [outerMinX, outerMinY, outerMaxX, outerMaxY] = outerBbox;
451
+ const [innerMinX, innerMinY, innerMaxX, innerMaxY] = innerBbox;
452
+
453
+ return (
454
+ outerMinX <= innerMinX &&
455
+ outerMinY <= innerMinY &&
456
+ outerMaxX >= innerMaxX &&
457
+ outerMaxY >= innerMaxY
458
+ );
459
+ }
@@ -0,0 +1,64 @@
1
+ import JSZip from "jszip";
2
+ import proj4 from "proj4";
3
+
4
+ import { parseDbf } from "./parseDbf";
5
+ import { parseShp } from "./parseShp";
6
+ import { combine } from ".";
7
+
8
+ export async function parseZip(buffer: ArrayBuffer): Promise<GeoJSON.GeoJSON | GeoJSON.GeoJSON[]> {
9
+ const zip = await JSZip.loadAsync(buffer);
10
+ const names: string[] = [];
11
+ const projections: { [key: string]: proj4.Converter } = {};
12
+
13
+ for (const key of Object.keys(zip.files)) {
14
+ if (key.indexOf("__MACOSX") !== -1) {
15
+ continue;
16
+ }
17
+
18
+ const fileExt = key.slice(-4).toLowerCase();
19
+ const fileName = key.slice(0, -4);
20
+
21
+ if (fileExt === ".shp") {
22
+ names.push(fileName);
23
+ zip.files[fileName + ".shp"] = zip.files[key];
24
+ } else if (fileExt === ".prj") {
25
+ projections[fileName] = proj4(await zip.files[key].async("text"));
26
+ } else if (key.slice(-5).toLowerCase() === ".json") {
27
+ names.push(fileName);
28
+ } else if (fileExt === ".dbf" || fileExt === ".cpg") {
29
+ zip.files[fileName + fileExt] = zip.files[key];
30
+ }
31
+ }
32
+
33
+ if (!names.length) {
34
+ throw new Error("no layers found");
35
+ }
36
+
37
+ const geojson = await Promise.all(
38
+ names.map(async name => {
39
+ const lastDotIdx = name.lastIndexOf(".");
40
+ if (lastDotIdx > -1 && name.slice(lastDotIdx).indexOf("json") > -1) {
41
+ const parsed = JSON.parse(await zip.files[name].async("text"));
42
+ parsed.name = name.slice(0, lastDotIdx);
43
+ return parsed;
44
+ } else {
45
+ const shpBuffer = await zip.files[name + ".shp"].async("arraybuffer");
46
+ const dbfBuffer = zip.files[name + ".dbf"]
47
+ ? await zip.files[name + ".dbf"].async("arraybuffer")
48
+ : undefined;
49
+ const cpgString = zip.files[name + ".cpg"]
50
+ ? await zip.files[name + ".cpg"].async("text")
51
+ : undefined;
52
+ const prj = projections[name];
53
+
54
+ const shp = parseShp(shpBuffer, prj);
55
+ const dbf = dbfBuffer ? parseDbf(dbfBuffer, cpgString) : undefined;
56
+
57
+ const parsed = combine(shp, dbf);
58
+ return parsed;
59
+ }
60
+ }),
61
+ );
62
+
63
+ return geojson.length === 1 ? geojson[0] : geojson;
64
+ }
@@ -6,14 +6,12 @@ import { JPLiteral } from "./expression";
6
6
 
7
7
  export const VARIABLE_PREFIX = "czm_";
8
8
 
9
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
9
  export function replaceVariables(expression: string, feature?: any): [string, JPLiteral[]] {
11
10
  let exp = expression;
12
11
  let result = "";
13
12
  const literalJP: JPLiteral[] = [];
14
13
  let i = exp.indexOf("${");
15
14
  const featureDefined = typeof feature !== "undefined";
16
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
15
  const jsonPathCache: Record<string, any[]> = {};
18
16
  const varExpRegex = /^\$./;
19
17
  while (i >= 0) {
@@ -68,7 +68,6 @@ export function evalLayerAppearances(
68
68
  );
69
69
  }
70
70
 
71
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
72
71
  function recursiveValEval(obj: any, layer: LayerSimple, feature?: Feature): any {
73
72
  return Object.fromEntries(
74
73
  Object.entries(obj).map(([k, v]) => {
@@ -92,7 +91,6 @@ export function clearAllExpressionCaches(
92
91
  });
93
92
  }
94
93
 
95
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
96
94
  function recursiveClear(obj: any, layer: LayerSimple | undefined, feature: Feature | undefined) {
97
95
  Object.entries(obj).forEach(([, v]) => {
98
96
  // if v is an object itself and not a null, recurse deeper
@@ -115,18 +113,15 @@ function recursiveClear(obj: any, layer: LayerSimple | undefined, feature: Featu
115
113
  });
116
114
  }
117
115
 
118
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
119
116
  function hasExpression(e: any): e is ExpressionContainer {
120
117
  return typeof e === "object" && e && "expression" in e;
121
118
  }
122
119
 
123
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
124
120
  function hasNonExpressionObject(v: any): boolean {
125
121
  return typeof v === "object" && v && !("expression" in v) && !Array.isArray(v);
126
122
  }
127
123
 
128
124
  export function evalExpression(
129
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
130
125
  expressionContainer: any,
131
126
  layer?: LayerSimple,
132
127
  feature?: Feature,
@@ -7,7 +7,6 @@ export const generateRandomString = (len: number): string => {
7
7
  .toLowerCase();
8
8
  };
9
9
 
10
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
10
  export const recursiveJSONParse = (obj: any): any => {
12
11
  if (typeof obj !== "object" || obj === null) {
13
12
  return obj;
@@ -33,8 +33,6 @@ const render = (
33
33
  return rtlRender(ui, { wrapper: Wrapper, ...renderOptions });
34
34
  };
35
35
 
36
- // eslint-disable-next-line import/export
37
36
  export * from "@testing-library/react";
38
37
 
39
- // eslint-disable-next-line import/export
40
38
  export { render };
Binary file