@@ -2440,6 +2440,64 @@ def from_path(path: str):
24402440
24412441 return obj
24422442
2443+
2444+ def parse_date (date_str : str , is_end : bool = False ):
2445+ """
2446+ Parse a partial or full date string and return ISO8601 with UTC 'Z'.
2447+ Handles year-only, year-month, and full dates.
2448+ """
2449+ from datetime import timedelta
2450+ import pandas as pd
2451+
2452+ # Handle partial dates
2453+ if len (date_str ) == 4 : # Year only
2454+ date_str = f"{ date_str } -12-31" if is_end else f"{ date_str } -01-01"
2455+ elif len (date_str ) == 7 : # Year-month
2456+ y , m = date_str .split ('-' )
2457+ if is_end :
2458+ last_day = pd .Timestamp (f"{ y } -{ m } -01" ).days_in_month
2459+ date_str = f"{ y } -{ m } -{ last_day :02d} "
2460+ else :
2461+ date_str = f"{ y } -{ m } -01"
2462+
2463+ # Parse with pandas (handles most formats automatically)
2464+ dt = pd .to_datetime (date_str , errors = 'coerce' , utc = True )
2465+ if pd .isna (dt ):
2466+ raise ValueError (f"Could not parse date: { date_str } " )
2467+
2468+ # Set end_date-of-day if needed
2469+ if is_end and dt .hour == 0 and dt .minute == 0 and dt .second == 0 :
2470+ dt = dt .replace (hour = 23 , minute = 59 , second = 59 )
2471+
2472+ return dt
2473+
2474+
2475+ def split_datetime (start_date : str , end_date : str , n : int = 10 , gap_milliseconds :int = 1000 ):
2476+ """
2477+ Split start_date/end_date dates into n chunks, returning list of (start_iso, end_iso) strings.
2478+ """
2479+ from datetime import timedelta
2480+ start_dt = parse_date (start_date , is_end = False )
2481+ end_dt = parse_date (end_date , is_end = True )
2482+
2483+ total_seconds = (end_dt - start_dt ).total_seconds ()
2484+ chunk_seconds = total_seconds / n
2485+
2486+ chunks = []
2487+ for i in range (n ):
2488+ chunk_start = start_dt + timedelta (seconds = i * chunk_seconds )
2489+ chunk_end = start_dt + timedelta (seconds = (i + 1 ) * chunk_seconds )
2490+ if i < n - 1 :
2491+ chunk_end = chunk_end - timedelta (milliseconds = gap_milliseconds )
2492+
2493+ chunks .append ((
2494+ chunk_start .strftime ("%Y-%m-%dT%H:%M:%SZ" ),
2495+ chunk_end .strftime ("%Y-%m-%dT%H:%M:%SZ" )
2496+ ))
2497+
2498+ return chunks
2499+
2500+
24432501def split_gdf (gdf , n : int = None , zoom : int = None , clip : bool = False , return_type : str = "gdf" ):
24442502 import geopandas as gpd
24452503 import pickle
@@ -3669,7 +3727,7 @@ def estimate_zoom(bounds, target_num_tiles=1):
36693727
36703728
36713729def get_tiles (
3672- bounds = None , target_num_tiles = 1 , zoom = None , max_tile_recursion = 6 , as_gdf = True , clip = False , verbose = False
3730+ bounds = None , target_num_tiles = 1 , zoom = None , max_tile_recursion = 6 , as_gdf = True , clip = False , add_bounds = False , verbose = False
36733731):
36743732 bounds = to_gdf (bounds )
36753733 import mercantile
@@ -3724,6 +3782,8 @@ def get_tiles(
37243782 if verbose :
37253783 print (f"Generated { len (gdf )} tiles at zoom level { zoom_level } " )
37263784
3785+ if add_bounds :
3786+ gdf ["bounds" ] = gdf ["geometry" ].map (lambda x : list (x .bounds ))
37273787 if clip :
37283788 return gdf .clip (bounds ) if as_gdf else gdf [["x" , "y" , "z" ]].values
37293789 else :
0 commit comments