@reearth/core 0.0.3 → 0.0.5-beta.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reearth/core",
3
- "version": "0.0.3",
3
+ "version": "0.0.5-beta.0",
4
4
  "author": "Re:Earth contributors <community@reearth.io>",
5
5
  "license": "Apache-2.0",
6
6
  "description": "A library that abstracts a map engine as one common API.",
@@ -13,20 +13,23 @@
13
13
  "dist"
14
14
  ],
15
15
  "scripts": {
16
- "dev": "vite",
16
+ "dev": "vite -c vite.config.example.ts",
17
17
  "build": "tsc && vite build",
18
- "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
18
+ "lint": "eslint . --ext ts,tsx --report-unused-disable-directives",
19
19
  "preview": "vite preview",
20
20
  "test": "vitest",
21
21
  "storybook": "storybook dev -p 6007",
22
- "build-storybook": "storybook build"
22
+ "build-storybook": "storybook build",
23
+ "preversion": "yarn test run",
24
+ "version": "yarn build"
23
25
  },
24
26
  "engines": {
25
27
  "node": ">=20"
26
28
  },
27
29
  "peerDependencies": {
28
30
  "react": "^18.2.0",
29
- "react-dom": "^18.2.0"
31
+ "react-dom": "^18.2.0",
32
+ "cesium": "1.x"
30
33
  },
31
34
  "dependencies": {
32
35
  "@reearth/cesium-mvt-imagery-provider": "1.5.4",
@@ -34,6 +37,7 @@
34
37
  "@seznam/compose-react-refs": "1.0.6",
35
38
  "@turf/invariant": "6.5.0",
36
39
  "@turf/turf": "6.5.0",
40
+ "@types/proj4": "2.5.5",
37
41
  "@ungap/event-target": "0.2.4",
38
42
  "@xstate/react": "3.2.1",
39
43
  "cesium-dnd": "1.1.0",
@@ -50,6 +54,7 @@
50
54
  "lodash-es": "4.17.21",
51
55
  "lru-cache": "8.0.4",
52
56
  "pbf": "3.2.1",
57
+ "proj4": "2.11.0",
53
58
  "protomaps": "1.23.1",
54
59
  "react-dnd": "16.0.1",
55
60
  "react-dnd-html5-backend": "16.0.1",
@@ -106,6 +111,7 @@
106
111
  "vite-plugin-cesium": "1.2.22",
107
112
  "vite-plugin-dts": "3.8.1",
108
113
  "vite-plugin-svgr": "4.2.0",
114
+ "vite-tsconfig-paths": "^4.3.2",
109
115
  "vitest": "1.0.4",
110
116
  "web-streams-polyfill": "3.2.1"
111
117
  },
package/src/Map/utils.ts CHANGED
@@ -20,8 +20,14 @@ export function mergeProperty(a: any, b: any) {
20
20
  return mergeWith(
21
21
  a2,
22
22
  b,
23
- (s: any, v: any, _k: string | number | symbol, _obj: any, _src: any, stack: { size: number }) =>
24
- stack.size > 0 || Array.isArray(v) ? v ?? s : undefined,
23
+ (
24
+ s: any,
25
+ v: any,
26
+ _k: string | number | symbol,
27
+ _obj: any,
28
+ _src: any,
29
+ stack: { size: number },
30
+ ) => (stack.size > 0 || Array.isArray(v) ? v ?? s : undefined),
25
31
  );
26
32
  }
27
33
 
@@ -372,11 +372,15 @@ export default ({
372
372
  | GroundPrimitive
373
373
  | ImageryLayer
374
374
  >();
375
+ const prevSelectedImageryFeatureId = useRef<string | undefined>();
376
+
375
377
  // manage layer selection
376
378
  useEffect(() => {
377
379
  const viewer = cesium.current?.cesiumElement;
378
380
  if (!viewer || viewer.isDestroyed()) return;
379
381
 
382
+ if (prevSelectedImageryFeatureId.current === selectedLayerId?.featureId) return;
383
+
380
384
  const prevTag = getTag(prevSelectedEntity.current);
381
385
  if (
382
386
  (!prevTag?.featureId &&
@@ -571,6 +575,8 @@ export default ({
571
575
  const viewer = cesium.current?.cesiumElement;
572
576
  if (!viewer || viewer.isDestroyed()) return;
573
577
 
578
+ prevSelectedImageryFeatureId.current = undefined;
579
+
574
580
  const entity =
575
581
  findEntity(viewer, undefined, selectedLayerId?.featureId) ||
576
582
  findEntity(viewer, selectedLayerId?.layerId);
@@ -679,63 +685,60 @@ export default ({
679
685
  const l = await scene.imageryLayers.pickImageryLayerFeatures(pickRay, scene);
680
686
 
681
687
  // NOTE: For now we only send the first selected feature to onLayerSelect instead of sending all of them: @pyshx
682
- if (!l) return;
683
- const f = l[0];
688
+ const f = l?.[0];
689
+
690
+ const appearanceType = f?.data?.appearanceType;
691
+
692
+ if (appearanceType && f?.data?.feature?.[appearanceType]?.show !== false) {
693
+ const tag = getTag(f.imageryLayer);
694
+
695
+ const pos = f.position;
696
+ if (pos) {
697
+ // NOTE: Instantiate temporal Cesium.Entity to display indicator.
698
+ // Although we want to use `viewer.selectionIndicator.viewModel.position` and `animateAppear`, Cesium reset selection position if `viewer.selectedEntity` is not set.
699
+ // ref: https://github.com/CesiumGS/cesium/blob/9295450e64c3077d96ad579012068ea05f97842c/packages/widgets/Source/Viewer/Viewer.js#L1843-L1876
700
+ // issue: https://github.com/CesiumGS/cesium/issues/7965
701
+ requestAnimationFrame(() => {
702
+ if (!tag?.hideIndicator) {
703
+ viewer.selectedEntity = new Entity({
704
+ position: Cartographic.toCartesian(pos),
705
+ });
706
+ }
707
+ });
708
+ }
684
709
 
685
- const appearanceType = f.data?.appearanceType;
686
- if (appearanceType && f.data?.feature?.[appearanceType]?.show === false) {
687
- return;
688
- }
710
+ const layer = tag?.layerId
711
+ ? layersRef?.current?.overriddenLayers().find(l => l.id === tag.layerId) ??
712
+ layersRef?.current?.findById(tag.layerId)
713
+ : undefined;
714
+ const content = getEntityContent(
715
+ f.data.feature ?? f,
716
+ viewer.clock.currentTime ?? new JulianDate(),
717
+ tag?.layerId ? layer?.infobox?.property?.defaultContent : undefined,
718
+ );
719
+ prevSelectedImageryFeatureId.current = f.data.featureId;
720
+ onLayerSelect?.(
721
+ f.data.layerId,
722
+ f.data.featureId,
723
+ content.value.length
724
+ ? {
725
+ defaultInfobox: {
726
+ title: layer?.title ?? f.name,
727
+ content,
728
+ },
729
+ }
730
+ : undefined,
731
+ {
732
+ feature: f.data.feature,
733
+ },
734
+ );
689
735
 
690
- const tag = getTag(f.imageryLayer);
691
-
692
- const pos = f.position;
693
- if (pos) {
694
- // NOTE: Instantiate temporal Cesium.Entity to display indicator.
695
- // Although we want to use `viewer.selectionIndicator.viewModel.position` and `animateAppear`, Cesium reset selection position if `viewer.selectedEntity` is not set.
696
- // ref: https://github.com/CesiumGS/cesium/blob/9295450e64c3077d96ad579012068ea05f97842c/packages/widgets/Source/Viewer/Viewer.js#L1843-L1876
697
- // issue: https://github.com/CesiumGS/cesium/issues/7965
698
- requestAnimationFrame(() => {
699
- if (!tag?.hideIndicator) {
700
- viewer.selectedEntity = new Entity({
701
- position: Cartographic.toCartesian(pos),
702
- });
703
- }
704
- });
736
+ return;
705
737
  }
706
-
707
- const layer = tag?.layerId
708
- ? layersRef?.current?.overriddenLayers().find(l => l.id === tag.layerId) ??
709
- layersRef?.current?.findById(tag.layerId)
710
- : undefined;
711
- const content = getEntityContent(
712
- f.data.feature ?? f,
713
- viewer.clock.currentTime ?? new JulianDate(),
714
- tag?.layerId ? layer?.infobox?.property?.defaultContent : undefined,
715
- );
716
- onLayerSelect?.(
717
- f.data.layerId,
718
- f.data.featureId,
719
- content.value.length
720
- ? {
721
- defaultInfobox: {
722
- title: layer?.title ?? f.name,
723
- content,
724
- },
725
- }
726
- : undefined,
727
- {
728
- feature: f.data.feature,
729
- },
730
- );
731
-
732
- return;
733
738
  }
734
739
  }
735
740
 
736
- if (!entity || (entity instanceof Entity && tag?.hideIndicator)) {
737
- viewer.selectedEntity = undefined;
738
- }
741
+ viewer.selectedEntity = undefined;
739
742
  onLayerSelect?.();
740
743
  },
741
744
  [
@@ -11,7 +11,6 @@ declare module "@cesium/engine" {
11
11
  // (...args: Parameter<Listener>[]).
12
12
  // This cannot be fixed by augmentation but by overloading.
13
13
  export interface Event<Listener extends (...args: any[]) => void = (...args: any[]) => void> {
14
- // eslint-disable-next-line @typescript-eslint/method-signature-style
15
14
  raiseEvent(...arguments: Parameters<Listener>): void;
16
15
  }
17
16
 
@@ -21,8 +21,8 @@ export function convertGeometryToPositionsArray(
21
21
  geometry.type === "LineString"
22
22
  ? coordinatesToPositionsArray([geometry.coordinates])
23
23
  : geometry.type === "Polygon"
24
- ? coordinatesToPositionsArray(geometry.coordinates)
25
- : geometry.coordinates.flatMap(coordinates => coordinatesToPositionsArray(coordinates))
24
+ ? coordinatesToPositionsArray(geometry.coordinates)
25
+ : geometry.coordinates.flatMap(coordinates => coordinatesToPositionsArray(coordinates))
26
26
  ).filter(({ length }) => length > 0);
27
27
  }
28
28
 
@@ -178,8 +178,8 @@ export function computeAtom(cache?: typeof globalDataFeaturesCache) {
178
178
  ...("properties" in value
179
179
  ? { properties: value.properties }
180
180
  : l && "properties" in l
181
- ? { properties: l.properties }
182
- : {}),
181
+ ? { properties: l.properties }
182
+ : {}),
183
183
  }
184
184
  : undefined,
185
185
  );
@@ -24,7 +24,7 @@ const handler = (xmlDataStr: string) => {
24
24
  const parseGPX = (gpxSource: string) => {
25
25
  const parseMethod = (gpxSource: string): Document | null => {
26
26
  // Verify that we are in a browser
27
- if (typeof document == undefined) return null;
27
+ if (typeof document === "undefined") return null;
28
28
 
29
29
  const domParser = new window.DOMParser();
30
30
  return domParser.parseFromString(gpxSource, "text/xml");
@@ -0,0 +1,51 @@
1
+ import type { GeometryObject, Feature as GeoJSONFeature, FeatureCollection } from "geojson";
2
+
3
+ import type { Data, DataRange, Feature } from "../../types";
4
+ import { processGeoJSON } from "../geojson";
5
+ import { f, FetchOptions, generateRandomString } from "../utils";
6
+
7
+ import { parseZip } from "./parseZip";
8
+
9
+ export async function combine(
10
+ shp: GeoJSONFeature<GeometryObject>[],
11
+ dbf?: any[],
12
+ ): Promise<FeatureCollection> {
13
+ const out: FeatureCollection = {
14
+ type: "FeatureCollection",
15
+ features: [],
16
+ };
17
+ const len = shp.length;
18
+ dbf = dbf || [];
19
+ for (let i = 0; i < len; i++) {
20
+ out.features.push({
21
+ type: "Feature",
22
+ geometry: shp[i].geometry,
23
+ id: generateRandomString(12),
24
+ properties: dbf[i] || {},
25
+ });
26
+ }
27
+ return out;
28
+ }
29
+
30
+ export async function fetchShapefile(
31
+ data: Data,
32
+ range?: DataRange,
33
+ options?: FetchOptions,
34
+ ): Promise<Feature[] | void> {
35
+ const arrayBuffer = data.url ? await (await f(data.url, options)).arrayBuffer() : data.value;
36
+
37
+ if (!arrayBuffer) {
38
+ console.error("No data provided");
39
+ }
40
+
41
+ const geojson = await parseZip(arrayBuffer);
42
+ if (Array.isArray(geojson)) {
43
+ const combinedFeatureCollection: FeatureCollection = {
44
+ type: "FeatureCollection",
45
+ features: geojson.flatMap(layer => (layer as FeatureCollection).features),
46
+ };
47
+ return processGeoJSON(combinedFeatureCollection, range);
48
+ } else {
49
+ return processGeoJSON(geojson as FeatureCollection, range);
50
+ }
51
+ }
@@ -0,0 +1,85 @@
1
+ export function parseDbf(dbf: ArrayBuffer, cpg?: string): Record<string, any>[] {
2
+ const header = parseHeader(dbf);
3
+ const records = parseRecords(dbf, header, cpg);
4
+ return records;
5
+ }
6
+
7
+ export function parseHeader(dbf: ArrayBuffer): {
8
+ version: number;
9
+ dateUpdated: Date;
10
+ recordCount: number;
11
+ recordSize: number;
12
+ fields: {
13
+ name: string;
14
+ type: string;
15
+ size: number;
16
+ decimals: number;
17
+ }[];
18
+ } {
19
+ const view = new DataView(dbf);
20
+ const version = view.getUint8(0);
21
+ const dateUpdated = new Date(1900 + view.getUint8(1), view.getUint8(2) - 1, view.getUint8(3));
22
+ const recordCount = view.getInt32(4, true);
23
+ const headerSize = view.getInt16(8, true);
24
+ const recordSize = view.getInt16(10, true);
25
+ const fields = parseFields(new DataView(dbf, 32, headerSize - 32));
26
+ return { version, dateUpdated, recordCount, recordSize, fields };
27
+ }
28
+
29
+ function parseFields(fieldData: DataView): {
30
+ name: string;
31
+ type: string;
32
+ size: number;
33
+ decimals: number;
34
+ }[] {
35
+ const fields = [];
36
+ let offset = 0;
37
+ while (offset < fieldData.byteLength && fieldData.getUint8(offset) !== 0x0d) {
38
+ const name = new TextDecoder()
39
+ .decode(new Uint8Array(fieldData.buffer, fieldData.byteOffset + offset, 11))
40
+ .replace(/\0.*/g, "");
41
+ const type = String.fromCharCode(fieldData.getUint8(offset + 11));
42
+ const size = fieldData.getUint8(offset + 16);
43
+ const decimals = fieldData.getUint8(offset + 17);
44
+ fields.push({ name, type, size, decimals });
45
+ offset += 32;
46
+ }
47
+ return fields;
48
+ }
49
+
50
+ function parseRecords(
51
+ dbf: ArrayBuffer,
52
+ header: ReturnType<typeof parseHeader>,
53
+ cpg?: string,
54
+ ): Record<string, any>[] {
55
+ const records = [];
56
+ const decoder = cpg ? new TextDecoder(cpg) : new TextDecoder();
57
+ const fields = header.fields;
58
+ for (let i = 0; i < header.recordCount; i++) {
59
+ const record: Record<string, any> = {};
60
+ const offset = header.recordSize * i + 1;
61
+ for (const field of fields) {
62
+ const value = new TextDecoder().decode(new Uint8Array(dbf, offset, field.size)).trim();
63
+ switch (field.type) {
64
+ case "N":
65
+ case "F":
66
+ record[field.name] = parseFloat(value);
67
+ break;
68
+ case "D":
69
+ record[field.name] = new Date(
70
+ parseInt(value.slice(0, 4), 10),
71
+ parseInt(value.slice(4, 6), 10) - 1,
72
+ parseInt(value.slice(6, 8), 10),
73
+ );
74
+ break;
75
+ case "L":
76
+ record[field.name] = value.toLowerCase() === "y" || value.toLowerCase() === "t";
77
+ break;
78
+ default:
79
+ record[field.name] = decoder.decode(new Uint8Array(dbf, offset, field.size)).trim();
80
+ }
81
+ }
82
+ records.push(record);
83
+ }
84
+ return records;
85
+ }