Isochrone¶
Wind-aware reachability boundaries. Given a starting state, a time budget, and (optionally) a recovery destination + on-station observation requirement, compute the polygon around the start where the aircraft can operate within the time budget.
Modes¶
one_way— single-leg reach. “Where can I be afterbudget?”round_trip— out and back from the same place. Default; the recovery destination defaults tostart.return_safe— out, observe, recover at a different airport withinbudget. Required for missions where the target is far from base.
Compute¶
- compute_isochrone(aircraft, start, budget, *, cruise_altitude=None, on_station_altitude=None, start_time=None, wind_source=None, return_destination=None, mode='round_trip', on_station_time=<Quantity(0, 'minute')>, reserve=<Quantity(0, 'minute')>, azimuth_resolution_deg=5.0, distance_tolerance_nmi=0.5, wind_sampling='cruise_midpoint', wind_sample_spacing=<Quantity(100, 'nautical_mile')>, max_wind_samples_per_leg=20, ray_strategy='uniform', adaptive_spacing_nmi=None, max_adaptive_rays=180)[source]¶
Compute a wind-aware isochrone around
start.- Parameters:
aircraft (
Aircraft) – The aircraft model. Climb / cruise / descent profiles and speed schedules are read from this object.start (
Union[Airport,Waypoint]) – Starting state. Accepts either anAirport(resolved to a Waypoint at runway elevation, heading 0°) or aWaypointdirectly. When a Waypoint is provided,altitude_mslis required. Pass an Airport for pre-flight planning; pass a Waypoint with a non-runway altitude for in-flight re-tasking.budget (
Quantity) – Total time available (e.g. endurance remaining).cruise_altitude (
Optional[Quantity]) – Transit / observation altitude. Defaults tostart.altitude_msl. v1 treats this as the single altitude for both the outbound transit and on-station phase.on_station_altitude (
Optional[Quantity]) – Reserved for future multi-altitude support. v1 requires this to beNoneor equal tocruise_altitude; a distinct value raisesHyPlanValueError.start_time (
Optional[datetime]) – UTC datetime when the sortie begins. Used to query time-varying wind providers. Defaults todatetime.now(UTC).wind_source (
Optional[WindField]) – AWindFieldprovider. Defaults toStillAirField.return_destination (
Union[Airport,Waypoint,None]) – Where the aircraft must reach bybudget − reserve. Required for"return_safe"; defaults tostartfor"round_trip"; ignored (with a warning) for"one_way". Accepts anAirport(resolved to its runway-elevation Waypoint) or aWaypoint.mode (
str) –"one_way"|"round_trip"|"return_safe".on_station_time (
Quantity) – Required dwell at the target.reserve (
Quantity) – Mandatory unflown time buffer (fuel/reserve fuel).azimuth_resolution_deg (
float) – Step in degrees between sweep rays. Default 5.0 → 72 rays.distance_tolerance_nmi (
float) – Binary-search stop criterion (boundary distance precision in nmi).wind_sampling (
str) – How wind is sampled per leg —"cruise_midpoint"(default; one sample at cruise midpoint, climb/descent still-air),"phase_midpoint"(adds one wind sample each at the climb-segment-mid and descent-segment-mid altitudes, redistributing ground distance between phases), or"segmented_cruise"(splits cruise into multiple subsegments, ~``wind_sample_spacing`` apart). See the module docstring’s “Wind sampling” section.wind_sample_spacing (
Quantity) – Cruise-segment spacing forwind_sampling="segmented_cruise"(ignored otherwise). Default 100 nmi.max_wind_samples_per_leg (
int) – Hard cap on cruise subsegments to keep_leg_timecost bounded for very long legs. Default 20.ray_strategy (
str) – How sweep azimuths are chosen."uniform"preserves the original fixed-angle sweep."auto"uses ellipse-aware seed rays when start and recovery differ, otherwise uniform rays."adaptive"adds midpoint rays where solved boundary chords exceedadaptive_spacing_nmi;"ellipse"and"ellipse_adaptive"force the two-focus seed distribution when possible.adaptive_spacing_nmi (
Optional[float]) – Target maximum boundary chord length for adaptive refinement. Defaults to 100 nmi when an adaptive strategy is selected.max_adaptive_rays (
int) – Hard cap on adaptive ray count.
- Return type:
GeoDataFrame- Returns:
A
geopandas.GeoDataFrameinEPSG:4326, one row per ray, withPointgeometries on the isochrone boundary. See the module docstring for the column schema. Invocation context is stashed ingdf.attrs.- Raises:
HyPlanValueError – For invalid arguments (see input validation in the plan / tests).
HyPlanRuntimeError – If the expanding bracket exceeds 20000 nmi (pathological wind field or aircraft).
- compute_concentric_isochrones(aircraft, start, budgets, *, cruise_altitude=None, on_station_altitude=None, start_time=None, wind_source=None, return_destination=None, mode='round_trip', on_station_time=<Quantity(0, 'minute')>, reserve=<Quantity(0, 'minute')>, azimuth_resolution_deg=5.0, distance_tolerance_nmi=0.5, wind_sampling='cruise_midpoint', wind_sample_spacing=<Quantity(100, 'nautical_mile')>, max_wind_samples_per_leg=20, ray_strategy='uniform', adaptive_spacing_nmi=None, max_adaptive_rays=180)[source]¶
Compute multiple isochrone contours in one call (e.g., 1/2/3 hr).
Sweeps
budgetsin ascending order and seeds each budget’s bracket search from the previous (smaller) budget’s converged distances per ray, exploiting the fact that feasibility is monotone in budget. Total cost is roughlyO(M + N)rather thanO(M·N)forMbudgets andNrays.- Parameters:
aircraft (
Aircraft)start_time (datetime | None)
wind_source (WindField | None)
mode (str)
on_station_time (Quantity)
reserve (Quantity)
azimuth_resolution_deg (float)
distance_tolerance_nmi (float)
wind_sampling (str)
wind_sample_spacing (Quantity)
max_wind_samples_per_leg (int)
ray_strategy (str)
adaptive_spacing_nmi (float | None)
max_adaptive_rays (int)
- Return type:
GeoDataFrame
:param : :type start_time:
Optional[datetime] :param start_time: :type wind_source:Optional[WindField] :param wind_source: :type return_destination:Union[Airport,Waypoint,None] :param return_destination: :type mode:str:param mode: :param : :type on_station_time:Quantity:param on_station_time: :type reserve:Quantity:param reserve: :type azimuth_resolution_deg:float:param azimuth_resolution_deg: :param : :type distance_tolerance_nmi:float:param distance_tolerance_nmi: :type wind_sampling:str:param wind_sampling: :type wind_sample_spacing:Quantity:param wind_sample_spacing: :param : :type max_wind_samples_per_leg:int:param max_wind_samples_per_leg: :type ray_strategy:str:param ray_strategy: :param : :type adaptive_spacing_nmi:Optional[float] :param adaptive_spacing_nmi: same semantics as- Parameters:
max_adaptive_rays (
int) – same semantics ascompute_isochrone().budgets (
Sequence[Quantity]) – iterable ofQuantitytime values to sweep. Must be non-empty; sorted ascending internally.aircraft (Aircraft)
cruise_altitude (Quantity | None)
on_station_altitude (Quantity | None)
start_time (datetime | None)
wind_source (WindField | None)
mode (str)
on_station_time (Quantity)
reserve (Quantity)
azimuth_resolution_deg (float)
distance_tolerance_nmi (float)
wind_sampling (str)
wind_sample_spacing (Quantity)
max_wind_samples_per_leg (int)
ray_strategy (str)
adaptive_spacing_nmi (float | None)
- Returns:
4326, one row per
(budget, azimuth)combination. Columns matchcompute_isochrone()plusbudget_minandbudget_hrtagging which contour each row belongs to.gdf.attrs["budgets_hr"]lists budgets in computed order (ascending).- Return type:
A
geopandas.GeoDataFramein EPSG
Refuel-aware concentric reach is intentionally out of scope — sweeping
(sortie, day)tuples is a different UI problem.
- compute_refuel_isochrone(aircraft, start, sortie_budget, *, flight_day_budget, cruise_altitude=None, refuel_airports, refuel_time=<Quantity(60, 'minute')>, return_destination=None, mode='return_safe', on_station_altitude=None, on_station_time=<Quantity(0, 'minute')>, reserve=<Quantity(0, 'minute')>, max_refuel_stops=1, start_time=None, wind_source=None, azimuth_resolution_deg=5.0, distance_tolerance_nmi=0.5, wind_sampling='cruise_midpoint', wind_sample_spacing=<Quantity(100, 'nautical_mile')>, max_wind_samples_per_leg=20)[source]¶
Wind-aware isochrone with a single optional refuel stop.
Two clocks are tracked:
sortie_budgetper fuel cycle (resets after each refuel) andflight_day_budgettotal wall-clock (does not reset, includes refuel time).reserveapplies per fuel cycle, not to the day budget.For every azimuth, three itinerary templates are evaluated and the one reaching the largest distance wins:
direct—start → target → recoveryoutbound_refuel(R)—start → R → target → recoveryreturn_refuel(R)—start → target → R → recovery
Each refuel airport in
refuel_airportsis tested in both placements (where eligible). v1 limitsmax_refuel_stopsto 1 — a single sortie touches at most two tanks.- Parameters:
aircraft (
Aircraft) – Aircraft model.start (
Union[Airport,Waypoint]) – Departure point (Airport or Waypoint).sortie_budget (
Quantity) – Per-fuel-cycle endurance. Positional to mirrorcompute_isochrone(..., budget=...).flight_day_budget (
Quantity) – Total wall-clock budget (kw-only — the new required clock).refuel_airports (
Sequence[Union[Airport,Waypoint]]) – Pre-cleared refuel candidates. Empty raises; usecompute_isochroneif no refuel option applies.refuel_time (
Quantity) – Per-refuel wall-clock cost. Default 60 min.return_destination (
Union[Airport,Waypoint,None]) – Recovery field. Required for"return_safe"; defaults tostartfor"round_trip".mode (
str) –"return_safe"(default) or"round_trip"."one_way"is rejected — multi-hop one-way reach is deferred.max_refuel_stops (
int) – v1 must be1. Reserved for future chained refuels.compute_isochrone. (Other kwargs match)
cruise_altitude (Quantity | None)
on_station_altitude (Quantity | None)
on_station_time (Quantity)
reserve (Quantity)
start_time (datetime | None)
wind_source (WindField | None)
azimuth_resolution_deg (float)
distance_tolerance_nmi (float)
wind_sampling (str)
wind_sample_spacing (Quantity)
max_wind_samples_per_leg (int)
- Return type:
GeoDataFrame- Returns:
A GeoDataFrame with one row per ray. Per-leg time columns (
start_to_target_time_min,start_to_refuel_time_min,refuel_to_target_time_min,target_to_refuel_time_min,refuel_to_return_time_min,target_to_return_time_min) are NaN unless the itinerary uses that leg.itinerary,refuel_airport,refuel_count,day_total_time_min,sortie_cycle_1_min,sortie_cycle_2_min,sortie_margin_min,day_margin_min,limiting_legdescribe the chosen path.limiting_legvalues for refuel results are"sortie"|"flight_day"|"both"|"slack"|"unflyable"(a different value vocabulary thancompute_isochrone’s, but the column name is shared so consumers holding both gdfs use the same accessor).gdf.attrsincludesrefuel_airports_evaluated,refuel_airports_unreachable, andrefuel_airports_used.
- evaluate_target_reachability(aircraft, start, target, *, sortie_budget, flight_day_budget=None, cruise_altitude=None, refuel_airports=(), refuel_time=<Quantity(60, 'minute')>, return_destination=None, mode='return_safe', on_station_altitude=None, on_station_time=<Quantity(0, 'minute')>, reserve=<Quantity(0, 'minute')>, start_time=None, wind_source=None, wind_sampling='cruise_midpoint', wind_sample_spacing=<Quantity(100, 'nautical_mile')>, max_wind_samples_per_leg=20)[source]¶
Evaluate reachability of a single target via direct + refuel paths.
The complement of
compute_refuel_isochrone(): rather than sweeping azimuths to find the boundary, this asks “given this target, which itineraries reach it within budget?” and reports every feasible option.- Parameters:
aircraft (
Aircraft)sortie_budget (
Quantity)refuel_time (Quantity)
mode (str)
on_station_altitude (Quantity | None)
on_station_time (Quantity)
reserve (Quantity)
start_time (datetime | None)
wind_source (WindField | None)
wind_sampling (str)
wind_sample_spacing (Quantity)
max_wind_samples_per_leg (int)
- Return type:
:param : :type refuel_airports:
Sequence[Union[Airport,Waypoint]] :param refuel_airports: :type refuel_time:Quantity:param refuel_time: :type return_destination:Union[Airport,Waypoint,None] :param return_destination: :type mode:str:param mode: :param : :type on_station_altitude:Optional[Quantity] :param on_station_altitude: :type on_station_time:Quantity:param on_station_time: :type reserve:Quantity:param reserve: :type start_time:Optional[datetime] :param start_time: :param : :type wind_source:Optional[WindField] :param wind_source: same semantics ascompute_refuel_isochrone().flight_day_budgetdefaults tosortie_budget(the day clock then never binds).refuel_airportsmay be empty — only the direct itinerary is then evaluated.- Parameters:
target (
Union[Airport,Waypoint]) – the point to evaluate. Accepts anAirportor aWaypoint.aircraft (Aircraft)
sortie_budget (Quantity)
flight_day_budget (Quantity | None)
cruise_altitude (Quantity | None)
refuel_time (Quantity)
mode (str)
on_station_altitude (Quantity | None)
on_station_time (Quantity)
reserve (Quantity)
start_time (datetime | None)
wind_source (WindField | None)
wind_sampling (str)
wind_sample_spacing (Quantity)
max_wind_samples_per_leg (int)
- Returns:
reachable:bool.best: itinerary diagnostic dict for the chosen route,or
Nonewhenreachableis False.alternatives: list of dicts for the other feasibleitineraries, sorted by ascending
day_total_time_min.unreachable_reason: short human-readable string whenreachableis False, elseNone.
target_lat,target_lon: coordinates of the target.- Return type:
Dict with keys
Concentric reach¶
compute_concentric_isochrones sweeps multiple budgets in one call and
amortizes the bracket search across them — each budget seeds the next
larger budget’s lower bound, so total cost is roughly O(M+N) rather than
O(M·N) for M budgets and N rays. Returns one stacked GeoDataFrame
tagged with budget_min / budget_hr columns.
Refuel-aware reach¶
compute_refuel_isochrone extends the standard isochrone with a single
optional refuel stop drawn from a list of pre-cleared candidates. Two
clocks are tracked: a per-fuel-cycle sortie_budget (resets after each
refuel) and a total wall-clock flight_day_budget (does not reset and
absorbs refuel_time). reserve applies per fuel cycle only.
For every azimuth, three itineraries are evaluated and the most-extending one wins:
direct—start → target → recoveryoutbound_refuel(R)—start → R → target → recoveryreturn_refuel(R)—start → target → R → recovery
v1 limits max_refuel_stops to 1 (a single sortie touches at most two
tanks). Chained refuels are deferred.
Single-target reachability¶
evaluate_target_reachability is the complement of
compute_refuel_isochrone: rather than sweeping azimuths to find the
boundary, it asks “given this target, which itineraries reach it?”
and returns a structured dict with the best route plus all feasible
alternatives.
Helpers¶
- isochrone_polygon(gdf)[source]¶
Connect the isochrone boundary points into a closed polygon.
Boundary points are taken in order of
azimuth_deg. Result is returned in EPSG:4326 (matches the input GeoDataFrame’s CRS).- Return type:
- Parameters:
gdf (GeoDataFrame)
- plot_isochrone(gdf, base_map=None, *, color='steelblue', fill_opacity=0.2, tiles='OpenStreetMap', zoom_start=6)[source]¶
Render the isochrone on a Folium map.
- Parameters:
gdf (
GeoDataFrame) – A GeoDataFrame returned bycompute_isochrone()orcompute_refuel_isochrone(). Refuel results are recognized viagdf.attrs["refuel_airports_evaluated"]and trigger refuel-airport markers + per-itinerary dot coloring (seecolorbelow).base_map (
Optional[Map]) – Optional existing Folium map to add to. IfNone, a new one is created centered onstartwith thetilesbasemap loaded.color (
str) – Polygon stroke + fill color. For refuel-isochrone results, this color is also used for"direct"ray dots, but"outbound_refuel"and"return_refuel"dots use a fixed palette (steel blue / red) so the itinerary structure is legible regardless of the polygon color.fill_opacity (
float) – Polygon fill opacity (0–1).tiles (
str) – Folium basemap identifier when constructing a new map. Built-in choices include"OpenStreetMap"(default),"CartoDB positron"(clean light gray, good for science figures),"CartoDB dark_matter", and"Esri.WorldImagery"(satellite). Ignored whenbase_mapis supplied.zoom_start (
int) – Initial zoom level (Folium scale 1–18). Ignored whenbase_mapis supplied.
- Return type:
Map- Returns:
The Folium map.
- plot_isochrone_static(layers, *, points=(), title='', figsize=(9, 9), wind_field=None, wind_altitude=None, wind_time=None, wind_caption=None, basemap_scale='50m', show_refuel_markers=True)[source]¶
Render one or more isochrones on a static Cartopy basemap.
Companion to
hyplan.plot_isochrone(the Folium / interactive plotter). This produces publication-quality figures with Natural Earth vector basemaps and is the right choice for static output (papers, briefings, reports).- Parameters:
layers – Either a single
gpd.GeoDataFramefromcompute_isochrone(),compute_refuel_isochrone(), orcompute_concentric_isochrones(); or an iterable of(gdf, color, label)tuples for layered comparison (e.g., still-air vs. windy). When a single concentric GDF is passed without explicit splitting, the function auto-groups rows bybudget_hrand renders each contour with a viridis-indexed color.points – Iterable of
{"lat", "lon", "label", "color", "marker", "markersize"}dicts for airports / waypoints of interest.title (
str) – Figure title.wind_field – Optional
WindFieldprovider — when supplied, samples wind on a grid covering the plot extent and overlays as aviation-convention wind barbs (half-barb 5 kt, full barb 10 kt, flag 50 kt).wind_altitude – Altitude (Quantity) for wind queries; required when
wind_fieldis supplied.wind_time – UTC datetime to query the wind at; defaults to now.
wind_caption (
Optional[str]) – Optional caption drawn in the corner.basemap_scale (
str) – Natural Earth resolution:"110m"(lowest, ships with cartopy),"50m"(default), or"10m"(highest; downloads on first use).show_refuel_markers (
bool) – When True (default) and the input GDF hasattrs["refuel_airports_evaluated"], draw refuel airport markers (used = darkblue, evaluated-not-used = gray, unreachable = light gray).
- Returns:
(fig, ax)so the caller can add overlays after.
plot_isochrone is the interactive Folium plotter.
plot_isochrone_static (in hyplan/plotting.py) is the
publication-quality Cartopy plotter — accepts either a single GDF or
a list of (gdf, color, label) tuples for layered comparison, and
auto-handles concentric and refuel results.
See also¶
flight_plan— once you’ve selected a target site inside the isochrone, hand that target tocompute_flight_planto produce the actual sortie.The isochrone tutorial notebook walks through all three modes plus a multi-aircraft fleet comparison.