mirror of
https://github.com/csharpee/Map-Projections.git
synced 2025-09-02 00:00:04 -04:00
Parsing, for real this time
I implemented more advanced SVG parsing and manipulation. It's somewhat slower now, I think because I changed the way it skips vertices to save time, but it was necessary to enable slightly more advanced SVG reading (which I have yet to test). All the projections work, though. I also changed my coordinate system. I also broke the bit where it does not draw long lines.
This commit is contained in:
parent
3c8150a276
commit
40201f6f46
2
.settings/org.eclipse.ltk.core.refactoring.prefs
Normal file
2
.settings/org.eclipse.ltk.core.refactoring.prefs
Normal file
@ -0,0 +1,2 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.ltk.core.refactoring.enable.project.refactoring.history=false
|
@ -21,11 +21,9 @@ The executable applications all have similar layouts that let you select an inpu
|
||||
## Dependencies
|
||||
While the excecutables are standalone, and the Jars require only Java, the source code makes use of several external libraries. These are
|
||||
|
||||
* Apache commons `math3`
|
||||
* "mfc"
|
||||
* "ellipticFunctions"
|
||||
|
||||
To be perfectly honest, I don't remember where I got most of these. Oops. It looks like they might be German. I would recommend looking the `de.jtem` package from (math.tu-berlin.de)[www3.math.tu.berlin.de/jtem/].
|
||||
* [Apache Commons Mathematics Library](https://commons.apache.org/proper/commons-math/)
|
||||
* [Java Tools for Experimental Mathematics "ellipticFunctions" package](http://www3.math.tu-berlin.de/jtem/ellipticFunctions/), which requires their ["mfc" package](http://www3.math.tu-berlin.de/jtem/mfc/)
|
||||
* [Apache Batik Java SVG Toolkit](https://xmlgraphics.apache.org/batik/)
|
||||
|
||||
## Wherefore?
|
||||
I'll write a little blurb here later.
|
||||
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
45
input/Grid.svg
Normal file
45
input/Grid.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 163 KiB |
@ -68,7 +68,7 @@ public abstract class MapApplication extends Application {
|
||||
private static final double[][] ASPECT_VALS = { //the aspect presets (in degrees)
|
||||
{ 90., 0., 0., 29.98, 31.78, 48.88, -28.52,-35., 47., 60. },
|
||||
{ 0., 0., 90., 31.13, 35.22, 56.61, 141.45,-13.61,-173., -6. },
|
||||
{ 0., 0.,-90.,-32., -35., -45., 161.5, 145., 138.,-100. } };
|
||||
{ 0., 0.,-90.,-32., -35., -45., 30., 145., 138.,-100. } };
|
||||
|
||||
|
||||
final private String name;
|
||||
|
@ -57,6 +57,7 @@ import maps.Tetrahedral;
|
||||
import maps.Tobler;
|
||||
import maps.WinkelTripel;
|
||||
import utils.ImageUtils;
|
||||
import utils.PixelMap;
|
||||
import utils.Procedure;
|
||||
|
||||
/**
|
||||
@ -98,7 +99,7 @@ public class MapDesignerRaster extends MapApplication {
|
||||
private Node aspectSelector;
|
||||
private Button updateBtn, saveMapBtn;
|
||||
private double[] aspect;
|
||||
private BufferedImage input;
|
||||
private PixelMap input;
|
||||
private ImageView display;
|
||||
private MapConfigurationDialog configDialog;
|
||||
|
||||
@ -163,7 +164,7 @@ public class MapDesignerRaster extends MapApplication {
|
||||
saveMapBtn.setDisable(true);
|
||||
|
||||
try {
|
||||
input = ImageIO.read(file);
|
||||
input = new PixelMap(file);
|
||||
} catch (IOException e) {
|
||||
final Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setHeaderText("File not found!");
|
||||
@ -230,8 +231,9 @@ public class MapDesignerRaster extends MapApplication {
|
||||
for (int dx = 0; dx < step; dx ++) {
|
||||
final double X = 2*(x+(dx+.5)/step)/out.getWidth()-1;
|
||||
final double Y = 1-2*(y+(dy+.5)/step)/out.getHeight();
|
||||
colors[step*dy+dx] = ImageUtils.getArgb(
|
||||
this.getProjection().inverse(X, Y, pole), input);
|
||||
final double[] coords = this.getProjection().inverse(X, Y, pole);
|
||||
if (coords != null) //if it is null, the default (0:transparent) is used
|
||||
colors[step*dy+dx] = input.getArgb(coords[0], coords[1]);
|
||||
}
|
||||
}
|
||||
out.setRGB(x, y, ImageUtils.blend(colors));
|
||||
|
@ -22,17 +22,17 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package apps;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.function.DoubleConsumer;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import dialogs.ProgressBarDialog;
|
||||
import javafx.application.Platform;
|
||||
import javafx.geometry.Insets;
|
||||
@ -58,8 +58,10 @@ import maps.Robinson;
|
||||
import maps.Tetrahedral;
|
||||
import maps.Tobler;
|
||||
import maps.WinkelTripel;
|
||||
import utils.Math2;
|
||||
import utils.Procedure;
|
||||
import utils.SVGMap;
|
||||
import utils.SVGMap.Command;
|
||||
import utils.SVGMap.Path;
|
||||
|
||||
/**
|
||||
* An application to make vector oblique aspects of map projections
|
||||
@ -96,10 +98,7 @@ public class MapDesignerVector extends MapApplication {
|
||||
private Node aspectSelector;
|
||||
private Button saveBtn;
|
||||
private double[] aspect;
|
||||
private List<String> format;
|
||||
private List<List<double[]>> input;
|
||||
private double minX, maxX, minY, maxY;
|
||||
private int numVtx;
|
||||
private SVGMap input;
|
||||
private Canvas viewer;
|
||||
|
||||
|
||||
@ -154,13 +153,16 @@ public class MapDesignerVector extends MapApplication {
|
||||
saveBtn.setDisable(true);
|
||||
|
||||
try {
|
||||
input = loadSVG(file.getAbsolutePath());
|
||||
} catch (IllegalArgumentException e) {
|
||||
showError("Unreadable file!",
|
||||
"We couldn't read "+file.getAbsolutePath()+". It may be corrupt or an unreadable format.");
|
||||
input = new SVGMap(file);
|
||||
} catch (IOException e) {
|
||||
showError("File not found!",
|
||||
"We couldn't find "+file.getAbsolutePath()+".");
|
||||
} catch (SAXException e) {
|
||||
showError("Unreadable file!",
|
||||
"We couldn't read "+file.getAbsolutePath()+". It may be corrupt or an unreadable format.");
|
||||
} catch (ParserConfigurationException e) {
|
||||
// TODO: Handle this
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
saveBtn.setDisable(false);
|
||||
}
|
||||
@ -169,71 +171,6 @@ public class MapDesignerVector extends MapApplication {
|
||||
}
|
||||
|
||||
|
||||
private List<List<double[]>> loadSVG(String filename) throws IOException { // this method is just awful.
|
||||
input = new ArrayList<List<double[]>>();
|
||||
format = new ArrayList<String>();
|
||||
minX = minY = Integer.MAX_VALUE;
|
||||
maxX = maxY = Integer.MIN_VALUE;
|
||||
numVtx = 0;
|
||||
|
||||
BufferedReader in = new BufferedReader(new FileReader(filename));
|
||||
String formatStuff = "";
|
||||
int c = in.read();
|
||||
|
||||
do {
|
||||
formatStuff += (char)c;
|
||||
if (formatStuff.length() >= 4 &&
|
||||
formatStuff.substring(formatStuff.length()-4).equals(" d=\"")) {
|
||||
format.add(formatStuff);
|
||||
formatStuff = "";
|
||||
List<double[]> currentShape = new ArrayList<double[]>();
|
||||
c = in.read();
|
||||
do {
|
||||
if (c == 'Z') {
|
||||
input.add(currentShape);
|
||||
currentShape = new ArrayList<double[]>();
|
||||
format.add("");
|
||||
}
|
||||
if (isDigit((char) c)) {
|
||||
String num = Character.toString((char)c);
|
||||
while (isDigit((char) (c = in.read())))
|
||||
num += (char) c;
|
||||
double x = Double.parseDouble(num);
|
||||
if (x < minX) minX = x;
|
||||
if (x > maxX) maxX = x;
|
||||
c = in.read();
|
||||
num = Character.toString((char)c);
|
||||
while (isDigit((char) (c = in.read())))
|
||||
num += (char) c;
|
||||
double y = Double.parseDouble(num);
|
||||
if (y < minY) minY = y;
|
||||
if (y > maxY) maxY = y;
|
||||
currentShape.add(new double[] {x,y});
|
||||
numVtx ++;
|
||||
}
|
||||
else {
|
||||
c = in.read();
|
||||
}
|
||||
} while (c != '"');
|
||||
}
|
||||
else {
|
||||
c = in.read();
|
||||
}
|
||||
} while (c >= 0);
|
||||
|
||||
format.add(formatStuff);
|
||||
in.close();
|
||||
|
||||
for (List<double[]> curve: input) {
|
||||
for (double[] coords: curve) {
|
||||
final double[] radCoords = convCoordsToMathy(coords);
|
||||
coords[0] = radCoords[0];
|
||||
coords[1] = radCoords[1];
|
||||
}
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
|
||||
private void hideAspect() {
|
||||
aspectSelector.setVisible(this.getProjection().hasAspect());
|
||||
@ -243,112 +180,63 @@ public class MapDesignerVector extends MapApplication {
|
||||
private void updateMap() {
|
||||
loadParameters();
|
||||
int maxVtx = this.getParamsChanging() ? FAST_MAX_VTX : DEF_MAX_VTX;
|
||||
final List<List<double[]>> transformed = map(input, numVtx/maxVtx+1, aspect.clone(), null);
|
||||
Platform.runLater(() -> drawImage(transformed, viewer));
|
||||
final Iterable<Path> transformed = map(input, input.size()/maxVtx+1, aspect.clone(), null);
|
||||
drawImage(transformed, viewer);
|
||||
}
|
||||
|
||||
|
||||
private void calculateAndSaveMap(File file, ProgressBarDialog pBar) {
|
||||
loadParameters();
|
||||
final List<List<double[]>> transformed = map(input, 1, aspect.clone(), pBar::setProgress); //calculate
|
||||
saveToSVG(transformed, file, pBar); //save
|
||||
final Iterable<Path> transformed = map(input, 1, aspect.clone(), pBar::setProgress); //calculate
|
||||
try {
|
||||
input.save(transformed, file, pBar::setProgress); //save
|
||||
} catch (IOException e) {
|
||||
showError("Failure!",
|
||||
"Could not access "+file.getAbsolutePath()+". It's possible that another program has it open.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public List<List<double[]>> map(List<List<double[]>> curves, int step,
|
||||
double[] pole, DoubleConsumer tracker) {
|
||||
List<List<double[]>> output = new LinkedList<List<double[]>>();
|
||||
public Iterable<Path> map(SVGMap input, int step, double[] pole, DoubleConsumer tracker) {
|
||||
List<Path> output = new LinkedList<Path>();
|
||||
int i = 0;
|
||||
for (List<double[]> curve0: curves) {
|
||||
if (curve0.size() < step*3) continue;
|
||||
for (Path path0: input) {
|
||||
if (path0.length() < step*3) continue;
|
||||
|
||||
List<double[]> curve1 = new ArrayList<double[]>(curve0.size()/step);
|
||||
for (int j = 0; j < curve0.size(); j += step)
|
||||
curve1.add(this.getProjection().project(curve0.get(j), pole));
|
||||
output.add(curve1);
|
||||
Path path1 = new Path();
|
||||
int counter = 0;
|
||||
for (Command cmd0: path0) {
|
||||
counter --;
|
||||
if (counter > 0 && cmd0.type != 'M' && cmd0.type != 'Z') continue;
|
||||
counter = step;
|
||||
|
||||
Command cmd1 = new Command(cmd0.type, new double[cmd0.args.length]);
|
||||
for (int k = 0; k < cmd0.args.length; k += 2) {
|
||||
System.arraycopy(
|
||||
this.getProjection().project(cmd0.args[k+1], cmd0.args[k], pole), 0,
|
||||
cmd1.args, k, 2);
|
||||
}
|
||||
path1.add(cmd1);
|
||||
}
|
||||
output.add(path1);
|
||||
|
||||
if (tracker != null) {
|
||||
i ++;
|
||||
tracker.accept((double)i/curves.size());
|
||||
tracker.accept((double)i/input.numCurves());
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
private void drawImage(List<List<double[]>> img, Canvas c) {
|
||||
private void drawImage(Iterable<Path> paths, Canvas c) {
|
||||
GraphicsContext g = c.getGraphicsContext2D();
|
||||
g.clearRect(0, 0, c.getWidth(), c.getHeight());
|
||||
for (List<double[]> closedCurve: img) {
|
||||
final double[] start = convCoordsToImg(closedCurve.get(0));
|
||||
g.beginPath();
|
||||
g.moveTo(start[0], start[1]);
|
||||
for (int i = 1; i < closedCurve.size()+1; i ++) {
|
||||
double[] p0 = convCoordsToImg(closedCurve.get(i-1));
|
||||
double[] p1 = convCoordsToImg(closedCurve.get(i%closedCurve.size()));
|
||||
if (Math.hypot(p1[0]-p0[0], p1[1]-p0[1]) >= c.getWidth()/8) {
|
||||
g.stroke();
|
||||
g.beginPath();
|
||||
g.moveTo(p1[0], p1[1]);
|
||||
}
|
||||
else
|
||||
g.lineTo(p1[0], p1[1]);
|
||||
}
|
||||
g.stroke();
|
||||
g.beginPath();
|
||||
for (Path path: paths) {
|
||||
g.appendSVGPath(path.toString(0, 0, IMG_WIDTH, IMG_WIDTH));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void saveToSVG(List<List<double[]>> curves, File file,
|
||||
ProgressBarDialog pBar) {
|
||||
try {
|
||||
BufferedWriter out = new BufferedWriter(new FileWriter(file));
|
||||
|
||||
for (int i = 0; i < curves.size(); i ++) {
|
||||
out.write(format.get(i));
|
||||
String curveCode = "M";
|
||||
for (double[] r: curves.get(i)) {
|
||||
final double[] p = convCoordsToSVG(r);
|
||||
curveCode += p[0]+" "+p[1]+"L";
|
||||
}
|
||||
out.write(curveCode.substring(0,curveCode.length()-2)+"Z");
|
||||
pBar.setProgress((double)i/curves.size());
|
||||
}
|
||||
out.write(format.get(curves.size()));
|
||||
out.close();
|
||||
|
||||
} catch (IOException e) {
|
||||
showError("Failure!",
|
||||
"Could not access "+file.getAbsolutePath()+". It's possible that another program has it open.");
|
||||
}
|
||||
saveBtn.setDisable(false);
|
||||
}
|
||||
|
||||
|
||||
private double[] convCoordsToMathy(double... coords) { // changes svg coordinates to radians
|
||||
final double NORTHMOST = 1.459095; //TODO: use image height and width
|
||||
final double SOUTHMOST = -1.4868809;
|
||||
final double EASTMOST = -Math.PI;
|
||||
final double WESTMOST = Math.PI;
|
||||
return new double[] {Math2.linInterp(coords[1], minY,maxY, SOUTHMOST,NORTHMOST),
|
||||
Math2.linInterp(coords[0], minX,maxX, EASTMOST,WESTMOST)};
|
||||
}
|
||||
|
||||
|
||||
private double[] convCoordsToImg(double... coords) { // changes [-1,1] coordinates to image coordinates
|
||||
return new double[] {Math2.linInterp(coords[0], -Math.PI,Math.PI, 0,IMG_WIDTH),
|
||||
Math2.linInterp(coords[1], -Math.PI,Math.PI, IMG_WIDTH,0)};
|
||||
}
|
||||
|
||||
|
||||
private double[] convCoordsToSVG(double... coords) { // changes image coordinates to svg coordinates
|
||||
return new double[] {Math2.linInterp(coords[0], -Math.PI,Math.PI, -180,180), //TODO: use image height and width
|
||||
Math2.linInterp(coords[1], -Math.PI,Math.PI, -180,180)};
|
||||
}
|
||||
|
||||
|
||||
private static final boolean isDigit(char c) {
|
||||
return c >= '-' && c <= '9';
|
||||
g.stroke();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ import maps.Tetrahedral;
|
||||
import maps.Tobler;
|
||||
import maps.WinkelTripel;
|
||||
import utils.ImageUtils;
|
||||
import utils.PixelMap;
|
||||
|
||||
/**
|
||||
* A program to generate and output an HTML snippet listing and explaining all of my maps
|
||||
@ -74,8 +75,8 @@ public class MapExplainer {
|
||||
public static void main(String[] args) throws IOException {
|
||||
|
||||
new File("images").mkdirs();
|
||||
final BufferedImage inputSkew = ImageIO.read(new File("input/Tissot-alt2.jpg"));
|
||||
final BufferedImage inputPole = ImageIO.read(new File("input/Tissot-alt1.jpg"));
|
||||
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;
|
||||
|
||||
for (Projection[] projs: ALL_PROJECTIONS) {
|
||||
@ -124,7 +125,7 @@ public class MapExplainer {
|
||||
}
|
||||
|
||||
|
||||
private static BufferedImage makeImage(Projection proj, BufferedImage input) {
|
||||
private static BufferedImage makeImage(Projection proj, PixelMap input) {
|
||||
proj.setParameters(proj.getDefaultParameters());
|
||||
final BufferedImage out = new BufferedImage(
|
||||
IMG_WIDTH, (int) (IMG_WIDTH/proj.getAspectRatio()), BufferedImage.TYPE_INT_ARGB);
|
||||
@ -135,8 +136,9 @@ public class MapExplainer {
|
||||
for (int dx = 0; dx < SMOOTHING; dx ++) {
|
||||
final double X = 2*(x+(dx+.5)/SMOOTHING)/out.getWidth()-1;
|
||||
final double Y = 1-2*(y+(dy+.5)/SMOOTHING)/out.getHeight();
|
||||
colors[SMOOTHING*dy+dx] = ImageUtils.getArgb(
|
||||
proj.inverse(X, Y), input);
|
||||
final double[] coords = proj.inverse(X, Y);
|
||||
if (coords != null)
|
||||
colors[SMOOTHING*dy+dx] = input.getArgb(coords[0], coords[1]);
|
||||
}
|
||||
}
|
||||
out.setRGB(x, y, ImageUtils.blend(colors));
|
||||
|
@ -33,7 +33,7 @@ public class Azimuthal {
|
||||
"mathematically important") {
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
final double r = Math.PI/2/(Math.tan(lat/2 + Math.PI/4));
|
||||
final double r = .5/(Math.tan(lat/2 + Math.PI/4));
|
||||
return new double[] {r*Math.sin(lon), -r*Math.cos(lon)};
|
||||
}
|
||||
|
||||
@ -48,7 +48,7 @@ public class Azimuthal {
|
||||
new Projection("Polar", 1., 0b1111, Type.AZIMUTHAL, Property.EQUIDISTANT) {
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
final double r = Math.PI/2 - lat;
|
||||
final double r = .5 - lat/Math.PI;
|
||||
return new double[] {r*Math.sin(lon), -r*Math.cos(lon)};
|
||||
}
|
||||
|
||||
@ -67,7 +67,7 @@ public class Azimuthal {
|
||||
"Azimuthal Equal-Area", 1., 0b1111, Type.AZIMUTHAL, Property.EQUAL_AREA) {
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
final double r = Math.PI*Math.cos((Math.PI/2+lat)/2);
|
||||
final double r = Math.cos((Math.PI/2+lat)/2);
|
||||
return new double[] {r*Math.sin(lon), -r*Math.cos(lon)};
|
||||
}
|
||||
|
||||
@ -87,8 +87,7 @@ public class Azimuthal {
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
if (lat < 0) lat = 0;
|
||||
final double r = Math.PI*Math.cos(lat);
|
||||
return new double[] { r*Math.sin(lon), -r*Math.cos(lon) };
|
||||
return new double[] { Math.cos(lat)*Math.sin(lon), -Math.cos(lat)*Math.cos(lon) };
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
@ -108,7 +107,7 @@ public class Azimuthal {
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
if (lat <= 0) lat = 1e-5;
|
||||
final double r = Math.tan(Math.PI/2 - lat);
|
||||
final double r = Math.tan(Math.PI/2 - lat)/2;
|
||||
return new double[] { r*Math.sin(lon), -r*Math.cos(lon)};
|
||||
}
|
||||
|
||||
@ -134,7 +133,7 @@ public class Azimuthal {
|
||||
public double[] project(double lat, double lon) {
|
||||
if (Double.isInfinite(d)) return ORTHOGRAPHIC.project(lat, lon);
|
||||
if (lat < Math.asin(1/d)) lat = Math.asin(1/d);
|
||||
final double r = Math.abs(Math.cos(lat)/(d - Math.sin(lat))) * Math.PI*Math.sqrt(d*d-1);
|
||||
final double r = Math.abs(Math.cos(lat)/(d - Math.sin(lat))) * Math.sqrt(d*d-1);
|
||||
return new double[] { r*Math.sin(lon), -r*Math.cos(lon) };
|
||||
}
|
||||
|
||||
|
@ -63,9 +63,9 @@ public class Conic {
|
||||
}
|
||||
final double r = F*Math.pow(Math.tan(Math.PI/4+lat/2), -n);
|
||||
final double s = reversed ? -1 : 1;
|
||||
final double x = s*Math.PI*r*Math.sin(n*lon);
|
||||
final double y = s*Math.PI*(r0 - r*Math.cos(n*lon));
|
||||
if (d > 0) return new double[] {x, y+Math.PI*d/2};
|
||||
final double x = s*r*Math.sin(n*lon);
|
||||
final double y = s*(r0 - r*Math.cos(n*lon));
|
||||
if (d > 0) return new double[] {x, y+d/2};
|
||||
else if (d < 0) return new double[] {-d*x, -d*y};
|
||||
else return new double[] {x, y};
|
||||
}
|
||||
@ -96,8 +96,10 @@ public class Conic {
|
||||
private double m, n, s;
|
||||
|
||||
public void setSpecificParameters() {
|
||||
if (lat1 == -lat2) //degenerates into Equirectangular; indicate with m=0
|
||||
if (lat1 == -lat2) { //degenerates into Equirectangular; indicate with m=0
|
||||
this.m = 0;
|
||||
Cylindrical.EQUIRECTANGULAR.setParameters(lat1);
|
||||
}
|
||||
else if (lat1 == lat2) //equation becomes indeterminate; use limit
|
||||
this.m = 1/(1/Math.tan(lat1)/Math.PI + lat1/Math.PI + .5);
|
||||
else //normal conic
|
||||
@ -124,9 +126,9 @@ public class Conic {
|
||||
if (reversed) {
|
||||
lat = -lat; lon = -lon;
|
||||
}
|
||||
final double r = Math.PI - m*lat - Math.PI*m/2;
|
||||
final double r = 1 - m*lat/Math.PI - m/2;
|
||||
final double x = s*r*Math.sin(n*lon);
|
||||
final double y = Math.PI*(s-1/Math.max(aspectRatio,1)) - s*r*Math.cos(n*lon);
|
||||
final double y = (s-1/Math.max(aspectRatio,1)) - s*r*Math.cos(n*lon);
|
||||
if (reversed) return new double[] { -x, -y };
|
||||
else return new double[] { x, y };
|
||||
}
|
||||
@ -155,8 +157,10 @@ public class Conic {
|
||||
private double n, C, s;
|
||||
|
||||
public void setSpecificParameters() {
|
||||
if (lat1 == -lat2) //degenerates into Equirectangular; indicate with n=0
|
||||
if (lat1 == -lat2) { //degenerates into Equirectangular; indicate with n=0
|
||||
this.n = 0;
|
||||
Cylindrical.EQUAL_AREA.setParameters(lat1);
|
||||
}
|
||||
else //normal conic
|
||||
this.n = (Math.sin(lat1) + Math.sin(lat2))/2;
|
||||
|
||||
@ -183,8 +187,8 @@ public class Conic {
|
||||
lat = -lat; lon = -lon;
|
||||
}
|
||||
final double r = Math.sqrt(C - 2*n*Math.sin(lat))/n;
|
||||
final double x = Math.PI*s*r*Math.sin(n*lon);
|
||||
final double y = Math.PI*(s*Math.sqrt(C+2*n)/n-1/Math.max(aspectRatio,1)) - Math.PI*s*r*Math.cos(n*lon);
|
||||
final double x = s*r*Math.sin(n*lon);
|
||||
final double y = s*Math.sqrt(C+2*n)/n - 1/Math.max(aspectRatio,1) - s*r*Math.cos(n*lon);
|
||||
if (reversed) return new double[] { -x, -y };
|
||||
else return new double[] { x, y };
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ public class Cylindrical {
|
||||
"Mercator", 1., 0b0111, Type.CYLINDRICAL, Property.CONFORMAL, "very popular") {
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
return new double[] {lon, Math.log(Math.tan(Math.PI/4+lat/2))};
|
||||
return new double[] {lon/Math.PI, Math.log(Math.tan(Math.PI/4+lat/2))/Math.PI};
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
@ -52,7 +52,7 @@ public class Cylindrical {
|
||||
Type.CYLINDRICAL, Property.EQUIDISTANT, null, "focused on the equator"){
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
return new double[] {lon, lat};
|
||||
return new double[] {lon/Math.PI, lat/Math.PI};
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
@ -73,9 +73,9 @@ public class Cylindrical {
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
if (aspectRatio >= 1)
|
||||
return new double[] {lon, lat/aspectRatio*2};
|
||||
return new double[] {lon/Math.PI, 2*lat/Math.PI/aspectRatio};
|
||||
else
|
||||
return new double[] {lon*aspectRatio, 2*lat};
|
||||
return new double[] {lon/Math.PI*aspectRatio, 2*lat/Math.PI};
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
@ -85,11 +85,11 @@ public class Cylindrical {
|
||||
|
||||
|
||||
public static final Projection GALL_PETERS =
|
||||
new Projection("Gall-Peters", 1.571, 0b1111,
|
||||
new Projection("Gall-Peters", Math.PI/2, 0b1111,
|
||||
Type.CYLINDRICAL, Property.EQUAL_AREA, "somewhat controversial") {
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
return new double[] {lon, Math.sin(lat)*Math.PI/1.571};
|
||||
return new double[] {lon/Math.PI, Math.sin(lat)/aspectRatio};
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
@ -103,7 +103,7 @@ public class Cylindrical {
|
||||
"with least distortion at 37.5\u00B0") {
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
return new double[] {lon, Math.sin(lat)*Math.PI/1.977};
|
||||
return new double[] {lon/Math.PI, Math.sin(lat)/aspectRatio};
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
@ -117,7 +117,7 @@ public class Cylindrical {
|
||||
"with least distortion at 30\u00B0") {
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
return new double[] {lon, Math.sin(lat)*Math.PI/2.356};
|
||||
return new double[] {lon/Math.PI, Math.sin(lat)/aspectRatio};
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
@ -131,7 +131,7 @@ public class Cylindrical {
|
||||
Property.EQUAL_AREA, null, "with least distortion along the equator") {
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
return new double[] {lon, Math.sin(lat)};
|
||||
return new double[] {lon/Math.PI, Math.sin(lat)/aspectRatio};
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
@ -152,9 +152,9 @@ public class Cylindrical {
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
if (aspectRatio >= 1)
|
||||
return new double[] {lon, Math.sin(lat)/aspectRatio*Math.PI};
|
||||
return new double[] {lon/Math.PI, Math.sin(lat)/aspectRatio};
|
||||
else
|
||||
return new double[] {lon*aspectRatio, Math.sin(lat)*Math.PI};
|
||||
return new double[] {lon*aspectRatio/Math.PI, Math.sin(lat)};
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
@ -168,7 +168,7 @@ public class Cylindrical {
|
||||
"Gall Stereographic", 4/3., 0b1111, Type.CYLINDRICAL, Property.COMPROMISE) {
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
return new double[] {lon, Math.tan(lat/2)*(1+Math.sqrt(2))};
|
||||
return new double[] {lon/Math.PI, Math.tan(lat/2)*(1+Math.sqrt(2))/Math.PI};
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
@ -182,7 +182,7 @@ public class Cylindrical {
|
||||
Type.CYLINDRICAL, Property.COMPROMISE) {
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
return new double[] {lon, 1.25*Math.log(Math.tan(Math.PI/4+.8*lat/2))};
|
||||
return new double[] {lon/Math.PI, 1.25*Math.log(Math.tan(Math.PI/4+.8*lat/2))/Math.PI};
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
@ -190,4 +190,3 @@ public class Cylindrical {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -44,8 +44,8 @@ public class Misc {
|
||||
public double[] project(double lat, double lon) {
|
||||
final double a = Math.acos(Math.cos(lat)*Math.cos(lon/2));
|
||||
return new double[] {
|
||||
2*Math.cos(lat)*Math.sin(lon/2)*a/Math.sin(a),
|
||||
Math.sin(lat)*a/Math.sin(a)};
|
||||
2*Math.cos(lat)*Math.sin(lon/2)*a/Math.sin(a)/Math.PI,
|
||||
Math.sin(lat)*a/Math.sin(a)/Math.PI};
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
@ -63,8 +63,8 @@ public class Misc {
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
return new double[] {
|
||||
Math.PI*Math.cos(lat)*Math.sin(lon/2)/Math.sqrt(1+Math.cos(lat)*Math.cos(lon/2)),
|
||||
Math.PI/2*Math.sin(lat)/Math.sqrt(1+Math.cos(lat)*Math.cos(lon/2)) };
|
||||
Math.cos(lat)*Math.sin(lon/2)/Math.sqrt(1+Math.cos(lat)*Math.cos(lon/2)),
|
||||
1/2.*Math.sin(lat)/Math.sqrt(1+Math.cos(lat)*Math.cos(lon/2)) };
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
@ -94,8 +94,8 @@ public class Misc {
|
||||
final double P = G*(2/Math.sin(t) - 1);
|
||||
final double Q = A*A + G;
|
||||
return new double[] {
|
||||
Math.PI*Math.signum(lon)*(A*(G-P*P)+Math.sqrt(A*A*(G-P*P)*(G-P*P)-(P*P+A*A)*(G*G-P*P)))/(P*P+A*A),
|
||||
Math.PI*Math.signum(lat)*(P*Q-A*Math.sqrt((A*A+1)*(P*P+A*A)-Q*Q))/(P*P+A*A)};
|
||||
Math.signum(lon)*(A*(G-P*P)+Math.sqrt(A*A*(G-P*P)*(G-P*P)-(P*P+A*A)*(G*G-P*P)))/(P*P+A*A),
|
||||
Math.signum(lat)*(P*Q-A*Math.sqrt((A*A+1)*(P*P+A*A)-Q*Q))/(P*P+A*A)};
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
@ -123,12 +123,14 @@ public class Misc {
|
||||
new Projection("Peirce Quincuncial", "A conformal projection that uses complex elliptic functions",
|
||||
1., 0b1001, Type.OTHER, Property.CONFORMAL) {
|
||||
|
||||
private static final double K_RT_HALF = 1.854; //1.854 is approx K(sqrt(1/2))
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
final double alat = Math.abs(lat);
|
||||
final double wMag = Math.tan(Math.PI/4-alat/2);
|
||||
final Complex w = new Complex(wMag*Math.sin(lon), -wMag*Math.cos(lon)); //this Complex comes from Apache Commons
|
||||
final Complex k = new Complex(Math.sqrt(0.5));
|
||||
Complex z = Elliptic.F(w.acos(),k).multiply(Math.PI/1.854).subtract(Math.PI).negate();
|
||||
Complex z = Elliptic.F(w.acos(),k).multiply(1/K_RT_HALF).subtract(Math.PI).negate();
|
||||
if (z.isInfinite() || z.isNaN()) z = new Complex(0);
|
||||
double x = z.getReal(), y = z.getImaginary();
|
||||
|
||||
@ -146,7 +148,7 @@ public class Misc {
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
mfc.field.Complex u = new mfc.field.Complex(1.854*(x+1), 1.854*y); //1.854 is approx K(sqrt(1/2)
|
||||
mfc.field.Complex u = new mfc.field.Complex(K_RT_HALF*(x+1), K_RT_HALF*y);
|
||||
mfc.field.Complex k = new mfc.field.Complex(Math.sqrt(0.5)); //the rest comes from some fancy complex calculus
|
||||
mfc.field.Complex ans = Jacobi.cn(u, k);
|
||||
double p = 2 * Math.atan(ans.abs());
|
||||
@ -161,6 +163,7 @@ public class Misc {
|
||||
new Projection("Guyou", "Peirce Quincuncial, rearranged a bit", 2., 0b1001,
|
||||
Type.OTHER, Property.CONFORMAL) {
|
||||
|
||||
private final double K_RT_HALF = 1.854; //1.854 is approx K(sqrt(1/2))
|
||||
private final double[] POLE = {0, -Math.PI/2, Math.PI/4};
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
@ -169,15 +172,15 @@ public class Misc {
|
||||
final double wMag = Math.tan(Math.PI/4-alat/2);
|
||||
final Complex w = new Complex(wMag*Math.sin(coords[1]), -wMag*Math.cos(coords[1]));
|
||||
final Complex k = new Complex(Math.sqrt(0.5));
|
||||
Complex z = Elliptic.F(w.acos(),k).multiply(new Complex(Math.PI/3.708,Math.PI/3.708)).subtract(new Complex(0,Math.PI/2));
|
||||
Complex z = Elliptic.F(w.acos(),k).multiply(2*K_RT_HALF).subtract(new Complex(0,0.5));
|
||||
if (z.isInfinite() || z.isNaN()) z = new Complex(0);
|
||||
if (coords[0] < 0) z = z.conjugate().negate();
|
||||
return new double[] {z.getReal(), z.getImaginary()};
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
mfc.field.Complex u = new mfc.field.Complex(1.8558*(x - y/2 - 0.5), 1.8558*(x + y/2 + 0.5)); // don't ask me where 3.7116 comes from
|
||||
mfc.field.Complex k = new mfc.field.Complex(Math.sqrt(0.5)); // the rest comes from some fancy complex calculus
|
||||
mfc.field.Complex u = new mfc.field.Complex(x-y/2-.5, x+y/2+.5).times(K_RT_HALF);
|
||||
mfc.field.Complex k = new mfc.field.Complex(Math.sqrt(0.5)); //just some fancy complex calculus stuff
|
||||
mfc.field.Complex ans = Jacobi.cn(u, k);
|
||||
double p = 2 * Math.atan(ans.abs());
|
||||
double theta = ans.arg();
|
||||
@ -205,10 +208,12 @@ public class Misc {
|
||||
}
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
final double z = Math.acos(Math.sin(phi0)*Math.sin(lat) + Math.cos(phi0)*Math.cos(lat)*Math.cos(lon-lam0));
|
||||
final double K = z/Math.sin(z);
|
||||
final double z = Math.acos(Math.sin(phi0)*Math.sin(lat) +
|
||||
Math.cos(phi0)*Math.cos(lat)*Math.cos(lon-lam0));
|
||||
final double K = z/Math.sin(z)/Math.PI;
|
||||
final double x = K*Math.cos(phi0)*Math.sin(lon-lam0);
|
||||
final double y = -K*(Math.sin(phi0)*Math.cos(lat) - Math.cos(phi0)*Math.sin(lat)*Math.cos(lon-lam0));
|
||||
final double y = -K*(Math.sin(phi0)*Math.cos(lat) -
|
||||
Math.cos(phi0)*Math.sin(lat)*Math.cos(lon-lam0));
|
||||
if (Math.cos(lon-lam0) < 0)
|
||||
return new double[] {-x, -y};
|
||||
else
|
||||
@ -269,8 +274,8 @@ public class Misc {
|
||||
Math.tan(lat1)*Math.sin(lon0-lon2) +
|
||||
Math.tan(lat2)*Math.sin(lon1-lon0));
|
||||
return new double[] {
|
||||
(d1*d1-d2*d2)/(2*D) * Math.PI/a,
|
||||
s*Math.sqrt(d1*d1 - Math.pow((d1*d1-d2*d2+D*D)/(2*D), 2)) * Math.PI/a };
|
||||
(d1*d1-d2*d2)/(2*D)/a,
|
||||
s*Math.sqrt(d1*d1 - Math.pow((d1*d1-d2*d2+D*D)/(2*D), 2))/a };
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y, double[] pole) {
|
||||
|
@ -29,8 +29,8 @@ import maps.Projection.Property;
|
||||
import maps.Projection.Type;
|
||||
|
||||
/**
|
||||
* All of the projections I invented, save the tetrahedral ones,
|
||||
* because those have so much in common with other tetrahedral projections.
|
||||
* All of the projections I invented, save the tetrahedral ones, because
|
||||
* those have so much in common with other tetrahedral projections.
|
||||
*
|
||||
* @author jkunimune
|
||||
*/
|
||||
@ -44,8 +44,7 @@ public class MyProjections {
|
||||
public double[] project(double lat, double lon) {
|
||||
final double p = 1/2.0+lat/Math.PI;
|
||||
final double fp = 1 - 0.1*p - 0.9*Math.pow(p,7);
|
||||
final double r = Math.PI*fp;
|
||||
return new double[] { r*Math.sin(lon), -r*Math.cos(lon) };
|
||||
return new double[] { fp*Math.sin(lon), -fp*Math.cos(lon) };
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
@ -67,13 +66,13 @@ public class MyProjections {
|
||||
public double[] project(double lat, double lon) {
|
||||
final double wMag = Math.tan(Math.PI/4-lat/2);
|
||||
final Complex w = new Complex(wMag*Math.sin(lon), -wMag*Math.cos(lon));
|
||||
Complex z = w.asin();
|
||||
Complex z = w.asin().divide(3);
|
||||
if (z.isInfinite() || z.isNaN()) z = new Complex(0);
|
||||
return new double[] { z.getReal(), z.getImaginary() };
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
Complex z = new Complex((x+1)*Math.PI, y*Math.PI);
|
||||
Complex z = new Complex(3*x+3, y*3);
|
||||
Complex ans = z.sin();
|
||||
double p = 2 * Math.atan(ans.abs());
|
||||
double theta = ans.getArgument() - Math.PI/2;
|
||||
@ -92,8 +91,8 @@ public class MyProjections {
|
||||
final double a = Math.PI - Math.acos(Math.cos(lat)*Math.cos(lon/2));
|
||||
final double b = Math.acos(Math.sin(lat)/Math.sin(a));
|
||||
return new double[] {
|
||||
Math.PI*Math.signum(lon)/Math.tan(a/2)*Math.sin(b),
|
||||
Math.PI/2/Math.tan(a/2)*Math.cos(b) };
|
||||
Math.signum(lon)/Math.tan(a/2)*Math.sin(b),
|
||||
Math.cos(b)/Math.tan(a/2)/2 };
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
@ -123,8 +122,8 @@ public class MyProjections {
|
||||
public double[] project(double lat, double lon) {
|
||||
final double ynorm = (1-Math.pow(1-Math.abs(lat/(Math.PI/2)), n));
|
||||
return new double[] {
|
||||
Math.pow(1 - Math.pow(ynorm, k),1/k)*lon,
|
||||
ynorm*Math.PI/2/Math.sqrt(n)*a*Math.signum(lat) };
|
||||
Math.pow(1 - Math.pow(ynorm, k),1/k)*lon/Math.PI,
|
||||
ynorm/2/Math.sqrt(n)*a*Math.signum(lat) };
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
@ -158,7 +157,7 @@ public class MyProjections {
|
||||
final double d2 = Math.acos(
|
||||
Math.sin(lat)*Math.cos(theta) + Math.cos(lat)*Math.sin(theta)*Math.cos(lon));
|
||||
final double k = Math.signum(lon)*Math.sqrt(Math.tan(theta)/theta);
|
||||
final double s = Math.PI/(Math.PI-theta/2);
|
||||
final double s = 1/(Math.PI-theta/2);
|
||||
return new double[] {
|
||||
s*k*Math.sqrt(d1*d1 - Math.pow((d1*d1-d2*d2+4*theta*theta)/(4*theta), 2)),
|
||||
s*(d2*d2-d1*d1)/(4*theta) };
|
||||
|
@ -24,6 +24,7 @@
|
||||
package maps;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.DoubleConsumer;
|
||||
|
||||
|
@ -38,7 +38,7 @@ public class Pseudocylindrical {
|
||||
2., 0b1111, Type.PSEUDOCYLINDRICAL, Property.EQUAL_AREA) {
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
return new double[] { Math.cos(lat)*lon, lat };
|
||||
return new double[] { Math.cos(lat)*lon/Math.PI, lat/Math.PI };
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
@ -56,7 +56,7 @@ public class Pseudocylindrical {
|
||||
for (int i = 0; i < 10; i ++)
|
||||
tht -= (2*tht+Math.sin(2*tht)-Math.PI*Math.sin(lat))/
|
||||
(2+2*Math.cos(2*tht));
|
||||
return new double[] { lon*Math.cos(tht), Math.PI/2*Math.sin(tht) };
|
||||
return new double[] { lon*Math.cos(tht)/Math.PI, Math.sin(tht)/2 };
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
@ -81,15 +81,17 @@ public class Pseudocylindrical {
|
||||
final int lemNum = (int)Math.floor(lon/LEM_SPAN);
|
||||
final double dl = (lon+2*Math.PI) % LEM_SPAN - LEM_SPAN/2;
|
||||
return new double[] {
|
||||
Math.asin(Math.cos(lat)*Math.sin(dl)) + (lemNum+.5)*LEM_SPAN,
|
||||
Math.asin(Math.sin(lat)/Math.sqrt(1-Math.pow(Math.cos(lat)*Math.sin(dl), 2))) };
|
||||
Math.asin(Math.cos(lat)*Math.sin(dl))/Math.PI + (lemNum+.5)*LEM_WIDTH,
|
||||
Math.asin(Math.sin(lat)/Math.sqrt(1-Math.pow(Math.cos(lat)*Math.sin(dl), 2)))
|
||||
/Math.PI };
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
final int lemNum = (int)Math.floor(x/LEM_WIDTH);
|
||||
final double dx = (x+2) % LEM_WIDTH * Math.PI - LEM_SPAN/2;
|
||||
y = y*Math.PI/2;
|
||||
final double dl = Math.asin(Math.sin(dx)/Math.sqrt(1-Math.pow(Math.cos(dx)*Math.sin(y), 2)));
|
||||
final double dl = Math.asin(
|
||||
Math.sin(dx)/Math.sqrt(1-Math.pow(Math.cos(dx)*Math.sin(y), 2)));
|
||||
if (Math.abs(dl) > LEM_SPAN/2)
|
||||
return null;
|
||||
else
|
||||
|
@ -55,8 +55,8 @@ public class Robinson {
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
return new double[] {
|
||||
lon*smartInterpolate(Math.toDegrees(lat), TABLE[0], TABLE[1], ORDER),
|
||||
Math.PI/2*smartInterpolate(Math.toDegrees(lat), TABLE[0], TABLE[2], ORDER) };
|
||||
lon/Math.PI*smartInterpolate(Math.toDegrees(lat), TABLE[0], TABLE[1], ORDER),
|
||||
.5*smartInterpolate(Math.toDegrees(lat), TABLE[0], TABLE[2], ORDER) };
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
|
@ -41,7 +41,7 @@ public class Tetrahedral {
|
||||
final mfc.field.Complex z = mfc.field.Complex.fromPolar(
|
||||
Math.pow(2, 5/6.)*Math.tan(Math.PI/4-lat/2), lon);
|
||||
final mfc.field.Complex w = Dixon.invFunc(z);
|
||||
return new double[] { w.abs()*1.186, w.arg() };
|
||||
return new double[] { w.abs()*.3775, w.arg() };
|
||||
}
|
||||
|
||||
public double[] innerInverse(double r, double tht) {
|
||||
@ -62,7 +62,7 @@ public class Tetrahedral {
|
||||
public double[] innerProject(double lat, double lon) {
|
||||
final double tht = lon - Math.floor(lon/(2*Math.PI/3))*(2*Math.PI/3) - Math.PI/3;
|
||||
return new double[] {
|
||||
Math.atan(1/Math.tan(lat)*Math.cos(tht))/Math.cos(tht)*Math.PI/3/Math.atan(Math.sqrt(2)),
|
||||
Math.atan(1/Math.tan(lat)*Math.cos(tht))/Math.cos(tht)/3/Math.atan(Math.sqrt(2)),
|
||||
lon };
|
||||
}
|
||||
|
||||
@ -98,7 +98,7 @@ public class Tetrahedral {
|
||||
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(lat)*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,
|
||||
(1 - Math.pow(1-rtgf,kRad))/(1 - Math.pow(1-rmax,kRad))*rmax*2/3,
|
||||
thtP + t0 };
|
||||
}
|
||||
|
||||
@ -140,7 +140,7 @@ public class Tetrahedral {
|
||||
else rmax = .75 - 1.5972774*Math.pow(Math.PI/3-Math.abs(thtP),2)/2;
|
||||
final double rtgf = Math.atan(1/Math.tan(lat)*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,
|
||||
(1 - Math.pow(1-rtgf,kRad))/(1 - Math.pow(1-rmax,kRad))*rmax*2/3,
|
||||
thtP + t0
|
||||
};
|
||||
}
|
||||
@ -185,7 +185,7 @@ public class Tetrahedral {
|
||||
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(lat)*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,
|
||||
(1 - Math.pow(1-rtgf,kRad))/(1 - Math.pow(1-rmax,kRad))*rmax*2/3,
|
||||
thtP + t0 };
|
||||
}
|
||||
|
||||
@ -322,23 +322,23 @@ public class Tetrahedral {
|
||||
case 0:
|
||||
if (Math.sin(lon) < 0)
|
||||
return new double[]
|
||||
{-2*Math.PI/3 + rtht[0]*Math.sin(rtht[1]-Math.PI/6), -Math.PI/Math.sqrt(3) - rtht[0]*Math.cos(rtht[1]-Math.PI/6)}; // lower left
|
||||
{-2/3. + rtht[0]*Math.sin(rtht[1]-Math.PI/6), -1/Math.sqrt(3) - rtht[0]*Math.cos(rtht[1]-Math.PI/6)}; // lower left
|
||||
else
|
||||
return new double[]
|
||||
{2*Math.PI/3 - rtht[0]*Math.sin(rtht[1]-Math.PI/6), -Math.PI/Math.sqrt(3) + rtht[0]*Math.cos(rtht[1]-Math.PI/6)}; // lower right
|
||||
{2/3. - rtht[0]*Math.sin(rtht[1]-Math.PI/6), -1/Math.sqrt(3) + rtht[0]*Math.cos(rtht[1]-Math.PI/6)}; // lower right
|
||||
case 1:
|
||||
if (Math.sin(lon) < 0)
|
||||
return new double[]
|
||||
{-2*Math.PI/3 + rtht[0]*Math.sin(rtht[1]-Math.PI/6), Math.PI/Math.sqrt(3) - rtht[0]*Math.cos(rtht[1]-Math.PI/6)}; // upper left
|
||||
{-2/3. + rtht[0]*Math.sin(rtht[1]-Math.PI/6), 1/Math.sqrt(3) - rtht[0]*Math.cos(rtht[1]-Math.PI/6)}; // upper left
|
||||
else
|
||||
return new double[]
|
||||
{2*Math.PI/3 - rtht[0]*Math.sin(rtht[1]-Math.PI/6), Math.PI/Math.sqrt(3) + rtht[0]*Math.cos(rtht[1]-Math.PI/6)}; // upper right
|
||||
{2/3. - rtht[0]*Math.sin(rtht[1]-Math.PI/6), 1/Math.sqrt(3) + rtht[0]*Math.cos(rtht[1]-Math.PI/6)}; // upper right
|
||||
case 2:
|
||||
return new double[]
|
||||
{Math.PI/3 + rtht[0]*Math.cos(rtht[1]), rtht[0]*Math.sin(rtht[1])}; // right
|
||||
{1/3. + rtht[0]*Math.cos(rtht[1]), rtht[0]*Math.sin(rtht[1])}; // right
|
||||
case 3:
|
||||
return new double[]
|
||||
{-Math.PI/3 - rtht[0]*Math.cos(rtht[1]), -rtht[0]*Math.sin(rtht[1])}; // left
|
||||
{-1/3. - rtht[0]*Math.cos(rtht[1]), -rtht[0]*Math.sin(rtht[1])}; // left
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
@ -70,8 +70,8 @@ public class Tobler {
|
||||
y = Math2.linInterp(z0, Z[-i-2], Z[-i-1], -i-2, -i-1)/
|
||||
(Z.length-1.);
|
||||
return new double[] {
|
||||
lon * Math.abs(alpha + (1-alpha)*hyperEllipse(y)),
|
||||
y * Math.signum(lat)/aspectRatio*Math.PI };
|
||||
lon * Math.abs(alpha + (1-alpha)*hyperEllipse(y))/Math.PI,
|
||||
y * Math.signum(lat)/aspectRatio };
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
|
@ -27,14 +27,15 @@ import maps.Projection.Property;
|
||||
import maps.Projection.Type;
|
||||
|
||||
/**
|
||||
* TODO: Write description
|
||||
* A polyhedral projection that's so interrupted it can get away with gnomonic
|
||||
*
|
||||
* @author jkunimune
|
||||
*/
|
||||
public class Waterman {
|
||||
|
||||
public static final Projection WATERMAN =
|
||||
new Projection("Waterman Butterfly", "An aesthetically pleasing octohedral map arrangement",
|
||||
new Projection(
|
||||
"Waterman Butterfly", "An aesthetically pleasing octohedral map arrangement",
|
||||
0, 0b1110, Type.POLYHEDRAL, Property.COMPROMISE) {
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
|
@ -36,10 +36,10 @@ import utils.NumericalAnalysis;
|
||||
*
|
||||
* Ipbüker, Cengizhan; Bildirici, I.Öztug (2002). "A General Algorithm for the
|
||||
* Inverse Transformation of Map Projections Using Jacobian Matrices".
|
||||
* Proceedings of the Third International Symposium Mathematical &
|
||||
* Computational Applications. Third International Symposium Mathematical &
|
||||
* Proceedings of the Third International Symposium Mathematical &
|
||||
* Computational Applications. Third International Symposium Mathematical &
|
||||
* Computational Applications September 4-6, 2002. Konya, Turkey. Selcuk,
|
||||
* Turkey. pp. 175—182. Archived from the original on 20 October 2014.
|
||||
* Turkey. pp. 175-182. Archived from the original on 20 October 2014.
|
||||
*
|
||||
* @author jkunimune
|
||||
*/
|
||||
@ -60,8 +60,7 @@ public final class WinkelTripel {
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
return NumericalAnalysis.newtonRaphsonApproximation(
|
||||
x*Math.PI*aspectRatio, y*Math.PI,
|
||||
y*Math.PI/2, x*Math.PI*(1 + Math.cos(y*Math.PI/2))/2,
|
||||
x*aspectRatio, y, y/2, x*(1 + Math.cos(y*Math.PI/2))/2,
|
||||
this::f1pX, this::f2pY,
|
||||
this::df1dphi, this::df1dlam, this::df2dphi, this::df2dlam, .002);
|
||||
}
|
||||
@ -69,13 +68,14 @@ public final class WinkelTripel {
|
||||
private double f1pX(double phi, double lam) {
|
||||
final double d = D(phi,lam);
|
||||
final double c = C(phi,lam);
|
||||
return 2*d/Math.sqrt(c)*Math.cos(phi)*Math.sin(lam/2) + lam*(aspectRatio-1);
|
||||
return 2/Math.PI*d/Math.sqrt(c)*Math.cos(phi)*Math.sin(lam/2)
|
||||
+ lam/Math.PI*(aspectRatio-1);
|
||||
}
|
||||
|
||||
private double f2pY(double phi, double lam) {
|
||||
final double d = D(phi,lam);
|
||||
final double c = C(phi,lam);
|
||||
return d/Math.sqrt(c)*Math.sin(phi) + phi;
|
||||
return d/Math.sqrt(c)*Math.sin(phi)/Math.PI + phi/Math.PI;
|
||||
}
|
||||
|
||||
private double df1dphi(double phi, double lam) {
|
||||
|
@ -30,8 +30,8 @@ import mfc.field.Complex;
|
||||
* All the algorithms here came directly from L.P. Lee's paper,
|
||||
* "Some Conformal Projections Based on Elliptic Functions"
|
||||
*
|
||||
* Lee, L. P. <EFBFBD>Some Conformal Projections Based on Elliptic Functions.<EFBFBD>
|
||||
* Geographical Review, vol. 55, no. 4, 1965, pp. 563<EFBFBD>580. JSTOR,
|
||||
* Lee, L. P. "Some Conformal Projections Based on Elliptic Functions."
|
||||
* Geographical Review, vol. 55, no. 4, 1965, pp. 563-580. JSTOR,
|
||||
* www.jstor.org/stable/212415.
|
||||
*
|
||||
* @author jkunimune
|
||||
|
@ -23,8 +23,6 @@
|
||||
*/
|
||||
package utils;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
/**
|
||||
* A collection of (currently just one) methods for dealing with images and coordinates
|
||||
*
|
||||
@ -32,22 +30,6 @@ import java.awt.image.BufferedImage;
|
||||
*/
|
||||
public class ImageUtils {
|
||||
|
||||
public static int getArgb(double[] coords, BufferedImage input) { // returns the color of any coordinate on earth
|
||||
if (coords == null) return 0;
|
||||
|
||||
double x = 1/2.0 + coords[1]/(2*Math.PI);
|
||||
x = (x - Math.floor(x)) * input.getWidth();
|
||||
|
||||
double y = input.getHeight()*(.5 - coords[0]/Math.PI);
|
||||
if (y < 0)
|
||||
y = 0;
|
||||
else if (y >= input.getHeight())
|
||||
y = input.getHeight() - 1;
|
||||
|
||||
return (0xFF000000) | input.getRGB((int) x, (int) y);
|
||||
}
|
||||
|
||||
|
||||
public static final int blend(int[] colors) {
|
||||
return blend(colors, 2.2);
|
||||
}
|
||||
|
59
src/utils/PixelMap.java
Normal file
59
src/utils/PixelMap.java
Normal file
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
/**
|
||||
* An input equirectangular map based on a raster image file
|
||||
*
|
||||
* @author jkunimune
|
||||
*/
|
||||
public class PixelMap {
|
||||
|
||||
private BufferedImage pixels;
|
||||
|
||||
|
||||
public PixelMap(File f) throws IOException {
|
||||
pixels = ImageIO.read(f);
|
||||
}
|
||||
|
||||
|
||||
public int getArgb(double lat, double lon) {
|
||||
double x = 1/2.0 + lon/(2*Math.PI);
|
||||
x = (x - Math.floor(x)) * pixels.getWidth();
|
||||
|
||||
double y = pixels.getHeight()*(.5 - lat/Math.PI);
|
||||
if (y < 0)
|
||||
y = 0;
|
||||
else if (y >= pixels.getHeight())
|
||||
y = pixels.getHeight() - 1;
|
||||
|
||||
return (0xFF000000) | pixels.getRGB((int) x, (int) y);
|
||||
}
|
||||
}
|
334
src/utils/SVGMap.java
Normal file
334
src/utils/SVGMap.java
Normal file
@ -0,0 +1,334 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
import java.util.function.DoubleConsumer;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.parsers.SAXParser;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
|
||||
/**
|
||||
* An input equirectangular map based on an SVG file
|
||||
*
|
||||
* @author jkunimune
|
||||
*/
|
||||
public class SVGMap implements Iterable<SVGMap.Path> {
|
||||
|
||||
public double[] NULL_TRANSFORM = {1, 1, 0, 0};
|
||||
|
||||
private List<Path> paths; //the set of closed curves in this image
|
||||
private List<String> format; //the stuff that goes between the curve descriptions, probably important for something.
|
||||
private int minX, minY, width, height; //the SVG viewBox
|
||||
private int size; //the total number of path commands, for optimization purposes
|
||||
|
||||
|
||||
|
||||
public SVGMap(File file) throws IOException, SAXException, ParserConfigurationException {
|
||||
paths = new LinkedList<Path>();
|
||||
format = new LinkedList<String>();
|
||||
|
||||
final SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
|
||||
|
||||
final DefaultHandler handler = new DefaultHandler() {
|
||||
|
||||
private Stack<double[]> transformStack = new Stack<double[]>();
|
||||
private String currentFormatString = "";
|
||||
|
||||
@Override
|
||||
public InputSource resolveEntity(String publicId, String systemId) {
|
||||
return new InputSource(new StringReader("")); //ignore all external references - we don't need them
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startElement(String uri, String localName, String qName, Attributes attributes) {
|
||||
currentFormatString += "<"+qName;
|
||||
|
||||
if (qName.equals("path"))
|
||||
parsePath(attributes.getValue("d"));
|
||||
if (qName.equals("svg"))
|
||||
parseViewBox(attributes.getValue("viewBox"));
|
||||
|
||||
for (int i = 0; i < attributes.getLength(); i ++)
|
||||
if (!attributes.getQName(i).equals("d") && //d is already taken care of
|
||||
!attributes.getQName(i).equals("transform")) //there shall be no transforms in the final output
|
||||
currentFormatString +=
|
||||
" "+attributes.getQName(i)+"="+attributes.getValue(i);
|
||||
currentFormatString += ">\n";
|
||||
|
||||
if (attributes.getIndex("transform") >= 0)
|
||||
parseTransform(attributes.getValue("transform"));
|
||||
else
|
||||
parseTransform();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endElement(String uri, String localName, String qName) {
|
||||
currentFormatString += "</"+qName+">";
|
||||
transformStack.pop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void characters(char[] ch, int start, int length) {
|
||||
for (int i = 0; i < length; i ++)
|
||||
currentFormatString += ch[start+i];
|
||||
}
|
||||
|
||||
private void parseViewBox(String viewBox) {
|
||||
String[] values = viewBox.split("\\s", 4);
|
||||
minX = Integer.parseInt(values[0]);
|
||||
minY = Integer.parseInt(values[1]);
|
||||
width = Integer.parseInt(values[2]);
|
||||
height = Integer.parseInt(values[3]);
|
||||
}
|
||||
|
||||
private void parseTransform() {
|
||||
if (transformStack.isEmpty())
|
||||
transformStack.push(NULL_TRANSFORM);
|
||||
else
|
||||
transformStack.push(transformStack.peek());
|
||||
}
|
||||
|
||||
private void parseTransform(String transString) {
|
||||
double xScale = 1, yScale = 1, xTrans = 0, yTrans = 0;
|
||||
int i;
|
||||
while ((i = transString.indexOf('(')) >= 0) {
|
||||
int j = transString.indexOf(')');
|
||||
String type = transString.substring(0, i).trim();
|
||||
String argString = transString.substring(i+1, j);
|
||||
String[] args = argString.split("[,\\s]+");
|
||||
if (type.equals("matrix")) {
|
||||
xScale = Double.parseDouble(args[0]);
|
||||
yScale = Double.parseDouble(args[3]);
|
||||
xTrans = Double.parseDouble(args[4]);
|
||||
yTrans = Double.parseDouble(args[5]);
|
||||
}
|
||||
else if (type.equals("translate")) {
|
||||
if (args.length == 1) {
|
||||
xTrans = yTrans = Double.parseDouble(args[0]);
|
||||
}
|
||||
else {
|
||||
xTrans = Double.parseDouble(args[0]);
|
||||
yTrans = Double.parseDouble(args[1]);
|
||||
}
|
||||
}
|
||||
else if (type.equals("scale")) {
|
||||
if (args.length == 1) {
|
||||
xScale = yScale = Double.parseDouble(args[0]);
|
||||
}
|
||||
else {
|
||||
xScale = Double.parseDouble(args[0]);
|
||||
yScale = Double.parseDouble(args[1]);
|
||||
}
|
||||
} //I'm not worrying about shear and rotation because I don't want to
|
||||
transString = transString.substring(j+1);
|
||||
}
|
||||
transformStack.push(new double[] {xScale, yScale, xTrans, yTrans});
|
||||
}
|
||||
|
||||
private void parsePath(String d) {
|
||||
currentFormatString += " d=\"";
|
||||
format.add(currentFormatString);
|
||||
paths.add(new Path(d, transformStack.peek(), minX, minY, width, height));
|
||||
currentFormatString = "\"";
|
||||
size += paths.get(paths.size()-1).length();
|
||||
}
|
||||
};
|
||||
|
||||
parser.parse(new BufferedInputStream(new FileInputStream(file)), handler);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public int size() {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
|
||||
public int numCurves() {
|
||||
return paths.size();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Iterator<Path> iterator() {
|
||||
return paths.iterator();
|
||||
}
|
||||
|
||||
|
||||
public void save(Iterable<Path> paths, File file, DoubleConsumer tracker) throws IOException {
|
||||
BufferedWriter out = new BufferedWriter(new FileWriter(file));
|
||||
|
||||
final Iterator<String> formatIterator = format.iterator();
|
||||
final Iterator<Path> curveIterator = paths.iterator();
|
||||
while (curveIterator.hasNext()) {
|
||||
out.write(formatIterator.next());
|
||||
out.write(curveIterator.next().toString(
|
||||
minX, minY, Math.min(width, height), Math.min(width, height)));
|
||||
// tracker.accept((double)i/curves.size());
|
||||
}
|
||||
out.write(formatIterator.next());
|
||||
out.close();
|
||||
}
|
||||
|
||||
|
||||
public static boolean isLetter(char c) {
|
||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An svg path String, stored in a modifiable form
|
||||
* @author jkunimune
|
||||
*/
|
||||
public static class Path implements Iterable<Command> {
|
||||
|
||||
final private List<Command> commands;
|
||||
|
||||
public Path() {
|
||||
commands = new ArrayList<Command>();
|
||||
}
|
||||
|
||||
public Path(String d, double vbWidth, double vbHeight) {
|
||||
this(d, new double[] {1,1,0,0}, 0, 0, vbWidth, vbHeight);
|
||||
}
|
||||
|
||||
public Path(String d, double[] transform,
|
||||
double vbMinX, double vbMinY, double vbWidth, double vbHeight) {
|
||||
commands = new ArrayList<Command>();
|
||||
int i = 0;
|
||||
double[] last = new double[] {0,0}; //for relative coordinates
|
||||
while (i < d.length()) {
|
||||
char type = d.charAt(i);
|
||||
String argString = "";
|
||||
while (i+1 < d.length() && !isLetter(d.charAt(i+1))) {
|
||||
i ++;
|
||||
argString += d.charAt(i);
|
||||
}
|
||||
|
||||
String[] argStrings;
|
||||
if (argString.isEmpty()) argStrings = new String[0];
|
||||
else argStrings = argString.trim().split("[\\s,]+");
|
||||
double[] args = new double[argStrings.length];
|
||||
for (int j = 0; j < args.length; j ++) {
|
||||
args[j] = Double.parseDouble(argStrings[j]); //parse the coordinate
|
||||
if (j%2 == 0) {
|
||||
args[j] = args[j]*transform[0] + transform[2]; //apply the transformation
|
||||
args[j] = Math2.linInterp(args[j], vbMinX, vbMinX+vbWidth,
|
||||
-Math.PI, Math.PI); //scale to radians
|
||||
}
|
||||
else {
|
||||
args[j] = args[j]*transform[1] + transform[3];
|
||||
args[j] = Math2.linInterp(args[j], vbMinY+vbHeight, vbMinY, //keep in mind that these are paired longitude-latitude
|
||||
-Math.PI/2, Math.PI/2); //not latitude-longitude, as they are elsewhere
|
||||
}
|
||||
}
|
||||
if (type >= 'a') { //make all letters uppercase
|
||||
type -= 32;
|
||||
for (int j = 0; j < args.length; j ++)
|
||||
args[j] += last[j%2];
|
||||
}
|
||||
if (args.length >= 2) {
|
||||
last[0] = args[args.length-2];
|
||||
last[1] = args[args.length-1];
|
||||
}
|
||||
|
||||
commands.add(new Command(type, args));
|
||||
i ++;
|
||||
}
|
||||
}
|
||||
|
||||
public Iterator<Command> iterator() {
|
||||
return commands.iterator();
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return commands.size();
|
||||
}
|
||||
|
||||
public boolean add(Command c) {
|
||||
return commands.add(c);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return this.toString(-1, -1, 2, 2);
|
||||
}
|
||||
|
||||
public String toString(double minX, double minY, double width, double height) {
|
||||
String s = "";
|
||||
for (Command c: commands)
|
||||
s += c.toString(minX, minY, width, height)+" ";
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* An SVG command, like line or bezier curve or whatever
|
||||
* @author jkunimune
|
||||
*/
|
||||
public static class Command {
|
||||
final public char type; //M, L, C, etc. This will never be lowercase
|
||||
final public double[] args; //the absolute coordinates that go with it
|
||||
|
||||
public Command(char type, double[] args) {
|
||||
this.type = type;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return this.toString(-1, -1, 2, 2);
|
||||
}
|
||||
|
||||
public String toString(double minX, double minY, double width, double height) {
|
||||
String s = type+" ";
|
||||
for (int i = 0; i < args.length; i ++) {
|
||||
if (i%2 == 0)
|
||||
s += Math2.linInterp(args[0], -1, 1, minX, minX+width)+",";
|
||||
else
|
||||
s += Math2.linInterp(args[1], -1, 1, minY+height, minY)+",";
|
||||
}
|
||||
return s.substring(0, s.length()-1);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user