mirror of
https://github.com/csharpee/Map-Projections.git
synced 2025-12-10 00:00:19 -05:00
Explain. Explain! EXPLAIN!
I fixed up and ran the MapExplainer, to update the website. I added more maps to it and made it handle threads properly. I also updated the MapPlotter to have that neat Bat Map, and fixed minor issues with Polyhedral and Octohedral.
This commit is contained in:
parent
31b1f98e23
commit
aa34b76190
BIN
input/Advanced/Tissot Not.jpg
Normal file
BIN
input/Advanced/Tissot Not.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
BIN
input/Advanced/Tissot Shift East.jpg
Normal file
BIN
input/Advanced/Tissot Shift East.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 MiB |
BIN
input/Advanced/Tissot Shift West.jpg
Normal file
BIN
input/Advanced/Tissot Shift West.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 47 KiB |
@ -22,10 +22,15 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package apps;
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.function.DoubleUnaryOperator;
|
||||
|
||||
import image.ImageUtils;
|
||||
import image.SavableImage;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.embed.swing.SwingFXUtils;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.SnapshotResult;
|
||||
@ -37,8 +42,6 @@ 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.PixelWriter;
|
||||
import javafx.scene.image.WritableImage;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
@ -76,6 +79,9 @@ public class MapAnalyzer extends MapApplication {
|
||||
new FileChooser.ExtensionFilter("JPG", "*.jpg","*.jpeg","*.jpe","*.jfif"),
|
||||
new FileChooser.ExtensionFilter("GIF", "*.gif") };
|
||||
|
||||
private static final Color GRATICULE_COLOR = Color.BLACK;
|
||||
private static final float GRATICULE_WIDTH = 0.5f;
|
||||
|
||||
private Flag cropAtIDL;
|
||||
private MutableDouble graticuleSpacing;
|
||||
private Text avgSizeDistort, avgShapeDistort;
|
||||
@ -224,7 +230,7 @@ public class MapAnalyzer extends MapApplication {
|
||||
* @param imgSize - The desired graphic size.
|
||||
* @param proj - The projection to analyze.
|
||||
* @param crop - Should points at extreme longitudes be hidden?
|
||||
* @param gratSpace - The number of degrees between graticule lines, or 0 if no graticule.
|
||||
* @param gratSpacing - The number of degrees between graticule lines, or 0 if no graticule.
|
||||
* @param mapDisplay - The optional ImageView into which to put the new Image.
|
||||
* @param sizeChart - The optional BarChart to update with the area histogram.
|
||||
* @param shapeChart - The optional BarChart to update with the stretch histogram.
|
||||
@ -233,13 +239,13 @@ public class MapAnalyzer extends MapApplication {
|
||||
* @return The new graphic as a SavableImage.
|
||||
*/
|
||||
public static Task<SavableImage> calculateGraphicTask(int imgSize,
|
||||
Projection proj, boolean crop, double gratSpace, ImageView mapDisplay,
|
||||
Projection proj, boolean crop, double gratSpacing, ImageView mapDisplay,
|
||||
BarChart<String, Number> sizeChart, BarChart<String, Number> shapeChart,
|
||||
Text avgSizeDistort, Text avgShapeDistort) { //TODO graticule still does nothing; just paste it in!
|
||||
return new Task<SavableImage>() {
|
||||
double[][][] distortionG; //some variables that might get used later
|
||||
double sizeDistort, shapeDistort;
|
||||
WritableImage graphic;
|
||||
BufferedImage graphic;
|
||||
|
||||
protected SavableImage call() {
|
||||
updateProgress(-1, 1);
|
||||
@ -257,22 +263,23 @@ public class MapAnalyzer extends MapApplication {
|
||||
updateProgress(1, 2);
|
||||
updateMessage("Generating graphic\u2026");
|
||||
|
||||
graphic = new WritableImage(
|
||||
distortionM[0][0].length, distortionM[0].length);
|
||||
PixelWriter writer = graphic.getPixelWriter();
|
||||
for (int y = 0; y < distortionM[0].length; y ++) {
|
||||
int width = distortionM[0][0].length;
|
||||
int height = distortionM[0].length;
|
||||
graphic = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
||||
for (int y = 0; y < height; y ++) {
|
||||
if (isCancelled()) return null;
|
||||
updateProgress(1 + (double)y/distortionM[0].length, 2);
|
||||
for (int x = 0; x < distortionM[0][y].length; x ++) {
|
||||
final double sizeDistort = distortionM[0][y][x], shapeDistort = distortionM[1][y][x];
|
||||
final double sizeContour = Math.round(sizeDistort/(LN_10/10))*LN_10/10; //contour the size by decibels
|
||||
final double shapeContour = Math.round(shapeDistort/(LN_10/20))*LN_10/20; //contour the size by semidecibels
|
||||
for (int x = 0; x < width; x ++) {
|
||||
double sizeDistort = distortionM[0][y][x];
|
||||
double shapeDistort = distortionM[1][y][x];
|
||||
double sizeContour = Math.round(sizeDistort/(LN_10/10))*LN_10/10; //contour the size by decibels
|
||||
double shapeContour = Math.round(shapeDistort/(LN_10/20))*LN_10/20; //contour the size by semidecibels
|
||||
if (Double.isNaN(sizeDistort) || Double.isNaN(shapeDistort)) {
|
||||
writer.setArgb(x, y, 0);
|
||||
graphic.setRGB(x, y, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
final int r, g, b;
|
||||
int r, g, b;
|
||||
if (sizeDistort < 0) { //if compressing
|
||||
r = (int)(255.9*Math.exp(-shapeContour*.6));
|
||||
g = (int)(255.9*Math.exp(-shapeContour*.6)*Math.exp(sizeContour*.6));
|
||||
@ -285,16 +292,27 @@ public class MapAnalyzer extends MapApplication {
|
||||
}
|
||||
|
||||
final int argb = ((((((0xFF)<<8)+r)<<8)+g)<<8)+b;
|
||||
writer.setArgb(x, y, argb);
|
||||
graphic.setRGB(x, y, argb);
|
||||
}
|
||||
}
|
||||
|
||||
if (gratSpacing != 0) { //draw the graticule, if desired
|
||||
if (isCancelled()) return null;
|
||||
updateProgress(-1, 1);
|
||||
updateMessage("Drawing graticule\u2026");
|
||||
ImageUtils.drawSVGPath(
|
||||
proj.drawGraticule(Math.toRadians(gratSpacing), .02,
|
||||
width, height, Math.PI/2, Math.PI, null),
|
||||
GRATICULE_COLOR, GRATICULE_WIDTH, true,
|
||||
(Graphics2D)graphic.getGraphics());
|
||||
}
|
||||
|
||||
return SavableImage.savable(graphic);
|
||||
}
|
||||
|
||||
protected void succeeded() {
|
||||
if (mapDisplay != null)
|
||||
mapDisplay.setImage(graphic);
|
||||
mapDisplay.setImage(SwingFXUtils.toFXImage(graphic, null));
|
||||
|
||||
if (sizeChart != null) {
|
||||
sizeChart.getData().clear();
|
||||
|
||||
@ -23,11 +23,8 @@
|
||||
*/
|
||||
package apps;
|
||||
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@ -36,8 +33,6 @@ import dialogs.MapConfigurationDialog;
|
||||
import image.ImageUtils;
|
||||
import image.PixelMap;
|
||||
import image.SavableImage;
|
||||
import image.SVGMap.Command;
|
||||
import image.SVGMap.Path;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.embed.swing.SwingFXUtils;
|
||||
import javafx.geometry.Insets;
|
||||
@ -78,8 +73,8 @@ public class MapDesignerRaster extends MapApplication {
|
||||
new FileChooser.ExtensionFilter("JPG", "*.jpg"),
|
||||
new FileChooser.ExtensionFilter("GIF", "*.gif") };
|
||||
|
||||
private static final float GRATICULE_WIDTH = 0.5f;
|
||||
private static final Color GRATICULE_COLOR = Color.WHITE;
|
||||
private static final float GRATICULE_WIDTH = 0.5f;
|
||||
|
||||
private Region aspectSelector;
|
||||
private double[] aspect;
|
||||
@ -244,28 +239,11 @@ public class MapDesignerRaster extends MapApplication {
|
||||
if (isCancelled()) return null;
|
||||
updateProgress(-1, 1);
|
||||
updateMessage("Drawing graticule\u2026");
|
||||
|
||||
Graphics2D g = (Graphics2D)theMap.getGraphics();
|
||||
g.setStroke(new BasicStroke(GRATICULE_WIDTH));
|
||||
g.setColor(GRATICULE_COLOR);
|
||||
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
Path svgPath = proj.drawGraticule(
|
||||
Math.toRadians(gratSpacing), .02, width, height, Math.PI/2, Math.PI, aspect);
|
||||
Path2D awtPath = new Path2D.Double(Path2D.WIND_NON_ZERO, svgPath.size());
|
||||
for (Command svgCmd: svgPath) {
|
||||
if (isCancelled()) return null;
|
||||
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();
|
||||
}
|
||||
}
|
||||
g.draw(awtPath);
|
||||
ImageUtils.drawSVGPath(
|
||||
proj.drawGraticule(Math.toRadians(gratSpacing), .02,
|
||||
width, height, Math.PI/2, Math.PI, aspect),
|
||||
GRATICULE_COLOR, GRATICULE_WIDTH, true,
|
||||
(Graphics2D)theMap.getGraphics());
|
||||
}
|
||||
|
||||
return SavableImage.savable(theMap);
|
||||
|
||||
@ -67,11 +67,13 @@ public class MapExplainer extends Application {
|
||||
Polyhedral.LEE_TETRAHEDRAL_RECTANGULAR, Polyhedral.AUTHAGRAPH,
|
||||
Pseudocylindrical.SINUSOIDAL, Pseudocylindrical.MOLLWEIDE, Tobler.TOBLER,
|
||||
Lenticular.HAMMER, Lenticular.AITOFF, Lenticular.VAN_DER_GRINTEN,
|
||||
Arbitrary.ROBINSON, WinkelTripel.WINKEL_TRIPEL, Octohedral.WATERMAN,
|
||||
Misc.PEIRCE_QUINCUNCIAL.transverse(), Snyder.GS50, Misc.TWO_POINT_EQUIDISTANT,
|
||||
Misc.HAMMER_RETROAZIMUTHAL, Misc.FLAT_EARTH },
|
||||
Arbitrary.ROBINSON, WinkelTripel.WINKEL_TRIPEL, Octohedral.CAHILL_KEYES,
|
||||
Octohedral.CAHILL_CONCIALDI, Misc.PEIRCE_QUINCUNCIAL.transverse(), Snyder.GS50,
|
||||
Misc.TWO_POINT_EQUIDISTANT, Misc.HAMMER_RETROAZIMUTHAL, Misc.FLAT_EARTH },
|
||||
{ MyProjections.PSEUDOSTEREOGRAPHIC, Polyhedral.TETRAGRAPH, Polyhedral.AUTHAPOWER,
|
||||
MyProjections.TWO_POINT_EQUALIZED.transverse() } };
|
||||
Polyhedral.ACTUAUTHAGRAPH, MyProjections.TWO_POINT_EQUALIZED.transverse() } };
|
||||
|
||||
private PixelMap inputSkew, inputPole, inputNone, inputEast, inputWest;
|
||||
|
||||
|
||||
public static void main(String[] args) {
|
||||
@ -81,19 +83,39 @@ public class MapExplainer extends Application {
|
||||
|
||||
public void start(Stage stage) throws Exception {
|
||||
new File("images").mkdirs();
|
||||
final PixelMap inputSkew = new PixelMap(new File("input/Tissot-alt2.jpg"));
|
||||
final PixelMap inputPole = new PixelMap(new File("input/Tissot-alt1.jpg"));
|
||||
final PrintStream out = System.out;
|
||||
try {
|
||||
inputSkew = new PixelMap(new File("input/Advanced/Tissot Oblique.jpg"));
|
||||
inputPole = new PixelMap(new File("input/Advanced/Tissot Standard.jpg"));
|
||||
inputEast = new PixelMap(new File("input/Advanced/Tissot Shift East.jpg"));
|
||||
inputWest = new PixelMap(new File("input/Advanced/Tissot Shift West.jpg"));
|
||||
inputNone = new PixelMap(new File("input/Advanced/Tissot Not.jpg"));
|
||||
} catch(IOException e) {
|
||||
e.printStackTrace();
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
final PrintStream out = System.out;
|
||||
for (Projection[] projs: ALL_PROJECTIONS) {
|
||||
out.println("<h1>Map Projections</h1>");
|
||||
|
||||
for (Projection proj: projs) {
|
||||
proj.setParameters(proj.getDefaultParameters());
|
||||
out.println("<h2>"+proj.getName()+"</h2>");
|
||||
|
||||
PixelMap input = inputSkew;
|
||||
if (!proj.hasAspect() || proj == Polyhedral.AUTHAGRAPH)
|
||||
input = inputPole;
|
||||
if (proj == Octohedral.CAHILL_KEYES) //some projections only look good in standard aspect
|
||||
input = inputWest;
|
||||
if (proj == Octohedral.CAHILL_CONCIALDI)
|
||||
input = inputEast;
|
||||
if (proj == Misc.FLAT_EARTH) //or can't be seen with indicatrices
|
||||
input = inputNone;
|
||||
|
||||
Task<SavableImage> task = MapDesignerRaster.calculateTask(
|
||||
IMG_WIDTH, (int)(IMG_WIDTH/proj.getAspectRatio()), 2,
|
||||
proj.hasAspect() ? inputSkew : inputPole, proj, null, false, 0, null);
|
||||
IMG_WIDTH, (int)(IMG_WIDTH/proj.getAspectRatio()), 2, input, proj, null,
|
||||
false, 0, null);
|
||||
task.setOnSucceeded((event) -> {
|
||||
try {
|
||||
task.getValue().save(new File("images/"+proj+".gif"));
|
||||
@ -108,6 +130,13 @@ public class MapExplainer extends Application {
|
||||
out.println(" <p>"+proj.getDescription()+"</p>");
|
||||
|
||||
out.println(" <dl>");
|
||||
out.println(" <dt>Rating: </dt>");
|
||||
out.print(" <dd>");
|
||||
for (int i = 0; i < proj.getRating(); i ++)
|
||||
out.print("★");
|
||||
for (int i = 0; i < 4-proj.getRating(); i ++)
|
||||
out.print("☆");
|
||||
out.println("</dt>");
|
||||
out.println(" <dt>Geometry: </dt>");
|
||||
out.println(" <dd>"+proj.getType().getName()+"</dd>");
|
||||
out.println(" <dt>Property: </dt>");
|
||||
|
||||
@ -81,7 +81,7 @@ public class MapPlotter extends Application {
|
||||
private static final Projection[] TETRAHEDRAL = { Polyhedral.LEE_TETRAHEDRAL_RECTANGULAR,
|
||||
Polyhedral.AUTHAGRAPH, Polyhedral.ACTUAUTHAGRAPH };
|
||||
private static final Projection[] CHEATY = { Pseudocylindrical.LEMONS,
|
||||
Octohedral.KEYES_BASIC_M, Polyhedral.DYMAXION };
|
||||
Octohedral.KEYES_BASIC_M, Polyhedral.DYMAXION, Octohedral.CAHILL_CONCIALDI };
|
||||
private static final Projection[] OTHER = { Misc.PEIRCE_QUINCUNCIAL };
|
||||
|
||||
|
||||
|
||||
@ -111,6 +111,6 @@ public class MapProducer extends Application {
|
||||
}
|
||||
}
|
||||
|
||||
stage.close();
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,6 +23,15 @@
|
||||
*/
|
||||
package image;
|
||||
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.geom.Path2D;
|
||||
|
||||
import image.SVGMap.Command;
|
||||
import image.SVGMap.Path;
|
||||
|
||||
/**
|
||||
* A collection of (currently just one) methods for dealing with images and coordinates
|
||||
*
|
||||
@ -34,6 +43,7 @@ public class ImageUtils {
|
||||
return blend(colors, 2.2);
|
||||
}
|
||||
|
||||
|
||||
public static final int blend(int[] colors, double gamma) {
|
||||
int a_tot = 0;
|
||||
int r_tot = 0;
|
||||
@ -53,4 +63,30 @@ public class ImageUtils {
|
||||
((int)Math.pow(g_tot/a_tot, 1/gamma) << 8) +
|
||||
((int)Math.pow(b_tot/a_tot, 1/gamma) << 0);
|
||||
}
|
||||
|
||||
|
||||
public static final void drawSVGPath(Path path, Color stroke, float strokeWidth, boolean antialias, Graphics2D g) {
|
||||
g.setStroke(new BasicStroke(strokeWidth));
|
||||
g.setColor(stroke);
|
||||
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
|
||||
antialias ? RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF);
|
||||
drawSVGPath(path, g);
|
||||
}
|
||||
|
||||
public static final void drawSVGPath(Path path, Graphics2D g) {
|
||||
Path2D awtPath = new Path2D.Double(Path2D.WIND_NON_ZERO, path.size());
|
||||
for (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();
|
||||
}
|
||||
}
|
||||
g.draw(awtPath);
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,7 +51,9 @@ public class Oblique extends Projection {
|
||||
|
||||
@Override
|
||||
public double[] inverse(double x, double y) {
|
||||
return obliquifyPlnr(base.inverse(x, y), axis);
|
||||
double[] coords = base.inverse(x, y);
|
||||
if (coords == null) return null;
|
||||
return obliquifyPlnr(coords, axis);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -214,7 +214,7 @@ public class Octohedral {
|
||||
double xMj = Math.sin(tht0)*(x-x0) - Math.cos(tht0)*(y-y0);
|
||||
double yMj = Math.cos(tht0)*(x-x0) + Math.sin(tht0)*(y-y0);
|
||||
|
||||
if (Math.abs(yMj) > Math.min(xMj, 2*size-xMj)/Math.sqrt(3)) return null; //restrict to one rhombus
|
||||
if (Math.abs(yMj) > Math.min(xMj, 2*size-xMj)/Math.sqrt(3)+1e-12) return null; //restrict to one rhombus (plus a little, to account for roundoff)
|
||||
double[] coords = this.faceInverse(Math.min(xMj, 2*size-xMj), Math.abs(yMj));
|
||||
if (coords == null) return null;
|
||||
double lat = coords[0], lon = coords[1];
|
||||
@ -356,7 +356,7 @@ public class Octohedral {
|
||||
if (y > -.5) return null; //more empty space
|
||||
return new double[] { sign*Math.sqrt(3), .5, 0, sign*Math.PI }; //the outer wings
|
||||
}
|
||||
else if (y <= -1.5 && Math.abs(x) > 1) {
|
||||
else if (y <= -1.5 && Math.abs(x) >= 1) {
|
||||
if (y+2.5 >= Math.abs(x)/Math.sqrt(3))
|
||||
return new double[] { 0, -2.5, Math.signum(x)*2*Math.PI/3, 0,
|
||||
-Math.PI/2, -Math.PI/4 }; //the bottoms of the wings
|
||||
|
||||
@ -436,10 +436,12 @@ public class Polyhedral {
|
||||
}
|
||||
},
|
||||
AUTHAGRAPH(3, 6, 4*Math.sqrt(3), 3, new double[][] { // |\/\/`| arrangement, vertex-centred
|
||||
{-ASIN_ONE_THD, Math.PI, Math.PI, 0, -2*Math.sqrt(3)-.6096, 1.5 },
|
||||
{-ASIN_ONE_THD, -Math.PI/3, Math.PI/3, 0, -Math.sqrt(3)-.6096, -1.5 },
|
||||
{ Math.PI/2, 0, Math.PI, 0, 0-.6096, 1.5 },
|
||||
{-ASIN_ONE_THD, Math.PI/3, -Math.PI/3, 0, Math.sqrt(3)-.6096, -1.5 },
|
||||
{-ASIN_ONE_THD, Math.PI, Math.PI, 0, 2*Math.sqrt(3)-.6096, 1.5 }}) {
|
||||
{-ASIN_ONE_THD, Math.PI, Math.PI, 0, 2*Math.sqrt(3)-.6096, 1.5 },
|
||||
{-ASIN_ONE_THD, -Math.PI/3, Math.PI/3, 0, 3*Math.sqrt(3)-.6096, -1.5 }}) {
|
||||
@Override public double[] rotateOOB(double x, double y, double xCen, double yCen) {
|
||||
if (Math.abs(y) > height/2) {
|
||||
x = 2*xCen - x;
|
||||
|
||||
@ -549,6 +549,10 @@ public abstract class Projection {
|
||||
return this.property;
|
||||
}
|
||||
|
||||
public final int getRating() {
|
||||
return this.rating;
|
||||
}
|
||||
|
||||
public final double getWidth() {
|
||||
return this.width;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user