mirror of
https://github.com/eried/portapack-mayhem.git
synced 2024-10-01 01:26:06 -04:00
Merge pull request #815 from heurist1/adsb-multiple-aircraft-on-map2
ADSB RX multiple aircraft on map - 2
This commit is contained in:
commit
e526061d13
@ -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:
|
||||
@ -434,19 +441,68 @@ 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 (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();
|
||||
}
|
||||
}
|
||||
|
||||
if (details_view) {
|
||||
if (send_updates && (entry.key() == detailed_entry_key)) // Check if the ICAO address match
|
||||
details_view->update(entry);
|
||||
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();
|
||||
|
||||
|
@ -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 };
|
||||
|
@ -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
|
||||
}
|
@ -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__*/
|
||||
|
@ -134,7 +134,7 @@ int32_t GeoPos::altitude() {
|
||||
|
||||
GeoMap::GeoMap(
|
||||
Rect parent_rect
|
||||
) : Widget { parent_rect }
|
||||
) : Widget { parent_rect }, markerListLen(0)
|
||||
{
|
||||
//set_focusable(true);
|
||||
}
|
||||
@ -145,33 +145,43 @@ void GeoMap::paint(Painter& painter) {
|
||||
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)) {
|
||||
// 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
|
||||
@ -223,6 +230,10 @@ 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();
|
||||
|
||||
@ -270,6 +340,10 @@ void GeoMapView::update_position(float lat, float lon, uint16_t angle, int32_t a
|
||||
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 */
|
||||
|
@ -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_;
|
||||
|
||||
|
@ -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]; }
|
||||
|
@ -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;
|
||||
|
@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -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__*/
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user