prop/lib/mix/tasks/buildings_fetch.ex
Graham McIntire 021608dc90
feat(buildings): bulk-prefetch tiles via BulkFetch.run/4 (works in prod)
Mix tasks don't run in production, so the orchestration lives in
Microwaveprop.Buildings.BulkFetch — callable from an attached iex on
the prod pod via:

    iex> Microwaveprop.Buildings.BulkFetch.run(32.8, -97.0, 500)

Default 500 mi radius around DFW yields ~440 candidate quadkeys at
zoom 9; only those present in the MS dataset index (typically a few
hundred over CONUS) get downloaded. Concurrency 4, per-task timeout
3 min, results logged with downloaded/cached/failed/missing counts.
The mix.task is kept as a dev-only thin wrapper.
2026-04-26 10:37:13 -05:00

49 lines
1.4 KiB
Elixir

defmodule Mix.Tasks.BuildingsFetch do
@shortdoc "Bulk-download Microsoft Building Footprint tiles around DFW (DEV ONLY)"
@moduledoc """
Pre-warms the local Microsoft Building Footprints cache so per-Calculate
requests never have to wait on a tile download.
mix buildings.fetch # default: 500 mi radius around EM13
mix buildings.fetch --lat 32.8 --lon -97 --radius-mi 500
mix buildings.fetch --concurrency 8
**Mix tasks do not run in production.** From an attached `iex` on the
prod pod, call the underlying module directly:
iex> Microwaveprop.Buildings.BulkFetch.run(32.8, -97.0, 500)
"""
use Mix.Task
alias Microwaveprop.Buildings.BulkFetch
@default_lat 32.8
@default_lon -97.0
@default_radius_mi 500
@switches [
lat: :float,
lon: :float,
radius_mi: :integer,
concurrency: :integer
]
@impl Mix.Task
def run(argv) do
{opts, _, _} = OptionParser.parse(argv, switches: @switches)
lat = Keyword.get(opts, :lat, @default_lat)
lon = Keyword.get(opts, :lon, @default_lon)
radius_mi = Keyword.get(opts, :radius_mi, @default_radius_mi)
concurrency = Keyword.get(opts, :concurrency)
Mix.Task.run("app.start")
bulk_opts = if concurrency, do: [concurrency: concurrency], else: []
result = BulkFetch.run(lat, lon, radius_mi, bulk_opts)
Mix.shell().info("Done. #{inspect(result)}")
end
end