From a08d57d0d41815ee62a91bf4ef363473448ce020 Mon Sep 17 00:00:00 2001 From: jkunimune Date: Sat, 8 Jul 2017 19:12:15 -0400 Subject: [PATCH] It's _still_ not precise enough! I spent a buttload of time working on the Tobler projection. It is now unbearably slow and still has an average distortion of, like .04. It's okay, though. I've got a plan to fix both of those problems. It involves ordinary differential equations and inheritance. Nyeh heh heh heh heh! --- output/graph.png | Bin 13505 -> 26168 bytes src/apps/MapAnalyzer.java | 22 ++--- src/apps/MapApplication.java | 12 ++- src/apps/MapDesignerRaster.java | 98 ++++++++++------------ src/apps/MapDesignerVector.java | 27 ++++--- src/maps/Projection.java | 75 ++++++++++++----- src/maps/Tobler.java | 51 +++++++----- src/util/NumericalAnalysis.java | 139 +++++++++++++++++++++++++------- 8 files changed, 276 insertions(+), 148 deletions(-) diff --git a/output/graph.png b/output/graph.png index f6da34903a5bef5a37ed989c3964d71256963d6f..fcb5bb596ceaaf133269653e5b47e9b6eb218da5 100644 GIT binary patch literal 26168 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

1eo zNR>Wf`CRX;b#ZV-fUox83U5=bprri?bPgs0Dofc5M`FFyu>oae6mD^f{0r5xA=KX2N<~+yS=IGsQixTwb7kezH`05FT{#kyL|;H-z5-=rR7TjXPI~DZKuKRSR)VI#E@Im|}RN7XnLE-YcXDv%ulH@LF-)>tnYasga{A zu(|p%CPL=ZM(gPMgh}YyHacMYbYFJEa%IVhpE#U`IDzyO=XYyiZC0N@oF_tnsgKaR zrx|iRqxmygaKFQQ;BL;XWJuBO>)6Kf#E+bDWshl=EJA1?d3xm@D~yMO$I58Z&z9t~P`1O_O9 zfCx7}JRm^{QzxR~HUj2-lG4IOBNa8or)lVKhc@^$j z(e&L?-x1-;?g7-Af5S_Cq$LHAVTn&D+>y+pwyT$d$_35ahlp3D zNFRvq?FG*Ei*I=QqTDy32piP;em*qyTfe(y+vv0jIwkU5f} zRQ1QU?b9&9KHKB0nyRfC57QfSQ}BIptVx+?aW($p_mbzBXTfhGF-%iV$JHV@LIp*izBmHN(@Z&hR&@^g=}Sa^ z=$hRaj)3Ihgc~!f=Spuj*GUt$Vaj_Cb*6b}4$pbkdk5mND`;4mr&6xYM82r2H3xl) z(N&p){>TVlv|keNlwA+DX+%dV1*6Z+^Qg;sa)jkv6 z8Z1P6V4{fkWY@4dG4*n%ky$UaOesLw7=O9*y2$lt$;;=%5Hk}RwcmR?GppwVG~7&V zZeR7T%zOU{;@8wJ-{%{D>NsCpcBC_Gccea1EC_e&OV1Y!KEe0cWD066))JYgY-AHC za`h0$M0kiD4XR=jF~E3#TYg1%1#u`V-@J-{KO+1o$C94K>03X*Dtbr;bhd*RVaoYl zSj#s%lLbG*A5X37YroIJEAG&HA z-I{+_F;Ida2?v{F839NXUIItVgqA9^#QNQ(4gN%QTkFJ78Ln>aA2{TM4=o2S-geqm#6=X g;9pX*v%b4pHX|)ebMZpGK)3@zbPTVQfp0(kKV#Y;asU7T diff --git a/src/apps/MapAnalyzer.java b/src/apps/MapAnalyzer.java index 85c6847..0d514bf 100644 --- a/src/apps/MapAnalyzer.java +++ b/src/apps/MapAnalyzer.java @@ -75,11 +75,11 @@ public class MapAnalyzer extends MapApplication { private static final int CHART_WIDTH = 400; private static final int ROUGH_SAMP_NUM = 250; private static final int FINE_SAMP_NUM = 1000; - private static final double GLOBE_RES = .02; + private static final double GLOBE_RES = .01; private static final FileChooser.ExtensionFilter[] RASTER_TYPES = { - new FileChooser.ExtensionFilter("PNG", "*.png"), - new FileChooser.ExtensionFilter("JPG", "*.jpg","*.jpeg","*.jpe","*.jfif") }; + new FileChooser.ExtensionFilter("JPG", "*.jpg","*.jpeg","*.jpe","*.jfif"), + new FileChooser.ExtensionFilter("PNG", "*.png") }; private static final Projection[] PROJ_ARR = { Projection.MERCATOR, Projection.PLATE_CARREE, Projection.GALL_PETERS, Projection.HOBO_DYER, Projection.BEHRMANN, Projection.LAMBERT_CYLIND, Projection.E_A_CYLIND, @@ -122,9 +122,9 @@ public class MapAnalyzer extends MapApplication { this.updateBtn = buildUpdateButton(this::calculateAndUpdate); this.updateBtn.setText("Calculate"); //I don't need to follow your darn conventions! final Button saveMapBtn = buildSaveButton(true, "map", RASTER_TYPES, - Procedure.NONE, this::calculateAndSaveMap); + RASTER_TYPES[1], Procedure.NONE, this::calculateAndSaveMap); final Button savePltBtn = buildSaveButton(true, "plots", RASTER_TYPES, - Procedure.NONE, this::calculateAndSavePlot); + RASTER_TYPES[1], Procedure.NONE, this::calculateAndSavePlot); final HBox buttons = new HBox(5, updateBtn, saveMapBtn, savePltBtn); buttons.setAlignment(Pos.CENTER); @@ -212,7 +212,7 @@ public class MapAnalyzer extends MapApplication { sizeChart.getData().add(histogram(distortionG[0], -2, 2, 14, Math::exp)); shapeChart.getData().add(histogram(distortionG[1], - 0, 2.8, 14, (x) -> 1/(x*x/2+1-x*Math.sqrt(x*x/4+1)))); + 0, 2, 14, (x) -> 1/(x*x/2+1-x*Math.sqrt(x*x/4+1)))); avgSizeDistort.setText(format(Math2.stdDev(distortionG[0]))); avgShapeDistort.setText(format(Math2.mean(distortionG[1]))); @@ -275,14 +275,14 @@ public class MapAnalyzer extends MapApplication { final int r, g, b; if (sizeDistort < 0) { //if compressing - r = (int)(255.9*Math.exp(-shapeDistort/1.5)); - g = (int)(255.9*Math.exp(-shapeDistort/1.5)*Math.exp(sizeDistort/1.5)); + r = (int)(255.9*Math.exp(-shapeDistort*.6)); + g = (int)(255.9*Math.exp(-shapeDistort*.6)*Math.exp(sizeDistort*.6)); b = g; } else { //if dilating - r = (int)(255.9*Math.exp(-shapeDistort/1.5)*Math.exp(-sizeDistort/1.5)); - g = r; - b = (int)(255.9*Math.exp(-shapeDistort/1.5)); + r = (int)(255.9*Math.exp(-shapeDistort*.6)*Math.exp(-sizeDistort*.6)); + g = r; //I find .6 to ba a rather visually pleasing sensitivity + b = (int)(255.9*Math.exp(-shapeDistort*.6)); } final int argb = ((((((0xFF)<<8)+r)<<8)+g)<<8)+b; diff --git a/src/apps/MapApplication.java b/src/apps/MapApplication.java index 7f353af..ac29075 100644 --- a/src/apps/MapApplication.java +++ b/src/apps/MapApplication.java @@ -64,9 +64,9 @@ public abstract class MapApplication extends Application { private static final String[] ASPECT_NAMES = { "Standard", "Transverse", "Center of Mass", "Jerusalem", "Point Nemo", "Longest Line", "Longest Line Transverse", "Cylindrical", "Conic", "Tetrahedral", "Quincuncial", "Antipode", "Random" }; private static final double[][] ASPECT_VALS = { - { 90., 0., 29.9792, 31.7833, 48.8767, -28.5217,-46.4883,-35., -10., 47., 60. }, - { 0., 0., 31.1344, 35.2160, 56.6067, 141.451, 16.5305,-13.6064, 65.,-173., -6. }, - { 0., 0.,-32., -35., -45., 161.5, 137., 145., -150., 138.,-10. } }; + { 90., 0., 29.98, 31.78, 48.88, -28.52,-46.4883,-35., -10., 47., 60. }, + { 0., 0., 31.13, 35.22, 56.61, 141.45, 16.5305,-13.61, 65.,-173., -6. }, + { 0., 0.,-32., -35., -45., 161.5, 137., 145., -150., 138.,-10. } }; final private String name; @@ -107,6 +107,7 @@ public abstract class MapApplication extends Application { */ protected Region buildInputSelector( FileChooser.ExtensionFilter[] allowedExtensions, + FileChooser.ExtensionFilter defaultExtension, Consumer inputSetter) { final Label label = new Label("Current input:"); final Text inputLabel = new Text("None"); @@ -115,6 +116,7 @@ public abstract class MapApplication extends Application { inputChooser.setInitialDirectory(new File("input")); inputChooser.setTitle("Choose an input map"); inputChooser.getExtensionFilters().addAll(allowedExtensions); + inputChooser.setSelectedExtensionFilter(defaultExtension); final Button loadButton = new Button("Choose input..."); loadButton.setTooltip(new Tooltip( @@ -300,13 +302,15 @@ public abstract class MapApplication extends Application { */ protected Button buildSaveButton(boolean bindCtrlS, String savee, FileChooser.ExtensionFilter[] allowedExtensions, + FileChooser.ExtensionFilter defaultExtension, Procedure settingCollector, BiConsumer calculateAndSaver) { final FileChooser saver = new FileChooser(); saver.setInitialDirectory(new File("output")); - saver.setInitialFileName("my"+savee+allowedExtensions[0].getExtensions().get(0).substring(1)); + saver.setInitialFileName("my"+savee+defaultExtension.getExtensions().get(0).substring(1)); saver.setTitle("Save "+savee); saver.getExtensionFilters().addAll(allowedExtensions); + saver.setSelectedExtensionFilter(defaultExtension); final Button saveButton = new Button("Save "+savee+"..."); saveButton.setOnAction(new EventHandler() { diff --git a/src/apps/MapDesignerRaster.java b/src/apps/MapDesignerRaster.java index dbb71d1..df62a59 100644 --- a/src/apps/MapDesignerRaster.java +++ b/src/apps/MapDesignerRaster.java @@ -63,8 +63,8 @@ public class MapDesignerRaster extends MapApplication { private static final FileChooser.ExtensionFilter[] RASTER_TYPES = { - new FileChooser.ExtensionFilter("PNG", "*.png"), - new FileChooser.ExtensionFilter("JPG", "*.jpg","*.jpeg","*.jpe","*.jfif") }; + new FileChooser.ExtensionFilter("JPG", "*.jpg","*.jpeg","*.jpe","*.jfif"), + new FileChooser.ExtensionFilter("PNG", "*.png") }; private static final Projection[] PROJ_ARR = { Projection.MERCATOR, Projection.PLATE_CARREE, Projection.HOBO_DYER, Projection.GALL, Projection.STEREOGRAPHIC, Projection.POLAR, Projection.E_A_AZIMUTH, @@ -102,13 +102,16 @@ public class MapDesignerRaster extends MapApplication { @Override protected Node makeWidgets() { this.aspect = new double[3]; - final Node inputSelector = buildInputSelector(RASTER_TYPES, this::setInput); - final Node projectionSelector = buildProjectionSelector(PROJ_ARR, Projection.MERCATOR, Procedure.NONE); - final Node aspectSelector = buildAspectSelector(this.aspect, Procedure.NONE); + final Node inputSelector = buildInputSelector(RASTER_TYPES, + RASTER_TYPES[0], this::setInput); + final Node projectionSelector = buildProjectionSelector(PROJ_ARR, + Projection.MERCATOR, Procedure.NONE); + final Node aspectSelector = buildAspectSelector(this.aspect, + Procedure.NONE); final Node parameterSelector = buildParameterSelector(Procedure.NONE); this.updateBtn = buildUpdateButton(this::updateMap); this.saveMapBtn = buildSaveButton(true, "map", RASTER_TYPES, - this::collectFinalSettings, this::calculateAndSaveMap); + RASTER_TYPES[1], this::collectFinalSettings, this::calculateAndSaveMap); final HBox buttons = new HBox(5, updateBtn, saveMapBtn); buttons.setAlignment(Pos.CENTER); @@ -151,7 +154,8 @@ public class MapDesignerRaster extends MapApplication { private void updateMap() { - display.setImage(map()); + display.setImage(makeImage( + this.getProjection().map(IMG_WIDTH, this.getParams(), aspect))); } @@ -163,9 +167,13 @@ public class MapDesignerRaster extends MapApplication { protected void calculateAndSaveMap(File file, ProgressBarDialog pBar) { - Image theMap = map( - configDialog.getDims(), configDialog.getSmoothing(), pBar); //calculate + final int[] outDims = configDialog.getDims(); + final int smoothing = configDialog.getSmoothing(); + double[][][] points =this.getProjection().map( + outDims[0]*smoothing, outDims[1]*smoothing, this.getParams(), aspect, pBar::setProgress); pBar.setProgress(-1); + Image theMap = makeImage(points, smoothing); //calculate + final String filename = file.getName(); final String extension = filename.substring(filename.lastIndexOf('.')+1); try { @@ -179,66 +187,42 @@ public class MapDesignerRaster extends MapApplication { } - public Image map() { - final double a = this.getProjection().getAspectRatio(this.getParams()); - return map(new int[] { IMG_WIDTH, (int)(IMG_WIDTH/a) }, 1); + private Image makeImage(double[][][] points) { + return makeImage(points, 1); } - public Image map(int[] outputDims, int smoothing) { - return map(outputDims,smoothing, null); - } - - public Image map(int[] outDims, int smoothing, - ProgressBarDialog pbar) { - final Projection proj = this.getProjection(); - final double[] params = this.getParams(); - final PixelReader ref = input.getPixelReader(); - final int[] refDims = {(int)input.getWidth(), (int)input.getHeight()}; - - WritableImage img = new WritableImage(outDims[0], outDims[1]); - - for (int x = 0; x < outDims[0]; x ++) { - for (int y = 0; y < outDims[1]; y ++) { - int[] colors = new int[smoothing*smoothing]; - int i = 0; - for (double dx = 0.5/smoothing; dx < 1; dx += 1.0/smoothing) { - for (double dy = .5/smoothing; dy < 1; dy += 1.0/smoothing) { - colors[i] = getArgb(x+dx, y+dy, - proj,params,aspect,ref,refDims,outDims); - i ++; + private Image makeImage(double[][][] points, int step) { + final PixelReader in = input.getPixelReader(); + final WritableImage out = new WritableImage(points[0].length/step, points.length/step); + for (int y = 0; y < out.getHeight(); y ++) { + for (int x = 0; x < out.getWidth(); x ++) { + int[] colors = new int[step*step]; + for (int dy = 0; dy < step; dy ++) { + for (int dx = 0; dx < step; dx ++) { + colors[step*dy+dx] = getArgb(points[step*y+dy][step*x+dx], in, input.getWidth(), input.getHeight()); } } - img.getPixelWriter().setArgb(x, y, blend(colors)); + out.getPixelWriter().setArgb(x, y, blend(colors)); } - if (pbar != null) pbar.setProgress((double)(x+1)/outDims[0]); } + return out; + } + + + public static int getArgb(double[] coords, PixelReader in, + double inWidth, double inHeight) { // returns the color of any coordinate on earth + if (coords == null) return 0; - return img; - } - - - public static int getArgb(double x, double y, Projection proj, double[] params, double[] pole, - PixelReader ref, int[] refDims, int[] outDims) { - final double[] coords = proj.inverse( - 2.*x/outDims[0]-1, 1-2.*y/outDims[1], params, pole); - if (coords != null) - return getColorAt(coords, ref, refDims); - else - return 0; - } - - - public static int getColorAt(double[] coords, PixelReader ref, int[] refDims) { // returns the color of any coordinate on earth double x = 1/2.0 + coords[1]/(2*Math.PI); - x = (x - Math.floor(x)) * refDims[0]; + x = (x - Math.floor(x)) * inWidth; - double y = refDims[1]/2.0 - coords[0]*refDims[1]/(Math.PI); + double y = inHeight/2.0 - coords[0]*inHeight/(Math.PI); if (y < 0) y = 0; - else if (y >= refDims[1]) - y = refDims[1] - 1; + else if (y >= inHeight) + y = inHeight - 1; - return ref.getArgb((int)x, (int)y); + return in.getArgb((int) x, (int) y); } diff --git a/src/apps/MapDesignerVector.java b/src/apps/MapDesignerVector.java index 782f17c..b0a27b7 100644 --- a/src/apps/MapDesignerVector.java +++ b/src/apps/MapDesignerVector.java @@ -104,12 +104,15 @@ public class MapDesignerVector extends MapApplication { @Override public Node makeWidgets() { this.aspect = new double[3]; - final Node inputSelector = buildInputSelector(VECTOR_TYPES, this::setInput); - final Node projectionSelector = buildProjectionSelector(PROJ_ARR, Projection.MERCATOR, this::updateMap); - final Node aspectSelector = buildAspectSelector(this.aspect, this::updateMap); + final Node inputSelector = buildInputSelector(VECTOR_TYPES, + VECTOR_TYPES[0], this::setInput); + final Node projectionSelector = buildProjectionSelector(PROJ_ARR, + Projection.MERCATOR, this::updateMap); + final Node aspectSelector = buildAspectSelector(this.aspect, + this::updateMap); final Node parameterSelector = buildParameterSelector(this::updateMap); this.saveBtn = buildSaveButton(true, "map", VECTOR_TYPES, - Procedure.NONE, this::calculateAndSaveMap); + VECTOR_TYPES[0], Procedure.NONE, this::calculateAndSaveMap); final VBox layout = new VBox(5, inputSelector, new Separator(), projectionSelector, @@ -220,12 +223,18 @@ public class MapDesignerVector extends MapApplication { g.clearRect(0, 0, c.getWidth(), c.getHeight()); for (List closedCurve: img) { g.beginPath(); - for (int i = 0; i < closedCurve.size(); i ++) { - double[] p = closedCurve.get(i); - if (i == 0) g.moveTo(p[0], p[1]); - else g.lineTo(p[0], p[1]); + g.moveTo(closedCurve.get(0)[0], closedCurve.get(0)[1]); + for (int i = 1; i < closedCurve.size()+1; i ++) { + double[] p0 = closedCurve.get(i-1); + double[] p1 = closedCurve.get(i%closedCurve.size()); + if (Math.hypot(p1[0]-p0[0], p1[1]-p0[1]) >= c.getWidth()/10) { + g.stroke(); + g.beginPath(); + g.moveTo(p1[0], p1[1]); + } + else + g.lineTo(p1[0], p1[1]); } - g.closePath(); g.stroke(); } } diff --git a/src/maps/Projection.java b/src/maps/Projection.java index b2109e9..14f642f 100644 --- a/src/maps/Projection.java +++ b/src/maps/Projection.java @@ -25,6 +25,7 @@ package maps; import java.util.ArrayList; import java.util.List; +import java.util.function.DoubleConsumer; import java.util.function.UnaryOperator; import org.apache.commons.math3.complex.Complex; @@ -109,8 +110,8 @@ public enum Projection { }, E_A_CYLIND("Equal-area cylindrical", "A generalized equal-area cylindrical projection", - Math.PI, 0b1111, "cylindrical", "equal-area", - new String[]{"Std. parallel"}, new double[][]{{0, 75, 37.5}}) { + 1.977, 0b1111, "cylindrical", "equal-area", + new String[]{"Std. parallel"}, new double[][]{{0, 85, 37.5}}) { public double[] project(double lat, double lon, double[] params) { final double a = Math.pow(Math.cos(Math.toRadians(params[0])), 2); return new double[] {lon, Math.sin(lat)/a}; @@ -395,17 +396,41 @@ public enum Projection { }, TOBLER("Tobler", "An equal-area projection shaped like a hyperellipse", - 2., 0b1001, "pseudocylindrical", "equal-area",new String[]{"alpha","gamma","k"}, - new double[][] {{0,1,0}, {1,5,2.5}, {1,2,1.831}}) { + 2., 0b1001, "pseudocylindrical", "equal-area",new String[]{"alpha","K"}, + new double[][] {{0,1,0}, {1,5,2.5}}) { public double[] project(double lat, double lon, double[] params) { - return new double[] { - lon*Tobler.xfacFromLat(lat)*18, - Tobler.yFromLat(lat)*Math.PI/10 }; + final double g = 1/NumericalAnalysis.simpsonIntegrate(0, 1, + Tobler::hyperEllipse, .01, params);//I think my gammaay be defined differently from Tobler's + final double y = + NumericalAnalysis.newtonRaphsonApproximation( + Math.abs(Math.sin(lat)), Math.abs(Math.sin(lat)), + Tobler::sinPhi, Tobler::dsinPhidY, .01, + params[0], params[1], g)*Math.signum(lat); + return new double[] { Tobler.X(y, lon, params), Math.PI/2*y }; //I suppose I could make this about twice as fast if I really wanted to deal with more inheritance, but this is fast enough. } public double[] inverse(double x, double y, double[] params) { - return new double[] { - Tobler.latFromY(5*y), //TODO: make this be real - x/Tobler.xfacFromY(5*y)*Math.PI/18 }; + return new double[] { 0, Tobler.lam(x,y,params) }; + } + public double[][][] map(double w, double h, double[] params, double[] pole, DoubleConsumer tracker) { //generate a matrix of coordinates based on a map projection + final int n = (int) h; //just so I don't forget + final double g = 1/NumericalAnalysis.simpsonIntegrate(0, 1, + Tobler::hyperEllipse, .01, params); + double[] z = NumericalAnalysis.simpsonODESolve( + 1, n, Tobler::dZdY, Math.min(1./n,.01), + params[0], params[1], g); + double[][][] output = new double[(int) h][(int) w][2]; //the coordinate matrix + for (int y = 0; y < output.length; y ++) { + final double zoy; + if (y < output.length/2) zoy = z[n - 2*y - 1]; + else zoy =-z[2*y + 1 - n]; + for (int x = 0; x < output[y].length; x ++) { + output[y][x] = inverse((2*x+1)/w-1, 1-(2*y+1)/h, params, pole); + output[y][x][0] = Math.asin(zoy); + } + if (tracker != null) tracker.accept((double) y/output.length); + } + + return output; } }, @@ -469,9 +494,10 @@ public enum Projection { final double cosphi0 = Math.cos(Math.toRadians(params[0])); return NumericalAnalysis.newtonRaphsonApproximation( x*Math.PI*(1 + cosphi0), y*Math.PI, - WinkelTripel::f1pX, WinkelTripel::f2pY, WinkelTripel::df1dphi, - WinkelTripel::df1dlam, WinkelTripel::df2dphi, WinkelTripel::df2dlam, - .05, cosphi0); + y*Math.PI/2, x*Math.PI*(1 + Math.cos(y*Math.PI/2))/2, + WinkelTripel::f1pX, WinkelTripel::f2pY, + WinkelTripel::df1dphi, WinkelTripel::df1dlam, + WinkelTripel::df2dphi, WinkelTripel::df2dlam, .05, cosphi0); } public double getAspectRatio(double[] params) { return 1 + Math.cos(Math.toRadians(params[0])); @@ -766,13 +792,26 @@ public enum Projection { } - public double[][][] map(int size, double[] params) { //generate a matrix of coordinates based on a map projection - final int w = size, h = (int)(size/this.getAspectRatio(params)); - double[][][] output = new double[h][w][2]; //the coordinate matrix + public double[][][] map(int size, double[] params) { + return map(size, params, new double[] {Math.PI/2,0,0}); + } + + public double[][][] map(int size, double[] params, double[] pole) { + final double ratio = this.getAspectRatio(params); + if (ratio < 1) + return map(Math.round(size*ratio), size, params, pole, null); + else + return map(size, Math.round(size/ratio), params, pole, null); + } + + public double[][][] map(double w, double h, double[] params, double[] pole, DoubleConsumer tracker) { //generate a matrix of coordinates based on a map projection + double[][][] output = new double[(int) h][(int) w][2]; //the coordinate matrix - for (int y = 0; y < output.length; y ++) + for (int y = 0; y < output.length; y ++) { for (int x = 0; x < output[y].length; x ++) - output[y][x] = inverse(2*(x+.5)/w - 1, 1 - 2*(y+.5)/h, params); //s0 is this point on the sphere + output[y][x] = inverse((2*x+1)/w-1, 1-(2*y+1)/h, params, pole); + if (tracker != null) tracker.accept((double) y/output.length); + } return output; } diff --git a/src/maps/Tobler.java b/src/maps/Tobler.java index 6d2ac31..04ecb53 100644 --- a/src/maps/Tobler.java +++ b/src/maps/Tobler.java @@ -23,7 +23,7 @@ */ package maps; -import org.apache.commons.math3.analysis.interpolation.SplineInterpolator; +import util.NumericalAnalysis; /** * A class of values and functions used to approximate the Tobler projection @@ -32,36 +32,43 @@ import org.apache.commons.math3.analysis.interpolation.SplineInterpolator; */ public class Tobler { - private static final double[][] TABLE = { - { -90,-85,-80,-75,-70,-65,-60,-55,-50,-45,-40,-35,-30,-25,-20,-15,-10,-05, 00, - 05, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90 }, - {-5.000000,-4.921966,-4.782840,-4.610387,-4.410173,-4.184957,-3.940243,-3.678861,-3.398039,-3.103326,-2.794726,-2.475016,-2.144195,-1.802264,-1.454780,-1.096182,-0.734807,-0.367979, 0.000000, - 0.367979, 0.734807, 1.096182, 1.454780, 1.802264, 2.144195, 2.475016, 2.794726, 3.103326, 3.398039, 3.678861, 3.940243, 4.184957, 4.410173, 4.610387, 4.782840, 4.921966, 5.000000 }, - { 0.000000, 0.022944, 0.030411, 0.035519, 0.039413, 0.042524, 0.045070, 0.047181, 0.048940, 0.050406, 0.051625, 0.052627, 0.053442, 0.054091, 0.054595, 0.054973, 0.055242, 0.055429, 0.055555, - 0.055429, 0.055242, 0.054973, 0.054595, 0.054091, 0.053442, 0.052627, 0.051625, 0.050406, 0.048940, 0.047181, 0.045070, 0.042524, 0.039413, 0.035519, 0.030411, 0.022944, 0.000000 } - }; //these values were calculated with some kind of iterative approach in 1973. - - private static final SplineInterpolator SI = new SplineInterpolator(); //I don't understand why these can't be static functions, but fine - - - - public static final double xfacFromLat(double lat) { - return SI.interpolate(TABLE[0], TABLE[2]).value(Math.toDegrees(lat)); //I also don't understand why I can't call interpolate into a final Object instead of calling it every time. It's weird. If I try to do that, then invoking this class terminates the thread, like there's something so awful about calling that method outside of any method that Java just dies + public static final double lam(double x, double y, double[] params) { + final double a = params[0]; + return Math.PI * x / (a + (1-a)*hyperEllipse(y, params)); } - public static final double yFromLat(double lat) { - return SI.interpolate(TABLE[0], TABLE[1]).value(Math.toDegrees(lat)); + public static final double X(double y, double lam, double[] params) { + final double a = params[0]; + return lam * (a + (1-a)*hyperEllipse(y, params)); } - public static final double latFromY(double pdfe) { - return Math.toRadians(SI.interpolate(TABLE[1], TABLE[0]).value(pdfe)); + public static final double sinPhi(double y, double[] params) { //subtract sin(phi), once you have phi, to get error + final double a = params[0], g = params[2]; + return (a*y + (1-a)*NumericalAnalysis.simpsonIntegrate( + 0, y, Tobler::hyperEllipse, .0625, params))/ + (a + (1-a)/g); } - public static final double xfacFromY(double pdfe) { - return SI.interpolate(TABLE[1], TABLE[2]).value(pdfe); + public static final double dsinPhidY(double y, double[] params) { + final double a = params[0], g = params[2]; + return (a + (1-a)*hyperEllipse(y, params))/ + (a + (1-a)/g); + } + + + public static final double dZdY(double y, double[] params) { + final double a = params[0], g = params[2]; + return (a + (1-a)*hyperEllipse(y, params))/ + (a + (1-a)/g); + } + + + public static final double hyperEllipse(double y, double[] params) { + final double k = params[1]; + return Math.pow(1 - Math.pow(Math.abs(y),k), 1/k); } } diff --git a/src/util/NumericalAnalysis.java b/src/util/NumericalAnalysis.java index 39bb360..28381fb 100644 --- a/src/util/NumericalAnalysis.java +++ b/src/util/NumericalAnalysis.java @@ -32,43 +32,123 @@ import java.util.Arrays; */ public class NumericalAnalysis { + public static final void main(String[] args) { + System.out.println(simpsonIntegrate(-1,1, (x,prms) -> 4*Math.sqrt(1-x*x), .001, null)); + } /** - * Applies Newton's method in two dimensions to solve for phi and lam given - * desired x and y values, x(phi,lam), y(phi,lam), and some derivatives - * @param x0 The x value that the functions need to match - * @param y0 The y value that the functions need to match - * @param f1pX x in terms of phi and lam - * @param f2pY y in terms of phi and lam + * Performs a definite integral using Simpson's rule and a constant step size + * @param a The start of the integration region + * @param b The end of the integration region (must be greater than a) + * @param f The integrand + * @param h The step size (must be positive) + * @param constants Constant parameters for the function + * @return \int_a^b \! f(x) \, \mathrm{d}x + */ + public static final double simpsonIntegrate(double a, double b, ScalarFunction f, double h, double... constants) { + double sum = 0; + for (double x = a; x < b; x += h) { + if (x+h > b) h = b-x; + sum += h/6*(f.evaluate(x,constants) + + 4*f.evaluate(x+h/2, constants) + + f.evaluate(x+h, constants)); + } + return sum; + } + + + /** + * Solves a simple ODE using Simpson's rule and a constant step size + * @param T The maximum time value at which to sample (must be positive) + * @param n The desired number of spaces (or the number of samples minus 1) + * @param f The derivative of y with respect to time + * @param h The internal step size (must be positive) + * @param constants Constant parameters for the function + * @return the double[] y, where y[i] is the value of y at t=i*T/n + */ + public static final double[] simpsonODESolve(double T, int n, ScalarFunction f, double h, double... constants) { + final double[] y = new double[n+1]; //the output + double t = 0; + double sum = 0; + for (int i = 0; i <= n; i ++) { + while (t < i*T/n) { + final double tph = Math.min(t+h, i*T/n); + sum += (tph-t)/6*(f.evaluate(t, constants) + + 4*f.evaluate((t+tph)/2, constants) + + f.evaluate(tph, constants)); + t = tph; + } + y[i] = sum; + } + return y; + } + + + /** + * Applies Newton's method in one dimension to solve for x such that f(x)=y + * @param y Desired value for f + * @param x0 Initial guess for x + * @param f The error in terms of x + * @param dfdx The derivative of f with respect to x + * @param tolerance The maximum error that this can return + * @param constants Constant parameters for the function + * @return The value of x that puts f near 0, or NaN if it does not + * converge in 5 iterations + */ + public static final double newtonRaphsonApproximation( + double y, double x0, ScalarFunction f, ScalarFunction dfdx, + double tolerance, double... constants) { + double x = x0; + double error = Math.PI; + for (int i = 0; i < 5 && error > tolerance; i ++) { + error = f.evaluate(x, constants) - y; + final double dydx = dfdx.evaluate(x, constants); + x -= error/dydx; + } + if (error > tolerance) + return Double.NaN; + else + return x; + } + + + /** + * Applies Newton's method in two dimensions to solve for phi and lam such + * that f1(phi,lam)=x and f2(phi,lam)=y + * @param x Desired value for f1 + * @param y Desired value for f2 + * @param phi0 Initial guess for phi + * @param lam0 Initial guess for lam + * @param f1 x-error in terms of phi and lam + * @param f2 y-error in terms of phi and lam * @param df1dp The partial derivative of x with respect to phi * @param df1dl The partial derivative of x with respect to lam * @param df2dp The partial derivative of y with respect to phi * @param df2dl The partial derivative of y with respect to lam * @param tolerance The maximum error that this can return - * @param params Constant parameters for the functions - * @return the values of phi and lam that put f1pX and f2pY near x0 and y0 + * @param constants Constant parameters for the functions + * @return the values of phi and lam that put f1 and f2 near 0, or + * null if it does not converge in 5 iterations. */ - public static final double[] newtonRaphsonApproximation(double x0, double y0, - VectorFunction fxpX, VectorFunction fypY, VectorFunction dfxdp, - VectorFunction dfxdl, VectorFunction dfydp, VectorFunction dfydl, - double tolerance, double... params) { - double x = x0; - double y = y0; - double phi = y/2; - double lam = x/2; // I used equirectangular for my initial guess + public static final double[] newtonRaphsonApproximation(double x, double y, + double phi0, double lam0, VectorFunction f1, VectorFunction f2, + VectorFunction df1dp, VectorFunction df1dl, VectorFunction df2dp, + VectorFunction df2dl, double tolerance, double... constants) { + double phi = phi0; + double lam = lam0; double error = Math.PI; for (int i = 0; i < 5 && error > tolerance; i++) { - final double f1 = fxpX.evaluate(phi, lam, params) - x; - final double f2 = fypY.evaluate(phi, lam, params) - y; - final double df1dP = dfxdp.evaluate(phi, lam, params); - final double df1dL = dfxdl.evaluate(phi, lam, params); - final double df2dP = dfydp.evaluate(phi, lam, params); - final double df2dL = dfydl.evaluate(phi, lam, params); + final double F1mx = f1.evaluate(phi, lam, constants) - x; + final double F2my = f2.evaluate(phi, lam, constants) - y; + final double dF1dP = df1dp.evaluate(phi, lam, constants); + final double dF1dL = df1dl.evaluate(phi, lam, constants); + final double dF2dP = df2dp.evaluate(phi, lam, constants); + final double dF2dL = df2dl.evaluate(phi, lam, constants); - phi -= (f1*df2dL - f2*df1dL) / (df1dP*df2dL - df2dP*df1dL); - lam -= (f2*df1dP - f1*df2dP) / (df1dP*df2dL - df2dP*df1dL); + phi -= (F1mx*dF2dL - F2my*dF1dL) / (dF1dP*dF2dL - dF2dP*dF1dL); + lam -= (F2my*dF1dP - F1mx*dF2dP) / (dF1dP*dF2dL - dF2dP*dF1dL); - error = Math.hypot(f1, f2); + error = Math.hypot(F1mx, F2my); } if (error > tolerance) // if it aborted due to timeout @@ -94,8 +174,8 @@ public class NumericalAnalysis { * @param x The input value * @param X The sorted array of inputs on which to interpolate * @param f The sorted array of outputs on which to interpolate - * @param from The index of the arrays at which to start - * @param to The index of the arrays at which to stop + * @param from The index of the arrays at which to start (inclusive) + * @param to The index of the arrays at which to stop (exclusive) * @return f(x), approximately */ public static final double aitkenInterpolate(double x, @@ -119,6 +199,11 @@ public class NumericalAnalysis { + @FunctionalInterface + public interface ScalarFunction { + public double evaluate(double x, double[] constants); + } + @FunctionalInterface public interface VectorFunction { public double evaluate(double x, double y, double[] constants);