Added a map to show where other stn are // refactored message adding to tables for performance, max 30.000 msg now
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
S53MM
|
||||||
|
PA9R
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,5 @@
|
|||||||
package kst4contest.view.map;
|
package kst4contest.view.map;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.UncheckedIOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTML host for the JavaFX WebView map.
|
* HTML host for the JavaFX WebView map.
|
||||||
*
|
*
|
||||||
@@ -21,21 +16,7 @@ public final class MapHtmlResources {
|
|||||||
private MapHtmlResources() {
|
private MapHtmlResources() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String readRequiredResource(String resourcePath) {
|
public static String createStationMapHtml() {
|
||||||
try (InputStream inputStream = MapHtmlResources.class.getResourceAsStream(resourcePath)) {
|
|
||||||
if (inputStream == null) {
|
|
||||||
throw new IllegalStateException("Missing map resource: " + resourcePath);
|
|
||||||
}
|
|
||||||
return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
|
|
||||||
} catch (IOException exception) {
|
|
||||||
throw new UncheckedIOException("Could not read map resource: " + resourcePath, exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String createStationMapHtml(int tileProxyPort) {
|
|
||||||
String leafletCss = readRequiredResource("/web/leaflet/leaflet.css");
|
|
||||||
String leafletJs = readRequiredResource("/web/leaflet/leaflet.js");
|
|
||||||
|
|
||||||
return """
|
return """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
@@ -43,9 +24,10 @@ public final class MapHtmlResources {
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>KST4Contest Station Map</title>
|
<title>KST4Contest Station Map</title>
|
||||||
<style>
|
<link rel="stylesheet"
|
||||||
""" + leafletCss + """
|
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
||||||
</style>
|
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
|
||||||
|
crossorigin="">
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--map-background: #ede9df;
|
--map-background: #ede9df;
|
||||||
@@ -214,10 +196,9 @@ public final class MapHtmlResources {
|
|||||||
<body class="kst-theme-light">
|
<body class="kst-theme-light">
|
||||||
<div id="map"></div>
|
<div id="map"></div>
|
||||||
|
|
||||||
<script>
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
|
||||||
""" + leafletJs + """
|
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
|
||||||
</script>
|
crossorigin=""></script>
|
||||||
<script>window._kstTileProxyPort=__TILE_PROXY_PORT__;</script>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
@@ -355,12 +336,7 @@ public final class MapHtmlResources {
|
|||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
if (map) {
|
if (map) {
|
||||||
return true;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof L === 'undefined') {
|
|
||||||
jsError('Leaflet is not loaded. Station map cannot initialize.');
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
applyThemeClass();
|
applyThemeClass();
|
||||||
@@ -371,13 +347,10 @@ public final class MapHtmlResources {
|
|||||||
|
|
||||||
jsLog('Leaflet map initialized');
|
jsLog('Leaflet map initialized');
|
||||||
|
|
||||||
L.tileLayer(
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
'http://127.0.0.1:' + window._kstTileProxyPort + '/tiles/{s}/{z}/{x}/{y}.png',
|
|
||||||
{
|
|
||||||
maxZoom: 18,
|
maxZoom: 18,
|
||||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
attribution: '© OpenStreetMap'
|
||||||
}
|
}).addTo(map);
|
||||||
).addTo(map);
|
|
||||||
|
|
||||||
map.createPane('beamPane');
|
map.createPane('beamPane');
|
||||||
map.getPane('beamPane').style.zIndex = 410;
|
map.getPane('beamPane').style.zIndex = 410;
|
||||||
@@ -412,7 +385,6 @@ public final class MapHtmlResources {
|
|||||||
|
|
||||||
notifyMapReady();
|
notifyMapReady();
|
||||||
notifyViewport();
|
notifyViewport();
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function invalidateSize() {
|
function invalidateSize() {
|
||||||
@@ -496,17 +468,13 @@ public final class MapHtmlResources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setHome(lat, lon, zoom) {
|
function setHome(lat, lon, zoom) {
|
||||||
if (!init()) {
|
init();
|
||||||
return;
|
|
||||||
}
|
|
||||||
jsLog('setHome lat=' + lat + ' lon=' + lon + ' zoom=' + zoom);
|
jsLog('setHome lat=' + lat + ' lon=' + lon + ' zoom=' + zoom);
|
||||||
map.setView([lat, lon], zoom);
|
map.setView([lat, lon], zoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setStations(stationsJson) {
|
function setStations(stationsJson) {
|
||||||
if (!init()) {
|
init();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
stationLayer.clearLayers();
|
stationLayer.clearLayers();
|
||||||
markersByCallsignRaw = {};
|
markersByCallsignRaw = {};
|
||||||
@@ -535,9 +503,7 @@ public final class MapHtmlResources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setBeam(beamJson) {
|
function setBeam(beamJson) {
|
||||||
if (!init()) {
|
init();
|
||||||
return;
|
|
||||||
}
|
|
||||||
beamLayer.clearLayers();
|
beamLayer.clearLayers();
|
||||||
|
|
||||||
if (!beamJson || beamJson === 'null') {
|
if (!beamJson || beamJson === 'null') {
|
||||||
@@ -564,9 +530,7 @@ public final class MapHtmlResources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setConnection(connectionJson) {
|
function setConnection(connectionJson) {
|
||||||
if (!init()) {
|
init();
|
||||||
return;
|
|
||||||
}
|
|
||||||
connectionLayer.clearLayers();
|
connectionLayer.clearLayers();
|
||||||
|
|
||||||
if (!connectionJson || connectionJson === 'null') {
|
if (!connectionJson || connectionJson === 'null') {
|
||||||
@@ -591,9 +555,7 @@ public final class MapHtmlResources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setProfileHoverPoint(point) {
|
function setProfileHoverPoint(point) {
|
||||||
if (!init()) {
|
init();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (profileHoverMarker) {
|
if (profileHoverMarker) {
|
||||||
map.removeLayer(profileHoverMarker);
|
map.removeLayer(profileHoverMarker);
|
||||||
@@ -622,9 +584,7 @@ public final class MapHtmlResources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setGrid(gridJson) {
|
function setGrid(gridJson) {
|
||||||
if (!init()) {
|
init();
|
||||||
return;
|
|
||||||
}
|
|
||||||
gridLayer.clearLayers();
|
gridLayer.clearLayers();
|
||||||
|
|
||||||
const cells = JSON.parse(gridJson);
|
const cells = JSON.parse(gridJson);
|
||||||
@@ -710,6 +670,6 @@ public final class MapHtmlResources {
|
|||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
""".replace("__TILE_PROXY_PORT__", String.valueOf(tileProxyPort));
|
""";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import javafx.collections.ListChangeListener;
|
|||||||
import javafx.scene.control.TableView;
|
import javafx.scene.control.TableView;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
import kst4contest.controller.ChatController;
|
import kst4contest.controller.ChatController;
|
||||||
|
import kst4contest.locatorUtils.Location;
|
||||||
import kst4contest.model.ChatMember;
|
import kst4contest.model.ChatMember;
|
||||||
import kst4contest.model.ChatPreferences;
|
import kst4contest.model.ChatPreferences;
|
||||||
|
|
||||||
@@ -14,13 +15,12 @@ import java.util.Comparator;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ThreadFactory;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import kst4contest.model.Band;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Synchronizes the application state with the station map window.
|
* Synchronizes the application state with the station map window.
|
||||||
*
|
*
|
||||||
@@ -32,7 +32,7 @@ import java.util.function.Predicate;
|
|||||||
*/
|
*/
|
||||||
public final class StationMapBridge {
|
public final class StationMapBridge {
|
||||||
|
|
||||||
|
private final ExecutorService pathAnalysisExecutor = Executors.newSingleThreadExecutor(new PathAnalysisThreadFactory());
|
||||||
private final AtomicLong pathAnalysisGeneration = new AtomicLong(0);
|
private final AtomicLong pathAnalysisGeneration = new AtomicLong(0);
|
||||||
|
|
||||||
private final ChatController chatController;
|
private final ChatController chatController;
|
||||||
@@ -45,7 +45,8 @@ public final class StationMapBridge {
|
|||||||
|
|
||||||
|
|
||||||
private final MapCallsignRawSnapshotBuilder snapshotBuilder = new MapCallsignRawSnapshotBuilder();
|
private final MapCallsignRawSnapshotBuilder snapshotBuilder = new MapCallsignRawSnapshotBuilder();
|
||||||
|
// private final OfflineDemManager offlineDemManager = new OfflineDemManager();
|
||||||
|
private final PathAnalysisService pathAnalysisService;
|
||||||
|
|
||||||
private String lastPathAnalysisRequestSignature = "";
|
private String lastPathAnalysisRequestSignature = "";
|
||||||
|
|
||||||
@@ -63,7 +64,9 @@ public final class StationMapBridge {
|
|||||||
|
|
||||||
this.refreshCoalescer.setOnFinished(event -> refreshNow());
|
this.refreshCoalescer.setOnFinished(event -> refreshNow());
|
||||||
|
|
||||||
|
this.pathAnalysisService = new GeometryOnlyPathAnalysisService(
|
||||||
|
new OpenMeteoTerrainProfileProvider()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void install() {
|
public void install() {
|
||||||
@@ -82,10 +85,6 @@ public final class StationMapBridge {
|
|||||||
(obs, oldValue, newValue) -> scheduleRefresh()
|
(obs, oldValue, newValue) -> scheduleRefresh()
|
||||||
);
|
);
|
||||||
|
|
||||||
chatController.getLst_chatMemberListFilterPredicates().addListener(
|
|
||||||
(ListChangeListener<Predicate<ChatMember>>) change -> requestImmediateRefresh()
|
|
||||||
);
|
|
||||||
|
|
||||||
requestImmediateRefresh();
|
requestImmediateRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,17 +170,6 @@ public final class StationMapBridge {
|
|||||||
requestPathAnalysisAsync(preferences.getStn_loginLocatorMainCat(), selectedSnapshot);
|
requestPathAnalysisAsync(preferences.getStn_loginLocatorMainCat(), selectedSnapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Requests the selected station path analysis through the central reachability
|
|
||||||
* service.
|
|
||||||
*
|
|
||||||
* <p>The map no longer owns a separate PathAnalysisService. This ensures that
|
|
||||||
* the map detail panel, the Tropo table column and the station filters all use
|
|
||||||
* exactly the same path/link-budget calculation.</p>
|
|
||||||
*
|
|
||||||
* @param ownLocator6 own locator from preferences
|
|
||||||
* @param selectedSnapshot selected map marker snapshot
|
|
||||||
*/
|
|
||||||
private void requestPathAnalysisAsync(String ownLocator6, MapCallsignRawSnapshot selectedSnapshot) {
|
private void requestPathAnalysisAsync(String ownLocator6, MapCallsignRawSnapshot selectedSnapshot) {
|
||||||
String normalizedOwnLocator6 = normalizeLocator6(ownLocator6);
|
String normalizedOwnLocator6 = normalizeLocator6(ownLocator6);
|
||||||
|
|
||||||
@@ -214,28 +202,20 @@ public final class StationMapBridge {
|
|||||||
PathAnalysisResult.loading(normalizedOwnLocator6, normalizedTargetLocator6, targetCallsignRaw)
|
PathAnalysisResult.loading(normalizedOwnLocator6, normalizedTargetLocator6, targetCallsignRaw)
|
||||||
);
|
);
|
||||||
|
|
||||||
ChatMember selectedMember = resolveBestChatMember(targetCallsignRaw);
|
pathAnalysisExecutor.submit(() -> {
|
||||||
|
PathAnalysisResult result = buildPathAnalysisResult(normalizedOwnLocator6, selectedSnapshot);
|
||||||
|
|
||||||
chatController.getReachabilityService().requestPathAnalysisForMap(
|
Platform.runLater(() -> {
|
||||||
selectedMember,
|
|
||||||
selectedSnapshot,
|
|
||||||
result -> {
|
|
||||||
if (generation != pathAnalysisGeneration.get()) {
|
if (generation != pathAnalysisGeneration.get()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
stationMapView.setPathAnalysisResult(result);
|
stationMapView.setPathAnalysisResult(result);
|
||||||
}
|
});
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Invalidates pending map callbacks.
|
|
||||||
*
|
|
||||||
* <p>The actual calculation executor is owned by ReachabilityService now, so
|
|
||||||
* this bridge no longer shuts down any path-analysis thread directly.</p>
|
|
||||||
*/
|
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
pathAnalysisGeneration.incrementAndGet();
|
pathAnalysisExecutor.shutdownNow();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleMapCallsignSelection(String callSignRaw) {
|
private void handleMapCallsignSelection(String callSignRaw) {
|
||||||
@@ -306,6 +286,43 @@ public final class StationMapBridge {
|
|||||||
return callSignRaw.trim().toUpperCase(Locale.ROOT);
|
return callSignRaw.trim().toUpperCase(Locale.ROOT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PathAnalysisResult buildPathAnalysisResult(String ownLocator6, MapCallsignRawSnapshot selectedSnapshot) {
|
||||||
|
String normalizedOwnLocator6 = normalizeLocator6(ownLocator6);
|
||||||
|
|
||||||
|
if (selectedSnapshot == null) {
|
||||||
|
return PathAnalysisResult.waitingForSelection(normalizedOwnLocator6);
|
||||||
|
}
|
||||||
|
|
||||||
|
String normalizedTargetLocator6 = normalizeLocator6(selectedSnapshot.locator6());
|
||||||
|
|
||||||
|
if (normalizedOwnLocator6.length() != 6) {
|
||||||
|
return PathAnalysisResult.waitingForValidHomeLocator(normalizedOwnLocator6, normalizedTargetLocator6);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selectedSnapshot.hasUsablePosition()) {
|
||||||
|
return PathAnalysisResult.waitingForValidTarget(normalizedOwnLocator6, normalizedTargetLocator6);
|
||||||
|
}
|
||||||
|
|
||||||
|
Location homeLocation = new Location(normalizedOwnLocator6);
|
||||||
|
double analysisFrequencyMHz = resolveAnalysisFrequencyMHz(selectedSnapshot);
|
||||||
|
|
||||||
|
PathAnalysisRequest request = new PathAnalysisRequest(
|
||||||
|
normalizedOwnLocator6,
|
||||||
|
homeLocation.getLatitude().toDegrees(),
|
||||||
|
homeLocation.getLongitude().toDegrees(),
|
||||||
|
selectedSnapshot.callSignRaw(),
|
||||||
|
normalizedTargetLocator6,
|
||||||
|
selectedSnapshot.latitudeDeg(),
|
||||||
|
selectedSnapshot.longitudeDeg(),
|
||||||
|
analysisFrequencyMHz,
|
||||||
|
chatController.getChatPreferences().getStn_pathAnalysisOwnAntennaHeightMeters(),
|
||||||
|
chatController.getChatPreferences().getStn_pathAnalysisDefaultTargetAntennaHeightMeters(),
|
||||||
|
PathGeometryUtils.DEFAULT_EFFECTIVE_EARTH_RADIUS_FACTOR,
|
||||||
|
chatController.getChatPreferences().buildPathLinkBudgetSettings()
|
||||||
|
);
|
||||||
|
|
||||||
|
return pathAnalysisService.analyze(request);
|
||||||
|
}
|
||||||
|
|
||||||
private double resolveAnalysisFrequencyMHz(MapCallsignRawSnapshot selectedSnapshot) {
|
private double resolveAnalysisFrequencyMHz(MapCallsignRawSnapshot selectedSnapshot) {
|
||||||
if (selectedSnapshot == null) {
|
if (selectedSnapshot == null) {
|
||||||
@@ -359,5 +376,12 @@ public final class StationMapBridge {
|
|||||||
return locator.trim().toUpperCase(Locale.ROOT);
|
return locator.trim().toUpperCase(Locale.ROOT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class PathAnalysisThreadFactory implements ThreadFactory {
|
||||||
|
@Override
|
||||||
|
public Thread newThread(Runnable runnable) {
|
||||||
|
Thread thread = new Thread(runnable, "station-map-path-analysis");
|
||||||
|
thread.setDaemon(true);
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,6 @@ import kst4contest.ApplicationConstants;
|
|||||||
import kst4contest.locatorUtils.Location;
|
import kst4contest.locatorUtils.Location;
|
||||||
import kst4contest.model.ChatPreferences;
|
import kst4contest.model.ChatPreferences;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@@ -49,7 +48,6 @@ public final class StationMapView {
|
|||||||
private final Stage stage = new Stage();
|
private final Stage stage = new Stage();
|
||||||
private final WebView webView = new WebView();
|
private final WebView webView = new WebView();
|
||||||
private final WebEngine webEngine = webView.getEngine();
|
private final WebEngine webEngine = webView.getEngine();
|
||||||
private TileProxyServer tileProxyServer;
|
|
||||||
|
|
||||||
private Scene scene;
|
private Scene scene;
|
||||||
private BorderPane rootPane;
|
private BorderPane rootPane;
|
||||||
@@ -129,11 +127,6 @@ public final class StationMapView {
|
|||||||
|
|
||||||
public StationMapView(ChatPreferences chatPreferences) {
|
public StationMapView(ChatPreferences chatPreferences) {
|
||||||
this.chatPreferences = Objects.requireNonNull(chatPreferences, "chatPreferences");
|
this.chatPreferences = Objects.requireNonNull(chatPreferences, "chatPreferences");
|
||||||
try {
|
|
||||||
tileProxyServer = new TileProxyServer();
|
|
||||||
} catch (IOException e) {
|
|
||||||
System.err.println("[StationMap] tile proxy failed to start: " + e.getMessage());
|
|
||||||
}
|
|
||||||
initializeUi();
|
initializeUi();
|
||||||
initializeWebView();
|
initializeWebView();
|
||||||
}
|
}
|
||||||
@@ -634,8 +627,7 @@ public final class StationMapView {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
int proxyPort = tileProxyServer != null ? tileProxyServer.getPort() : 0;
|
webEngine.loadContent(MapHtmlResources.createStationMapHtml());
|
||||||
webEngine.loadContent(MapHtmlResources.createStationMapHtml(proxyPort));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void requestMapInvalidateSize() {
|
private void requestMapInvalidateSize() {
|
||||||
@@ -1101,7 +1093,6 @@ public final class StationMapView {
|
|||||||
webEngine.executeScript(script);
|
webEngine.executeScript(script);
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
System.err.println("[StationMap] executeScript failed: " + exception.getMessage());
|
System.err.println("[StationMap] executeScript failed: " + exception.getMessage());
|
||||||
exception.printStackTrace();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2366,8 +2366,3 @@ OZ1FDH;Claus;JO55QX;StringProperty [value: null]; wkd true; wkd144 true; wkd432f
|
|||||||
OZ7KJ;Skive Club;JO46ML;StringProperty [value: 144.225]; wkd true; wkd144 true; wkd432false; wkd1240false; wkd2300false; wkd3400false; wkd5600false; wkd10Gfalse ; 2: 144/432 MHz
|
OZ7KJ;Skive Club;JO46ML;StringProperty [value: 144.225]; wkd true; wkd144 true; wkd432false; wkd1240false; wkd2300false; wkd3400false; wkd5600false; wkd10Gfalse ; 2: 144/432 MHz
|
||||||
SM7VUK;Bengt;JO66LI;StringProperty [value: 144.304]; wkd true; wkd144 true; wkd432false; wkd1240false; wkd2300false; wkd3400false; wkd5600false; wkd10Gfalse ; 2: 144/432 MHz
|
SM7VUK;Bengt;JO66LI;StringProperty [value: 144.304]; wkd true; wkd144 true; wkd432false; wkd1240false; wkd2300false; wkd3400false; wkd5600false; wkd10Gfalse ; 2: 144/432 MHz
|
||||||
OZ1HDF;Ken;JO55UN;StringProperty [value: null]; wkd true; wkd144 true; wkd432false; wkd1240false; wkd2300false; wkd3400false; wkd5600false; wkd10Gfalse ; 2: 144/432 MHz
|
OZ1HDF;Ken;JO55UN;StringProperty [value: null]; wkd true; wkd144 true; wkd432false; wkd1240false; wkd2300false; wkd3400false; wkd5600false; wkd10Gfalse ; 2: 144/432 MHz
|
||||||
DO5SA;unknown;unknown;StringProperty [value: null]; wkd true; wkd144 true; wkd432false; wkd1240false; wkd2300false; wkd3400false; wkd5600false; wkd10Gfalse ; null
|
|
||||||
9A2HM;unknown;unknown;StringProperty [value: null]; wkd true; wkd144 false; wkd432false; wkd1240false; wkd2300false; wkd3400false; wkd5600false; wkd10Gfalse ; null
|
|
||||||
9A2HM;null;null;StringProperty [value: null]; wkd true; wkd144 false; wkd432true; wkd1240false; wkd2300false; wkd3400false; wkd5600false; wkd10Gfalse ; null
|
|
||||||
9A2HM;null;null;StringProperty [value: null]; wkd true; wkd144 false; wkd432false; wkd1240false; wkd2300true; wkd3400false; wkd5600false; wkd10Gfalse ; null
|
|
||||||
OV3T;Thomas;JO46CM;StringProperty [value: null]; wkd true; wkd144 true; wkd432false; wkd1240false; wkd2300false; wkd3400false; wkd5600false; wkd10Gfalse ; 2: 144/432 MHz
|
|
||||||
Reference in New Issue
Block a user