mirror of
https://github.com/praktimarc/kst4contest.git
synced 2026-07-14 00:26:52 +02:00
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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* HTML host for the JavaFX WebView map.
|
||||
*
|
||||
@@ -21,21 +16,7 @@ public final class MapHtmlResources {
|
||||
private MapHtmlResources() {
|
||||
}
|
||||
|
||||
private static String readRequiredResource(String resourcePath) {
|
||||
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");
|
||||
|
||||
public static String createStationMapHtml() {
|
||||
return """
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
@@ -43,9 +24,10 @@ public final class MapHtmlResources {
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>KST4Contest Station Map</title>
|
||||
<style>
|
||||
""" + leafletCss + """
|
||||
</style>
|
||||
<link rel="stylesheet"
|
||||
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
||||
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
|
||||
crossorigin="">
|
||||
<style>
|
||||
:root {
|
||||
--map-background: #ede9df;
|
||||
@@ -214,10 +196,9 @@ public final class MapHtmlResources {
|
||||
<body class="kst-theme-light">
|
||||
<div id="map"></div>
|
||||
|
||||
<script>
|
||||
""" + leafletJs + """
|
||||
</script>
|
||||
<script>window._kstTileProxyPort=__TILE_PROXY_PORT__;</script>
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
|
||||
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
|
||||
crossorigin=""></script>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -355,12 +336,7 @@ public final class MapHtmlResources {
|
||||
|
||||
function init() {
|
||||
if (map) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof L === 'undefined') {
|
||||
jsError('Leaflet is not loaded. Station map cannot initialize.');
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
applyThemeClass();
|
||||
@@ -371,13 +347,10 @@ public final class MapHtmlResources {
|
||||
|
||||
jsLog('Leaflet map initialized');
|
||||
|
||||
L.tileLayer(
|
||||
'http://127.0.0.1:' + window._kstTileProxyPort + '/tiles/{s}/{z}/{x}/{y}.png',
|
||||
{
|
||||
maxZoom: 18,
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
||||
}
|
||||
).addTo(map);
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
maxZoom: 18,
|
||||
attribution: '© OpenStreetMap'
|
||||
}).addTo(map);
|
||||
|
||||
map.createPane('beamPane');
|
||||
map.getPane('beamPane').style.zIndex = 410;
|
||||
@@ -412,7 +385,6 @@ public final class MapHtmlResources {
|
||||
|
||||
notifyMapReady();
|
||||
notifyViewport();
|
||||
return true;
|
||||
}
|
||||
|
||||
function invalidateSize() {
|
||||
@@ -496,17 +468,13 @@ public final class MapHtmlResources {
|
||||
}
|
||||
|
||||
function setHome(lat, lon, zoom) {
|
||||
if (!init()) {
|
||||
return;
|
||||
}
|
||||
init();
|
||||
jsLog('setHome lat=' + lat + ' lon=' + lon + ' zoom=' + zoom);
|
||||
map.setView([lat, lon], zoom);
|
||||
}
|
||||
|
||||
function setStations(stationsJson) {
|
||||
if (!init()) {
|
||||
return;
|
||||
}
|
||||
init();
|
||||
|
||||
stationLayer.clearLayers();
|
||||
markersByCallsignRaw = {};
|
||||
@@ -535,9 +503,7 @@ public final class MapHtmlResources {
|
||||
}
|
||||
|
||||
function setBeam(beamJson) {
|
||||
if (!init()) {
|
||||
return;
|
||||
}
|
||||
init();
|
||||
beamLayer.clearLayers();
|
||||
|
||||
if (!beamJson || beamJson === 'null') {
|
||||
@@ -564,9 +530,7 @@ public final class MapHtmlResources {
|
||||
}
|
||||
|
||||
function setConnection(connectionJson) {
|
||||
if (!init()) {
|
||||
return;
|
||||
}
|
||||
init();
|
||||
connectionLayer.clearLayers();
|
||||
|
||||
if (!connectionJson || connectionJson === 'null') {
|
||||
@@ -591,9 +555,7 @@ public final class MapHtmlResources {
|
||||
}
|
||||
|
||||
function setProfileHoverPoint(point) {
|
||||
if (!init()) {
|
||||
return;
|
||||
}
|
||||
init();
|
||||
|
||||
if (profileHoverMarker) {
|
||||
map.removeLayer(profileHoverMarker);
|
||||
@@ -622,9 +584,7 @@ public final class MapHtmlResources {
|
||||
}
|
||||
|
||||
function setGrid(gridJson) {
|
||||
if (!init()) {
|
||||
return;
|
||||
}
|
||||
init();
|
||||
gridLayer.clearLayers();
|
||||
|
||||
const cells = JSON.parse(gridJson);
|
||||
@@ -710,6 +670,6 @@ public final class MapHtmlResources {
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
""".replace("__TILE_PROXY_PORT__", String.valueOf(tileProxyPort));
|
||||
""";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import javafx.collections.ListChangeListener;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.util.Duration;
|
||||
import kst4contest.controller.ChatController;
|
||||
import kst4contest.locatorUtils.Location;
|
||||
import kst4contest.model.ChatMember;
|
||||
import kst4contest.model.ChatPreferences;
|
||||
|
||||
@@ -14,13 +15,12 @@ import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
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.function.Consumer;
|
||||
|
||||
import kst4contest.model.Band;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Synchronizes the application state with the station map window.
|
||||
*
|
||||
@@ -32,7 +32,7 @@ import java.util.function.Predicate;
|
||||
*/
|
||||
public final class StationMapBridge {
|
||||
|
||||
|
||||
private final ExecutorService pathAnalysisExecutor = Executors.newSingleThreadExecutor(new PathAnalysisThreadFactory());
|
||||
private final AtomicLong pathAnalysisGeneration = new AtomicLong(0);
|
||||
|
||||
private final ChatController chatController;
|
||||
@@ -45,7 +45,8 @@ public final class StationMapBridge {
|
||||
|
||||
|
||||
private final MapCallsignRawSnapshotBuilder snapshotBuilder = new MapCallsignRawSnapshotBuilder();
|
||||
|
||||
// private final OfflineDemManager offlineDemManager = new OfflineDemManager();
|
||||
private final PathAnalysisService pathAnalysisService;
|
||||
|
||||
private String lastPathAnalysisRequestSignature = "";
|
||||
|
||||
@@ -63,7 +64,9 @@ public final class StationMapBridge {
|
||||
|
||||
this.refreshCoalescer.setOnFinished(event -> refreshNow());
|
||||
|
||||
|
||||
this.pathAnalysisService = new GeometryOnlyPathAnalysisService(
|
||||
new OpenMeteoTerrainProfileProvider()
|
||||
);
|
||||
}
|
||||
|
||||
public void install() {
|
||||
@@ -82,10 +85,6 @@ public final class StationMapBridge {
|
||||
(obs, oldValue, newValue) -> scheduleRefresh()
|
||||
);
|
||||
|
||||
chatController.getLst_chatMemberListFilterPredicates().addListener(
|
||||
(ListChangeListener<Predicate<ChatMember>>) change -> requestImmediateRefresh()
|
||||
);
|
||||
|
||||
requestImmediateRefresh();
|
||||
}
|
||||
|
||||
@@ -171,17 +170,6 @@ public final class StationMapBridge {
|
||||
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) {
|
||||
String normalizedOwnLocator6 = normalizeLocator6(ownLocator6);
|
||||
|
||||
@@ -214,28 +202,20 @@ public final class StationMapBridge {
|
||||
PathAnalysisResult.loading(normalizedOwnLocator6, normalizedTargetLocator6, targetCallsignRaw)
|
||||
);
|
||||
|
||||
ChatMember selectedMember = resolveBestChatMember(targetCallsignRaw);
|
||||
pathAnalysisExecutor.submit(() -> {
|
||||
PathAnalysisResult result = buildPathAnalysisResult(normalizedOwnLocator6, selectedSnapshot);
|
||||
|
||||
chatController.getReachabilityService().requestPathAnalysisForMap(
|
||||
selectedMember,
|
||||
selectedSnapshot,
|
||||
result -> {
|
||||
if (generation != pathAnalysisGeneration.get()) {
|
||||
return;
|
||||
}
|
||||
stationMapView.setPathAnalysisResult(result);
|
||||
Platform.runLater(() -> {
|
||||
if (generation != pathAnalysisGeneration.get()) {
|
||||
return;
|
||||
}
|
||||
);
|
||||
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() {
|
||||
pathAnalysisGeneration.incrementAndGet();
|
||||
pathAnalysisExecutor.shutdownNow();
|
||||
}
|
||||
|
||||
private void handleMapCallsignSelection(String callSignRaw) {
|
||||
@@ -306,6 +286,43 @@ public final class StationMapBridge {
|
||||
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) {
|
||||
if (selectedSnapshot == null) {
|
||||
@@ -359,5 +376,12 @@ public final class StationMapBridge {
|
||||
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.model.ChatPreferences;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -49,7 +48,6 @@ public final class StationMapView {
|
||||
private final Stage stage = new Stage();
|
||||
private final WebView webView = new WebView();
|
||||
private final WebEngine webEngine = webView.getEngine();
|
||||
private TileProxyServer tileProxyServer;
|
||||
|
||||
private Scene scene;
|
||||
private BorderPane rootPane;
|
||||
@@ -129,11 +127,6 @@ public final class StationMapView {
|
||||
|
||||
public StationMapView(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();
|
||||
initializeWebView();
|
||||
}
|
||||
@@ -634,8 +627,7 @@ public final class StationMapView {
|
||||
}
|
||||
});
|
||||
|
||||
int proxyPort = tileProxyServer != null ? tileProxyServer.getPort() : 0;
|
||||
webEngine.loadContent(MapHtmlResources.createStationMapHtml(proxyPort));
|
||||
webEngine.loadContent(MapHtmlResources.createStationMapHtml());
|
||||
}
|
||||
|
||||
private void requestMapInvalidateSize() {
|
||||
@@ -1101,7 +1093,6 @@ public final class StationMapView {
|
||||
webEngine.executeScript(script);
|
||||
} catch (Exception exception) {
|
||||
System.err.println("[StationMap] executeScript failed: " + exception.getMessage());
|
||||
exception.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1452,4 +1443,4 @@ public final class StationMapView {
|
||||
|
||||
return storedSize[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,4 +18,4 @@ module praktiKST {
|
||||
|
||||
|
||||
opens kst4contest.view.map to javafx.web;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-6
@@ -2365,9 +2365,4 @@ OZ1DLD/P;Bent;JO45SK;StringProperty [value: 144.285]; wkd true; wkd144 true; wkd
|
||||
OZ1FDH;Claus;JO55QX;StringProperty [value: null]; 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
|
||||
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
|
||||
OZ1HDF;Ken;JO55UN;StringProperty [value: null]; wkd true; wkd144 true; wkd432false; wkd1240false; wkd2300false; wkd3400false; wkd5600false; wkd10Gfalse ; 2: 144/432 MHz
|
||||
Reference in New Issue
Block a user