Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save jdbcode/f6639195ce3a2f7ebed15b2aee4d5ea3 to your computer and use it in GitHub Desktop.

Select an option

Save jdbcode/f6639195ce3a2f7ebed15b2aee4d5ea3 to your computer and use it in GitHub Desktop.
replicating_goes_geocolor_in_earth_engine.ipynb
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "view-in-github",
"colab_type": "text"
},
"source": [
"<a href=\"https://colab.research.google.com/gist/jdbcode/f6639195ce3a2f7ebed15b2aee4d5ea3/replicating_goes_geocolor_in_earth_engine.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "PtiZKWd0pzEg"
},
"source": [
"\n",
"# Replicating GOES GeoColor in Earth Engine: A Winter Solstice Showcase\n",
"\n",
"Date: 2025-12-29<br>\n",
"Author: Justin Braaten<br><br>\n",
"\n",
"\n",
"December 21st marked the winter solstice—the shortest day of the year in the Northern Hemisphere. With 15 hours of darkness across much of the U.S., it's the perfect time to showcase the GOES GeoColor visualization: seamless transitions from sunlit landscapes to city lights and cloud illumination at night.\n",
"\n",
"A few years ago, I wrote about working with [GOES data in Earth Engine](https://medium.com/@jstnbraaten/goes-in-earth-engine-53fbc8783c16). Since then, the constellation has evolved: GOES-16 and GOES-17 have been replaced by **GOES-18** (West, ~137°W) and **GOES-19** (East, ~75°W).\n",
"\n",
"This post revisits GOES and shows how to replicate CIRA/NOAA's stunning [GeoColor product](https://www.star.nesdis.noaa.gov/goes/conus_band.php?sat=G16&band=GEOCOLOR&length=24) using Earth Engine's Python API. We'll use it to animate 24 hours across the solstice to watch the contiguous United States transition from night to day and back again.\n",
"\n",
"## What is GOES?\n",
"\n",
"GOES (Geostationary Operational Environmental Satellite) is a series of weather satellites operated by NOAA and NASA that provide continuous monitoring of Earth's western hemisphere from a fixed position in geostationary orbit, delivering data for storm tracking, weather forecasting, and climate research. The imaging cadence is between 30 seconds and 10 minutes, depending on the data product. Earth Engine ingests images as soon as they are available, with latency under an hour. Earth Engine offers the Cloud and Moisture Imagery (CMI) and Fire/Hotspot Characterization (FHS) products.\n",
"\n",
"## What is GeoColor?\n",
"\n",
"GeoColor is a composite visualization developed by CIRA (Cooperative Institute for Research in the Atmosphere) that blends true-color daytime imagery with infrared cloud data and nighttime enhancements including city lights and terrain shading. The result is a single, continuous view that works equally well at noon and midnight.\n",
"\n",
"Key components:\n",
"\n",
"- **Daytime**: Solar-corrected true color with synthetic green band\n",
"- **Nighttime**: City lights from VIIRS, terrain shading from ETOPO1, and IR cloud overlay\n",
"- **Transition**: Smooth blending based on solar zenith angle\n",
"\n",
"Let's recreate GeoColor in Earth Engine using an approximation of the [Miller et al., 2020](https://rammb2.cira.colostate.edu/wp-content/uploads/2020/01/jtechd190134.pdf) methods.\n",
"\n",
"## Setup\n",
"\n",
"The examples below run on a Jupyter kernel with Python 3.10+, `earthengine-api` and `geemap` installed. Set `EE_PROJECT_ID` in your environment or edit `PROJECT_ID` below to your Earth Engine project before running."
]
},
{
"cell_type": "code",
"metadata": {
"id": "bySgnqcspzEh"
},
"source": [
"import ee\n",
"import geemap\n",
"from IPython.display import Image, display\n",
"from pathlib import Path\n",
"from urllib.request import urlopen\n",
"import os\n",
"import math\n",
"\n",
"\n",
"def save_image(url: str, filename: str) -> Path:\n",
" \"\"\"Download an EE thumb URL to a local PNG so the post can embed it.\"\"\"\n",
" dest = Path(filename)\n",
" with urlopen(url) as response, open(dest, \"wb\") as fp:\n",
" fp.write(response.read())\n",
" return dest\n",
"\n",
"# Prefer env var; readers can also hardcode directly if preferred.\n",
"PROJECT_ID = os.environ.get(\"EE_PROJECT_ID\", \"YOUR_PROJECT_ID\")\n",
"ee.Authenticate()\n",
"ee.Initialize(project=PROJECT_ID)"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "KyUl--CrpzEi"
},
"source": [
"## Step 1: Configuration and Utilities\n",
"\n",
"We'll start by defining satellite positions and border overlays, then create a utility for applying scale factors from GOES image metadata. Note that we're including GOES 16 and 17, in case you're doing historical analysis."
]
},
{
"cell_type": "code",
"metadata": {
"id": "YQEYCC3BpzEi"
},
"source": [
"# Satellite longitude positions.\n",
"GOES_LONGITUDES = {\n",
" '16': -75, # East (retired)\n",
" '17': -137, # West (retired)\n",
" '18': -137, # West (operational)\n",
" '19': -75 # East (operational)\n",
"}\n",
"\n",
"# Border overlay using global administrative boundaries.\n",
"BORDERS_FC = ee.FeatureCollection('WM/geoLab/geoBoundaries/600/ADM1')\n",
"BORDER_IMAGE = ee.Image().byte().paint(BORDERS_FC, 1, 1).visualize(\n",
" palette=['ffffff'], opacity=0.6\n",
")\n",
"\n",
"def apply_scale_factors(image):\n",
" \"\"\"Apply band-specific scale and offset from image properties.\"\"\"\n",
" bands = image.bandNames()\n",
" props = image.toDictionary()\n",
"\n",
" def scale_band(band_name):\n",
" band_str = ee.String(band_name)\n",
" scale = ee.Number(props.get(band_str.cat('_scale'), 1.0))\n",
" offset = ee.Number(props.get(band_str.cat('_offset'), 0.0))\n",
" return image.select([band_str]).multiply(scale).add(offset).rename([band_str])\n",
"\n",
" scaled = ee.ImageCollection.fromImages(bands.map(scale_band)).toBands()\n",
" return ee.Image(scaled.rename(bands)\n",
" .copyProperties(image, image.propertyNames())\n",
" .set('system:time_start', image.get('system:time_start')))"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "6bQQjqoBpzEi"
},
"source": [
"## Step 2: Geometry Calculations\n",
"\n",
"GeoColor requires two angle calculations: solar zenith (for day/night blending) and satellite zenith (for atmospheric correction)."
]
},
{
"cell_type": "code",
"metadata": {
"id": "zzKSeyDZpzEi"
},
"source": [
"def calculate_sun_zenith(image):\n",
" \"\"\"Calculate cosine of solar zenith angle for each pixel.\"\"\"\n",
" time_ms = image.date().millis()\n",
" year_start = ee.Date.fromYMD(image.date().get('year'), 1, 1).millis()\n",
" days_since_start = ee.Number(time_ms).subtract(year_start).divide(24 * 60 * 60 * 1000)\n",
"\n",
" # Solar declination\n",
" fractional_year = days_since_start.multiply(2 * math.pi / 365.25)\n",
" declination = fractional_year.subtract(1.39).sin().multiply(0.4091)\n",
"\n",
" # Solar time\n",
" seconds_in_day = 86400\n",
" mod_time = ee.Number(time_ms).mod(seconds_in_day * 1000).divide(1000)\n",
" utc_hour = mod_time.divide(3600)\n",
"\n",
" lat_lon = ee.Image.pixelLonLat()\n",
" lat = lat_lon.select('latitude').multiply(math.pi / 180)\n",
" lon = lat_lon.select('longitude')\n",
"\n",
" solar_time = lon.divide(15).add(utc_hour)\n",
" hour_angle = solar_time.subtract(12).multiply(15).multiply(math.pi / 180)\n",
"\n",
" # Cosine of solar zenith angle\n",
" cos_sza = lat.sin().multiply(declination.sin()) \\\n",
" .add(lat.cos().multiply(declination.cos()).multiply(hour_angle.cos()))\n",
"\n",
" return cos_sza\n",
"\n",
"def calculate_sat_zenith_and_air_mass(image, sat_lon):\n",
" \"\"\"Calculate satellite viewing angle and atmospheric path length.\"\"\"\n",
" re = 6378.137 # Earth radius (km)\n",
" h = 42164.16 # Geostationary orbit height (km)\n",
" r_ratio = re / h\n",
"\n",
" lat_lon = ee.Image.pixelLonLat()\n",
" lat = lat_lon.select('latitude').multiply(math.pi / 180)\n",
" lon = lat_lon.select('longitude').multiply(math.pi / 180)\n",
" sat_lon_rad = ee.Number(sat_lon).multiply(math.pi / 180)\n",
"\n",
" lon_diff = lon.subtract(sat_lon_rad)\n",
" cos_beta = lat.cos().multiply(lon_diff.cos())\n",
"\n",
" numerator = cos_beta.subtract(r_ratio)\n",
" denominator = ee.Image(1.0).add(r_ratio**2) \\\n",
" .subtract(ee.Image(2 * r_ratio).multiply(cos_beta)).sqrt()\n",
" cos_sat_zenith = numerator.divide(denominator).clamp(-1, 1)\n",
"\n",
" air_mass = ee.Image(1.0).divide(cos_sat_zenith).clamp(1.0, 6.0)\n",
" sat_zenith_deg = cos_sat_zenith.acos().multiply(180 / math.pi)\n",
"\n",
" return sat_zenith_deg, air_mass"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "BB0rD5OGpzEj"
},
"source": [
"## Step 3: Daytime True Color\n",
"\n",
"The daytime layer uses GOES bands 1-3 (blue, red, vegetation red edge) to create a synthetic green band, then applies atmospheric haze correction and solar-aware gamma stretching."
]
},
{
"cell_type": "code",
"metadata": {
"id": "wppSP3K8pzEj"
},
"source": [
"def get_daytime_layer(image, sat_lon, cos_sun):\n",
" \"\"\"Generate corrected true-color RGB for daytime.\"\"\"\n",
" img = apply_scale_factors(image)\n",
" sat_zenith_deg, air_mass = calculate_sat_zenith_and_air_mass(img, sat_lon)\n",
"\n",
" # Cloud-modulated haze correction.\n",
" ir_temp = img.select('CMI_C13')\n",
" cloud_mod = ir_temp.subtract(233).divide(283 - 233).clamp(0.3, 1.0)\n",
"\n",
" b_haze = air_mass.multiply(0.045).add(0.015).multiply(cloud_mod)\n",
" r_haze = air_mass.multiply(0.022).add(0.005).multiply(cloud_mod)\n",
" v_haze = air_mass.multiply(0.008).add(0.002).multiply(cloud_mod)\n",
"\n",
" b = img.select('CMI_C01').max(0).clamp(0, 1.3).subtract(b_haze).max(0)\n",
" r = img.select('CMI_C02').max(0).clamp(0, 1.3).subtract(r_haze).max(0)\n",
" v = img.select('CMI_C03').max(0).clamp(0, 1.3).subtract(v_haze).max(0)\n",
"\n",
" # Synthetic green band.\n",
" g_syn = r.multiply(0.45).add(b.multiply(0.45)).add(v.multiply(0.10))\n",
" g_hybrid = g_syn.multiply(0.93).add(v.multiply(0.07))\n",
"\n",
" def apply_log_stretch(img):\n",
" clamped = img.clamp(0.01, 1.1)\n",
" logged = clamped.log().divide(math.log(10))\n",
" min_log = math.log10(0.01)\n",
" max_log = math.log10(1.1)\n",
" normalized = logged.subtract(min_log).divide(max_log - min_log)\n",
"\n",
" # Limb correction (darker at edges).\n",
" sza_clamped = sat_zenith_deg.clamp(0, 80)\n",
" gamma_sat = ee.Image(0.85).add(ee.Image(0.15).multiply(sza_clamped.divide(80)))\n",
"\n",
" # Sunset boost (brighter near terminator).\n",
" solar_boost = ee.Image(1.0).subtract(cos_sun.clamp(0.1, 1.0)).multiply(0.25)\n",
" gamma_final = gamma_sat.subtract(solar_boost).max(0.65)\n",
"\n",
" return normalized.pow(gamma_final)\n",
"\n",
" r_final = apply_log_stretch(r)\n",
" g_final = apply_log_stretch(g_hybrid)\n",
" b_final = apply_log_stretch(b)\n",
"\n",
" return ee.Image.cat([r_final, g_final, b_final]).rename(['red', 'green', 'blue'])"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "Ab7rm00ppzEj"
},
"source": [
"## Step 4: Nighttime Enhancement\n",
"\n",
"The nighttime layer combines VIIRS city lights, terrain-shaded elevation, and infrared cloud imagery. This is where GeoColor really shines—literally. Note that the night lights layer is the 2024 median of the VIIRS VCMSLCFG product."
]
},
{
"cell_type": "code",
"metadata": {
"id": "T9neiXzApzEj"
},
"source": [
"# Static nighttime assets.\n",
"CITY_LIGHTS = ee.ImageCollection(\"NOAA/VIIRS/DNB/MONTHLY_V1/VCMSLCFG\") \\\n",
" .filterDate('2024-01-01', '2025-01-01').select('avg_rad') \\\n",
" .median() \\\n",
" .setDefaultProjection('EPSG:4326', None, 1000)\n",
"DEM = ee.Image(\"NOAA/NGDC/ETOPO1\").select('bedrock')\n",
"NIGHT_BG_BASE = ee.Image.constant([0.06, 0.03, 0.13])\n",
"FOG_COLOR = ee.Image.constant([0.55, 0.75, 0.98])\n",
"\n",
"def get_nighttime_layer(image):\n",
" \"\"\"Generate city lights + terrain + IR clouds for nighttime.\"\"\"\n",
" img = apply_scale_factors(image)\n",
"\n",
" # Terrain shading.\n",
" is_land = DEM.gt(0)\n",
" n_e = DEM.max(0).divide(10000).clamp(0, 1)\n",
" terrain_tint = NIGHT_BG_BASE.multiply(ee.Image(1).subtract(n_e)).add(n_e)\n",
" shading = ee.Terrain.hillshade(DEM.multiply(5), 315, 35).divide(255)\n",
" shading_soft = shading.multiply(0.3).add(0.7)\n",
" nightscape = ee.Image.constant([0,0,0]).where(is_land, terrain_tint.multiply(shading_soft))\n",
"\n",
" # City lights (log-scaled).\n",
" lights_log = CITY_LIGHTS.where(CITY_LIGHTS.lte(0), 1e-10).log10()\n",
" lights_norm = lights_log.subtract(-0.5).divide(2.5).clamp(0, 1)\n",
" lights_dim = lights_norm.multiply(0.85)\n",
" lights_mask = lights_dim.gt(0.25)\n",
" lights_rgb = ee.Image.cat([\n",
" lights_dim.pow(0.75), # Red\n",
" lights_dim.pow(1.25), # Green\n",
" lights_dim.pow(2.00) # Blue (cooler tint)\n",
" ])\n",
"\n",
" # Infrared clouds and fog detection.\n",
" ir = img.select('CMI_C13')\n",
" ir_norm = ir.subtract(242).multiply(-1).divide(62).clamp(0, 1)\n",
" swir = img.select('CMI_C07')\n",
" diff = ir.subtract(swir)\n",
" fog_opacity = diff.subtract(1.0).divide(3.0).clamp(0, 1).multiply(ir.gt(240))\n",
"\n",
" # Layer composition.\n",
" canvas = nightscape.where(lights_mask, lights_rgb)\n",
" canvas = canvas.blend(FOG_COLOR.updateMask(fog_opacity))\n",
" ir_gray = ee.Image.cat([ir_norm, ir_norm, ir_norm])\n",
" canvas = canvas.blend(ir_gray.updateMask(ir_norm))\n",
"\n",
" return canvas.rename(['red', 'green', 'blue'])"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "vsdUky5hpzEj"
},
"source": [
"## Step 5: Blending and Processing\n",
"\n",
"Now we combine daytime and nighttime layers with a smooth transition based on solar angle, then overlay borders."
]
},
{
"cell_type": "code",
"metadata": {
"id": "OsRf42VxpzEj"
},
"source": [
"def process_single_image(image, sat_lon):\n",
" \"\"\"Generate GeoColor composite with borders.\"\"\"\n",
" # Calculate solar angle.\n",
" cos_sun = calculate_sun_zenith(image)\n",
"\n",
" # Generate base layers.\n",
" day_layer = get_daytime_layer(image, sat_lon, cos_sun)\n",
" night_layer = get_nighttime_layer(image)\n",
"\n",
" # Blending.\n",
" day_weight = cos_sun.unitScale(0.1, 0.3).clamp(0, 1).pow(1.5)\n",
"\n",
" geocolor = day_layer.multiply(day_weight).add(\n",
" night_layer.multiply(ee.Image(1).subtract(day_weight))\n",
" )\n",
"\n",
" # Add border overlay.\n",
" final_vis = geocolor.visualize(min=0, max=1)\n",
" final_composite = final_vis.blend(BORDER_IMAGE)\n",
"\n",
" return final_composite.copyProperties(image, ['system:time_start'])\n",
"\n",
"def get_goes_collection(collection_id, start_date, end_date, step=1):\n",
" \"\"\"\n",
" Generate processed GeoColor collection with optional frame skipping.\n",
"\n",
" Args:\n",
" collection_id: e.g., \"NOAA/GOES/19/MCMIPF\"\n",
" start_date: ISO string\n",
" end_date: ISO string\n",
" step: Frame skip interval (1=every frame, 2=every 2nd frame, etc.)\n",
" \"\"\"\n",
" # Parse satellite ID\n",
" sat_id = collection_id.split('/')[2]\n",
" sat_lon = GOES_LONGITUDES.get(sat_id)\n",
"\n",
" if sat_lon is None:\n",
" raise ValueError(f\"Unknown GOES ID '{sat_id}'\")\n",
"\n",
" # Load and optionally subsample collection.\n",
" raw_col = ee.ImageCollection(collection_id).filterDate(start_date, end_date)\n",
"\n",
" if step > 1:\n",
" col_list = raw_col.toList(raw_col.size())\n",
" sliced_list = col_list.slice(0, None, step)\n",
" raw_col = ee.ImageCollection.fromImages(sliced_list)\n",
"\n",
" return raw_col.map(lambda img: process_single_image(img, sat_lon))"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "t8UUROPSpzEj"
},
"source": [
"## A Midday Example\n",
"\n",
"Let's render a single GeoColor frame from midday on the solstice to see the result before animating."
]
},
{
"cell_type": "code",
"metadata": {
"id": "e7eojWg2pzEj"
},
"source": [
"# Get a single image.\n",
"single_col = get_goes_collection(\n",
" \"NOAA/GOES/19/MCMIPF\",\n",
" '2025-12-21T16:00',\n",
" '2025-12-21T16:15',\n",
" step=1\n",
")\n",
"\n",
"single_image = single_col.first()\n",
"\n",
"# Define region (CONUS).\n",
"roi = ee.Geometry.Rectangle([-125.0, 24.0, -66.0, 50.0], None, False)\n",
"\n",
"url = single_image.getThumbURL({\n",
" 'region': roi,\n",
" 'dimensions': 800,\n",
" 'format': 'png',\n",
" 'crs': 'EPSG:5070'\n",
"})\n",
"\n",
"output_image = 'goes_midday.png'\n",
"save_image(url, output_image)"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"metadata": {
"id": "BfJsX0VEpzEk"
},
"source": [
"# Display the midday image.\n",
"Image('goes_midday.png')"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "Fh0iQo99pzEk"
},
"source": [
"The synthetic green band creates natural-looking vegetation, atmospheric correction reduces haze, and state borders provide geographic context. In the upper left you can see the nightime begin to fade to day.\n",
"\n",
"## Creating the Solstice Animation\n",
"\n",
"Now for the main event: 24 hours across the winter solstice, watching illumination across CONUS at the annual northern hemisphere minimum. Here we use [geemap](https://geemap.org/) to download the GIF and add a time stamp to each frame and a progress bar to the bottom. Note that `geemap.download_ee_video` calls `ee.ImageCollection.getVideoThumbURL` which has a limit on pixels/download size that can be. If you require larger downloads, see the export to Google Cloud Storage section below or manage downloading and rendering frames with your own system."
]
},
{
"cell_type": "code",
"metadata": {
"id": "RDubJcGKpzEk"
},
"source": [
"# Define animation parameters.\n",
"roi = ee.Geometry.Rectangle([-125.0, 24.0, -66.0, 50.0], None, False) # CONUS\n",
"start_date = '2025-12-21T04:30' # Pre-dawn\n",
"end_date = '2025-12-22T04:30' # Next morning\n",
"skip_step = 2 # Every 20 minutes (GOES-19 full disk = 10 min cadence)\n",
"\n",
"# Generate collection.\n",
"goes_col = get_goes_collection(\n",
" \"NOAA/GOES/19/MCMIPF\",\n",
" start_date,\n",
" end_date,\n",
" step=skip_step\n",
")\n",
"\n",
"# Add timestamp labels.\n",
"def add_readable_time(img):\n",
" date = ee.Date(img.get('system:time_start'))\n",
" return img.set('label', date.format('yyyy-MM-dd HH:mm').cat(' UTC'))\n",
"\n",
"goes_col = goes_col.map(add_readable_time)\n",
"\n",
"# Download and create GIF (disabled during site render)\n",
"output_gif = 'goes_solstice_animation.gif'\n",
"\n",
"video_args = {\n",
" 'dimensions': 800,\n",
" 'region': roi,\n",
" 'framesPerSecond': 1,\n",
" 'crs': 'EPSG:5070'\n",
"}\n",
"\n",
"geemap.download_ee_video(goes_col, video_args, output_gif)\n",
"\n",
"geemap.add_text_to_gif(\n",
" output_gif,\n",
" output_gif,\n",
" xy=('3%', '5%'),\n",
" text_sequence=goes_col.aggregate_array('label').getInfo(),\n",
" font_size=20,\n",
" font_color='white',\n",
" duration=100\n",
")"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"metadata": {
"id": "olfR4mfwpzEk"
},
"source": [
"# Display the animation.\n",
"display(Image('/content/goes_solstice_animation.gif'))"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "6gYFpEvZpzEk"
},
"source": [
"Watch as morning light sweeps westward across the Atlantic, cities fade into daylight, the sun reaches its low arc across the southern sky, and by late afternoon the eastern seaboard is already dark. City lights bloom and western cities remain visible until the Pacific coast finally succumbs to night.\n",
"\n",
"The extended darkness showcases GeoColor's unique nighttime representation, and the rapid transitions at the solstice create dramatic temporal dynamics.\n",
"\n",
"The code above works identically for GOES-18 by changing the collection ID—just swap `19` for `18` to see the western hemisphere view.\n",
"\n",
"## Export Options\n",
"\n",
"For high-resolution exports, use Earth Engine's batch export to Google Cloud Storage:"
]
},
{
"cell_type": "code",
"metadata": {
"id": "HkQLVFqGpzEk"
},
"source": [
"task = ee.batch.Export.video.toCloudStorage(\n",
" collection=goes_col,\n",
" description='goes_geocolor_solstice',\n",
" bucket= 'your-bucket-name', # <-- Replace with your GCS bucket name\n",
" fileNamePrefix='goes_geocolor_solstice', # <-- Edit as needed\n",
" dimensions=1280,\n",
" framesPerSecond=12,\n",
" region=roi,\n",
" crs='EPSG:5070'\n",
")\n",
"task.start()"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "Waohd53UpzEk"
},
"source": [
"## Why This Matters\n",
"\n",
"GeoColor transforms GOES from a meteorological workhorse into a visual storytelling platform. The algorithm's thoughtful design—terrain-aware nighttime rendering, solar-corrected color balance, seamless transitions—creates imagery that's both scientifically accurate and aesthetically compelling.\n",
"\n",
"With GOES-18 and GOES-19 now operational and streaming into Earth Engine, you can:\n",
"\n",
"- Monitor severe weather in near real-time\n",
"- Create custom visualizations for public outreach\n",
"- Analyze diurnal patterns in cloud cover, fire behavior, or coastal fog\n",
"- Build time-aware composites that respect day/night boundaries\n",
"\n",
"The winter solstice offers a perfect testing ground: maximum nighttime coverage, dramatic lighting transitions, and a reminder that Earth Engine's data catalog extends far beyond Landsat and Sentinel.\n",
"\n",
"## Try It\n",
"\n",
"Explore the [GOES-19 Full Disk collection](https://developers.google.com/earth-engine/datasets/catalog/NOAA_GOES_19_MCMIPF) in the Earth Engine Data Catalog. Modify the date range, adjust the blending parameters, or try the CONUS collection (`NOAA/GOES/19/MCMIPC`) for 5-minute updates over North America."
]
}
],
"metadata": {
"kernelspec": {
"name": "python3",
"language": "python",
"display_name": "Python 3 (ipykernel)",
"path": "/usr/local/google/home/braaten/y/envs/ground-truth-env/share/jupyter/kernels/python3"
},
"colab": {
"provenance": [],
"include_colab_link": true
}
},
"nbformat": 4,
"nbformat_minor": 0
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment