@mmerlone/react-tz-globepicker 0.1.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.
@@ -0,0 +1,246 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { fileURLToPath } from "url";
4
+ import { geoContains, geoCentroid, geoArea } from "d3-geo";
5
+ import type {
6
+ FeatureCollection,
7
+ Feature,
8
+ Geometry,
9
+ GeoJsonProperties,
10
+ Position,
11
+ } from "geojson";
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = path.dirname(__filename);
15
+
16
+ const COUNTRIES_PATH = path.resolve(__dirname, "../src/data/globe-countries.json");
17
+ const GEOGRAPHIC_IDL_PATH = path.resolve(
18
+ __dirname,
19
+ "../src/data/geographic-idl.json",
20
+ );
21
+ const IANA_TIMEZONES_DIR = path.resolve(__dirname, "../src/data/iana-timezones");
22
+
23
+ function tzidToFileStem(tzid: string): string {
24
+ const encoded = encodeURIComponent(tzid).replace(/%/g, "_");
25
+ return `tz-${encoded}`;
26
+ }
27
+
28
+ function readFeatureCollection(
29
+ filePath: string,
30
+ ): FeatureCollection<Geometry, GeoJsonProperties> {
31
+ const parsed = JSON.parse(
32
+ fs.readFileSync(filePath, "utf8"),
33
+ ) as FeatureCollection<Geometry, GeoJsonProperties>;
34
+
35
+ if (parsed.type !== "FeatureCollection" || !Array.isArray(parsed.features)) {
36
+ throw new Error(`Invalid FeatureCollection at ${filePath}`);
37
+ }
38
+
39
+ return parsed;
40
+ }
41
+
42
+ function flattenCoords(
43
+ geom: Geometry | null | undefined,
44
+ out: Array<[number, number]> = [],
45
+ ): Array<[number, number]> {
46
+ if (!geom) return out;
47
+
48
+ type CoordsUnion = Position | Position[] | Position[][] | Position[][][];
49
+ function hasCoordinates(obj: unknown): obj is { coordinates: CoordsUnion } {
50
+ return (
51
+ typeof obj === "object" &&
52
+ obj !== null &&
53
+ "coordinates" in (obj as Record<string, unknown>)
54
+ );
55
+ }
56
+
57
+ if (!hasCoordinates(geom)) return out;
58
+ const coords = geom.coordinates as CoordsUnion;
59
+
60
+ function walk(arr: unknown): void {
61
+ if (!Array.isArray(arr)) return;
62
+ if (
63
+ arr.length === 2 &&
64
+ typeof arr[0] === "number" &&
65
+ typeof arr[1] === "number"
66
+ ) {
67
+ out.push([arr[0], arr[1]]);
68
+ return;
69
+ }
70
+ for (const c of arr) walk(c);
71
+ }
72
+
73
+ walk(coords);
74
+ return out;
75
+ }
76
+
77
+ function analyzeFeature(
78
+ feat: Feature<Geometry, GeoJsonProperties>,
79
+ countriesFeat: FeatureCollection<Geometry, GeoJsonProperties> | null,
80
+ ): {
81
+ geomType: string;
82
+ totalCoords: number;
83
+ sampleCoord: [number, number] | undefined;
84
+ firstAbsGt90: number;
85
+ secondAbsGt90: number;
86
+ minFirst: number;
87
+ maxFirst: number;
88
+ minSecond: number;
89
+ maxSecond: number;
90
+ containedCountries: number;
91
+ containedSample: string[];
92
+ } {
93
+ const geom = feat.geometry as Geometry | undefined;
94
+ const coords = flattenCoords(geom);
95
+ if (coords.length === 0) {
96
+ return {
97
+ geomType: geom?.type ?? "unknown",
98
+ totalCoords: 0,
99
+ sampleCoord: undefined,
100
+ firstAbsGt90: 0,
101
+ secondAbsGt90: 0,
102
+ minFirst: NaN,
103
+ maxFirst: NaN,
104
+ minSecond: NaN,
105
+ maxSecond: NaN,
106
+ containedCountries: 0,
107
+ containedSample: [],
108
+ };
109
+ }
110
+
111
+ const sample = coords[0];
112
+ const firstAbsGt90 = coords.filter(([a]) => Math.abs(a) > 90).length;
113
+ const secondAbsGt90 = coords.filter(([, b]) => Math.abs(b) > 90).length;
114
+
115
+ const minFirst = Math.min(...coords.map((c) => c[0]));
116
+ const maxFirst = Math.max(...coords.map((c) => c[0]));
117
+ const minSecond = Math.min(...coords.map((c) => c[1]));
118
+ const maxSecond = Math.max(...coords.map((c) => c[1]));
119
+
120
+ let containedCountries = 0;
121
+ const containedSample: string[] = [];
122
+ if (countriesFeat) {
123
+ for (const country of countriesFeat.features) {
124
+ const centroid = geoCentroid(country);
125
+ try {
126
+ if (geoContains(feat, centroid)) {
127
+ containedCountries++;
128
+ const properties = country.properties as Record<string, unknown> | null;
129
+ const name = properties?.name ?? properties?.ADMIN ?? "unknown";
130
+ containedSample.push(String(name));
131
+ if (containedSample.length >= 5) break;
132
+ }
133
+ } catch {
134
+ // Ignore invalid geometries during diagnostics.
135
+ }
136
+ }
137
+ }
138
+
139
+ return {
140
+ geomType: geom?.type ?? "unknown",
141
+ totalCoords: coords.length,
142
+ sampleCoord: sample,
143
+ firstAbsGt90,
144
+ secondAbsGt90,
145
+ minFirst,
146
+ maxFirst,
147
+ minSecond,
148
+ maxSecond,
149
+ containedCountries,
150
+ containedSample,
151
+ };
152
+ }
153
+
154
+ async function main(): Promise<void> {
155
+ if (!fs.existsSync(COUNTRIES_PATH)) {
156
+ console.error("globe-countries.json not found at", COUNTRIES_PATH);
157
+ process.exit(1);
158
+ }
159
+ if (!fs.existsSync(GEOGRAPHIC_IDL_PATH)) {
160
+ console.error("geographic-idl.json not found at", GEOGRAPHIC_IDL_PATH);
161
+ process.exit(1);
162
+ }
163
+ if (!fs.existsSync(IANA_TIMEZONES_DIR)) {
164
+ console.error("iana-timezones directory not found at", IANA_TIMEZONES_DIR);
165
+ process.exit(1);
166
+ }
167
+
168
+ const countriesFc = readFeatureCollection(COUNTRIES_PATH);
169
+ const geographicIdlFc = readFeatureCollection(GEOGRAPHIC_IDL_PATH);
170
+
171
+ const targets = [
172
+ "America/New_York",
173
+ "America/Belem",
174
+ "America/Bahia",
175
+ "Asia/Yakutsk",
176
+ ];
177
+
178
+ for (const tz of targets) {
179
+ console.log("\n==", tz, "==");
180
+ const featurePath = path.join(IANA_TIMEZONES_DIR, `${tzidToFileStem(tz)}.json`);
181
+ if (!fs.existsSync(featurePath)) {
182
+ console.log("No split geometry file for", tz);
183
+ continue;
184
+ }
185
+
186
+ const tzFc = readFeatureCollection(featurePath);
187
+ console.log("matching features:", tzFc.features.length);
188
+
189
+ for (const feature of tzFc.features) {
190
+ const result = analyzeFeature(feature, countriesFc);
191
+ const area = geoArea(feature);
192
+ console.log("geomType:", result.geomType);
193
+ console.log("totalCoords:", result.totalCoords);
194
+ console.log("geoArea (steradians):", area);
195
+ console.log("sampleCoord:", result.sampleCoord);
196
+ console.log(
197
+ "firstAbsGt90:",
198
+ result.firstAbsGt90,
199
+ "secondAbsGt90:",
200
+ result.secondAbsGt90,
201
+ );
202
+ console.log("minFirst..maxFirst:", result.minFirst, "..", result.maxFirst);
203
+ console.log(
204
+ "minSecond..maxSecond:",
205
+ result.minSecond,
206
+ "..",
207
+ result.maxSecond,
208
+ );
209
+ console.log(
210
+ "containedCountries (centroid test):",
211
+ result.containedCountries,
212
+ "sample:",
213
+ result.containedSample,
214
+ );
215
+ }
216
+ }
217
+
218
+ console.log("\n== geographic_idl ==");
219
+ console.log("features:", geographicIdlFc.features.length);
220
+ if (geographicIdlFc.features.length) {
221
+ const sample = geographicIdlFc.features[0];
222
+ if (sample) {
223
+ console.log("sample properties:", sample.properties || {});
224
+ const result = analyzeFeature(sample, countriesFc);
225
+ console.log(
226
+ "sample coord count:",
227
+ result.totalCoords,
228
+ "geomType:",
229
+ result.geomType,
230
+ );
231
+ console.log("sample coord:", result.sampleCoord);
232
+ console.log("minFirst..maxFirst:", result.minFirst, "..", result.maxFirst);
233
+ console.log(
234
+ "minSecond..maxSecond:",
235
+ result.minSecond,
236
+ "..",
237
+ result.maxSecond,
238
+ );
239
+ }
240
+ }
241
+ }
242
+
243
+ main().catch((error: unknown) => {
244
+ console.error(error);
245
+ process.exit(1);
246
+ });