-
Notifications
You must be signed in to change notification settings - Fork 9
/
qso-mapper.js
370 lines (315 loc) · 10.3 KB
/
qso-mapper.js
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
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
/* qso-mapper.js - Show Amateur Radio QSOs on an interactive map and table.
*
* 2020/09/05 Stephen Houser, N1SH
*/
/*
TODO: Enable loading ADIF files from URL query string and saving in local storage
1. Use AJAX to get file from same repo as passed from URL
https://stephenhouser.com/qso-mapper?short.adi
https://stephenhouser.com/qso-mapper?https://stephenhouser.com/qso-mapper/short.adi
- don't use any local storage
- should the file upload be available when this is the case? prob not.
- disable reset button and file upload
2. Use data uploaded by the file upload form
- save in local storage
*/
/* Names of HTML DOM elements */
var mapElementName = 'map-canvas';
var fileUploadFormName = 'fileUpload';
var fileInputFieldName = 'fileInput';
var resetButtonName = 'resetButton';
/* Global list of QSOs */
var qsos = [];
/* initQsoMapper - main initialization for page
*
* - Creates and loads map element
* - Attaches page event handlers
*/
function initQsoMapper() {
createMap(mapElementName);
// Update file name when a file is chosen
document.getElementById(fileInputFieldName).addEventListener('change', handleUploadFile);
// Call removeAllMarkers when 'Reset' is clicked and reset fileName
document.getElementById(resetButtonName).addEventListener('click', handleReset);
// If a url is specified in the query string, try to load and show markers from it
// This allows pre-coding a URL with the ADIF file
// https://stephenhouser.com/qso-mapper?url=sample/short.adi
// https://stephenhouser.com/qso-mapper?url=https://stephenhouser.com/qso-mapper/sample/short.adi
var url = getQueryVariable('url');
if (url !== null) {
disableFileUpload();
loadQSOsFromURL(url);
}
// TODO: Remove BootstrapTable jQuery dependency
$('#call-log').on('click-cell.bs.table', function (field, value, row, element) {
// TODO: Popping up the popup is leaflet specific
if (element.marker !== null) {
element.marker.openPopup();
}
});
}
/* disableFileUpload - disable the file upload form and reset buttons
*
* - Updates the file upload form (with the current file name)
* - Checks and parses the file, loading QSOs into an array
* - Adds markers (from QSOs) to the map
*/
function disableFileUpload() {
var fileUploadForm = document.getElementById(fileUploadFormName);
fileUploadForm.hidden = true;
var resetButton = document.getElementById(resetButtonName);
resetButton.hidden = true;
}
/* handleFileUpload - handle when upload file is triggered
*
* - Updates the file upload form (with the current file name)
* - Checks and parses the file, loading QSOs into an array
* - Adds markers (from QSOs) to the map
*/
function handleUploadFile() {
var fileName = this.value;
fileName = fileName.replace('C:\\fakepath\\', '');
setFileInputLabel(fileName);
// For each file object uploaded...(usually only one).
for (var i = 0; i < this.files.length; i++) {
loadQSOsFromFile(this.files[i]);
}
}
/* handleReset - handle pressing of the 'clear' button
*
* - Removes any markers or polygons from the map
* - Resets the form so a file with the same name can be loaded again.
*/
function handleReset() {
setFileInputLabel('Select file...');
removeAllQsos();
var fileUploadForm = document.getElementById(fileUploadFormName);
fileUploadForm.reset();
}
function setTitle(title) {
if (title.startsWith('# ')) {
var navBrand = document.getElementsByClassName('navbar-brand');
for (let i = 0; i < navBrand.length; i++) {
navBrand[i].textContent = title.substring(2);
}
}
}
/* loadQSOsFromFile - loads QSOs from uploaded file */
function loadQSOsFromFile(file) {
var reader = new FileReader();
reader.onload = function (e) {
var [header, loadedQsos] = Adif.parseAdif(e.target.result);
setTitle(header);
addQsos(loadedQsos);
};
reader.readAsText(file);
}
/* loadQSOsFromURL - load QSOs from URL */
function loadQSOsFromURL(url) {
var httpRequest;
function makeRequest(url) {
httpRequest = new XMLHttpRequest();
if (!httpRequest) {
alert("Giving up :( Cannot create an XMLHTTP instance");
return false;
}
httpRequest.onreadystatechange = readyStateChanged;
httpRequest.open("GET", url);
httpRequest.send();
}
function readyStateChanged() {
if (httpRequest.readyState === XMLHttpRequest.DONE) {
if (httpRequest.status === 200) {
var [header, loadedQsos] = Adif.parseAdif(httpRequest.responseText);
setTitle(header);
addQsos(loadedQsos);
} else {
alert("There was a problem loating " + url);
}
}
}
makeRequest(url);
}
/* addQsos - load QSOs from an ADIF (string)
*
* - Calls addMarkerFunc(lat, lon, text) for each QSO
* - Calls zoomToAllMarkers to zoom the map to contain the markers.
*/
function addQsos(qsos) {
for (var q = 0; q < qsos.length; q++) {
addQso(qsos[q]);
}
// TODO: Remove BootstrapTable jQuery dependency
$('#call-table').bootstrapTable('append', qsos);
zoomToAllMarkers();
}
/* removeAllQsos - remove all loaded QSOs from the map and the application */
function removeAllQsos() {
while (qsos.length > 0) {
var qso = qsos.pop();
removeQso(qso);
}
// TODO: Remove BootstrapTable jQuery dependency
$('#call-table').bootstrapTable('removeAll');
}
/* addQso - Add a single QSO to the Map
*
* - Calls addMarkerFunc(lat, lon, text) for each QSO
* - Calls zoomToAllMarkers to zoom the map to contain the markers.
*/
function addQso(qso) {
// Keep copies of the marker in the QSO. Might be a bad idea.
qso['marker'] = addMarkerForQso(qso);
qso['square'] = addSquareForQSO(qso);
qsos.push(qso);
}
function removeQso(qso) {
if (qso !== null) {
var qsoIndex = qsos.indexOf(qso);
if (qsoIndex > -1) {
qsos.splice(qsoIndex, 1);
}
if (typeof (qso.square) !== 'undefined') {
removePolygon(qso.square);
}
if (typeof (qso.marker) !== 'undefined') {
removeMarker(qso.marker);
}
}
}
/* createQTHMarker - create a marker on the map at the given position.
*
* Set up a marker on the map for operating QTH and corresponding pop-up for
* when the marker is selected.
*
* Change the color of the marker using CSS huechange
* https://stackoverflow.com/questions/23567203/leaflet-changing-marker-color
*/
function createQTHMarker(latlong, popupText) {
const [latitude, longitude] = latlong.split(',');
const popupDiv = '<div class="qth">' +
'<h1>' + popupText + '</h1><hr/>' +
'</div>';
marker = createMarker(latitude, longitude, popupDiv, {zIndexOffset: 1000});
marker._icon.classList.add("qth-icon");
return marker;
}
/* addMarkerforQth - add a marker to the map for a given operating location
*
* TODO: This function is for when we load QTH from ADIF File.
* It is not currently used.
*/
function addMarkerForQth(qth) {
var latlon = latLonForQso(qth);
if (latlon == null) {
return null;
}
const [latitude, longitude] = latlon;
const popupText = "Home QTH";
return createQTHMarker(`${latitude},${longitude}`, popupText);
}
/* addMarkerForQso - add a marker to the map for a given QSO */
function addMarkerForQso(qso) {
var latlon = latLonForQso(qso);
if (latlon === null) {
//throw "No location for qso";
return null;
}
var [latitude, longitude] = latlon;
var popupText = '<div class="qso">' +
'<h1>' + qso.call + '</h1><hr/>' +
'<div><p>' +
'Time:' + qso.qso_date + ' ' + qso.time_on +
' Band:' + qso.band +
' Mode:' + qso.mode +
'</p></div></div>';
marker = createMarker(latitude, longitude, popupText);
return marker;
}
/* addSquareForQso - add a polygon to the map to outline QSO grid square
*
* Color squares where the QSO has a latitude and longitude green
* Other squares are default colored (blue).
*/
function addSquareForQSO(qso) {
var square = squareForQso(qso);
if (square === null) {
return null;
}
// This is specifit to Leaflet.js and may not work on Google Maps.
var options = {};
if (typeof (qso.lat) === 'string' && typeof (qso.lon) === 'string') {
options['color'] = 'green';
}
var [[top, left], [bottom, right]] = square;
polygon = createPolygon(
[[top, left],
[top, right],
[bottom, right],
[bottom, left]],
options
);
return polygon;
}
/* squareForQso - returns a polygon for the QSO's approximate location
*
* top left, top right, bottom right, bottom left
* [lat, lon], [lat, lon], [lat, lon], [lat, lon]
*/
function squareForQso(qso) {
var gridsquare = qso.gridsquare
if (typeof (qso.gridsquare) === 'string' && qso.gridsquare !== '') {
var gsLen = gridsquare.length;
var topLeftSquare = gridsquare + 'AA00AA00AA'.substring(gsLen);
var botRightSquare = gridsquare + 'RR99XX99XX'.substring(gsLen);
var [top, left] = Locator.loc2latlon(topLeftSquare);
var [bottom, right] = Locator.loc2latlon(botRightSquare);
return [[top, left], [bottom, right]];
}
return null;
}
/* latLonForQSO - get the latitude and longitude for a QSO
*
* A QSO *may* already contain a latitide and longitude. If so, use those
* values. Otherwise, if the QSO contains a grid square (maidenhead locator),
* use the center of the square as the latitude and longitude.
* This makes use of the HamGridSquare.js:
* https://gist.github.com/stephenhouser/4ad8c1878165fc7125cb547431a2bdaa
*/
function latLonForQso(qso) {
// TODO: Using lat and lon from the ADIF is sometime off from real position.
if (typeof (qso.lat) === 'string' && typeof (qso.lon) === 'string') {
var latitude = Adif.parseCoordinate(qso.lat);
var longitude = Adif.parseCoordinate(qso.lon);
return [latitude, longitude];
}
if (typeof (qso.gridsquare) === 'string' && qso.gridsquare !== '') {
var [latitude, longitude] = Locator.loc2latlon(qso.gridsquare);
return [latitude, longitude];
}
return null;
}
/* setFileInputLabel - set the current 'uploaded' file name
*
* Utility function to set the last loaded file name on the file upload
* form.
*/
function setFileInputLabel(message) {
var fileUploadForm = document.getElementById(fileUploadFormName);
var fileLabels = fileUploadForm.getElementsByClassName('custom-file-label');
if (fileLabels !== null && fileLabels.length >= 1) {
fileLabels[0].innerHTML = message;
}
}
/* getQueryVariable - get HTTP/URL query variable or null if it does not exist. */
function getQueryVariable(variable) {
var query = window.location.search.substring(1);
var vars = query.split('&');
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split('=');
if (decodeURIComponent(pair[0]) == variable) {
return decodeURIComponent(pair[1]);
}
}
return null;
}