121 lines
3.7 KiB
Python
121 lines
3.7 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""
|
||
|
|
PostGIS index optimization script for grid queries.
|
||
|
|
|
||
|
|
Creates spatial indexes on the grids table for efficient bounding box
|
||
|
|
and radius queries used by the monitoring and prediction APIs.
|
||
|
|
|
||
|
|
Usage:
|
||
|
|
python scripts/setup_postgis_indexes.py --connection postgresql://user:pass@localhost:5432/wuhan_disease
|
||
|
|
"""
|
||
|
|
|
||
|
|
import sys
|
||
|
|
import argparse
|
||
|
|
|
||
|
|
def create_indexes(connection_string):
|
||
|
|
import asyncpg
|
||
|
|
|
||
|
|
indexes = [
|
||
|
|
("idx_grids_geometry", "CREATE INDEX IF NOT EXISTS idx_grids_geometry ON grids USING GIST (geometry)"),
|
||
|
|
("idx_grids_centroid", "CREATE INDEX IF NOT EXISTS idx_grids_centroid ON grids USING GIST (ST_Transform(geometry, 32650))"),
|
||
|
|
("idx_grids_grid_id", "CREATE INDEX IF NOT EXISTS idx_grids_grid_id ON grids (grid_id)"),
|
||
|
|
("idx_grids_district", "CREATE INDEX IF NOT EXISTS idx_grids_district ON grids (district)"),
|
||
|
|
]
|
||
|
|
|
||
|
|
print("Creating PostGIS spatial indexes...")
|
||
|
|
|
||
|
|
async def run_indexes():
|
||
|
|
conn = await asyncpg.connect(connection_string)
|
||
|
|
|
||
|
|
for idx_name, sql in indexes:
|
||
|
|
try:
|
||
|
|
await conn.execute(sql)
|
||
|
|
print(f" Created: {idx_name}")
|
||
|
|
except Exception as e:
|
||
|
|
print(f" Failed: {idx_name} - {e}")
|
||
|
|
|
||
|
|
await conn.close()
|
||
|
|
|
||
|
|
import asyncio
|
||
|
|
asyncio.run(run_indexes())
|
||
|
|
print("Done!")
|
||
|
|
|
||
|
|
|
||
|
|
def create_grid_table_sql():
|
||
|
|
|
||
|
|
return """
|
||
|
|
-- Create grids table for 100m grid cells
|
||
|
|
CREATE TABLE IF NOT EXISTS grids (
|
||
|
|
grid_id VARCHAR(20) PRIMARY KEY,
|
||
|
|
geometry GEOMETRY(POLYGON, 4326) NOT NULL,
|
||
|
|
center_lat DOUBLE PRECISION NOT NULL,
|
||
|
|
center_lon DOUBLE PRECISION NOT NULL,
|
||
|
|
district VARCHAR(50),
|
||
|
|
dem DOUBLE PRECISION,
|
||
|
|
population_density DOUBLE PRECISION,
|
||
|
|
created_at TIMESTAMP DEFAULT NOW()
|
||
|
|
);
|
||
|
|
|
||
|
|
-- Spatial index for geometry queries
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_grids_geometry ON grids USING GIST (geometry);
|
||
|
|
|
||
|
|
-- Index for district lookups
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_grids_district ON grids (district);
|
||
|
|
|
||
|
|
-- Index for grid_id lookups
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_grids_grid_id ON grids (grid_id);
|
||
|
|
|
||
|
|
-- Index for bounding box queries (UTM projection for meters)
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_grids_centroid ON grids USING GIST (ST_Transform(geometry, 32650));
|
||
|
|
|
||
|
|
-- Cluster the table by geometry for better spatial query performance
|
||
|
|
CLUSTER grids USING idx_grids_geometry;
|
||
|
|
|
||
|
|
-- Analyze the table for query planner
|
||
|
|
ANALYZE grids;
|
||
|
|
|
||
|
|
-- Example queries:
|
||
|
|
|
||
|
|
-- 1. Bounding box query (within 114.0-115.0 lon, 29.5-30.5 lat)
|
||
|
|
SELECT grid_id, center_lat, center_lon
|
||
|
|
FROM grids
|
||
|
|
WHERE geometry && ST_MakeEnvelope(113.8, 29.4, 115.2, 30.6, 4326);
|
||
|
|
|
||
|
|
-- 2. Radius query (within 10km of point)
|
||
|
|
SELECT grid_id, center_lat, center_lon,
|
||
|
|
ST_Distance(geometry, ST_Transform(ST_SetSRID(ST_MakePoint(114.3, 30.6), 4326), 32650)) as distance
|
||
|
|
FROM grids
|
||
|
|
WHERE ST_DWithin(
|
||
|
|
ST_Transform(geometry, 32650),
|
||
|
|
ST_Transform(ST_SetSRID(ST_MakePoint(114.3, 30.6), 4326), 32650),
|
||
|
|
10000
|
||
|
|
)
|
||
|
|
ORDER BY distance
|
||
|
|
LIMIT 100;
|
||
|
|
|
||
|
|
-- 3. District aggregation
|
||
|
|
SELECT district, COUNT(*) as grid_count, AVG(population_density) as avg_pop
|
||
|
|
FROM grids
|
||
|
|
GROUP BY district
|
||
|
|
ORDER BY grid_count DESC;
|
||
|
|
"""
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
parser = argparse.ArgumentParser(description='PostGIS index setup for grid queries')
|
||
|
|
parser.add_argument('--connection', help='PostgreSQL connection string')
|
||
|
|
parser.add_argument('--sql-only', action='store_true', help='Print SQL only')
|
||
|
|
args = parser.parse_args()
|
||
|
|
|
||
|
|
if args.sql_only:
|
||
|
|
print(create_grid_table_sql())
|
||
|
|
elif args.connection:
|
||
|
|
create_indexes(args.connection)
|
||
|
|
else:
|
||
|
|
print("Usage:")
|
||
|
|
print(" python scripts/setup_postgis_indexes.py --sql-only # Print SQL")
|
||
|
|
print(" python scripts/setup_postgis_indexes.py --connection postgresql://...")
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == '__main__':
|
||
|
|
main()
|