{"spec_id":"heatmap-geographic","library":"makie","language":"julia","code":"# anyplot.ai\n# heatmap-geographic: Geographic Heatmap for Spatial Density\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 87/100 | Created: 2026-09-02\n\nusing CairoMakie\nusing Colors\nusing Random\nusing Statistics\n\nRandom.seed!(42)\n\n# --- Theme tokens -----------------------------------------------------------\nTHEME    = get(ENV, \"ANYPLOT_THEME\", \"light\")\nPAGE_BG  = THEME == \"light\" ? colorant\"#FAF8F1\" : colorant\"#1A1A17\"\nINK      = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nINK_SOFT = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\n\n# Imprint sequential colormap — density is single-polarity (never negative)\nANYPLOT_SEQ = cgrad([colorant\"#009E73\", colorant\"#4467A3\"])\n\n# --- Data: simulated wildfire ignitions across the western US ---------------\n# Bounding box for the plotted region\nLON_MIN, LON_MAX = -125.0, -103.0\nLAT_MIN, LAT_MAX = 31.0, 49.0\n\n# Wildfire-prone regions with an average burn severity (acres) per cluster\ncluster_lon      = [-119.5, -121.5, -108.0, -116.0, -112.5]\ncluster_lat      = [37.5, 44.0, 39.5, 40.5, 33.0]\ncluster_spread   = [1.6, 1.3, 1.7, 1.9, 1.4]\ncluster_severity = [320.0, 260.0, 220.0, 190.0, 160.0]\npoints_per_cluster = 300\n\nlongitudes = Float64[]\nlatitudes = Float64[]\nacres_burned = Float64[]\nfor c in 1:length(cluster_lon)\n    append!(longitudes, cluster_lon[c] .+ cluster_spread[c] .* randn(points_per_cluster))\n    append!(latitudes, cluster_lat[c] .+ 0.75 * cluster_spread[c] .* randn(points_per_cluster))\n    append!(acres_burned, cluster_severity[c] .* exp.(0.6 .* randn(points_per_cluster)))\nend\nlongitudes = clamp.(longitudes, LON_MIN, LON_MAX)\nlatitudes = clamp.(latitudes, LAT_MIN, LAT_MAX)\nn_points = length(longitudes)\nweights = acres_burned ./ mean(acres_burned)\n\n# --- Simplified western-US geographic context (Pacific coastline + national\n# borders) — a small set of hardcoded boundary points, no GeoMakie needed ----\nboundary_lon = [-117.2, -118.5, -120.5, -122.5, -124.3, -124.1, -124.0, -124.7,\n    -123.2, -116.0, -104.05, -104.05, -104.05, -102.05, -103.0, -106.5, -111.0, -117.2]\nboundary_lat = [32.5, 33.9, 34.6, 37.8, 40.3, 43.5, 46.2, 47.9,\n    49.0, 49.0, 49.0, 45.0, 41.0, 37.0, 32.0, 31.8, 31.3, 32.5]\n\n# --- Kernel density estimation on a regular lon/lat grid ---------------------\n# Silverman's rule of thumb bandwidth, per dimension\nbw_lon = std(longitudes) * n_points^(-1 / 6)\nbw_lat = std(latitudes) * n_points^(-1 / 6)\n\ngrid_n = 100\nlon_grid = range(LON_MIN, LON_MAX; length=grid_n)\nlat_grid = range(LAT_MIN, LAT_MAX; length=grid_n)\nnorm_factor = 1.0 / (2 * pi * bw_lon * bw_lat)\n\n# Broadcast the (grid_n,) squared-distance vectors into a (grid_n, grid_n)\n# outer-sum matrix per point, accumulating over points — avoids a manual\n# triple-nested loop while keeping the same grid_n × grid_n × n_points cost.\ndensity = zeros(grid_n, grid_n)\nfor k in 1:n_points\n    dx2 = ((lon_grid .- longitudes[k]) ./ bw_lon) .^ 2\n    dy2 = ((lat_grid .- latitudes[k]) ./ bw_lat) .^ 2\n    density .+= weights[k] .* exp.(-0.5 .* (dx2 .+ dy2'))\nend\ndensity .*= norm_factor\n\n# --- Title (fontsize scales down once the descriptive prefix pushes past the\n# ~67-char mandated-title baseline) -------------------------------------------\ntitle_str = \"Wildfire Ignition Density, Western US · heatmap-geographic · julia · makie · anyplot.ai\"\ntitle_ratio = length(title_str) > 67 ? 67 / length(title_str) : 1.0\ntitle_size = round(Int, 20 * title_ratio)\n\n# --- Plot ---------------------------------------------------------------------\nfig = Figure(\n    resolution      = (1200, 1200),\n    fontsize        = 14,\n    backgroundcolor = PAGE_BG,\n)\n\nax = Axis(\n    fig[1, 1];\n    title             = title_str,\n    titlesize         = title_size,\n    titlecolor        = INK,\n    xlabel            = \"Longitude (°)\",\n    ylabel            = \"Latitude (°)\",\n    xlabelsize        = 16,\n    ylabelsize        = 16,\n    xlabelcolor       = INK,\n    ylabelcolor       = INK,\n    xticklabelsize    = 13,\n    yticklabelsize    = 13,\n    xticklabelcolor   = INK_SOFT,\n    yticklabelcolor   = INK_SOFT,\n    xtickcolor        = INK_SOFT,\n    ytickcolor        = INK_SOFT,\n    backgroundcolor   = PAGE_BG,\n    aspect            = DataAspect(),\n    topspinevisible   = false,\n    rightspinevisible = false,\n    leftspinecolor    = INK_SOFT,\n    bottomspinecolor  = INK_SOFT,\n    xgridvisible      = false,\n    ygridvisible      = false,\n)\n\n# Basemap: simplified coastline/border outline, drawn first so the\n# semi-transparent density layer shows it through underneath\nlines!(ax, boundary_lon, boundary_lat; color=INK_SOFT, linewidth=1.2)\n\nhm = heatmap!(ax, lon_grid, lat_grid, density; colormap=ANYPLOT_SEQ, alpha=0.85)\n\n# Raw ignition points, faint, for spatial context under the density layer\nscatter!(ax, longitudes, latitudes;\n    color       = (PAGE_BG, 0.45),\n    markersize  = 5,\n    strokewidth = 0,\n)\n\nColorbar(fig[1, 2], hm;\n    label          = \"Weighted ignition density\",\n    labelsize      = 15,\n    labelcolor     = INK,\n    ticklabelsize  = 12,\n    ticklabelcolor = INK_SOFT,\n    tickcolor      = INK_SOFT,\n)\n\n# --- Save ---------------------------------------------------------------------\nsave(\"plot-$(THEME).png\", fig; px_per_unit=2)\n"}