diff --git a/MapAnalyzer.jar b/MapAnalyzer.jar index ac4fe0e..874bb90 100644 Binary files a/MapAnalyzer.jar and b/MapAnalyzer.jar differ diff --git a/MapDesigner - Raster.jar b/MapDesigner - Raster.jar index d56e7b5..7cfad9b 100644 Binary files a/MapDesigner - Raster.jar and b/MapDesigner - Raster.jar differ diff --git a/MapDesigner - Vector.jar b/MapDesigner - Vector.jar index 5ad2979..6b6830c 100644 Binary files a/MapDesigner - Vector.jar and b/MapDesigner - Vector.jar differ diff --git a/input/squares.jpg b/input/squares.jpg index 2dc454e..fa4cccb 100644 Binary files a/input/squares.jpg and b/input/squares.jpg differ diff --git a/output/Compass Rose.jpg b/output/Compass Rose.jpg new file mode 100644 index 0000000..2798517 Binary files /dev/null and b/output/Compass Rose.jpg differ diff --git a/output/Fancy Tetrafillet.jpg b/output/Fancy Tetrafillet.jpg new file mode 100644 index 0000000..443ed4a Binary files /dev/null and b/output/Fancy Tetrafillet.jpg differ diff --git a/output/parameters.txt b/output/parameters.txt index 4471c26..e69de29 100644 --- a/output/parameters.txt +++ b/output/parameters.txt @@ -1,13 +0,0 @@ -We got the best Tobler projections using: - t0=0.25022057521781305; t1=4.49997342476493; (4.0724237830145623E-4, 0.5079777001726435) - t0=0.2505753087520266; t1=4.499956546953281; (4.064604422025349E-4, 0.5079765807787102) - t0=0.2508838353035556; t1=4.4999316499660384; (4.0573108482153483E-4, 0.5079755362982413) - t0=0.2508314520322239; t1=4.499728417979239; (4.0450171936633287E-4, 0.5079758925954643) - t0=0.2500693069768243; t1=4.499328429617362; (4.0750703627578564E-4, 0.5079785011865504) - t0=0.2600667982838587; t1=4.5008562930364775; (3.8249122842821693E-4, 0.5079640977617719) - t0=0.2558237748512546; t1=4.4998740643492035; (3.9365452439070936E-4, 0.5079694226751023) - t0=0.24889877394521065; t1=4.498227902031633; (4.100009439557956E-4, 0.507979411007307) - t0=0.259316034943474; t1=4.4995552660836395; (3.8333968917550053E-4, 0.5079662063081046) - t0=0.2759279673286983; t1=4.5009128718891915; (3.3514923608814645E-4, 0.5079628635806102) - t0=0.2554293245455028; t1=4.492372958335096; (3.945025957463458E-4, 0.5079708674912415) - diff --git a/src/apps/MapAnalyzer.java b/src/apps/MapAnalyzer.java index 8cdcecf..97af8dc 100644 --- a/src/apps/MapAnalyzer.java +++ b/src/apps/MapAnalyzer.java @@ -90,7 +90,7 @@ public class MapAnalyzer extends MapApplication { Projection.ALBERS, Projection.LEE, Projection.TETRAGRAPH, Projection.SINUSOIDAL, Projection.MOLLWEIDE, Projection.HAMMER, Projection.TOBLER, Projection.AITOFF, Projection.VAN_DER_GRINTEN, Projection.ROBINSON, Projection.WINKEL_TRIPEL, Projection.PEIRCE_QUINCUNCIAL, Projection.GUYOU, Projection.MAGNIFIER, - Projection.EXPERIMENT, Projection.HYPERELLIPOWER, Projection.TETRAPOWER, Projection.TETRAFILLET }; + Projection.EXPERIMENT, Projection.HYPERELLIPOWER, Projection.TETRAPOWER, Projection.TETRAFILLET, Projection.TETRACHAMFER }; private Button updateBtn; diff --git a/src/apps/MapApplication.java b/src/apps/MapApplication.java index 43ca13e..4fc1b2d 100644 --- a/src/apps/MapApplication.java +++ b/src/apps/MapApplication.java @@ -113,7 +113,7 @@ public abstract class MapApplication extends Application { final Label label = new Label("Current input:"); final Text inputLabel = new Text("None"); - final FileChooser inputChooser = new FileChooser(); + final FileChooser inputChooser = new FileChooser(); //TODO: remember last directory inputChooser.setInitialDirectory(new File("input")); inputChooser.setTitle("Choose an input map"); inputChooser.getExtensionFilters().addAll(allowedExtensions); @@ -376,7 +376,7 @@ public abstract class MapApplication extends Application { Slider[] sliders, Spinner[] spinners) { if (presetName.equals("Antipode")) { sliders[0].setValue(-sliders[0].getValue()); - sliders[1].setValue((sliders[1].getValue()+180)%360); + sliders[1].setValue((sliders[1].getValue()+360)%360-180); sliders[2].setValue(-sliders[2].getValue()); } else if (presetName.equals("Random")) { diff --git a/src/apps/MapDesignerRaster.java b/src/apps/MapDesignerRaster.java index 578dd76..cb503f4 100644 --- a/src/apps/MapDesignerRaster.java +++ b/src/apps/MapDesignerRaster.java @@ -72,7 +72,7 @@ public class MapDesignerRaster extends MapApplication { Projection.ALBERS, Projection.LEE, Projection.TETRAGRAPH, Projection.AUTHAGRAPH, Projection.SINUSOIDAL, Projection.MOLLWEIDE, Projection.TOBLER, Projection.AITOFF, Projection.VAN_DER_GRINTEN, Projection.ROBINSON, Projection.WINKEL_TRIPEL, Projection.PEIRCE_QUINCUNCIAL, Projection.GUYOU, Projection.LEMONS, - Projection.MAGNIFIER, Projection.EXPERIMENT }; + Projection.MAGNIFIER, Projection.EXPERIMENT, Projection.HYPERELLIPOWER, Projection.TETRAPOWER, Projection.TETRAFILLET, Projection.TETRACHAMFER }; private Button updateBtn, saveMapBtn; diff --git a/src/apps/MapDesignerVector.java b/src/apps/MapDesignerVector.java index 0684bb3..6586596 100644 --- a/src/apps/MapDesignerVector.java +++ b/src/apps/MapDesignerVector.java @@ -69,7 +69,7 @@ public class MapDesignerVector extends MapApplication { Projection.ALBERS, Projection.LEE, Projection.TETRAGRAPH, Projection.SINUSOIDAL, Projection.MOLLWEIDE, Projection.TOBLER, Projection.AITOFF, Projection.VAN_DER_GRINTEN, Projection.ROBINSON, Projection.WINKEL_TRIPEL, Projection.PEIRCE_QUINCUNCIAL, Projection.GUYOU, Projection.MAGNIFIER, - Projection.EXPERIMENT, Projection.HYPERELLIPOWER, Projection.TETRAPOWER, Projection.TETRAFILLET }; + Projection.EXPERIMENT, Projection.HYPERELLIPOWER, Projection.TETRAPOWER, Projection.TETRAFILLET, Projection.TETRACHAMFER }; private static final int DEF_MAX_VTX = 5000; diff --git a/src/apps/MapOptimizer.java b/src/apps/MapOptimizer.java index 80621e1..f14b7e1 100644 --- a/src/apps/MapOptimizer.java +++ b/src/apps/MapOptimizer.java @@ -73,10 +73,10 @@ public class MapOptimizer extends Application { chart.getData().add(analyzeAll(globe, EXISTING_PROJECTIONS)); // chart.getData().add(optimizeFamily(Projection.WINKEL_TRIPEL, globe, log)); - chart.getData().add(optimizeFamily(Projection.TOBLER, globe, log)); +// chart.getData().add(optimizeFamily(Projection.TOBLER, globe, log)); // chart.getData().add(optimizeFamily(Projection.HYPERELLIPOWER, globe, log)); // chart.getData().add(optimizeFamily(Projection.TETRAPOWER, globe, log)); -// chart.getData().add(optimizeFamily(Projection.TETRAFILLET, globe, log)); + chart.getData().add(optimizeFamily(Projection.TETRAFILLET, globe, log)); System.out.println("Total time elapsed: "+ (System.currentTimeMillis()-startTime)/1000.+"s"); diff --git a/src/dialogs/MapConfigurationDialog.java b/src/dialogs/MapConfigurationDialog.java index d14b461..b0bcc70 100644 --- a/src/dialogs/MapConfigurationDialog.java +++ b/src/dialogs/MapConfigurationDialog.java @@ -69,7 +69,7 @@ public class MapConfigurationDialog extends Dialog { this.widthBox.valueProperty().addListener((observable, prev, now) -> { // link the Spinners if (realEdit && maintainRatio.isSelected()) { realEdit = false; - int prefHeight = (int)Math.floor(widthBox.getValue()/defaultRatio); + int prefHeight = (int)Math.round(widthBox.getValue()/defaultRatio); heightBox.getValueFactory().setValue(prefHeight); realEdit = true; } @@ -77,7 +77,7 @@ public class MapConfigurationDialog extends Dialog { this.heightBox.valueProperty().addListener((observable, prev, now) -> { if (realEdit && maintainRatio.isSelected()) { realEdit = false; - int prefWidth = (int)Math.ceil(heightBox.getValue()*defaultRatio); + int prefWidth = (int)Math.round(heightBox.getValue()*defaultRatio); widthBox.getValueFactory().setValue(prefWidth); realEdit = true; } @@ -87,7 +87,7 @@ public class MapConfigurationDialog extends Dialog { if (!now) widthBox.increment(0); }); this.heightBox.focusedProperty().addListener((observable, prev,now) -> { //values when focus is lost - if (!now) heightBox.increment(); + if (!now) heightBox.increment(0); }); ObservableList items = FXCollections.observableArrayList( diff --git a/src/maps/Projection.java b/src/maps/Projection.java index adea6d2..3c630e0 100644 --- a/src/maps/Projection.java +++ b/src/maps/Projection.java @@ -667,12 +667,21 @@ public enum Projection { new double[][] {{1,5,3.7308},{.5,2.,1.2027},{.5,2.,1.1443}}) { public double[] project(double lat, double lon, double[] params) { final double k = params[0], n = params[1], a = params[2]; + final double ynorm = (1-Math.pow(1-Math.abs(lat/(Math.PI/2)), n)); return new double[] { - Math.pow(1 - Math.pow(Math.abs(lat/(Math.PI/2)), k),1/k)*lon, - (1-Math.pow(1-Math.abs(lat/(Math.PI/2)), n))/Math.sqrt(n)*Math.signum(lat)*Math.PI/2*a}; + Math.pow(1 - Math.pow(ynorm, k),1/k)*lon, + ynorm*Math.PI/2/Math.sqrt(n)*a*Math.signum(lat) + }; } public double[] inverse(double x, double y, double[] params) { - return null; + final double k = params[0], n = params[1]; + return new double[] { + (1 - Math.pow(1-Math.abs(y), 1/n))*Math.PI/2*Math.signum(y), + x/Math.pow(1 - Math.pow(Math.abs(y),k),1/k)*Math.PI }; + } + public double getAspectRatio(double[] params) { + final double n = params[1], a = params[2]; + return 2*Math.sqrt(n)/a; } }, @@ -687,19 +696,34 @@ public enum Projection { final double thtP = Math.PI/3*(1 - Math.pow(1-Math.abs(tht)/(Math.PI/2),k1))/(1 - 1/Math.pow(3,k1))*Math.signum(tht); final double kRad = k3*Math.abs(thtP)/(Math.PI/3) + k2*(1-Math.abs(thtP)/(Math.PI/3)); final double rmax = .5/Math.cos(thtP); //the max normalized radius of this triangle (in the plane) - final double rtgf = Math.atan(1/Math.tan(coordR[0])*Math.cos(tht))/Math.atan(Math.sqrt(2))*rmax; //normalized tetragraph radius + final double rtgf = Math.atan(1/Math.tan(coordR[0])*Math.cos(tht))/Math.atan(Math.sqrt(2))*rmax; return new double[] { (1 - Math.pow(1-rtgf,kRad))/(1 - Math.pow(1-rmax,kRad))*rmax*2*Math.PI/3, thtP + t0 }; }); } public double[] inverse(double x, double y, double[] params) { - return null; + final double k1 = params[0], k2 = params[1], k3 = params[2]; + final double[] doubles = tetrahedralProjectionInverse(x,y); + final double[] faceCenter = { doubles[0], doubles[1], doubles[2] }; + final double tht = doubles[3], xp = doubles[4], yp = doubles[5]; + final double R = Math.hypot(xp, yp)*Math.sqrt(3)/2; + final double t = Math.atan2(yp, xp) + tht; + final double t0 = Math.floor((t+Math.PI/2)/(2*Math.PI/3)+0.5)*(2*Math.PI/3) - Math.PI/2; + final double thtP = t-t0; + final double lamS = (1-Math.pow(1-Math.abs(thtP)*(1-1/Math.pow(3,k1))/(Math.PI/3), 1/k1))*Math.PI/2*Math.signum(thtP); + final double kRad = k3*Math.abs(thtP)/(Math.PI/3) + k2*(1-Math.abs(thtP)/(Math.PI/3)); + final double rmax = .5/Math.cos(thtP); //the max normalized radius of this triangle (in the plane) + final double rtgf = 1-Math.pow(1-R/rmax*(1-Math.pow(Math.abs(1-rmax), kRad)), 1/kRad); //normalized tetragraph radius + double[] triCoords = { + Math.atan(Math.cos(lamS)/Math.tan(rtgf/rmax*Math.atan(Math.sqrt(2)))), + Math.PI/2 + t0 + lamS }; + return obliquifyPlnr(triCoords, faceCenter); } }, TETRAFILLET("Tetrafillet", "A parametric projection that I'm still testing", - 2., 0b1111, "other", "compromise", new String[] {"k1","k2","k3"}, + Math.sqrt(3), 0b1111, "other", "compromise", new String[] {"k1","k2","k3"}, new double[][] {{.25,4.,1.1598},{.25,4.,.36295},{.25,4.,1.9553}}) { public double[] project(double lat, double lon, double[] params) { final double k1 = params[0], k2 = params[1], k3 = params[2]; @@ -708,7 +732,9 @@ public enum Projection { final double tht = coordR[1] - t0; final double thtP = Math.PI/3*(1 - Math.pow(1-Math.abs(tht)/(Math.PI/2),k1))/(1 - 1/Math.pow(3,k1))*Math.signum(tht); final double kRad = k3*Math.abs(thtP)/(Math.PI/3) + k2*(1-Math.abs(thtP)/(Math.PI/3)); - final double rmax = 1/2. + 1/4.*Math.pow(thtP,2) + 5/48.*Math.pow(thtP,4) - .132621*Math.pow(thtP,6); //the max normalized radius of this triangle (in the plane) + final double rmax; //the max normalized radius of this triangle (in the plane) + if (Math.abs(thtP) < .70123892) rmax = .5/Math.cos(thtP); + else rmax = .75 - 1.5972774*Math.pow(Math.PI/3-Math.abs(thtP),2)/2; final double rtgf = Math.atan(1/Math.tan(coordR[0])*Math.cos(tht))/Math.atan(Math.sqrt(2))*rmax; //normalized tetragraph radius return new double[] { (1 - Math.pow(1-rtgf,kRad))/(1 - Math.pow(1-rmax,kRad))*rmax*2*Math.PI/3, @@ -717,7 +743,64 @@ public enum Projection { }); } public double[] inverse(double x, double y, double[] params) { - return null; + final double k1 = params[0], k2 = params[1], k3 = params[2]; + final double[] doubles = tetrahedralProjectionInverse(x,y); + final double[] faceCenter = { doubles[0], doubles[1], doubles[2] }; + final double tht = doubles[3], xp = doubles[4], yp = doubles[5]; + final double R = Math.hypot(xp, yp)*Math.sqrt(3)/2; + final double t = Math.atan2(yp, xp) + tht; + final double t0 = Math.floor((t+Math.PI/2)/(2*Math.PI/3)+0.5)*(2*Math.PI/3) - Math.PI/2; + final double thtP = t-t0; + final double lamS = (1-Math.pow(1-Math.abs(thtP)*(1-1/Math.pow(3,k1))/(Math.PI/3), 1/k1))*Math.PI/2*Math.signum(thtP); + final double kRad = k3*Math.abs(thtP)/(Math.PI/3) + k2*(1-Math.abs(thtP)/(Math.PI/3)); + final double rmax; //the max normalized radius of this triangle (in the plane) + if (Math.abs(thtP) < .70123892) rmax = .5/Math.cos(thtP); + else rmax = .75 - 1.5972774*Math.pow(Math.PI/3-Math.abs(thtP),2)/2; + final double rtgf = 1-Math.pow(1-R/rmax*(1-Math.pow(Math.abs(1-rmax), kRad)), 1/kRad); //normalized tetragraph radius + if (R > rmax) return null; + double[] triCoords = { + Math.atan(Math.cos(lamS)/Math.tan(rtgf/rmax*Math.atan(Math.sqrt(2)))), + Math.PI/2 + t0 + lamS }; + return obliquifyPlnr(triCoords, faceCenter); + } + }, + + TETRACHAMFER("Tetrachamfer", "A parametric projection that I'm still testing", + Math.sqrt(3), 0b1111, "other", "compromise", new String[] {"k1","k2","k3"}, + new double[][] {{.25,4.,1.1598},{.25,4.,.36295},{.25,4.,1.9553}}) { + public double[] project(double lat, double lon, double[] params) { + final double k1 = params[0], k2 = params[1], k3 = params[2]; + return tetrahedralProjectionForward(lat, lon, (coordR) -> { + final double t0 = Math.floor(coordR[1]/(2*Math.PI/3))*(2*Math.PI/3) + Math.PI/3; + final double tht = coordR[1] - t0; + final double thtP = Math.PI/3*(1 - Math.pow(1-Math.abs(tht)/(Math.PI/2),k1))/(1 - 1/Math.pow(3,k1))*Math.signum(tht); + final double kRad = k3*Math.abs(thtP)/(Math.PI/3) + k2*(1-Math.abs(thtP)/(Math.PI/3)); + final double rmax = Math.min(.5/Math.cos(thtP), .75/Math.cos(Math.PI/3-Math.abs(thtP))); //the max normalized radius of this triangle (in the plane) + final double rtgf = Math.atan(1/Math.tan(coordR[0])*Math.cos(tht))/Math.atan(Math.sqrt(2))*rmax; //normalized tetragraph radius + return new double[] { + (1 - Math.pow(1-rtgf,kRad))/(1 - Math.pow(1-rmax,kRad))*rmax*2*Math.PI/3, + thtP + t0 + }; + }); + } + public double[] inverse(double x, double y, double[] params) { + final double k1 = params[0], k2 = params[1], k3 = params[2]; + final double[] doubles = tetrahedralProjectionInverse(x,y); + final double[] faceCenter = { doubles[0], doubles[1], doubles[2] }; + final double tht = doubles[3], xp = doubles[4], yp = doubles[5]; + final double R = Math.hypot(xp, yp)*Math.sqrt(3)/2; + final double t = Math.atan2(yp, xp) + tht; + final double t0 = Math.floor((t+Math.PI/2)/(2*Math.PI/3)+0.5)*(2*Math.PI/3) - Math.PI/2; + final double thtP = t-t0; + final double lamS = (1-Math.pow(1-Math.abs(thtP)*(1-1/Math.pow(3,k1))/(Math.PI/3), 1/k1))*Math.PI/2*Math.signum(thtP); + final double kRad = k3*Math.abs(thtP)/(Math.PI/3) + k2*(1-Math.abs(thtP)/(Math.PI/3)); + final double rmax = Math.min(.5/Math.cos(thtP), .75/Math.cos(Math.PI/3-Math.abs(thtP))); //the max normalized radius of this triangle (in the plane) + final double rtgf = 1-Math.pow(1-R/rmax*(1-Math.pow(Math.abs(1-rmax), kRad)), 1/kRad); //normalized tetragraph radius + if (R > rmax) return null; + double[] triCoords = { + Math.atan(Math.cos(lamS)/Math.tan(rtgf/rmax*Math.atan(Math.sqrt(2)))), + Math.PI/2 + t0 + lamS }; + return obliquifyPlnr(triCoords, faceCenter); } }; @@ -988,26 +1071,22 @@ public enum Projection { private static double[] tetrahedralProjectionInverse(double x, double y) { // a function to help with tetrahedral projections - if (y < x-1) { + if (y < x-1) return new double[] { -Math.PI/2, 0, 0, -Math.PI/2, Math.sqrt(3)*(x-2/3.), y+1 }; - } - else if (y < -x-1) { + else if (y < -x-1) return new double[] { -Math.PI/2, 0, 0, Math.PI/2, Math.sqrt(3)*(x+2/3.), y+1 }; - } - else if (y > -x+1) { + else if (y > -x+1) return new double[] { Math.PI/2-Math.asin(Math.sqrt(8)/3), Math.PI, 0, -Math.PI/2, Math.sqrt(3)*(x-2/3.), y-1 }; - } - else if (y > x+1) { + else if (y > x+1) return new double[] { Math.PI/2-Math.asin(Math.sqrt(8)/3), Math.PI, 0, Math.PI/2, Math.sqrt(3)*(x+2/3.), y-1 }; - } else if (x < 0) return new double[] { Math.PI/2-Math.asin(Math.sqrt(8)/3), -Math.PI/3, 0, @@ -1078,8 +1157,7 @@ public enum Projection { lonf = lon0 - Math.acos(innerFunc); - double thtf = 0; - thtf += pole[2]; + double thtf = pole[2]; double[] output = {latf, lonf, thtf}; return output; @@ -1150,5 +1228,26 @@ public enum Projection { public String getProperty() { return this.property; } + + + public static final void main(String[] args) { + double[] pole = {47, -173, 138}; + System.out.println("The pole is at "+Arrays.toString(pole)); + for (int i = 0; i < 3; i ++) + pole[i] = Math.toRadians(pole[i]); + for (double[] ref: new double[][] {{-Math.PI/2, 0, Math.PI/3}, + {Math.asin(1/3.0), Math.PI, Math.PI/3}, + {Math.asin(1/3.0), Math.PI/3, Math.PI/3}, + {Math.asin(1/3.0), -Math.PI/3, -Math.PI/3}}) { + ref[0] *= -1; + ref[1] = (ref[1]+2*Math.PI)%(2*Math.PI)-Math.PI; + ref[2] *= -1; + System.out.println("The relative singularity is at "+Arrays.toString(ref)); + final double[] coords = obliquifyPlnr(ref, pole); + for (int i = 0; i < 3; i ++) + coords[i] = Math.toDegrees(coords[i]); + System.out.println("That comes out to "+Arrays.toString(coords)); + } + } }