Files
kst4contest/src/main/java/kst4contest/view/map/MaidenheadGridUtils.java
T

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));
}
}