Skip to content

Commit 9609a49

Browse files
committed
melhora fechamento do wal
1 parent 123b4a5 commit 9609a49

File tree

8 files changed

+362
-284
lines changed

8 files changed

+362
-284
lines changed

.claude/settings.local.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
"Bash(docker-compose exec:*)",
2323
"Bash(git stash:*)",
2424
"Bash(node --test:*)",
25-
"Bash(npm run knip:*)"
25+
"Bash(npm run knip:*)",
26+
"Bash(findstr:*)",
27+
"Bash(node scripts/cleanup-wal.js:*)"
2628
]
2729
}
2830
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"estimate-slope-roll": "node scripts/estimate-slope-roll.js",
1313
"generate-pmtiles": "node scripts/generate-pmtiles.js",
1414
"verify": "node scripts/verify.js",
15+
"cleanup-wal": "node scripts/cleanup-wal.js",
1516
"test": "node --test tests/**/*.test.js",
1617
"knip": "knip",
1718
"lint": "eslint . --max-warnings 0",

scripts/cleanup-wal.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* @module scripts/cleanup-wal
5+
* @description Checkpoints and closes all SQLite databases, removing -wal and -shm files.
6+
* Usage: node scripts/cleanup-wal.js [--data-dir <path>]
7+
*/
8+
9+
import Database from 'better-sqlite3';
10+
import { readdirSync, existsSync } from 'node:fs';
11+
import { join, resolve } from 'node:path';
12+
import { parseArgs } from 'node:util';
13+
14+
const { values } = parseArgs({
15+
options: {
16+
'data-dir': { type: 'string', default: '' },
17+
},
18+
});
19+
20+
const dataDir = values['data-dir']
21+
? resolve(values['data-dir'])
22+
: resolve(process.cwd(), 'data');
23+
24+
function checkpointDb(dbPath) {
25+
const label = dbPath.replace(dataDir, '.');
26+
try {
27+
const db = new Database(dbPath);
28+
const result = db.pragma('wal_checkpoint(TRUNCATE)');
29+
db.close();
30+
console.log(` OK ${label} (checkpoint: ${JSON.stringify(result[0])})`);
31+
} catch (err) {
32+
console.error(` FAIL ${label} ${err.message}`);
33+
}
34+
}
35+
36+
console.log(`Data dir: ${dataDir}\n`);
37+
38+
// index.db
39+
const indexPath = join(dataDir, 'index.db');
40+
if (existsSync(indexPath)) {
41+
console.log('index.db:');
42+
checkpointDb(indexPath);
43+
}
44+
45+
// project databases
46+
const projectsDir = join(dataDir, 'projects');
47+
if (existsSync(projectsDir)) {
48+
const files = readdirSync(projectsDir).filter(f => f.endsWith('.db'));
49+
console.log(`\nProject databases (${files.length}):`);
50+
for (const file of files) {
51+
checkpointDb(join(projectsDir, file));
52+
}
53+
}
54+
55+
console.log('\nDone. WAL/SHM files should now be removed.');

scripts/estimate-slope-roll.js

Lines changed: 133 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -93,157 +93,159 @@ function main() {
9393
const db = new Database(indexDbPath);
9494
db.pragma('journal_mode = WAL');
9595

96-
// Get projects to process
97-
let projects;
98-
if (opts.project) {
99-
projects = db.prepare('SELECT id, slug, name, photo_count FROM projects WHERE slug = ?').all(opts.project);
100-
if (projects.length === 0) {
101-
console.error(`Project not found: ${opts.project}`);
102-
process.exit(1);
96+
try {
97+
// Get projects to process
98+
let projects;
99+
if (opts.project) {
100+
projects = db.prepare('SELECT id, slug, name, photo_count FROM projects WHERE slug = ?').all(opts.project);
101+
if (projects.length === 0) {
102+
console.error(`Project not found: ${opts.project}`);
103+
process.exit(1);
104+
}
105+
} else {
106+
projects = db.prepare('SELECT id, slug, name, photo_count FROM projects ORDER BY name').all();
103107
}
104-
} else {
105-
projects = db.prepare('SELECT id, slug, name, photo_count FROM projects ORDER BY name').all();
106-
}
107108

108-
// Query: for each photo, get its elevation and the next target's elevation + distance
109-
const getPhotosWithNext = db.prepare(`
110-
SELECT
111-
p.id,
112-
p.display_name,
113-
p.ele AS cam_ele,
114-
p.mesh_rotation_z AS current_z,
115-
t_next.ele AS next_ele,
116-
tgt.distance_m AS next_dist
117-
FROM photos p
118-
LEFT JOIN targets tgt ON tgt.source_id = p.id AND tgt.is_next = 1
119-
LEFT JOIN photos t_next ON t_next.id = tgt.target_id
120-
WHERE p.project_id = ?
121-
ORDER BY p.sequence_number
122-
`);
123-
124-
const updateRotZ = db.prepare('UPDATE photos SET mesh_rotation_z = ? WHERE id = ?');
125-
126-
console.log('Project | Photos | With Ele | Updated | Avg Slope | Max Slope');
127-
console.log('--------------------------|--------|----------|---------|-----------|----------');
128-
129-
let totalPhotos = 0;
130-
let totalWithEle = 0;
131-
let totalUpdated = 0;
132-
133-
for (const project of projects) {
134-
const rows = getPhotosWithNext.all(project.id);
135-
let withEle = 0;
136-
let updated = 0;
137-
let sumAbsSlope = 0;
138-
let maxAbsSlope = 0;
139-
140-
const updates = []; // {id, newZ, displayName, slopeDeg}
141-
142-
for (const row of rows) {
143-
if (opts.clear) {
144-
// Clear mode: reset to 0 if currently non-zero
145-
if (row.current_z !== 0) {
146-
updates.push({ id: row.id, newZ: 0, displayName: row.display_name, slopeDeg: 0 });
147-
updated++;
109+
// Query: for each photo, get its elevation and the next target's elevation + distance
110+
const getPhotosWithNext = db.prepare(`
111+
SELECT
112+
p.id,
113+
p.display_name,
114+
p.ele AS cam_ele,
115+
p.mesh_rotation_z AS current_z,
116+
t_next.ele AS next_ele,
117+
tgt.distance_m AS next_dist
118+
FROM photos p
119+
LEFT JOIN targets tgt ON tgt.source_id = p.id AND tgt.is_next = 1
120+
LEFT JOIN photos t_next ON t_next.id = tgt.target_id
121+
WHERE p.project_id = ?
122+
ORDER BY p.sequence_number
123+
`);
124+
125+
const updateRotZ = db.prepare('UPDATE photos SET mesh_rotation_z = ? WHERE id = ?');
126+
127+
console.log('Project | Photos | With Ele | Updated | Avg Slope | Max Slope');
128+
console.log('--------------------------|--------|----------|---------|-----------|----------');
129+
130+
let totalPhotos = 0;
131+
let totalWithEle = 0;
132+
let totalUpdated = 0;
133+
134+
for (const project of projects) {
135+
const rows = getPhotosWithNext.all(project.id);
136+
let withEle = 0;
137+
let updated = 0;
138+
let sumAbsSlope = 0;
139+
let maxAbsSlope = 0;
140+
141+
const updates = []; // {id, newZ, displayName, slopeDeg}
142+
143+
for (const row of rows) {
144+
if (opts.clear) {
145+
// Clear mode: reset to 0 if currently non-zero
146+
if (row.current_z !== 0) {
147+
updates.push({ id: row.id, newZ: 0, displayName: row.display_name, slopeDeg: 0 });
148+
updated++;
149+
}
150+
continue;
148151
}
149-
continue;
150-
}
151152

152-
// Skip if missing elevation data on either end
153-
if (row.cam_ele == null || row.next_ele == null || row.next_dist == null) {
154-
continue;
155-
}
153+
// Skip if missing elevation data on either end
154+
if (row.cam_ele == null || row.next_ele == null || row.next_dist == null) {
155+
continue;
156+
}
156157

157-
// Skip if distance is too small (same spot, unreliable angle)
158-
if (row.next_dist < 1) {
159-
continue;
160-
}
158+
// Skip if distance is too small (same spot, unreliable angle)
159+
if (row.next_dist < 1) {
160+
continue;
161+
}
161162

162-
withEle++;
163+
withEle++;
163164

164-
// Compute slope angle: positive = uphill (next is higher)
165-
const deltaEle = row.next_ele - row.cam_ele;
166-
const slopeRad = Math.atan2(deltaEle, row.next_dist);
167-
const slopeDeg = slopeRad * RAD_TO_DEG;
165+
// Compute slope angle: positive = uphill (next is higher)
166+
const deltaEle = row.next_ele - row.cam_ele;
167+
const slopeRad = Math.atan2(deltaEle, row.next_dist);
168+
const slopeDeg = slopeRad * RAD_TO_DEG;
168169

169-
// Discard outliers: slopes beyond maxAngle are GPS noise, not real inclines.
170-
// Leave mesh_rotation_z at 0 for these photos.
171-
if (Math.abs(slopeDeg) > opts.maxAngle) {
172-
continue;
173-
}
170+
// Discard outliers: slopes beyond maxAngle are GPS noise, not real inclines.
171+
// Leave mesh_rotation_z at 0 for these photos.
172+
if (Math.abs(slopeDeg) > opts.maxAngle) {
173+
continue;
174+
}
175+
176+
// Round to 1 decimal for clean values
177+
const newZ = Math.round(slopeDeg * 10) / 10;
174178

175-
// Round to 1 decimal for clean values
176-
const newZ = Math.round(slopeDeg * 10) / 10;
179+
sumAbsSlope += Math.abs(slopeDeg);
180+
maxAbsSlope = Math.max(maxAbsSlope, Math.abs(slopeDeg));
177181

178-
sumAbsSlope += Math.abs(slopeDeg);
179-
maxAbsSlope = Math.max(maxAbsSlope, Math.abs(slopeDeg));
182+
// Only update if the value is changing
183+
const currentRounded = Math.round(row.current_z * 10) / 10;
184+
if (newZ !== currentRounded) {
185+
updates.push({ id: row.id, newZ, displayName: row.display_name, slopeDeg });
186+
updated++;
187+
}
188+
}
180189

181-
// Only update if the value is changing
182-
const currentRounded = Math.round(row.current_z * 10) / 10;
183-
if (newZ !== currentRounded) {
184-
updates.push({ id: row.id, newZ, displayName: row.display_name, slopeDeg });
185-
updated++;
190+
// Apply updates
191+
if (!opts.dryRun && updates.length > 0) {
192+
db.transaction(() => {
193+
for (const u of updates) {
194+
updateRotZ.run(u.newZ, u.id);
195+
}
196+
})();
186197
}
187-
}
188198

189-
// Apply updates
190-
if (!opts.dryRun && updates.length > 0) {
191-
db.transaction(() => {
192-
for (const u of updates) {
193-
updateRotZ.run(u.newZ, u.id);
199+
totalPhotos += rows.length;
200+
totalWithEle += withEle;
201+
totalUpdated += updated;
202+
203+
const avgSlope = withEle > 0 ? (sumAbsSlope / withEle).toFixed(1) : 'N/A';
204+
const maxSlope = withEle > 0 ? maxAbsSlope.toFixed(1) : 'N/A';
205+
206+
console.log(
207+
`${project.slug.padEnd(25)} | ` +
208+
`${String(rows.length).padStart(6)} | ` +
209+
`${String(withEle).padStart(8)} | ` +
210+
`${String(updated).padStart(7)} | ` +
211+
`${String(avgSlope + '°').padStart(9)} | ` +
212+
`${String(maxSlope + '°').padStart(9)}`
213+
);
214+
215+
// In dry-run mode with a single project, show per-photo details
216+
if (opts.dryRun && opts.project && updates.length > 0) {
217+
console.log('');
218+
console.log(' Detail (changes only):');
219+
console.log(' Photo | Current Z | New Z | Slope');
220+
console.log(' -------------------------------|-----------|---------|------');
221+
for (const u of updates.slice(0, 50)) {
222+
console.log(
223+
` ${u.displayName.padEnd(30)} | ` +
224+
`${String(u.slopeDeg.toFixed(1) + '°').padStart(9)} | ` +
225+
`${String(u.newZ.toFixed(1) + '°').padStart(7)} | ` +
226+
`${String(u.slopeDeg.toFixed(2) + '°').padStart(7)}`
227+
);
194228
}
195-
})();
229+
if (updates.length > 50) {
230+
console.log(` ... and ${updates.length - 50} more`);
231+
}
232+
console.log('');
233+
}
196234
}
197235

198-
totalPhotos += rows.length;
199-
totalWithEle += withEle;
200-
totalUpdated += updated;
201-
202-
const avgSlope = withEle > 0 ? (sumAbsSlope / withEle).toFixed(1) : 'N/A';
203-
const maxSlope = withEle > 0 ? maxAbsSlope.toFixed(1) : 'N/A';
204-
236+
console.log('--------------------------|--------|----------|---------|-----------|----------');
205237
console.log(
206-
`${project.slug.padEnd(25)} | ` +
207-
`${String(rows.length).padStart(6)} | ` +
208-
`${String(withEle).padStart(8)} | ` +
209-
`${String(updated).padStart(7)} | ` +
210-
`${String(avgSlope + '°').padStart(9)} | ` +
211-
`${String(maxSlope + '°').padStart(9)}`
238+
`${'TOTAL'.padEnd(25)} | ` +
239+
`${String(totalPhotos).padStart(6)} | ` +
240+
`${String(totalWithEle).padStart(8)} | ` +
241+
`${String(totalUpdated).padStart(7)} | ` +
242+
`${''.padStart(9)} | ` +
243+
`${''.padStart(9)}`
212244
);
213-
214-
// In dry-run mode with a single project, show per-photo details
215-
if (opts.dryRun && opts.project && updates.length > 0) {
216-
console.log('');
217-
console.log(' Detail (changes only):');
218-
console.log(' Photo | Current Z | New Z | Slope');
219-
console.log(' -------------------------------|-----------|---------|------');
220-
for (const u of updates.slice(0, 50)) {
221-
console.log(
222-
` ${u.displayName.padEnd(30)} | ` +
223-
`${String(u.slopeDeg.toFixed(1) + '°').padStart(9)} | ` +
224-
`${String(u.newZ.toFixed(1) + '°').padStart(7)} | ` +
225-
`${String(u.slopeDeg.toFixed(2) + '°').padStart(7)}`
226-
);
227-
}
228-
if (updates.length > 50) {
229-
console.log(` ... and ${updates.length - 50} more`);
230-
}
231-
console.log('');
232-
}
245+
} finally {
246+
db.close();
233247
}
234248

235-
console.log('--------------------------|--------|----------|---------|-----------|----------');
236-
console.log(
237-
`${'TOTAL'.padEnd(25)} | ` +
238-
`${String(totalPhotos).padStart(6)} | ` +
239-
`${String(totalWithEle).padStart(8)} | ` +
240-
`${String(totalUpdated).padStart(7)} | ` +
241-
`${''.padStart(9)} | ` +
242-
`${''.padStart(9)}`
243-
);
244-
245-
db.close();
246-
247249
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
248250
console.log(`\n=== ${opts.dryRun ? 'Dry run' : opts.clear ? 'Clear' : 'Estimation'} complete in ${elapsed}s ===`);
249251
if (opts.dryRun) {

scripts/generate-pmtiles.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ try {
183183
console.log(` fotos.pmtiles generated at ${pointsOutput}`);
184184
} catch (error) {
185185
console.error('Error running tippecanoe for points:', error.message);
186+
db.close();
186187
process.exit(1);
187188
}
188189

0 commit comments

Comments
 (0)