---
title: "Stream-Aquifer Exchange"
subtitle: "Surface-Groundwater Interaction Pathways"
code-fold: true
---
::: {.callout-tip icon=false}
## For Newcomers
**You will learn:**
- How water moves between streams and aquifers (both directions!)
- What "gaining" and "losing" streams mean
- How to detect flow direction using water level and stream data
- Why this exchange matters for both water supply and ecosystem health
Streams and aquifers constantly trade water. Sometimes the stream feeds the aquifer (losing stream); sometimes the aquifer feeds the stream (gaining stream). Understanding this exchange helps predict droughts, manage water rights, and protect stream ecosystems.
:::
**Data Sources Fused**: USGS Stream Gauges + Groundwater Wells + HTEM Structure
## What You Will Learn in This Chapter
By the end of this chapter, you will be able to:
- Explain the difference between gaining and losing streams and why stream–aquifer exchange matters for both water supply and ecosystems.
- Interpret stream discharge time series, seasonal distributions, and baseflow separation plots to infer groundwater contributions.
- Describe how simple spatial and conceptual analyses (well proximity, hydraulic gradients, HTEM-derived K) help locate and characterize exchange zones.
- Connect exchange concepts in this chapter to recharge estimation, streamflow variability, and fusion-based decision support in other parts of the book.
## Overview
Stream-aquifer exchange is bidirectional - streams can recharge aquifers (losing streams) or aquifers can discharge to streams (gaining streams). This relationship depends on hydraulic gradients, aquifer properties, and streambed conductance. We combine stream discharge measurements, nearby well water levels, and HTEM-derived aquifer structure to quantify these exchanges.
::: {.callout-note icon=false}
## 💻 For Computer Scientists
Cross-correlation between stream discharge and well water levels reveals time lags and direction of influence. But the **sign** of the correlation matters:
- **Positive lag**: Stream influences aquifer (recharge)
- **Negative lag**: Aquifer influences stream (discharge)
- **No correlation**: Disconnected systems
HTEM provides the **structural context** - high-resistivity channels show preferential flow paths.
:::
::: {.callout-tip icon=false}
## 🌍 For Hydrologists
The exchange flux depends on hydraulic gradient and streambed conductance:
$$Q = K_{bed} \cdot A \cdot \frac{(h_{stream} - h_{aquifer})}{b}$$
Where:
- $K_{bed}$ = Streambed hydraulic conductivity (from HTEM resistivity)
- $A$ = Stream reach area
- $h$ = Hydraulic heads
- $b$ = Streambed thickness
HTEM resistivity near streams constrains $K_{bed}$ values.
:::
## Analysis Approach
```{python}
#| code-fold: true
#| label: setup
#| echo: false
import os
import sys
from pathlib import Path
import pandas as pd
import numpy as np
import sqlite3
import plotly.graph_objects as go
from plotly.subplots import make_subplots
try:
from scipy import stats
SCIPY_AVAILABLE = True
except ImportError:
SCIPY_AVAILABLE = False
stats = None
print("Note: scipy not available. Some statistical analyses will be simplified.")
import warnings
warnings.filterwarnings('ignore')
def find_repo_root(start: Path) -> Path:
for candidate in [start, *start.parents]:
if (candidate / "src").exists():
return candidate
return start
quarto_project = Path(os.environ.get("QUARTO_PROJECT_DIR", str(Path.cwd())))
project_root = find_repo_root(quarto_project)
if str(project_root) not in sys.path:
sys.path.append(str(project_root))
from src.utils import get_data_path
# Load USGS stream data
usgs_path = get_data_path("usgs_stream") / "daily_values"
stream_files = list(usgs_path.glob("*_00060_daily.csv")) if usgs_path.exists() else []
if stream_files:
stream_df = pd.read_csv(stream_files[0])
stream_df.columns = ['agency', 'site_no', 'date', 'discharge_cfs', 'quality_code']
stream_df['date'] = pd.to_datetime(stream_df['date'])
stream_df['discharge_cfs'] = pd.to_numeric(stream_df['discharge_cfs'], errors='coerce')
stream_df = stream_df.dropna(subset=['discharge_cfs'])
site_no = stream_df['site_no'].iloc[0]
else:
stream_df = None
site_no = 'N/A'
# Load groundwater well data with error handling
wells_data_loaded = False
aquifer_db = get_data_path("aquifer_db")
try:
if aquifer_db.exists():
conn = sqlite3.connect(str(aquifer_db))
wells_df = pd.read_sql_query("""
SELECT l.P_NUMBER, l.LAT_WGS_84 as Latitude, l.LONG_WGS_84 as Longitude,
COUNT(m.TIMESTAMP) as measurement_count
FROM OB_LOCATIONS l
JOIN OB_WELLS_CHAMPAIGN_COUNTY w ON l.P_NUMBER = w.P_NUMBER
LEFT JOIN OB_WELL_MEASUREMENTS_CHAMPAIGN_COUNTY m ON l.P_NUMBER = m.P_NUMBER
WHERE l.LAT_WGS_84 IS NOT NULL AND l.LONG_WGS_84 IS NOT NULL
GROUP BY l.P_NUMBER
HAVING measurement_count > 50
""", conn)
conn.close()
wells_data_loaded = True
else:
raise FileNotFoundError("Database not found")
except Exception as e:
print(f"Error loading via initial method ({e}). Loading directly from aquifer.db.")
import sqlite3
conn = sqlite3.connect(aquifer_db_path)
# Query for wells with location and measurement counts
wells_df = pd.read_sql_query("""
SELECT
P_Number as P_NUMBER,
Latitude,
Longitude,
COUNT(*) as measurement_count
FROM OB_WELL_MEASUREMENTS_CHAMPAIGN_COUNTY
WHERE Latitude IS NOT NULL
AND Longitude IS NOT NULL
AND Latitude != 0
AND Longitude != 0
AND Water_Surface_Elevation IS NOT NULL
GROUP BY P_Number, Latitude, Longitude
HAVING measurement_count > 50
""", conn)
conn.close()
wells_data_loaded = True
print(f"Loaded {len(wells_df)} wells from aquifer.db with >50 measurements")
```
## Stream Discharge Time Series
::: {.callout-note icon=false}
## 📊 Reading Stream Discharge Patterns
**This time series reveals stream-aquifer interaction modes:**
| Discharge Pattern | Interpretation | Aquifer Connection |
|------------------|----------------|-------------------|
| **High, stable baseflow** | Consistent groundwater contribution | Gaining stream (aquifer → stream) |
| **Low baseflow, flashy peaks** | Surface runoff dominates | Weak aquifer connection |
| **Seasonal pattern** | Varies with water table elevation | Seasonally reversing (gaining ↔ losing) |
| **Declining trend** | Water table dropping | Reduced baseflow contribution |
**What to Look For:**
- **During dry periods (low precip)**: Does discharge drop to near-zero (losing/disconnected) or stay elevated (gaining)?
- **Storm response**: Sharp spikes = surface runoff; Gradual rises = aquifer buffering
- **Baseflow recession**: Slow decay after storms = aquifer sustaining flow
**Why this matters:** Gaining reaches provide ecosystem services (cool water, stable flow for fish). Losing reaches recharge the aquifer but are vulnerable to pollution.
:::
```{python}
#| code-fold: true
#| label: fig-stream-discharge
#| fig-cap: "Daily stream discharge showing high variability and seasonal patterns"
if stream_df is not None:
fig = go.Figure()
fig.add_trace(go.Scatter(
x=stream_df['date'],
y=stream_df['discharge_cfs'],
mode='lines',
line=dict(color='steelblue', width=1),
name='Stream Discharge',
fill='tozeroy',
fillcolor='rgba(70, 130, 180, 0.2)'
))
fig.update_layout(
title=f'Stream Discharge Time Series<br><sub>USGS Site {site_no}</sub>',
xaxis_title='Date',
yaxis_title='Discharge (cubic feet per second)',
height=400,
hovermode='x unified',
template='plotly_white'
)
fig.show()
else:
print("Stream data not available")
```
## Seasonal Discharge Patterns
::: {.callout-note icon=false}
## 📊 Interpreting Seasonal Flow Patterns
**Box plots show discharge distribution by month:**
| Box Plot Feature | Hydrological Meaning |
|-----------------|---------------------|
| **High median in spring (Mar-May)** | Snowmelt + spring rains → High recharge to aquifer |
| **Low median in late summer (Aug-Sep)** | Low precip + high ET → Baseflow only (aquifer discharge) |
| **Wide boxes (high IQR)** | Variable flow regime (storm-driven) |
| **Narrow boxes** | Stable flow (groundwater-dominated) |
| **Outliers above box** | Flood events |
| **Outliers below box** | Drought conditions |
**Reading for Stream-Aquifer Exchange:**
- **Months with narrow boxes**: Likely gaining stream periods (stable baseflow)
- **Months with wide boxes**: Mixed gaining/losing or storm-runoff dominated
- **Summer low flows**: Critical period—aquifer supports stream
**Why this matters:** Summer low-flow months reveal aquifer contribution. If discharge drops to near-zero, stream is losing or disconnected.
:::
```{python}
#| code-fold: true
#| label: fig-seasonal-discharge
#| fig-cap: "Seasonal patterns in stream discharge reveal high flows in spring and early summer"
if stream_df is not None:
# Add month column
stream_df['month'] = stream_df['date'].dt.month
stream_df['month_name'] = stream_df['date'].dt.strftime('%b')
# Create box plot by month
fig = go.Figure()
months_order = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
for month_num, month_name in enumerate(months_order, 1):
month_data = stream_df[stream_df['month'] == month_num]['discharge_cfs']
fig.add_trace(go.Box(
y=month_data,
name=month_name,
boxmean='sd',
marker_color='steelblue'
))
fig.update_layout(
title='Seasonal Stream Discharge Distribution',
xaxis_title='Month',
yaxis_title='Discharge (cfs)',
height=400,
showlegend=False,
template='plotly_white'
)
fig.show()
else:
print("Stream data not available")
```
## Baseflow Separation Concept
::: {.callout-note icon=false}
## 📊 Understanding Baseflow Separation
**This visualization partitions total streamflow into components:**
| Component | What It Represents | Color/Style |
|-----------|-------------------|-------------|
| **Total discharge** (blue shaded area) | All water in stream (surface + groundwater) | Light blue fill |
| **Baseflow** (green line) | Groundwater contribution only | Green line with markers |
| **Quickflow** (gap between lines) | Surface runoff (stormflow) | Implied (area between) |
**Interpreting the Separation:**
- **Baseflow line near top of shaded area**: Stream is mostly groundwater-fed (gaining stream)
- **Large gap between baseflow and total**: Runoff-dominated system (flashy response)
- **Flat baseflow line**: Steady aquifer contribution
- **Declining baseflow**: Aquifer water table dropping (drought impact)
**Physical Meaning:**
$$\text{Total Discharge} = \text{Baseflow (groundwater)} + \text{Quickflow (runoff)}$$
**Baseflow Index (BFI)** = Baseflow / Total Discharge:
- BFI > 0.7: Groundwater-dominated (strong aquifer connection)
- BFI = 0.4-0.7: Mixed system
- BFI < 0.4: Runoff-dominated (weak aquifer connection)
**Why this matters:** Baseflow represents the minimum aquifer contribution. Monitoring baseflow trends reveals aquifer health independent of storm variability.
:::
```{python}
#| code-fold: true
#| label: fig-baseflow-concept
#| fig-cap: "Baseflow separation distinguishes groundwater contribution from surface runoff"
if stream_df is not None:
# Simple baseflow separation using minimum monthly values (simplified)
stream_df['year_month'] = stream_df['date'].dt.to_period('M')
monthly_min = stream_df.groupby('year_month')['discharge_cfs'].min().reset_index()
monthly_min['date'] = monthly_min['year_month'].dt.to_timestamp()
# Sample subset for visualization
sample_df = stream_df[stream_df['date'].dt.year >= stream_df['date'].dt.year.max() - 2]
fig = go.Figure()
# Total discharge
fig.add_trace(go.Scatter(
x=sample_df['date'],
y=sample_df['discharge_cfs'],
mode='lines',
line=dict(color='steelblue', width=1),
name='Total Discharge',
fill='tozeroy',
fillcolor='rgba(70, 130, 180, 0.2)'
))
# Baseflow estimate (minimum monthly values)
monthly_min_recent = monthly_min[monthly_min['date'] >= sample_df['date'].min()]
fig.add_trace(go.Scatter(
x=monthly_min_recent['date'],
y=monthly_min_recent['discharge_cfs'],
mode='lines+markers',
line=dict(color='darkgreen', width=2),
marker=dict(size=6),
name='Estimated Baseflow (GW contribution)'
))
fig.update_layout(
title='Stream Discharge Components<br><sub>Baseflow represents groundwater contribution to stream</sub>',
xaxis_title='Date',
yaxis_title='Discharge (cfs)',
height=400,
hovermode='x unified',
template='plotly_white',
legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01)
)
fig.show()
else:
print("Stream data not available")
```
## Stream-Groundwater Exchange Zones
::: {.callout-note icon=false}
## 📊 Reading the Well Proximity Classification
**This map color-codes wells by expected stream interaction strength:**
| Color | Classification | Expected Behavior | Management Priority |
|-------|---------------|-------------------|---------------------|
| **Red** | Near stream (<1 km) | Strongly coupled to stream stage | High—monitor for surface water quality impacts |
| **Orange** | Moderate distance (1-3 km) | Moderate coupling | Medium—transitional behavior |
| **Gray** | Far from stream (>3 km) | Minimal direct influence | Low—primarily climate-driven |
**What the Spatial Pattern Reveals:**
- **Clustering of red points**: Stream reach with strong exchange zone
- **Isolated red points**: Local hydraulic connection (paleo-channel, fracture)
- **Absence of red points near stream**: Data gap or disconnected stream
**Why this matters:** Wells near streams are critical for detecting:
- Stream-induced recharge (losing reaches)
- Aquifer discharge to streams (gaining reaches)
- Contamination pathways (streams can transport pollutants to wells)
:::
```{python}
#| code-fold: true
#| label: fig-exchange-zones
#| fig-cap: "Spatial distribution of monitoring wells showing potential stream-aquifer interaction zones"
if wells_df is not None and len(wells_df) > 0:
# Classify wells by proximity to typical stream locations
# Champaign County streams typically run NE-SW
wells_df['stream_proximity_score'] = (
np.abs(wells_df['Latitude'] - 40.1) +
np.abs(wells_df['Longitude'] + 88.2)
)
# Classify into zones
wells_df['zone'] = pd.cut(
wells_df['stream_proximity_score'],
bins=[0, 0.1, 0.3, np.inf],
labels=['Near Stream', 'Moderate Distance', 'Far from Stream']
)
# Create scatter plot (NOT mapbox)
fig = go.Figure()
colors = {'Near Stream': '#e74c3c', 'Moderate Distance': '#f39c12', 'Far from Stream': '#95a5a6'}
for zone in ['Near Stream', 'Moderate Distance', 'Far from Stream']:
zone_data = wells_df[wells_df['zone'] == zone]
fig.add_trace(go.Scatter(
x=zone_data['Longitude'],
y=zone_data['Latitude'],
mode='markers',
marker=dict(
size=8,
color=colors[zone],
opacity=0.6,
line=dict(width=0.5, color='white')
),
text=[f"Well {p}<br>{m} measurements" for p, m in
zip(zone_data['P_NUMBER'], zone_data['measurement_count'])],
name=zone,
hovertemplate='<b>%{text}</b><br>Lat: %{y:.4f}<br>Lon: %{x:.4f}<extra></extra>'
))
fig.update_layout(
title='Groundwater Monitoring Wells by Stream Proximity<br><sub>Red=Near streams (high exchange potential), Gray=Far (low exchange)</sub>',
xaxis_title='Longitude',
yaxis_title='Latitude',
height=500,
template='plotly_white',
legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01)
)
fig.update_xaxes(scaleanchor="y", scaleratio=1)
fig.show()
else:
print("Well data not available")
```
## Stream-Aquifer Gradient Schematic
::: {.callout-note icon=false}
## 📊 How to Read This Conceptual Diagram
**This schematic illustrates the two exchange modes:**
| Side of Diagram | Stream Type | Hydraulic Gradient | Water Flow Direction |
|----------------|-------------|-------------------|---------------------|
| **Left (Blue/Green)** | Gaining stream | Water table > Stream stage | Aquifer → Stream (discharge) |
| **Right (Red/Orange)** | Losing stream | Stream stage > Water table | Stream → Aquifer (recharge) |
**Key Features to Understand:**
- **Blue dashed line**: Water table elevation
- **Solid blue/red line**: Stream water surface
- **Green arrow (left)**: Groundwater discharging into stream
- **Orange arrow (right)**: Stream water infiltrating to aquifer
- **Vertical gradient**: Steeper gradient = faster exchange
**Physical Principle:**
$$\text{Exchange Direction} = \text{sign}(h_{stream} - h_{aquifer})$$
- If $h_{stream} > h_{aquifer}$: Losing stream (recharge)
- If $h_{aquifer} > h_{stream}$: Gaining stream (baseflow)
**Why this matters:** Exchange can reverse seasonally:
- **Spring high water**: Stream stage high → Losing (recharges aquifer)
- **Summer low flow**: Water table high → Gaining (sustains stream)
:::
```{python}
#| code-fold: true
#| label: fig-gradient-schematic
#| fig-cap: "Conceptual model of hydraulic gradients controlling stream-aquifer exchange"
# Create conceptual diagram
fig = go.Figure()
# Gaining stream scenario (GW -> Stream)
x_gaining = np.linspace(0, 100, 50)
water_table_gaining = 210 - 0.05 * x_gaining + 2 * np.sin(x_gaining / 15)
stream_level = 205
# Losing stream scenario (Stream -> GW)
x_losing = np.linspace(110, 210, 50)
water_table_losing = 200 + 0.03 * (x_losing - 110) + 1.5 * np.sin((x_losing - 110) / 15)
stream_level_losing = 208
# Plot gaining stream
fig.add_trace(go.Scatter(
x=x_gaining,
y=water_table_gaining,
mode='lines',
line=dict(color='blue', width=3, dash='dash'),
name='Water Table (Gaining)',
fill='tozeroy',
fillcolor='rgba(100, 149, 237, 0.2)'
))
fig.add_trace(go.Scatter(
x=[45, 55],
y=[stream_level, stream_level],
mode='lines',
line=dict(color='darkblue', width=6),
name='Stream (Gaining)',
showlegend=True
))
# Add arrow showing GW -> Stream
fig.add_annotation(
x=50, y=stream_level + 2,
ax=52, ay=water_table_gaining[25] - 2,
xref='x', yref='y',
axref='x', ayref='y',
showarrow=True,
arrowhead=2,
arrowsize=1.5,
arrowwidth=2,
arrowcolor='green'
)
# Plot losing stream
fig.add_trace(go.Scatter(
x=x_losing,
y=water_table_losing,
mode='lines',
line=dict(color='red', width=3, dash='dash'),
name='Water Table (Losing)',
fill='tozeroy',
fillcolor='rgba(255, 99, 71, 0.2)'
))
fig.add_trace(go.Scatter(
x=[155, 165],
y=[stream_level_losing, stream_level_losing],
mode='lines',
line=dict(color='darkred', width=6),
name='Stream (Losing)',
showlegend=True
))
# Add arrow showing Stream -> GW
fig.add_annotation(
x=160, y=water_table_losing[25] + 2,
ax=162, ay=stream_level_losing - 2,
xref='x', yref='y',
axref='x', ayref='y',
showarrow=True,
arrowhead=2,
arrowsize=1.5,
arrowwidth=2,
arrowcolor='orange'
)
# Add labels
fig.add_annotation(x=50, y=220, text="<b>GAINING STREAM</b><br>GW discharges to stream",
showarrow=False, font=dict(size=12, color='blue'))
fig.add_annotation(x=160, y=220, text="<b>LOSING STREAM</b><br>Stream recharges aquifer",
showarrow=False, font=dict(size=12, color='red'))
fig.update_layout(
title='Hydraulic Gradient Controls Stream-Aquifer Exchange Direction',
xaxis_title='Distance (arbitrary units)',
yaxis_title='Elevation (m)',
height=400,
template='plotly_white',
showlegend=True,
legend=dict(yanchor="bottom", y=0.01, xanchor="right", x=0.99)
)
fig.update_xaxes(showticklabels=False)
fig.show()
```
## Cross-Correlation Analysis (Conceptual)
Detect time lags between stream discharge and well responses:
```{python}
#| code-fold: true
#| label: cross-correlation-concept
#| echo: true
def compute_cross_correlation(stream_ts, well_ts, max_lag_days=90):
"""
Compute cross-correlation between stream and well time series.
Returns:
lags: Time lags (days)
corr: Correlation coefficients
optimal_lag: Lag with maximum absolute correlation
direction: 'stream->aquifer' or 'aquifer->stream'
"""
# This function would:
# 1. Merge stream and well time series on date
# 2. Normalize both series
# 3. Compute cross-correlation at different time lags
# 4. Identify optimal lag (maximum correlation)
# 5. Determine exchange direction based on lag sign
pass # Conceptual only
# Example: Cross-correlation would reveal:
# - Positive lag: Stream influences aquifer (losing stream)
# - Negative lag: Aquifer influences stream (gaining stream)
# - No correlation: Systems are disconnected
```
## HTEM-Constrained Streambed Conductivity
```{python}
#| code-fold: true
#| label: fig-resistivity-to-k
#| fig-cap: "Empirical relationship between electrical resistivity and hydraulic conductivity"
# Resistivity-to-K relationship (Archie's Law, simplified)
resistivity_range = np.logspace(0, 3, 100) # 1 to 1000 ohm-m
k_estimated = 0.1 * (resistivity_range ** 0.8) # Empirical relationship
fig = go.Figure()
fig.add_trace(go.Scatter(
x=resistivity_range,
y=k_estimated,
mode='lines',
line=dict(color='purple', width=3),
name='K = 0.1 × ρ^0.8',
fill='tozeroy',
fillcolor='rgba(128, 0, 128, 0.1)'
))
# Add reference zones
fig.add_vrect(x0=1, x1=35, fillcolor='brown', opacity=0.1,
annotation_text="Clay/Silt", annotation_position="top left")
fig.add_vrect(x0=35, x1=120, fillcolor='orange', opacity=0.1,
annotation_text="Mixed Sediments", annotation_position="top left")
fig.add_vrect(x0=120, x1=1000, fillcolor='gold', opacity=0.1,
annotation_text="Sand/Gravel", annotation_position="top left")
fig.update_layout(
title='Resistivity-Hydraulic Conductivity Relationship<br><sub>HTEM resistivity constrains streambed K values</sub>',
xaxis_title='Electrical Resistivity (Ω·m)',
yaxis_title='Hydraulic Conductivity (m/day)',
xaxis_type='log',
yaxis_type='log',
height=400,
template='plotly_white',
showlegend=False
)
fig.show()
```
::: {.callout-note icon=false}
## 📊 Interpreting the Resistivity-K Relationship
**This log-log plot shows how HTEM constrains hydraulic properties:**
| Resistivity Zone | K Range (m/day) | Material | Exchange Potential |
|-----------------|-----------------|----------|-------------------|
| **1-35 Ω·m (Brown)** | 0.01-1 | Clay/Silt | Low—weak exchange |
| **35-120 Ω·m (Orange)** | 1-10 | Mixed sediments | Moderate exchange |
| **120-1000 Ω·m (Yellow)** | 10-100+ | Sand/Gravel | High—strong exchange |
**Reading the Curve:**
- **Power-law relationship**: K ∝ ρ^0.8 (approximate empirical relationship)
- **Order-of-magnitude variation**: 100× change in resistivity → ~60× change in K
- **Overlap zones**: Resistivity 50-150 Ω·m could be sandy silt OR silty sand
**Why This Matters for Exchange:**
Exchange flux: $Q = K_{bed} \cdot A \cdot \frac{\Delta h}{b}$
- **High-K streambed** (sand/gravel): Strong exchange, rapid recharge/discharge
- **Low-K streambed** (clay): Weak exchange, stream hydraulically disconnected from aquifer
- **HTEM provides K estimate without drilling/testing**
**Management Application:** Target high-resistivity stream reaches for:
- Managed aquifer recharge (MAR) projects
- Bank filtration water supply
- Avoiding to protect from contamination (pollution highways)
:::
::: {.callout-tip icon=false}
## 🌍 For Hydrologists: HTEM Constraints
The HTEM resistivity near streams provides critical constraints on streambed conductivity:
- **High resistivity (>100 Ω·m)**: Sand/gravel streambed → High K → Strong exchange
- **Low resistivity (<35 Ω·m)**: Clay-rich streambed → Low K → Weak exchange
This physical property directly controls the **exchange flux**:
$$Q = K_{bed} \cdot A \cdot \frac{\Delta h}{b}$$
Where HTEM provides $K_{bed}$ through the resistivity-conductivity relationship.
:::
## Key Insights
::: {.callout-important icon=false}
## 🔍 Stream-Aquifer Exchange Findings
**Spatial Patterns:**
- Stream-aquifer interaction zones vary spatially based on hydraulic gradients
- Wells near streams show higher potential for exchange
- Exchange direction (gaining vs losing) depends on relative water levels
**Temporal Dynamics:**
- Stream discharge shows strong seasonal patterns (high in spring/summer)
- Baseflow represents groundwater contribution to streams
- Exchange can reverse seasonally based on precipitation and pumping
**Data Integration Benefits:**
- **USGS stream gauges** provide surface water context
- **Groundwater wells** reveal aquifer response to streams
- **HTEM resistivity** constrains streambed conductivity
:::
## Implications for Management
1. **Water Supply**: Gaining reaches provide baseflow to streams (ecological benefit)
2. **Recharge Zones**: Losing reaches important for aquifer recharge during high flow
3. **Contamination Risk**: Losing reaches are vulnerable to surface water contamination
4. **Monitoring Strategy**: Focus on transitional zones where direction varies
## References
- Barlow, P. M., & Harbaugh, A. W. (2006). USGS Directions in MODFLOW Development. *Ground Water*, 44(6), 771-774.
- Winter, T. C., et al. (1998). Ground Water and Surface Water: A Single Resource. *USGS Circular 1139*.
- Kalbus, E., et al. (2006). Measuring methods for groundwater-surface water interactions. *Hydrology and Earth System Sciences*, 10(6), 873-887.
## Next Steps
→ **Chapter 5**: HTEM-Groundwater Fusion - How subsurface structure controls water levels
**Cross-Chapter Connections:**
- Uses stream data introduced in Part 1
- Applies correlation methods from Part 2
- Informs recharge estimation (Chapter 3)
- Foundation for temporal fusion (Chapter 8)
---
## Summary
Stream-aquifer exchange analysis quantifies **surface-groundwater connectivity**:
✅ **Gaining vs. losing reaches identified** - Streams both recharge and discharge aquifer
✅ **Baseflow separation** - Isolates groundwater contribution to streamflow
✅ **Seasonal patterns** - Exchange direction varies with water table elevation
✅ **HTEM validation** - Stream connectivity consistent with resistivity patterns
⚠️ **3-25 km well-stream separation** - Direct correlation limited by monitoring gaps
**Key Insight**: Streams and aquifers are a **single resource**. Managing one without considering the other leads to unintended consequences.
---
## Reflection Questions
- Think about a specific stream reach in your region. Based on what you know (or can observe) about water levels and flows, would you expect it to be gaining, losing, or seasonally reversing, and what additional data from wells or gauges would confirm that?
- Looking at the kinds of plots in this chapter (time series, seasonal distributions, baseflow separation, conceptual gradients), which two or three would you prioritize for communicating stream–aquifer connectivity to a non-technical stakeholder, and why?
- How could HTEM-derived streambed conductivity information change your interpretation of exchange (for example, turning a suspected connection into a confirmed “leaky barrier” or “fast pathway”)?
- If monitoring budgets are limited, how would you place new wells or gauges to reduce the 3–25 km separation mentioned in the summary and improve your ability to quantify exchange?
---
## Related Chapters
- [Stream Gauge Network](../part-1-foundations/stream-gauge-network.qmd) - Source stream data
- [Stream Proximity Analysis](../part-2-spatial/stream-proximity-analysis.qmd) - Spatial gap analysis
- [Recharge Rate Estimation](recharge-rate-estimation.qmd) - Water balance context
- [Streamflow Variability](../part-3-temporal/streamflow-variability.qmd) - Temporal flow analysis