geopreview 1.0.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/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # geopreview
2
+
3
+ Geospatial file inspector for the terminal. Displays statistics summary and a Braille-character map preview.
4
+
5
+ ```
6
+ world.geojson
7
+ FeatureCollection / 240 features / 9.4 MB
8
+
9
+ GEOMETRY SUMMARY PROPERTY SCHEMA
10
+ MultiPolygon 240 fid number ▓▓▓▓▓▓▓▓▓▓ (240/240)
11
+ iso_a2 string ▓▓▓▓▓▓▓▓▓▓ (240/240)
12
+ NAME string ▓▓▓▓▓▓▓▓▓▓ (238/240)
13
+
14
+ MAP PREVIEW
15
+ ⣀⡀ ⢀⣀⣀⣀⣀⡀ ⣀⣰⣻⣿⣿⡿⣿⣿⣿⣿⣿⣿⣤⣉⠉⠙⣦⡀ ⢠⣴⡿⠃ ⡀ ⢙⣊⣁⣀
16
+ ⠿⣶⣮⣿⡆ ⡏⠉⠉⠉⠉⠙⠛⠛⠛⠛⣫⣿⣿⣹⣿⣹⣷⠂⠹⣷⢀⣴⠶⠛⠩⣧⣶⠄
17
+ ```
18
+
19
+ ## Supported Formats
20
+
21
+ | Format | Extension |
22
+ |---|---|
23
+ | GeoJSON | `.geojson`, `.json` |
24
+ | FlatGeobuf | `.fgb` |
25
+ | GeoParquet | `.parquet` |
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ npm install -g geopreview
31
+ ```
32
+
33
+ Requires Node.js v18+.
34
+
35
+ ## Usage
36
+
37
+ ```bash
38
+ gp <file>
39
+ ```
40
+
41
+ ### Options
42
+
43
+ | Option | Description | Default |
44
+ |---|---|---|
45
+ | `--no-map` | Hide map preview | — |
46
+ | `--width`, `-w` | Map width (chars) | terminal width - 4 |
47
+ | `--height`, `-h` | Map height (chars) | 24 |
48
+ | `--props`, `-p` | Property display limit | 15 |
49
+
50
+ ### Examples
51
+
52
+ ```bash
53
+ gp world.geojson
54
+ gp countries.fgb --no-map
55
+ gp buildings.parquet --props 30
56
+ gp routes.geojson -w 100 -h 30
57
+ ```
58
+
59
+ ### Keyboard
60
+
61
+ | Key | Action |
62
+ |---|---|
63
+ | `q` / `Ctrl+C` | Exit |
64
+
65
+ ## What It Shows
66
+
67
+ - **Header** — File name, feature count, file size
68
+ - **Geometry Summary** — Count per geometry type (Point, Polygon, MultiPolygon, etc.)
69
+ - **Property Schema** — Property names, types, and fill rate with visual bar
70
+ - **Map Preview** — Braille-character rendering of all geometries
71
+
72
+ ## License
73
+
74
+ ISC
package/dist/App.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * ルートコンポーネント — 全UIコンポーネントを組み立てる。
3
+ *
4
+ * データフロー:
5
+ * cli.tsx → App(filePath, parser, options)
6
+ * → useFileParser で非同期解析
7
+ * → ParseResult を各子コンポーネントに分配
8
+ *
9
+ * useInput で "q" キーによる終了を実装。
10
+ * ただしパイプ入力等で stdin が TTY でない場合は useInput を無効化する
11
+ * (Ink の Raw mode エラーを回避するため)。
12
+ */
13
+ import type { FC } from "react";
14
+ import type { CliOptions, FileParser } from "./lib/types.js";
15
+ interface Props {
16
+ filePath: string;
17
+ parser: FileParser;
18
+ options: CliOptions;
19
+ }
20
+ declare const App: FC<Props>;
21
+ export default App;
package/dist/App.js ADDED
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * ルートコンポーネント — 全UIコンポーネントを組み立てる。
4
+ *
5
+ * データフロー:
6
+ * cli.tsx → App(filePath, parser, options)
7
+ * → useFileParser で非同期解析
8
+ * → ParseResult を各子コンポーネントに分配
9
+ *
10
+ * useInput で "q" キーによる終了を実装。
11
+ * ただしパイプ入力等で stdin が TTY でない場合は useInput を無効化する
12
+ * (Ink の Raw mode エラーを回避するため)。
13
+ */
14
+ import { Box, Text, useApp, useInput } from "ink";
15
+ import { Spinner } from "@inkjs/ui";
16
+ import Header from "./components/Header.js";
17
+ import GeometrySummary from "./components/GeometrySummary.js";
18
+ import PropertySchema from "./components/PropertySchema.js";
19
+ import MapPreview from "./components/MapPreview.js";
20
+ import Footer from "./components/Footer.js";
21
+ import { useFileParser } from "./hooks/useFileParser.js";
22
+ const App = ({ filePath, parser, options }) => {
23
+ const { result, loading, error } = useFileParser(filePath, parser);
24
+ const { exit } = useApp();
25
+ useInput((input) => {
26
+ if (input === "q")
27
+ exit();
28
+ }, { isActive: process.stdin.isTTY === true });
29
+ if (loading)
30
+ return _jsx(Spinner, { label: "Loading..." });
31
+ if (error)
32
+ return _jsx(Text, { color: "red", children: error.message });
33
+ if (!result)
34
+ return null;
35
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Header, { result: result }), _jsxs(Box, { children: [_jsx(GeometrySummary, { counts: result.geometryCounts }), _jsx(PropertySchema, { stats: result.propertyStats, limit: options.props })] }), !options.noMap && (_jsx(MapPreview, { result: result, width: options.width, height: options.height })), _jsx(Footer, {})] }));
36
+ };
37
+ export default App;
package/dist/cli.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI エントリポイント。
4
+ *
5
+ * meow でコマンドライン引数を解析し、ファイル拡張子に応じたパーサーを
6
+ * レジストリから取得して、Ink の App コンポーネントに渡す。
7
+ *
8
+ * 使用例:
9
+ * gi world.geojson
10
+ * gi ports.geojson --no-map --props 30
11
+ * gi routes.geojson -w 100 -h 30
12
+ */
13
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env node
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ /**
4
+ * CLI エントリポイント。
5
+ *
6
+ * meow でコマンドライン引数を解析し、ファイル拡張子に応じたパーサーを
7
+ * レジストリから取得して、Ink の App コンポーネントに渡す。
8
+ *
9
+ * 使用例:
10
+ * gi world.geojson
11
+ * gi ports.geojson --no-map --props 30
12
+ * gi routes.geojson -w 100 -h 30
13
+ */
14
+ import meow from "meow";
15
+ import { render } from "ink";
16
+ import App from "./App.js";
17
+ import { getParser } from "./lib/registry.js";
18
+ const cli = meow(`
19
+ Usage
20
+ $ gp <file>
21
+
22
+ Options
23
+ --no-map Hide map preview
24
+ --width, -w Map width (chars) Default: terminal width - 4
25
+ --height, -h Map height (chars) Default: 24
26
+ --props, -p Property display limit Default: 15
27
+
28
+ Examples
29
+ $ gp world.geojson
30
+ $ gp ports.geojson --no-map --props 30
31
+ `, {
32
+ importMeta: import.meta,
33
+ flags: {
34
+ map: {
35
+ type: "boolean",
36
+ default: true,
37
+ },
38
+ width: {
39
+ type: "number",
40
+ shortFlag: "w",
41
+ default: (process.stdout.columns ?? 80) - 4,
42
+ },
43
+ height: {
44
+ type: "number",
45
+ shortFlag: "h",
46
+ default: 24,
47
+ },
48
+ props: {
49
+ type: "number",
50
+ shortFlag: "p",
51
+ default: 15,
52
+ },
53
+ },
54
+ });
55
+ const filePath = cli.input[0];
56
+ if (!filePath) {
57
+ cli.showHelp();
58
+ process.exit(1);
59
+ }
60
+ // 拡張子からパーサーを解決。未対応の形式の場合はエラーメッセージを表示して終了
61
+ let parser;
62
+ try {
63
+ parser = getParser(filePath);
64
+ }
65
+ catch (e) {
66
+ console.error(e.message);
67
+ process.exit(1);
68
+ }
69
+ const options = {
70
+ noMap: !cli.flags.map,
71
+ width: cli.flags.width,
72
+ height: cli.flags.height,
73
+ props: cli.flags.props,
74
+ };
75
+ render(_jsx(App, { filePath: filePath, parser: parser, options: options }), {
76
+ exitOnCtrlC: true,
77
+ });
@@ -0,0 +1,3 @@
1
+ import type { FC } from "react";
2
+ declare const Footer: FC;
3
+ export default Footer;
@@ -0,0 +1,4 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Text } from "ink";
3
+ const Footer = () => (_jsx(Text, { dimColor: true, children: "[q: exit]" }));
4
+ export default Footer;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * ジオメトリサマリー — ジオメトリ種別ごとの件数を表形式で表示する。
3
+ * カウントが 0 の種別は非表示にして、実際に含まれる種別のみ列挙する。
4
+ */
5
+ import type { FC } from "react";
6
+ import type { GeometryType } from "../lib/types.js";
7
+ interface Props {
8
+ counts: Record<GeometryType, number>;
9
+ }
10
+ declare const GeometrySummary: FC<Props>;
11
+ export default GeometrySummary;
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * ジオメトリサマリー — ジオメトリ種別ごとの件数を表形式で表示する。
4
+ * カウントが 0 の種別は非表示にして、実際に含まれる種別のみ列挙する。
5
+ */
6
+ import { Box, Text } from "ink";
7
+ const GeometrySummary = ({ counts }) => {
8
+ const entries = Object.entries(counts).filter(([, v]) => v > 0);
9
+ return (_jsxs(Box, { flexDirection: "column", marginRight: 4, children: [_jsx(Text, { bold: true, underline: true, children: "GEOMETRY SUMMARY" }), entries.length === 0 ? (_jsx(Text, { dimColor: true, children: "(none)" })) : (entries.map(([type, count]) => (_jsxs(Text, { children: [_jsx(Text, { color: "green", children: type.padEnd(20) }), _jsx(Text, { children: count.toLocaleString().padStart(8) })] }, type))))] }));
10
+ };
11
+ export default GeometrySummary;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * ヘッダー — ファイル名・Feature 数・ファイルサイズを表示する。
3
+ */
4
+ import type { FC } from "react";
5
+ import type { ParseResult } from "../lib/types.js";
6
+ interface Props {
7
+ result: ParseResult;
8
+ }
9
+ declare const Header: FC<Props>;
10
+ export default Header;
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * ヘッダー — ファイル名・Feature 数・ファイルサイズを表示する。
4
+ */
5
+ import { Box, Text } from "ink";
6
+ /** バイト数を B / KB / MB の読みやすい単位に変換する */
7
+ function formatBytes(bytes) {
8
+ if (bytes < 1024)
9
+ return `${bytes} B`;
10
+ if (bytes < 1024 * 1024)
11
+ return `${(bytes / 1024).toFixed(1)} KB`;
12
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
13
+ }
14
+ const Header = ({ result }) => {
15
+ const fileName = result.filePath.split("/").pop() ?? result.filePath;
16
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: fileName }), _jsxs(Text, { dimColor: true, children: ["FeatureCollection / ", result.featureCount.toLocaleString(), " features /", " ", formatBytes(result.fileSizeBytes)] })] }));
17
+ };
18
+ export default Header;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * マッププレビュー — drawMap() の出力をブライユ文字列として表示する。
3
+ *
4
+ * result / width / height が変わるたびに useEffect で再描画する。
5
+ * --no-map フラグが指定された場合、App.tsx 側でこのコンポーネント自体がマウントされない。
6
+ */
7
+ import { type FC } from "react";
8
+ import type { ParseResult } from "../lib/types.js";
9
+ interface Props {
10
+ result: ParseResult;
11
+ width: number;
12
+ height: number;
13
+ }
14
+ declare const MapPreview: FC<Props>;
15
+ export default MapPreview;
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * マッププレビュー — drawMap() の出力をブライユ文字列として表示する。
4
+ *
5
+ * result / width / height が変わるたびに useEffect で再描画する。
6
+ * --no-map フラグが指定された場合、App.tsx 側でこのコンポーネント自体がマウントされない。
7
+ */
8
+ import { Text } from "ink";
9
+ import { useEffect, useState } from "react";
10
+ import { drawMap } from "../lib/drawMap.js";
11
+ const MapPreview = ({ result, width, height }) => {
12
+ const [frame, setFrame] = useState("");
13
+ useEffect(() => {
14
+ const output = drawMap(result, width, height);
15
+ setFrame(output);
16
+ }, [result, width, height]);
17
+ return (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, underline: true, children: "MAP PREVIEW" }), _jsx(Text, { children: frame })] }));
18
+ };
19
+ export default MapPreview;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * プロパティスキーマ — 各プロパティの名前・型・充足率を表示する。
3
+ *
4
+ * 充足率(count / total)を ▓░ のバーで視覚化する(10段階)。
5
+ * --props オプションで表示件数を制限でき、超過分は "... and N more" と表示する。
6
+ * 型が複数混在する場合は "string|number" のようにパイプ区切りで表示する。
7
+ */
8
+ import type { FC } from "react";
9
+ import type { PropertyStat } from "../lib/types.js";
10
+ interface Props {
11
+ stats: PropertyStat[];
12
+ limit: number;
13
+ }
14
+ declare const PropertySchema: FC<Props>;
15
+ export default PropertySchema;
@@ -0,0 +1,25 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * プロパティスキーマ — 各プロパティの名前・型・充足率を表示する。
4
+ *
5
+ * 充足率(count / total)を ▓░ のバーで視覚化する(10段階)。
6
+ * --props オプションで表示件数を制限でき、超過分は "... and N more" と表示する。
7
+ * 型が複数混在する場合は "string|number" のようにパイプ区切りで表示する。
8
+ */
9
+ import { Box, Text } from "ink";
10
+ /** 充足率を ▓(充足)/ ░(未充足)のバー文字列に変換 */
11
+ function makeBar(ratio, width = 10) {
12
+ const filled = Math.round(ratio * width);
13
+ return "▓".repeat(filled) + "░".repeat(width - filled);
14
+ }
15
+ const PropertySchema = ({ stats, limit }) => {
16
+ const displayed = stats.slice(0, limit);
17
+ const remaining = stats.length - displayed.length;
18
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, underline: true, children: "PROPERTY SCHEMA" }), displayed.length === 0 ? (_jsx(Text, { dimColor: true, children: "(none)" })) : (displayed.map((s) => {
19
+ const types = [...s.types].join("|");
20
+ const ratio = s.total > 0 ? s.count / s.total : 0;
21
+ const bar = makeBar(ratio);
22
+ return (_jsxs(Text, { children: [_jsx(Text, { color: "yellow", children: s.key.padEnd(16) }), _jsx(Text, { children: types.padEnd(14) }), _jsxs(Text, { dimColor: true, children: [bar, " "] }), _jsxs(Text, { children: ["(", s.count, "/", s.total, ")"] })] }, s.key));
23
+ })), remaining > 0 && (_jsxs(Text, { dimColor: true, children: ["... and ", remaining, " more properties"] }))] }));
24
+ };
25
+ export default PropertySchema;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * ファイル読み込み・解析を行う汎用 React フック。
3
+ *
4
+ * パーサーを外部から注入する設計のため、ファイル形式に依存しない。
5
+ * コンポーネントのマウント時に parser.parse() を非同期で実行し、
6
+ * loading / result / error の3状態を返す。
7
+ *
8
+ * useEffect のクリーンアップで cancelled フラグを立てることで、
9
+ * アンマウント後の setState を防止している。
10
+ */
11
+ import type { FileParser, ParseResult } from "../lib/types.js";
12
+ interface UseFileParserResult {
13
+ result: ParseResult | null;
14
+ loading: boolean;
15
+ error: Error | null;
16
+ }
17
+ export declare function useFileParser(filePath: string, parser: FileParser): UseFileParserResult;
18
+ export {};
@@ -0,0 +1,37 @@
1
+ /**
2
+ * ファイル読み込み・解析を行う汎用 React フック。
3
+ *
4
+ * パーサーを外部から注入する設計のため、ファイル形式に依存しない。
5
+ * コンポーネントのマウント時に parser.parse() を非同期で実行し、
6
+ * loading / result / error の3状態を返す。
7
+ *
8
+ * useEffect のクリーンアップで cancelled フラグを立てることで、
9
+ * アンマウント後の setState を防止している。
10
+ */
11
+ import { useEffect, useState } from "react";
12
+ export function useFileParser(filePath, parser) {
13
+ const [result, setResult] = useState(null);
14
+ const [loading, setLoading] = useState(true);
15
+ const [error, setError] = useState(null);
16
+ useEffect(() => {
17
+ let cancelled = false;
18
+ parser
19
+ .parse(filePath)
20
+ .then((r) => {
21
+ if (!cancelled) {
22
+ setResult(r);
23
+ setLoading(false);
24
+ }
25
+ })
26
+ .catch((e) => {
27
+ if (!cancelled) {
28
+ setError(e instanceof Error ? e : new Error(String(e)));
29
+ setLoading(false);
30
+ }
31
+ });
32
+ return () => {
33
+ cancelled = true;
34
+ };
35
+ }, [filePath, parser]);
36
+ return { result, loading, error };
37
+ }
@@ -0,0 +1,8 @@
1
+ import type { ParseResult } from "../lib/types.js";
2
+ interface UseGeojsonResult {
3
+ result: ParseResult | null;
4
+ loading: boolean;
5
+ error: Error | null;
6
+ }
7
+ export declare function useGeojson(filePath: string): UseGeojsonResult;
8
+ export {};
@@ -0,0 +1,27 @@
1
+ import { useEffect, useState } from "react";
2
+ import { parseGeojson } from "../lib/parseGeojson.js";
3
+ export function useGeojson(filePath) {
4
+ const [result, setResult] = useState(null);
5
+ const [loading, setLoading] = useState(true);
6
+ const [error, setError] = useState(null);
7
+ useEffect(() => {
8
+ let cancelled = false;
9
+ parseGeojson(filePath)
10
+ .then((r) => {
11
+ if (!cancelled) {
12
+ setResult(r);
13
+ setLoading(false);
14
+ }
15
+ })
16
+ .catch((e) => {
17
+ if (!cancelled) {
18
+ setError(e instanceof Error ? e : new Error(String(e)));
19
+ setLoading(false);
20
+ }
21
+ });
22
+ return () => {
23
+ cancelled = true;
24
+ };
25
+ }, [filePath]);
26
+ return { result, loading, error };
27
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * drawille-canvas を使ってジオメトリをブライユ文字(Unicode Braille)で描画する。
3
+ *
4
+ * ブライユ文字は 1 文字あたり 2×4 のドットマトリクスを持つため、
5
+ * ターミナルの 1 文字が 2px(横)× 4px(縦)の解像度になる。
6
+ *
7
+ * 座標変換:
8
+ * 経度(lng) → px = (lng - minLng) / lngRange * (幅px - 1)
9
+ * 緯度(lat) → py = (1 - (lat - minLat) / latRange) * (高さpx - 1)
10
+ * ↑ Y軸反転(ターミナルは上から下、緯度は南から北)
11
+ *
12
+ * 描画方針: ストロークのみ(塗りつぶしなし)で輪郭を描く。
13
+ */
14
+ import type { ParseResult } from "./types.js";
15
+ /**
16
+ * ParseResult の features を drawille キャンバスに描画し、ブライユ文字列を返す。
17
+ *
18
+ * @param widthChars - マップの幅(ターミナル文字数)
19
+ * @param heightChars - マップの高さ(ターミナル文字数)
20
+ */
21
+ export declare function drawMap(result: ParseResult, widthChars: number, heightChars: number): string;
@@ -0,0 +1,113 @@
1
+ /**
2
+ * drawille-canvas を使ってジオメトリをブライユ文字(Unicode Braille)で描画する。
3
+ *
4
+ * ブライユ文字は 1 文字あたり 2×4 のドットマトリクスを持つため、
5
+ * ターミナルの 1 文字が 2px(横)× 4px(縦)の解像度になる。
6
+ *
7
+ * 座標変換:
8
+ * 経度(lng) → px = (lng - minLng) / lngRange * (幅px - 1)
9
+ * 緯度(lat) → py = (1 - (lat - minLat) / latRange) * (高さpx - 1)
10
+ * ↑ Y軸反転(ターミナルは上から下、緯度は南から北)
11
+ *
12
+ * 描画方針: ストロークのみ(塗りつぶしなし)で輪郭を描く。
13
+ */
14
+ // @ts-expect-error drawille-canvas has no type declarations
15
+ import DrawilleCanvas from "drawille-canvas";
16
+ /**
17
+ * ParseResult の features を drawille キャンバスに描画し、ブライユ文字列を返す。
18
+ *
19
+ * @param widthChars - マップの幅(ターミナル文字数)
20
+ * @param heightChars - マップの高さ(ターミナル文字数)
21
+ */
22
+ export function drawMap(result, widthChars, heightChars) {
23
+ // ブライユ1文字 = 横2ドット × 縦4ドット
24
+ const pxW = widthChars * 2;
25
+ const pxH = heightChars * 4;
26
+ const ctx = new DrawilleCanvas(pxW, pxH);
27
+ const [minLng, minLat, maxLng, maxLat] = result.bbox;
28
+ const lngRange = maxLng - minLng;
29
+ const latRange = maxLat - minLat;
30
+ /** 経度をピクセルX座標に変換 */
31
+ function projectLng(lng) {
32
+ return ((lng - minLng) / lngRange) * (pxW - 1);
33
+ }
34
+ /** 緯度をピクセルY座標に変換(Y軸反転) */
35
+ function projectLat(lat) {
36
+ return (1 - (lat - minLat) / latRange) * (pxH - 1);
37
+ }
38
+ /** Point 用: 1ピクセルの点を打つ */
39
+ function drawCoord(coord) {
40
+ const px = Math.round(projectLng(coord[0]));
41
+ const py = Math.round(projectLat(coord[1]));
42
+ ctx.fillRect(px, py, 1, 1);
43
+ }
44
+ /** LineString 用: 頂点間を線分で結ぶ */
45
+ function drawLineString(coords) {
46
+ if (coords.length === 0)
47
+ return;
48
+ ctx.beginPath();
49
+ const x0 = Math.round(projectLng(coords[0][0]));
50
+ const y0 = Math.round(projectLat(coords[0][1]));
51
+ ctx.moveTo(x0, y0);
52
+ for (let i = 1; i < coords.length; i++) {
53
+ const x = Math.round(projectLng(coords[i][0]));
54
+ const y = Math.round(projectLat(coords[i][1]));
55
+ ctx.lineTo(x, y);
56
+ }
57
+ ctx.stroke();
58
+ }
59
+ /** Polygon 用: 各リング(外周・穴)の輪郭を描画(塗りつぶしなし) */
60
+ function drawPolygon(rings) {
61
+ for (const ring of rings) {
62
+ if (ring.length === 0)
63
+ continue;
64
+ ctx.beginPath();
65
+ const x0 = Math.round(projectLng(ring[0][0]));
66
+ const y0 = Math.round(projectLat(ring[0][1]));
67
+ ctx.moveTo(x0, y0);
68
+ for (let i = 1; i < ring.length; i++) {
69
+ const x = Math.round(projectLng(ring[i][0]));
70
+ const y = Math.round(projectLat(ring[i][1]));
71
+ ctx.lineTo(x, y);
72
+ }
73
+ ctx.closePath();
74
+ ctx.stroke();
75
+ }
76
+ }
77
+ /** ジオメトリ種別に応じて描画関数を振り分ける(GeometryCollection は再帰) */
78
+ function drawGeometry(geometry) {
79
+ switch (geometry.type) {
80
+ case "Point":
81
+ drawCoord(geometry.coordinates);
82
+ break;
83
+ case "MultiPoint":
84
+ for (const coord of geometry.coordinates)
85
+ drawCoord(coord);
86
+ break;
87
+ case "LineString":
88
+ drawLineString(geometry.coordinates);
89
+ break;
90
+ case "MultiLineString":
91
+ for (const line of geometry.coordinates)
92
+ drawLineString(line);
93
+ break;
94
+ case "Polygon":
95
+ drawPolygon(geometry.coordinates);
96
+ break;
97
+ case "MultiPolygon":
98
+ for (const polygon of geometry.coordinates)
99
+ drawPolygon(polygon);
100
+ break;
101
+ case "GeometryCollection":
102
+ for (const g of geometry.geometries)
103
+ drawGeometry(g);
104
+ break;
105
+ }
106
+ }
107
+ for (const feature of result.features) {
108
+ if (feature.geometry) {
109
+ drawGeometry(feature.geometry);
110
+ }
111
+ }
112
+ return ctx.toString();
113
+ }
@@ -0,0 +1,2 @@
1
+ import type { ParseResult } from "./types.js";
2
+ export declare function parseGeojson(filePath: string): Promise<ParseResult>;