Use a better projection for BasicDrawing.
This commit is contained in:
parent
0030ab7aef
commit
f3964f1958
@ -51,6 +51,36 @@ public class GraphStatistics {
|
|||||||
return topLeft;
|
return topLeft;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new bounding box by extending the current one according to the given
|
||||||
|
* value for each side.
|
||||||
|
*
|
||||||
|
* @param left Extra size to add to the left of the box.
|
||||||
|
* @param top Extra size to add to the top of the box.
|
||||||
|
* @param right Extra size to add to the right of the box.
|
||||||
|
* @param bottom Extra size to add to the bottom of the box.
|
||||||
|
*
|
||||||
|
* @return New bounding box corresponding to an extension of the current one.
|
||||||
|
*/
|
||||||
|
public BoundingBox extend(float left, float top, float right, float bottom) {
|
||||||
|
return new BoundingBox(
|
||||||
|
new Point(this.topLeft.getLongitude() - left, this.topLeft.getLatitude() + top),
|
||||||
|
new Point(this.bottomRight.getLongitude() + right,
|
||||||
|
this.bottomRight.getLatitude() - bottom));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new bounding box by extending the current one according by the given
|
||||||
|
* value on each side.
|
||||||
|
*
|
||||||
|
* @param size Extra size to add to each side of this box.
|
||||||
|
*
|
||||||
|
* @return New bounding box corresponding to an extension of the current one.
|
||||||
|
*/
|
||||||
|
public BoundingBox extend(float size) {
|
||||||
|
return this.extend(size, size, size, size);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bounding box for this graph.
|
// Bounding box for this graph.
|
||||||
|
144
src/main/org/insa/graphics/drawing/MercatorProjection.java
Normal file
144
src/main/org/insa/graphics/drawing/MercatorProjection.java
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
package org.insa.graphics.drawing;
|
||||||
|
|
||||||
|
import java.awt.Dimension;
|
||||||
|
|
||||||
|
import org.insa.graph.GraphStatistics.BoundingBox;
|
||||||
|
|
||||||
|
public class MercatorProjection {
|
||||||
|
|
||||||
|
public static final double MAX_LATITUDE = 82;
|
||||||
|
|
||||||
|
public static final double MIN_LATITUDE = -MAX_LATITUDE;
|
||||||
|
|
||||||
|
// From Wikipedia... for the above max/min latitude.
|
||||||
|
private static final double IMAGE_WIDTH = 2058, IMAGE_HEIGHT = 1746;
|
||||||
|
|
||||||
|
private static final double MAX_LATITUDE_PROJ = projectY(MAX_LATITUDE);
|
||||||
|
private static final double MIN_LATITUDE_PROJ = projectY(MIN_LATITUDE);
|
||||||
|
|
||||||
|
// Bounding box
|
||||||
|
private final float minLatitude, minLongitude, maxLatitude, maxLongitude;
|
||||||
|
|
||||||
|
// Projection of min and max latitude.
|
||||||
|
private final double minLatitudeProj, maxLatitudeProj;
|
||||||
|
|
||||||
|
// Dimension of the image
|
||||||
|
private final double width, height;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new MercatorProjection corresponding to the given BoundingBox and
|
||||||
|
* maxSize.
|
||||||
|
*
|
||||||
|
* @param boundingBox Box for this projection.
|
||||||
|
* @param maxSize Maximum size of any side (width / height) of the image to
|
||||||
|
* which this projection should draw.
|
||||||
|
*/
|
||||||
|
public MercatorProjection(BoundingBox boundingBox, int maxSize) {
|
||||||
|
// Find minimum/maximum longitude and latitude.
|
||||||
|
this.minLongitude = boundingBox.getTopLeftPoint().getLongitude();
|
||||||
|
this.maxLongitude = boundingBox.getBottomRightPoint().getLongitude();
|
||||||
|
this.minLatitude = boundingBox.getBottomRightPoint().getLatitude();
|
||||||
|
this.maxLatitude = boundingBox.getTopLeftPoint().getLatitude();
|
||||||
|
|
||||||
|
// Compute projection
|
||||||
|
this.minLatitudeProj = projectY(this.minLatitude);
|
||||||
|
this.maxLatitudeProj = projectY(this.maxLatitude);
|
||||||
|
|
||||||
|
Dimension imageDimension = computeImageSize(maxSize);
|
||||||
|
this.width = imageDimension.getWidth();
|
||||||
|
this.height = imageDimension.getHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Image width for this projection to work properly.
|
||||||
|
*/
|
||||||
|
public double getImageWidth() {
|
||||||
|
return this.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Image weight for this projection to work properly.
|
||||||
|
*/
|
||||||
|
public double getImageHeight() {
|
||||||
|
return this.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the projection (without scaling) of the given latitude.
|
||||||
|
*
|
||||||
|
* @param latitude Latitude to project.
|
||||||
|
*
|
||||||
|
* @return Projection of the given latitude (without scaling).
|
||||||
|
*/
|
||||||
|
private static double projectY(double latitude) {
|
||||||
|
double sinLatitude = Math.sin(latitude * Math.PI / 180.0);
|
||||||
|
return Math.log((1 + sinLatitude) / (1 - sinLatitude)) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the dimension required for drawing a projection of the given box on
|
||||||
|
* an image, ensuring that none of the side of image is greater than maxSize.
|
||||||
|
*
|
||||||
|
* @param maxSize Maximum side of any side of the image.
|
||||||
|
*
|
||||||
|
* @return Dimension corresponding to the preferred size for the image.
|
||||||
|
*/
|
||||||
|
protected Dimension computeImageSize(int maxSize) {
|
||||||
|
double propWidth = (maxLongitude - minLongitude) * IMAGE_WIDTH / 360.0;
|
||||||
|
double propHeight = (this.maxLatitudeProj - this.minLatitudeProj)
|
||||||
|
/ (MAX_LATITUDE_PROJ - MIN_LATITUDE_PROJ) * IMAGE_HEIGHT;
|
||||||
|
|
||||||
|
return propWidth < propHeight
|
||||||
|
? new Dimension((int) (maxSize * propWidth / propHeight), maxSize)
|
||||||
|
: new Dimension(maxSize, (int) (maxSize * propHeight / propWidth));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project the given latitude on the image.
|
||||||
|
*
|
||||||
|
* @param latitude Latitude to project.
|
||||||
|
*
|
||||||
|
* @return Projected position of the latitude on the image.
|
||||||
|
*/
|
||||||
|
public int latitudeToPixelY(float latitude) {
|
||||||
|
return (int) ((this.maxLatitudeProj - projectY(latitude))
|
||||||
|
/ (this.maxLatitudeProj - this.minLatitudeProj) * this.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project the given longitude on the image.
|
||||||
|
*
|
||||||
|
* @param longitude Longitude to project.
|
||||||
|
*
|
||||||
|
* @return Projected position of the longitude on the image.
|
||||||
|
*/
|
||||||
|
public int longitudeToPixelX(float longitude) {
|
||||||
|
return (int) (width * (longitude - minLongitude) / (maxLongitude - minLongitude));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the latitude associated to the given projected point.
|
||||||
|
*
|
||||||
|
* @param py Projected y-position for which latitude should be retrieved.
|
||||||
|
*
|
||||||
|
* @return The original latitude of the point.
|
||||||
|
*/
|
||||||
|
public float pixelYToLatitude(double py) {
|
||||||
|
float y = (float) (this.maxLatitudeProj
|
||||||
|
- (py / this.height) * (this.maxLatitudeProj - this.minLatitudeProj));
|
||||||
|
return (float) (180 * (2 * Math.atan(Math.exp(y)) - Math.PI / 2) / Math.PI);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the longitude associated to the given projected point.
|
||||||
|
*
|
||||||
|
* @param px Projected x-position for which longitude should be retrieved.
|
||||||
|
*
|
||||||
|
* @return The original longitude of the point.
|
||||||
|
*/
|
||||||
|
public float pixelXToLongitude(double px) {
|
||||||
|
return (float) ((px / this.width) * (this.maxLongitude - this.minLongitude)
|
||||||
|
+ this.minLongitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -31,6 +31,7 @@ import org.insa.graphics.drawing.BasicGraphPalette;
|
|||||||
import org.insa.graphics.drawing.Drawing;
|
import org.insa.graphics.drawing.Drawing;
|
||||||
import org.insa.graphics.drawing.DrawingClickListener;
|
import org.insa.graphics.drawing.DrawingClickListener;
|
||||||
import org.insa.graphics.drawing.GraphPalette;
|
import org.insa.graphics.drawing.GraphPalette;
|
||||||
|
import org.insa.graphics.drawing.MercatorProjection;
|
||||||
import org.insa.graphics.drawing.overlays.MarkerOverlay;
|
import org.insa.graphics.drawing.overlays.MarkerOverlay;
|
||||||
import org.insa.graphics.drawing.overlays.MarkerUtils;
|
import org.insa.graphics.drawing.overlays.MarkerUtils;
|
||||||
import org.insa.graphics.drawing.overlays.Overlay;
|
import org.insa.graphics.drawing.overlays.Overlay;
|
||||||
@ -146,8 +147,8 @@ public class BasicDrawing extends JPanel implements Drawing {
|
|||||||
@Override
|
@Override
|
||||||
public void drawImpl(Graphics2D graphics) {
|
public void drawImpl(Graphics2D graphics) {
|
||||||
|
|
||||||
int px = BasicDrawing.this.projx(getPoint().getLongitude());
|
int px = projection.longitudeToPixelX(getPoint().getLongitude());
|
||||||
int py = BasicDrawing.this.projy(getPoint().getLatitude());
|
int py = projection.latitudeToPixelY(getPoint().getLatitude());
|
||||||
|
|
||||||
graphics.drawImage(this.image, px - MARKER_WIDTH / 2, py - MARKER_HEIGHT, MARKER_WIDTH,
|
graphics.drawImage(this.image, px - MARKER_WIDTH / 2, py - MARKER_HEIGHT, MARKER_WIDTH,
|
||||||
MARKER_HEIGHT, BasicDrawing.this);
|
MARKER_HEIGHT, BasicDrawing.this);
|
||||||
@ -193,10 +194,10 @@ public class BasicDrawing extends JPanel implements Drawing {
|
|||||||
while (itPoint.hasNext()) {
|
while (itPoint.hasNext()) {
|
||||||
Point curr = itPoint.next();
|
Point curr = itPoint.next();
|
||||||
|
|
||||||
int x1 = BasicDrawing.this.projx(prev.getLongitude());
|
int x1 = projection.longitudeToPixelX(prev.getLongitude());
|
||||||
int x2 = BasicDrawing.this.projx(curr.getLongitude());
|
int x2 = projection.longitudeToPixelX(curr.getLongitude());
|
||||||
int y1 = BasicDrawing.this.projy(prev.getLatitude());
|
int y1 = projection.latitudeToPixelY(prev.getLatitude());
|
||||||
int y2 = BasicDrawing.this.projy(curr.getLatitude());
|
int y2 = projection.latitudeToPixelY(curr.getLatitude());
|
||||||
|
|
||||||
graphics.drawLine(x1, y1, x2, y2);
|
graphics.drawLine(x1, y1, x2, y2);
|
||||||
|
|
||||||
@ -253,8 +254,8 @@ public class BasicDrawing extends JPanel implements Drawing {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addPoint(Point point) {
|
public void addPoint(Point point) {
|
||||||
int x = BasicDrawing.this.projx(point.getLongitude()) - this.width / 2;
|
int x = projection.longitudeToPixelX(point.getLongitude()) - this.width / 2;
|
||||||
int y = BasicDrawing.this.projy(point.getLatitude()) - this.width / 2;
|
int y = projection.latitudeToPixelY(point.getLatitude()) - this.width / 2;
|
||||||
this.graphics.fillOval(x, y, this.width, this.width);
|
this.graphics.fillOval(x, y, this.width, this.width);
|
||||||
BasicDrawing.this.repaint();
|
BasicDrawing.this.repaint();
|
||||||
}
|
}
|
||||||
@ -294,7 +295,7 @@ public class BasicDrawing extends JPanel implements Drawing {
|
|||||||
// Maximum width for the drawing (in pixels).
|
// Maximum width for the drawing (in pixels).
|
||||||
private static final int MAXIMUM_DRAWING_WIDTH = 2000;
|
private static final int MAXIMUM_DRAWING_WIDTH = 2000;
|
||||||
|
|
||||||
private double long1, long2, lat1, lat2;
|
private MercatorProjection projection;
|
||||||
|
|
||||||
// Width and height of the image
|
// Width and height of the image
|
||||||
private int width, height;
|
private int width, height;
|
||||||
@ -429,22 +430,6 @@ public class BasicDrawing extends JPanel implements Drawing {
|
|||||||
return this.zoomAndPanListener;
|
return this.zoomAndPanListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param lon
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private int projx(double lon) {
|
|
||||||
return (int) (width * (lon - this.long1) / (this.long2 - this.long1));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param lat
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private int projy(double lat) {
|
|
||||||
return (int) (height * (1 - (lat - this.lat1) / (this.lat2 - this.lat1)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the longitude and latitude corresponding to the given position of the
|
* Return the longitude and latitude corresponding to the given position of the
|
||||||
* MouseEvent.
|
* MouseEvent.
|
||||||
@ -465,13 +450,8 @@ public class BasicDrawing extends JPanel implements Drawing {
|
|||||||
.inverseTransform(event.getPoint(), null);
|
.inverseTransform(event.getPoint(), null);
|
||||||
|
|
||||||
// Inverse the "projection" on x/y to get longitude and latitude.
|
// Inverse the "projection" on x/y to get longitude and latitude.
|
||||||
double lon = ptDst.getX();
|
return new Point(projection.pixelXToLongitude(ptDst.getX()),
|
||||||
double lat = ptDst.getY();
|
projection.pixelYToLatitude(ptDst.getY()));
|
||||||
lon = (lon / this.width) * (this.long2 - this.long1) + this.long1;
|
|
||||||
lat = (1 - lat / this.height) * (this.lat2 - this.lat1) + this.lat1;
|
|
||||||
|
|
||||||
// Return a new point.
|
|
||||||
return new Point((float) lon, (float) lat);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -546,10 +526,10 @@ public class BasicDrawing extends JPanel implements Drawing {
|
|||||||
while (it1.hasNext()) {
|
while (it1.hasNext()) {
|
||||||
Point curr = it1.next();
|
Point curr = it1.next();
|
||||||
|
|
||||||
int x1 = this.projx(prev.getLongitude());
|
int x1 = projection.longitudeToPixelX(prev.getLongitude());
|
||||||
int x2 = this.projx(curr.getLongitude());
|
int x2 = projection.longitudeToPixelX(curr.getLongitude());
|
||||||
int y1 = this.projy(prev.getLatitude());
|
int y1 = projection.latitudeToPixelY(prev.getLatitude());
|
||||||
int y2 = this.projy(curr.getLatitude());
|
int y2 = projection.latitudeToPixelY(curr.getLatitude());
|
||||||
|
|
||||||
graphGraphics.drawLine(x1, y1, x2, y2);
|
graphGraphics.drawLine(x1, y1, x2, y2);
|
||||||
prev = curr;
|
prev = curr;
|
||||||
@ -573,30 +553,20 @@ public class BasicDrawing extends JPanel implements Drawing {
|
|||||||
BoundingBox box = graph.getGraphInformation().getBoundingBox();
|
BoundingBox box = graph.getGraphInformation().getBoundingBox();
|
||||||
|
|
||||||
// Find minimum/maximum longitude and latitude.
|
// Find minimum/maximum longitude and latitude.
|
||||||
double minLon = box.getTopLeftPoint().getLongitude(),
|
float minLon = box.getTopLeftPoint().getLongitude(),
|
||||||
maxLon = box.getBottomRightPoint().getLongitude(),
|
maxLon = box.getBottomRightPoint().getLongitude(),
|
||||||
minLat = box.getBottomRightPoint().getLatitude(),
|
minLat = box.getBottomRightPoint().getLatitude(),
|
||||||
maxLat = box.getTopLeftPoint().getLatitude();
|
maxLat = box.getTopLeftPoint().getLatitude();
|
||||||
|
|
||||||
// Add a little delta to avoid drawing on the edge...
|
// Add a little delta to avoid drawing on the edge...
|
||||||
double diffLon = maxLon - minLon, diffLat = maxLat - minLat;
|
float diffLon = maxLon - minLon, diffLat = maxLat - minLat;
|
||||||
double deltaLon = 0.01 * diffLon, deltaLat = 0.01 * diffLat;
|
float deltaLon = 0.01f * diffLon, deltaLat = 0.01f * diffLat;
|
||||||
|
|
||||||
this.long1 = minLon - deltaLon;
|
// Create the projection and retrieve width and height for the box.
|
||||||
this.long2 = maxLon + deltaLon;
|
projection = new MercatorProjection(box.extend(deltaLon, deltaLat, deltaLon, deltaLat),
|
||||||
this.lat1 = minLat - deltaLat;
|
MAXIMUM_DRAWING_WIDTH);
|
||||||
this.lat2 = maxLat + deltaLat;
|
this.width = (int) projection.getImageWidth();
|
||||||
|
this.height = (int) projection.getImageHeight();
|
||||||
// Compute width/height for the image
|
|
||||||
|
|
||||||
if (diffLat < diffLon) {
|
|
||||||
this.width = MAXIMUM_DRAWING_WIDTH;
|
|
||||||
this.height = (int) (this.width * diffLat / diffLon);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.height = MAXIMUM_DRAWING_WIDTH;
|
|
||||||
this.width = (int) (this.height * diffLon / diffLat);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the image
|
// Create the image
|
||||||
BufferedImage img = new BufferedImage(this.width, this.height,
|
BufferedImage img = new BufferedImage(this.width, this.height,
|
||||||
|
Loading…
Reference in New Issue
Block a user