2024 Solar Eclipse Weather Report

2024-04-07 | [misc]

I wanted a better answer to what the weather along the eclipse path will look like, so I make this python program [video] to download a bunch of weather reports from the National Weather Service API and make it into a file I can put into Google Earth.

weather.png

The scale goes from more red (bad/rain/mostly cloudy) to more green (mostly sunny/sunny).

Directions

  1. install google earth (pro) desktop
  2. Go get the eclipse path kmz file and open it in google earth
  3. download the latest weather report KML I made below and open that in google earth too

Generated KML list (I'll probably do a couple more runs today and some tomorrow):

eclipse-weather-1712528627.zip
eclipse-weather-1712529313.zip
eclipse-weather-1712537783.zip
eclipse-weather-1712576870.zip
eclipse-weather-1712586230.zip
eclipse-weather-1712592925.zip
eclipse-weather-1712594734.zip

... and the results

gif
282
320
322
Some strange shadows. Note that the shadow lines are blurrier in one axis than the other.
185
191
The classic pinhole camera effect:
192

python code

#!/usr/bin/python3
#eclipse 2024 wether predictior - alnwlsn 2024
#using NWS forecast data api
#eclipse path extracted manually from KML at http://xjubier.free.fr/en/site_pages/SolarEclipsesGoogleEarth.html
import simplekml
import requests
import json
import csv
import time
import os.path
from datetime import datetime
import random
import subprocess

times = [
    "2024-04-07T16:00:00-04:00",
    "2024-04-08T13:00:00-04:00",
    "2024-04-08T14:00:00-04:00",
    "2024-04-08T15:00:00-04:00",
    "2024-04-08T16:00:00-04:00"
]

timesInterest = {}
for i in times:
    timesInterest[(datetime.fromisoformat(i).timestamp())] = i

def jj(j): #(debugging) write a json file to look at
    with open("jj.json", "w") as f:
        f.write(json.dumps(j, indent=2, sort_keys=True))

def get_grid_coordinates(x, y):
    url = f"https://api.weather.gov/points/{x},{y}"
    # print(url)
    response = requests.get(url)
    
    if response.status_code == 200:
        data = response.json()
        gridX = data['properties']['gridX']
        gridY = data['properties']['gridY']
        gridId = data['properties']['gridId']
        return gridId,gridX, gridY
    else:
        print("Error:", response.status_code)
        quit()
        return None, None
    
def get_forecast(grid_tuple): #gets hour-by hour forecast
    file = f'responses/{grid_tuple[0]},{grid_tuple[1]},{grid_tuple[2]}.json'
    if os.path.isfile(file): #do not get existing files
        return True
    url = f"https://api.weather.gov/gridpoints/{grid_tuple[0]}/{grid_tuple[1]},{grid_tuple[2]}/forecast/hourly"
    print(url)
    response = requests.get(url)
    
    if response.status_code == 200:
        data = response.json()
        with open(file, "w") as f:
            f.write(json.dumps(data, indent=2, sort_keys=True))
        return data
    else:
        print("Error:", response.status_code)
        if response.status_code == 500: #rate limiting timeout
            time.sleep(2)
        return False


def interpolate_points(points):
    interpolated_points = []
    for i in range(len(points) - 1):
        x1, y1 = points[i]
        x2, y2 = points[i + 1]
        interpolated_points.append((x1, y1))
        for j in range(1, 8):
            x_interpolated = x1 + (x2 - x1) * (j / 8)
            y_interpolated = y1 + (y2 - y1) * (j / 8)
            interpolated_points.append((x_interpolated, y_interpolated))
    interpolated_points.append(points[-1])  # Adding the last point
    return interpolated_points

def step1(): #get NWS grid coodinates of all points along the path
    path = []
    with open('path.csv') as f:
        index = 0
        rows = csv.reader(f)
        for row in rows:
            path.append((float(row[0]),float(row[1])))
            index += 1

    path = path[622:685] #trim this as needed
    path = interpolate_points(path)

    grid = []
    n = 0
    for i in path:
        g = get_grid_coordinates(i[1],i[0])
        # print(g)
        grid.append(g)
        # if n >= 4:
        #     break
        print(n,g)
        n+=1
        # time.sleep(1)
    with open("grid1.json", "w") as f:
        f.write(json.dumps(grid, indent=2, sort_keys=True))

def step2(): #extend the grid by some in up down grid direction (randomly)
    ex_grid = set()
    with open('grid1.json') as f:
        j = json.load(f)
        for i in j:
            # ex_grid.add((i[0], i[1]-4, i[2]))
            # ex_grid.add((i[0], i[1]-3, i[2]))
            # ex_grid.add((i[0], i[1]-2, i[2]))
            # ex_grid.add((i[0], i[1]-1, i[2]))
            # ex_grid.add((i[0], i[1], i[2]))
            # ex_grid.add((i[0], i[1]+1, i[2]))
            # ex_grid.add((i[0], i[1]+2, i[2]))
            # ex_grid.add((i[0], i[1]+3, i[2]))
            # ex_grid.add((i[0], i[1]+4, i[2]))
            for l in range(1,4):
                ex_grid.add((i[0], i[1]+random.randint(-30, 30), i[2]))

    ex_grid = list(ex_grid)
    # jj(ex_grid)
    print(ex_grid)
    print(len(ex_grid))
    with open("grid2.json", "w") as f:
        f.write(json.dumps(ex_grid, indent=2, sort_keys=True))

def step3(): #get the forecast for all the grid entries (store as a bunch of files)
    with open('grid2.json') as f:
    # with open('grid3.json') as f: #or this if you did step4 earlier
        j = json.load(f)
        # print(len(j))
        # time.sleep(4)
        for i in j:
            print(i)
            if False==get_forecast(i): #with 3 retries
                if False==get_forecast(i):
                    get_forecast(i)

def step4(): #reduce grid to ones I know are accessable by api (because I got the file in step3)
     g = []
     with open('grid2.json') as f:
        j = json.load(f)
        for i in j:
            file = f'responses/{i[0]},{i[1]},{i[2]}.json'
            if os.path.isfile(file):
                g.append(i)
        with open("grid3.json", "w") as f:
            f.write(json.dumps(g, indent=2, sort_keys=True))

def colorer(weather):
    #in BGR format
    opacity = "60"
    if "Mostly Cloudy" in weather:
        return opacity+'0080ff'
    if "Partly Cloudy" in weather:
        return opacity+'00bfff'
    if "Partly Sunny" in weather:
        return opacity+'00ffff'
    if "Mostly Sunny" in weather:
        return opacity+'00ffbf'
    if "Sunny" in weather:
        return opacity+'00ff00'
    return opacity+'0000ff' #unknown weather

def step5():
    kml = simplekml.Kml()
    subfolders = {}
    for i in timesInterest.keys():
        subfolders[i] = kml.newfolder(name=timesInterest[i])

    with open('grid2.json') as f:
        j = json.load(f)
        for i in j:
            file = f'responses/{i[0]},{i[1]},{i[2]}.json'
            if os.path.isfile(file):
                with open(file) as g:
                    color = '99ffac59'
                    forecast = json.load(g)
                    #reformat cordinates list into [(lon, lat),(lon, lat)...]
                    geo_raw = forecast['geometry']['coordinates'][0]
                    geo = []
                    for point in geo_raw:
                        geo.append((point[0],point[1]))
                    # quit()
                    hourly = forecast['properties']['periods']
                    for h in hourly:
                        startt = datetime.fromisoformat(h['startTime']).timestamp()
                        if startt in timesInterest.keys():
                            conditions = h['shortForecast']
                            color = colorer(conditions)
                            # print(timesInterest[startt],color,conditions)                       
                            pol = subfolders[startt].newpolygon(name=f'{i[0]},{i[1]},{i[2]},{conditions}', outerboundaryis=geo)
                            pol.style.linestyle.color = color
                            pol.style.polystyle.color = simplekml.Color.changealphaint(100, color)
                    # break
        # index += 1
        fstamp = 'eclipse-weather-'+str(int(time.time()))
        kml.save('/home/alnwlsn/temp/'+fstamp+".kml")

# step1() #first find the NWS grid coordinates for all points along the eclipse
# step2() #extend the collection with grid coords +1/-1 from the ones we have already
# step3() #grab forecast data for all grid points we generated. Try running this one a couple times
# step4() #store a list of all areas we know we can get, so I don't have to check against the API guess later (it can't do marine areas, for one)
step5() #make the KML

comments







comments rss link

no comments posted yet


Alnwlsn 2024