tracelines 0.2.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.
Files changed (3) hide show
  1. package/README.md +46 -0
  2. package/bin/cli.js +139 -0
  3. package/package.json +38 -0
package/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # tracelines (CLI)
2
+
3
+ A tiny, **zero-dependency** command-line client for
4
+ [tracelines](https://github.com/Prekzursil/tracelines) — find the nearest **official** Street View
5
+ car coverage (never a photosphere), list historical captures, and extract coverage to GeoJSON, all
6
+ through the hosted proxy. No Python needed.
7
+
8
+ ```bash
9
+ npx tracelines nearest 44.435072 26.050430
10
+ # → Ui8V1HlfwJBw8pnmoShJfw launch 2024-07 65.1m (third-party: false)
11
+ # https://www.google.com/maps/@?api=1&map_action=pano&pano=Ui8V1HlfwJBw8pnmoShJfw
12
+ ```
13
+
14
+ ## Commands
15
+
16
+ ```
17
+ tracelines nearest <lat> <lon> [--trekker]
18
+ tracelines historical <lat> <lon>
19
+ tracelines extract (--bbox W,S,E,N | --area NAME) [--sources google,mapillary] [--precision] [--out file.geojson]
20
+ tracelines probe --bbox W,S,E,N [--sources google]
21
+ tracelines health
22
+ ```
23
+
24
+ Options: `--proxy <url>` (or env `TRACELINES_PROXY`; defaults to the hosted demo) · `--json` ·
25
+ `-h/--help` · `-v/--version`. Requires **Node ≥ 18** (uses the built-in `fetch`).
26
+
27
+ ## The default proxy is a shared demo
28
+
29
+ The default endpoint is the hosted demo proxy — **hardened and rate-limited**, it may sleep or cap
30
+ the bbox size, and it extracts Google from a shared IP (a ToS gray area). For real work, run your
31
+ own and point at it:
32
+
33
+ ```bash
34
+ TRACELINES_PROXY=https://your-proxy npx tracelines extract --bbox 26.09,44.45,26.11,44.46 --out out.geojson
35
+ ```
36
+
37
+ See **[Deploy your own proxy (free)](https://github.com/Prekzursil/tracelines#deploy-your-own-proxy-free)**.
38
+
39
+ The **HARD RULE** holds everywhere: output is only genuine official car coverage — never a
40
+ photosphere/photopet. Prefer pure, reproducible extraction? Use the
41
+ [Python package](https://pypi.org/project/tracelines/) (`pip install tracelines`), which runs the
42
+ engine locally instead of via a proxy.
43
+
44
+ ## License
45
+
46
+ AGPL-3.0-or-later.
package/bin/cli.js ADDED
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env node
2
+ // tracelines — a tiny CLI over the hosted tracelines proxy.
3
+ // Nearest OFFICIAL Street View car coverage (never a photosphere), historical stacks,
4
+ // area extraction to GeoJSON, and density probes. Zero dependencies (Node >=18 global fetch).
5
+ import { writeFileSync } from "node:fs";
6
+
7
+ const VERSION = "0.2.0";
8
+ const DEFAULT_PROXY =
9
+ process.env.TRACELINES_PROXY || "https://tracelines-proxy-ttfjfqlcwq-ew.a.run.app";
10
+
11
+ const HELP = `tracelines ${VERSION} — nearest OFFICIAL Street View coverage (never photospheres)
12
+
13
+ Usage:
14
+ tracelines nearest <lat> <lon> [--trekker]
15
+ tracelines historical <lat> <lon>
16
+ tracelines extract (--bbox W,S,E,N | --area NAME) [--sources google,mapillary] [--precision] [--out file.geojson]
17
+ tracelines probe --bbox W,S,E,N [--sources google]
18
+ tracelines health
19
+
20
+ Options:
21
+ --proxy <url> Proxy base URL (env TRACELINES_PROXY; default: the hosted demo)
22
+ --json Print raw JSON
23
+ -h, --help Show this help
24
+ -v, --version Show version
25
+
26
+ The default hosted proxy is hardened & rate-limited and may sleep or cap bbox size.
27
+ Run your own for real work: https://github.com/Prekzursil/tracelines#deploy-your-own-proxy-free`;
28
+
29
+ const BOOL_FLAGS = new Set(["trekker", "precision", "json", "help", "version"]);
30
+
31
+ function parseArgs(argv) {
32
+ const flags = {};
33
+ const pos = [];
34
+ for (let i = 0; i < argv.length; i++) {
35
+ const a = argv[i];
36
+ if (a === "-h") flags.help = true;
37
+ else if (a === "-v") flags.version = true;
38
+ else if (a.startsWith("--")) {
39
+ const key = a.slice(2);
40
+ if (BOOL_FLAGS.has(key)) flags[key] = true;
41
+ else flags[key] = argv[++i];
42
+ } else pos.push(a);
43
+ }
44
+ return { flags, pos };
45
+ }
46
+
47
+ function die(msg, code = 1) {
48
+ console.error(`tracelines: ${msg}`);
49
+ process.exit(code);
50
+ }
51
+
52
+ function num(x, name) {
53
+ const n = Number(x);
54
+ if (x === undefined || !Number.isFinite(n)) die(`${name} must be a number (got ${JSON.stringify(x)})`);
55
+ return n;
56
+ }
57
+
58
+ async function req(base, path, { method = "GET", body } = {}) {
59
+ const url = base.replace(/\/$/, "") + path;
60
+ let res;
61
+ try {
62
+ res = await fetch(url, {
63
+ method,
64
+ headers: body ? { "content-type": "application/json" } : undefined,
65
+ body: body ? JSON.stringify(body) : undefined,
66
+ });
67
+ } catch (e) {
68
+ return die(`could not reach the proxy at ${base} (${e.message}). Is it running / awake?`);
69
+ }
70
+ const text = await res.text();
71
+ let data;
72
+ try {
73
+ data = JSON.parse(text);
74
+ } catch {
75
+ data = text;
76
+ }
77
+ if (!res.ok) {
78
+ const detail = data && data.detail ? data.detail : String(text).slice(0, 200);
79
+ return die(`proxy ${res.status}: ${detail}`);
80
+ }
81
+ return data;
82
+ }
83
+
84
+ async function main() {
85
+ const { flags, pos } = parseArgs(process.argv.slice(2));
86
+ if (flags.version) return console.log(VERSION);
87
+ const cmd = pos[0];
88
+ if (!cmd || flags.help) return console.log(HELP);
89
+ const base = flags.proxy || DEFAULT_PROXY;
90
+ const raw = Boolean(flags.json);
91
+
92
+ if (cmd === "nearest") {
93
+ const lat = num(pos[1], "lat");
94
+ const lon = num(pos[2], "lon");
95
+ const q = new URLSearchParams({ lat, lon, include_trekker: Boolean(flags.trekker) });
96
+ const d = await req(base, `/nearest?${q}`);
97
+ if (raw) return console.log(JSON.stringify(d, null, 2));
98
+ if (!d.id) return console.log("No official coverage nearby.");
99
+ console.log(`${d.id} ${d.sv_source} ${d.date || "?"} ${d.distance_m}m (third-party: ${d.is_third_party})`);
100
+ console.log(d.streetview_url);
101
+ } else if (cmd === "historical") {
102
+ const lat = num(pos[1], "lat");
103
+ const lon = num(pos[2], "lon");
104
+ const d = await req(base, `/historical?lat=${lat}&lon=${lon}`);
105
+ if (raw) return console.log(JSON.stringify(d, null, 2));
106
+ console.log(`${d.count} capture(s):`);
107
+ for (const p of d.panos) console.log(` ${p.date || "?"} ${p.sv_source || ""} ${p.id}`);
108
+ } else if (cmd === "extract") {
109
+ if (!flags.bbox && !flags.area) die("extract needs --bbox W,S,E,N or --area NAME");
110
+ const body = {
111
+ bbox: flags.bbox || null,
112
+ area: flags.area || null,
113
+ sources: (flags.sources || "google").split(",").map((s) => s.trim()).filter(Boolean),
114
+ precision: Boolean(flags.precision),
115
+ include_trekker: Boolean(flags.trekker),
116
+ };
117
+ if (flags["min-run"]) body.min_run = num(flags["min-run"], "--min-run");
118
+ const fc = await req(base, "/extract", { method: "POST", body });
119
+ if (flags.out) {
120
+ writeFileSync(flags.out, JSON.stringify(fc));
121
+ const km = fc.properties?.stats?.total_km;
122
+ console.log(`Wrote ${fc.features?.length ?? 0} features to ${flags.out}${km ? ` (${km} km)` : ""}`);
123
+ } else {
124
+ console.log(JSON.stringify(fc, null, raw ? 2 : 0));
125
+ }
126
+ } else if (cmd === "probe") {
127
+ if (!flags.bbox) die("probe needs --bbox W,S,E,N");
128
+ const q = new URLSearchParams({ bbox: flags.bbox, sources: flags.sources || "google" });
129
+ const d = await req(base, `/probe?${q}`);
130
+ console.log(JSON.stringify(d, null, 2));
131
+ } else if (cmd === "health") {
132
+ const d = await req(base, "/health");
133
+ console.log(JSON.stringify(d, null, 2));
134
+ } else {
135
+ die(`unknown command ${JSON.stringify(cmd)}. Run: tracelines --help`);
136
+ }
137
+ }
138
+
139
+ main();
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "tracelines",
3
+ "version": "0.2.0",
4
+ "description": "CLI for tracelines — nearest OFFICIAL Street View car coverage (never photospheres), historical stacks, and GeoJSON extraction via the hosted proxy.",
5
+ "type": "module",
6
+ "bin": {
7
+ "tracelines": "bin/cli.js"
8
+ },
9
+ "engines": {
10
+ "node": ">=18"
11
+ },
12
+ "files": [
13
+ "bin",
14
+ "README.md"
15
+ ],
16
+ "keywords": [
17
+ "street-view",
18
+ "streetlevel",
19
+ "mapillary",
20
+ "kartaview",
21
+ "coverage",
22
+ "geojson",
23
+ "gis",
24
+ "panorama",
25
+ "cli"
26
+ ],
27
+ "homepage": "https://prekzursil.github.io/tracelines/",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/Prekzursil/tracelines.git",
31
+ "directory": "npm"
32
+ },
33
+ "bugs": {
34
+ "url": "https://github.com/Prekzursil/tracelines/issues"
35
+ },
36
+ "license": "AGPL-3.0-or-later",
37
+ "author": "Prekzursil"
38
+ }