@@ -78,6 +78,28 @@ def status
7878
7979 private
8080
81+ def save_last_run_timestamp ( db )
82+ db [ :metadata ] . insert_conflict (
83+ target : :name ,
84+ update : { value : Sequel [ :excluded ] [ :value ] }
85+ ) . insert (
86+ name : 'reconstruction_last_run' ,
87+ value : Time . now . utc . iso8601
88+ )
89+ rescue => e
90+ LOGGER . warn ( "TileReconstructor: failed to save last run timestamp: #{ e . message } " )
91+ end
92+
93+ def get_last_run_timestamp ( db )
94+ timestamp_str = db [ :metadata ] . where ( name : 'reconstruction_last_run' ) . get ( :value )
95+ return nil unless timestamp_str
96+
97+ Time . parse ( timestamp_str )
98+ rescue => e
99+ LOGGER . warn ( "TileReconstructor: failed to get last run timestamp: #{ e . message } " )
100+ nil
101+ end
102+
81103 # Parses schedule time from config, returns {hour:, minute:} or nil
82104 def parse_schedule_time
83105 time_str = @route . dig ( :gap_filling , :schedule , :time ) || @route . dig ( :gap_filling , :schedule , 'time' )
@@ -111,34 +133,38 @@ def run_reconstruction
111133
112134 downsample_opts = build_downsample_opts ( @route )
113135
114- LOGGER . info ( "TileReconstructor: starting gap filling for #{ @source_name } from zoom #{ start_zoom } to #{ minzoom } " )
136+ last_run_time = get_last_run_timestamp ( db )
137+ LOGGER . info ( "TileReconstructor: starting #{ last_run_time ? "incremental (last run: #{ last_run_time . iso8601 } )" : "full (first run)" } gap filling for #{ @source_name } from zoom #{ start_zoom } to #{ minzoom } " )
115138
116139 start_zoom . downto ( minzoom ) do |z |
117140 break unless @running
118141
119142 begin
120- process_zoom_level ( z , db , downsample_opts , minzoom , maxzoom )
143+ process_zoom_level ( z , db , downsample_opts , minzoom , maxzoom , last_run_time )
121144 rescue => e
122145 LOGGER . error ( "TileReconstructor: failed to process zoom #{ z } for #{ @source_name } : #{ e . message } " )
123146 LOGGER . debug ( "TileReconstructor: backtrace: #{ e . backtrace . join ( "\n " ) } " )
124147 end
125148 end
126149
150+ save_last_run_timestamp ( db )
151+
127152 LOGGER . info ( "TileReconstructor: gap filling completed for #{ @source_name } " )
128153 end
129154
130155 otl_def :run_reconstruction
131156
132- def process_zoom_level ( z , db , downsample_opts , minzoom , maxzoom )
157+ def process_zoom_level ( z , db , downsample_opts , minzoom , maxzoom , last_run_time = nil )
133158 parent_z = z - 1
134159 return if parent_z < minzoom
135160
136161 LOGGER . info ( "TileReconstructor: processing zoom #{ z } -> #{ parent_z } " )
137162
138- all_tiles_z = load_tiles_for_zoom ( z , db )
163+ all_tiles_z = load_tiles_for_zoom ( z , db , last_run_time )
139164 return if all_tiles_z . empty?
140165
141- LOGGER . info ( "TileReconstructor: loaded #{ all_tiles_z . size } tiles for zoom #{ z } " )
166+ log_msg = last_run_time ? "loaded #{ all_tiles_z . size } tiles for zoom #{ z } (filtered by timestamp)" : "loaded #{ all_tiles_z . size } tiles for zoom #{ z } "
167+ LOGGER . info ( "TileReconstructor: #{ log_msg } " )
142168
143169 parent_coords_set = calculate_parent_coords ( all_tiles_z )
144170 LOGGER . info ( "TileReconstructor: calculated #{ parent_coords_set . size } unique parents for zoom #{ parent_z } " )
@@ -182,11 +208,19 @@ def process_parent(parent_coords, z, parent_z, db, downsample_opts, minzoom)
182208 generate_and_save_parent ( px , py , parent_z , children_data_array , used_count , downsample_opts , db , grandparent_tile , parent_tile , parent_validation )
183209 end
184210
185- # Loads all tiles for a zoom level (coordinates only, no blob)
186- def load_tiles_for_zoom ( z , db )
187- db [ :tiles ] . where ( zoom_level : z )
188- . select ( :tile_column , :tile_row , :generated )
189- . to_a
211+ def load_tiles_for_zoom ( z , db , last_run_time = nil )
212+ query = db [ :tiles ] . where ( zoom_level : z )
213+
214+ if last_run_time
215+ last_run_utc = last_run_time . utc
216+ conditions = [
217+ Sequel [ :updated_at ] > last_run_utc ,
218+ Sequel [ :generated ] => -5
219+ ]
220+ query = query . where { Sequel . |( *conditions ) }
221+ end
222+
223+ query . select ( :tile_column , :tile_row , :generated ) . to_a
190224 end
191225
192226 def calculate_parent_coords ( all_tiles_z )
@@ -323,14 +357,16 @@ def generate_and_save_parent(px, py, parent_z, children_data_array, used_count,
323357 target : [ :zoom_level , :tile_column , :tile_row ] ,
324358 update : {
325359 tile_data : Sequel [ :excluded ] [ :tile_data ] ,
326- generated : Sequel [ :excluded ] [ :generated ]
360+ generated : Sequel [ :excluded ] [ :generated ] ,
361+ updated_at : Sequel . lit ( "datetime('now', 'utc')" )
327362 }
328363 ) . insert (
329364 zoom_level : parent_z ,
330365 tile_column : px ,
331366 tile_row : py ,
332367 tile_data : Sequel . blob ( new_data ) ,
333- generated : used_count
368+ generated : used_count ,
369+ updated_at : Sequel . lit ( "datetime('now', 'utc')" )
334370 )
335371
336372 mark_grandparent_for_regeneration ( grandparent_tile , db ) if grandparent_tile && grandparent_tile [ :generated ] != 0
@@ -347,7 +383,7 @@ def mark_grandparent_for_regeneration(grandparent_tile, db)
347383 zoom_level : grandparent_tile [ :zoom_level ] ,
348384 tile_column : grandparent_tile [ :tile_column ] ,
349385 tile_row : grandparent_tile [ :tile_row ]
350- ) . update ( generated : -5 )
386+ ) . update ( generated : -5 , updated_at : Sequel . lit ( "datetime('now', 'utc')" ) )
351387 end
352388
353389 # Validates tile and returns its status
0 commit comments