Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closest shoreline distance #182

Merged
merged 16 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
233 changes: 218 additions & 15 deletions GlobalQuakeCore/src/main/java/globalquake/core/regions/Regions.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.fasterxml.jackson.databind.ObjectMapper;
import globalquake.utils.GeoUtils;
import globalquake.utils.LookupTableIO;
import org.geojson.*;
import org.json.JSONObject;
import org.tinylog.Logger;
Expand All @@ -14,7 +15,11 @@
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

public class Regions {
Expand Down Expand Up @@ -51,6 +56,8 @@ public class Regions {


private static final ArrayList<Region> regionSearchHD = new ArrayList<>();
private static HashMap<String, Double> shorelineLookup;


public static void init() throws IOException {
parseGeoJson("polygons/countriesMD.json", raw_polygonsMD, regionsMD, NONE);
Expand All @@ -64,11 +71,34 @@ public static void init() throws IOException {
parseGeoJson("polygons_converted/new-zealand-districts.geojson", raw_polygonsNZ, regionsNZ, NONE);
parseGeoJson("polygons_converted/hawaii-countries.geojson", raw_polygonsHW, regionsHW, NONE);
parseGeoJson("polygons_converted/italy_provinces.geojson", raw_polygonsIT, regionsIT, NONE);
parseGeoJson("polygons_converted/italy_provinces.geojson", raw_polygonsIT, regionsIT, NONE);
parseGeoJson("polygons_converted/region_dataset.geojson", null, regionSearchHD, NONE);

for(ArrayList<Region> list : List.of(regionsUS, regionsAK, regionsJP, regionsNZ, regionsHW, regionsIT)){
regionSearchHD.addAll(list);
}

shorelineLookup = LookupTableIO.importLookupTableFromFile();

if(shorelineLookup == null){
System.err.println("No lookup table found! Generating...");
double start = System.currentTimeMillis();
boolean exportResult = LookupTableIO.exportLookupTableToFile();
System.out.println("Generating took: " + (System.currentTimeMillis() - start)/1000 + "s");

if (exportResult) {
System.out.println("Lookup table successfully generated! Loading " + shorelineLookup.size() + " items.");
shorelineLookup = LookupTableIO.importLookupTableFromFile();
} else {
System.err.println("Failed to export lookup table!");
}
}

}


@SuppressWarnings("EmptyMethod")
public static synchronized void awaitDownload() {
}

@Deprecated
public static synchronized String downloadRegion(double lat, double lon) {
if (!enabled) {
Expand Down Expand Up @@ -96,6 +126,28 @@ public static synchronized String downloadRegion(double lat, double lon) {
}
}

public static double getOceanDistance(double lat, double lon) {
double closestDistance = Double.MAX_VALUE;
Point2D.Double point = new Point2D.Double(lon, lat);
for (Region reg : regionsMD) {
for(Path2D.Double path : reg.paths()){
if(path.contains(point)){
return 0.0;
}
}
for (Polygon polygon : reg.raws()) {
for (LngLatAlt pos : polygon.getCoordinates().get(0)) {
double dist = GeoUtils.greatCircleDistance(pos.getLatitude(), pos.getLongitude(), lat, lon);
if (dist < closestDistance) {
closestDistance = dist;
}
}
}
}

return closestDistance;
}

public static boolean isOcean(double lat, double lng, boolean uhd) {
return isOcean(lat, lng, uhd ? regionsUHD : regionsHD);
}
Expand Down Expand Up @@ -137,12 +189,17 @@ public static String getName(double lat, double lon, List<Region> regions){

public static String getExtendedName(double lat, double lon){
String localName = getName(lat, lon, regionSearchHD);
String globalName = getName(lat, lon, regionsUHD);

if(localName != null && globalName != null) {
return "%s, %s".formatted(localName, globalName);
}

if(localName != null){
return localName;
}

return getName(lat, lon, regionsUHD);
return globalName;
}

public static String getRegion(double lat, double lon) {
Expand Down Expand Up @@ -188,16 +245,166 @@ public static String getRegion(double lat, double lon) {
return name;
}

public static double getShorelineDistance(double lat, double lon) {
String extendedName = getExtendedName(lat, lon);
if(extendedName != null){
return 0;
}

double closestDistance = Double.MAX_VALUE;
for (Region reg : regionsMD) {
for (Polygon polygon : reg.raws()) {
for (LngLatAlt pos : polygon.getCoordinates().get(0)) {
double dist = GeoUtils.greatCircleDistance(pos.getLatitude(), pos.getLongitude(), lat, lon);
if (dist < closestDistance) {
closestDistance = dist;
}
}
}
}


return closestDistance;
}

public static HashMap<String, Double> generateLookupTable(double minLat, double maxLat, double minLon, double maxLon) {
final double STEP_LAT = 0.5;
final double STEP_LON = 0.5;
HashMap<String, Double> lookupTable = new HashMap<>();

for (double lat = minLat; lat < maxLat; lat += STEP_LAT) {
for (double lon = minLon; lon < maxLon; lon += STEP_LON) {
double distance = getShorelineDistance(lat, lon);

if (distance != 0) {
lookupTable.put(String.format("%f,%f", lat, lon), distance);
}
}
}

return lookupTable;
}

public static List<HashMap<String, Double>> generateLookupTablesInParallel() {
final double MIN_LAT = -90;
final double MAX_LAT = 90;
final double MIN_LON = -180;
final double MAX_LON = 180;

ExecutorService executorService = Executors.newFixedThreadPool(4);
List<HashMap<String, Double>> allLookupTables = new ArrayList<>();

for (double latStart = MIN_LAT; latStart < MAX_LAT; latStart += (MAX_LAT - MIN_LAT) / 2) {
for (double lonStart = MIN_LON; lonStart < MAX_LON; lonStart += (MAX_LON - MIN_LON) / 2) {
double latEnd = latStart + (MAX_LAT - MIN_LAT) / 2;
double lonEnd = lonStart + (MAX_LON - MIN_LON) / 2;

double finalLatStart = latStart;
double finalLonStart = lonStart;
executorService.submit(() -> {
HashMap<String, Double> lookupTable = generateLookupTable(finalLatStart, latEnd, finalLonStart, lonEnd);
synchronized (allLookupTables) {
allLookupTables.add(lookupTable);
}
});
}
}

executorService.shutdown();
try {
boolean ignored = executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
executorService.shutdownNow();
Thread.currentThread().interrupt();
}

return allLookupTables;
}

public static boolean isValidPoint(double x, double y) {
return x >= -90 && x <= 90 && y >= -180 && y <= 180;
}

public static double interpolate(
double lat, double lon,
HashMap<String, Double> lookupTable
) {
if(lookupTable.containsKey(String.format("%.6f,%.6f", lat, lon))){
return lookupTable.get(String.format("%.6f,%.6f", lat, lon));
}

double tmp = lat - Math.floor(lat);
double x0, x1, y0, y1;

if(tmp < 0.5) {
x0 = (int) Math.floor(lat);
x1 = x0 + 0.5;
} else {
x0 = (int) Math.floor(lat) + 0.5;
x1 = x0 + 0.5;
}

tmp = lon - Math.floor(lon);

if(tmp < 0.5) {
y0 = (int) Math.floor(lon);
y1 = y0 + 0.5;
} else {
y0 = (int) Math.floor(lon) + 0.5;
y1 = y0 + 0.5;
}

if (!isValidPoint(x0, y0) ||
!isValidPoint(x1, y1)) {
return -1;
}

String first = String.format("%.6f,%.6f", x0, y0);
String second = String.format("%.6f,%.6f", x0, y1);
String third = String.format("%.6f,%.6f", x1, y0);
String fourth = String.format("%.6f,%.6f", x1, y1);

double f00 = lookupTable.getOrDefault(first, (double) 0);
double f01 = lookupTable.getOrDefault(second, (double) 0);
double f10 = lookupTable.getOrDefault(third, (double) 0);
double f11 = lookupTable.getOrDefault(fourth, (double) 0);

double r1 = ((x1 - lat)/(x1 - x0) * f00) + ((lat - x0)/(x1 - x0) * f10);
double r2 = ((x1 - lat)/(x1 - x0) * f01) + ((lat - x0)/(x1 - x0) * f11);

double result = ((y1 - lon)/(y1 - y0) * r1) + ((lon - y0)/(y1 - y0) * r2);

return Double.isNaN(result) ? 0 : result;
}


public static void main(String[] args) throws Exception{
System.out.println("INIT");
init();

System.out.println("FIND");
long a = System.currentTimeMillis();
for(int i = 0; i < 500; i++){
getRegion(58.79,-150.80);

double lat = 39.59763558387561,
lon = -9.14040362258988;

assert shorelineLookup != null;
double interpolation = interpolate(lat, lon, shorelineLookup);

if (Double.isNaN(interpolation) || interpolation == -1){
System.err.println("Values couldn't be interpolated, using legacy method...");
double shorelineDistance = getShorelineDistance(lat, lon);
shorelineLookup.putIfAbsent(String.format("%.6f,%.6f", lat, lon), shorelineDistance);
} else {
System.out.println("Interpolated distance to the closest shoreline is: " + interpolation);
shorelineLookup.putIfAbsent(String.format("%.6f,%.6f", lat, lon), interpolation);
}

boolean exportResult = LookupTableIO.exportLookupTableToFile(shorelineLookup);
if(exportResult){
System.out.println("Lookup Table was successfully exported.");
} else {
System.err.println("Lookup Table export failed");
}
System.out.println(getRegion(33.78,135.74));
System.err.println(System.currentTimeMillis()-a);
}

public static void parseGeoJson(String path, ArrayList<Polygon> raw, ArrayList<Region> regions, List<String> remove) throws IOException {
Expand Down Expand Up @@ -228,25 +435,21 @@ public static void parseGeoJson(String path, ArrayList<Polygon> raw, ArrayList<R
raws.add(pol);
paths.add(toPath(pol));

if(raw != null) {
raw.add(pol);
}
raw.add(pol);
regions.add(new Region(name, paths, paths.stream().map(Path2D.Double::getBounds2D).collect(Collectors.toList()), raws));
} else if (o instanceof MultiPolygon mp) {
createRegion(regions, mp, name);

List<List<List<LngLatAlt>>> polygons = mp.getCoordinates();
for (List<List<LngLatAlt>> polygon : polygons) {
org.geojson.Polygon pol = new org.geojson.Polygon(polygon.get(0));
if(raw != null) {
raw.add(pol);
}
raw.add(pol);
}
}
}
}

private static final String[] NAME_NAMES = {"name_long", "name", "NAME_2", "NAME_1", "NAME", "name_l"};
private static final String[] NAME_NAMES = {"name_long", "name", "NAME_2", "NAME_1", "NAME"};

private static String fetchName(Feature f) {
String name;
Expand Down
60 changes: 60 additions & 0 deletions GlobalQuakeCore/src/main/java/globalquake/utils/LookupTableIO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package globalquake.utils;

import java.io.*;
import java.net.URL;
import java.util.HashMap;
import java.util.List;

import globalquake.core.regions.Regions;

public class LookupTableIO {
public static boolean exportLookupTableToFile() {
List<HashMap<String, Double>> lookupTables = Regions.generateLookupTablesInParallel();
HashMap<String, Double> lookupTable = new HashMap<>();
for(HashMap<String, Double> singleLT : lookupTables) {
lookupTable.putAll(singleLT);
}

return performExport(lookupTable);
}

public static boolean exportLookupTableToFile(HashMap<String, Double> lookupTable) throws IOException {
return performExport(lookupTable);
}

private static boolean performExport(HashMap<String, Double> lookupTable) {
String path = "lookup/lookupTable.dat";
URL resource = ClassLoader.getSystemClassLoader().getResource(path);

try{
ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("lookupTable.dat"));
output.writeObject(lookupTable);
output.close();
} catch (Exception e){
System.err.println("Unable to save a lookup table! " + e);
return false;
}

return true;
}

public static HashMap<String, Double> importLookupTableFromFile() throws IOException {
String path = "lookup/lookupTable.dat";
URL resource = ClassLoader.getSystemClassLoader().getResource(path);

if (resource == null) {
System.err.printf("Unable to load a lookup table: %s", path);
return null;
}

HashMap<String, Double> lookupTable;
try{
ObjectInput input = new ObjectInputStream(resource.openStream());
lookupTable = (HashMap<String, Double>) input.readObject();
} catch (IOException | ClassNotFoundException e) {
throw new IOException("Unable to load stream of a lookup table! ", e);
}

return lookupTable;
}
}
Binary file not shown.
Loading