Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • issue-10-fxmapcontrol
  • issue-13-gpx
  • issue-9-file-menu
  • master
4 results

Target

Select target project
  • bragearn/examples
  • johngu/examples
  • krisschn/examples
  • erikbd/examples
  • helenesm/examples
  • balazor/examples
  • htechter/examples
  • aslakho/examples
  • jonri/examples
  • andreski/examples
  • sigriksc/examples
  • norasbr/examples
  • jannash/examples
  • smledsaa/examples
  • mtorre/examples
  • tdt4140-staff/examples
16 results
Select Git revision
  • issue-10-fxmapcontrol
  • issue-9-file-menu
  • master
  • patch-1
4 results
Show changes
Commits on Source (35)
Showing
with 866 additions and 15 deletions
image: kaiwinter/docker-java8-maven
image: krissrex/docker-java8-maven-testfxmonocle:1.0
# image: kaiwinter/docker-java8-maven
# image: maven:3.3.9-jdk-8
# most of this taken from https://stackoverflow.com/questions/37785154/how-to-enable-maven-artifact-caching-for-gitlab-ci-runner
......@@ -11,24 +12,30 @@ cache:
variables:
MAVEN_OPTS: "-Djava.awt.headless=true -Dmaven.repo.local=.m2/repository"
MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version -Dskip-ui-tests=true"
MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version -Dgitlab-ci=true"
build-job:
stage: build
script:
- "mvn clean compile -f tdt4140-gr1800/pom.xml $MAVEN_CLI_OPTS"
#build-job:
# stage: build
# script:
# - "mvn clean compile -f tdt4140-gr18nn/pom.xml $MAVEN_CLI_OPTS"
# - "mvn clean compile -f tdt4140-gr1800/pom.xml $MAVEN_CLI_OPTS"
unittest-job:
stage: test
dependencies:
- build-job
script:
- "mvn package -f tdt4140-gr1800/pom.xml $MAVEN_CLI_OPTS"
- "cat tdt4140-gr1800/app.core/target/site/jacoco/index.html"
#unittest-job:
# stage: test
# dependencies:
# - build-job
# script:
# - "mvn test -f tdt4140-gr18nn/pom.xml $MAVEN_CLI_OPTS"
# - "mvn test -f tdt4140-gr1800/pom.xml $MAVEN_CLI_OPTS"
# - "cat tdt4140-gr1800/app.core/target/site/jacoco/index.html"
integrationtest-job:
stage: test
dependencies:
- build-job
only:
- web
# dependencies:
# - build-job
script:
- "mvn verify -f tdt4140-gr18nn/pom.xml $MAVEN_CLI_OPTS"
- "mvn verify -f tdt4140-gr1800/pom.xml $MAVEN_CLI_OPTS"
- "cat tdt4140-gr1800/app.core/target/site/jacoco/index.html"
# Example repository for the TDT4140 course, Spring 2018
This repository contains templates and examples for the TDT4140 course, for Spring 2018. Currently, it contains two project, both using maven as build system and configured for using Eclipse:
* [tdt4140-gr18nn](tdt4140-gr18nn/README.md): Template for JavaFX project with two sub-modules, one for the model and one for the UI. To use the template, copy the whole structure and systematically rename files and edit file contents to match your group number.
* [tdt4140-gr1800](tdt4140-gr1800/README.md): Example project based on the template, and extended to include a web server module.
<?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.
*/
public interface ITileCache {
public static class CacheItem {
private final byte[] buffer;
private final long expiration; // milliseconds since 1970/01/01 00:00:00 UTC
public CacheItem(byte[] buffer, long expiration) {
this.buffer = buffer;
this.expiration = expiration;
}
public final byte[] getBuffer() {
return buffer;
}
public final long getExpiration() {
return expiration;
}
}
CacheItem get(String tileLayerName, int x, int y, int zoomLevel);
void set(String tileLayerName, int x, int y, int zoomLevel, byte[] buffer, long expiration);
}
/*
* FX Map Control - https://github.com/ClemensFischer/FX-Map-Control
* © 2016 Clemens Fischer
*/
package fxmapcontrol;
/**
* Provides methods to begin and cancel loading of map tile images for a specific MapTileLayer.
*/
public interface ITileImageLoader {
void beginLoadTiles(MapTileLayer tileLayer, Iterable<Tile> tiles);
void cancelLoadTiles(MapTileLayer tileLayer);
}
/*
* FX Map Control - https://github.com/ClemensFischer/FX-Map-Control
* © 2016 Clemens Fischer
*/
package fxmapcontrol;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Default ITileCache implementation. Caches tile image files in a directory given by the rootDirectory property.
*/
public class ImageFileCache implements ITileCache {
// For compatibility with XAML Map Control ImageFileCache, expiration dates are stored as .NET DateTime ticks,
// i.e. 100-nanosecond intervals since 0001/01/01 00:00:00 UTC. The DATETIME_OFFSET and DATETIME_FACTOR constants
// are used to convert to and from java.util.Date milliseconds since 1970/01/01 00:00:00 UTC.
private static final long DATETIME_OFFSET = 62135596800000L;
private static final long DATETIME_FACTOR = 10000L;
private static final ByteBuffer EXPIRATION_MARKER = ByteBuffer.wrap("EXPIRES:".getBytes(StandardCharsets.US_ASCII));
private final Path rootDirectory;
public ImageFileCache(Path rootDirectory) {
this.rootDirectory = rootDirectory;
//System.out.println(rootDirectory.toAbsolutePath());
}
public ImageFileCache() {
this(getDefaultRootDirectory());
}
public static final Path getDefaultRootDirectory() {
String osName = System.getProperty("os.name").toLowerCase();
if (osName.contains("windows")) {
String programData = System.getenv("ProgramData");
if (programData != null) {
// use default XAML Map Control cache directory
return Paths.get(programData, "MapControl", "TileCache");
}
} else {//if (osName.contains("linux")) {
return Paths.get("/var", "tmp", "FxMapControl-Cache");
}
return null;
}
public final Path getRootDirectory() {
return rootDirectory;
}
@Override
public CacheItem get(String tileLayerName, int x, int y, int zoomLevel) {
File cacheDir = rootDirectory
.resolve(tileLayerName)
.resolve(Integer.toString(zoomLevel))
.resolve(Integer.toString(x)).toFile();
String fileNameFilter = Integer.toString(y) + ".";
if (cacheDir.isDirectory()) {
File[] cacheFiles = cacheDir.listFiles((dir, name) -> name.startsWith(fileNameFilter));
if (cacheFiles.length > 0) {
//System.out.println("Reading " + cacheFiles[0].getPath());
try {
byte[] buffer = new byte[(int) cacheFiles[0].length()];
long expiration = 0;
try (FileInputStream fileStream = new FileInputStream(cacheFiles[0])) {
fileStream.read(buffer);
}
if (buffer.length >= 16 && ByteBuffer.wrap(buffer, buffer.length - 16, 8).equals(EXPIRATION_MARKER)) {
expiration = ByteBuffer.wrap(buffer, buffer.length - 8, 8).order(ByteOrder.LITTLE_ENDIAN)
.getLong() / DATETIME_FACTOR - DATETIME_OFFSET;
}
return new CacheItem(buffer, expiration);
} catch (IOException ex) {
Logger.getLogger(ImageFileCache.class.getName()).log(Level.WARNING, ex.toString());
}
}
}
return null;
}
@Override
public void set(String tileLayerName, int x, int y, int zoomLevel, byte[] buffer, long expiration) {
File cacheFile = rootDirectory
.resolve(tileLayerName)
.resolve(Integer.toString(zoomLevel))
.resolve(Integer.toString(x))
.resolve(String.format("%d%s", y, getFileExtension(buffer))).toFile();
//System.out.println("Writing " + cacheFile.getPath() + ", Expires " + new Date(expiration));
try {
cacheFile.getParentFile().mkdirs();
try (FileOutputStream fileStream = new FileOutputStream(cacheFile)) {
fileStream.write(buffer, 0, buffer.length);
fileStream.write(EXPIRATION_MARKER.array());
fileStream.write(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN)
.putLong((expiration + DATETIME_OFFSET) * DATETIME_FACTOR).array());
}
cacheFile.setReadable(true, false);
cacheFile.setWritable(true, false);
} catch (IOException ex) {
Logger.getLogger(ImageFileCache.class.getName()).log(Level.WARNING, ex.toString());
}
}
private static String getFileExtension(byte[] buffer) {
if (buffer.length >= 8
&& buffer[0] == (byte) 0x89
&& buffer[1] == (byte) 0x50
&& buffer[2] == (byte) 0x4E
&& buffer[3] == (byte) 0x47
&& buffer[4] == (byte) 0x0D
&& buffer[5] == (byte) 0x0A
&& buffer[6] == (byte) 0x1A
&& buffer[7] == (byte) 0x0A) {
return ".png";
}
if (buffer.length >= 3
&& buffer[0] == (byte) 0xFF
&& buffer[1] == (byte) 0xD8
&& buffer[2] == (byte) 0xFF) {
return ".jpg";
}
if (buffer.length >= 3
&& buffer[0] == (byte) 0x47
&& buffer[1] == (byte) 0x49
&& buffer[2] == (byte) 0x46) {
return ".gif";
}
if (buffer.length >= 2
&& buffer[0] == (byte) 0x42
&& buffer[1] == (byte) 0x4D) {
return ".bmp";
}
if (buffer.length >= 4
&& buffer[0] == (byte) 0x49
&& buffer[1] == (byte) 0x49
&& buffer[2] == (byte) 0x2A
&& buffer[3] == (byte) 0x00) {
return ".tif";
}
if (buffer.length >= 4
&& buffer[0] == (byte) 0x4D
&& buffer[1] == (byte) 0x4D
&& buffer[2] == (byte) 0x00
&& buffer[3] == (byte) 0x2A) {
return ".tif";
}
return ".bin";
}
}
/*
* FX Map Control - https://github.com/ClemensFischer/FX-Map-Control
* © 2016 Clemens Fischer
*/
package fxmapcontrol;
/**
* A geographic location with latitude and longitude values in degrees.
*/
public class Location {
private final double latitude;
private final double longitude;
public Location(double latitude, double longitude) {
this.latitude = latitude;
this.longitude = longitude;
}
public final double getLatitude() {
return latitude;
}
public final double getLongitude() {
return longitude;
}
public final boolean equals(Location location) {
return this == location
|| (latitude == location.latitude && longitude == location.longitude);
}
@Override
public final boolean equals(Object obj) {
return (obj instanceof Location) && equals((Location) obj);
}
@Override
public int hashCode() {
return Double.hashCode(latitude) ^ Double.hashCode(longitude);
}
public static Location valueOf(String locationString) {
String[] pair = locationString.split(",");
if (pair.length != 2) {
throw new IllegalArgumentException(
"Location string must be a comma-separated pair of double values");
}
return new Location(
Double.parseDouble(pair[0]),
Double.parseDouble(pair[1]));
}
public static double normalizeLongitude(double longitude) {
if (longitude < -180.) {
longitude = ((longitude + 180.) % 360.) + 180.;
} else if (longitude > 180.) {
longitude = ((longitude - 180.) % 360.) - 180.;
}
return longitude;
}
static double nearestLongitude(double longitude, double referenceLongitude) {
longitude = normalizeLongitude(longitude);
if (longitude > referenceLongitude + 180d) {
longitude -= 360d;
} else if (longitude < referenceLongitude - 180d) {
longitude += 360d;
}
return longitude;
}
}