diff --git a/build.gradle.kts b/build.gradle.kts index 760775d..ee90e56 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,6 +11,7 @@ dependencies { implementation(libs.alpslib.utils) { exclude(group = "com.github.cryptomorin", module = "XSeries") } + implementation(libs.alpslib.geo) implementation(libs.com.alpsbte.canvas) implementation(libs.com.github.cryptomorin.xseries) implementation(libs.net.wesjd.anvilgui) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0ccedcb..e730b6c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,6 +22,7 @@ fawe-bom = "1.56" # Ref: https://github.com/IntellectualSites/bom bstats = "3.+" # https://central.sonatype.com/artifact/org.bstats/bstats-bukkit bluemap-api = "2.+" # Ref: https://github.com/BlueMap-Minecraft/BlueMapAPI net-buildtheearth-projection = "1.+" # Ref: https://github.com/BuildTheEarth/projection +alpslib-geo = "1.0.0" # https://mvn.alps-bte.com/service/rest/repository/browse/alps-bte/com/alpsbte/alpslib/alpslib-geo/ [libraries] com-alpsbte-alpslib-alpslib-libpsterra = { module = "com.alpsbte.alpslib:alpslib-libpsterra", version.ref = "com-alpsbte-alpslib-alpslib-libpsterra" } @@ -41,6 +42,7 @@ fawe-bom = { module = "com.intellectualsites.bom:bom-newest", version.ref = "faw bstats-bukkit = { module = "org.bstats:bstats-bukkit", version.ref = "bstats" } bluemap-api = { module = "de.bluecolored:bluemap-api", version.ref = "bluemap-api" } net-buildtheearth-projection = { module = "net.buildtheearth:projection", version.ref = "net-buildtheearth-projection"} +alpslib-geo = { module = "com.alpsbte.alpslib:alpslib-geo", version.ref = "alpslib-geo" } [plugins] lombok = { id = "io.freefair.lombok", version.ref = "io-freefair-lombok" } diff --git a/settings.gradle.kts b/settings.gradle.kts index c6b80f7..ee0ae27 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -4,7 +4,7 @@ dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { mavenCentral() - //mavenLocal() // NEVER use in Production/Commits! + // mavenLocal() // NEVER use in Production/Commits! maven { url = uri("https://repo.papermc.io/repository/maven-public/") } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/NavUtils.java b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/NavUtils.java index 04030a1..2a0c905 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/NavUtils.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/NavUtils.java @@ -24,6 +24,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Objects; + @UtilityClass public class NavUtils { public static void sendPlayerToConnectedServer(Player player, String server) { @@ -173,4 +175,18 @@ public static Location getLocationFromCoordinatesYawPitch(GeographicalCoordinate public static Location getLocationFromCoordinates(GeographicalCoordinate coordinate) { return getLocationFromCoordinatesYawPitch(coordinate, 0, 0); } + + /** + * Returns the CCA2 code of the country of the given country name. + */ + public static String getCCA2FromCountryName(String countryName, Player clickPlayer) { + var region = Objects.requireNonNull(NetworkModule.getInstance().getBuildTeam()).getRegions().stream().filter(regionF -> regionF.getName().equals(countryName)).findFirst(); + if (region.isPresent()) { + return region.get().getCountryCodeCca2(); + } else { + clickPlayer.sendMessage(ChatHelper.getErrorString("Could not find the country of the location! Please report that")); + return ""; + } + + } } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/NavigationModule.java b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/NavigationModule.java index 00bd28a..72e78ed 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/NavigationModule.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/NavigationModule.java @@ -1,5 +1,7 @@ package net.buildtheearth.buildteamtools.modules.navigation; +import com.alpsbte.alpslib.geo.rgc.RgcHandler; +import com.alpsbte.alpslib.utils.ChatHelper; import lombok.Getter; import net.buildtheearth.buildteamtools.BuildTeamTools; import net.buildtheearth.buildteamtools.modules.Module; @@ -21,6 +23,15 @@ import net.buildtheearth.buildteamtools.utils.io.ConfigPaths; import net.buildtheearth.buildteamtools.utils.io.ConfigUtil; import org.bukkit.Bukkit; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.FileOutputStream; +import java.net.URI; +import java.net.URL; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; /** * Manages all things related to universal tpll @@ -36,7 +47,9 @@ public class NavigationModule extends Module { private TpllComponent tpllComponent; @Getter private BluemapComponent bluemapComponent; - + @Getter + @Nullable + private RgcHandler rgcHandler = null; private static NavigationModule instance = null; @@ -60,9 +73,41 @@ public void enable() { navigatorComponent = new NavigatorComponent(); tpllComponent = new TpllComponent(); + var navConfig = BuildTeamTools.getInstance().getConfig(ConfigUtil.NAVIGATION); + + if (navConfig.getBoolean(ConfigPaths.Navigation.RGC_LOCAL_DB_ENABLED, false)) { + File rgcFile = BuildTeamTools.getInstance().getDataPath().resolve("modules/navigation").resolve(navConfig.getString(ConfigPaths.Navigation.RGC_LOCAL_DB_PATH, "bs.file")).toFile(); + ChatHelper.logDebug("Reverse Geocode local database support is enabled. Checking for local database file at: %s", rgcFile.getAbsolutePath()); + if (rgcFile.exists()) { + rgcHandler = new RgcHandler(rgcFile, BuildTeamTools.getInstance().getSLF4JLogger(), false); + } else { + BuildTeamTools.getInstance().getComponentLogger().info("Reverse Geocode local database is enabled but the file does not exist at the specified path, installing it from the configured url."); + Bukkit.getScheduler().runTaskAsynchronously(BuildTeamTools.getInstance(), () -> { + try { + if (!rgcFile.getParentFile().mkdirs()) { + BuildTeamTools.getInstance().getComponentLogger().warn("Failed to create parent directories for Reverse Geocode local database file. Make sure the plugin has the necessary permissions to create directories and files in the plugin data folder."); + } + URL url = URI.create(navConfig.getString(ConfigPaths.Navigation.RGC_LOCAL_DB_UPDATE_URL, "")).toURL(); + try (ReadableByteChannel readableByteChannel = Channels.newChannel(url.openStream())) { + try (FileOutputStream fileOutputStream = new FileOutputStream(rgcFile)) { + FileChannel fileChannel = fileOutputStream.getChannel(); + fileChannel.transferFrom(readableByteChannel, 0, Long.MAX_VALUE); + } + } + Bukkit.getScheduler().runTask(BuildTeamTools.getInstance(), () -> { + rgcHandler = new RgcHandler(rgcFile, BuildTeamTools.getInstance().getSLF4JLogger(), false); + BuildTeamTools.getInstance().getComponentLogger().info("Successfully downloaded Reverse Geocode local database and enabled local database support for Reverse Geocoding."); + }); + } catch (Exception e) { + BuildTeamTools.getInstance().getComponentLogger().error("Failed to download Reverse Geocode local database from the configured url, disabling local database support for Reverse Geocoding.", e); + navConfig.set(ConfigPaths.Navigation.RGC_LOCAL_DB_ENABLED, false); + } + }); + } + } + // Check if BlueMap plugin is enabled and config allows BlueMap integration - boolean bluemapConfigEnabled = BuildTeamTools.getInstance().getConfig(ConfigUtil.NAVIGATION) - .getBoolean(ConfigPaths.Navigation.BLUEMAP_ENABLED, true); + boolean bluemapConfigEnabled = navConfig.getBoolean(ConfigPaths.Navigation.BLUEMAP_ENABLED, true); if (Bukkit.getPluginManager().isPluginEnabled("BlueMap") && bluemapConfigEnabled) { bluemapComponent = new BluemapComponent(); diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/WarpsComponent.java b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/WarpsComponent.java index fb88359..466e1bd 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/WarpsComponent.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/WarpsComponent.java @@ -168,6 +168,8 @@ public static void createWarp(@NonNull Player creator, WarpGroup group) { String regionName = result[0]; String countryCodeCCA2 = result[1].toUpperCase(); + if (countryCodeCCA2.isEmpty()) countryCodeCCA2 = NavUtils.getCCA2FromCountryName(regionName, creator); + //Check if the team owns this region/country boolean ownsRegion = NetworkModule.getInstance().ownsRegion(regionName, countryCodeCCA2); diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/menu/WarpEditMenu.java b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/menu/WarpEditMenu.java index 0230cd5..cc50cee 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/menu/WarpEditMenu.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/menu/WarpEditMenu.java @@ -6,6 +6,7 @@ import net.buildtheearth.OutOfProjectionBoundsException; import net.buildtheearth.Projection; import net.buildtheearth.buildteamtools.BuildTeamTools; +import net.buildtheearth.buildteamtools.modules.navigation.NavUtils; import net.buildtheearth.buildteamtools.modules.navigation.components.warps.model.Warp; import net.buildtheearth.buildteamtools.modules.network.NetworkModule; import net.buildtheearth.buildteamtools.modules.network.api.OpenStreetMapAPI; @@ -144,6 +145,9 @@ protected void setItemClickEventsAsync() { String regionName = result[0]; String countryCodeCCA2 = result[1].toUpperCase(); + if (countryCodeCCA2.isEmpty()) + countryCodeCCA2 = NavUtils.getCCA2FromCountryName(regionName, clickPlayer); + //Check if the team owns this region/country boolean ownsRegion = NetworkModule.getInstance().ownsRegion(regionName, countryCodeCCA2); diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/OpenStreetMapAPI.java b/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/OpenStreetMapAPI.java index 92d7993..c48dbeb 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/OpenStreetMapAPI.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/OpenStreetMapAPI.java @@ -1,11 +1,16 @@ package net.buildtheearth.buildteamtools.modules.network.api; +import com.alpsbte.alpslib.geo.AdminLevel; import com.alpsbte.alpslib.utils.ChatHelper; +import net.buildtheearth.buildteamtools.BuildTeamTools; +import net.buildtheearth.buildteamtools.modules.navigation.NavigationModule; +import org.bukkit.Bukkit; import org.jetbrains.annotations.NotNull; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import java.io.IOException; +import java.util.Objects; import java.util.concurrent.CompletableFuture; public class OpenStreetMapAPI extends API { @@ -16,6 +21,37 @@ public class OpenStreetMapAPI extends API { */ public static @NotNull CompletableFuture getCountryFromLocationAsync(double @NotNull [] coordinates) { CompletableFuture future = new CompletableFuture<>(); + + if (NavigationModule.getInstance().isEnabled() && NavigationModule.getInstance().getRgcHandler() != null) { + ChatHelper.logDebug("Using custom file API to get country from location: %s, %s", coordinates[0], coordinates[1]); + + // If we are not on main thread, schedule lookup on main thread and complete the outer future there + if (!Bukkit.isPrimaryThread()) { + ChatHelper.logDebug("Not on main thread: scheduling RGC lookup on main thread..."); + Bukkit.getScheduler().runTask(BuildTeamTools.getInstance(), () -> { + try { + var rgcGeoLocation = NavigationModule.getInstance().getRgcHandler() + .locationFromCoordinates((float) coordinates[0], (float) coordinates[1]); + ChatHelper.logDebug("RGC lookup successful: %s", rgcGeoLocation); + future.complete(new String[]{rgcGeoLocation.get(AdminLevel.COUNTRY), ""}); + } catch (Exception ex) { + future.completeExceptionally(ex); + } + }); + return future; + } + + // We're on the main thread already — run synchronously + try { + var location = Objects.requireNonNull(NavigationModule.getInstance().getRgcHandler()).locationFromCoordinates((float) coordinates[0], (float) coordinates[1]); + ChatHelper.logDebug("RGC lookup successful: %s", location); + future.complete(new String[]{location.get(AdminLevel.COUNTRY), ""}); + } catch (Exception ex) { + future.completeExceptionally(ex); + } + return future; + } + String url = "https://photon.komoot.io/reverse?lat=" + coordinates[0] + "&lon=" + coordinates[1] + "&lang=en"; ChatHelper.logDebug("Requesting country from location: %s", url); diff --git a/src/main/java/net/buildtheearth/buildteamtools/utils/io/ConfigPaths.java b/src/main/java/net/buildtheearth/buildteamtools/utils/io/ConfigPaths.java index 671d022..51dadc8 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/utils/io/ConfigPaths.java +++ b/src/main/java/net/buildtheearth/buildteamtools/utils/io/ConfigPaths.java @@ -45,6 +45,12 @@ public static class Navigation { // BlueMap Integration private static final String BLUEMAP = "bluemap."; public static final String BLUEMAP_ENABLED = BLUEMAP + "enabled"; + + // Reverse Geocode + private static final String RGC_LOCAL_DB = "reverse-geocode.local-database."; + public static final String RGC_LOCAL_DB_ENABLED = RGC_LOCAL_DB + "enabled"; + public static final String RGC_LOCAL_DB_UPDATE_URL = RGC_LOCAL_DB + "url"; + public static final String RGC_LOCAL_DB_PATH = RGC_LOCAL_DB + "path"; } public static class PlotSystem { diff --git a/src/main/resources/modules/navigation/config.yml b/src/main/resources/modules/navigation/config.yml index 4b8d116..f32043d 100644 --- a/src/main/resources/modules/navigation/config.yml +++ b/src/main/resources/modules/navigation/config.yml @@ -52,5 +52,16 @@ bluemap: # Enables or disables the BlueMap integration for displaying warps [true|false] enabled: true +reverse-geocode: + local-database: + # Whether to use the local database for reverse geocoding or not. If false, the online Photon/Komoot API will be used. + # Currently, we use only https://photon.komoot.io/ for online reverse geocoding. + enabled: true + # The URL to the local database. More infos: https://github.com/kno10/reversegeocode/blob/master/data/README.md + # The file is automatically downloaded once if it does not exist. + url: "https://data.ub.uni-muenchen.de/61/8/osm-20151130-0.001-2.bin" + # The relative path to the local database. + path: "reversegeocode/osm-20151130-0.001-2.bin" + # NOTE: Do not change -config-version: 1.6 \ No newline at end of file +config-version: 1.7 \ No newline at end of file