Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Daniel Wirtz committed Feb 20, 2020
1 parent cfa4980 commit 1e98765
Show file tree
Hide file tree
Showing 11 changed files with 1,182 additions and 2 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
.classpath
.project
.settings/
# Compiled class file
*.class

Expand All @@ -21,3 +24,4 @@

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
/target/
33 changes: 31 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,31 @@
# nds-tiles
A simple java implementation of the NDS Tiling Scheme
NDS Tiles Toolset
=================
A simple java implementation of the NDS Tiling Scheme along with some utility classes and functions.
Short feature list:
- Create NDSTiles from coordinates, level+nr, packedId
- Access NDS Tile properties and bounding boxes
- Convert between WGS84 and NDS coordinate formats
- Get Morton codes for NDS Coordinates
- GeoJSON output of all classes

Usage
=====

Compiling
---------
This is a simple maven project. You can build the current jar with

$ mvn package

Development
-----------
I used the Lombok java agent for easy code generation of class' default methods etc.
See https://projectlombok.org/ for setup instructions if you want to include the source code in your projects with e.g. Eclipse.

References
==========
- https://nds-association.org/
- https://en.wikipedia.org/wiki/Navigation_Data_Standard
- NDS Format Specification, Version 2.5.4
* NDS Tiles: [1, §7.3.1]
* Morton codes: [1, §7.2.1]
42 changes: 42 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<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>

<groupId>de.rondiplomatico</groupId>
<artifactId>nds-tiles</artifactId>
<version>1.0</version>
<packaging>jar</packaging>

<name>Tools for NDS Tiling Scheme</name>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
100 changes: 100 additions & 0 deletions src/main/java/de/rondiplomatico/nds/NDSBBox.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package de.rondiplomatico.nds;

import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.ToString;

/**
* Utility class for bounding boxes in NDS coordinates.
*
* For efficiency, this is implemented as separate class instead of including this into NDSTile.
*
* @author Daniel Wirtz
* @since 20.02.2020
*/
@Data
@RequiredArgsConstructor
@ToString
public class NDSBBox {
/**
* The western hemisphere part of the world map corresponds to NDS Tile number 0 on level 0
*/
public static final NDSBBox WEST_HEMISPHERE = new NDSBBox(NDSCoordinate.MAX_LATITUDE, 0, NDSCoordinate.MIN_LATITUDE, NDSCoordinate.MIN_LONGITUDE);
/**
* The eastern hemisphere part of the world map corresponds to NDS Tile number 1 on level 0
*/
public static final NDSBBox EAST_HEMISPHERE = new NDSBBox(NDSCoordinate.MAX_LATITUDE, NDSCoordinate.MAX_LONGITUDE, NDSCoordinate.MIN_LATITUDE, 0);

private final int north;
private final int east;
private final int south;
private final int west;

/**
*
* Gets the south west corner of the bounding box
*
* @return
*/
public NDSCoordinate southWest() {
return new NDSCoordinate(west, south);
}

/**
* Gets the south east corner of the bounding box
*
* @return NDSCoordinate
*/
public NDSCoordinate southEast() {
return new NDSCoordinate(east, south);
}

/**
* Gets the north west corner of the bounding box
*
* @return NDSCoordinate
*/
public NDSCoordinate northWest() {
return new NDSCoordinate(west, north);
}

/**
* Gets the north east corner of the bounding box
*
* @return NDSCoordinate
*/
public NDSCoordinate northEast() {
return new NDSCoordinate(east, north);
}

/**
* Returns the center of the bounding box
*
* @return NDSCoordinate
*/
public NDSCoordinate center() {
return new NDSCoordinate((east + west) / 2, (north + south) / 2);
}

/**
*
* Converts this bounding box to a WGS84-coordinate based bounding box.
*
* @return WGS84BBox
*/
public WGS84BBox toWGS84() {
WGS84Coordinate ne = northEast().toWGS84();
WGS84Coordinate sw = southWest().toWGS84();
return new WGS84BBox(ne.getLatitude(), ne.getLongitude(), sw.getLatitude(), sw.getLongitude());
}

/**
* Creates a GeoJSON representation of this bounding box as a "Polygon" feature.
*
* @return String
*/
public String toGeoJSON() {
return toWGS84().toGeoJSON();
}

}
167 changes: 167 additions & 0 deletions src/main/java/de/rondiplomatico/nds/NDSCoordinate.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package de.rondiplomatico.nds;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;

/**
* Implementation of a NDS coordinate, according to the NDS Format Specification, Version 2.5.4, §7.2.1.
*
* The NDS coordinate encoding divides the 360° range into 2^32 steps.
* Consequently, each coordinate is represented by a pair of signed integers, where
* a coordinate unit corresponds to 360/2^32 = 90/2^30 degrees longitude/latitude
* (with their respective longitude [-180°,180°] and latitude [-90°,90°] ranges).
*
* Note: The integer range is not fully used encoding latitude values due to the half degree range.
* But this is done in favor of equally sized coordinate units along longitude/latitude.
*
* No warranties for correctness, use at own risk.
*
* @author Daniel Wirtz
* @since 20.02.2020
*/
@Getter
@ToString
@EqualsAndHashCode
public class NDSCoordinate {

public static final int MAX_LONGITUDE = Integer.MAX_VALUE;
public static final int MIN_LONGITUDE = Integer.MIN_VALUE;
public static final int MAX_LATITUDE = MAX_LONGITUDE / 2;
public static final int MIN_LATITUDE = MIN_LONGITUDE / 2;

public static final long LONGITUDE_RANGE = (long) MAX_LONGITUDE - MIN_LONGITUDE;
public static final long LATITUDE_RANGE = (long) MAX_LATITUDE - MIN_LATITUDE;

public final int latitude;
public final int longitude;

/**
* Creates a new {@link NDSCoordinate} instance
*
* @param longitude
* @param latitude
*/
public NDSCoordinate(int longitude, int latitude) {
verify(longitude, latitude);
this.latitude = latitude;
this.longitude = longitude;
}

/**
* Instantiates a new NDS coordinate from WGS84 coordinates.
*
* @param lon
* the longitude within [-180, 180]
* @param lat
* the latitude within [-90, 90]
*/
public NDSCoordinate(double lon, double lat) {
if (lon < -180 || lon > 180) {
throw new IllegalArgumentException("The longitude value " + lon + " exceeds the valid range of [-180; 180]");
}
if (lat < -90 || lat > 90) {
throw new IllegalArgumentException("The latitude value " + lat + " exceeds the valid range of [-90; 90]");
}
latitude = (int) Math.floor(lat / 180.0 * LATITUDE_RANGE);
longitude = (int) Math.floor(lon / 360.0 * LONGITUDE_RANGE);
}

/**
* Creates a new NDSCoordinate from a morton code.
*
* @param ndsMortonCoordinates
* @return
*/
public NDSCoordinate(long ndsMortonCoordinates) {
int lat = 0;
int lon = 0;
for (int pos = 0; pos < 32; ++pos) {
if (pos < 31 && (ndsMortonCoordinates & 1L << (pos * 2 + 1)) != 0L) {
lat |= 1 << pos;
}
if ((ndsMortonCoordinates & 1L << (pos * 2)) != 0L) {
lon |= 1 << pos;
}
}
/*
* with NDS, the latitude value is considered a 31-bit signed integer.
* hence, if the 31st bit is 1, this means we have a negative integer, requiring
* to set the 32st bit to 1 for native java 32bit signed integers.
*/
if ((lat & 1 << 30) > 0) {
lat |= 1 << 31;
}
verify(lon, lat);
latitude = lat;
longitude = lon;
}

private void verify(int lon, int lat) {
if (lat < MIN_LATITUDE || MAX_LATITUDE < lat) {
throw new IllegalArgumentException("Latitude value " + lat + " exceeds allowed range [-2^30; 2^30] [" + MIN_LATITUDE + "," + MAX_LATITUDE + "].");
}
}

/**
* Adds an offset specified by two int values to the coordinate.
* Useful for NDS coordinate decoding using tile offsets.
*
* @param deltaLongitude
* @param deltaLatitude
* @return NDSCoordinate
*/
public NDSCoordinate add(int deltaLongitude, int deltaLatitude) {
return new NDSCoordinate(longitude + deltaLongitude, latitude + deltaLatitude);
}

/**
* Returns the unique morton code for this NDSCoordinate.
*
* @see NDS Format Specification, Version 2.5.4, §7.2.1.
*
* @return long
*/
public long getMortonCode() {
long res = 0L;
for (int pos = 0; pos < 31; pos++) {
if ((longitude & 1 << pos) > 0) {
res |= 1L << (2 * pos);
}
if (pos < 31 && (latitude & 1 << pos) > 0) {
res |= 1L << (2 * pos + 1);
}
}
if (longitude < 0) {
res |= 1L << 62;
}
// For 31-bit signed integers the 32st bit needs to be copied to the 31st bit in case of negative numbers.
if (latitude < 0) {
res |= 1L << 61;
}
return res;
}

/**
*
* Converts this coordinate to a WGS84 coordinate (using the "usual" longitude/latitude degree ranges)
*
* @return
*/
public WGS84Coordinate toWGS84() {
double lon = longitude >= 0 ? (double) longitude / (double) MAX_LONGITUDE * 180.0D
: (double) longitude / (double) MIN_LONGITUDE * -180.0D;
double lat = latitude >= 0 ? (double) latitude / (double) MAX_LATITUDE * 90.0D
: (double) latitude / (double) MIN_LATITUDE * -90.0D;
return new WGS84Coordinate(lon, lat);
}

/**
* Creates a GeoJSON "Point" feature representation of this coordinate
*
* @return
*/
public String toGeoJSON() {
return toWGS84().toGeoJSON();
}
}
Loading

0 comments on commit 1e98765

Please sign in to comment.