Auto-update: Fri Nov 15 13:05:58 PST 2024
This commit is contained in:
parent
2d49d296fb
commit
f44d2e7122
1 changed files with 83 additions and 51 deletions
|
@ -128,7 +128,7 @@ async def generate_and_save_heatmap(
|
||||||
output_path: Optional[Path] = None
|
output_path: Optional[Path] = None
|
||||||
) -> Path:
|
) -> Path:
|
||||||
"""
|
"""
|
||||||
Generate a heatmap for the given date range and save it as a PNG file using matplotlib.
|
Generate a heatmap for the given date range and save it as a PNG file.
|
||||||
|
|
||||||
:param start_date: The start date for the map (or the only date if end_date is not provided)
|
:param start_date: The start date for the map (or the only date if end_date is not provided)
|
||||||
:param end_date: The end date for the map (optional)
|
:param end_date: The end date for the map (optional)
|
||||||
|
@ -137,7 +137,9 @@ async def generate_and_save_heatmap(
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
|
import contextily as ctx
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
from matplotlib.colors import LinearSegmentedColormap
|
||||||
|
|
||||||
start_date = await dt(start_date)
|
start_date = await dt(start_date)
|
||||||
if end_date:
|
if end_date:
|
||||||
|
@ -149,27 +151,56 @@ async def generate_and_save_heatmap(
|
||||||
if not locations:
|
if not locations:
|
||||||
raise ValueError("No locations found for the given date range")
|
raise ValueError("No locations found for the given date range")
|
||||||
|
|
||||||
lats = [loc.latitude for loc in locations]
|
lats = np.array([loc.latitude for loc in locations])
|
||||||
lons = [loc.longitude for loc in locations]
|
lons = np.array([loc.longitude for loc in locations])
|
||||||
|
|
||||||
plt.style.use('dark_background')
|
# Calculate bounds with 5% buffer
|
||||||
fig, ax = plt.subplots(figsize=(10, 6))
|
lat_range = max(lats) - min(lats)
|
||||||
|
lon_range = max(lons) - min(lons)
|
||||||
|
buffer = max(lat_range, lon_range) * 0.05
|
||||||
|
|
||||||
# Create heatmap
|
# Enforce minimum zoom
|
||||||
heatmap, xedges, yedges = np.histogram2d(lons, lats, bins=50)
|
MIN_RANGE = 0.05 # roughly 3-4 miles
|
||||||
extent = [xedges[0], xedges[-1], yedges[0], yedges[-1]]
|
lat_range = max(lat_range, MIN_RANGE)
|
||||||
|
lon_range = max(lon_range, MIN_RANGE)
|
||||||
|
|
||||||
# Plot with no axes or labels
|
bounds = [
|
||||||
ax.imshow(heatmap.T, extent=extent, origin='lower', cmap='hot', interpolation='gaussian')
|
min(lons) - buffer,
|
||||||
ax.axis('off')
|
max(lons) + buffer,
|
||||||
|
min(lats) - buffer,
|
||||||
|
max(lats) + buffer
|
||||||
|
]
|
||||||
|
|
||||||
# Remove white border
|
# Create figure with fixed size
|
||||||
plt.gca().set_position([0, 0, 1, 1])
|
fig, ax = plt.subplots(figsize=(6.4, 3.6), dpi=100) # 640x360 pixels
|
||||||
|
|
||||||
|
# Add dark basemap
|
||||||
|
ctx.add_basemap(
|
||||||
|
ax,
|
||||||
|
crs='EPSG:4326',
|
||||||
|
source=ctx.providers.CartoDB.DarkMatter,
|
||||||
|
zoom='auto',
|
||||||
|
bbox=bounds
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create heatmap overlay
|
||||||
|
heatmap = ax.hexbin(
|
||||||
|
lons, lats,
|
||||||
|
extent=bounds,
|
||||||
|
gridsize=25,
|
||||||
|
cmap='hot',
|
||||||
|
alpha=0.6,
|
||||||
|
zorder=2
|
||||||
|
)
|
||||||
|
|
||||||
|
# Remove axes and margins
|
||||||
|
ax.set_axis_off()
|
||||||
|
plt.subplots_adjust(left=0, right=1, top=1, bottom=0)
|
||||||
|
|
||||||
if output_path is None:
|
if output_path is None:
|
||||||
output_path, relative_path = assemble_journal_path(end_date, filename="map", extension=".png", no_timestamp=True)
|
output_path, relative_path = assemble_journal_path(end_date, filename="map", extension=".png", no_timestamp=True)
|
||||||
|
|
||||||
plt.savefig(output_path, bbox_inches='tight', pad_inches=0, transparent=True)
|
plt.savefig(output_path, bbox_inches='tight', pad_inches=0, dpi=100)
|
||||||
plt.close()
|
plt.close()
|
||||||
|
|
||||||
l.info(f"Heatmap saved as PNG: {output_path}")
|
l.info(f"Heatmap saved as PNG: {output_path}")
|
||||||
|
@ -180,6 +211,7 @@ async def generate_and_save_heatmap(
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def generate_map(start_date: datetime, end_date: datetime, max_points: int):
|
async def generate_map(start_date: datetime, end_date: datetime, max_points: int):
|
||||||
locations = await fetch_locations(start_date, end_date)
|
locations = await fetch_locations(start_date, end_date)
|
||||||
if not locations:
|
if not locations:
|
||||||
|
|
Loading…
Reference in a new issue