@@ -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 ) {
0 commit comments