mirror of
https://github.com/praktimarc/kst4contest.git
synced 2026-09-13 12:45:32 +02:00
220 lines
8.3 KiB
Java
220 lines
8.3 KiB
Java
package kst4contest.view.map;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
|
|
/**
|
|
* Utility methods for generating visible Maidenhead grid rectangles.
|
|
*
|
|
* Supported levels:
|
|
* - 2 characters (fields)
|
|
* - 4 characters (squares)
|
|
* - 6 characters (subsquares)
|
|
*/
|
|
public final class MaidenheadGridUtils {
|
|
|
|
private static final double EPSILON = 1e-9;
|
|
|
|
private MaidenheadGridUtils() {
|
|
}
|
|
|
|
public enum GridPrecision {
|
|
FIELD_2(2, 20.0, 10.0),
|
|
SQUARE_4(4, 2.0, 1.0),
|
|
SUBSQUARE_6(6, 5.0 / 60.0, 2.5 / 60.0);
|
|
|
|
private final int locatorLength;
|
|
private final double cellWidthDeg;
|
|
private final double cellHeightDeg;
|
|
|
|
GridPrecision(int locatorLength, double cellWidthDeg, double cellHeightDeg) {
|
|
this.locatorLength = locatorLength;
|
|
this.cellWidthDeg = cellWidthDeg;
|
|
this.cellHeightDeg = cellHeightDeg;
|
|
}
|
|
|
|
public int locatorLength() {
|
|
return locatorLength;
|
|
}
|
|
|
|
public double cellWidthDeg() {
|
|
return cellWidthDeg;
|
|
}
|
|
|
|
public double cellHeightDeg() {
|
|
return cellHeightDeg;
|
|
}
|
|
}
|
|
|
|
public record GridCell(
|
|
String locatorLabel,
|
|
double southLat,
|
|
double westLon,
|
|
double northLat,
|
|
double eastLon,
|
|
int rowIndex,
|
|
int columnIndex
|
|
) {
|
|
}
|
|
|
|
public static GridPrecision precisionForZoom(int leafletZoom) {
|
|
if (leafletZoom <= 5) {
|
|
return GridPrecision.FIELD_2;
|
|
}
|
|
if (leafletZoom <= 7) {
|
|
return GridPrecision.SQUARE_4;
|
|
}
|
|
return GridPrecision.SUBSQUARE_6;
|
|
}
|
|
|
|
public static List<GridCell> buildVisibleCells(double southLat,
|
|
double westLon,
|
|
double northLat,
|
|
double eastLon,
|
|
int leafletZoom) {
|
|
return buildVisibleCells(southLat, westLon, northLat, eastLon, precisionForZoom(leafletZoom));
|
|
}
|
|
|
|
public static List<GridCell> buildVisibleCells(double southLat,
|
|
double westLon,
|
|
double northLat,
|
|
double eastLon,
|
|
GridPrecision precision) {
|
|
|
|
if (westLon > eastLon) {
|
|
// Anti-meridian handling is not needed for Europe in this project stage.
|
|
return List.of();
|
|
}
|
|
|
|
double clampedSouth = clamp(southLat, -90.0 + EPSILON, 90.0 - EPSILON);
|
|
double clampedNorth = clamp(northLat, -90.0 + EPSILON, 90.0 - EPSILON);
|
|
double clampedWest = clamp(westLon, -180.0 + EPSILON, 180.0 - EPSILON);
|
|
double clampedEast = clamp(eastLon, -180.0 + EPSILON, 180.0 - EPSILON);
|
|
|
|
return switch (precision) {
|
|
case FIELD_2 -> build2CharFields(clampedSouth, clampedWest, clampedNorth, clampedEast);
|
|
case SQUARE_4 -> build4CharSquares(clampedSouth, clampedWest, clampedNorth, clampedEast);
|
|
case SUBSQUARE_6 -> build6CharSubsquares(clampedSouth, clampedWest, clampedNorth, clampedEast);
|
|
};
|
|
}
|
|
|
|
private static List<GridCell> build2CharFields(double southLat, double westLon, double northLat, double eastLon) {
|
|
List<GridCell> cells = new ArrayList<>();
|
|
|
|
int lonStart = clampIndex((int) Math.floor((westLon + 180.0) / 20.0), 0, 17);
|
|
int lonEnd = clampIndex((int) Math.floor((eastLon + 180.0 - EPSILON) / 20.0), 0, 17);
|
|
|
|
int latStart = clampIndex((int) Math.floor((southLat + 90.0) / 10.0), 0, 17);
|
|
int latEnd = clampIndex((int) Math.floor((northLat + 90.0 - EPSILON) / 10.0), 0, 17);
|
|
|
|
for (int lonIndex = lonStart; lonIndex <= lonEnd; lonIndex++) {
|
|
for (int latIndex = latStart; latIndex <= latEnd; latIndex++) {
|
|
double west = -180.0 + lonIndex * 20.0;
|
|
double east = west + 20.0;
|
|
double south = -90.0 + latIndex * 10.0;
|
|
double north = south + 10.0;
|
|
|
|
String label = "" + (char) ('A' + lonIndex) + (char) ('A' + latIndex);
|
|
cells.add(new GridCell(label, south, west, north, east, latIndex, lonIndex));
|
|
}
|
|
}
|
|
|
|
return cells;
|
|
}
|
|
|
|
private static List<GridCell> build4CharSquares(double southLat, double westLon, double northLat, double eastLon) {
|
|
List<GridCell> cells = new ArrayList<>();
|
|
|
|
int lonStart = clampIndex((int) Math.floor((westLon + 180.0) / 2.0), 0, 179);
|
|
int lonEnd = clampIndex((int) Math.floor((eastLon + 180.0 - EPSILON) / 2.0), 0, 179);
|
|
|
|
int latStart = clampIndex((int) Math.floor((southLat + 90.0) / 1.0), 0, 179);
|
|
int latEnd = clampIndex((int) Math.floor((northLat + 90.0 - EPSILON) / 1.0), 0, 179);
|
|
|
|
for (int lonTotalIndex = lonStart; lonTotalIndex <= lonEnd; lonTotalIndex++) {
|
|
for (int latTotalIndex = latStart; latTotalIndex <= latEnd; latTotalIndex++) {
|
|
|
|
int lonFieldIndex = lonTotalIndex / 10;
|
|
int lonSquareIndex = lonTotalIndex % 10;
|
|
|
|
int latFieldIndex = latTotalIndex / 10;
|
|
int latSquareIndex = latTotalIndex % 10;
|
|
|
|
double west = -180.0 + lonTotalIndex * 2.0;
|
|
double east = west + 2.0;
|
|
double south = -90.0 + latTotalIndex;
|
|
double north = south + 1.0;
|
|
|
|
String label = String.format(
|
|
Locale.ROOT,
|
|
"%c%c%d%d",
|
|
(char) ('A' + lonFieldIndex),
|
|
(char) ('A' + latFieldIndex),
|
|
lonSquareIndex,
|
|
latSquareIndex
|
|
);
|
|
|
|
cells.add(new GridCell(label, south, west, north, east, latTotalIndex, lonTotalIndex));
|
|
}
|
|
}
|
|
|
|
return cells;
|
|
}
|
|
|
|
private static List<GridCell> build6CharSubsquares(double southLat, double westLon, double northLat, double eastLon) {
|
|
List<GridCell> cells = new ArrayList<>();
|
|
|
|
double lonStepDeg = 5.0 / 60.0;
|
|
double latStepDeg = 2.5 / 60.0;
|
|
|
|
int lonStart = clampIndex((int) Math.floor((westLon + 180.0) / lonStepDeg), 0, 4319);
|
|
int lonEnd = clampIndex((int) Math.floor((eastLon + 180.0 - EPSILON) / lonStepDeg), 0, 4319);
|
|
|
|
int latStart = clampIndex((int) Math.floor((southLat + 90.0) / latStepDeg), 0, 4319);
|
|
int latEnd = clampIndex((int) Math.floor((northLat + 90.0 - EPSILON) / latStepDeg), 0, 4319);
|
|
|
|
for (int lonTotalIndex = lonStart; lonTotalIndex <= lonEnd; lonTotalIndex++) {
|
|
for (int latTotalIndex = latStart; latTotalIndex <= latEnd; latTotalIndex++) {
|
|
|
|
int lonFieldIndex = lonTotalIndex / 240;
|
|
int lonWithinField = lonTotalIndex % 240;
|
|
int lonSquareIndex = lonWithinField / 24;
|
|
int lonSubsquareIndex = lonWithinField % 24;
|
|
|
|
int latFieldIndex = latTotalIndex / 240;
|
|
int latWithinField = latTotalIndex % 240;
|
|
int latSquareIndex = latWithinField / 24;
|
|
int latSubsquareIndex = latWithinField % 24;
|
|
|
|
double west = -180.0 + lonTotalIndex * lonStepDeg;
|
|
double east = west + lonStepDeg;
|
|
double south = -90.0 + latTotalIndex * latStepDeg;
|
|
double north = south + latStepDeg;
|
|
|
|
String label = String.format(
|
|
Locale.ROOT,
|
|
"%c%c%d%d%c%c",
|
|
(char) ('A' + lonFieldIndex),
|
|
(char) ('A' + latFieldIndex),
|
|
lonSquareIndex,
|
|
latSquareIndex,
|
|
(char) ('a' + lonSubsquareIndex),
|
|
(char) ('a' + latSubsquareIndex)
|
|
);
|
|
|
|
cells.add(new GridCell(label, south, west, north, east, latTotalIndex, lonTotalIndex));
|
|
}
|
|
}
|
|
|
|
return cells;
|
|
}
|
|
|
|
private static int clampIndex(int value, int min, int max) {
|
|
return Math.max(min, Math.min(max, value));
|
|
}
|
|
|
|
private static double clamp(double value, double min, double max) {
|
|
return Math.max(min, Math.min(max, value));
|
|
}
|
|
} |