From 77c9d34d64f0adfc7a1e93762d944a71ed820bc6 Mon Sep 17 00:00:00 2001 From: Marc Froehlich Date: Fri, 10 Jul 2026 00:29:43 +0200 Subject: [PATCH] Introduced clustering in the map --- .../view/map/MapHtmlResources.java | 688 ++++++++++++++++-- .../kst4contest/view/map/StationMapView.java | 21 +- src/main/resources/web/leaflet/leaflet.css | 2 + 3 files changed, 627 insertions(+), 84 deletions(-) diff --git a/src/main/java/kst4contest/view/map/MapHtmlResources.java b/src/main/java/kst4contest/view/map/MapHtmlResources.java index ab6223b..490208a 100644 --- a/src/main/java/kst4contest/view/map/MapHtmlResources.java +++ b/src/main/java/kst4contest/view/map/MapHtmlResources.java @@ -13,8 +13,12 @@ import java.nio.charset.StandardCharsets; * - inspectPoint(x,y) returns what is under the cursor * - zoomIn()/zoomOut() are callable from Java * - grid / beam / connection use non-interactive panes - * - JavaScript logs are forwarded to Java through javaMapBridge + * - JavaScript errors are forwarded to Java through javaMapBridge * - setTheme(light|dark) aligns the map with the JavaFX application theme + * + * Important: + * This version intentionally uses integer Leaflet zoom levels again. + * Fractional zoom in JavaFX WebView caused unreliable marker positioning. */ public final class MapHtmlResources { @@ -184,6 +188,111 @@ public final class MapHtmlResources { font-weight: 800; } + .station-cluster-wrapper { + background: transparent; + border: none; + box-shadow: none; + } + + .station-cluster-root { + position: relative; + width: 1px; + height: 1px; + pointer-events: auto; + cursor: pointer; + user-select: none; + } + + /* + * Cluster design aligned with normal KST4Contest station markers: + * - dark center like station-dot + * - blue border for normal clustered stations + * - yellow border when the cluster contains worked stations + * - orange hover border similar to selected station state + */ + .station-cluster-bubble { + position: absolute; + left: -16px; + top: -16px; + min-width: 32px; + height: 32px; + padding: 0 7px; + border-radius: 18px; + background: #1d1d1d; + color: #f4f7fa; + border: 2px solid #4da6ff; + box-shadow: + 0 0 0 1px rgba(0, 0, 0, 0.35), + 0 2px 7px rgba(0, 0, 0, 0.42); + box-sizing: border-box; + font-size: 13px; + font-weight: 800; + line-height: 28px; + text-align: center; + white-space: nowrap; + } + + .station-cluster-bubble.medium { + left: -18px; + top: -18px; + min-width: 36px; + height: 36px; + border-radius: 20px; + font-size: 14px; + line-height: 32px; + } + + .station-cluster-bubble.large { + left: -21px; + top: -21px; + min-width: 42px; + height: 42px; + border-radius: 23px; + font-size: 15px; + line-height: 38px; + } + + .station-cluster-bubble.worked { + border-color: #ffd24d; + } + + .station-cluster-bubble.warning { + border-color: #00ff66; + color: #00ff66; + } + + .station-cluster-root:hover .station-cluster-bubble { + border-color: #ff9900; + box-shadow: + 0 0 0 2px rgba(255, 153, 0, 0.28), + 0 2px 9px rgba(0, 0, 0, 0.48); + } + + body.kst-theme-dark .station-cluster-bubble { + background: #202428; + color: #f1f3f5; + border-color: #4da6ff; + box-shadow: + 0 0 0 1px rgba(255, 255, 255, 0.12), + 0 2px 8px rgba(0, 0, 0, 0.58); + } + + body.kst-theme-dark .station-cluster-bubble.worked { + border-color: #ffd24d; + } + + body.kst-theme-dark .station-cluster-bubble.warning { + border-color: #00ff66; + color: #00ff66; + } + + body.kst-theme-dark .station-cluster-root:hover .station-cluster-bubble { + border-color: #ff9900; + box-shadow: + 0 0 0 2px rgba(255, 153, 0, 0.32), + 0 2px 10px rgba(0, 0, 0, 0.65); + } + .maidenhead-grid-label-wrapper { background: transparent; border: none; @@ -225,9 +334,6 @@ public final class MapHtmlResources { """.replace("__TILE_PROXY_PORT__", String.valueOf(tileProxyPort)); } -} +} \ No newline at end of file diff --git a/src/main/java/kst4contest/view/map/StationMapView.java b/src/main/java/kst4contest/view/map/StationMapView.java index 8bbc367..f42d37a 100644 --- a/src/main/java/kst4contest/view/map/StationMapView.java +++ b/src/main/java/kst4contest/view/map/StationMapView.java @@ -41,6 +41,14 @@ import javafx.scene.layout.ColumnConstraints; */ public final class StationMapView { + /** + * Enables verbose map debug output. + * + * Keep this false for normal operation because scroll and viewport logs are very + * noisy during map interaction. + */ + private static final boolean MAP_DEBUG_LOGGING = false; + private final PathProfileChart detailPathProfileChart = new PathProfileChart(); private final Label detailPathModeValue = new Label("-"); @@ -287,12 +295,17 @@ public final class StationMapView { webView.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> handleWebViewClick(event)); webView.addEventHandler(ScrollEvent.SCROLL, event -> { - System.out.println("[StationMap FX] SCROLL x=" + (int) event.getX() - + " y=" + (int) event.getY() - + " deltaY=" + event.getDeltaY()); + + if (MAP_DEBUG_LOGGING) { + System.out.println("[StationMap FX] SCROLL x=" + (int) event.getX() + + " y=" + (int) event.getY() + + " deltaY=" + event.getDeltaY()); + } InteractiveTarget target = inspectInteractiveTarget(event.getX(), event.getY()); - System.out.println("[StationMap FX] inspect scroll -> " + target); + if (MAP_DEBUG_LOGGING) { + System.out.println("[StationMap FX] inspect scroll -> " + target); + } if (event.getDeltaY() > 0) { executeMapScriptSafely("window.kstMapApi.zoomIn();"); diff --git a/src/main/resources/web/leaflet/leaflet.css b/src/main/resources/web/leaflet/leaflet.css index ba1e940..1de4a18 100644 --- a/src/main/resources/web/leaflet/leaflet.css +++ b/src/main/resources/web/leaflet/leaflet.css @@ -650,6 +650,8 @@ svg.leaflet-image-layer.leaflet-interactive path { border-right-color: #fff; } + + /* Printing */ @media print {