Commit 3000c395 authored by Hallvard Trætteberg's avatar Hallvard Trætteberg
Browse files

Imports and uses FxMapControl. Issue #10.

parent 4e8e8e36
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>fx-map-control</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding/<project>=UTF-8
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.source=1.8
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<name>FxMapControl</name>
<groupId>fischer.clemens</groupId>
<artifactId>fx-map-control</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
</project>
/*
* FX Map Control - https://github.com/ClemensFischer/FX-Map-Control
* © 2017 Clemens Fischer
*/
package fxmapcontrol;
import javafx.geometry.Point2D;
/**
* Transforms geographic coordinates to cartesian coordinates according to the Azimuthal Equidistant
* Projection.
*/
public class AzimuthalEquidistantProjection extends AzimuthalProjection {
public AzimuthalEquidistantProjection() {
// No known standard or de-facto standard CRS ID
}
public AzimuthalEquidistantProjection(String crsId) {
this.crsId = crsId;
}
@Override
public Point2D locationToPoint(Location location) {
if (location.equals(projectionCenter)) {
return new Point2D(0d, 0d);
}
double[] azimuthDistance = getAzimuthDistance(projectionCenter, location);
double azimuth = azimuthDistance[0];
double distance = WGS84_EQUATORIAL_RADIUS * azimuthDistance[1];
return new Point2D(distance * Math.sin(azimuth), distance * Math.cos(azimuth));
}
@Override
public Location pointToLocation(Point2D point) {
double x = point.getX();
double y = point.getY();
if (x == 0d && y == 0d) {
return projectionCenter;
}
double azimuth = Math.atan2(x, y);
double distance = Math.sqrt(x * x + y * y) / WGS84_EQUATORIAL_RADIUS;
return getLocation(projectionCenter, azimuth, distance);
}
}
/*
* FX Map Control - https://github.com/ClemensFischer/FX-Map-Control
* © 2017 Clemens Fischer
*/
package fxmapcontrol;
import java.util.Locale;
import javafx.geometry.BoundingBox;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
/**
* Base class for azimuthal map projections.
*/
public abstract class AzimuthalProjection extends MapProjection {
protected Location projectionCenter = new Location(0d, 0d);
@Override
public boolean isWebMercator() {
return false;
}
@Override
public boolean isNormalCylindrical() {
return false;
}
@Override
public boolean isAzimuthal() {
return true;
}
@Override
public double maxLatitude() {
return 90d;
}
@Override
public Point2D getMapScale(Location location) {
return new Point2D(viewportScale, viewportScale);
}
@Override
public Bounds boundingBoxToBounds(MapBoundingBox boundingBox) {
if (boundingBox instanceof CenteredBoundingBox) {
CenteredBoundingBox cbbox = (CenteredBoundingBox) boundingBox;
Point2D center = locationToPoint(cbbox.getCenter());
double width = cbbox.getWidth();
double height = cbbox.getHeight();
return new BoundingBox(center.getX() - width / 2d, center.getY() - height / 2d, width, height);
}
return super.boundingBoxToBounds(boundingBox);
}
@Override
public MapBoundingBox boundsToBoundingBox(Bounds bounds) {
Location center = pointToLocation(new Point2D(
bounds.getMinX() + bounds.getWidth() / 2d,
bounds.getMinY() + bounds.getHeight() / 2d));
return new CenteredBoundingBox(center, bounds.getWidth(), bounds.getHeight()); // width and height in meters
}
@Override
public double getViewportScale(double zoomLevel) {
return super.getViewportScale(zoomLevel) / METERS_PER_DEGREE;
}
@Override
public void setViewportTransform(Location projectionCenter, Location mapCenter, Point2D viewportCenter, double zoomLevel, double heading) {
this.projectionCenter = projectionCenter;
super.setViewportTransform(projectionCenter, mapCenter, viewportCenter, zoomLevel, heading);
}
@Override
public String wmsQueryParameters(MapBoundingBox boundingBox, String version) {
if (crsId == null || crsId.isEmpty()) {
return null;
}
Bounds bounds = boundingBoxToBounds(boundingBox);
String crs = version.startsWith("1.1.") ? "SRS" : "CRS";
return String.format(Locale.ROOT,
"%s=%s,1,%f,%f&BBOX=%f,%f,%f,%f&WIDTH=%d&HEIGHT=%d",
crs, crsId, projectionCenter.getLongitude(), projectionCenter.getLatitude(),
bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMaxY(),
(int) Math.round(viewportScale * bounds.getWidth()),
(int) Math.round(viewportScale * bounds.getHeight()));
}
/**
* Calculates azimuth and distance in radians from location1 to location2.
*/
public static double[] getAzimuthDistance(Location location1, Location location2) {
double lat1 = location1.getLatitude() * Math.PI / 180d;
double lon1 = location1.getLongitude() * Math.PI / 180d;
double lat2 = location2.getLatitude() * Math.PI / 180d;
double lon2 = location2.getLongitude() * Math.PI / 180d;
double cosLat1 = Math.cos(lat1);
double sinLat1 = Math.sin(lat1);
double cosLat2 = Math.cos(lat2);
double sinLat2 = Math.sin(lat2);
double cosLon12 = Math.cos(lon2 - lon1);
double sinLon12 = Math.sin(lon2 - lon1);
double cosDistance = sinLat1 * sinLat2 + cosLat1 * cosLat2 * cosLon12;
double azimuth = Math.atan2(sinLon12, cosLat1 * sinLat2 / cosLat2 - sinLat1 * cosLon12);
double distance = Math.acos(Math.max(Math.min(cosDistance, 1d), -1d));
return new double[]{azimuth, distance};
}
/**
* Calculates the Location of the point given by azimuth and distance in radians from location.
*/
public static Location getLocation(Location location, double azimuth, double distance) {
double lat = location.getLatitude() * Math.PI / 180d;
double sinDistance = Math.sin(distance);
double cosDistance = Math.cos(distance);
double cosAzimuth = Math.cos(azimuth);
double sinAzimuth = Math.sin(azimuth);
double cosLat1 = Math.cos(lat);
double sinLat1 = Math.sin(lat);
double sinLat2 = sinLat1 * cosDistance + cosLat1 * sinDistance * cosAzimuth;
double lat2 = Math.asin(Math.max(Math.min(sinLat2, 1d), -1d));
double dLon = Math.atan2(sinDistance * sinAzimuth, cosLat1 * cosDistance - sinLat1 * sinDistance * cosAzimuth);
return new Location(180d / Math.PI * lat2, location.getLongitude() + 180d / Math.PI * dLon);
}
}
/*
* FX Map Control - https://github.com/ClemensFischer/FX-Map-Control
* © 2016 Clemens Fischer
*/
package fxmapcontrol;
import java.io.IOException;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
* Displays Bing Maps tiles. The static apiKey property must be set to a Bing Maps API Key.
*/
public class BingMapsTileLayer extends MapTileLayer {
public enum MapMode {
Road, Aerial, AerialWithLabels
}
private static String apiKey;
private final MapMode mapMode;
public BingMapsTileLayer(MapMode mode) {
this(new TileImageLoader(), mode);
}
public BingMapsTileLayer(ITileImageLoader tileImageLoader, MapMode mode) {
super(tileImageLoader);
mapMode = mode;
if (apiKey == null || apiKey.isEmpty()) {
Logger.getLogger(BingMapsTileLayer.class.getName()).log(Level.SEVERE, "BingMapsTileLayer requires a Bing Maps API Key.");
} else {
try {
String url = String.format("http://dev.virtualearth.net/REST/V1/Imagery/Metadata/%s?output=xml&key=%s", mapMode.toString(), apiKey);
Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new URL(url).openStream());
Element metadataElement = (Element) document.getElementsByTagName("ImageryMetadata").item(0);
Element imageUrlElement = (Element) metadataElement.getElementsByTagName("ImageUrl").item(0);
Element subdomainsElement = (Element) metadataElement.getElementsByTagName("ImageUrlSubdomains").item(0);
Element zoomMinElement = (Element) metadataElement.getElementsByTagName("ZoomMin").item(0);
Element zoomMaxElement = (Element) metadataElement.getElementsByTagName("ZoomMax").item(0);
NodeList subdomainStrings = subdomainsElement.getElementsByTagName("string");
String[] subdomains = new String[subdomainStrings.getLength()];
for (int i = 0; i < subdomains.length; i++) {
subdomains[i] = subdomainStrings.item(i).getTextContent();
}
setName("Bing Maps " + mapMode);
setTileSource(new BingMapsTileSource(imageUrlElement.getTextContent(), subdomains));
setMinZoomLevel(Integer.parseInt(zoomMinElement.getTextContent()));
setMaxZoomLevel(Integer.parseInt(zoomMaxElement.getTextContent()));
} catch (IOException | ParserConfigurationException | SAXException | DOMException ex) {
Logger.getLogger(BingMapsTileLayer.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
public static String getApiKey() {
return apiKey;
}
public static void setApiKey(String key) {
apiKey = key;
}
public final MapMode getMapMode() {
return mapMode;
}
}
/*
* FX Map Control - https://github.com/ClemensFischer/FX-Map-Control
* © 2016 Clemens Fischer
*/
package fxmapcontrol;
class BingMapsTileSource extends TileSource {
private final String[] subdomains;
public BingMapsTileSource(String urlFormat, String[] subdomains) {
super(urlFormat);
this.subdomains = subdomains;
}
@Override
public String getUrl(int x, int y, int zoomLevel) {
if (zoomLevel < 1) {
return null;
}
String subdomain = subdomains[(x + y) % subdomains.length];
char[] quadkey = new char[zoomLevel];
for (int z = zoomLevel - 1; z >= 0; z--, x /= 2, y /= 2) {
quadkey[z] = (char) ('0' + 2 * (y % 2) + (x % 2));
}
return getUrlFormat()
.replace("{subdomain}", subdomain)
.replace("{quadkey}", new String(quadkey));
}
}
/*
* FX Map Control - https://github.com/ClemensFischer/FX-Map-Control
* © 2017 Clemens Fischer
*/
package fxmapcontrol;
/**
*/
public class CenteredBoundingBox extends MapBoundingBox {
private final Location center;
private final double width;
private final double height;
public CenteredBoundingBox(Location center, double width, double height) {
this.center = center;
this.width = width;
this.height = height;
}
public Location getCenter() {
return center;
}
@Override
public double getWidth() {
return width;
}
@Override
public double getHeight() {
return height;
}
@Override
public MapBoundingBox clone() {
return new CenteredBoundingBox(center, width, height);
}
}
/*
* FX Map Control - https://github.com/ClemensFischer/FX-Map-Control
* © 2017 Clemens Fischer
*/
package fxmapcontrol;
import javafx.geometry.Point2D;
/**
* Transforms geographic coordinates to cartesian coordinates according to the Equirectangular
* Projection. Longitude and Latitude values are transformed identically to X and Y.
*/
public class EquirectangularProjection extends MapProjection {
public EquirectangularProjection() {
this("EPSG:4326");
}
public EquirectangularProjection(String crsId) {
this.crsId = crsId;
}
@Override
public boolean isWebMercator() {
return false;
}
@Override
public boolean isNormalCylindrical() {
return true;
}
@Override
public boolean isAzimuthal() {
return false;
}
@Override
public double maxLatitude() {
return 90d;
}
@Override
public Point2D getMapScale(Location location) {
return new Point2D(
viewportScale / (METERS_PER_DEGREE * Math.cos(location.getLatitude() * Math.PI / 180d)),
viewportScale / METERS_PER_DEGREE);
}
@Override
public Point2D locationToPoint(Location location) {
return new Point2D(location.getLongitude(), location.getLatitude());
}
@Override
public Location pointToLocation(Point2D point) {
return new Location(point.getY(), point.getX());
}
}
/*
* FX Map Control - https://github.com/ClemensFischer/FX-Map-Control
* © 2017 Clemens Fischer
*/
package fxmapcontrol;
import static fxmapcontrol.AzimuthalProjection.getAzimuthDistance;
import javafx.geometry.Point2D;
import static fxmapcontrol.AzimuthalProjection.getLocation;
/**
* Transforms geographic coordinates to cartesian coordinates according to the Gnomonic Projection.
*/
public class GnomonicProjection extends AzimuthalProjection {
public GnomonicProjection() {
this("AUTO2:97001"); // GeoServer non-standard CRS ID
}
public GnomonicProjection(String crsId) {
this.crsId = crsId;
}
@Override
public Point2D locationToPoint(Location location) {
if (location.equals(projectionCenter)) {
return new Point2D(0d, 0d);
}
double[] azimuthDistance = getAzimuthDistance(projectionCenter, location);
double azimuth = azimuthDistance[0];
double distance = azimuthDistance[1];
double mapDistance = distance < Math.PI / 2d ? WGS84_EQUATORIAL_RADIUS * Math.tan(distance) : Double.POSITIVE_INFINITY;
return new Point2D(mapDistance * Math.sin(azimuth), mapDistance * Math.cos(azimuth));
}
@Override
public Location pointToLocation(Point2D point) {
double x = point.getX();
double y = point.getY();
if (x == 0d && y == 0d) {
return projectionCenter;
}
double azimuth = Math.atan2(x, y);
double mapDistance = Math.sqrt(x * x + y * y);
double distance = Math.atan(mapDistance / WGS84_EQUATORIAL_RADIUS);
return getLocation(projectionCenter, azimuth, distance);
}
}
/*
* FX Map Control - https://github.com/ClemensFischer/FX-Map-Control
* © 2016 Clemens Fischer
*/
package fxmapcontrol;
/**
* Represents a Node in the visual tree of a MapBase instance.
*/
public interface IMapNode {
MapBase getMap();
void setMap(MapBase map);
}
/*
* FX Map Control - https://github.com/ClemensFischer/FX-Map-Control
* © 2016 Clemens Fischer
*/
package fxmapcontrol;
/**
* Provides methods for caching tile image buffers.
*/