From f22808f8cadf0b1828e3efb1ed853da4d816da4c Mon Sep 17 00:00:00 2001 From: Mark Thompson <129641948+NotherNgineer@users.noreply.github.com> Date: Wed, 21 Jun 2023 14:56:07 -0500 Subject: [PATCH] Pan & Zoom-in support for GeoMap (#1172) * Zoom-in support * Zoom-in support * Clang formatting * Clang formatting * Zoom-in support * Zoom-in support * Zoom-in support * Roll-back wrong file edited * Zoom-in support * Clang formatting * Clang formatting * Zoom-in support * Zoom-in support * Zoom-in support * Oops copy/paste error --- firmware/application/apps/ui_adsb_rx.cpp | 3 +- firmware/application/ui/ui_geomap.cpp | 151 ++++++++++++++++++----- firmware/application/ui/ui_geomap.hpp | 8 ++ 3 files changed, 131 insertions(+), 31 deletions(-) diff --git a/firmware/application/apps/ui_adsb_rx.cpp b/firmware/application/apps/ui_adsb_rx.cpp index 6a376242b..7607403d6 100644 --- a/firmware/application/apps/ui_adsb_rx.cpp +++ b/firmware/application/apps/ui_adsb_rx.cpp @@ -468,7 +468,8 @@ void ADSBRxView::updateDetailsAndMap(int ageStep) { details_view->update(entry); } // Store if the view is present and the list isn't full - else if (otherMarkersCanBeSent && (markerStored != MARKER_LIST_FULL) && entry.pos.valid && (entry.age_state <= 2)) { + // Note -- Storing the selected entry too, in case map panning occurs + if (otherMarkersCanBeSent && (markerStored != MARKER_LIST_FULL) && entry.pos.valid && (entry.age_state <= 2)) { marker.lon = entry.pos.longitude; marker.lat = entry.pos.latitude; marker.angle = entry.velo.heading; diff --git a/firmware/application/ui/ui_geomap.cpp b/firmware/application/ui/ui_geomap.cpp index 0cd454b31..354af2099 100644 --- a/firmware/application/ui/ui_geomap.cpp +++ b/firmware/application/ui/ui_geomap.cpp @@ -31,6 +31,7 @@ using namespace portapack; #include "string_format.hpp" #include "complex.hpp" +#include "ui_styles.hpp" namespace ui { @@ -80,8 +81,8 @@ GeoPos::GeoPos( } void GeoPos::set_read_only(bool v) { - for (auto child : children_) - child->set_focusable(!v); + // only setting altitude to read-only (allow manual panning via lat/lon fields) + field_altitude.set_focusable(!v); } // Stupid hack to avoid an event loop @@ -90,7 +91,15 @@ void GeoPos::set_report_change(bool v) { } void GeoPos::focus() { - field_altitude.focus(); + if (field_altitude.focusable()) + field_altitude.focus(); + else + field_lat_degrees.focus(); +} + +void GeoPos::hide_altitude() { + // Color altitude grey to indicate it's not updated in manual panning mode + field_altitude.set_style(&Styles::grey); } void GeoPos::set_altitude(int32_t altitude) { @@ -132,11 +141,74 @@ int32_t GeoPos::altitude() { GeoMap::GeoMap( Rect parent_rect) : Widget{parent_rect}, markerListLen(0) { - // set_focusable(true); +} + +bool GeoMap::on_encoder(const EncoderEvent delta) { + if ((delta > 0) && (map_zoom < 5)) { + map_zoom++; + + // Ensure that MOD(240,map_zoom)==0 for the map_zoom_line() function + if (240 % map_zoom != 0) { + map_zoom--; + } + } else if ((delta < 0) && (map_zoom > 1)) { + map_zoom--; + } else { + return false; + } + + // Trigger map redraw + markerListUpdated = true; + set_dirty(); + return true; +} + +void GeoMap::map_zoom_line(ui::Color* buffer) { + if (map_zoom <= 1) { + return; + } + + // As long as MOD(width,map_zoom)==0 then we don't need to check buffer overflow case when stretching last pixel; + // For 240 width, than means no check is needed for map_zoom values up to 6 + for (int i = (240 / map_zoom) - 1; i >= 0; i--) { + for (int j = 0; j < map_zoom; j++) { + buffer[(i * map_zoom) + j] = buffer[i]; + } + } +} + +void GeoMap::draw_markers(Painter& painter) { + const auto r = screen_rect(); + + for (int i = 0; i < markerListLen; ++i) { + GeoMarker& item = markerList[i]; + double lat_rad = sin(item.lat * pi / 180); + int x = (map_width * (item.lon + 180) / 360) - x_pos; + int y = (map_height - ((map_world_lon / 2 * log((1 + lat_rad) / (1 - lat_rad))) - map_offset)) - y_pos; // Offset added for the GUI + + if (map_zoom != 1) { + x = ((x - (r.width() / 2)) * map_zoom) + (r.width() / 2); + y = ((y - (r.height() / 2)) * map_zoom) + (r.height() / 2); + } + + if ((x >= 0) && (x < r.width()) && + (y > 10) && (y < r.height())) // Dont draw within symbol size of top + { + ui::Point itemPoint(x, y + r.top()); + if (y >= 32) { // Dont draw text if it would overlap top + // Text and symbol + draw_marker(painter, itemPoint, item.angle, item.tag, Color::blue(), Color::blue(), Color::magenta()); + } else { + // Only symbol + draw_bearing(itemPoint, item.angle, 10, Color::blue()); + } + } + } } void GeoMap::paint(Painter& painter) { - uint16_t line; + uint16_t line, j; + uint32_t zoom_seek_x, zoom_seek_y; std::array map_line_buffer; const auto r = screen_rect(); @@ -144,39 +216,41 @@ void GeoMap::paint(Painter& painter) { // or the markers list was updated int x_diff = abs(x_pos - prev_x_pos); int y_diff = abs(y_pos - prev_y_pos); - if (markerListUpdated || (x_diff >= 3) || (y_diff >= 3)) { - for (line = 0; line < r.height(); line++) { - map_file.seek(4 + ((x_pos + (map_width * (y_pos + line))) << 1)); - map_file.read(map_line_buffer.data(), r.width() << 1); - display.draw_pixels({0, r.top() + line, r.width(), 1}, map_line_buffer); + if (markerListUpdated || (x_diff >= map_zoom * 3) || (y_diff >= map_zoom * 3)) { + if (map_zoom == 1) { + zoom_seek_x = zoom_seek_y = 0; + } else { + zoom_seek_x = (r.width() - (r.width() / map_zoom)) / 2; + zoom_seek_y = (r.height() - (r.height() / map_zoom)) / 2; + } + + for (line = 0; line < (r.height() / map_zoom); line++) { + map_file.seek(4 + ((x_pos + zoom_seek_x + (map_width * (y_pos + line + zoom_seek_y))) << 1)); + map_file.read(map_line_buffer.data(), (r.width() / map_zoom) << 1); + map_zoom_line(map_line_buffer.data()); + for (j = 0; j < map_zoom; j++) { + display.draw_pixels({0, r.top() + (line * map_zoom) + j, r.width(), 1}, map_line_buffer); + } } prev_x_pos = x_pos; prev_y_pos = y_pos; + // Draw crosshairs in center in manual panning mode + if (manual_panning_) { + display.fill_rectangle({r.center() - Point(16, 1), {32, 2}}, Color::red()); + display.fill_rectangle({r.center() - Point(1, 16), {2, 32}}, Color::red()); + } + // Draw the other markers - for (int i = 0; i < markerListLen; ++i) { - GeoMarker& item = markerList[i]; - double lat_rad = sin(item.lat * pi / 180); - int x = (map_width * (item.lon + 180) / 360) - x_pos; - int y = (map_height - ((map_world_lon / 2 * log((1 + lat_rad) / (1 - lat_rad))) - map_offset)) - y_pos; // Offset added for the GUI - if ((x >= 0) && (x < r.width()) && - (y > 10) && (y < r.height())) // Dont draw within symbol size of top - { - ui::Point itemPoint(x, y + r.top()); - if (y >= 32) { // Dont draw text if it would overlap top - // Text and symbol - draw_marker(painter, itemPoint, item.angle, item.tag, Color::blue(), Color::blue(), Color::magenta()); - } else { - // Only symbol - draw_bearing(itemPoint, item.angle, 10, Color::blue()); - } - } - markerListUpdated = false; - } // Draw the other markers + draw_markers(painter); + markerListUpdated = false; + set_clean(); } // Draw the marker in the center - draw_marker(painter, r.center(), angle_, tag_, Color::red(), Color::white(), Color::black()); + if (!manual_panning_) { + draw_marker(painter, r.center(), angle_, tag_, Color::red(), Color::white(), Color::black()); + } } bool GeoMap::on_touch(const TouchEvent event) { @@ -236,6 +310,14 @@ void GeoMap::set_mode(GeoMapMode mode) { mode_ = mode; } +void GeoMap::set_manual_panning(bool v) { + manual_panning_ = v; +} + +bool GeoMap::manual_panning() { + return manual_panning_; +} + void GeoMap::draw_bearing(const Point origin, const uint16_t angle, uint32_t size, const Color color) { Point arrow_a, arrow_b, arrow_c; @@ -311,6 +393,11 @@ void GeoMapView::focus() { } void GeoMapView::update_position(float lat, float lon, uint16_t angle, int32_t altitude) { + if (geomap.manual_panning()) { + geomap.set_dirty(); + return; + } + lat_ = lat; lon_ = lon; altitude_ = altitude; @@ -342,6 +429,8 @@ void GeoMapView::setup() { altitude_ = altitude; lat_ = lat; lon_ = lon; + geopos.hide_altitude(); + geomap.set_manual_panning(true); geomap.move(lon_, lat_); geomap.set_dirty(); }; @@ -396,6 +485,7 @@ GeoMapView::GeoMapView( geomap.set_tag(tag); geomap.set_angle(angle); geomap.move(lon_, lat_); + geomap.set_focusable(true); geopos.set_read_only(true); } @@ -425,6 +515,7 @@ GeoMapView::GeoMapView( geomap.set_mode(mode_); geomap.move(lon_, lat_); + geomap.set_focusable(true); button_ok.on_select = [this, on_done, &nav](Button&) { if (on_done) diff --git a/firmware/application/ui/ui_geomap.hpp b/firmware/application/ui/ui_geomap.hpp index 97a4dc3cc..0eaca8f21 100644 --- a/firmware/application/ui/ui_geomap.hpp +++ b/firmware/application/ui/ui_geomap.hpp @@ -71,6 +71,7 @@ class GeoPos : public View { void set_lat(float lat); void set_lon(float lon); int32_t altitude(); + void hide_altitude(); float lat(); float lon(); @@ -157,9 +158,12 @@ class GeoMap : public Widget { void paint(Painter& painter) override; bool on_touch(const TouchEvent event) override; + bool on_encoder(const EncoderEvent delta) override; bool init(); void set_mode(GeoMapMode mode); + void set_manual_panning(bool v); + bool manual_panning(); void move(const float lon, const float lat); void set_tag(std::string new_tag) { tag_ = new_tag; @@ -177,11 +181,15 @@ class GeoMap : public Widget { private: void draw_bearing(const Point origin, const uint16_t angle, uint32_t size, const Color color); void draw_marker(Painter& painter, const ui::Point itemPoint, const uint16_t itemAngle, const std::string itemTag, const Color color = Color::red(), const Color fontColor = Color::white(), const Color backColor = Color::black()); + void draw_markers(Painter& painter); + void map_zoom_line(ui::Color* buffer); + bool manual_panning_{false}; GeoMapMode mode_{}; File map_file{}; uint16_t map_width{}, map_height{}; int32_t map_center_x{}, map_center_y{}; + int16_t map_zoom{1}; float lon_ratio{}, lat_ratio{}; double map_bottom{}; double map_world_lon{};