2017-07-22 18:30:20 +00:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
|
|
|
* Copyright (C) 2017 Furrtek
|
2023-05-18 20:16:05 +00:00
|
|
|
*
|
2017-07-22 18:30:20 +00:00
|
|
|
* This file is part of PortaPack.
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
|
|
* any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; see the file COPYING. If not, write to
|
|
|
|
* the Free Software Foundation, Inc., 51 Franklin Street,
|
|
|
|
* Boston, MA 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "ui_geomap.hpp"
|
|
|
|
|
|
|
|
#include "portapack.hpp"
|
|
|
|
|
|
|
|
#include <cstring>
|
|
|
|
#include <stdio.h>
|
|
|
|
|
|
|
|
using namespace portapack;
|
|
|
|
|
2017-10-28 17:16:06 +00:00
|
|
|
#include "string_format.hpp"
|
2020-05-06 14:28:38 +00:00
|
|
|
#include "complex.hpp"
|
2023-06-21 19:56:07 +00:00
|
|
|
#include "ui_styles.hpp"
|
2023-07-24 12:52:30 +00:00
|
|
|
#include "ui_font_fixed_5x8.hpp"
|
2017-10-28 17:16:06 +00:00
|
|
|
|
2017-07-22 18:30:20 +00:00
|
|
|
namespace ui {
|
|
|
|
|
2017-08-03 18:06:59 +00:00
|
|
|
GeoPos::GeoPos(
|
2023-05-18 20:16:05 +00:00
|
|
|
const Point pos,
|
2024-01-05 12:44:30 +00:00
|
|
|
const alt_unit altitude_unit,
|
|
|
|
const spd_unit speed_unit)
|
|
|
|
: altitude_unit_(altitude_unit), speed_unit_(speed_unit) {
|
2023-05-18 20:16:05 +00:00
|
|
|
set_parent_rect({pos, {30 * 8, 3 * 16}});
|
|
|
|
|
|
|
|
add_children({&labels_position,
|
2024-01-05 12:44:30 +00:00
|
|
|
&label_spd_position,
|
2023-05-18 20:16:05 +00:00
|
|
|
&field_altitude,
|
2024-01-05 12:44:30 +00:00
|
|
|
&field_speed,
|
2023-05-18 20:16:05 +00:00
|
|
|
&text_alt_unit,
|
2024-01-05 12:44:30 +00:00
|
|
|
&text_speed_unit,
|
2023-05-18 20:16:05 +00:00
|
|
|
&field_lat_degrees,
|
|
|
|
&field_lat_minutes,
|
|
|
|
&field_lat_seconds,
|
|
|
|
&text_lat_decimal,
|
|
|
|
&field_lon_degrees,
|
|
|
|
&field_lon_minutes,
|
|
|
|
&field_lon_seconds,
|
|
|
|
&text_lon_decimal});
|
|
|
|
|
|
|
|
// Defaults
|
|
|
|
set_altitude(0);
|
2024-01-05 12:44:30 +00:00
|
|
|
set_speed(0);
|
2023-05-18 20:16:05 +00:00
|
|
|
set_lat(0);
|
|
|
|
set_lon(0);
|
|
|
|
|
|
|
|
const auto changed_fn = [this](int32_t) {
|
|
|
|
float lat_value = lat();
|
|
|
|
float lon_value = lon();
|
|
|
|
|
|
|
|
text_lat_decimal.set(to_string_decimal(lat_value, 5));
|
|
|
|
text_lon_decimal.set(to_string_decimal(lon_value, 5));
|
|
|
|
|
|
|
|
if (on_change && report_change)
|
2024-01-05 12:44:30 +00:00
|
|
|
on_change(altitude(), lat_value, lon_value, speed());
|
2023-05-18 20:16:05 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
field_altitude.on_change = changed_fn;
|
2024-01-05 12:44:30 +00:00
|
|
|
field_speed.on_change = changed_fn;
|
2023-05-18 20:16:05 +00:00
|
|
|
field_lat_degrees.on_change = changed_fn;
|
|
|
|
field_lat_minutes.on_change = changed_fn;
|
|
|
|
field_lat_seconds.on_change = changed_fn;
|
|
|
|
field_lon_degrees.on_change = changed_fn;
|
|
|
|
field_lon_minutes.on_change = changed_fn;
|
|
|
|
field_lon_seconds.on_change = changed_fn;
|
|
|
|
|
|
|
|
text_alt_unit.set(altitude_unit_ ? "m" : "ft");
|
2024-01-05 12:44:30 +00:00
|
|
|
if (speed_unit_ == KMPH) text_speed_unit.set("kmph");
|
|
|
|
if (speed_unit_ == MPH) text_speed_unit.set("mph");
|
|
|
|
if (speed_unit_ == HIDDEN) {
|
|
|
|
text_speed_unit.hidden(true);
|
|
|
|
label_spd_position.hidden(true);
|
|
|
|
field_speed.hidden(true);
|
|
|
|
}
|
2017-08-12 06:07:21 +00:00
|
|
|
}
|
|
|
|
|
2017-10-28 17:16:06 +00:00
|
|
|
void GeoPos::set_read_only(bool v) {
|
2023-06-21 19:56:07 +00:00
|
|
|
// only setting altitude to read-only (allow manual panning via lat/lon fields)
|
|
|
|
field_altitude.set_focusable(!v);
|
2024-01-05 12:44:30 +00:00
|
|
|
field_speed.set_focusable(!v);
|
2017-10-28 17:16:06 +00:00
|
|
|
}
|
|
|
|
|
2017-08-12 06:07:21 +00:00
|
|
|
// Stupid hack to avoid an event loop
|
|
|
|
void GeoPos::set_report_change(bool v) {
|
2023-05-18 20:16:05 +00:00
|
|
|
report_change = v;
|
2017-08-03 18:06:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void GeoPos::focus() {
|
2023-06-21 19:56:07 +00:00
|
|
|
if (field_altitude.focusable())
|
|
|
|
field_altitude.focus();
|
|
|
|
else
|
|
|
|
field_lat_degrees.focus();
|
|
|
|
}
|
|
|
|
|
2024-01-05 12:44:30 +00:00
|
|
|
void GeoPos::hide_altandspeed() {
|
2023-06-21 19:56:07 +00:00
|
|
|
// Color altitude grey to indicate it's not updated in manual panning mode
|
|
|
|
field_altitude.set_style(&Styles::grey);
|
2024-01-05 12:44:30 +00:00
|
|
|
field_speed.set_style(&Styles::grey);
|
2017-08-03 18:06:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void GeoPos::set_altitude(int32_t altitude) {
|
2023-05-18 20:16:05 +00:00
|
|
|
field_altitude.set_value(altitude);
|
2017-08-03 18:06:59 +00:00
|
|
|
}
|
2024-01-05 12:44:30 +00:00
|
|
|
void GeoPos::set_speed(int32_t speed) {
|
|
|
|
field_speed.set_value(speed);
|
|
|
|
}
|
2017-08-03 18:06:59 +00:00
|
|
|
|
|
|
|
void GeoPos::set_lat(float lat) {
|
2023-05-18 20:16:05 +00:00
|
|
|
field_lat_degrees.set_value(lat);
|
|
|
|
field_lat_minutes.set_value((uint32_t)abs(lat / (1.0 / 60)) % 60);
|
|
|
|
field_lat_seconds.set_value((uint32_t)abs(lat / (1.0 / 3600)) % 60);
|
2017-08-03 18:06:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void GeoPos::set_lon(float lon) {
|
2023-05-18 20:16:05 +00:00
|
|
|
field_lon_degrees.set_value(lon);
|
|
|
|
field_lon_minutes.set_value((uint32_t)abs(lon / (1.0 / 60)) % 60);
|
|
|
|
field_lon_seconds.set_value((uint32_t)abs(lon / (1.0 / 3600)) % 60);
|
2017-08-03 18:06:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
float GeoPos::lat() {
|
2023-05-18 20:16:05 +00:00
|
|
|
if (field_lat_degrees.value() < 0) {
|
|
|
|
return -1 * (-1 * field_lat_degrees.value() + (field_lat_minutes.value() / 60.0) + (field_lat_seconds.value() / 3600.0));
|
|
|
|
} else {
|
|
|
|
return field_lat_degrees.value() + (field_lat_minutes.value() / 60.0) + (field_lat_seconds.value() / 3600.0);
|
|
|
|
}
|
2017-08-03 18:06:59 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
float GeoPos::lon() {
|
2023-05-18 20:16:05 +00:00
|
|
|
if (field_lon_degrees.value() < 0) {
|
|
|
|
return -1 * (-1 * field_lon_degrees.value() + (field_lon_minutes.value() / 60.0) + (field_lon_seconds.value() / 3600.0));
|
|
|
|
} else {
|
|
|
|
return field_lon_degrees.value() + (field_lon_minutes.value() / 60.0) + (field_lon_seconds.value() / 3600.0);
|
|
|
|
}
|
2017-08-03 18:06:59 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
int32_t GeoPos::altitude() {
|
2023-05-18 20:16:05 +00:00
|
|
|
return field_altitude.value();
|
2017-08-03 18:06:59 +00:00
|
|
|
};
|
|
|
|
|
2024-01-05 12:44:30 +00:00
|
|
|
int32_t GeoPos::speed() {
|
|
|
|
return field_speed.value();
|
|
|
|
};
|
|
|
|
|
2017-08-12 06:07:21 +00:00
|
|
|
GeoMap::GeoMap(
|
2023-05-18 20:16:05 +00:00
|
|
|
Rect parent_rect)
|
|
|
|
: Widget{parent_rect}, markerListLen(0) {
|
2023-06-21 19:56:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool GeoMap::on_encoder(const EncoderEvent delta) {
|
2024-01-04 06:32:44 +00:00
|
|
|
// Valid map_zoom values are -2 to -MAX_MAP_ZOOM_OUT, and +1 to +MAX_MAP_ZOOM_IN (values of 0 and -1 are not permitted)
|
|
|
|
if (delta > 0) {
|
|
|
|
if (map_zoom < MAX_MAP_ZOOM_IN) {
|
|
|
|
if (map_zoom == -2) {
|
|
|
|
map_zoom = 1;
|
|
|
|
} else {
|
|
|
|
map_zoom++;
|
2023-06-21 19:56:07 +00:00
|
|
|
|
2024-01-04 06:32:44 +00:00
|
|
|
// When zooming in, ensure that MOD(240,map_zoom)==0 for the map_zoom_line() function
|
|
|
|
if ((map_zoom > 1) && (geomap_rect_width % map_zoom != 0)) {
|
|
|
|
map_zoom--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (delta < 0) {
|
|
|
|
if (map_zoom > -MAX_MAP_ZOOM_OUT) {
|
|
|
|
if (map_zoom == 1) {
|
|
|
|
map_zoom = -2;
|
|
|
|
} else {
|
|
|
|
map_zoom--;
|
|
|
|
}
|
2023-06-21 19:56:07 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Trigger map redraw
|
|
|
|
markerListUpdated = true;
|
|
|
|
set_dirty();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-01-04 06:32:44 +00:00
|
|
|
void GeoMap::map_read_line(ui::Color* buffer, uint16_t pixels) {
|
|
|
|
if (map_zoom == 1) {
|
|
|
|
map_file.read(buffer, pixels << 1);
|
|
|
|
} else if (map_zoom > 1) {
|
|
|
|
map_file.read(buffer, (pixels / map_zoom) << 1);
|
|
|
|
|
|
|
|
// Zoom in: Expand each pixel to "map_zoom" number of pixels.
|
|
|
|
// Future TODO: Add dithering to smooth out the pixelation.
|
|
|
|
// 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 = (geomap_rect_width / map_zoom) - 1; i >= 0; i--) {
|
|
|
|
for (int j = 0; j < map_zoom; j++) {
|
|
|
|
buffer[(i * map_zoom) + j] = buffer[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ui::Color* zoom_out_buffer = new ui::Color[(pixels * (-map_zoom))];
|
|
|
|
map_file.read(zoom_out_buffer, (pixels * (-map_zoom)) << 1);
|
2023-06-21 19:56:07 +00:00
|
|
|
|
2024-01-04 06:32:44 +00:00
|
|
|
// Zoom out: Collapse each group of "-map_zoom" pixels into one pixel.
|
|
|
|
// Future TODO: Average each group of pixels (in both X & Y directions if possible).
|
|
|
|
for (int i = 0; i < geomap_rect_width; i++) {
|
|
|
|
buffer[i] = zoom_out_buffer[i * (-map_zoom)];
|
2023-06-21 19:56:07 +00:00
|
|
|
}
|
2024-01-04 06:32:44 +00:00
|
|
|
delete zoom_out_buffer;
|
2023-06-21 19:56:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
|
2024-01-04 06:32:44 +00:00
|
|
|
if (map_zoom > 1) {
|
2023-06-21 19:56:07 +00:00
|
|
|
x = ((x - (r.width() / 2)) * map_zoom) + (r.width() / 2);
|
|
|
|
y = ((y - (r.height() / 2)) * map_zoom) + (r.height() / 2);
|
2024-01-04 06:32:44 +00:00
|
|
|
} else if (map_zoom < 0) {
|
|
|
|
x = ((x - (r.width() / 2)) / (-map_zoom)) + (r.width() / 2);
|
|
|
|
y = ((y - (r.height() / 2)) / (-map_zoom)) + (r.height() / 2);
|
2023-06-21 19:56:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-07-22 18:30:20 +00:00
|
|
|
}
|
|
|
|
|
2017-08-12 06:07:21 +00:00
|
|
|
void GeoMap::paint(Painter& painter) {
|
2023-05-18 20:16:05 +00:00
|
|
|
const auto r = screen_rect();
|
2024-01-04 06:32:44 +00:00
|
|
|
std::array<ui::Color, 240> map_line_buffer;
|
2023-05-18 20:16:05 +00:00
|
|
|
|
|
|
|
// Ony redraw map if it moved by at least 1 pixel
|
|
|
|
// or the markers list was updated
|
|
|
|
int x_diff = abs(x_pos - prev_x_pos);
|
|
|
|
int y_diff = abs(y_pos - prev_y_pos);
|
2024-01-04 06:32:44 +00:00
|
|
|
|
|
|
|
if (markerListUpdated || (x_diff >= 3) || (y_diff >= 3)) {
|
|
|
|
int32_t zoom_seek_x = x_pos;
|
|
|
|
int32_t zoom_seek_y = y_pos;
|
|
|
|
|
|
|
|
// Adjust starting corner position of map per zoom setting
|
|
|
|
if (map_zoom > 1) {
|
|
|
|
zoom_seek_x += (r.width() - (r.width() / map_zoom)) / 2;
|
|
|
|
zoom_seek_y += (r.height() - (r.height() / map_zoom)) / 2;
|
|
|
|
} else if (map_zoom < 0) {
|
|
|
|
zoom_seek_x += (r.width() - (r.width() * (-map_zoom))) / 2;
|
|
|
|
zoom_seek_y += (r.height() - (r.height() * (-map_zoom))) / 2;
|
2023-06-21 19:56:07 +00:00
|
|
|
}
|
|
|
|
|
2024-01-04 06:32:44 +00:00
|
|
|
// Read from map file and display to zoomed scale
|
|
|
|
int duplicate_lines = (map_zoom < 0) ? 1 : map_zoom;
|
|
|
|
for (uint16_t line = 0; line < (r.height() / duplicate_lines); line++) {
|
|
|
|
uint16_t seek_line = zoom_seek_y + ((map_zoom >= 0) ? line : line * (-map_zoom));
|
|
|
|
map_file.seek(4 + ((zoom_seek_x + (map_width * seek_line)) << 1));
|
|
|
|
map_read_line(map_line_buffer.data(), r.width());
|
|
|
|
|
|
|
|
for (uint16_t j = 0; j < duplicate_lines; j++) {
|
|
|
|
display.draw_pixels({0, r.top() + (line * duplicate_lines) + j, r.width(), 1}, map_line_buffer);
|
2023-06-21 19:56:07 +00:00
|
|
|
}
|
2023-05-18 20:16:05 +00:00
|
|
|
}
|
2024-01-04 06:32:44 +00:00
|
|
|
|
2023-05-18 20:16:05 +00:00
|
|
|
prev_x_pos = x_pos;
|
|
|
|
prev_y_pos = y_pos;
|
|
|
|
|
2023-06-21 19:56:07 +00:00
|
|
|
// 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());
|
|
|
|
}
|
|
|
|
|
2023-05-18 20:16:05 +00:00
|
|
|
// Draw the other markers
|
2023-06-21 19:56:07 +00:00
|
|
|
draw_markers(painter);
|
2023-07-24 12:52:30 +00:00
|
|
|
draw_scale(painter);
|
2024-01-11 16:46:35 +00:00
|
|
|
draw_mypos();
|
2023-06-21 19:56:07 +00:00
|
|
|
markerListUpdated = false;
|
|
|
|
set_clean();
|
2023-05-18 20:16:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Draw the marker in the center
|
2023-06-21 19:56:07 +00:00
|
|
|
if (!manual_panning_) {
|
|
|
|
draw_marker(painter, r.center(), angle_, tag_, Color::red(), Color::white(), Color::black());
|
|
|
|
}
|
2017-07-22 18:30:20 +00:00
|
|
|
}
|
|
|
|
|
2024-01-04 16:36:31 +00:00
|
|
|
bool GeoMap::on_keyboard(KeyboardEvent key) {
|
|
|
|
if (key == '+' || key == ' ') return on_encoder(1);
|
|
|
|
if (key == '-') return on_encoder(-1);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-08-12 06:07:21 +00:00
|
|
|
bool GeoMap::on_touch(const TouchEvent event) {
|
2023-05-18 20:16:05 +00:00
|
|
|
if ((event.type == TouchEvent::Type::Start) && (mode_ == PROMPT)) {
|
|
|
|
set_highlighted(true);
|
|
|
|
if (on_move) {
|
|
|
|
Point p = event.point - screen_rect().center();
|
|
|
|
on_move(p.x() / 2.0 * lon_ratio, p.y() / 2.0 * lat_ratio);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
2017-07-22 18:30:20 +00:00
|
|
|
}
|
|
|
|
|
2017-08-12 06:07:21 +00:00
|
|
|
void GeoMap::move(const float lon, const float lat) {
|
2023-05-18 20:16:05 +00:00
|
|
|
lon_ = lon;
|
|
|
|
lat_ = lat;
|
|
|
|
|
|
|
|
// Using WGS 84/Pseudo-Mercator projection
|
2023-10-11 05:44:56 +00:00
|
|
|
x_pos = map_width * (lon_ + 180) / 360 - (geomap_rect_width / 2);
|
2023-05-18 20:16:05 +00:00
|
|
|
|
|
|
|
// Latitude calculation based on https://stackoverflow.com/a/10401734/2278659
|
|
|
|
double lat_rad = sin(lat * pi / 180);
|
|
|
|
y_pos = map_height - ((map_world_lon / 2 * log((1 + lat_rad) / (1 - lat_rad))) - map_offset) - 128; // Offset added for the GUI
|
|
|
|
|
|
|
|
// Cap position
|
2023-10-11 05:44:56 +00:00
|
|
|
if (x_pos > (map_width - geomap_rect_width))
|
|
|
|
x_pos = map_width - geomap_rect_width;
|
|
|
|
if (y_pos > (map_height + geomap_rect_height))
|
|
|
|
y_pos = map_height - geomap_rect_height;
|
2023-07-24 12:52:30 +00:00
|
|
|
|
|
|
|
// Scale calculation
|
|
|
|
float km_per_deg_lon = cos(lat * pi / 180) * 111.321; // 111.321 km/deg longitude at equator, and 0 km at poles
|
2023-10-11 05:44:56 +00:00
|
|
|
pixels_per_km = (geomap_rect_width / 2) / km_per_deg_lon;
|
2017-07-22 18:30:20 +00:00
|
|
|
}
|
|
|
|
|
2017-08-12 06:07:21 +00:00
|
|
|
bool GeoMap::init() {
|
2023-05-18 20:16:05 +00:00
|
|
|
auto result = map_file.open("ADSB/world_map.bin");
|
|
|
|
if (result.is_valid())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
map_file.read(&map_width, 2);
|
|
|
|
map_file.read(&map_height, 2);
|
|
|
|
|
|
|
|
map_center_x = map_width >> 1;
|
|
|
|
map_center_y = map_height >> 1;
|
|
|
|
|
|
|
|
lon_ratio = 180.0 / map_center_x;
|
|
|
|
lat_ratio = -90.0 / map_center_y;
|
|
|
|
|
|
|
|
map_bottom = sin(-85.05 * pi / 180); // Map bitmap only goes from about -85 to 85 lat
|
|
|
|
map_world_lon = map_width / (2 * pi);
|
2023-02-28 19:09:05 +00:00
|
|
|
map_offset = (map_world_lon / 2 * log((1 + map_bottom) / (1 - map_bottom)));
|
|
|
|
|
2023-05-18 20:16:05 +00:00
|
|
|
return true;
|
2017-08-12 06:07:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void GeoMap::set_mode(GeoMapMode mode) {
|
2023-05-18 20:16:05 +00:00
|
|
|
mode_ = mode;
|
2017-08-12 06:07:21 +00:00
|
|
|
}
|
|
|
|
|
2023-06-21 19:56:07 +00:00
|
|
|
void GeoMap::set_manual_panning(bool v) {
|
|
|
|
manual_panning_ = v;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool GeoMap::manual_panning() {
|
|
|
|
return manual_panning_;
|
|
|
|
}
|
|
|
|
|
2023-07-24 12:52:30 +00:00
|
|
|
void GeoMap::draw_scale(Painter& painter) {
|
2024-01-04 06:32:44 +00:00
|
|
|
uint16_t km = 800;
|
|
|
|
uint16_t scale_width = (map_zoom > 0) ? km * pixels_per_km * map_zoom : km * pixels_per_km / (-map_zoom);
|
2023-07-24 12:52:30 +00:00
|
|
|
|
|
|
|
while (scale_width > screen_width / 2) {
|
|
|
|
scale_width /= 2;
|
|
|
|
km /= 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string km_string = to_string_dec_uint(km) + " km";
|
|
|
|
|
|
|
|
display.fill_rectangle({{screen_width - 5 - scale_width, screen_height - 4}, {scale_width, 2}}, Color::black());
|
|
|
|
display.fill_rectangle({{screen_width - 5, screen_height - 8}, {2, 6}}, Color::black());
|
|
|
|
display.fill_rectangle({{screen_width - 5 - scale_width, screen_height - 8}, {2, 6}}, Color::black());
|
|
|
|
|
|
|
|
painter.draw_string({(uint16_t)(screen_width - 25 - scale_width - km_string.length() * 5 / 2), screen_height - 10}, ui::font::fixed_5x8, Color::black(), Color::white(), km_string);
|
|
|
|
}
|
|
|
|
|
2020-07-24 21:09:21 +00:00
|
|
|
void GeoMap::draw_bearing(const Point origin, const uint16_t angle, uint32_t size, const Color color) {
|
2023-05-18 20:16:05 +00:00
|
|
|
Point arrow_a, arrow_b, arrow_c;
|
|
|
|
|
|
|
|
for (size_t thickness = 0; thickness < 3; thickness++) {
|
|
|
|
arrow_a = fast_polar_to_point((int)angle, size) + origin;
|
|
|
|
arrow_b = fast_polar_to_point((int)(angle + 180 - 35), size) + origin;
|
|
|
|
arrow_c = fast_polar_to_point((int)(angle + 180 + 35), size) + origin;
|
|
|
|
|
|
|
|
display.draw_line(arrow_a, arrow_b, color);
|
|
|
|
display.draw_line(arrow_b, arrow_c, color);
|
|
|
|
display.draw_line(arrow_c, arrow_a, color);
|
2017-08-12 06:07:21 +00:00
|
|
|
|
2023-05-18 20:16:05 +00:00
|
|
|
size--;
|
|
|
|
}
|
2023-02-28 19:09:05 +00:00
|
|
|
}
|
|
|
|
|
2023-05-18 20:16:05 +00:00
|
|
|
void GeoMap::draw_marker(Painter& painter, const ui::Point itemPoint, const uint16_t itemAngle, const std::string itemTag, const Color color, const Color fontColor, const Color backColor) {
|
|
|
|
int tagOffset = 10;
|
|
|
|
if (mode_ == PROMPT) {
|
|
|
|
// Cross
|
|
|
|
display.fill_rectangle({itemPoint - Point(16, 1), {32, 2}}, color);
|
|
|
|
display.fill_rectangle({itemPoint - Point(1, 16), {2, 32}}, color);
|
|
|
|
tagOffset = 16;
|
|
|
|
} else if (angle_ < 360) {
|
|
|
|
// if we have a valid angle draw bearing
|
|
|
|
draw_bearing(itemPoint, itemAngle, 10, color);
|
|
|
|
tagOffset = 10;
|
|
|
|
} else {
|
|
|
|
// draw a small cross
|
|
|
|
display.fill_rectangle({itemPoint - Point(8, 1), {16, 2}}, color);
|
|
|
|
display.fill_rectangle({itemPoint - Point(1, 8), {2, 16}}, color);
|
|
|
|
tagOffset = 8;
|
|
|
|
}
|
|
|
|
// center tag above point
|
|
|
|
if (itemTag.find_first_not_of(' ') != itemTag.npos) { // only draw tag if we have something other than spaces
|
|
|
|
painter.draw_string(itemPoint - Point(((int)itemTag.length() * 8 / 2), 14 + tagOffset),
|
|
|
|
style().font, fontColor, backColor, itemTag);
|
|
|
|
}
|
2023-02-28 19:09:05 +00:00
|
|
|
}
|
|
|
|
|
2024-01-11 16:46:35 +00:00
|
|
|
void GeoMap::draw_mypos() {
|
|
|
|
const auto r = screen_rect();
|
|
|
|
if (my_lat >= 200 || my_lon >= 200) return; // invalid
|
|
|
|
int x = (map_width * (my_lon + 180) / 360) - x_pos;
|
|
|
|
double lat_rad = sin(my_lat * pi / 180);
|
|
|
|
int y = (map_height - ((map_world_lon / 2 * log((1 + lat_rad) / (1 - lat_rad))) - map_offset)) - y_pos; // Offset added for the GUI
|
|
|
|
auto color = Color::yellow();
|
|
|
|
|
|
|
|
if (map_zoom > 1) {
|
|
|
|
x = ((x - (r.width() / 2)) * map_zoom) + (r.width() / 2);
|
|
|
|
y = ((y - (r.height() / 2)) * map_zoom) + (r.height() / 2);
|
|
|
|
} else if (map_zoom < 0) {
|
|
|
|
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 (mode_ == PROMPT) {
|
|
|
|
// Cross
|
|
|
|
display.fill_rectangle({itemPoint - Point(16, 1), {32, 2}}, color);
|
|
|
|
display.fill_rectangle({itemPoint - Point(1, 16), {2, 32}}, color);
|
|
|
|
} else if (my_angle < 360) {
|
|
|
|
// if we have a valid angle draw bearing
|
|
|
|
draw_bearing(itemPoint, my_angle, 10, color);
|
|
|
|
} else {
|
|
|
|
// draw a small cross
|
|
|
|
display.fill_rectangle({itemPoint - Point(8, 1), {16, 2}}, color);
|
|
|
|
display.fill_rectangle({itemPoint - Point(1, 8), {2, 16}}, color);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-18 20:16:05 +00:00
|
|
|
void GeoMap::clear_markers() {
|
|
|
|
markerListLen = 0;
|
2023-02-28 19:09:05 +00:00
|
|
|
}
|
|
|
|
|
2023-05-18 20:16:05 +00:00
|
|
|
MapMarkerStored GeoMap::store_marker(GeoMarker& marker) {
|
|
|
|
MapMarkerStored ret;
|
|
|
|
|
|
|
|
// Check if it could be on screen
|
|
|
|
// Only checking one direction to reduce CPU
|
|
|
|
double lat_rad = sin(marker.lat * pi / 180);
|
|
|
|
int x = (map_width * (marker.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
|
2023-10-11 05:44:56 +00:00
|
|
|
if (false == ((x >= 0) && (x < geomap_rect_width) && (y > 10) && (y < geomap_rect_height))) // Dont draw within symbol size of top
|
2023-05-18 20:16:05 +00:00
|
|
|
{
|
|
|
|
ret = MARKER_NOT_STORED;
|
|
|
|
} else if (markerListLen < NumMarkerListElements) {
|
|
|
|
markerList[markerListLen] = marker;
|
|
|
|
markerListLen++;
|
|
|
|
markerListUpdated = true;
|
|
|
|
ret = MARKER_STORED;
|
|
|
|
} else {
|
|
|
|
ret = MARKER_LIST_FULL;
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
2023-02-28 19:09:05 +00:00
|
|
|
|
2024-01-11 16:46:35 +00:00
|
|
|
void GeoMap::update_my_position(float lat, float lon, int32_t altitude) {
|
|
|
|
my_lat = lat;
|
|
|
|
my_lon = lon;
|
|
|
|
my_altitude = altitude;
|
|
|
|
markerListUpdated = true;
|
|
|
|
set_dirty();
|
|
|
|
}
|
|
|
|
void GeoMap::update_my_orientation(uint16_t angle) {
|
|
|
|
my_angle = angle;
|
|
|
|
}
|
|
|
|
|
2017-08-12 06:07:21 +00:00
|
|
|
void GeoMapView::focus() {
|
2023-05-18 20:16:05 +00:00
|
|
|
geopos.focus();
|
|
|
|
|
|
|
|
if (!map_opened)
|
2023-09-27 19:03:02 +00:00
|
|
|
nav_.display_modal("No map", "No world_map.bin file in\n/ADSB/ directory", ABORT);
|
2017-08-12 06:07:21 +00:00
|
|
|
}
|
|
|
|
|
2024-01-11 16:46:35 +00:00
|
|
|
void GeoMapView::update_my_position(float lat, float lon, int32_t altitude) {
|
|
|
|
geomap.update_my_position(lat, lon, altitude);
|
|
|
|
}
|
|
|
|
void GeoMapView::update_my_orientation(uint16_t angle) {
|
|
|
|
geomap.update_my_orientation(angle);
|
|
|
|
}
|
|
|
|
|
2024-01-05 12:44:30 +00:00
|
|
|
void GeoMapView::update_position(float lat, float lon, uint16_t angle, int32_t altitude, int32_t speed) {
|
2023-06-21 19:56:07 +00:00
|
|
|
if (geomap.manual_panning()) {
|
|
|
|
geomap.set_dirty();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-05-18 20:16:05 +00:00
|
|
|
lat_ = lat;
|
|
|
|
lon_ = lon;
|
|
|
|
altitude_ = altitude;
|
2024-01-05 12:44:30 +00:00
|
|
|
speed_ = speed;
|
2023-05-18 20:16:05 +00:00
|
|
|
|
|
|
|
// Stupid hack to avoid an event loop
|
|
|
|
geopos.set_report_change(false);
|
|
|
|
geopos.set_lat(lat_);
|
|
|
|
geopos.set_lon(lon_);
|
|
|
|
geopos.set_altitude(altitude_);
|
2024-01-05 12:44:30 +00:00
|
|
|
geopos.set_speed(speed_);
|
2023-05-18 20:16:05 +00:00
|
|
|
geopos.set_report_change(true);
|
|
|
|
|
|
|
|
geomap.set_angle(angle);
|
|
|
|
geomap.move(lon_, lat_);
|
|
|
|
geomap.set_dirty();
|
2017-08-27 21:06:11 +00:00
|
|
|
}
|
2023-02-28 19:09:05 +00:00
|
|
|
|
|
|
|
void GeoMapView::update_tag(const std::string tag) {
|
2023-05-18 20:16:05 +00:00
|
|
|
geomap.set_tag(tag);
|
2023-02-28 19:09:05 +00:00
|
|
|
}
|
2023-05-18 20:16:05 +00:00
|
|
|
|
2017-08-12 06:07:21 +00:00
|
|
|
void GeoMapView::setup() {
|
2023-05-18 20:16:05 +00:00
|
|
|
add_child(&geomap);
|
|
|
|
|
|
|
|
geopos.set_altitude(altitude_);
|
|
|
|
geopos.set_lat(lat_);
|
|
|
|
geopos.set_lon(lon_);
|
|
|
|
|
2024-01-05 12:44:30 +00:00
|
|
|
geopos.on_change = [this](int32_t altitude, float lat, float lon, int32_t speed) {
|
2023-05-18 20:16:05 +00:00
|
|
|
altitude_ = altitude;
|
|
|
|
lat_ = lat;
|
|
|
|
lon_ = lon;
|
2024-01-05 12:44:30 +00:00
|
|
|
speed_ = speed;
|
|
|
|
geopos.hide_altandspeed();
|
2023-06-21 19:56:07 +00:00
|
|
|
geomap.set_manual_panning(true);
|
2023-05-18 20:16:05 +00:00
|
|
|
geomap.move(lon_, lat_);
|
|
|
|
geomap.set_dirty();
|
|
|
|
};
|
|
|
|
|
|
|
|
geomap.on_move = [this](float move_x, float move_y) {
|
|
|
|
lon_ += move_x;
|
|
|
|
lat_ += move_y;
|
|
|
|
|
|
|
|
// Stupid hack to avoid an event loop
|
|
|
|
geopos.set_report_change(false);
|
|
|
|
geopos.set_lon(lon_);
|
|
|
|
geopos.set_lat(lat_);
|
|
|
|
geopos.set_report_change(true);
|
|
|
|
|
|
|
|
geomap.move(lon_, lat_);
|
|
|
|
geomap.set_dirty();
|
|
|
|
};
|
2017-08-03 18:06:59 +00:00
|
|
|
}
|
|
|
|
|
2017-08-28 06:05:31 +00:00
|
|
|
GeoMapView::~GeoMapView() {
|
2023-05-18 20:16:05 +00:00
|
|
|
if (on_close_)
|
|
|
|
on_close_();
|
2017-08-28 06:05:31 +00:00
|
|
|
}
|
|
|
|
|
2017-08-03 18:06:59 +00:00
|
|
|
// Display mode
|
|
|
|
GeoMapView::GeoMapView(
|
2023-05-18 20:16:05 +00:00
|
|
|
NavigationView& nav,
|
|
|
|
const std::string& tag,
|
|
|
|
int32_t altitude,
|
|
|
|
GeoPos::alt_unit altitude_unit,
|
2024-01-05 12:44:30 +00:00
|
|
|
GeoPos::spd_unit speed_unit,
|
2023-05-18 20:16:05 +00:00
|
|
|
float lat,
|
|
|
|
float lon,
|
|
|
|
uint16_t angle,
|
|
|
|
const std::function<void(void)> on_close)
|
|
|
|
: nav_(nav),
|
|
|
|
altitude_(altitude),
|
|
|
|
altitude_unit_(altitude_unit),
|
2024-01-05 12:44:30 +00:00
|
|
|
speed_unit_(speed_unit),
|
2023-05-18 20:16:05 +00:00
|
|
|
lat_(lat),
|
|
|
|
lon_(lon),
|
|
|
|
angle_(angle),
|
|
|
|
on_close_(on_close) {
|
|
|
|
mode_ = DISPLAY;
|
|
|
|
|
|
|
|
add_child(&geopos);
|
|
|
|
|
|
|
|
map_opened = geomap.init();
|
|
|
|
if (!map_opened) return;
|
|
|
|
|
|
|
|
setup();
|
|
|
|
|
|
|
|
geomap.set_mode(mode_);
|
|
|
|
geomap.set_tag(tag);
|
|
|
|
geomap.set_angle(angle);
|
|
|
|
geomap.move(lon_, lat_);
|
2023-06-21 19:56:07 +00:00
|
|
|
geomap.set_focusable(true);
|
2023-05-18 20:16:05 +00:00
|
|
|
|
|
|
|
geopos.set_read_only(true);
|
2017-08-03 18:06:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Prompt mode
|
|
|
|
GeoMapView::GeoMapView(
|
2023-05-18 20:16:05 +00:00
|
|
|
NavigationView& nav,
|
|
|
|
int32_t altitude,
|
|
|
|
GeoPos::alt_unit altitude_unit,
|
2024-01-05 12:44:30 +00:00
|
|
|
GeoPos::spd_unit speed_unit,
|
2023-05-18 20:16:05 +00:00
|
|
|
float lat,
|
|
|
|
float lon,
|
2024-01-05 12:44:30 +00:00
|
|
|
const std::function<void(int32_t, float, float, int32_t)> on_done)
|
2023-05-18 20:16:05 +00:00
|
|
|
: nav_(nav),
|
|
|
|
altitude_(altitude),
|
|
|
|
altitude_unit_(altitude_unit),
|
2024-01-05 12:44:30 +00:00
|
|
|
speed_unit_(speed_unit),
|
2023-05-18 20:16:05 +00:00
|
|
|
lat_(lat),
|
|
|
|
lon_(lon) {
|
|
|
|
mode_ = PROMPT;
|
|
|
|
|
|
|
|
add_child(&geopos);
|
|
|
|
|
|
|
|
map_opened = geomap.init();
|
|
|
|
if (!map_opened) return;
|
|
|
|
|
|
|
|
setup();
|
|
|
|
add_child(&button_ok);
|
|
|
|
|
|
|
|
geomap.set_mode(mode_);
|
|
|
|
geomap.move(lon_, lat_);
|
2023-06-21 19:56:07 +00:00
|
|
|
geomap.set_focusable(true);
|
2023-05-18 20:16:05 +00:00
|
|
|
|
|
|
|
button_ok.on_select = [this, on_done, &nav](Button&) {
|
|
|
|
if (on_done)
|
2024-01-05 12:44:30 +00:00
|
|
|
on_done(altitude_, lat_, lon_, speed_);
|
2023-05-18 20:16:05 +00:00
|
|
|
nav.pop();
|
|
|
|
};
|
2017-07-22 18:30:20 +00:00
|
|
|
}
|
|
|
|
|
2023-05-18 20:16:05 +00:00
|
|
|
void GeoMapView::clear_markers() {
|
|
|
|
geomap.clear_markers();
|
2023-02-28 19:09:05 +00:00
|
|
|
}
|
|
|
|
|
2023-05-18 20:16:05 +00:00
|
|
|
MapMarkerStored GeoMapView::store_marker(GeoMarker& marker) {
|
|
|
|
return geomap.store_marker(marker);
|
2023-02-28 19:09:05 +00:00
|
|
|
}
|
|
|
|
|
2017-07-22 18:30:20 +00:00
|
|
|
} /* namespace ui */
|