-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrun_pipeline.js
More file actions
158 lines (146 loc) · 5.43 KB
/
run_pipeline.js
File metadata and controls
158 lines (146 loc) · 5.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#!/usr/bin/env node
/*
* run_pipeline.js
*
* This script provides a simple Node‑only entry point for the Voxel Earth
* pipeline. It replaces the original Lua integration shipped with
* the luanti‑earth mod by directly invoking the existing JavaScript
* modules (tile_downloader.js, voxelize_tiles.js, etc.) from Node.
*
* Usage:
* node run_pipeline.js --key <GOOGLE_API_KEY> --location "1600 Amphitheatre Parkway, Mountain View, CA" --radius 50 --out ./output [--resolution 200]
* node run_pipeline.js --key <GOOGLE_API_KEY> --lat 37.4219999 --lng -122.0840575 --radius 50 --out ./output [--resolution 200]
*
* Required arguments:
* --key Google API key for both geocoding and 3D tile requests
* --radius Radius in meters around the location to download 3D tiles
* --out Output directory for downloaded GLBs and voxelized JSON files
*
* Optional arguments:
* --location A free‑form address to geocode. If provided, latitude and
* longitude will be looked up automatically. If omitted,
* --lat and --lng must be provided instead.
* --lat Latitude coordinate of the centre point (if not using --location)
* --lng Longitude coordinate of the centre point (if not using --location)
* --resolution Voxel resolution per dimension (defaults to 200). Higher
* values produce denser voxel grids but increase memory usage.
*
* Example:
* node run_pipeline.js --key ABC123 --location "Eiffel Tower" --radius 50 --out ./out
*/
import { spawn } from 'child_process';
import fs from 'fs';
import path from 'path';
// Parse command line arguments using a minimal parser. We avoid the
// yargs dependency here to keep the script self‑contained. Arguments
// are provided in the form --name value or --flag. Unknown flags are
// ignored.
function parseArgs(argv) {
const args = {};
for (let i = 0; i < argv.length; i++) {
const arg = argv[i];
if (arg.startsWith('--')) {
const key = arg.slice(2);
const next = argv[i + 1];
if (!next || next.startsWith('--')) {
args[key] = true;
} else {
args[key] = next;
i++;
}
}
}
return args;
}
async function geocode(address, key) {
const encoded = encodeURIComponent(address);
const url = `https://maps.googleapis.com/maps/api/geocode/json?address=${encoded}&key=${key}`;
const res = await fetch(url);
if (!res.ok) {
throw new Error(`Geocoding request failed with HTTP ${res.status}`);
}
const data = await res.json();
if (data.status !== 'OK' || !data.results || !data.results.length) {
throw new Error(`No geocoding results for address: ${address}`);
}
const { lat, lng } = data.results[0].geometry.location;
return { lat, lng };
}
async function run() {
const argv = process.argv.slice(2);
const args = parseArgs(argv);
const key = args.key;
if (!key) {
console.error('Missing required argument: --key');
process.exit(1);
}
const radius = args.radius ? Number(args.radius) : null;
if (!radius || isNaN(radius)) {
console.error('Missing or invalid --radius (must be a number in metres)');
process.exit(1);
}
const outDir = args.out || args.o;
if (!outDir) {
console.error('Missing required argument: --out');
process.exit(1);
}
const resolution = args.resolution ? Number(args.resolution) : 200;
let lat, lng;
if (args.location) {
console.log(`Geocoding location: ${args.location}`);
try {
const coords = await geocode(args.location, key);
lat = coords.lat;
lng = coords.lng;
} catch (err) {
console.error(err.message);
process.exit(1);
}
} else if (args.lat && args.lng) {
lat = Number(args.lat);
lng = Number(args.lng);
if (isNaN(lat) || isNaN(lng)) {
console.error('Invalid latitude or longitude provided');
process.exit(1);
}
} else {
console.error('Either --location or both --lat and --lng must be provided');
process.exit(1);
}
// Create output directories for GLBs and voxels.
const tilesDir = path.join(outDir, 'tiles');
const voxelsDir = path.join(outDir, 'voxels');
fs.mkdirSync(tilesDir, { recursive: true });
fs.mkdirSync(voxelsDir, { recursive: true });
// Helper to spawn a Node child process and stream output to stdout.
function spawnProcess(file, argsArr) {
return new Promise((resolve, reject) => {
const proc = spawn('node', [file, ...argsArr], { cwd: path.dirname(file), stdio: 'inherit' });
proc.on('error', (err) => reject(err));
proc.on('exit', (code) => {
if (code !== 0) {
reject(new Error(`${file} exited with code ${code}`));
} else {
resolve();
}
});
});
}
// Download and rotate 3D tiles.
const tileScript = path.resolve('tile_downloader.js');
const tileArgs = ['--key', key, '--lat', String(lat), '--lng', String(lng), '--radius', String(radius), '--out', tilesDir, '--parallel', '4'];
console.log('Downloading Google Earth tiles...');
await spawnProcess(tileScript, tileArgs);
console.log('Tile download complete');
// Voxelize the downloaded GLB tiles.
const voxelScript = path.resolve('voxelize_tiles.js');
const voxelArgs = [tilesDir, voxelsDir, String(resolution)];
console.log('Voxelizing tiles...');
await spawnProcess(voxelScript, voxelArgs);
console.log('Voxelization complete');
console.log('Pipeline finished successfully');
}
run().catch((err) => {
console.error(err);
process.exit(1);
});