Numbers outside of the map [still cluttered]
This commit is contained in:
1 parent
b692f94122
commit
e39de24921
5 files changed
+274
-45
No files matched your search
+113
-42
@@ -74,6 +74,68 @@ def label(group: ET.Element, text: str, x: float, y: float, height: float) -> No
|
||||
add_line(group, line)
|
||||
|
||||
|
||||
def outside_index(
|
||||
group: ET.Element,
|
||||
text: str,
|
||||
curves: list[Cubic],
|
||||
map_rectangle: BaseGeometry,
|
||||
outer_rectangle: BaseGeometry,
|
||||
occupied: list[BaseGeometry],
|
||||
height: float,
|
||||
pen_width: float,
|
||||
) -> None:
|
||||
west, top, east, bottom = map_rectangle.bounds
|
||||
width = (len(text) - 1 + 2 / 3) * height
|
||||
padding = pen_width / 2 + 0.2
|
||||
anchors: list[tuple[float, float]] = []
|
||||
for curve in curves:
|
||||
for first, last in curve.inside_intervals(map_rectangle.bounds):
|
||||
anchors.extend((curve.point(first), curve.point(last)))
|
||||
candidates = []
|
||||
for ax, ay in anchors:
|
||||
candidates.extend(
|
||||
[
|
||||
(abs(ax - west), "left", ax, ay),
|
||||
(abs(ax - east), "right", ax, ay),
|
||||
(abs(ay - top), "top", ax, ay),
|
||||
(abs(ay - bottom), "bottom", ax, ay),
|
||||
]
|
||||
)
|
||||
for _, side, ax, ay in sorted(candidates):
|
||||
for step in (0, 1, -1, 2, -2, 3, -3):
|
||||
shift = step * (height + 2 * padding)
|
||||
if side == "left":
|
||||
x, y = west - width - 2 * padding, ay - height / 2 + shift
|
||||
endpoint = (x + width + padding, y + height / 2)
|
||||
elif side == "right":
|
||||
x, y = east + 2 * padding, ay - height / 2 + shift
|
||||
endpoint = (x - padding, y + height / 2)
|
||||
elif side == "top":
|
||||
x, y = ax - width / 2 + shift, top - height - 2 * padding
|
||||
endpoint = (x + width / 2, y + height + padding)
|
||||
else:
|
||||
x, y = ax - width / 2 + shift, bottom + 2 * padding
|
||||
endpoint = (x + width / 2, y - padding)
|
||||
extent = box(x - padding, y - padding, x + width + padding, y + height + padding)
|
||||
leader = LineString([(ax, ay), endpoint])
|
||||
if (
|
||||
outer_rectangle.covers(extent)
|
||||
and extent.disjoint(map_rectangle)
|
||||
and not any(
|
||||
extent.intersects(previous) or leader.intersects(previous)
|
||||
for previous in occupied
|
||||
)
|
||||
):
|
||||
add_line(group, leader)
|
||||
label(group, text, x, y, height)
|
||||
occupied.append(extent)
|
||||
return
|
||||
raise ValueError(
|
||||
"Cannot place track identifiers outside the map; select fewer tracks "
|
||||
"or increase --extension."
|
||||
)
|
||||
|
||||
|
||||
def utc(timestamp: float) -> str:
|
||||
return datetime.fromtimestamp(timestamp, UTC).strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
@@ -195,7 +257,8 @@ def render(
|
||||
)
|
||||
row_height = options.font_height * 1.8
|
||||
terrain_attribution = terrain_source.startswith("PDOK")
|
||||
legend_height = (len(tracks) + 5 + int(terrain_attribution)) * row_height
|
||||
flight_rows = (len(tracks) + 1) // 2
|
||||
legend_height = (flight_rows + 5 + int(terrain_attribution)) * row_height
|
||||
available_width = options.width - 2 * (options.margin + options.extension)
|
||||
available_height = options.height - 2 * (options.margin + options.extension) - legend_height
|
||||
if available_height < 25:
|
||||
@@ -284,60 +347,68 @@ def render(
|
||||
},
|
||||
)
|
||||
add_curves(group, page_curves)
|
||||
short_label = str(index)
|
||||
placed = False
|
||||
for fraction in (0.9, 0.1, 0.7, 0.3, 0.5):
|
||||
curve_index = min(int(fraction * len(page_curves)), len(page_curves) - 1)
|
||||
anchor_x, anchor_y = page_curves[curve_index].point(
|
||||
fraction * len(page_curves) - curve_index
|
||||
)
|
||||
for dx, dy in ((1, 1), (1, -3), (-4, 1), (-4, -3)):
|
||||
x, y = anchor_x + dx, anchor_y + dy
|
||||
extent = box(
|
||||
x - 0.3,
|
||||
y - 0.3,
|
||||
x + len(short_label) * options.font_height,
|
||||
y + options.font_height + 0.3,
|
||||
)
|
||||
if track_rectangle.covers(extent) and not any(
|
||||
extent.intersects(previous) for previous in occupied
|
||||
):
|
||||
label(text_group, short_label, x, y, options.font_height)
|
||||
occupied.append(extent)
|
||||
placed = True
|
||||
break
|
||||
if placed:
|
||||
break
|
||||
if not placed:
|
||||
raise ValueError("Cannot place track identifiers legibly; select fewer tracks.")
|
||||
outside_index(
|
||||
text_group,
|
||||
str(index),
|
||||
page_curves,
|
||||
map_rectangle,
|
||||
track_rectangle,
|
||||
occupied,
|
||||
options.font_height,
|
||||
options.pen_width,
|
||||
)
|
||||
legend_y = top + map_height + options.extension + row_height
|
||||
ground_label = "N/A" if ground is None else f"{ground:.1f} M"
|
||||
legend = [
|
||||
"FLIGHT TIMES UTC - REPORTED BAROMETRIC ALTITUDE, NOT HEIGHT ABOVE GROUND",
|
||||
"FLIGHT MIDPOINT TIMES UTC - ALT IS REPORTED PRESSURE ALTITUDE, NOT HEIGHT ABOVE GROUND",
|
||||
f"MAP AVERAGE LAND ELEVATION: {ground_label} - N/A MEANS UNAVAILABLE",
|
||||
f"LAND ELEVATION COVERAGE: {100 * land_coverage:.1f} PERCENT - MAPPED WATER EXCLUDED",
|
||||
]
|
||||
for index, track in enumerate(tracks, 1):
|
||||
legend_width = options.width - 2 * options.margin
|
||||
for index, text in enumerate(legend):
|
||||
if len(text) * options.font_height > legend_width:
|
||||
raise ValueError("Legend is too wide; reduce --font-height or increase page width.")
|
||||
label(text_group, text, options.margin, legend_y + index * row_height, options.font_height)
|
||||
column_gap = 2 * options.font_height
|
||||
column_width = (legend_width - column_gap) / 2
|
||||
for offset, track in enumerate(tracks):
|
||||
first = track.observations[0]
|
||||
altitude_label = (
|
||||
"N/A" if track.average_altitude is None else f"{track.average_altitude:.0f} M"
|
||||
)
|
||||
legend.append(
|
||||
f"{index}: {utc(track.start)} TO {utc(track.end)} "
|
||||
f"{first.callsign or 'CALLSIGN N/A'} ({first.icao24.upper()}) BARO ALT {altitude_label}"
|
||||
text = (
|
||||
f"{offset + 1}: {utc((track.start + track.end) / 2)} "
|
||||
f"{first.callsign or 'CALLSIGN N/A'} ({first.icao24.upper()}) Alt {altitude_label}"
|
||||
)
|
||||
legend.extend(
|
||||
[
|
||||
"MAP: OPENSTREETMAP CONTRIBUTORS (ODBL) - OPENSTREETMAP.ORG/COPYRIGHT",
|
||||
"FLIGHTS: THE OPENSKY NETWORK - OPENSKY-NETWORK.ORG",
|
||||
]
|
||||
)
|
||||
if len(text) * options.font_height > column_width:
|
||||
raise ValueError(
|
||||
"Flight legend entry is too wide for two columns; reduce --font-height "
|
||||
"or increase page width."
|
||||
)
|
||||
column, row = divmod(offset, flight_rows)
|
||||
label(
|
||||
text_group,
|
||||
text,
|
||||
options.margin + column * (column_width + column_gap),
|
||||
legend_y + (len(legend) + row) * row_height,
|
||||
options.font_height,
|
||||
)
|
||||
attribution = [
|
||||
"MAP: OPENSTREETMAP CONTRIBUTORS (ODBL) - OPENSTREETMAP.ORG/COPYRIGHT",
|
||||
"FLIGHTS: THE OPENSKY NETWORK - OPENSKY-NETWORK.ORG",
|
||||
]
|
||||
if terrain_attribution:
|
||||
legend.append("TERRAIN: AHN DTM VIA PDOK (CC0) - ELEVATIONS RELATIVE TO NAP")
|
||||
for index, text in enumerate(legend):
|
||||
if len(text) * options.font_height > options.width - 2 * options.margin:
|
||||
attribution.append("TERRAIN: AHN DTM VIA PDOK (CC0) - ELEVATIONS RELATIVE TO NAP")
|
||||
for index, text in enumerate(attribution):
|
||||
if len(text) * options.font_height > legend_width:
|
||||
raise ValueError("Legend is too wide; reduce --font-height or increase page width.")
|
||||
label(text_group, text, options.margin, legend_y + index * row_height, options.font_height)
|
||||
label(
|
||||
text_group,
|
||||
text,
|
||||
options.margin,
|
||||
legend_y + (len(legend) + flight_rows + index) * row_height,
|
||||
options.font_height,
|
||||
)
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
temp_path: Path | None = None
|
||||
try:
|
||||
|
||||
Reference in new issue
Block a user