Merge pull request #815 from heurist1/adsb-multiple-aircraft-on-map2

ADSB RX multiple aircraft on map - 2
This commit is contained in:
gullradriel 2023-03-01 18:41:25 +01:00 committed by GitHub
commit e526061d13
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 423 additions and 122 deletions

View File

@ -46,10 +46,10 @@ void RecentEntriesTable<AircraftRecentEntries>::draw(
auto entry_age = entry.age;
// Color decay for flights not being updated anymore
if (entry_age < ADSB_DECAY_A) {
if (entry_age < ADSB_CURRENT) {
aged_color = 0x10;
target_color = Color::green();
} else if (entry_age < ADSB_DECAY_B) {
} else if (entry_age < ADSB_RECENT) {
aged_color = 0x07;
target_color = Color::light_grey();
} else {
@ -59,21 +59,13 @@ void RecentEntriesTable<AircraftRecentEntries>::draw(
std::string entry_string = "\x1B";
entry_string += aged_color;
#if false
entry_string += to_string_hex(entry.ICAO_address, 6) + " " +
entry.callsign + " " +
(entry.hits <= 999 ? to_string_dec_uint(entry.hits, 4) : "999+") + " " +
entry.time_string;
#else
// SBT
entry_string +=
(entry.callsign[0]!=' ' ? entry.callsign + " " : to_string_hex(entry.ICAO_address, 6) + " ") +
to_string_dec_uint((unsigned int)((entry.pos.altitude+50)/100),4) +
(entry.callsign[0]!=' ' ? entry.callsign + " " : entry.icaoStr + " ") +
to_string_dec_uint((unsigned int)(entry.pos.altitude/100),4) +
to_string_dec_uint((unsigned int)entry.velo.speed,4) +
to_string_dec_uint((unsigned int)(entry.amp>>9),4) + " " +
(entry.hits <= 999 ? to_string_dec_uint(entry.hits, 3) + " " : "1k+ ") +
to_string_dec_uint(entry.age, 4);
#endif
painter.draw_string(
target_rect.location(),
@ -124,8 +116,6 @@ ADSBRxAircraftDetailsView::ADSBRxAircraftDetailsView(
});
std::unique_ptr<ADSBLogger> logger { };
//update(entry_copy);
icao_code = to_string_hex(entry_copy.ICAO_address, 6);
text_icao_address.set(to_string_hex(entry_copy.ICAO_address, 6));
@ -225,7 +215,10 @@ void ADSBRxDetailsView::update(const AircraftRecentEntry& entry) {
text_frame_pos_odd.set(to_string_hex_array(entry_copy.frame_pos_odd.get_raw_data(), 14));
if (send_updates)
{
geomap_view->update_tag(trimr(entry.callsign[0]!=' ' ? entry.callsign : to_string_hex(entry.ICAO_address, 6)));
geomap_view->update_position(entry_copy.pos.latitude, entry_copy.pos.longitude, entry_copy.velo.heading, entry_copy.pos.altitude);
}
}
ADSBRxDetailsView::~ADSBRxDetailsView() {
@ -277,19 +270,14 @@ ADSBRxDetailsView::ADSBRxDetailsView(
text_icao_address.set(to_string_hex(entry_copy.ICAO_address, 6));
button_aircraft_details.on_select = [this, &nav](Button&) {
//detailed_entry_key = entry.key();
aircraft_details_view = nav.push<ADSBRxAircraftDetailsView>(
entry_copy,
[this]() {
send_updates = false;
});
aircraft_details_view = nav.push<ADSBRxAircraftDetailsView>(entry_copy, [this]() { send_updates = false;});
send_updates = false;
};
button_see_map.on_select = [this, &nav](Button&) {
if (!send_updates) { // Prevent recursively launching the map
geomap_view = nav.push<GeoMapView>(
entry_copy.callsign,
trimr(entry_copy.callsign[0]!=' ' ? entry_copy.callsign : entry_copy.icaoStr),
entry_copy.pos.altitude,
GeoPos::alt_unit::FEET,
entry_copy.pos.latitude,
@ -309,6 +297,7 @@ void ADSBRxView::focus() {
}
ADSBRxView::~ADSBRxView() {
receiver_model.set_tuning_frequency(prevFreq); // Restore previous frequency on exit
// save app settings
settings.save("rx_adsb", &app_settings);
@ -326,8 +315,6 @@ AircraftRecentEntry ADSBRxView::find_or_create_entry(uint32_t ICAO_address) {
// If not found
if (it == std::end(recent)){
recent.emplace_front(ICAO_address); // Add it
truncate_entries(recent); // Truncate the list
sort_entries_by_state();
it = find(recent, ICAO_address); // Find it again
}
return *it;
@ -342,6 +329,21 @@ void ADSBRxView::replace_entry(AircraftRecentEntry & entry)
entry);
}
void ADSBRxView::remove_old_entries()
{
auto it = recent.rbegin();
auto end = recent.rend();
while (it != end)
{
if (it->age_state>=4) {
std::advance(it, 1);
recent.erase( it.base() );
} else {
break; // stop looking because the list is sorted
}
}
}
void ADSBRxView::sort_entries_by_state()
{
// Sorting List pn age_state using lambda function as comparator
@ -349,8 +351,8 @@ void ADSBRxView::sort_entries_by_state()
}
void ADSBRxView::on_frame(const ADSBFrameMessage * message) {
logger = std::make_unique<ADSBLogger>();
rtc::RTC datetime;
std::string str_timestamp;
std::string callsign;
std::string str_info;
std::string logentry;
@ -365,16 +367,16 @@ void ADSBRxView::on_frame(const ADSBFrameMessage * message) {
entry.reset_age();
if (entry.hits==0)
{
entry.amp = message->amp;
entry.amp = message->amp; // Store amplitude on first hit
} else {
entry.amp = ((entry.amp*15)+message->amp)>>4;
entry.amp = ((entry.amp*15)+message->amp)>>4; // Update smoothed amplitude on updates
}
str_timestamp = to_string_datetime(datetime, HMS);
entry.set_time_string(str_timestamp);
entry.inc_hit();
logentry += to_string_hex_array(frame.get_raw_data(), 14) + " ";
logentry += "ICAO:" + to_string_hex(ICAO_address, 6) + " ";
if (logger) {
logentry += to_string_hex_array(frame.get_raw_data(), 14) + " ";
logentry += "ICAO:" + entry.icaoStr + " ";
}
if (frame.get_DF() == DF_ADSB) {
uint8_t msg_type = frame.get_msg_type();
@ -385,7 +387,9 @@ void ADSBRxView::on_frame(const ADSBFrameMessage * message) {
if ((msg_type >= AIRCRAFT_ID_L) && (msg_type <= AIRCRAFT_ID_H)) {
callsign = decode_frame_id(frame);
entry.set_callsign(callsign);
logentry+=callsign+" ";
if (logger) {
logentry+=callsign+" ";
}
}
// 9:
// 18: { // Extended squitter/non-transponder
@ -400,30 +404,33 @@ void ADSBRxView::on_frame(const ADSBFrameMessage * message) {
" Lat:" + to_string_decimal(entry.pos.latitude, 2) +
" Lon:" + to_string_decimal(entry.pos.longitude, 2);
// printing the coordinates in the log file with more
// resolution, as we are not constrained by screen
// real estate there:
std::string log_info = "Alt:" + to_string_dec_int(entry.pos.altitude) +
" Lat:" + to_string_decimal(entry.pos.latitude, 7) +
" Lon:" + to_string_decimal(entry.pos.longitude, 7);
entry.set_info_string(str_info);
logentry+=log_info + " ";
if (logger) {
// printing the coordinates in the log file with more
// resolution, as we are not constrained by screen
// real estate there:
std::string log_info = "Alt:" + to_string_dec_int(entry.pos.altitude) +
" Lat:" + to_string_decimal(entry.pos.latitude, 7) +
" Lon:" + to_string_decimal(entry.pos.longitude, 7);
logentry+=log_info + " ";
}
}
} else if(msg_type == AIRBORNE_VEL && msg_sub >= VEL_GND_SUBSONIC && msg_sub <= VEL_AIR_SUPERSONIC){
entry.set_frame_velo(frame);
logentry += "Type:" + to_string_dec_uint(msg_sub) +
" Hdg:" + to_string_dec_uint(entry.velo.heading) +
" Spd: "+ to_string_dec_int(entry.velo.speed);
if (logger) {
logentry += "Type:" + to_string_dec_uint(msg_sub) +
" Hdg:" + to_string_dec_uint(entry.velo.heading) +
" Spd: "+ to_string_dec_int(entry.velo.speed);
}
}
}
replace_entry(entry);
} // frame.get_DF() == DF_ADSB
replace_entry(entry);
logger = std::make_unique<ADSBLogger>();
if (logger) {
logger->append(u"adsb.txt");
// will log each frame in format:
@ -433,20 +440,69 @@ void ADSBRxView::on_frame(const ADSBFrameMessage * message) {
}
}
void ADSBRxView::on_tick_second() {
// Decay and refresh if needed
for (auto& entry : recent) {
entry.inc_age();
if (details_view) {
if (send_updates && (entry.key() == detailed_entry_key)) // Check if the ICAO address match
details_view->update(entry);
void ADSBRxView::on_tick_second() {
if (recent.size() <= 16){ // Not many entries update everything (16 is one screen full)
updateDetailsAndMap(1);
updateRecentEntries();
} else if (updateState==0) { // Even second
updateState = 1;
updateDetailsAndMap(2);
} else { // Odd second only performed when there are many entries
updateState = 0;
updateRecentEntries();
}
}
void ADSBRxView::updateDetailsAndMap(int ageStep) {
ui::GeoMarker marker;
bool storeNewMarkers = false;
// Sort and truncate the entries, grouped, newest group first
sort_entries_by_state();
truncate_entries(recent);
remove_old_entries();
// Calculate if it is time to update markers
if (send_updates && details_view && details_view->geomap_view) {
ticksSinceMarkerRefresh += ageStep;
if (ticksSinceMarkerRefresh >= MARKER_UPDATE_SECONDS) { // Update other aircraft every few seconds
storeNewMarkers = true;
ticksSinceMarkerRefresh=0;
}
} else {
ticksSinceMarkerRefresh = MARKER_UPDATE_SECONDS; // Send the markers as soon as the geoview exists
}
// Sort the list if it is being displayed
if (!send_updates) {
sort_entries_by_state();
// Increment age, and pass updates to the details and map
const bool otherMarkersCanBeSent = send_updates && storeNewMarkers && details_view && details_view->geomap_view; // Save retesting all of this
MapMarkerStored markerStored = MARKER_NOT_STORED;
if (otherMarkersCanBeSent) {details_view->geomap_view->clear_markers();}
// Loop through all entries
for (auto& entry : recent) {
entry.inc_age(ageStep);
// Only if there is a details view
if (send_updates && details_view) {
if (entry.key() == detailed_entry_key) // Check if the ICAO address match
{
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))
{
marker.lon = entry.pos.longitude;
marker.lat = entry.pos.latitude;
marker.angle = entry.velo.heading;
marker.tag = trimr(entry.callsign[0]!=' ' ? entry.callsign : entry.icaoStr);
markerStored = details_view->geomap_view->store_marker(marker);
}
}
} // Loop through all entries, if only to update the age
}
void ADSBRxView::updateRecentEntries() {
// Redraw the list of aircraft
if (!send_updates){
recent_entries_view.set_dirty();
}
}
@ -492,7 +548,7 @@ ADSBRxView::ADSBRxView(NavigationView& nav) {
on_tick_second();
};
prevFreq = receiver_model.tuning_frequency();
prevFreq = receiver_model.tuning_frequency(); // Store previous frequency on creation
baseband::set_adsb();

View File

@ -25,6 +25,7 @@
#include "ui_receiver.hpp"
#include "ui_geomap.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "string_format.hpp"
#include "file.hpp"
#include "database.hpp"
@ -39,9 +40,9 @@ using namespace adsb;
namespace ui {
#define ADSB_DECAY_A 10 // In seconds
#define ADSB_DECAY_B 30
#define ADSB_DECAY_C 60 // Can be used for removing old entries, RecentEntries already caps to 64
#define ADSB_CURRENT 10 // Seconds
#define ADSB_RECENT 30 // Seconds
#define ADSB_REMOVE 300 // Used for removing old entries
#define AIRCRAFT_ID_L 1 // aircraft ID message type (lowest type id)
#define AIRCRAFT_ID_H 4 // aircraft ID message type (highest type id)
@ -83,14 +84,15 @@ struct AircraftRecentEntry {
ADSBFrame frame_pos_even { };
ADSBFrame frame_pos_odd { };
std::string icaoStr {" "};
std::string callsign { " " };
std::string time_string { "" };
std::string info_string { "" };
AircraftRecentEntry(
const uint32_t ICAO_address
) : ICAO_address { ICAO_address }
{
this->icaoStr = to_string_hex(ICAO_address, 6);
}
Key key() const {
@ -125,23 +127,20 @@ struct AircraftRecentEntry {
info_string = new_info_string;
}
void set_time_string(std::string& new_time_string) {
time_string = new_time_string;
}
void reset_age() {
age = 0;
}
void inc_age() {
age++;
if (age < ADSB_DECAY_A)
{
void inc_age(int delta) {
age+=delta;
if (age < ADSB_CURRENT){
age_state = pos.valid ? 0 : 1;
}
else
{
age_state = (age < ADSB_DECAY_B) ? 2 : 3;
} else if(age < ADSB_RECENT){
age_state = 2;
} else if(age < ADSB_REMOVE){
age_state = 3;
} else{
age_state = 4;
}
}
};
@ -273,10 +272,10 @@ public:
std::database::AirlinesDBRecord airline_record = {};
GeoMapView* geomap_view { nullptr };
private:
AircraftRecentEntry entry_copy { 0 };
std::function<void(void)> on_close_ { };
GeoMapView* geomap_view { nullptr };
ADSBRxAircraftDetailsView* aircraft_details_view { nullptr };
bool send_updates { false };
std::database db = { };
@ -364,6 +363,7 @@ public:
std::string title() const override { return "ADS-B RX"; };
void replace_entry(AircraftRecentEntry & entry);
void remove_old_entries();
AircraftRecentEntry find_or_create_entry(uint32_t ICAO_address);
void sort_entries_by_state();
@ -372,24 +372,23 @@ private:
std::unique_ptr<ADSBLogger> logger { };
void on_frame(const ADSBFrameMessage * message);
void on_tick_second();
int updateState = { 0 };
void updateRecentEntries();
void updateDetailsAndMap(int ageStep);
#define MARKER_UPDATE_SECONDS (5)
int ticksSinceMarkerRefresh { MARKER_UPDATE_SECONDS-1 };
// app save settings
std::app_settings settings { };
std::app_settings::AppSettings app_settings { };
const RecentEntriesColumns columns { {
#if false
{ "ICAO", 6 },
{ "Callsign", 9 },
{ "Hits", 4 },
{ "Time", 8 }
#else
{ "ICAO/Call", 9 },
{ "Lvl", 3 },
{ "Spd", 3 },
{ "Amp", 3 },
{ "Hit", 3 },
{ "Age", 4 }
#endif
} };
AircraftRecentEntries recent { };
RecentEntriesView<RecentEntries<AircraftRecentEntry>> recent_entries_view { columns, recent };

View File

@ -255,3 +255,9 @@ double get_decimals(double num, int16_t mult, bool round) {
if (num > .5) intnum++; //Round up
return intnum;
}
std::string trimr(std::string str)
{
size_t last = str.find_last_not_of(' ');
return (last!=std::string::npos) ? str.substr(0, last+1) : ""; // Remove the trailing spaces
}

View File

@ -58,4 +58,7 @@ std::string to_string_FAT_timestamp(const FATTimestamp& timestamp);
std::string unit_auto_scale(double n, const uint32_t base_nano, uint32_t precision);
double get_decimals(double num, int16_t mult, bool round = false); //euquiq added
std::string trimr(std::string str); // Remove trailing spaces
#endif/*__STRING_FORMAT_H__*/

View File

@ -134,7 +134,7 @@ int32_t GeoPos::altitude() {
GeoMap::GeoMap(
Rect parent_rect
) : Widget { parent_rect }
) : Widget { parent_rect }, markerListLen(0)
{
//set_focusable(true);
}
@ -144,34 +144,44 @@ void GeoMap::paint(Painter& painter) {
std::array<ui::Color, 240> map_line_buffer;
const auto r = screen_rect();
// Ony redraw map if it moved by at least 1 pixel
if ((x_pos != prev_x_pos) || (y_pos != prev_y_pos)) {
// 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);
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);
}
prev_x_pos = x_pos;
prev_y_pos = y_pos;
// 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
}
//center tag above point
if(tag_.find_first_not_of(' ') != tag_.npos){ //only draw tag if we have something other than spaces
painter.draw_string(r.center() - Point(((int)tag_.length() * 8 / 2), 2 * 16), style(), tag_);
}
if (mode_ == PROMPT) {
// Cross
display.fill_rectangle({ r.center() - Point(16, 1), { 32, 2 } }, Color::red());
display.fill_rectangle({ r.center() - Point(1, 16), { 2, 32 } }, Color::red());
} else if (angle_ < 360){
//if we have a valid angle draw bearing
draw_bearing(r.center(), angle_, 10, Color::red());
}
else {
//draw a small cross
display.fill_rectangle({ r.center() - Point(8, 1), { 16, 2 } }, Color::red());
display.fill_rectangle({ r.center() - Point(1, 8), { 2, 16 } }, Color::red());
}
//Draw the marker in the center
draw_marker(painter, r.center(), angle_, tag_, Color::red(), Color::white(), Color::black() );
}
bool GeoMap::on_touch(const TouchEvent event) {
@ -196,10 +206,7 @@ void GeoMap::move(const float lon, const float lat) {
x_pos = map_width * (lon_+180)/360 - (map_rect.width() / 2);
// Latitude calculation based on https://stackoverflow.com/a/10401734/2278659
double map_bottom = sin(-85.05 * pi / 180); // Map bitmap only goes from about -85 to 85 lat
double lat_rad = sin(lat * pi / 180);
double map_world_lon = map_width / (2 * pi);
double map_offset = (map_world_lon / 2 * log((1 + map_bottom) / (1 - map_bottom)));
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
@ -222,7 +229,11 @@ bool GeoMap::init() {
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);
map_offset = (map_world_lon / 2 * log((1 + map_bottom) / (1 - map_bottom)));
return true;
}
@ -234,9 +245,9 @@ void GeoMap::draw_bearing(const Point origin, const uint16_t angle, uint32_t siz
Point arrow_a, arrow_b, arrow_c;
for (size_t thickness = 0; thickness < 3; thickness++) {
arrow_a = polar_to_point(angle, size) + origin;
arrow_b = polar_to_point(angle + 180 - 35, size) + origin;
arrow_c = polar_to_point(angle + 180 + 35, size) + origin;
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);
@ -246,6 +257,65 @@ void GeoMap::draw_bearing(const Point origin, const uint16_t angle, uint32_t siz
}
}
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);
}
}
void GeoMap::clear_markers()
{
markerListLen = 0;
}
MapMarkerStored GeoMap::store_marker(GeoMarker & marker)
{
MapMarkerStored ret;
// Check if it could be on screen
// Only checking one direction to reduce CPU
const auto r = screen_rect();
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
if (false==((x>=0) && (x<r.width()) && (y>10) && (y<r.height()))) // Dont draw within symbol size of top
{
ret = MARKER_NOT_STORED;
}
else if (markerListLen<NumMarkerListElements)
{
markerList[markerListLen] = marker;
markerListLen++;
markerListUpdated = true;
ret = MARKER_STORED;
} else {
ret = MARKER_LIST_FULL;
}
return ret;
}
void GeoMapView::focus() {
geopos.focus();
@ -269,6 +339,10 @@ void GeoMapView::update_position(float lat, float lon, uint16_t angle, int32_t a
geomap.move(lon_, lat_);
geomap.set_dirty();
}
void GeoMapView::update_tag(const std::string tag) {
geomap.set_tag(tag);
}
void GeoMapView::setup() {
add_child(&geomap);
@ -375,4 +449,14 @@ GeoMapView::GeoMapView(
};
}
void GeoMapView::clear_markers()
{
geomap.clear_markers();
}
MapMarkerStored GeoMapView::store_marker(GeoMarker & marker)
{
return geomap.store_marker(marker);
}
} /* namespace ui */

View File

@ -37,6 +37,24 @@ enum GeoMapMode {
PROMPT
};
struct GeoMarker {
public:
float lat {0};
float lon {0};
uint16_t angle {0};
std::string tag {""};
GeoMarker & operator=(GeoMarker & rhs){
lat = rhs.lat;
lon = rhs.lon;
angle = rhs.angle;
tag = rhs.tag;
return *this;
}
};
class GeoPos : public View {
public:
enum alt_unit {
@ -112,6 +130,12 @@ private:
};
};
enum MapMarkerStored {
MARKER_NOT_STORED,
MARKER_STORED,
MARKER_LIST_FULL
};
class GeoMap : public Widget {
public:
std::function<void(float, float)> on_move { };
@ -133,20 +157,35 @@ public:
angle_ = new_angle;
}
static const int NumMarkerListElements = 30;
void clear_markers();
MapMarkerStored store_marker(GeoMarker & marker);
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() );
GeoMapMode mode_ { };
File map_file { };
uint16_t map_width { }, map_height { };
int32_t map_center_x { }, map_center_y { };
float lon_ratio { }, lat_ratio { };
double map_bottom { };
double map_world_lon { };
double map_offset { };
int32_t x_pos { }, y_pos { };
int32_t prev_x_pos { 0xFFFF }, prev_y_pos { 0xFFFF };
float lat_ { };
float lon_ { };
uint16_t angle_ { };
std::string tag_ { };
int markerListLen {0};
GeoMarker markerList[NumMarkerListElements];
bool markerListUpdated {false};
};
class GeoMapView : public View {
@ -181,6 +220,12 @@ public:
std::string title() const override { return "Map view"; };
void clear_markers();
MapMarkerStored store_marker(GeoMarker & marker);
void update_tag(const std::string tag);
private:
NavigationView& nav_;

View File

@ -45,8 +45,8 @@ void ADSBRXProcessor::execute(const buffer_c8_t& buffer) {
for (size_t i = 0; i < buffer.count; i++) {
// Compute sample's magnitude
re = (int32_t)buffer.p[i].real(); // make re float and scale it
im = (int32_t)buffer.p[i].imag(); // make re float and scale it
re = (int32_t)buffer.p[i].real();
im = (int32_t)buffer.p[i].imag();
mag = ((uint32_t)(re*re) + (uint32_t)(im*im));
if (decoding) {
@ -63,7 +63,6 @@ void ADSBRXProcessor::execute(const buffer_c8_t& buffer) {
}
else
{
//confidence = true;
bit = (prev_mag > mag) ? 1 : 0;
}
@ -94,7 +93,7 @@ void ADSBRXProcessor::execute(const buffer_c8_t& buffer) {
}
// Continue looking for preamble even if in a packet
// switch is new preamble id higher magnitude
// switch if new preamble is higher magnitude
// Shift the preamble
for (c = 0; c < (ADSB_PREAMBLE_LENGTH ); c++) { shifter[c] = shifter[c + 1]; }

View File

@ -22,6 +22,7 @@
#include "adsb.hpp"
#include "sine_table.hpp"
#include "utility.hpp"
#include <math.h>
@ -369,11 +370,11 @@ adsb_vel decode_frame_velo(ADSBFrame& frame){
if(frame_data[5]&0x04) velo_ew *= -1; //check ew direction sign
if(frame_data[7]&0x80) velo_ns *= -1; //check ns direction sign
velo.speed = sqrt(velo_ns*velo_ns + velo_ew*velo_ew);
velo.speed = fast_int_magnitude(velo_ns,velo_ew);
if(velo.speed){
//calculate heading in degrees from ew/ns velocities
int16_t heading_temp = (int16_t)(atan2(velo_ew,velo_ns) * 180.0 / pi);
int16_t heading_temp = (int16_t)(int_atan2(velo_ew,velo_ns)); // Nearest degree
// We don't want negative values but a 0-360 scale.
if (heading_temp < 0) heading_temp += 360.0;
velo.heading = (uint16_t)heading_temp;

View File

@ -606,12 +606,35 @@ void ILI9341::draw_bitmap(
const ui::Color foreground,
const ui::Color background
) {
lcd_start_ram_write(p, size);
// Not a transparent background
if (ui::Color::magenta().v!=background.v)
{
lcd_start_ram_write(p, size);
const size_t count = size.width() * size.height();
for(size_t i=0; i<count; i++) {
const auto pixel = pixels[i >> 3] & (1U << (i & 0x7));
io.lcd_write_pixel(pixel ? foreground : background);
const size_t count = size.width() * size.height();
for(size_t i=0; i<count; i++) {
const auto pixel = pixels[i >> 3] & (1U << (i & 0x7));
io.lcd_write_pixel(pixel ? foreground : background);
}
}
else
{
int x = p.x();
int y = p.y();
int maxX = x + size.width();
const size_t count = size.width() * size.height();
for(size_t i=0; i<count; i++) {
const auto pixel = pixels[i >> 3] & (1U << (i & 0x7));
if (pixel) {
draw_pixel(ui::Point(x,y), foreground);
}
// Move to the next pixel
x++;
if (x>=maxX){
x = p.x();
y++;
}
}
}
}

View File

@ -21,6 +21,7 @@
#include "ui.hpp"
#include "sine_table.hpp"
#include "utility.hpp"
#include <algorithm>
@ -96,4 +97,10 @@ Point polar_to_point(float angle, uint32_t distance) {
sin_f32(DEG_TO_RAD(-angle) - (pi / 2)) * distance);
}
Point fast_polar_to_point(int32_t angle, uint32_t distance) {
//polar to compass with y negated for screen drawing
return Point((int16_sin_s4(((1<<16)*(-angle + 180))/360) * distance)/(1<<16),
(int16_sin_s4(((1<<16)*(-angle - 90))/360) * distance)/(1<<16));
}
} /* namespace ui */

View File

@ -343,6 +343,8 @@ struct TouchEvent {
Point polar_to_point(float angle, uint32_t distance);
Point fast_polar_to_point(int32_t angle, uint32_t distance);
} /* namespace ui */
#endif/*__UI_H__*/

View File

@ -91,6 +91,78 @@ float mag2_to_dbv_norm(const float mag2) {
return (fast_log2(mag2) - mag2_log2_max) * mag2_to_db_factor;
}
// Integer in and out approximation
// >40 times faster float sqrt(x*x+y*y) on Cortex M0
// derived from https://dspguru.com/dsp/tricks/magnitude-estimator/
int fast_int_magnitude(int y, int x)
{
if(y<0){y=-y;}
if(x<0){x=-x;}
if (x>y) {
return ((x*61)+(y*26)+32)/64;
} else {
return ((y*61)+(x*26)+32)/64;
}
}
// Integer x and y returning an integer bearing in degrees
// Accurate to 0.5 degrees, so output scaled to whole degrees
// >60 times faster than float atan2 on Cortex M0
int int_atan2(int y, int x)
{
// Number of bits to shift up before doing the maths. A larger shift
// may beable to gain accuracy, but it would cause the correction
// entries to be larger than 1 byte
static const int bits = 10;
static const int pi4 = (1 << bits);
static const int pi34 = (3 << bits);
// Special case
if (x == 0 && y == 0) { return 0; }
// Form an approximate angle
const int yabs = y >= 0 ? y : -y;
int angle;
if (x >= 0) {
angle = pi4 - pi4 * (x - yabs) / (x + yabs);
} else {
angle = pi34 - pi4 * (x + yabs) / (yabs - x);
}
// Correct the result using a lookup table
static const int8_t correct[32] = { 0, -23, -42, -59, -72, -83 ,-89 ,-92 ,-92 ,-88 ,-81, -71, -58, -43 ,-27, -9, 9, 27, 43, 58, 71, 81, 88, 92, 92, 89, 83, 72, 59, 42, 23, 0 };
static const int rnd = (1 << (bits - 1)) / 45; // Minor correction to round to correction values better (add 0.5)
const int idx = ((angle + rnd) >> (bits - 4)) & 0x1F;
angle += correct[idx];
// Scale for output in degrees
static const int half = (1 << (bits - 1));
angle = ((angle * 45)+half) >> bits; // Add on half before rounding
if (y < 0) { angle = -angle; }
return angle;
}
// 16 bit value represents a full cycle in but can handle multiples of this.
// Output in range +/- 16 bit value representing +/- 1.0
// 4th order cosine approximation has very small error
// >200 times faster tan float sin on Cortex M0
// see https://www.coranac.com/2009/07/sines/
int32_t int16_sin_s4(int32_t x)
{
static const int qN = 14, qA = 16, qR=12, B = 19900, C = 3516;
const int32_t c = x << (30 - qN); // Semi-circle info into carry.
x -= 1 << qN; // sine -> cosine calc
x = x << (31 - qN); // Mask with PI
x = x >> (31 - qN); // Note: SIGNED shift! (to qN)
x = x*x >> (2 * qN - 14); // x=x^2 To Q14
int32_t y = B - (x*C >> 14); // B - x^2*C
y = (1 << qA) - (x*y >> qR); // A - x^2*(B-x^2*C)
return c >= 0 ? y : -y;
}
/* GCD implementation derived from recursive implementation at
* http://en.wikipedia.org/wiki/Binary_GCD_algorithm
*/

View File

@ -90,6 +90,10 @@ inline float magnitude_squared(const std::complex<float> c) {
return r2 + i2;
}
int fast_int_magnitude(int y, int x);
int int_atan2(int y, int x);
int32_t int16_sin_s4(int32_t x);
template<class T>
struct range_t {
const T minimum;