-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathspots.cpp
More file actions
330 lines (282 loc) · 10.6 KB
/
spots.cpp
File metadata and controls
330 lines (282 loc) · 10.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
/* handy tools for Spots
*/
#include "HamClock.h"
/* find list element, subject to possible filtering, that is closest to ll on the given end(s).
* return whether found one within MAX_CSR_DIST.
*/
bool getClosestSpot (DXSpot *list, int n_list, SpotFilter sfp, LabelOnMapEnd which_end,
LatLong &from_ll, DXSpot *closest_sp, LatLong *closest_llp)
{
// linear search -- not worth kdtree etc
const DXSpot *min_sp = NULL;
float min_d = 1e10;
bool min_is_de = false;
for (int i = 0; i < n_list; i++) {
DXSpot *sp = &list[i];
// skip if filtered out
if (sfp && !(*sfp)(sp))
continue;
// check RX end if used
if (which_end == LOME_RXEND || which_end == LOME_BOTH) {
float d = sp->rx_ll.GSD(from_ll);
if (d < min_d) {
min_d = d;
min_sp = sp;
min_is_de = true;
}
}
// check TX end if used
if (which_end == LOME_TXEND || which_end == LOME_BOTH) {
float d = sp->tx_ll.GSD(from_ll);
if (d < min_d) {
min_d = d;
min_sp = sp;
min_is_de = false;
}
}
}
// use if close enough
if (min_sp && min_d*ERAD_M < MAX_CSR_DIST) {
// return ll depending on end
*closest_llp = min_is_de ? min_sp->rx_ll : min_sp->tx_ll;
// return spot
*closest_sp = *min_sp;
// ok
return (true);
}
// none within MAX_CSR_DIST
return (false);
}
/* draw a dot and/or label at the given end of a spot path, as per setup options.
* N.B. this only handles LOME_RXEND or LOME_TXEND, not LOME_BOTH.
*/
static void drawSpotTXRXOnMap (DXSpot &spot, LabelOnMapEnd txrx, LabelOnMapDot dot)
{
// always draw at least the dot unless no label at all
LabelType lblt = getSpotLabelType();
if (lblt == LBL_NONE)
return;
// handy
bool tx_end = txrx == LOME_TXEND;
LatLong &ll = tx_end ? spot.tx_ll : spot.rx_ll;
bool just_dot = dot == LOMD_JUSTDOT || de_ll.GSD(ll) < minLabelDist();
// get dot size
int dot_r = getRawBandSpotRadius (spot.kHz);
// get screen coord, insure over map
SCoord s;
ll2s (ll, s, dot_r); // overkill since raw >= canonical
if (!overMap(s))
return;
// color depends on band
uint16_t b_color = getBandColor (spot.kHz);
// rx "dot" end is square, tx is a circle
SCoord s_raw;
ll2sRaw (ll, s_raw, dot_r);
drawSpotDot (s_raw.x, s_raw.y, dot_r, txrx, b_color);
// done if no text label
if (lblt == LBL_DOT || just_dot)
return;
// decide text: whole call or just prefix
char prefix[MAX_PREF_LEN];
const char *call = tx_end ? spot.tx_call : spot.rx_call;
const char *tag = NULL;
if (lblt == LBL_PREFIX) {
findCallPrefix (call, prefix);
tag = prefix;
} else if (lblt == LBL_CALL) {
tag = call;
} else
fatalError ("Bogus label type: %d\n", (int)lblt);
// position and draw
SBox b;
setMapTagBox (tag, s, dot_r/tft.SCALESZ+1, b); // wants canonical size
uint16_t txt_color = getGoodTextColor (b_color);
drawMapTag (tag, b, txt_color, b_color);
}
/* draw a dot and/or label at the given end/ends of a spot path, as per setup options.
* N.B. we don't draw the path, use drawSpotPathOnMap() for that.
*/
void drawSpotLabelOnMap (DXSpot &spot, LabelOnMapEnd txrx, LabelOnMapDot dot)
{
if (txrx == LOME_TXEND || txrx == LOME_BOTH)
drawSpotTXRXOnMap (spot, LOME_TXEND, dot);
if (txrx == LOME_RXEND || txrx == LOME_BOTH)
drawSpotTXRXOnMap (spot, LOME_RXEND, dot);
}
/* draw path if enabled as per setup options.
* N.B. we don't draw ends or labels; use drawSpotLabelOnMap() for those.
*/
void drawSpotPathOnMap (const DXSpot &spot)
{
// raw line size, unless none
int raw_pw = getRawBandPathWidth(spot.kHz);
if (raw_pw == 0)
return;
// printf ("******** pw %d\n", raw_pw); // RBF
const uint16_t color = getBandColor(spot.kHz);
// draw from rx to tx
float slat = sinf (spot.rx_ll.lat);
float clat = cosf (spot.rx_ll.lat);
float dist, bear;
propPath (false, spot.rx_ll, slat, clat, spot.tx_ll, &dist, &bear);
const int n_step = ((int)ceilf(dist/deg2rad(PATH_SEGLEN))) | 1; // always odd so both ends are drawn
const float step = dist/n_step;
const bool dashed = getBandPathDashed (spot.kHz);
SCoord prev_s = {0, 0}; // .x == 0 means don't show
for (int i = 0; i <= n_step; i++) { // fence posts
float r = i*step;
float ca, B;
SCoord s;
solveSphere (bear, r, slat, clat, &ca, &B);
ll2sRaw (asinf(ca), fmodf(spot.rx_ll.lng+B+5*M_PIF,2*M_PIF)-M_PIF, s, raw_pw);
if (prev_s.x > 0) {
if (segmentSpanOkRaw(prev_s, s, raw_pw)) {
if (!dashed || n_step < 7 || (i & 1))
tft.drawLineRaw (prev_s.x, prev_s.y, s.x, s.y, raw_pw, color);
} else
s.x = 0;
}
prev_s = s;
}
}
/* draw the given spot in the given pane row with given bg color, known to be visible.
*/
void drawSpotOnList (const SBox &box, const DXSpot &spot, int row, uint16_t bg_col)
{
if (debugLevel (DEBUG_SCROLL, 1))
Serial.printf ("row %2d: drawing\n", row);
selectFontStyle (LIGHT_FONT, FAST_FONT);
char line[50];
// set entire row to bg_col
const uint16_t x = box.x+1;
const uint16_t y = box.y + LISTING_Y0 + row*LISTING_DY;
const uint16_t h = LISTING_DY - 2;
tft.fillRect (x, y-LISTING_OS, box.w-2, h, bg_col);
// pretty freq, fixed 8 chars, bg matching band color assignment
const char *f_fmt = spot.kHz < 1e6F ? "%8.1f" : "%8.0f";
snprintf (line, sizeof(line), f_fmt, spot.kHz);
const uint16_t fbg_col = getBandColor (spot.kHz);
const uint16_t ffg_col = getGoodTextColor(fbg_col);
tft.setTextColor(ffg_col);
tft.fillRect (x, y-LISTING_OS, 50, h, fbg_col);
tft.setCursor (x, y);
tft.print (line);
// add call
const int max_call = BOX_IS_PANE_0(box) ? MAX_SPOTCALL_LEN-3 : MAX_SPOTCALL_LEN-1;
tft.setTextColor(RA8875_WHITE);
snprintf (line, sizeof(line), " %-*.*s ", max_call, max_call, spot.tx_call);
tft.print (line);
// and finally age, width depending on pane
time_t age = myNow() - spot.spotted;
tft.print (formatAge (age, line, sizeof(line), BOX_IS_PANE_0(box) ? 3 : 4));
}
/* shift ll slightly so it's more likely to have a separate pick position
*/
void ditherLL (LatLong &ll)
{
// move around within roughly +/- 2 pixels
const float deg_per_pix = (360.0F/BUILD_W)/pan_zoom.zoom;
ll.lat_d += deg_per_pix * 2*(random(100)/50.0F - 1);
ll.lng_d += deg_per_pix * 2*(random(100)/50.0F - 1);
ll.normalize();
}
/* draw the visible spots and scroll controls
*/
void drawVisibleSpots (WatchListId wl_id, const DXSpot *spots, const ScrollState &ss, const SBox &box,
int16_t app_color)
{
// show vis spots and note if any would be red above and below
bool any_older = false;
bool any_newer = false;
int min_i, max_i;
if (ss.getVisDataIndices (min_i, max_i) > 0) {
for (int i = 0; i < ss.n_data; i++) {
const DXSpot &spot = spots[i];
if (i < min_i) {
if (!any_older)
any_older = checkWatchListSpot (wl_id, spot) == WLS_HILITE;
} else if (i > max_i) {
if (!any_newer)
any_newer = checkWatchListSpot (wl_id, spot) == WLS_HILITE;
} else {
uint16_t bg_col = checkWatchListSpot (wl_id, spot) == WLS_HILITE ? RA8875_RED:RA8875_BLACK;
drawSpotOnList (box, spot, ss.getDisplayRow(i), bg_col);
}
}
}
// erase unused rows below min_i
for (int row = ss.getDisplayRow(--min_i); row >= 0 && row < ss.max_vis; row = ss.getDisplayRow(--min_i)) {
if (debugLevel (DEBUG_SCROLL, 1))
Serial.printf ("row %2d: erasing\n", row);
const uint16_t y = box.y + LISTING_Y0 + row*LISTING_DY - LISTING_OS;
tft.fillRect (box.x+1, y, box.w-2, LISTING_DY - 2, RA8875_BLACK);
}
// scroll controls red if any more red spots in their directions
uint16_t up_color = app_color;
uint16_t dw_color = app_color;
if (ss.okToScrollDown() &&
((scrollTopToBottom() && any_older) || (!scrollTopToBottom() && any_newer)))
dw_color = RA8875_RED;
if (ss.okToScrollUp() &&
((scrollTopToBottom() && any_newer) || (!scrollTopToBottom() && any_older)))
up_color = RA8875_RED;
ss.drawScrollUpControl (box, up_color, app_color);
ss.drawScrollDownControl (box, dw_color, app_color);
}
/* qsort-style function to compare two DXSpot by freq
*/
int qsDXCFreq (const void *v1, const void *v2)
{
DXSpot *s1 = (DXSpot *)v1;
DXSpot *s2 = (DXSpot *)v2;
return (roundf(s2->kHz - s1->kHz));
}
/* qsort-style function to compare two DXSpot by rx_grid
* N.B. actually used by OnAir to sort by org
*/
int qsDXCRXGrid (const void *v1, const void *v2)
{
DXSpot *s1 = (DXSpot *)v1;
DXSpot *s2 = (DXSpot *)v2;
return (strcmp (s2->rx_grid, s1->rx_grid));
}
/* qsort-style function to compare two DXSpot by tx_call
*/
int qsDXCTXCall (const void *v1, const void *v2)
{
DXSpot *s1 = (DXSpot *)v1;
DXSpot *s2 = (DXSpot *)v2;
return (strcmp (s2->tx_call, s1->tx_call));
}
/* qsort-style function to compare two DXSpot by time spotted
*/
int qsDXCSpotted (const void *v1, const void *v2)
{
DXSpot *s1 = (DXSpot *)v1;
DXSpot *s2 = (DXSpot *)v2;
return (s1->spotted - s2->spotted); // newest times (larger numbers) first
}
/* qsort-style function to compare two DXSpot by separation distance
*/
int qsDXCDist (const void *v1, const void *v2)
{
DXSpot *s1 = (DXSpot *)v1;
DXSpot *s2 = (DXSpot *)v2;
float d1 = s1->rx_ll.GSD(s1->tx_ll);
float d2 = s2->rx_ll.GSD(s2->tx_ll);
return (roundf(1000*(d1 - d2))); // want farthest (largest) first
}
/* actual drawing of a DXSpot dot
*/
void drawSpotDot (int16_t raw_x, int16_t raw_y, uint16_t radius, LabelOnMapEnd txrx, uint16_t color)
{
if (txrx == LOME_TXEND) {
// circle suggesting expanding wave
tft.fillCircleRaw (raw_x, raw_y, radius, color);
tft.drawCircleRaw (raw_x, raw_y, radius+1, RA8875_BLACK);
} else {
// square suggesting receiving array ??
tft.fillRectRaw (raw_x-radius, raw_y-radius, 2*radius, 2*radius, color);
tft.drawRectRaw (raw_x-radius-1, raw_y-radius-1, 2*radius+1, 2*radius+1, RA8875_BLACK);
}
}