103 lines
3.6 KiB
Python
103 lines
3.6 KiB
Python
"""
|
|
HELPER SCRIPT: generate_keys.py
|
|
-------------------------------
|
|
This script generates a list of 'Quadkeys' (map tiles) that intersect a specific
|
|
geographic area (WKT Geometry). This list is used to seed the scraper in 'power2.py'
|
|
so it knows exactly where to look for outages without scanning the whole world.
|
|
|
|
PREREQUISITES:
|
|
pip install shapely mercantile
|
|
|
|
USAGE INSTRUCTIONS:
|
|
1. Obtain the WKT (Well-Known Text) geometry for your target area from your database.
|
|
|
|
Example SQL to get WKT for a specific county:
|
|
SELECT ST_AsText(geom) FROM county WHERE countyname = 'Kanawha' AND state = 'WV';
|
|
|
|
Example SQL to get WKT for a whole NWS Warning Area (Union of counties):
|
|
SELECT ST_AsText(ST_Union(geom)) FROM county WHERE cwa = 'RLX';
|
|
|
|
2. Run this script:
|
|
python generate_keys.py
|
|
|
|
3. Paste the huge text string returned by your SQL query into the prompt and hit Enter.
|
|
|
|
4. Copy the resulting 'KEY_LIST = [...]' output and paste it into the configuration
|
|
section of 'power2.py' (e.g., replace AEP_WV_KEYS).
|
|
"""
|
|
|
|
import mercantile
|
|
from shapely import wkt
|
|
from shapely.geometry import box
|
|
import sys
|
|
|
|
# --- CONFIGURATION ---
|
|
# The zoom level to generate keys for.
|
|
# Level 7 is standard for the "entry" lists in your scraper (e.g. '0320001').
|
|
# If the utility uses a different zoom level for its top-level clusters, adjust this.
|
|
TARGET_ZOOM = 6
|
|
|
|
# Increase the CSV field size limit to handle massive WKT strings
|
|
import csv
|
|
csv.field_size_limit(sys.maxsize)
|
|
|
|
def generate_keys_from_wkt(wkt_string):
|
|
"""
|
|
Takes a WKT geometry string, finds the bounding box, generates all tiles
|
|
within that box, and filters them to keep only those that actually
|
|
intersect the geometry.
|
|
"""
|
|
try:
|
|
print("Parsing WKT...")
|
|
# 1. Parse the WKT
|
|
service_area = wkt.loads(wkt_string)
|
|
|
|
# 2. Get the Bounding Box (minx, miny, maxx, maxy)
|
|
bounds = service_area.bounds
|
|
|
|
# 3. Generate all tiles covering the bounding box at the target zoom
|
|
# mercantile.tiles(west, south, east, north, zooms)
|
|
candidate_tiles = list(mercantile.tiles(bounds[0], bounds[1], bounds[2], bounds[3], zooms=[TARGET_ZOOM]))
|
|
|
|
valid_keys = []
|
|
|
|
print(f"Scanning {len(candidate_tiles)} candidate tiles...")
|
|
|
|
for tile in candidate_tiles:
|
|
# 4. Create a geometry for the tile itself
|
|
# mercantile.bounds returns (west, south, east, north)
|
|
t_bounds = mercantile.bounds(tile)
|
|
tile_geom = box(t_bounds.west, t_bounds.south, t_bounds.east, t_bounds.north)
|
|
|
|
# 5. Intersection Check: Does this tile actually touch the service area?
|
|
# We use intersects() because contains() might miss tiles that only partially overlap.
|
|
if service_area.intersects(tile_geom):
|
|
valid_keys.append(mercantile.quadkey(tile))
|
|
|
|
import json
|
|
|
|
# 6. Output in JSON format
|
|
valid_keys.sort()
|
|
print(f"\nFound {len(valid_keys)} intersecting tiles.")
|
|
print("-" * 30)
|
|
print(json.dumps(valid_keys))
|
|
print("-" * 30)
|
|
|
|
except Exception as e:
|
|
print(f"Error processing geometry: {e}")
|
|
|
|
if __name__ == "__main__":
|
|
print("Paste your PostGIS WKT (Well-Known Text) string below and press Enter:")
|
|
|
|
# Use standard input reading to handle potentially large inputs better than input()
|
|
try:
|
|
user_wkt = input().strip()
|
|
if user_wkt:
|
|
generate_keys_from_wkt(user_wkt)
|
|
else:
|
|
print("No input provided.")
|
|
except EOFError:
|
|
print("Error reading input.")
|
|
except KeyboardInterrupt:
|
|
print("\nCancelled.")
|