Skip to content

Commit

Permalink
edits w.r.t. review on PR rondiplomatico#1
Browse files Browse the repository at this point in the history
  • Loading branch information
beauof committed Feb 28, 2020
1 parent 1a3ca83 commit 05dc383
Show file tree
Hide file tree
Showing 13 changed files with 872 additions and 863 deletions.
8 changes: 0 additions & 8 deletions src/main/java/de/rondiplomatico/nds/NDSBBox.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,6 @@ public class NDSBBox {
private final int south;
private final int west;

// constructor to get global bounding box in NDS coordinates
public NDSBBox() {
north = 90;
east = 180;
south = -90;
west = -180;
}

/**
*
* Gets the south west corner of the bounding box
Expand Down
9 changes: 6 additions & 3 deletions src/main/java/de/rondiplomatico/nds/NDSCoordinate.java
Original file line number Diff line number Diff line change
Expand Up @@ -98,18 +98,21 @@ public NDSCoordinate(long ndsMortonCoordinates) {
}

/*
* Compute midpoint of this and given NDSCoordinate
* Computes a point along the distance of this and given NDSCoordinate
*
* @param p
* the given coordinate
* @param fraction
* the fraction
*/
public NDSCoordinate getMidpoint(NDSCoordinate p) {
public NDSCoordinate fractionBetween(NDSCoordinate p, double fraction) {
assert (0.0 <= fraction) && (fraction <= 1.0);
double lat = p.toWGS84().getLatitude();
double lon = p.toWGS84().getLongitude();
WGS84Coordinate lonlat = toWGS84();
double longitude = lonlat.getLongitude();
double latitude = lonlat.getLatitude();
return new NDSCoordinate(longitude + 0.5 * (lon - longitude), latitude + 0.5 * (lat - latitude));
return new NDSCoordinate(longitude + fraction * (lon - longitude), latitude + fraction * (lat - latitude));
}

private void verify(int lon, int lat) {
Expand Down
84 changes: 68 additions & 16 deletions src/main/java/de/rondiplomatico/nds/NDSEnvelope.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import com.vividsolutions.jts.geom.OctagonalEnvelope;

/**
* Class to get a vividsolutions envelope of coordinates.
* NDSEnvelope allows to compute an envelope for a set of NDSCoordinates.
*
* No warranties for correctness, use at own risk.
*
Expand All @@ -15,72 +15,124 @@ public class NDSEnvelope {

private static OctagonalEnvelope vividEnvelope = new OctagonalEnvelope();

/**
* NDSEnvelope of a given set of polygon points
*
* @param polygonCoordinates
* the polygonCoordinates
*/
public NDSEnvelope(double[][] polygonCoordinates) {
for(int i = 0; i < polygonCoordinates.length; i++) {
Coordinate vividCoord = new Coordinate(polygonCoordinates[i][0], polygonCoordinates[i][1]);
vividEnvelope.expandToInclude(vividCoord);
}
}

/**
* NDSEnvelope constructor for OctagonalEnvelope
*
* @param e
* the OctagonalEnvelope e
*/
public NDSEnvelope(OctagonalEnvelope e) {
vividEnvelope = e;
}

/**
* Get the NDSEnvelope
*
* @return
*/
public OctagonalEnvelope getEnvelope() {
return vividEnvelope;
}

/**
* Get the SouthWest corner of the envelope
*
* @return
*/
public NDSCoordinate getSouthWest() {
return new NDSCoordinate(vividEnvelope.getMinX(), vividEnvelope.getMinY());
}


/**
* Get the SouthEast corner of the envelope
*
* @return
*/
public NDSCoordinate getSouthEast() {
return new NDSCoordinate(vividEnvelope.getMaxX(), vividEnvelope.getMinY());
}


/**
* Get the NorthEast corner of the envelope
*
* @return
*/
public NDSCoordinate getNorthEast() {
return new NDSCoordinate(vividEnvelope.getMinX(), vividEnvelope.getMaxY());
}


/**
* Get the NorthWest corner of the envelope
*
* @return
*/
public NDSCoordinate getNorthWest() {
return new NDSCoordinate(vividEnvelope.getMaxX(), vividEnvelope.getMaxY());
}

/**
* Get the master tile for the SouthWest, SouthEast, NorthEast, NorthWest corners for a given number of levels
*
* @param maxLevels
* the maxLevels
* @return masterTileInfo
* the masterTileInfo consisting of the {masterTileLevel, masterTileNumber}
*/
public int[] getMasterTileInfo(int maxLevels) {
NDSCoordinate point0 = getSouthWest();
NDSCoordinate point1 = getSouthEast();
NDSCoordinate point2 = getNorthEast();
NDSCoordinate point3 = getNorthWest();
return getMasterTileInfo(point0, point1, point2, point3, maxLevels);
}


/**
* Get the master tile for a set of four NDSCoordinates for a given number of levels
*
* @param maxLevels
* the maxLevels
* @return masterTileInfo
* the masterTileInfo consisting of the {masterTileLevel, masterTileNumber}
*/
public int[] getMasterTileInfo(NDSCoordinate point0, NDSCoordinate point1, NDSCoordinate point2, NDSCoordinate point3, int maxLevels) {
int masterTileLevel = -1;
int masterTileID = -1;
int masterTileNumber = -1;
for (int li = 0; li < maxLevels; li++) {
NDSTile currTile0 = new NDSTile(li, point0);
NDSTile currTile1 = new NDSTile(li, point1);
NDSTile currTile2 = new NDSTile(li, point2);
NDSTile currTile3 = new NDSTile(li, point3);
int currTileID0 = currTile0.getTileNumber();
int currTileID1 = currTile1.getTileNumber();
int currTileID2 = currTile2.getTileNumber();
int currTileID3 = currTile3.getTileNumber();
boolean singleTileID = (currTileID0 == currTileID1) && (currTileID0 == currTileID2) && (currTileID0 == currTileID3);
int currTileNumber0 = currTile0.getTileNumber();
int currTileNumber1 = currTile1.getTileNumber();
int currTileNumber2 = currTile2.getTileNumber();
int currTileNumber3 = currTile3.getTileNumber();
boolean singleTileNumber = (currTileNumber0 == currTileNumber1) && (currTileNumber0 == currTileNumber2) && (currTileNumber0 == currTileNumber3);
// if at least one tile ID is different, we discard the tile IDs and keep our previously detected tile ID (i.e. on the previous level)
if(!singleTileID) {
if(!singleTileNumber) {
break;
}
// store tile info
masterTileLevel = li;
masterTileID = currTileID0;
masterTileNumber = currTileNumber0;
}
// check if valid result
if (masterTileID == -1) {
if (masterTileNumber == -1) {
System.out.println(">>>ERROR: Invalid master tile ID.");
System.exit(1);
}
int[] masterTileInfo = {masterTileLevel, masterTileID};
return masterTileInfo;
return new int[] {masterTileLevel, masterTileNumber};
}
}
93 changes: 93 additions & 0 deletions src/main/java/de/rondiplomatico/nds/NDSHashmap.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package de.rondiplomatico.nds;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* NDSHashmap allows to store tileY - tileX pairs in a convenient format,
* where tileY is the key and tileX are the value.
* This is convenient because each tileY key has a (sorted) number of tileX value(s)
* and makes several tasks straighforward, such as:
* - drawing a map
* - applying flood-fill algorithm to get map tiles covered by polygon
* - etc.
*
* No warranties for correctness, use at own risk.
*
* @author Andreas Hessenthaler
* @since 28.02.2020
*/
public class NDSHashmap {

/**
* Compute a hash map from tile numbers.
*
* @param level
* the level
* @param tileNumbers
* the tileNumbers
* @return
*/
public Map<Integer, List<Integer>> tileNumbersToHM(int level, int[] tileNumbers){
// get the master tile number 0
NDSTile masterTile = new NDSTile(0, 0);
// create hashmap of (key, value) pairs, where key is tileY and val is tileX
// note: val may contain multiple values
Map<Integer, List<Integer>> tileHM = new HashMap<Integer, List<Integer>>();
for (int ti = 0; ti < tileNumbers.length; ti++) {
int[] tileXY = masterTile.getTileXYfromTileNumber(level, tileNumbers[ti]);
int key = tileXY[1];
int newVal = tileXY[0];
// if key already exists, add value to sorted list; else create new list
if (tileHM.containsKey(key)) {
List<Integer> prevList = tileHM.get(key);
prevList.add(newVal);
Collections.sort(prevList);
tileHM.put(key, prevList);
} else {
List<Integer> newList = new ArrayList<Integer>();
newList.add(newVal);
tileHM.put(key, newList);
}
}
return tileHM;
}

/**
* Compute tile numbers from hash map.
*
* @param level
* the level
* @param hm
* the hash map
* @return
*/
public int[] hmToTileNumbers(int level, Map<Integer, List<Integer>> hm) {
NDSTile masterTile = new NDSTile(0, 0);
int numVals = getNumberOfValuesHM(hm);
int[] filledTileIDs = new int[numVals];
int idx = 0;
for (Map.Entry<Integer, List<Integer>> entry : hm.entrySet()) {
int key = entry.getKey();
List<Integer> currList = entry.getValue();
for (int idx0 = 0; idx0 < currList.size(); idx0++) {
filledTileIDs[idx] = masterTile.getTileNumberFromTileXY(level, currList.get(idx0), key);
idx++;
}
}
return filledTileIDs;
}


private int getNumberOfValuesHM(Map<Integer, List<Integer>> hm) {
int numVals = 0;
for (Map.Entry<Integer, List<Integer>> entry : hm.entrySet()) {
numVals = numVals + entry.getValue().size();
}
return numVals;
}

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

import java.util.List;
import java.util.Map;

import de.rondiplomatico.nds.NDSUtils;
import de.rondiplomatico.nds.NDSHashmap;

public class NDSPolyFillDemo {

/**
* Main method to test selecting all tiles covered by a sample polygon.
*
* @param args
*/
public static void main(String[] args) {

long t0 = System.currentTimeMillis();

// get some random polygon data for testing purposes
// double[][] polygonCoordinates = new double[][] {
// {10.5, 45.9},
// {13.0, 50.3},
// {15.0, 47.0},
// {13.4, 70.0},
// {10.5, 45.9}
// };

// get some random polygon data for testing purposes
// double[][] polygonCoordinates = new double[][] {
// {10.5, 45.9},
// {13.0, 63.3},
// {15.0, 47.0},
// {13.4, 70.0},
// {10.5, 45.9}
// };

// get some random polygon data for testing purposes
// double[][] polygonCoordinates = new double[][] {
// {10.5, 45.9},
// {13.0, 50.3},
// {15.0, 47.0},
// {17.0, 50.3},
// {20.0, 47.0},
// {13.4, 60.0},
// {13.4, 70.0},
// {10.5, 45.9}
// };

// get some random polygon data approximating Germany
// https://www.mapsofworld.com/lat_long/germany-lat-long.html
double[][] polygonCoordinates = new double[][] {
{10.5, 45.9},
{13.0, 45.9},
{14.0, 49.0},
{12.0, 50.0},
{15.0, 51.0},
{15.0, 54.0},
{13.5, 54.5},
{11.0, 54.0},
{10.0, 55.0},
{ 8.5, 55.0},
{ 9.0, 54.0},
{ 7.0, 53.5},
{ 6.0, 52.0},
{ 6.1, 50.0},
{ 8.0, 49.0},
{ 7.5, 47.5},
{10.5, 45.9}
};

// number of levels in map hierarchy
int maxLevels = 15;
// get a bounding octagonal envelope (defaults to quadrilateral in 2D case)
// for sample data corresponding to a polygon (e.g. borders of a country)
NDSEnvelope envelope = new NDSEnvelope(polygonCoordinates);
// get corresponding bounding tile ID, i.e. find level where all polygon points are on the same tile
int[] masterTileInfo = envelope.getMasterTileInfo(maxLevels);
int masterTileLevel = masterTileInfo[0];
int masterTileID = masterTileInfo[1];
// store the master tile
NDSTile masterTile = new NDSTile(masterTileLevel, masterTileID);
// get all tiles covered by the polygon on the tstLevel
int tstLevel = 11;
// refine polygon coordinates to avoid edges that are crossing a tile
// set refinement factor to -1 to adaptively refine
int numSamples = -1;
double[][] polygonCoordinatesRef = NDSPolygon.refinePolygon(tstLevel, polygonCoordinates, numSamples);
// let's grab all tiles with polygon points
int[] uniqueTileIDs = NDSPolygon.getUniqueTileNumbersOnLevel(tstLevel, polygonCoordinatesRef);
// dump to image file for debugging
NDSUtils.printMap(masterTile, tstLevel, uniqueTileIDs, "png", "map");
// get x/y tile indices for tiles with polygon points
// key is tileY, val is tileX (note: val may contain multiple tileX indices)
NDSHashmap hm = new NDSHashmap();
Map<Integer, List<Integer>> tileHM = hm.tileNumbersToHM(tstLevel, uniqueTileIDs);
// dump to image file for debugging
Map<Integer, List<Integer>> filledTileHM = NDSPolygon.mapFillPolygon(tileHM, uniqueTileIDs);
int[] filledTileIDs = hm.hmToTileNumbers(tstLevel, filledTileHM);
NDSUtils.printMap(masterTile, tstLevel, filledTileIDs, "png", "mapFilled");

long t1 = System.currentTimeMillis();
System.out.println("\n>>>INFO: Program finished in "+(t1-t0)+" ms.");
}
}
Loading

0 comments on commit 05dc383

Please sign in to comment.