attention to detail
I went thru and corrected numerus inspection warnings. I'd say most of it is correcting typos. I also removed a bunch of unused variables and methods (and even a cupple of files), adjusted varius stylings, improved a few docstrings, made local variables where one quantity was getting calculated multiple times, and cetera. the spiciest thing was that I refatord the part where it saves a screenshot of the graphs in the MapAnalyzer; previusly it was bizzy-waiting for the GUI thread to finish taking the screenshot, which was probably fine, but now it uses FutureTask.get(), which is probably faster and definitely cleaner. I also deleted the code for the two-point equalized projection because it was bad and didn't work very well (to be precise it wasn't in use after I removed it from the projection list a few days ago so this is just codifying that renunciation). I also made AuthaGraph fixd instead of parameterized because I think that was just a mistake.
@ -3,7 +3,6 @@
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="2048"
|
||||
height="1024"
|
||||
@ -15,7 +14,7 @@
|
||||
<rdf:Description
|
||||
about="https://github.com/jkunimune/Map-Projections/"
|
||||
dc:title="Blank world map (de facto country borders)"
|
||||
dc:description="A map of the world, showing all countries' defacto borders, with major lakes overlaid. Borders precise to 110km. Equirectangular projection."
|
||||
dc:description="A map of the world, showing all countries' de facto borders, with major lakes overlaid. Borders precise to 110km. Equirectangular projection."
|
||||
dc:creator="Justin Kunimune"
|
||||
dc:source="http://www.naturalearthdata.com/"
|
||||
dc:date="2018-02-04"
|
||||
@ -35,7 +34,7 @@
|
||||
</metadata>
|
||||
<title>Blank world map (de facto country borders)</title>
|
||||
<desc>
|
||||
A map of the world, showing all countries' defacto borders, with major lakes overlaid. Borders precise to 110km. Equirectangular projection.
|
||||
A map of the world, showing all countries' de facto borders, with major lakes overlaid. Borders precise to 110km. Equirectangular projection.
|
||||
</desc>
|
||||
<style type="text/css">
|
||||
.country {
|
||||
|
||||
|
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 192 KiB |
@ -3,7 +3,6 @@
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="2048"
|
||||
height="1024"
|
||||
|
||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
@ -3,7 +3,6 @@
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1600"
|
||||
height="800"
|
||||
|
||||
|
Before Width: | Height: | Size: 290 KiB After Width: | Height: | Size: 290 KiB |
@ -3,7 +3,6 @@
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1600"
|
||||
height="800"
|
||||
|
||||
|
Before Width: | Height: | Size: 290 KiB After Width: | Height: | Size: 290 KiB |
@ -3,7 +3,6 @@
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="2048"
|
||||
height="1024"
|
||||
|
||||
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
@ -3,7 +3,6 @@
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="2048"
|
||||
height="1024"
|
||||
|
||||
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
@ -3,7 +3,6 @@
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="2048"
|
||||
height="1024"
|
||||
|
||||
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
@ -3,7 +3,6 @@
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="2048"
|
||||
height="1024"
|
||||
@ -14,8 +13,8 @@
|
||||
<cc:work>
|
||||
<rdf:Description
|
||||
about="https://github.com/jkunimune/Map-Projections/"
|
||||
dc:title="Icosohedral orthodromic mesh - Equirectangular projection"
|
||||
dc:description="A mesh of great circles that intersect with 10-fold symmetry at 12 principal nodes and 6-fold symmetry at 20 minor nodes. Aligns with the edges of an icosohedron and its dual dodecahedron."
|
||||
dc:title="Icosahedral orthodromic mesh - Equirectangular projection"
|
||||
dc:description="A mesh of great circles that intersect with 10-fold symmetry at 12 principal nodes and 6-fold symmetry at 20 minor nodes. Aligns with the edges of an icosahedron and its dual dodecahedron."
|
||||
dc:creator="Justin Kunimune"
|
||||
dc:source="http://www.naturalearthdata.com/"
|
||||
dc:date="2018-02-04"
|
||||
@ -33,9 +32,9 @@
|
||||
</cc:License>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<title>Icosohedral orthodromic mesh – Equirectangular projection</title>
|
||||
<title>Icosahedral orthodromic mesh – Equirectangular projection</title>
|
||||
<desc>
|
||||
A mesh of great circles that intersect with 10-fold symmetry at 12 principal nodes and 6-fold symmetry at 20 minor nodes. Aligns with the edges of an icosohedron and its dual dodecahedron.
|
||||
A mesh of great circles that intersect with 10-fold symmetry at 12 principal nodes and 6-fold symmetry at 20 minor nodes. Aligns with the edges of an icosahedron and its dual dodecahedron.
|
||||
</desc>
|
||||
<style type="text/css">
|
||||
.lines {
|
||||
|
||||
|
Before Width: | Height: | Size: 438 KiB After Width: | Height: | Size: 438 KiB |
@ -3,7 +3,6 @@
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="2048"
|
||||
height="1024"
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 336 KiB After Width: | Height: | Size: 336 KiB |
@ -3,7 +3,6 @@
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="2048"
|
||||
height="1024"
|
||||
|
||||
|
Before Width: | Height: | Size: 250 KiB After Width: | Height: | Size: 250 KiB |
@ -3,7 +3,6 @@
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="2048"
|
||||
height="1024"
|
||||
|
||||
|
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 1.8 MiB |
@ -25,11 +25,11 @@ package apps;
|
||||
|
||||
import image.ImageUtils;
|
||||
import image.SavableImage;
|
||||
import javafx.application.Platform;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.embed.swing.SwingFXUtils;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.SnapshotResult;
|
||||
import javafx.scene.chart.BarChart;
|
||||
import javafx.scene.chart.CategoryAxis;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
@ -38,6 +38,7 @@ import javafx.scene.chart.XYChart.Series;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Separator;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.image.WritableImage;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
@ -53,6 +54,7 @@ import utils.Procedure;
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.function.DoubleUnaryOperator;
|
||||
|
||||
import static java.lang.Double.isFinite;
|
||||
@ -194,20 +196,16 @@ public class MapAnalyzer extends MapApplication {
|
||||
|
||||
private Task<SavableImage> calculatePlotTask() {
|
||||
Task<SavableImage> task = new Task<SavableImage>() {
|
||||
private SnapshotResult snapRes = null;
|
||||
|
||||
protected void running() {
|
||||
charts.snapshot((snapRes) -> {
|
||||
this.snapRes = snapRes;
|
||||
return null;
|
||||
}, null, null);
|
||||
}
|
||||
|
||||
protected SavableImage call() { //yeah, this is weird and dumb, but only because the
|
||||
updateProgress(-1, 1); //snapshot method is dumb. Why does it have to be called on
|
||||
updateMessage("Converting graph\u2026"); //the GUI thread? It's clearly doing Thread
|
||||
while (!isCancelled() && snapRes == null) {} //things, judging by the fact that it
|
||||
return SavableImage.savable(snapRes.getImage()); //_has_ a Callback version
|
||||
protected SavableImage call() throws ExecutionException, InterruptedException {
|
||||
updateProgress(-1, 1);
|
||||
updateMessage("Converting graph\u2026");
|
||||
Task<WritableImage> snapshotTask = new Task<>() {
|
||||
protected WritableImage call() {
|
||||
return charts.snapshot(null, null);
|
||||
}
|
||||
};
|
||||
Platform.runLater(snapshotTask);
|
||||
return SavableImage.savable(snapshotTask.get());
|
||||
}
|
||||
};
|
||||
disableWhile(task.runningProperty(), ButtonType.SAVE_GRAPH);
|
||||
|
||||
@ -74,7 +74,6 @@ import maps.EqualEarth;
|
||||
import maps.Gyorffy;
|
||||
import maps.Lenticular;
|
||||
import maps.Misc;
|
||||
import maps.MyProjections;
|
||||
import maps.Octahedral;
|
||||
import maps.Polyhedral;
|
||||
import maps.Projection;
|
||||
@ -224,7 +223,7 @@ public abstract class MapApplication extends Application {
|
||||
FileChooser.ExtensionFilter defaultExtension,
|
||||
Function<File, Task<Void>> inputSetter) {
|
||||
final Label label = new Label("Current input:");
|
||||
final Text inputLabel = new Text("Basic"+defaultExtension.getExtensions().get(0).substring(1)); //this line kind of cheats, since it assumes the first input will be called "Basic", but I couldn't figure out a good way for this to update when the subclass programatically sets the input
|
||||
final Text inputLabel = new Text("Basic"+defaultExtension.getExtensions().get(0).substring(1)); //this line kind of cheats, since it assumes the first input will be called "Basic", but I couldn't figure out a good way for this to update when the subclass programmatically sets the input
|
||||
|
||||
final FileChooser inputChooser = new FileChooser();
|
||||
inputChooser.setInitialDirectory(new File("input"));
|
||||
@ -415,9 +414,7 @@ public abstract class MapApplication extends Application {
|
||||
final CheckBox cropBox = new CheckBox("Crop at antimeridian"); //the CheckBox for whether there should be shown imagery beyond += 180 degrees
|
||||
cropBox.setSelected(cropAtIDL.isSet());
|
||||
cropBox.setTooltip(new Tooltip("Hide points of extreme longitude."));
|
||||
cropBox.selectedProperty().addListener((observable, old, now) -> {
|
||||
cropAtIDL.set(now);
|
||||
});
|
||||
cropBox.selectedProperty().addListener((observable, old, now) -> cropAtIDL.set(now));
|
||||
|
||||
final ObservableList<Double> factorsOf90 = FXCollections.observableArrayList();
|
||||
for (double f = 1; f <= 90; f += 0.5)
|
||||
@ -533,9 +530,7 @@ public abstract class MapApplication extends Application {
|
||||
"Could not access "+f.getAbsolutePath()+". It's possible that another program has it open.");
|
||||
}
|
||||
pBar.close();
|
||||
// try {
|
||||
// Desktop.getDesktop().open(f.getParentFile()); // TODO: show a popup here with a button to open the folder inste of of doing it automatically
|
||||
// } catch (IOException e) {} //if you can't open the directory for some reason, don't worry about it.
|
||||
// // TODO: show a popup here with a button to open the folder
|
||||
});
|
||||
disableWhile(task.runningProperty(), buttonType);
|
||||
new Thread(task).start(); //now execute!
|
||||
@ -698,7 +693,7 @@ public abstract class MapApplication extends Application {
|
||||
|
||||
|
||||
protected enum ButtonType {
|
||||
LOAD_INPUT, UPDATE_MAP, SAVE_MAP, SAVE_GRAPH;
|
||||
LOAD_INPUT, UPDATE_MAP, SAVE_MAP, SAVE_GRAPH
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -49,41 +49,42 @@ public class MapConverter {
|
||||
// the desired map projection
|
||||
Projection projection = Polyhedral.DYMAXION;
|
||||
|
||||
Stream<Path> paths = Files.walk(Paths.get(directory));
|
||||
Iterable<Path> pathIterable = paths::iterator;
|
||||
try (Stream<Path> paths = Files.walk(Paths.get(directory))) {
|
||||
Iterable<Path> pathIterable = paths::iterator;
|
||||
|
||||
// iterate thru the directory
|
||||
for (Path inputPath: pathIterable) {
|
||||
// look for images that are not dymaxion projections
|
||||
if (inputPath.toString().endsWith(".jpg") ||
|
||||
inputPath.toString().endsWith(".jpeg") ||
|
||||
inputPath.toString().endsWith(".tif") ||
|
||||
inputPath.toString().endsWith(".tiff") ||
|
||||
inputPath.toString().endsWith(".png") &&
|
||||
!inputPath.toString().endsWith(".dymaxion.png")) {
|
||||
System.out.println(inputPath);
|
||||
PixelMap inputImage = new PixelMap(inputPath.toFile());
|
||||
// iterate thru the directory
|
||||
for (Path inputPath : pathIterable) {
|
||||
// look for images that are not dymaxion projections
|
||||
if (inputPath.toString().endsWith(".jpg") ||
|
||||
inputPath.toString().endsWith(".jpeg") ||
|
||||
inputPath.toString().endsWith(".tif") ||
|
||||
inputPath.toString().endsWith(".tiff") ||
|
||||
inputPath.toString().endsWith(".png") &&
|
||||
!inputPath.toString().endsWith(".dymaxion.png")) {
|
||||
System.out.println(inputPath);
|
||||
PixelMap inputImage = new PixelMap(inputPath.toFile());
|
||||
|
||||
// reduce the area by 2 to avoid pixelation
|
||||
double area = inputImage.getWidth() * inputImage.getHeight() / 2.;
|
||||
// and calculate the new dimensions according to Dymaxion's aspect ratio
|
||||
int width = (int) sqrt(area * projection.getShape().aspectRatio);
|
||||
int height = (int) sqrt(area / projection.getShape().aspectRatio);
|
||||
// reduce the area by 2 to avoid pixelation
|
||||
double area = inputImage.getWidth()*inputImage.getHeight()/2.;
|
||||
// and calculate the new dimensions according to Dymaxion's aspect ratio
|
||||
int width = (int) sqrt(area*projection.getShape().aspectRatio);
|
||||
int height = (int) sqrt(area/projection.getShape().aspectRatio);
|
||||
|
||||
// generate the new map
|
||||
BufferedImage outputImage = MapDesignerRaster.calculate(
|
||||
width, height, 2, inputImage, projection,
|
||||
null, true, 0,
|
||||
null, null, null);
|
||||
// generate the new map
|
||||
BufferedImage outputImage = MapDesignerRaster.calculate(
|
||||
width, height, 2, inputImage, projection,
|
||||
null, true, 0,
|
||||
null, null, null);
|
||||
|
||||
// update the filename and save to disk
|
||||
String outputPath = inputPath.toString();
|
||||
outputPath = outputPath.replace(".jpg", ".png");
|
||||
outputPath = outputPath.replace(".jpeg", ".png");
|
||||
outputPath = outputPath.replace(".tif", ".png");
|
||||
outputPath = outputPath.replace(".tiff", ".png");
|
||||
outputPath = outputPath.replace(".png", ".dymaxion.png");
|
||||
SavableImage.savable(outputImage).save(new File(outputPath));
|
||||
// update the filename and save to disk
|
||||
String outputPath = inputPath.toString();
|
||||
outputPath = outputPath.replace(".jpg", ".png");
|
||||
outputPath = outputPath.replace(".jpeg", ".png");
|
||||
outputPath = outputPath.replace(".tif", ".png");
|
||||
outputPath = outputPath.replace(".tiff", ".png");
|
||||
outputPath = outputPath.replace(".png", ".dymaxion.png");
|
||||
SavableImage.savable(outputImage).save(new File(outputPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -69,7 +69,7 @@ public class MapEvaluator {
|
||||
};
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
BufferedImage img = ImageIO.read(new File("input/Sillouette.png")); // start by loading the mask
|
||||
BufferedImage img = ImageIO.read(new File("input/Silhouette.png")); // start by loading the mask
|
||||
boolean[][] land = new boolean[img.getHeight()][img.getWidth()];
|
||||
for (int i = 0; i < land.length; i ++)
|
||||
for (int j = 0; j < land[i].length; j ++)
|
||||
@ -142,7 +142,7 @@ public class MapEvaluator {
|
||||
}
|
||||
|
||||
System.out.printf("Usable area: %.6f sr\n", totalArea);
|
||||
System.out.printf("Anglular: %8.4f %%\n", 100*shearedArea/totalArea);
|
||||
System.out.printf("Angular: %8.4f %%\n", 100*shearedArea/totalArea);
|
||||
System.out.printf("Areal: %8.4f %%\n", 100*swollenArea/totalArea);
|
||||
System.out.printf("Precision: %8.4f %%\n", 100*maxArea/totalArea);
|
||||
System.out.println();
|
||||
|
||||
@ -141,17 +141,17 @@ public class MapExplainer extends Application {
|
||||
out.print("☆");
|
||||
out.println("</dt>");
|
||||
out.println(" <dt>Geometry: </dt>");
|
||||
out.println(" <dd>"+proj.getType().getName()+"</dd>");
|
||||
out.println(" <dd>" + proj.getType().getName() + "</dd>");
|
||||
out.println(" <dt>Property: </dt>");
|
||||
out.println(" <dd>"+proj.getProperty().getName()+"</dd>");
|
||||
out.println(" <dd>" + proj.getProperty().getName() + "</dd>");
|
||||
out.println(" <dt>Uninterrupted: </dt>");
|
||||
out.println(" <dd>"+ (proj.isContinuous() ? "Yes":"No") +"</dd>");
|
||||
out.println(" <dd>" + (proj.isContinuous() ? "Yes":"No") + "</dd>");
|
||||
out.println(" <dt>Shows entire world: </dt>");
|
||||
out.println(" <dd>" + (proj.isComprehensive() ? "Yes" : "No") + "</dd>");
|
||||
out.println(" <dt>Closed-form solution: </dt>");
|
||||
out.println(" <dd>"+ (proj.isSolveable() ? "Yes":"No") +"</dd>");
|
||||
out.println(" <dd>" + (proj.isSolvable() ? "Yes" : "No") + "</dd>");
|
||||
out.println(" <dt>Closed-form inverse: </dt>");
|
||||
out.println(" <dd>"+ (proj.isInvertable() ? "Yes":"No") +"</dd>");
|
||||
out.println(" <dd>" + (proj.isInvertible() ? "Yes" : "No") + "</dd>");
|
||||
out.println(" </dl>");
|
||||
out.println(" <br>");
|
||||
out.println(" </div>");
|
||||
|
||||
@ -93,7 +93,7 @@ public class MapOptimizer extends Application {
|
||||
chart.setCreateSymbols(true);
|
||||
chart.setAxisSortingPolicy(SortingPolicy.NONE);
|
||||
|
||||
PrintStream log = new PrintStream(new File("output/parameters.txt"));
|
||||
PrintStream log = new PrintStream("output/parameters.txt");
|
||||
|
||||
chart.getData().add(analyzeAll(EXISTING_PROJECTIONS));
|
||||
for (Projection p: PROJECTIONS_TO_OPTIMIZE)
|
||||
@ -119,13 +119,13 @@ public class MapOptimizer extends Application {
|
||||
|
||||
for (Projection proj : projs)
|
||||
if (!proj.isParametrized())
|
||||
output.getData().add(plotDistortion(proj, new double[0]));
|
||||
output.getData().add(plotDistortion(proj));
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
private static Data<Number, Number> plotDistortion(Projection proj, double[] params) {
|
||||
private static Data<Number, Number> plotDistortion(Projection proj) {
|
||||
double[] distortion = proj.avgDistortion(GLOBE, proj.getDefaultParameters());
|
||||
return new Data<Number, Number>(distortion[0], distortion[1]);
|
||||
}
|
||||
@ -193,8 +193,8 @@ public class MapOptimizer extends Application {
|
||||
bestValue = avgDist;
|
||||
bestParams = params.clone();
|
||||
}
|
||||
for (int i = 0; i < params.length; i ++)
|
||||
System.out.print(params[i]+", ");
|
||||
for (double param: params)
|
||||
System.out.print(param + ", ");
|
||||
System.out.println(avgDist+";");
|
||||
|
||||
int i;
|
||||
@ -222,7 +222,7 @@ public class MapOptimizer extends Application {
|
||||
* a backtracking line search.
|
||||
* @param arrFunction - The function that takes a parameter array and returns a double value.
|
||||
* @param x0 - The initial guess.
|
||||
* @return The array of parameters that mimimise arrFunction.
|
||||
* @return The array of parameters that minimise arrFunction.
|
||||
*/
|
||||
private static double[] bfgsMinimise(Function<double[], Double> arrFunction, double[] x0) { //The Broyden-Fletcher-Goldfarb-Shanno algorithm
|
||||
System.out.println("BFGS = [");
|
||||
|
||||
@ -155,7 +155,7 @@ public class MapPlotter extends Application {
|
||||
for (Projection projection: projections) {
|
||||
System.out.print(projection+": ");
|
||||
final double[] params = projection.getDefaultParameters();
|
||||
final double distortion[] = projection.avgDistortion(points, params);
|
||||
final double[] distortion = projection.avgDistortion(points, params);
|
||||
final Data<Number, Number> datum = new Data<Number, Number>(
|
||||
distortion[0]/DECIBEL, distortion[1]/DECIBEL);
|
||||
series.getData().add(datum);
|
||||
|
||||
@ -144,8 +144,8 @@ public class ProjectionSelectionDialog extends Dialog<Projection> {
|
||||
text.addRow(1, new Label("Property:"), new Label(p.getProperty().getName()));
|
||||
text.addRow(2, new Label("Uninterrupted:"), new Label(p.isContinuous() ? "Yes" : "No"));
|
||||
text.addRow(3, new Label("Shows entire world:"), new Label(p.isComprehensive() ? "Yes" : "No"));
|
||||
text.addRow(4, new Label("Closed-form solution:"), new Label(p.isSolveable() ? "Yes" : "No"));
|
||||
text.addRow(5, new Label("Closed-form inverse:"), new Label(p.isInvertable() ? "Yes" : "No"));
|
||||
text.addRow(4, new Label("Closed-form solution:"), new Label(p.isSolvable() ? "Yes" : "No"));
|
||||
text.addRow(5, new Label("Closed-form inverse:"), new Label(p.isInvertible() ? "Yes" : "No"));
|
||||
for (Node label: text.getChildren())
|
||||
((Label)label).setFont(body.getFont());
|
||||
}
|
||||
|
||||
@ -77,14 +77,9 @@ public class ImageUtils {
|
||||
Path2D awtPath = new Path2D.Double(Path2D.WIND_NON_ZERO, path.size());
|
||||
for (Path.Command svgCmd: path) {
|
||||
switch (svgCmd.type) {
|
||||
case 'M':
|
||||
awtPath.moveTo(svgCmd.args[0], svgCmd.args[1]);
|
||||
break;
|
||||
case 'L':
|
||||
awtPath.lineTo(svgCmd.args[0], svgCmd.args[1]);
|
||||
break;
|
||||
case 'Z':
|
||||
awtPath.closePath();
|
||||
case 'M' -> awtPath.moveTo(svgCmd.args[0], svgCmd.args[1]);
|
||||
case 'L' -> awtPath.lineTo(svgCmd.args[0], svgCmd.args[1]);
|
||||
case 'Z' -> awtPath.closePath();
|
||||
}
|
||||
}
|
||||
g.draw(awtPath);
|
||||
|
||||
@ -243,7 +243,7 @@ public class SVGMap implements Iterable<SVGMap.SVGElement>, SavableImage {
|
||||
y = y*transform[1] + transform[3];
|
||||
width = width*transform[0];
|
||||
height = height*transform[1];
|
||||
// check if it exactly covers the viewbox (it's okay if it's vertically inverted)
|
||||
// check if it exactly covers the viewBox (it's okay if it's vertically inverted)
|
||||
if ((x == header.vbMinX && y == header.vbMinY && width == header.vbWidth && height == header.vbHeight) ||
|
||||
(x == header.vbMinX && y == header.vbMinY + header.vbHeight && width == header.vbWidth && height == -header.vbHeight)) {
|
||||
// if so, remove the dimensions and stick the remainder in a Background Object
|
||||
@ -516,7 +516,7 @@ public class SVGMap implements Iterable<SVGMap.SVGElement>, SavableImage {
|
||||
partI.remove(0); //remove the useless moveto
|
||||
partJ.addAll(partI); //combine them
|
||||
partI = partJ;
|
||||
parts.remove(j); //don't look at J anymone; it has been absorbed.
|
||||
parts.remove(j); //don't look at J anymore; it has been absorbed.
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -553,10 +553,6 @@ public class SVGMap implements Iterable<SVGMap.SVGElement>, SavableImage {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public Content projected() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return this.content;
|
||||
}
|
||||
@ -623,10 +619,6 @@ public class SVGMap implements Iterable<SVGMap.SVGElement>, SavableImage {
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public GeographicPoint projected() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return format(formatSpecifier, x, y);
|
||||
}
|
||||
|
||||
@ -42,7 +42,8 @@ import static utils.Math2.tand;
|
||||
/**
|
||||
* A truncated octahedral map. The projection is Cahill-Keyes, because it was invented after Cahill,
|
||||
* so it is presumably better (not that Tobler World in a Square wasn't invented after Lambert EAC).
|
||||
* http://www.genekeyes.com/CKOG-OOo/7-CKOG-illus-&-coastline.html
|
||||
*
|
||||
* <a href="http://www.genekeyes.com/CKOG-OOo/7-CKOG-illus-&-coastline.html">genekeyes.com/CKOG-OOo/7-CKOG-illus-&-coastline</a>
|
||||
*
|
||||
* @author jkunimune
|
||||
*/
|
||||
@ -74,7 +75,7 @@ public class CahillKeyes {
|
||||
public static final double POLE_OFFSET = lMA*SCALE_FACTOR; //lMA expressed in output coordinates
|
||||
|
||||
|
||||
public static Projection FACE = new Projection(
|
||||
public static final Projection FACE = new Projection(
|
||||
"Cahill–Keyes (face)", "A single face of Gene Keyes's octahedral projection",
|
||||
Shape.polygon(new double[][] {{0., 0.}, {0., -sqrt(3)/2.}, {1/2., -sqrt(3)/2.}}),
|
||||
true, true, true, false, Projection.Type.OTHER, Projection.Property.COMPROMISE, 2) {
|
||||
|
||||
@ -189,7 +189,9 @@ public class Elastic {
|
||||
/**
|
||||
* convert a location on the map plane to a location on the globe by iteratively searching
|
||||
* for the coordinates that project to x and y on a given section. the algorithm is
|
||||
* Levenberg-Marquardt; see <i>J. Res. NIST</i> 103, p. 633 (1998).
|
||||
* Levenberg-Marquardt; see <i>J. Res. NIST</i> 103, p. 633 (1998). this procedure could
|
||||
* probably be a lot simpler (or omitted entirely) depending on how high quality an inverse
|
||||
* you want.
|
||||
* @param x the x value, in the same units as this.width
|
||||
* @param y the y value, in the same units as this.height
|
||||
* @param section_index the index of the section to search
|
||||
@ -484,7 +486,7 @@ public class Elastic {
|
||||
/**
|
||||
* determine whether the given point falls within this polygon or not. the method used is a typical even/odd
|
||||
* algorithm, with the caveat that the directionality of the polygon is significant. if the vertices go
|
||||
* counterclockwise, the contianed points are everything inside the polygon as one would expect, but if the
|
||||
* counterclockwise, the contained points are everything inside the polygon as one would expect, but if the
|
||||
* vertices go clockwise, the contained points are actually everything <i>outside</i> the polygon.
|
||||
* @param ф the latitude of the point
|
||||
* @param λ the longitude of the point
|
||||
@ -681,7 +683,7 @@ public class Elastic {
|
||||
* recenter a set of spherical coordinates so that the latitude is in the range [-PI/2, PI/2]
|
||||
* and the longitude is in the range [-PI, PI]. if it's outside of the latitude bounds, it
|
||||
* will get reflected about the poles until it isn't. if it's out of the longitude bounds,
|
||||
* it will get modulused such that it isn't.
|
||||
* it will get moduloed such that it isn't.
|
||||
* @param ф the latitude (radians)
|
||||
* @param λ the longitude (radians)
|
||||
* @return the equivalent in-bounds latitude and longitude in an array
|
||||
|
||||
@ -170,7 +170,9 @@ public class Misc {
|
||||
double phi1 = PI/2 - hypot(x, y);
|
||||
if (phi1 < -PI/2) return null;
|
||||
double lam1 = atan2(x, -y);
|
||||
double phiP = asin(sin(phi0)/hypot(sin(phi1),cos(phi1)*cos(lam1))) - atan2(cos(phi1)*cos(lam1),sin(phi1));
|
||||
double y1 = cos(phi1)*cos(lam1);
|
||||
double z1 = sin(phi1);
|
||||
double phiP = asin(sin(phi0)/hypot(z1, y1)) - atan2(y1, z1);
|
||||
if (abs(phiP) > PI/2)
|
||||
phiP = signum(phiP)*PI - phiP;
|
||||
double delL = acos(sin(phi1)/cos(phiP)/cos(phi0) - tan(phiP)*tan(phi0));
|
||||
|
||||
@ -1,92 +0,0 @@
|
||||
/**
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2017 Justin Kunimune
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package maps;
|
||||
|
||||
import maps.Projection.Property;
|
||||
import maps.Projection.Type;
|
||||
import utils.Shape;
|
||||
|
||||
import static java.lang.Math.PI;
|
||||
import static java.lang.Math.acos;
|
||||
import static java.lang.Math.asin;
|
||||
import static java.lang.Math.cos;
|
||||
import static java.lang.Math.hypot;
|
||||
import static java.lang.Math.pow;
|
||||
import static java.lang.Math.signum;
|
||||
import static java.lang.Math.sin;
|
||||
import static java.lang.Math.sqrt;
|
||||
import static java.lang.Math.tan;
|
||||
import static java.lang.Math.toRadians;
|
||||
|
||||
/**
|
||||
* All of the projections I invented, save the tetrahedral ones, because
|
||||
* those have so much in common with other tetrahedral projections.
|
||||
*
|
||||
* @author jkunimune
|
||||
*/
|
||||
public class MyProjections {
|
||||
|
||||
public static final Projection TWO_POINT_EQUALIZED = new Projection("Two-Point Equalized",
|
||||
"A projection I invented specifically for viewing small elliptical regions of the Earth",
|
||||
null, true, true, true, true, Type.OTHER, Property.EQUIDISTANT, 2,
|
||||
new String[] {"Width"}, new double[][] { {0, 180, 120} }) {
|
||||
|
||||
private double theta;
|
||||
|
||||
public void initialize(double... params) {
|
||||
theta = toRadians(params[0])/2;
|
||||
if (theta != 0)
|
||||
this.shape = Shape.ellipse(
|
||||
sqrt(pow(PI - theta, 2) - pow(theta, 2))*
|
||||
sqrt(tan(theta)/theta), //minor axis
|
||||
PI - theta //major axis
|
||||
);
|
||||
else
|
||||
this.shape = Shape.circle(PI);
|
||||
}
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
if (theta == 0) return Azimuthal.POLAR.project(lat, lon);
|
||||
final double d1 = acos(
|
||||
sin(lat)*cos(theta) - cos(lat)*sin(theta)*cos(lon));
|
||||
final double d2 = acos(
|
||||
sin(lat)*cos(theta) + cos(lat)*sin(theta)*cos(lon));
|
||||
final double k = signum(lon)*sqrt(tan(theta)/theta);
|
||||
return new double[] {
|
||||
k*sqrt(d1*d1 - pow((d1*d1-d2*d2+4*theta*theta)/(4*theta), 2)),
|
||||
(d2*d2-d1*d1)/(4*theta) };
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
if (theta == 0) return Azimuthal.POLAR.inverse(x, y);
|
||||
final double d1 = hypot(x/sqrt(tan(theta)/theta), y - theta);
|
||||
final double d2 = hypot(x/sqrt(tan(theta)/theta), y + theta);
|
||||
if (d1 + d2 > 2*shape.yMax) return null;
|
||||
final double phi = asin((cos(d1)+cos(d2))/(2*cos(theta)));
|
||||
final double lam = signum(x)*acos(
|
||||
(sin(phi)*cos(theta) - cos(d1))/(cos(phi)*sin(theta)));
|
||||
return new double[] { phi, lam };
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -182,7 +182,7 @@ public class Octahedral {
|
||||
|
||||
public OctahedralProjection(String name, String desc, int rating, double tipOffset,
|
||||
Projection faceProj, Configuration config) {
|
||||
super(name, desc, null, false, config.comprehensive, faceProj.isSolveable(), faceProj.isInvertable(),
|
||||
super(name, desc, null, false, config.comprehensive, faceProj.isSolvable(), faceProj.isInvertible(),
|
||||
(tipOffset == 0) ? Type.OCTAHEDRAL : Type.TETRADECAHEDRAL, faceProj.getProperty(), rating,
|
||||
new String[] {}, new double[][] {}, config.hasAspect);
|
||||
this.octants = config.placeOctants(tipOffset);
|
||||
|
||||
@ -113,18 +113,14 @@ public class Polyhedral {
|
||||
|
||||
public static final PolyhedralProjection AUTHAGRAPH = new PolyhedralProjection(
|
||||
"IMAGO (AuthaGraph)",
|
||||
"Authagraph is a hip new Japanese map that would be super great if they actually " +
|
||||
"AuthaGraph is a hip new Japanese map that would be super great if they actually " +
|
||||
"published their equations. This is technically just an approximation, also known as " +
|
||||
"the Infinitessimal Mutated AuthaGraph Offspring.",
|
||||
"the Infinitesimal Mutated AuthaGraph Offspring.",
|
||||
true, true, false, Polyhedron.AUTHAGRAPH, Property.COMPROMISE, 3,
|
||||
new String[] {"Power"}, new double[][] {{.5,1,.68}}) {
|
||||
|
||||
private final double[] POLE = {toRadians(77), toRadians(143), toRadians(17)};
|
||||
private double k;
|
||||
|
||||
public void initialize(double... params) {
|
||||
this.k = params[0];
|
||||
}
|
||||
private final double k = .68;
|
||||
|
||||
@Override
|
||||
public double[] project(double lat, double lon) { //apply a pole shift to AuthaGraph
|
||||
@ -137,21 +133,14 @@ public class Polyhedral {
|
||||
return transformToOblique(super.inverse(x, y), POLE);
|
||||
}
|
||||
|
||||
|
||||
public double[] faceProject(double lat, double lon) {
|
||||
final double tht = atan((lon - asin(sin(lon)/sqrt(3)))/PI*sqrt(12));
|
||||
final double p = (PI/2 - lat) / atan(sqrt(2)/cos(lon));
|
||||
return new double[] { pow(p,k)*sqrt(3)/cos(tht), tht };
|
||||
AUTHAPOWER.initialize(k);
|
||||
return AUTHAPOWER.faceProject(lat, lon);
|
||||
}
|
||||
|
||||
protected double[] faceInverse(double r, double th) {
|
||||
final double lon = NumericalAnalysis.newtonRaphsonApproximation(th, th*2,
|
||||
(l) -> atan((l - asin(sin(l)/sqrt(3)))/PI*sqrt(12)),
|
||||
(l) -> (1-1/sqrt(1+2*pow(cos(l),-2)))/sqrt(pow(PI,2)/12+pow(l-asin(sin(l)/sqrt(3)),2)),
|
||||
.001);
|
||||
final double R = r / (sqrt(3)/cos(th));
|
||||
return new double[] {
|
||||
PI/2 - pow(R,1/k)*atan(sqrt(2)/cos(lon)), lon };
|
||||
public double[] faceInverse(double r, double th) {
|
||||
AUTHAPOWER.initialize(k);
|
||||
return AUTHAPOWER.faceInverse(r, th);
|
||||
}
|
||||
};
|
||||
|
||||
@ -173,7 +162,7 @@ public class Polyhedral {
|
||||
return new double[] { pow(p,k)*sqrt(3)/cos(tht), tht };
|
||||
}
|
||||
|
||||
protected double[] faceInverse(double r, double th) {
|
||||
public double[] faceInverse(double r, double th) {
|
||||
final double lon = NumericalAnalysis.newtonRaphsonApproximation(th, th*2,
|
||||
(l) -> atan((l - asin(sin(l)/sqrt(3)))/PI*sqrt(12)),
|
||||
(l) -> (1-1/sqrt(1+2*pow(cos(l),-2)))/sqrt(pow(PI,2)/12+pow(l-asin(sin(l)/sqrt(3)),2)),
|
||||
@ -310,9 +299,9 @@ public class Polyhedral {
|
||||
double y = r*sin(th)*atan(2);
|
||||
double a = sqrt(3)*x + y; //angular distance up each side of the triangle
|
||||
double b = sqrt(3)*x - y;
|
||||
double xG = cos36*(sin(a) + sin(b))/(1 + cos(a) + cos(b)); //unnormalised gnomonic coordinates
|
||||
double yG = sin36*
|
||||
(sin(a) - sin(b) + 2*sin(a-b))/(1 + cos(a) + cos(b));
|
||||
double correction = 1 + cos(a) + cos(b);
|
||||
double xG = cos36*(sin(a) + sin(b))/correction; //unnormalised gnomonic coordinates
|
||||
double yG = sin36*(sin(a) - sin(b) + 2*sin(a-b))/correction;
|
||||
return new double[] {atan(1/hypot(xG, yG)), atan2(yG, xG)}; //inverse gnomonic projection
|
||||
}
|
||||
};
|
||||
@ -530,7 +519,7 @@ public class Polyhedral {
|
||||
new Facet(-0.5, -0.5*sqrt(3), PI/2, -PI/2, 0 , -3*PI/5, -PI/5, 2*PI/5), //West Pacific O.
|
||||
new Facet(-1.5, -0.5*sqrt(3), PI/2, -PI/2, 0 , -PI/5, 0 , 3*PI/5), //Melanesia
|
||||
});
|
||||
/** the number of the rotational spmmetry in the spherical coordinate system */
|
||||
/** the number of the rotational symmetry in the spherical coordinate system */
|
||||
public final int sphereSym;
|
||||
/** the number of the rotational symmetry in the planar coordinate system */
|
||||
public final int planarSym;
|
||||
@ -549,7 +538,7 @@ public class Polyhedral {
|
||||
if (sphereSym == 3)
|
||||
this.type = Type.TETRAHEDRAL;
|
||||
else
|
||||
this.type = Type.ICOSOHEDRAL;
|
||||
this.type = Type.ICOSAHEDRAL;
|
||||
}
|
||||
|
||||
public double[] rotateOOB(double x, double y, double xCen, double yCen) { //move points that are out of bounds for project()
|
||||
|
||||
@ -70,8 +70,8 @@ public abstract class Projection {
|
||||
|
||||
private final boolean continuous; //is the interruption kept to no more than one meridian's equivalent?
|
||||
private final boolean comprehensive; //does it display the entire world?
|
||||
private final boolean solveable; //is the solution closed-form?
|
||||
private final boolean invertable; //is the inverse solution closed-form?
|
||||
private final boolean solvable; //is the solution closed-form?
|
||||
private final boolean invertible; //is the inverse solution closed-form?
|
||||
private final Type type; //the geometry of the projection
|
||||
private final Property property; //what it is good for
|
||||
private final int rating; //how good I think it is
|
||||
@ -87,15 +87,15 @@ public abstract class Projection {
|
||||
|
||||
protected Projection(
|
||||
String name, String description, Shape shape, boolean continuous, boolean comprehensive,
|
||||
boolean solveable, boolean invertable, Type type, Property property, int rating,
|
||||
boolean solvable, boolean invertible, Type type, Property property, int rating,
|
||||
String[] paramNames, double[][] paramValues) {
|
||||
this(name, description, shape, continuous, comprehensive, solveable, invertable, type, property, rating,
|
||||
this(name, description, shape, continuous, comprehensive, solvable, invertible, type, property, rating,
|
||||
paramNames, paramValues, true);
|
||||
}
|
||||
|
||||
protected Projection (
|
||||
String name, String description, Shape shape, boolean continuous, boolean comprehensive,
|
||||
boolean solveable, boolean invertable, Type type, Property property, int rating,
|
||||
boolean solvable, boolean invertible, Type type, Property property, int rating,
|
||||
String[] paramNames, double[][] paramValues, boolean hasAspect) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
@ -105,16 +105,16 @@ public abstract class Projection {
|
||||
this.shape = shape;
|
||||
this.continuous = continuous;
|
||||
this.comprehensive = comprehensive;
|
||||
this.solveable = solveable;
|
||||
this.invertable = invertable;
|
||||
this.solvable = solvable;
|
||||
this.invertible = invertible;
|
||||
this.type = type;
|
||||
this.property = property;
|
||||
this.rating = rating;
|
||||
}
|
||||
|
||||
protected Projection(String name, Projection base) {
|
||||
this(name, base.description, base.shape, base.comprehensive, base.invertable,
|
||||
base.solveable, base.continuous, base.type, base.property, base.rating,
|
||||
this(name, base.description, base.shape, base.comprehensive, base.invertible,
|
||||
base.solvable, base.continuous, base.type, base.property, base.rating,
|
||||
base.paramNames, base.paramValues, base.hasAspect);
|
||||
}
|
||||
|
||||
@ -188,30 +188,7 @@ public abstract class Projection {
|
||||
public double[] inverse(double[] coords) {
|
||||
return inverse(coords[0], coords[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* convert a location on the map plane to a location on the rotated globe
|
||||
* @param coords the x and y value, in the same units as this.bounds
|
||||
* @param pole the desired aspect: the latitude and longitude of the location that should appear as the North Pole
|
||||
* and the angle to rotate the globe about its new axis
|
||||
* @return the latitude and longitude in radians
|
||||
*/
|
||||
public double[] inverse(double[] coords, double[] pole) {
|
||||
return inverse(coords[0], coords[1], pole);
|
||||
}
|
||||
|
||||
/**
|
||||
* convert a location on the map plane to a location on the rotated globe
|
||||
* @param x the x value in the same units as this.bounds
|
||||
* @param y the y value in the same units as this.bounds
|
||||
* @param pole the desired aspect: the latitude and longitude of the location that should appear as the North Pole
|
||||
* and the angle to rotate the globe about its new axis
|
||||
* @return the latitude and longitude in radians
|
||||
*/
|
||||
public double[] inverse(double x, double y, double[] pole) {
|
||||
return inverse(x, y, pole, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* convert a location on the map plane to a location on the rotated globe
|
||||
* @param x the x value in the same units as this.bounds
|
||||
@ -233,19 +210,8 @@ public abstract class Projection {
|
||||
else
|
||||
return transformToOblique(relCoords, hasAspect ? pole : null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* perform the inverse projection on a 2D array of points
|
||||
* @param size the maximum linear dimension of the 2D array
|
||||
* @return an array of latitude-longitude pairs, where each row is at a particular x value,
|
||||
* and each column is at a particular y value. y decreases with increasing row index.
|
||||
* elements corresponding to points not on the map will be set to zero.
|
||||
*/
|
||||
public double[][][] map(int size) {
|
||||
return map(size, false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* perform the inverse projection on a 2D array of points
|
||||
* @param size the maximum linear dimension of the 2D array
|
||||
@ -431,11 +397,11 @@ public abstract class Projection {
|
||||
}
|
||||
|
||||
public double[][][] calculateDistortion(double[][][] points,
|
||||
BooleanSupplier cancelation, DoubleConsumer progressTracker) { //calculate both kinds of distortion over the given region
|
||||
BooleanSupplier cancellation, DoubleConsumer progressTracker) { //calculate both kinds of distortion over the given region
|
||||
double[][][] output = new double[2][points.length][points[0].length]; //the distortion matrix
|
||||
|
||||
for (int y = 0; y < points.length; y ++) {
|
||||
if (cancelation.getAsBoolean()) return null;
|
||||
if (cancellation.getAsBoolean()) return null;
|
||||
progressTracker.accept((double)y/points.length);
|
||||
for (int x = 0; x < points[y].length; x ++) {
|
||||
if (points[y][x] != null) {
|
||||
@ -632,12 +598,12 @@ public abstract class Projection {
|
||||
return this.comprehensive;
|
||||
}
|
||||
|
||||
public final boolean isInvertable() {
|
||||
return this.invertable;
|
||||
public final boolean isInvertible() {
|
||||
return this.invertible;
|
||||
}
|
||||
|
||||
public final boolean isSolveable() {
|
||||
return this.solveable;
|
||||
public final boolean isSolvable() {
|
||||
return this.solvable;
|
||||
}
|
||||
|
||||
public final boolean isContinuous() {
|
||||
@ -692,7 +658,7 @@ public abstract class Projection {
|
||||
PSEUDOCYLINDRICAL("Pseudocylindrical"), PSEUDOCONIC("Pseudoconic"),
|
||||
PSEUDOAZIMUTHAL("Pseudoazimuthal"),
|
||||
TETRAHEDRAL("Tetrahedral"), OCTAHEDRAL("Octahedral"),
|
||||
TETRADECAHEDRAL("Truncated Octahedral"), ICOSOHEDRAL("Icosohedral"),
|
||||
TETRADECAHEDRAL("Truncated Octahedral"), ICOSAHEDRAL("Icosahedral"),
|
||||
POLYNOMIAL("Polynomial"), STREBE("Strebe blend"), PLANAR("Planar"), OTHER("Other");
|
||||
|
||||
private final String name;
|
||||
|
||||
@ -35,8 +35,9 @@ import static utils.Math2.linInterp;
|
||||
|
||||
/**
|
||||
* A class of methods pertaining to the Waterman projection.
|
||||
* http://www.watermanpolyhedron.com/methodp.html
|
||||
*
|
||||
*
|
||||
* <a href="http://www.watermanpolyhedron.com/methodp.html">watermanpolyhedron.com/methodp</a>
|
||||
*
|
||||
* @author jkunimune
|
||||
*/
|
||||
public class Waterman {
|
||||
@ -50,7 +51,7 @@ public class Waterman {
|
||||
private static final double cos15 = (sqrt(3)+1)/sqrt(8); // cos(15°)
|
||||
|
||||
|
||||
public static Projection FACE = new Projection(
|
||||
public static final Projection FACE = new Projection(
|
||||
"Waterman (face)", "A single face of Waterman's octahedral projection",
|
||||
Shape.polygon(new double[][] {{0., 0.}, {0., -sqrt(3)/2.}, {1/2., -sqrt(3)/2.}}),
|
||||
true, true, true, false, Type.OTHER, Property.COMPROMISE, 2) {
|
||||
|
||||
@ -74,7 +74,7 @@ public final class WinkelTripel {
|
||||
public double[] inverse(double x, double y) {
|
||||
return NumericalAnalysis.newtonRaphsonApproximation(
|
||||
x, y,
|
||||
y/2, x*(1 + cos(y*PI/2))/(2 + 2*cos(stdParallel)), //inital guess is Eckert V
|
||||
y/2, x*(1 + cos(y*PI/2))/(2 + 2*cos(stdParallel)), //initial guess is Eckert V
|
||||
this::f1pX, this::f2pY,
|
||||
this::df1dphi, this::df1dlam, this::df2dphi, this::df2dlam, .001);
|
||||
}
|
||||
|
||||
@ -41,7 +41,7 @@ public class Dixon {
|
||||
/**
|
||||
* One third of the period of a dixon sm or cm function
|
||||
*/
|
||||
public static double PERIOD_THIRD = 1.7666387503;
|
||||
public static final double PERIOD_THIRD = 1.7666387503;
|
||||
|
||||
private static final double[] COEF = { 1.000000e0, .625000e-1, .223214e-2, .069754e-3,
|
||||
.020121e-4/*, .005539e-5, .001477e-6, .000385e-7, .000099e-8, .000025e-9*/ };
|
||||
|
||||
@ -154,52 +154,8 @@ public class Math2 {
|
||||
public static double linInterp(double x, double a0, double a1, double b0, double b1) {
|
||||
return (x-a0)*(b1-b0)/(a1-a0) + b0;
|
||||
}
|
||||
|
||||
public static double[] linInterp(double[] xs, double[][] A, double[][] B) {
|
||||
double[] out = new double[xs.length];
|
||||
for (int i = 0; i < xs.length; i ++)
|
||||
out[i] = linInterp(xs[i], A[0][i], A[1][i], B[0][i], B[1][i]);
|
||||
return out;
|
||||
}
|
||||
|
||||
public static boolean outOfBoundsInSameDirection(double[][] range, double[]... xs) {
|
||||
boolean allOutOnLeft = true;
|
||||
for (double[] x : xs)
|
||||
if (x[0] >= range[0][0]) {
|
||||
allOutOnLeft = false;
|
||||
break;
|
||||
}
|
||||
if (allOutOnLeft)
|
||||
return true;
|
||||
|
||||
boolean allOutOnRight = true;
|
||||
for (double[] x : xs)
|
||||
if (x[0] <= range[1][0]) {
|
||||
allOutOnRight = false;
|
||||
break;
|
||||
}
|
||||
if (allOutOnRight)
|
||||
return true;
|
||||
|
||||
boolean allOutOnBottom = true;
|
||||
for (double[] x : xs)
|
||||
if (x[1] >= range[0][1]) {
|
||||
allOutOnBottom = false;
|
||||
break;
|
||||
}
|
||||
if (allOutOnBottom)
|
||||
return true;
|
||||
|
||||
boolean allOutOnTop = true;
|
||||
for (double[] x : xs)
|
||||
if (x[1] <= range[1][1]) {
|
||||
allOutOnTop = false;
|
||||
break;
|
||||
}
|
||||
return allOutOnTop;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static double hypot(double[] a, double[] b) {
|
||||
return Math.hypot(a[0] - b[0], a[1] - b[1]);
|
||||
}
|
||||
|
||||
@ -44,11 +44,7 @@ public class MutableDouble {
|
||||
public double get() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public boolean isNaN() {
|
||||
return Double.isNaN(this.value);
|
||||
}
|
||||
|
||||
|
||||
public void set(double value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@ -1,46 +0,0 @@
|
||||
/**
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2017 Justin Kunimune
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package utils;
|
||||
|
||||
/**
|
||||
* A class for working with and manipulating XML stuff.
|
||||
*
|
||||
* @author jkunimune
|
||||
*/
|
||||
public class SAXUtils {
|
||||
|
||||
public static String encode(String s0) { //encode with the ampersand notation
|
||||
StringBuilder s1 = new StringBuilder();
|
||||
for (int i = 0; i < s0.length(); i ++) {
|
||||
if (s0.charAt(i) >= 128)
|
||||
s1.append("&#").append((int) s0.charAt(i)).append(";");
|
||||
else if (s0.charAt(i) == '&')
|
||||
s1.append("&");
|
||||
else
|
||||
s1.append(s0.charAt(i));
|
||||
}
|
||||
return s1.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@ -2,7 +2,7 @@ import math
|
||||
|
||||
|
||||
AXIAL_TILT = 23.43694
|
||||
ANTI_TILT = 66.56306 #I have both of these because it's the easiest way to deal with roundoff
|
||||
ANTI_TILT = 66.56306 # I have both of these because it's the easiest way to deal with roundoff
|
||||
|
||||
|
||||
def plot_meridian(lamd, granularity, cut=0, clazz=None) -> str:
|
||||
@ -27,7 +27,7 @@ def generate_graticule(spacing, granularity, include_tropics=False, adjust_poles
|
||||
result = ""
|
||||
NUM_BASE = 90//spacing
|
||||
cuts = [0]*(4*NUM_BASE)
|
||||
if adjust_poles: #if this is True, reduce the number of meridians as you approach the pole
|
||||
if adjust_poles: # if this is True, reduce the number of meridians as you approach the pole
|
||||
old_num = 1
|
||||
for p in range(0, 90, spacing):
|
||||
new_num = old_num*int(1/math.cos(math.radians(p+spacing/2))/old_num)
|
||||
|
||||
@ -15,7 +15,7 @@ def plot_orthodrome(phi0, lam0, tht0) -> str:
|
||||
|
||||
|
||||
def generate_orthodromes() -> str:
|
||||
"""generate an icosohedral orthodromic mesh, like the Brilliant logo (#notsponsored)"""
|
||||
"""generate an icosahedral orthodromic mesh, like the Brilliant logo (#notsponsored)"""
|
||||
result = ""
|
||||
for l in range(-180, 180, 36):
|
||||
result += plot_orthodrome(pi/2, 0, radians(l))
|
||||
|
||||
@ -67,7 +67,7 @@ def plot_political_shapes(filename, mode="normal",
|
||||
else:
|
||||
regions = load_shapes_from_one_place_and_records_from_another(filename, include_circles_from)
|
||||
# go thru the records and sort them into a dictionary by ISO 3166 codes
|
||||
hierarchially_arranged_regions: dict[tuple[tuple[str, ...], str], ShapeRecord] = {}
|
||||
hierarchically_arranged_regions: dict[tuple[tuple[str, ...], str], ShapeRecord] = {}
|
||||
for shape, record in regions:
|
||||
# if it Antarctica, trim it
|
||||
if trim_antarctica:
|
||||
@ -84,25 +84,25 @@ def plot_political_shapes(filename, mode="normal",
|
||||
if province_code.endswith("~"):
|
||||
province_code = province_code[:-1]
|
||||
country_code = province_code[:province_code.index("-")]
|
||||
hierarchial_identifier = (sovereign_code, country_code, province_code)
|
||||
hierarchical_identifier = (sovereign_code, country_code, province_code)
|
||||
unique_identifier = record["adm1_code"]
|
||||
else: # or by sovereign, admin0
|
||||
country_code = record["adm0_a3"]
|
||||
hierarchial_identifier = (sovereign_code, country_code)
|
||||
hierarchical_identifier = (sovereign_code, country_code)
|
||||
unique_identifier = country_code
|
||||
if hierarchial_identifier[0] == hierarchial_identifier[1]: # remove duplicate layers
|
||||
hierarchial_identifier = hierarchial_identifier[1:]
|
||||
key = (hierarchial_identifier, unique_identifier)
|
||||
hierarchially_arranged_regions[key] = (shape, record)
|
||||
if hierarchical_identifier[0] == hierarchical_identifier[1]: # remove duplicate layers
|
||||
hierarchical_identifier = hierarchical_identifier[1:]
|
||||
key = (hierarchical_identifier, unique_identifier)
|
||||
hierarchically_arranged_regions[key] = (shape, record)
|
||||
|
||||
# next, go thru and plot the borders
|
||||
current_state = []
|
||||
result = ""
|
||||
already_titled = set()
|
||||
# for each item
|
||||
for key in sorted(hierarchially_arranged_regions.keys()):
|
||||
for key in sorted(hierarchically_arranged_regions.keys()):
|
||||
hierarchy, identifier = key
|
||||
shape, record = hierarchially_arranged_regions[key]
|
||||
shape, record = hierarchically_arranged_regions[key]
|
||||
|
||||
# decide whether it's "small"
|
||||
is_small = True
|
||||
@ -179,7 +179,7 @@ def plot_political_shapes(filename, mode="normal",
|
||||
current_state.pop()
|
||||
|
||||
# remove any groups with no elements
|
||||
for i in range(3):
|
||||
for _ in range(3):
|
||||
result = re.sub(r'(\t)*<g class="([A-Za-z_-]+)">(\s*)</g>\n',
|
||||
'', result)
|
||||
# simplify any groups with only a single element
|
||||
|
||||
@ -6,10 +6,11 @@ from helpers import plot, trim_edges, lengthen_edges, load_shaperecords
|
||||
def plot_shapes(
|
||||
filename, max_rank=float('inf'), clazz=None,
|
||||
trim_antarctica=False, flesh_out_antarctica=False, mark_antarctica=False,
|
||||
filter_field=None, filter_values=['']) -> str:
|
||||
filter_field=None, filter_values=None) -> str:
|
||||
result = ""
|
||||
for shape, record in load_shaperecords(filename):
|
||||
if filter_field is not None:
|
||||
assert filter_values is not None
|
||||
filter_value = record[filter_field]
|
||||
if filter_value not in filter_values:
|
||||
continue # skip it if it is filtered out
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import random as rng
|
||||
from typing import Any, Iterable
|
||||
from typing import Any, Iterable, Optional
|
||||
|
||||
import shapefile
|
||||
from numpy import pi, sin, cos, tan, arcsin, arccos, degrees, ceil, radians, arctan2, hypot
|
||||
|
||||
|
||||
def load_shaperecords(filename) -> Iterable[ShapeRecord]:
|
||||
@ -49,31 +49,46 @@ def load_shapes_from_one_place_and_records_from_another(shape_filename, record_f
|
||||
|
||||
def obliquify(lat1, lon1, lat0, lon0):
|
||||
""" go from relative to absolute coordinates """
|
||||
latf = math.asin(math.sin(lat0)*math.sin(lat1) - math.cos(lat0)*math.cos(lon1)*math.cos(lat1))
|
||||
innerFunc = math.sin(lat1)/math.cos(lat0)/math.cos(latf) - math.tan(lat0)*math.tan(latf)
|
||||
if lat0 == math.pi/2: # accounts for special case when lat0 = pi/2
|
||||
latf = arcsin(sin(lat0)*sin(lat1) - cos(lat0)*cos(lon1)*cos(lat1))
|
||||
innerFunc = sin(lat1)/cos(lat0)/cos(latf) - tan(lat0)*tan(latf)
|
||||
if lat0 == pi/2: # accounts for special case when lat0 = pi/2
|
||||
lonf = lon1+lon0
|
||||
elif lat0 == -math.pi/2: # accounts for special case when lat0 = -pi/2
|
||||
lonf = -lon1+lon0 + math.pi
|
||||
elif lat0 == -pi/2: # accounts for special case when lat0 = -pi/2
|
||||
lonf = -lon1+lon0 + pi
|
||||
elif abs(innerFunc) > 1: # accounts for special case when cos(lat1) -> 0
|
||||
if (lon1 == 0 and lat1 < -lat0) or (lon1 != 0 and lat1 < lat0):
|
||||
lonf = lon0 + math.pi
|
||||
lonf = lon0 + pi
|
||||
else:
|
||||
lonf = lon0
|
||||
elif math.sin(lon1) > 0:
|
||||
lonf = lon0 + math.acos(innerFunc)
|
||||
elif sin(lon1) > 0:
|
||||
lonf = lon0 + arccos(innerFunc)
|
||||
else:
|
||||
lonf = lon0 - math.acos(innerFunc)
|
||||
lonf = lon0 - arccos(innerFunc)
|
||||
|
||||
while lonf > math.pi:
|
||||
lonf -= 2*math.pi
|
||||
while lonf < -math.pi:
|
||||
lonf += 2*math.pi
|
||||
while lonf > pi:
|
||||
lonf -= 2*pi
|
||||
while lonf < -pi:
|
||||
lonf += 2*pi
|
||||
|
||||
return latf, lonf
|
||||
|
||||
|
||||
def plot(coords, midx=[0], close=True, fourmat='pr', clazz=None, ident=None, tabs=3) -> str:
|
||||
def plot(coords: list[tuple[float, float]], midx: Optional[list[int]] = None, close=True, fourmat='pr', clazz=None, ident=None, tabs=3) -> str:
|
||||
"""
|
||||
express a list of 2D points as an SVG <path> tag
|
||||
:param coords: the coordinate pairs of the vertices
|
||||
:param midx: the indices at which each part of the path starts (that is, the indices of the movetos in the path)
|
||||
:param close: whether to toss a Z on the end of the path string
|
||||
:param fourmat: the order and units of the coordinates in each pair. probably "pr" if it's (ф,λ) coordinates in
|
||||
radians, or "xd" if it's (λ,ф) in degrees. the output will always be expressed as (λ,ф) in degrees,
|
||||
so that's why I ask.
|
||||
:param clazz: the class attribute to give the <path>, if any
|
||||
:param ident: the id attribute to give the <path>, if any
|
||||
:param tabs: the number of tab characters to prepend to the <path> element
|
||||
:return: a string describing the <path> in SVG syntax, including some tab characters on the front and a newline on the back
|
||||
"""
|
||||
if midx is None:
|
||||
midx = [0]
|
||||
class_attr = f'class="{clazz}" ' if clazz is not None else ''
|
||||
ident_attr = f'id="{ident}" ' if ident is not None else ''
|
||||
tag = '\t'*tabs + f'<path {class_attr}{ident_attr}d="'
|
||||
@ -92,11 +107,13 @@ def plot(coords, midx=[0], close=True, fourmat='pr', clazz=None, ident=None, tab
|
||||
letter = 'L'
|
||||
|
||||
if 'r' in fourmat:
|
||||
coord = (math.degrees(c) for c in coord)
|
||||
coord = (degrees(c) for c in coord)
|
||||
if 'p' in fourmat:
|
||||
y, x = coord
|
||||
elif 'x' in fourmat:
|
||||
x, y = coord
|
||||
else:
|
||||
raise ValueError(f"unrecognized format string: '{fourmat}'")
|
||||
|
||||
tag += '{}{:.3f},{:.3f} '.format(letter, x, y)
|
||||
if close:
|
||||
@ -126,11 +143,11 @@ def lengthen_edges(coast):
|
||||
x1, y1 = coast[i+1]
|
||||
if x0 == x1 and abs(y1-y0) > 1:
|
||||
step = 1 if y0 > y1 else -1
|
||||
for j in range(math.ceil(abs(y1-y0))-1):
|
||||
for j in range(int(ceil(abs(y1-y0))-1)):
|
||||
coast.insert(i+1, (x1, y1+step*(j+1)))
|
||||
elif y0 == y1 and abs(x1-x0) > 1:
|
||||
step = 1 if x0 > x1 else -1
|
||||
for j in range(math.ceil(abs(x1-x0))-1):
|
||||
for j in range(int(ceil(abs(x1-x0))-1)):
|
||||
coast.insert(i+1, (x1+step*(j+1), y1))
|
||||
return coast
|
||||
|
||||
@ -143,10 +160,10 @@ def get_centroid(points, parts=None):
|
||||
maxP = max([p[1] for p in points])
|
||||
|
||||
if maxL-minL < 15 and maxP-minP < 60:
|
||||
return ((maxL+minL)/2, (maxP+minP)/2)
|
||||
elif parts: #if there are multiple parts, try guessing the centroid of just one; the biggest one by bounding box
|
||||
return (maxL + minL)/2, (maxP + minP)/2
|
||||
elif parts: # if there are multiple parts, try guessing the centroid of just one; the biggest one by bounding box
|
||||
parts.append(len(points))
|
||||
max_area = 0
|
||||
max_area, best_part = 0, None
|
||||
for i in range(1, len(parts)):
|
||||
part = points[parts[i-1]:parts[i]]
|
||||
minL = min([p[0] for p in part])
|
||||
@ -156,7 +173,10 @@ def get_centroid(points, parts=None):
|
||||
if (maxL-minL)*(maxP-minP) > max_area:
|
||||
max_area = (maxL-minL)*(maxP-minP)
|
||||
best_part = (parts[i-1], parts[i])
|
||||
return get_centroid(points[best_part[0]:best_part[1]])
|
||||
if best_part is not None:
|
||||
return get_centroid(points[best_part[0]:best_part[1]])
|
||||
else:
|
||||
raise ValueError("no parts from which to take the centroid were found")
|
||||
|
||||
lines = []
|
||||
for i in range(1, len(points)):
|
||||
@ -164,30 +184,30 @@ def get_centroid(points, parts=None):
|
||||
xc, yc, zc = 0, 0, 0
|
||||
j = 0
|
||||
num_in = 0
|
||||
min_latnum = math.sin(math.radians(minP))
|
||||
max_latnum = math.sin(math.radians(maxP))
|
||||
min_lonnum = math.radians(minL)
|
||||
max_lonnum = math.radians(maxL)
|
||||
min_latnum = sin(radians(minP))
|
||||
max_latnum = sin(radians(maxP))
|
||||
min_lonnum = radians(minL)
|
||||
max_lonnum = radians(maxL)
|
||||
rng.seed(0)
|
||||
while j < 4000 or num_in < 10: #monte carlo
|
||||
while j < 4000 or num_in < 10: # monte carlo
|
||||
j += 1
|
||||
latr = math.asin(rng.random()*(max_latnum-min_latnum)+min_latnum)
|
||||
latr = arcsin(rng.random()*(max_latnum-min_latnum)+min_latnum)
|
||||
lonr = rng.random()*(max_lonnum-min_lonnum) + min_lonnum
|
||||
latd, lond = math.degrees(latr), math.degrees(lonr)
|
||||
latd, lond = degrees(latr), degrees(lonr)
|
||||
num_crosses = 0
|
||||
for l1, p1, l2, p2 in lines: #count the lines a northward ray crosses
|
||||
for l1, p1, l2, p2 in lines: # count the lines a northward ray crosses
|
||||
if ((l1 <= lond and l2 > lond) or (l2 <= lond and l1 > lond)) and (p1*(l2-lond)/(l2-l1) + p2*(l1-lond)/(l1-l2) > latd):
|
||||
num_crosses += 1
|
||||
|
||||
if num_crosses%2 == 1: #if odd,
|
||||
if num_crosses%2 == 1: # if odd,
|
||||
num_in += 1
|
||||
xc += math.cos(latr)*math.cos(lonr) #this point is in.
|
||||
yc += math.cos(latr)*math.sin(lonr) #update the centroid
|
||||
zc += math.sin(latr)
|
||||
xc += cos(latr)*cos(lonr) # this point is in.
|
||||
yc += cos(latr)*sin(lonr) # update the centroid
|
||||
zc += sin(latr)
|
||||
|
||||
loncr = math.atan2(yc, xc)
|
||||
latcr = math.atan2(zc, math.hypot(xc, yc))
|
||||
return (math.degrees(loncr), math.degrees(latcr))
|
||||
loncr = arctan2(yc, xc)
|
||||
latcr = arctan2(zc, hypot(xc, yc))
|
||||
return degrees(loncr), degrees(latcr)
|
||||
|
||||
|
||||
def normalize_shaperecord(shaperecord: shapefile.ShapeRecord) -> ShapeRecord:
|
||||
|
||||