104 lines
3.9 KiB
Python
104 lines
3.9 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""
|
||
|
|
Weather interpolation to 100m grid using scipy griddata.
|
||
|
|
Optimized: vectorized operations, chunked processing, gzip compression.
|
||
|
|
"""
|
||
|
|
|
||
|
|
import pandas as pd
|
||
|
|
import numpy as np
|
||
|
|
from scipy.interpolate import griddata
|
||
|
|
from pathlib import Path
|
||
|
|
import time
|
||
|
|
import warnings
|
||
|
|
warnings.filterwarnings('ignore')
|
||
|
|
|
||
|
|
def process_year_fast(year, station_data_dir, grid_parquet_path, output_dir):
|
||
|
|
print(f"=== Processing year {year} (optimized) ===")
|
||
|
|
t0 = time.time()
|
||
|
|
|
||
|
|
grid_df = pd.read_parquet(grid_parquet_path)
|
||
|
|
grid_ids = grid_df['grid_id'].values
|
||
|
|
grid_points = grid_df[['center_lon', 'center_lat']].values
|
||
|
|
n_grid = len(grid_df)
|
||
|
|
print(f"Grid: {n_grid:,} cells")
|
||
|
|
|
||
|
|
station_df = pd.read_parquet(f"{station_data_dir}/daily_wuhan_{year}.parquet")
|
||
|
|
station_df['date'] = pd.to_datetime(station_df['date']).dt.date
|
||
|
|
dates = sorted(station_df['date'].unique())
|
||
|
|
print(f"Days: {len(dates)}")
|
||
|
|
|
||
|
|
pollutants = ['AQI', 'PM25', 'PM10', 'SO2', 'NO2', 'O3', 'CO']
|
||
|
|
|
||
|
|
station_locs = station_df.groupby('station_id').first()[['lat', 'lon', 'district']].reset_index()
|
||
|
|
print(f"Stations: {len(station_locs)}")
|
||
|
|
|
||
|
|
output_path = Path(output_dir)
|
||
|
|
output_path.mkdir(parents=True, exist_ok=True)
|
||
|
|
|
||
|
|
for poll_idx, poll in enumerate(pollutants):
|
||
|
|
print(f"\n[{poll_idx+1}/{len(pollutants)}] {poll}...")
|
||
|
|
t1 = time.time()
|
||
|
|
|
||
|
|
all_records = []
|
||
|
|
chunk_size = 50
|
||
|
|
|
||
|
|
for chunk_start in range(0, len(dates), chunk_size):
|
||
|
|
chunk_dates = dates[chunk_start:chunk_start + chunk_size]
|
||
|
|
chunk_records = []
|
||
|
|
|
||
|
|
for date in chunk_dates:
|
||
|
|
day_data = station_df[station_df['date'] == date]
|
||
|
|
values = day_data.set_index('station_id')[poll]
|
||
|
|
merged = station_locs.merge(values.reset_index(), on='station_id', how='inner')
|
||
|
|
|
||
|
|
if len(merged) < 3:
|
||
|
|
continue
|
||
|
|
|
||
|
|
sc = merged[['lon', 'lat']].values
|
||
|
|
sv = merged[poll].values
|
||
|
|
valid_mask = ~pd.isna(sv)
|
||
|
|
|
||
|
|
if valid_mask.sum() < 3:
|
||
|
|
continue
|
||
|
|
|
||
|
|
result = griddata(sc[valid_mask], sv[valid_mask], grid_points, method='nearest')
|
||
|
|
|
||
|
|
if result is not None and not np.all(np.isnan(result)):
|
||
|
|
valid_result = ~np.isnan(result)
|
||
|
|
if valid_result.any():
|
||
|
|
day_records = pd.DataFrame({
|
||
|
|
'grid_id': grid_ids[valid_result],
|
||
|
|
'date': date,
|
||
|
|
'pollutant': poll,
|
||
|
|
'value': result[valid_result].astype(np.float32)
|
||
|
|
})
|
||
|
|
chunk_records.append(day_records)
|
||
|
|
|
||
|
|
if chunk_records:
|
||
|
|
all_records.append(pd.concat(chunk_records, ignore_index=True))
|
||
|
|
|
||
|
|
print(f" {min(chunk_start + chunk_size, len(dates))}/{len(dates)} days")
|
||
|
|
|
||
|
|
if all_records:
|
||
|
|
final_df = pd.concat(all_records, ignore_index=True)
|
||
|
|
out_file = output_path / f'grid_weather_{poll}_{year}.parquet'
|
||
|
|
final_df.to_parquet(out_file, index=False, compression='gzip')
|
||
|
|
size_mb = out_file.stat().st_size / 1024 / 1024
|
||
|
|
print(f" Saved: {len(final_df):,} records, {size_mb:.1f} MB")
|
||
|
|
else:
|
||
|
|
print(f" No valid data")
|
||
|
|
|
||
|
|
print(f" Time: {time.time()-t1:.0f}s")
|
||
|
|
|
||
|
|
print(f"\n=== Total: {time.time()-t0:.0f}s ===")
|
||
|
|
|
||
|
|
if __name__ == '__main__':
|
||
|
|
import argparse
|
||
|
|
parser = argparse.ArgumentParser()
|
||
|
|
parser.add_argument('--year', type=int, required=True)
|
||
|
|
parser.add_argument('--station-data-dir', default='processed/weather')
|
||
|
|
parser.add_argument('--grid-parquet', default='processed/grid_100m_index.parquet')
|
||
|
|
parser.add_argument('--output-dir', default='processed/weather')
|
||
|
|
args = parser.parse_args()
|
||
|
|
process_year_fast(args.year, args.station_data_dir, args.grid_parquet, args.output_dir)
|