Welcome to my Solar & Sky Watch page—a dedicated space where I monitor activity in our solar system and beyond. Using a combination of Python scripts and data from NASA and other scientific institutions, I track asteroids, comets, solar activity, and planetary positions. Feel free to explore the scripts below and run them yourself if you’d like to keep an eye on the skies.
What I Monitor
☄️ Asteroids & Comets (NASA Scout Data)
The first script pulls real-time data from NASA’s Scout API, which continuously monitors the Minor Planet Center’s NEO Confirmation Page. Scout identifies new near-Earth objects (NEOs) and assesses their potential impact risk. The script displays:
-
Object ID and type (asteroid or comet)
-
Risk level (Low, Moderate, High, or Warning)
-
Estimated size based on absolute magnitude (H)
-
Speed and distance from Earth (in Lunar Distances)
-
Objects flagged as Potentially Hazardous Asteroids (PHAs)
If any object has a risk score, it is highlighted separately. The script also provides a summary of how many NEOs and PHAs are currently being tracked.
🧲 Solar Activity & Geomagnetic Storms (KP Index Forecast)
The second script forecasts geomagnetic activity by retrieving the planetary K-index (KP) from GFZ Potsdam or NOAA. The KP index measures disturbances in Earth’s magnetic field caused by solar wind. This is particularly useful for:
-
Aurora borealis hunters (high KP = better chances of seeing the Northern Lights)
-
Radio amateurs and satellite operators
-
Anyone interested in space weather
The script presents a forecast for the next 3–9 days, including:
🌍 Planetary Position and Distance from Oslo (Skyfield)
The third script uses the Skyfield astronomy library to calculate the exact position and distance of celestial objects as seen from Oslo, Norway. You can enter any major solar system body—such as the Moon, Sun, Jupiter, or Saturn—and a specific date and time. The script returns:
This is a handy tool for planning observations or simply satisfying curiosity about where a particular planet is right now.
Why I Built This
I have always been fascinated by what moves in the space around us—whether it is a distant comet, a potentially hazardous asteroid, or the solar storms that paint the sky with auroras. These scripts are my way of staying connected to the cosmos, and I hope they inspire you to look up as well.
All scripts are written in Python and use open data from NASA, NOAA, and other research institutions. You are welcome to download, modify, and run them on your own machine.
Scripts
Below you will find the Python scripts I use for monitoring. Simply copy the code, save it as a .py file, and run it in your terminal (ensure you have the required libraries installed).
Script 1: Asteroids & Comets (NASA Scout Data)
#!/usr/bin/env python3
“””
Enkelt program for å vise status for asteroider og kometer.
Henter data fra NASA Scout (https://ssd-api.jpl.nasa.gov/doc/scout.html)
“””
import requests
from datetime import datetime
def get_scout_data():
“””Hent data fra NASA Scout API”””
url = “https://ssd-api.jpl.nasa.gov/scout.api”
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f”Feil ved henting av data: {e}”)
returnNone
def format_risk_level(score):
“””Konverter risikoscore til lesbart nivå”””
if score isNone:
return”Ingen”
try:
s = float(score)
if s < 0.5:
return”Lav 🌕”
elif s < 2.0:
return”Moderat 🌖”
elif s < 5.0:
return”Høy 🌗”
else:
return”Spesiell 🌘”
except (ValueError, TypeError):
return”Ukjent”
def calculate_size_from_H(H):
“””Beregn omtrentlig størrelse fra absolutt magnitude (H)”””
ifnot H:
return”?”
try:
# Standard formel: diameter (km) = 1329 / sqrt(albedo) * 10^(-H/5)
# Antar albedo = 0.14 (typisk for asteroider)
H_val = float(H)
diameter_km = 1329 / 0.374 * 10**(-H_val/5) # 1329/sqrt(0.14) ≈ 1329/0.374
diameter_m = diameter_km * 1000
if diameter_m < 10:
returnf”{diameter_m:.0f}m”
elif diameter_m < 1000:
returnf”{diameter_m:.0f}m”
else:
returnf”{diameter_m/1000:.1f}km”
except:
return”?”
def vis_asteroid_status():
“””Hovedfunksjon som viser status for asteroider og kometer”””
print(“\n” + “=”*80)
print(“ASTEROID- OG KOMETOVERSIKT – NASA SCOUT”)
print(“=”*80)
print(f”Data hentet: {datetime.now().strftime(‘%Y-%m-%d %H:%M:%S UTC’)}”)
print(“Kilde: NASA/JPL Center for NEO Studies (CNEOS)”)
print(“\nScout overvåker Minor Planet Center sin NEO Confirmation Page 24/7”)
print(“=”*80)
# Hent data fra Scout API
data = get_scout_data()
ifnot data or’data’notin data:
print(“\nFEIL: Kunne ikke hente data fra NASA Scout”)
return
print(f”\nAntall objekt under overvåking: {len(data[‘data’])}”)
print(“\n” + “-“*80)
print(f”{‘Nr’:<4} {‘Objekt ID’:<12} {‘Type’:<10} {‘Risiko’:<15} {‘Størrelse’:<12} {‘H’:<6} {‘Fart’:<8} {‘Avstand’:<10}”)
print(“-“*80)
interesting = []
for i, obj in enumerate(data[‘data’], 1):
# Objekt ID
obj_id = obj.get(‘objectName’, f’Obj_{i}’)
# Type (basert på ID og data)
obj_type = “Asteroide”
if obj_id and (‘P/’in obj_id or’C/’in obj_id):
obj_type = “Komet”
elif obj.get(‘neoScore’, 0) and obj.get(‘neoScore’, 0) > 50:
obj_type = “NEO*”
# Risikoscore – håndter None-verdier
risk_score = obj.get(‘rating’)
risk_level = format_risk_level(risk_score)
# Størrelse fra H-verdi
H = obj.get(‘H’, ‘?’)
size = calculate_size_from_H(H)
# Fart (vInf)
speed = obj.get(‘vInf’, ‘?’)
speed_str = f”{speed} km/s” if speed != ‘?’ else ‘?’
# Avstand til jorden (caDist) i Lunar Distance
distance = obj.get(‘caDist’, ‘?’)
dist_str = f”{distance} LD” if distance != ‘?’ else ‘?’
print(f”{i:<4} {obj_id:<12} {obj_type:<10} {risk_level:<15} {size:<12} {H:<6} {speed_str:<8} {dist_str:<10}”)
# Samle interessante objekt (med rating > 0)
if risk_score and float(risk_score) > 0:
interesting.append(obj)
print(“-“*80)
# Vis objekt med risikoscore
if interesting:
print(“\n” + “!”*80)
print(“OBJEKTER MED RISIKOSCORE:”)
print(“!”*80)
for obj in sorted(interesting, key=lambda x: float(x.get(‘rating’, 0) or0), reverse=True):
obj_id = obj.get(‘objectName’, ‘Ukjent’)
rating = obj.get(‘rating’, 0)
H = obj.get(‘H’, ‘?’)
speed = obj.get(‘vInf’, ‘?’)
print(f” • {obj_id}: Rating {rating} – H={H}, Fart={speed} km/s”)
print(“!”*80)
else:
print(“\nIngen objekt med risikoscore funnet.”)
# Vis statistikk
neo_count = sum(1 for o in data[‘data’] if o.get(‘neoScore’, 0) and o.get(‘neoScore’, 0) > 50)
pha_count = sum(1 for o in data[‘data’] if o.get(‘phaScore’, 0) and o.get(‘phaScore’, 0) > 0)
print(“\n” + “=”*80)
print(“STATISTIKK:”)
print(f” • NEO-objekt (neoScore > 50): {neo_count}”)
print(f” • Potensielt farlige (phaScore > 0): {pha_count}”)
print(f” • Med risikoscore: {len(interesting)}”)
print(“=”*80)
print(“\nForklaring:”)
print(” 🌕 Lav risiko – Normal overvåking”)
print(” 🌖 Moderat risiko – Krever oppfølging”)
print(” 🌗 Høy risiko – Prioriteres for observasjon”)
print(” NEO* = Near-Earth Object (nærjordsobjekt)”)
print(” LD = Lunar Distance (1 LD ≈ 384,400 km)”)
print(” H = Absolutt magnitude (lavere tall = større objekt)”)
print(“=”*80)
if __name__ == “__main__”:
vis_asteroid_status()
Script 2: Solar Activity & Geomagnetic Storms (KP Index Forecast)
#!/usr/bin/env python3
import requests
import pytz
from datetime import datetime, timedelta
def get_kp_forecast():
“””Hent KP-indeks prognose fra GFZ Potsdam eller NOAA”””
# KP interpretation mapping
kp_levels = [
(1, “Very Quiet”), (2, “Quiet”), (3, “Quiet”), (4, “Unsettled”),
(5, “Unsettled”), (6, “Active”), (7, “Minor Storm”),
(8, “Moderate Storm”), (9, “Storm”)
]
def interpret_kp(kp):
return next((level for threshold, level in kp_levels if kp < threshold), “Extreme Geomagnetic Storm”)
def parse_gfz(data):
ifnot data ornot isinstance(data.get(“forecast”), list): returnNone
return [{
“timestamp”: datetime.fromisoformat(e[“time”]).strftime(“%Y-%m-%d %H:%M:%S UTC”),
“kp_index”: float(e[“kp”]),
“activity”: interpret_kp(float(e[“kp”]))
} for e in data[“forecast”] if e.get(“time”) and e.get(“kp”)]
# Vi prøver GFZ Potsdam først, og hvis det feiler, prøver vi NOAA
for url in [
“https://kp.gfz-potsdam.de/app/json/forecast”,
“https://services.swpc.noaa.gov/products/noaa-planetary-k-index-forecast.json”
]:
try:
resp = requests.get(url, timeout=5)
resp.raise_for_status()
data = resp.json()
if”gfz-potsdam”in url:
forecasts = parse_gfz(data)
else: # NOAA
forecasts = [{
“timestamp”: f”{e[0]} UTC”,
“kp_index”: float(e[1]),
“activity”: interpret_kp(float(e[1]))
} for e in data[1:] if len(e) >= 2]
if forecasts:
days = 9 if “gfz-potsdam” in url else 3
return {
“forecast_period”: {
“start”: datetime.now(pytz.UTC).strftime(“%Y-%m-%d”),
“end”: (datetime.now(pytz.UTC) + timedelta(days=days)).strftime(“%Y-%m-%d”)
},
“kp_forecast”: forecasts
}
except: continue
returnNone
def vis_kp_prognose():
“””Vis KP-indeks prognose i terminalen”””
print(“\n” + “=”*80 + “\nKP INDEX FORECAST\n” + “=”*80)
forecast = get_kp_forecast()
ifnot forecast:
print(“\nFEIL: Kunne ikke hente KP prognose\nSjekk internettforbindelsen og prøv igjen”)
return
print(f”\nPrognoseperiode: {forecast[‘forecast_period’][‘start’]} til {forecast[‘forecast_period’][‘end’]}”)
print(“\n” + “-“*80)
print(f”{‘Dato’:<12} {‘Tid (UTC)’:<15} {‘KP’:<6} {‘Aktivitet’:<20} {‘Varsling’:<10}”)
print(“-“*80)
storm_warnings = []
warning_map = {5: “⚠️ MINOR”, 6: “⚠️ MODERATE”, 7: “⚠️ STRONG”, 8: “⚠️ SEVERE”, 9: “⚠️ SEVERE”}
for entry in forecast[‘kp_forecast’]:
date_part, time_part = entry[‘timestamp’].split(‘ ‘, 1) if ‘ ‘ in entry[‘timestamp’] else (entry[‘timestamp’], “”)
kp, activity = entry[‘kp_index’], entry[‘activity’]
warning = warning_map.get(int(kp), “”)
if warning:
storm_warnings.append(f”{date_part} {time_part}: {warning} STORM (KP {kp})”)
print(f”{date_part:<12} {time_part:<15} {kp:<6.1f} {activity:<20} {warning:<10}”)
print(“-“*80 + (“\n\n” + “!”*80 + “\nSTORVARSLER:\n” + “!”*80 + “\n ” +
“\n “.join(storm_warnings) + “\n” + “!”*80if storm_warnings else”\n\nIngen stormvarsler i prognoseperioden”))
print(“\n” + “=”*80 + “\nForklaring:\n KP 0-1: Quiet (Stille)\n KP 2-4: Unsettled/Active (Urolig/Aktivt)”
“\n KP 5: Minor Storm (Liten storm)\n KP 6: Moderate Storm (Moderat storm)”
“\n KP 7: Strong Storm (Sterk storm)\n KP 8-9: Severe/Extreme Storm (Alvorlig/Ekstrem storm)\n” + “=”*80)
if __name__ == “__main__”:
vis_kp_prognose()
Library requirements:
# pip install requests pytz
Script 3: Planetary Position and Distance from Oslo
from skyfield.api import load, Topos, utc
from datetime import datetime
class astro_calculator:
def __init__(self):
pass
def get_object_distance_and_position(self, object_name, date):
try:
planets = load(‘de421.bsp’)
earth, sun, moon = planets[‘earth’], planets[‘sun’], planets[‘moon’]
if object_name == ‘moon’:
celestial_body = moon
elif object_name == ‘sun’:
celestial_body = sun
elif object_name == ‘jupiter’:
celestial_body = planets[‘JUPITER BARYCENTER’]
elif object_name == ‘saturn’:
celestial_body = planets[‘SATURN BARYCENTER’]
elif object_name == ‘uranus’:
celestial_body = planets[‘URANUS BARYCENTER’]
elif object_name == ‘neptune’:
celestial_body = planets[‘NEPTUNE BARYCENTER’]
else:
try:
celestial_body = planets[object_name]
except KeyError:
raise ValueError(“Ugyldig objektnavn!”)
ts = load.timescale()
t = ts.utc(datetime.strptime(date, “%Y-%m-%d %H:%M:%S”).replace(tzinfo=utc))
# Beregn for Oslo (59.91° N, 10.75° Ø)
oslo = earth + Topos(‘59.91 N’, ‘10.75 E’)
astrometric_oslo = oslo.at(t).observe(celestial_body)
distance_oslo = astrometric_oslo.distance().au
ra_oslo, dec_oslo, _ = astrometric_oslo.radec()
return distance_oslo, (ra_oslo, dec_oslo)
except Exception as e:
print(f”Feil i get_object_distance_and_position: {e}”)
raise
if __name__ == “__main__”:
calculator = astro_calculator()
objekt_navn = input(“Skriv himmellegeme (f.eks. ‘moon’, ‘sun’): “).strip().lower()
dato_tid = input(“Skriv dato og tid (ÅÅÅÅ-MM-DD TT:MM:SS): “).strip()
try:
avstand, posisjon = calculator.get_object_distance_and_position(objekt_navn, dato_tid)
ra, dec = posisjon
print(f”\n— Resultater for {objekt_navn} sett fra Oslo —“)
print(f”Avstand: {avstand:.6f} AU”)
print(f”Posisjon:”)
print(f” Rektascensjon: {ra}”)
print(f” Deklinasjon: {dec}”)
except Exception as e:
print(f”\nFeil: {e}”)
Library requirements:
You much download the file de421.bsp. The file should be in the same directory as your python program.
# pip3 install skyfield
or
# pip install skyfield
Notes
-
Data is updated regularly, but always check the official sources for critical information.
-
The asteroid script uses NASA’s Scout API—some objects may be very new and have limited data.
-
For the KP forecast, I prioritize GFZ Potsdam data (9-day forecast) but fall back to NOAA (3-day forecast) if necessary.
-
The planetary position script uses the DE421 ephemeris from NASA JPL.
Thank you for visiting my Solar & Sky Watch page. Clear skies!