feat(nav): draw waypoints on the trail map

Phase 5b. The Map view now overlays saved waypoints as a hollow-diamond
marker with the label's first character beside it. The bounding box folds
in every waypoint so off-track ones stay in frame, and the map renders
even when the trail is empty but waypoints exist. project() generalised to
projectLL(lat, lon) so the same projection draws track points and waypoints.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-06-03 08:30:16 +02:00
parent 99bfcb1bd7
commit 6f7647481a

View File

@@ -614,7 +614,11 @@ private:
const int top = display.listStart();
const int bottom = display.height() - 2;
if (_store->empty()) {
WaypointStore& wp = _task->waypoints();
const int nwp = wp.count();
const bool have_trail = !_store->empty();
if (!have_trail && nwp == 0) {
display.drawTextCentered(display.width() / 2, (top + bottom) / 2, "No trail yet");
return;
}
@@ -625,11 +629,27 @@ private:
const int area_h = bottom - top - 2;
if (area_w < 6 || area_h < 6) return;
int32_t min_lat, min_lon, max_lat, max_lon;
_store->boundingBox(min_lat, min_lon, max_lat, max_lon);
// Bounding box spans the trail AND every waypoint, so off-track waypoints
// stay in frame.
int32_t min_lat = 0, min_lon = 0, max_lat = 0, max_lon = 0;
bool init = false;
if (have_trail) { _store->boundingBox(min_lat, min_lon, max_lat, max_lon); init = true; }
for (int i = 0; i < nwp; i++) {
const Waypoint& w = wp.at(i);
if (!init) { min_lat = max_lat = w.lat_1e6; min_lon = max_lon = w.lon_1e6; init = true; }
else {
if (w.lat_1e6 < min_lat) min_lat = w.lat_1e6;
if (w.lat_1e6 > max_lat) max_lat = w.lat_1e6;
if (w.lon_1e6 < min_lon) min_lon = w.lon_1e6;
if (w.lon_1e6 > max_lon) max_lon = w.lon_1e6;
}
}
if (_store->count() == 1 || (min_lat == max_lat && min_lon == max_lon)) {
drawCurrentMarker(display, area_x + area_w / 2, area_y + area_h / 2);
// Degenerate: everything at one coordinate — just centre the markers.
if (min_lat == max_lat && min_lon == max_lon) {
int ccx = area_x + area_w / 2, ccy = area_y + area_h / 2;
if (have_trail) drawCurrentMarker(display, ccx, ccy);
if (nwp > 0) drawWaypointMarker(display, ccx, ccy);
return;
}
@@ -649,38 +669,52 @@ private:
int off_x = area_x + (area_w - used_w) / 2;
int off_y = area_y + (area_h - used_h) / 2;
auto projectLL = [&](int32_t lat, int32_t lon, int& px, int& py) {
px = off_x + (int)((float)(lon - min_lon) * lon_scale_geo * scale);
py = off_y + (int)((float)(max_lat - lat) * scale);
};
auto project = [&](const TrailPoint& p, int& px, int& py) {
float dx = (float)(p.lon_1e6 - min_lon) * lon_scale_geo * scale;
float dy = (float)(max_lat - p.lat_1e6) * scale;
px = off_x + (int)dx;
py = off_y + (int)dy;
projectLL(p.lat_1e6, p.lon_1e6, px, py);
};
if (_map_grid)
renderGrid(display, area_x, area_y, area_w, area_h,
min_lat, max_lat, min_lon, lon_scale_geo, scale, off_x, off_y);
int x0, y0;
project(_store->at(0), x0, y0);
display.fillRect(x0, y0, 1, 1);
for (int i = 1; i < _store->count(); i++) {
int x1, y1;
project(_store->at(i), x1, y1);
if (_store->at(i).flags & TRAIL_FLAG_SEG_START) {
drawFilledDot(display, x0, y0);
drawOpenDot(display, x1, y1);
} else {
drawLine(display, x0, y0, x1, y1);
if (have_trail) {
int x0, y0;
project(_store->at(0), x0, y0);
display.fillRect(x0, y0, 1, 1);
for (int i = 1; i < _store->count(); i++) {
int x1, y1;
project(_store->at(i), x1, y1);
if (_store->at(i).flags & TRAIL_FLAG_SEG_START) {
drawFilledDot(display, x0, y0);
drawOpenDot(display, x1, y1);
} else {
drawLine(display, x0, y0, x1, y1);
}
x0 = x1;
y0 = y1;
}
x0 = x1;
y0 = y1;
int sx, sy, ex, ey;
project(_store->first(), sx, sy);
project(_store->last(), ex, ey);
drawStartMarker(display, sx, sy);
drawCurrentMarker(display, ex, ey);
}
int sx, sy, ex, ey;
project(_store->first(), sx, sy);
project(_store->last(), ex, ey);
drawStartMarker(display, sx, sy);
drawCurrentMarker(display, ex, ey);
// Waypoints on top, with the label's first character beside the marker.
for (int i = 0; i < nwp; i++) {
const Waypoint& w = wp.at(i);
int wx, wy; projectLL(w.lat_1e6, w.lon_1e6, wx, wy);
drawWaypointMarker(display, wx, wy);
if (w.label[0] && wx + 3 + display.getCharWidth() <= area_x + area_w) {
char s[2] = { w.label[0], 0 };
display.setCursor(wx + 3, wy - 3);
display.print(s);
}
}
drawNorthArrow(display, area_x + area_w - 4, area_y + display.getLineHeight() + 1);
}
@@ -814,6 +848,15 @@ private:
static void drawFilledDot(DisplayDriver& d, int cx, int cy) {
d.fillRect(cx - 1, cy - 1, 3, 3);
}
// Hollow diamond — distinct from the trail's dots (○●), start (+) and
// current (✕) markers.
static void drawWaypointMarker(DisplayDriver& d, int cx, int cy) {
d.fillRect(cx, cy - 2, 1, 1);
d.fillRect(cx - 1, cy - 1, 1, 1); d.fillRect(cx + 1, cy - 1, 1, 1);
d.fillRect(cx - 2, cy, 1, 1); d.fillRect(cx + 2, cy, 1, 1);
d.fillRect(cx - 1, cy + 1, 1, 1); d.fillRect(cx + 1, cy + 1, 1, 1);
d.fillRect(cx, cy + 2, 1, 1);
}
static void drawOpenDot(DisplayDriver& d, int cx, int cy) {
d.fillRect(cx - 1, cy - 1, 3, 1);
d.fillRect(cx - 1, cy + 1, 3, 1);