Skip to content

Commit 03d68ec

Browse files
author
newtom28
committed
Add airport and radio menu
1 parent 7870224 commit 03d68ec

File tree

13 files changed

+825
-104
lines changed

13 files changed

+825
-104
lines changed

index.html

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,22 @@
120120
class="control-button"
121121
data-i18n-title="tooltips.my_locale"
122122
>
123-
🌐
123+
🌎
124124
</button>
125125

126+
<div id="more-wrapper" class="dropdown-wrapper">
127+
<button
128+
id="more-button"
129+
class="control-button"
130+
data-i18n-title="tooltips.more_options"
131+
>
132+
133+
</button>
134+
<div id="more-menu" class="dropdown-menu">
135+
<!-- menu items will be inserted here -->
136+
</div>
137+
</div>
138+
126139
<!-- Search Bar -->
127140
<div id="search-bar-container" class="hidden">
128141
<!-- Form input to auto-save history -->

public/locales/en.json

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
"app": {
33
"loading_text": "Loading map…",
44
"caching_text": "Finding landmarks…",
5-
"search_placeholder": "Search location…"
5+
"search_placeholder": "Search location…",
6+
"airport_menu": "Search Airports",
7+
"radio_menu": "Search Radio Stations"
68
},
79
"Settings": {
810
"GOOGLE_MAPS_API_KEY": "Google Maps API Key",
@@ -20,15 +22,14 @@
2022
"connection_description": "There was a problem retrieving landmarks information. This could be due to:",
2123
"network_issue": "Network connectivity issues",
2224
"api_unavailable": "API service temporarily unavailable",
23-
"connection_suggestion": "Please check your network connection and try again later.",
2425
"retry_button": "Try Again"
2526
}
2627
},
2728
"landmark": {
2829
"overlay": {
2930
"load_failed_title": "3D Map Failed to Load",
30-
"load_failed_description": "The photorealistic 3D view could not be initialized.",
31-
"load_failed_details": "This may be due to browser compatibility, network issues, or Google Maps 3D beta access limitations.",
31+
"load_failed_description": "The Google 3D Map view could not be initialized.",
32+
"load_failed_details": "This may be due to browser compatibility, network issues, or API access limitations.",
3233
"close_button": "Close 3D View"
3334
}
3435
},
@@ -51,11 +52,13 @@
5152
"replay_animation": "Replay Animation",
5253
"teleport_to_aircraft": "Teleport to aircraft position",
5354
"view_in_3d": "View in 3D",
54-
"delete_setting": "Delete Setting"
55+
"delete_setting": "Delete Setting",
56+
"more_options": "More Options"
5557
},
5658
"errors": {
5759
"no_landmarks_found": "No landmarks found in this area",
5860
"location_not_found": "Location not found",
61+
"no_results": "No search results",
5962
"geolocation_not_supported": "Geolocation not supported",
6063
"unable_to_get_geolocation": "Unable to get Geolocation",
6164
"moving_map_server_unavailable": "Moving Map server not available",

server/simconnect.py

Lines changed: 40 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2,59 +2,44 @@
22
Connection to MSFS via SimConnect for real-time aircraft telemetry.
33
"""
44

5-
import time
65
import threading
6+
import time
77

88
try:
99
from SimConnect import SimConnect, AircraftRequests
1010
except Exception as e: # noqa: F841
1111
SimConnect = None
12-
AircraftRequests = None
1312

14-
# Global variables for aircraft data
1513
aircraft_data = {
1614
"latitude": 0.0,
1715
"longitude": 0.0,
1816
"altitude": 0.0, # in meters
1917
"heading": 0.0, # in degrees
2018
}
21-
22-
# SimConnect instance
23-
sm = None
19+
sm_aq = None
2420
data_thread = None
25-
data_lock = threading.Lock()
2621

2722

2823
def connect_to_simconnect():
29-
"""Initialize SimConnect connection to MSFS"""
30-
global sm, aircraft_data
3124
if SimConnect is None:
32-
print("SimConnect library not available")
25+
print("SimConnect connectivity not available")
3326
return None
34-
3527
try:
36-
if sm is None:
37-
sm = SimConnect()
38-
39-
# Request data definitions for aircraft position and orientation
40-
# AircraftRequests handles the data request setup automatically
41-
if AircraftRequests is None:
42-
print("SimConnect library not available")
43-
return None
44-
aq = AircraftRequests(sm, _time=100) # Update every 100ms
45-
46-
with data_lock:
28+
global sm_aq
29+
if sm_aq is None:
30+
sm_aq = AircraftRequests(
31+
SimConnect(), _time=100
32+
) # Update every 100ms
4733
aircraft_data["connected"] = True
48-
return aq
34+
return sm_aq
4935
except Exception as e:
5036
print(f"✗ Failed to connect via SimConnect: {e}")
5137
return None
5238

5339

54-
def update_aircraft_data():
40+
def update_aircraft_data(stop_event):
5541
"""Background thread to continuously update aircraft data"""
56-
global aircraft_data
57-
if aq := connect_to_simconnect():
42+
if connect_to_simconnect():
5843
print("Starting SimConnect Proxy for MSFS Moving map...")
5944
else:
6045
center_lat = 37.6188 # e.g. SFO airport
@@ -63,25 +48,25 @@ def update_aircraft_data():
6348
t = 0.0
6449
print("Starting Circular flight demo/test mode...")
6550

66-
while True:
51+
global sm_aq
52+
while not stop_event.is_set():
6753
try:
68-
if sm and aq:
54+
if sm_aq:
6955
# Get current aircraft position and heading
70-
lat = aq.get("PLANE_LATITUDE")
71-
lon = aq.get("PLANE_LONGITUDE")
72-
alt_ft = aq.get("PLANE_ALTITUDE")
73-
heading = aq.get("MAGNETIC_COMPASS")
74-
with data_lock:
75-
aircraft_data.update(
76-
{
77-
"latitude": lat if lat else 0.0,
78-
"longitude": lon if lon else 0.0,
79-
"altitude": alt_ft * 0.3048 if alt_ft else 0.0,
80-
"heading": heading if heading else 0.0,
81-
"connected": True,
82-
"last_update": time.time(),
83-
}
84-
)
56+
lat = sm_aq.get("PLANE_LATITUDE")
57+
lon = sm_aq.get("PLANE_LONGITUDE")
58+
alt_ft = sm_aq.get("PLANE_ALTITUDE")
59+
heading = sm_aq.get("MAGNETIC_COMPASS")
60+
aircraft_data.update(
61+
{
62+
"latitude": lat if lat else 0.0,
63+
"longitude": lon if lon else 0.0,
64+
"altitude": alt_ft * 0.3048 if alt_ft else 0.0,
65+
"heading": heading if heading else 0.0,
66+
"connected": True,
67+
"last_update": time.time(),
68+
}
69+
)
8570
else:
8671
import math
8772

@@ -96,20 +81,26 @@ def update_aircraft_data():
9681
t += 0.05
9782

9883
except Exception as err:
84+
stop_event.set()
85+
sm_aq = None
9986
print(f"Error reading aircraft data: {err}")
100-
with data_lock:
101-
aircraft_data["connected"] = False
87+
aircraft_data["connected"] = False
10288

10389
# Update at 1Hz
10490
time.sleep(1)
10591

10692

10793
def start_simconnect_server(status):
10894
if status == "connect":
109-
return connect_to_simconnect()
95+
return connect_to_simconnect()
11096

11197
global data_thread
112-
if status == "track" and (data_thread is None or not data_thread.is_alive()):
113-
# Start background thread for data collection
114-
data_thread = threading.Thread(target=update_aircraft_data, daemon=True)
98+
if status == "track" and (
99+
data_thread is None or not data_thread.is_alive()
100+
):
101+
# Start background thread for data updates
102+
stop_event = threading.Event()
103+
data_thread = threading.Thread(
104+
target=update_aircraft_data, args=(stop_event,), daemon=True
105+
)
115106
data_thread.start()

src/app.js

Lines changed: 62 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
/* eslint-disable no-undef */
2-
import { initSearch, searchLandmarks, getUserLocation } from './search.js';
2+
import {
3+
initSearch,
4+
searchLandmarks,
5+
getUserLocation,
6+
searchAirport,
7+
openInternetRadio,
8+
} from './search.js';
39
import { initLandmark } from './landmark.js';
410
import {
511
getConfig,
@@ -16,11 +22,11 @@ import { i18n, initi18n, updateTranslation } from './lion.js';
1622

1723
const translationMap = {
1824
// mapping DOM selectors to translation keys
19-
'.loading-text': { property: 'textContent', key: 'app.loading_text' },
20-
'.caching-text': { property: 'textContent', key: 'app.caching_text' },
25+
'.loading-text': { property: 'textContent', strkey: 'app.loading_text' },
26+
'.caching-text': { property: 'textContent', strkey: 'app.caching_text' },
2127
'input#search-input': {
2228
property: 'placeholder',
23-
key: 'app.search_placeholder',
29+
strkey: 'app.search_placeholder',
2430
},
2531
};
2632

@@ -33,6 +39,9 @@ const settingsButton = document.getElementById('settings-button');
3339
const localeButton = document.getElementById('locale-button');
3440
const searchSideBar = document.getElementById('search-bar-container');
3541
const landmarkSidebar = document.getElementById('landmarks-sidebar');
42+
const moreWrapper = document.getElementById('more-wrapper');
43+
const moreButton = document.getElementById('more-button');
44+
const moreMenu = document.getElementById('more-menu');
3645

3746
// Default coordinates (San Francisco)
3847
let defaultLocation = { lat: 37.7749, lng: -122.4194 };
@@ -144,16 +153,43 @@ async function initMap() {
144153
setLoading(false);
145154
}
146155

156+
/**
157+
* Adds a new option to More-menu dropdown.
158+
* @param {string} strkey - The translation key for this label.
159+
* @param {Function} handler - Function called when the option is clicked.
160+
*/
161+
export function addMoreOption(strkey, handler) {
162+
const item = document.createElement('div');
163+
item.className = 'dropdown-item';
164+
item.setAttribute('data-i18n-text', strkey);
165+
item.addEventListener('click', (ev) => {
166+
handler(ev);
167+
moreMenu.classList.remove('show'); // hide after selection
168+
});
169+
moreMenu.appendChild(item);
170+
}
171+
172+
// when clicking elsewhere on the document
173+
document.addEventListener('click', () => {
174+
moreMenu.classList.remove('show');
175+
});
176+
147177
/**
148178
* Set up custom controls
149179
*/
150180
async function setupCustomControl() {
181+
// add each button into gmap DOM structure, attaching click listeners
151182
map.controls[google.maps.ControlPosition.RIGHT_BOTTOM].push(myLocationButton);
152183
myLocationButton.addEventListener('click', async () => {
153184
await markUserLocation();
154185
});
155186

156-
// Add click event to search landmarks button
187+
map.controls[google.maps.ControlPosition.RIGHT_BOTTOM].push(moreWrapper);
188+
moreButton.addEventListener('click', (ev) => {
189+
ev.stopPropagation(); // Prevent click bubbling
190+
moreMenu.classList.toggle('show');
191+
});
192+
157193
map.controls[google.maps.ControlPosition.RIGHT_BOTTOM].push(
158194
searchLandmarksButton
159195
);
@@ -178,9 +214,13 @@ async function setupCustomControl() {
178214
if (i18n.lang.secondLocale) {
179215
map.controls[google.maps.ControlPosition.LEFT_BOTTOM].push(localeButton);
180216
localeButton.addEventListener('click', async () => {
181-
if (i18n.userLocale === i18n.lang.preferLocale)
217+
if (i18n.userLocale === i18n.lang.preferLocale) {
182218
i18n.userLocale = i18n.lang.secondLocale;
183-
else i18n.userLocale = i18n.lang.preferLocale;
219+
localeButton.textContent = '🌏';
220+
} else {
221+
i18n.userLocale = i18n.lang.preferLocale;
222+
localeButton.textContent = '🌎';
223+
}
184224
// await updateTranslation();
185225
await applyTranslations();
186226
});
@@ -257,23 +297,23 @@ function loadGoogleMapsAPI() {
257297
}
258298

259299
async function applyTranslations() {
260-
Object.entries(translationMap).forEach(([selector, { property, key }]) => {
300+
Object.entries(translationMap).forEach(([selector, { property, strkey }]) => {
261301
document.querySelectorAll(selector).forEach((el) => {
262302
if (property in el || property === 'textContent') {
263-
el[property] = i18n.t(key);
303+
el[property] = i18n.t(strkey);
264304
}
265305
});
266306
});
267307

268308
document.querySelectorAll('[data-i18n-text]').forEach((el) => {
269-
const key = el.getAttribute('data-i18n-text');
270-
const str_value = i18n.t(key);
271-
el.textContent = str_value === key ? '' : str_value;
309+
const strkey = el.getAttribute('data-i18n-text');
310+
const str_value = i18n.t(strkey);
311+
el.textContent = str_value === strkey ? '' : str_value;
272312
});
273313

274314
document.querySelectorAll('[data-i18n-title]').forEach((el) => {
275-
const key = el.getAttribute('data-i18n-title');
276-
el.title = i18n.t(key); // Set title for tooltips
315+
const strkey = el.getAttribute('data-i18n-title');
316+
el.title = i18n.t(strkey); // Set title for tooltips
277317
});
278318
}
279319

@@ -289,6 +329,14 @@ document.addEventListener('DOMContentLoaded', async () => {
289329
// Load Google Maps API
290330
loadGoogleMapsAPI();
291331

332+
addMoreOption('app.airport_menu', async () => {
333+
await searchAirport();
334+
});
335+
336+
addMoreOption('app.radio_menu', async () => {
337+
await openInternetRadio();
338+
});
339+
292340
// Skip auto-translation if no resource bundles are loaded
293341
if (Object.keys(i18n.translations).length > 0) {
294342
await updateTranslation();

src/components.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -219,9 +219,9 @@ class SettingDialog {
219219
const tr = document.createElement('tr');
220220

221221
const tdLabel = document.createElement('td');
222-
const str_key = `Settings.${key}`;
223-
const str_value = i18n.t(str_key);
224-
tdLabel.textContent = str_value === str_key ? key : str_value;
222+
const strkey = `Settings.${key}`;
223+
const strval = i18n.t(strkey);
224+
tdLabel.textContent = strval === strkey ? key : strval;
225225
tr.appendChild(tdLabel);
226226

227227
const tdInput = document.createElement('td');

src/gmap.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,14 +93,15 @@ export async function getLocationDetails(latitude, longitude) {
9393
parts.push(country);
9494

9595
const locationName = parts.length ? parts.join(', ') : 'Unknown Location';
96-
console.debug(`Where is (${cacheKey}): ${locationName} (${countryCode})`);
97-
9896
const resultDict = {
9997
locationName,
98+
city: locality ? locality : adminArea,
99+
state: locality ? adminArea : undefined,
100100
country: country || 'Unknown',
101101
countryCode,
102102
};
103103
locationCache[cacheKey] = resultDict;
104+
console.debug(`Where is (${cacheKey}): ${locationName}`, resultDict);
104105
return resultDict;
105106
}
106107
} catch (error) {

0 commit comments

Comments
 (0)