DEV Community

Cover image for Visualizing Day and Night on Earth with Python (Real-Time World Map)
Developer Service
Developer Service

Posted on • Originally published at developer-service.blog

Visualizing Day and Night on Earth with Python (Real-Time World Map)

Have you ever wondered what time it is in Tokyo when it’s midnight in New York? Or wanted to see which parts of the world are basking in sunlight while others rest under the night sky?

In this article, we’ll create a Python application that generates a captivating visual map of day and night across the globe. Using real-time geocoding and astronomical data, the map shows exactly where the sun is shining at any given moment.

This project leverages several powerful Python libraries and APIs to produce an interactive day/night map that illustrates the Earth’s rotation and the relationship between time zones, geographic coordinates, and the sun’s position.

Whether you’re a data visualization enthusiast, a geography lover, or simply curious about how our planet’s rotation impacts different regions, this project offers both educational insights and striking visual appeal.


Requirements

Before starting, ensure you have Python 3.9 or higher installed (required for full zoneinfo support).

You’ll also need the following Python packages:

pip install requests pandas numpy matplotlib cartopy
Enter fullscreen mode Exit fullscreen mode

Package Overview:

  • requests: Handles HTTP requests to the Open-Meteo APIs (for geocoding and weather data).
  • pandas: Used for data manipulation (minimal use in this project).
  • numpy: Supports numerical operations.
  • matplotlib: Creates the visualization and plots the map.
  • cartopy: Provides geographic projections and map features, including the Nightshade feature to display day and night regions.

API Access

This project uses the Open-Meteo API, which is free and requires no API key. We’ll interact with two endpoints:

  1. Geocoding API – Converts location names into coordinates and retrieves timezones.
  2. Weather Forecast API – Provides sunrise and sunset times for specific locations.

Both APIs are publicly accessible and free to use, making it easy to integrate real-world astronomical data into your Python project.


Step-by-Step Implementation

Step 1: Geocoding the Location

The first step is to transform a human-readable location name, such as "Berlin" or "New York", into precise geographic coordinates (latitude and longitude) and the corresponding timezone.

For this, we use the Open-Meteo Geocoding API, which allows us to look up locations and retrieve accurate coordinates and timezone data that we’ll need for calculating sunrise, sunset, and local time.

import requests
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
from cartopy.feature.nightshade import Nightshade
import datetime
from zoneinfo import ZoneInfo


# -----------------------------------------------
# 1. Get location from user and geocode it
# -----------------------------------------------
def get_coordinates(location_name):
    """Get coordinates and timezone for a location name using Open-Meteo Geocoding API."""
    base_url = "https://geocoding-api.open-meteo.com/v1/search"
    params = {
        "name": location_name,
        "count": 1,
        "language": "en"
    }
    try:
        response = requests.get(base_url, params=params, timeout=10)
        data = response.json()
        if "results" in data and len(data["results"]) > 0:
            result = data["results"][0]
            timezone = result.get("timezone", "UTC")
            return result["latitude"], result["longitude"], result.get("name", location_name), timezone
        else:
            return None, None, None, None
    except Exception as e:
        print(f"Error geocoding location: {e}")
        return None, None, None, None

# Ask user for location
location_name = input("Enter a location name (e.g., 'Berlin', 'New York', 'Tokyo'): ").strip()
if not location_name:
    location_name = "Berlin"  # Default location
    print(f"Using default location: {location_name}")

print(f"Searching for coordinates of '{location_name}'...")
LOCATION_LAT, LOCATION_LON, found_name, location_timezone = get_coordinates(location_name)

if LOCATION_LAT is None or LOCATION_LON is None:
    print(f"Error: Could not find coordinates for '{location_name}'")
    print("Please try a different location name.")
    exit(1)

print(f"Found: {found_name} at ({LOCATION_LAT}, {LOCATION_LON})")
print(f"Timezone: {location_timezone}")

# Ask for datetime (optional, defaults to current time)
date_input = input("Enter date/time (YYYY-MM-DD HH:MM:SS) or press Enter for current time: ").strip()
if date_input:
    try:
        DATE_TIME = datetime.datetime.strptime(date_input, "%Y-%m-%d %H:%M:%S")
    except ValueError:
        print("Invalid date format. Using current time.")
        DATE_TIME = datetime.datetime.now()
else:
    DATE_TIME = datetime.datetime.now()

print(f"Date/Time: {DATE_TIME}")
Enter fullscreen mode Exit fullscreen mode

This gathers a location and a date/time from the user, then retrieve geographical and timezone information for that location.

It first prompts the user to enter a location name, defaulting to Berlin if none is provided. Using the Open-Meteo Geocoding API, it fetches the corresponding latitude, longitude, and timezone, handling cases where the location cannot be found or if the API call fails. This ensures that the script always has valid coordinates to work with for further processing.

After determining the location, the script asks the user for a specific date and time, allowing the user to either input a custom value or use the current system time. The input is validated and parsed; if the format is incorrect, the script defaults to the current time.

Finally, it prints out the resolved location, its coordinates, timezone, and the selected date/time.

Step 2: Fetching Sunrise and Sunset Data

With the location’s coordinates in hand, the next step is to retrieve the sunrise and sunset times for that specific location and date.

We accomplish this by querying the Open-Meteo Weather Forecast API, which provides daily astronomical data. Using the latitude, longitude, and timezone, the API returns precise sunrise and sunset times. This information allows us to determine whether it is currently day or night at the chosen location and will serve as the basis for our day/night map visualization.

# -------------------------------------------------------------------
# 2. Fetch sunrise and sunset for the specified location and date
# -------------------------------------------------------------------
print(f"Fetching sunrise and sunset times for {found_name} on {DATE_TIME.date()}...")
# Use the date from DATE_TIME to get sunrise/sunset for that specific date
date_str = DATE_TIME.strftime("%Y-%m-%d")
# Request with location's timezone (or let API auto-detect) instead of forcing UTC
# If we have timezone from geocoding, use it; otherwise API will auto-detect
timezone_param = location_timezone if location_timezone else "auto"
url = f"https://api.open-meteo.com/v1/forecast?latitude={LOCATION_LAT}&longitude={LOCATION_LON}&daily=sunrise,sunset&timezone={timezone_param}&start_date={date_str}&end_date={date_str}"
# Store original timezone in case API doesn't return it
original_timezone = location_timezone
time_status = "UNKNOWN"
try:
    resp = requests.get(url, timeout=10).json()
    sunrise = resp['daily']['sunrise'][0]
    sunset = resp['daily']['sunset'][0]

    # Get timezone from API response - this is the actual timezone for the location
    if 'timezone' in resp:
        location_timezone = resp['timezone']
        print(f"Location timezone from API: {location_timezone}")
    elif original_timezone:
        # Use timezone from geocoding if API didn't return one
        location_timezone = original_timezone
        print(f"Using timezone from geocoding: {location_timezone}")
    else:
        print("Warning: No timezone information available")

    # Parse sunrise/sunset times - API returns ISO format strings in the location's timezone
    # We need to parse them and convert to UTC for comparison
    sunrise_str = sunrise.replace('Z', '') if sunrise.endswith('Z') else sunrise
    sunset_str = sunset.replace('Z', '') if sunset.endswith('Z') else sunset

    # Parse as naive datetime first
    sunrise_dt_naive = datetime.datetime.fromisoformat(sunrise_str)
    sunset_dt_naive = datetime.datetime.fromisoformat(sunset_str)

    # Convert to UTC for comparison (we'll use the timezone to convert local times to UTC)
    tz_obj = ZoneInfo(location_timezone) if location_timezone else datetime.timezone.utc

    # Make sunrise/sunset timezone-aware in local timezone, then convert to UTC
    sunrise_dt = sunrise_dt_naive.replace(tzinfo=tz_obj).astimezone(datetime.timezone.utc)
    sunset_dt = sunset_dt_naive.replace(tzinfo=tz_obj).astimezone(datetime.timezone.utc)

    print(f"Sunrise: {sunrise} ({location_timezone})")
    print(f"Sunset: {sunset} ({location_timezone})")

    # Make DATE_TIME timezone-aware (assume UTC if not specified)
    if DATE_TIME.tzinfo is None:
        current_time_utc = DATE_TIME.replace(tzinfo=datetime.timezone.utc)
    else:
        current_time_utc = DATE_TIME.astimezone(datetime.timezone.utc)

    # Now all datetimes are timezone-aware, safe to compare
    is_daytime = sunrise_dt <= current_time_utc <= sunset_dt
    time_status = "DAYTIME" if is_daytime else "NIGHTTIME"
    print(f"At {DATE_TIME.strftime('%Y-%m-%d %H:%M:%S')} UTC, {found_name} is in {time_status}")
except Exception as e:
    print(f"Error fetching data: {e}")
    sunrise = None
    sunset = None
    time_status = "UNKNOWN"
Enter fullscreen mode Exit fullscreen mode

This section of the code is responsible for fetching sunrise and sunset times for a specific location and date and determining whether it is currently daytime or nighttime at that location.

It first formats the date and constructs a request to the Open-Meteo API, including the latitude, longitude, and timezone obtained from the previous geocoding step. The API returns sunrise and sunset times for that date, typically as ISO-formatted strings in the location’s local timezone.

The code then carefully handles timezone conversion. It parses the sunrise and sunset strings into datetime objects, applies the correct timezone, and converts them to UTC to allow accurate comparison with the user-specified date/time. It also ensures that the input date/time is timezone-aware.

Finally, it checks whether the specified moment falls between sunrise and sunset, setting a time_status of "DAYTIME" or "NIGHTTIME" accordingly.

Step 3: Creating the Visual Map

Next, we’ll build a visual map of day and night using Cartopy, a powerful Python library for geospatial plotting.

The map includes several key features:

  • Stock Image Background: A world map showing continents and oceans for context.
  • Coastlines: Detailed outlines of coastlines to enhance geographic accuracy.
  • Nightshade Overlay: A semi-transparent layer indicating regions currently in darkness. This feature automatically calculates shadows based on the Earth’s rotation and the sun’s position at the specified datetime.
  • Location Marker: A red dot marking the user-specified location.
  • Gridlines: Latitude and longitude lines with labels for easy reference.
  • Title and Information: Displays UTC time, local time, coordinates, and whether the location is in daytime or nighttime.

This step brings all the data together into a dynamic and informative visualization that clearly shows which parts of the world are in light or shadow at a given moment.

Next, we’ll look at the code implementation to see how all these elements come together.

# --------------------------------
# 3. Create map with nightshade
# --------------------------------
print("Creating map with nightshade...")
fig = plt.figure(figsize=(18, 9))
ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())

# Add stock image (world map)
ax.stock_img()

# Add coastlines
ax.coastlines()

# Add nightshade for the specified datetime
ax.add_feature(Nightshade(DATE_TIME, alpha=0.3))

# Mark the specified location
ax.plot(LOCATION_LON, LOCATION_LAT, 'ro', markersize=15, transform=ccrs.PlateCarree(), 
        label=f'{found_name} ({LOCATION_LAT:.2f}, {LOCATION_LON:.2f})', zorder=10)

# Calculate and display local time for the location
# Use the timezone we got from the API
try:
    tz = ZoneInfo(location_timezone) if location_timezone else datetime.timezone.utc
    timezone_name = location_timezone

    # Convert UTC time to local time
    if DATE_TIME.tzinfo is None:
        utc_time = DATE_TIME.replace(tzinfo=datetime.timezone.utc)
    else:
        utc_time = DATE_TIME.astimezone(datetime.timezone.utc)

    local_time = utc_time.astimezone(tz)
    local_time_str = local_time.strftime("%Y-%m-%d %H:%M:%S")
    print(f"Local time for {found_name}: {local_time_str} ({timezone_name})")
except Exception as e:
    print(f"Warning: Could not calculate local time: {e}")
    # Fallback to simple UTC offset based on longitude
    offset_hours = LOCATION_LON / 15.0
    if DATE_TIME.tzinfo is None:
        utc_time = DATE_TIME.replace(tzinfo=datetime.timezone.utc)
    else:
        utc_time = DATE_TIME.astimezone(datetime.timezone.utc)
    tz = datetime.timezone(datetime.timedelta(hours=offset_hours))
    local_time = utc_time.astimezone(tz)
    local_time_str = local_time.strftime("%Y-%m-%d %H:%M:%S")
    timezone_name = f"UTC{offset_hours:+.0f} (approx)"

# Set title with time status
title = f'Day/Night Map for {DATE_TIME.strftime("%Y-%m-%d %H:%M:%S")} UTC\n'
title += f'Location: {found_name} ({LOCATION_LAT:.2f}, {LOCATION_LON:.2f})'
title += f'\nLocal Time: {local_time_str} ({timezone_name})'
if sunrise and sunset:
    title += f'\n{found_name} is in {"DAYTIME" if time_status == "DAYTIME" else "NIGHTTIME"} at this moment'
ax.set_title(title, fontsize=14, pad=20)

# Add gridlines
ax.gridlines(draw_labels=True, linestyle='--', alpha=0.5)

# Add legend
ax.legend(loc='upper left')

plt.tight_layout()
plt.show()
Enter fullscreen mode Exit fullscreen mode

This section of the code creates a visual map showing day and night regions for a specific date and time, highlighting the chosen location.

It starts by setting up a world map using a Plate Carrée projection, adding a stock image and coastlines for geographic context. The Nightshade feature overlays shaded areas representing nighttime for the specified DATE_TIME, giving a clear visual distinction between day and night across the globe.

The script then marks the user-specified location with a red dot and calculates the local time at that location using the timezone obtained from the API. If the timezone is unavailable, it estimates the local time based on longitude. The map’s title includes the UTC date/time, the location, its local time, and whether it is currently daytime or nighttime there.

Finally, it adds gridlines, a legend, and displays the map, resulting in an informative visualization of global day/night patterns with the selected location highlighted.

The full source is available at: https://github.com/nunombispo/SunriseSunsetMap-Article


How It Works: The Science Behind the Visualization

This application illustrates several key concepts that govern day and night on Earth:

  1. Earth’s Rotation: The planet completes a full rotation every 24 hours, producing the familiar cycle of day and night.
  2. Time Zones: Different longitudes experience different local times, with roughly 15 degrees of longitude corresponding to a one-hour time difference.
  3. Solar Position: The sun’s position relative to the Earth determines which regions are illuminated at any given moment.
  4. Astronomical Calculations: Sunrise and sunset times vary by location and date due to the Earth’s axial tilt and its orbit around the sun.

The Cartopy Nightshade feature leverages these astronomical principles to calculate the terminator line—the boundary between day and night—accurately for any specified datetime, creating a realistic and dynamic visualization of Earth’s day/night patterns.


Usage Example

Here’s what using the script might look like in practice:

Enter a location name (e.g., 'Berlin', 'New York', 'Tokyo'): sydney, australia
Searching for coordinates of 'sydney, australia'...
Found: Sydney at (-33.86785, 151.20732)
Timezone: Australia/Sydney
Enter date/time (YYYY-MM-DD HH:MM:SS) or press Enter for current time: 
Date/Time: 2025-12-17 13:08:25.427231
Fetching sunrise and sunset times for Sydney on 2025-12-17...
Location timezone from API: Australia/Sydney
Sunrise: 2025-12-17T05:39 (Australia/Sydney)
Sunset: 2025-12-17T20:03 (Australia/Sydney)
At 2025-12-17 13:08:25 UTC, Sydney is in NIGHTTIME
Creating map with nightshade...
Local time for Sydney: 2025-12-18 00:08:25 (Australia/Sydney)
Enter fullscreen mode Exit fullscreen mode

Running the script generates a map like this, showing day and night regions, the marked location, and relevant time information:

Example for Sydney, Australia


Final Thoughts

This day/night visualization project beautifully blends geography, astronomy, and programming to create an experience that is both educational and visually captivating. It highlights how modern APIs can deliver rich geographic and astronomical data effortlessly, while Python’s visualization libraries transform that data into an intuitive, interactive map.

More than just a technical exercise, the project offers a tangible way to grasp Earth’s rotation and the global rhythm of day and night. Watching the terminator, the boundary between light and shadow, sweep across continents makes abstract concepts instantly understandable. Whether you’re coordinating international meetings, tracking global events, or simply appreciating the natural cycles of our planet, this tool provides both practical insights and aesthetic enjoyment.

By combining real-time API data, precise timezone handling, and polished visualization techniques, the project becomes a powerful tool for exploration, learning, and curiosity. Experimenting with different locations and times offers a fresh perspective on how Earth’s rotation connects every corner of the globe.

So next time you wonder what’s happening on the other side of the world, run this script and watch the map come alive, one shaded region at a time.


Follow me on Twitter: https://twitter.com/DevAsService

Follow me on Instagram: https://www.instagram.com/devasservice/

Follow me on TikTok: https://www.tiktok.com/@devasservice

Follow me on YouTube: https://www.youtube.com/@DevAsService

Top comments (0)