Skip to content

Commit

Permalink
Developed initial HilbertSpaceFillingCurve 2D to 1D mapper
Browse files Browse the repository at this point in the history
  • Loading branch information
Lojjs committed Nov 29, 2017
1 parent 413ae38 commit 03456bf
Show file tree
Hide file tree
Showing 2 changed files with 258 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package org.neo4j.gis.spatial.index.hilbert;

import org.neo4j.gis.spatial.rtree.Envelope;

public class HilbertSpaceFillingCurve {

static class CurveRule {
private int[] npointValues;
private CurveRule[] children = null;

private CurveRule(int... npointValues) {
this.npointValues = npointValues;
}

private int indexForNPoint(int npoint) {
for (int index = 0; index < npointValues.length; index++) {
if (npointValues[index] == npoint) {
return index;
}
}
return -1;
}

public void setChildren(CurveRule... children) {
this.children = children;
}

public CurveRule childAt(int npoint) {
return children[npoint];
}
}

public static final CurveRule curveUp = new CurveRule(0, 1, 3, 2);
public static final CurveRule curveRight = new CurveRule(0, 2, 3, 1);
public static final CurveRule curveLeft = new CurveRule(3, 1, 0, 2);
public static final CurveRule curveDown = new CurveRule(3, 2, 0, 1);

static {
curveUp.setChildren(curveRight, curveUp, curveUp, curveLeft);
curveRight.setChildren(curveUp, curveRight, curveRight, curveDown);
curveDown.setChildren(curveLeft, curveDown, curveDown, curveRight);
curveLeft.setChildren(curveDown, curveLeft, curveLeft, curveUp);
}

public static final int MAX_LEVEL = 27;

private final Envelope range;
private final int maxLevel;
private final long width;

private double[] scalingFactor;

public HilbertSpaceFillingCurve(Envelope range) {
this(range, MAX_LEVEL);
}

public HilbertSpaceFillingCurve(Envelope range, int maxLevel) {
this.range = range;
this.maxLevel = maxLevel;
if (maxLevel < 1) {
throw new IllegalArgumentException("Hilbert index needs at least one level");
}
if (range.getDimension() != 2) {
throw new IllegalArgumentException("Hilbert index does not yet support more than 2 dimensions");
}
this.width = (long) Math.pow(2, maxLevel);
this.scalingFactor = new double[range.getDimension()];
for (int dim = 0; dim < range.getDimension(); dim++) {
scalingFactor[dim] = this.width / range.getWidth(dim);
}
}

public int getMaxLevel() {
return maxLevel;
}

public long getWidth() {
return this.width;
}

public long getMaxValue() {
return (long) Math.pow(2, maxLevel * range.getDimension()) - 1;
}

public double getTileWidth(int dimension) {
return 1.0/scalingFactor[dimension];
}

public Long longValueFor(double x, double y) {
return longValueFor(x, y, maxLevel);
}

private long getLongCoord(double value, int dimension) {
if (value >= range.getMax(dimension)) {
return width - 1;
} else if (value < range.getMin(dimension)) {
return 0;
} else {
return (long) ((value - range.getMin(dimension)) * scalingFactor[dimension]);
}
}

public Long longValueFor(double x, double y, int level) {
if (level > maxLevel) {
throw new IllegalArgumentException("Level " + level + " greater than max-level " + maxLevel);
}
long longX = getLongCoord(x, 0);
long longY = getLongCoord(y, 1);
long newValue = 0;
long mask = 1 << (maxLevel - 1);
int dimensions = range.getDimension();

// First level is a single curveUp
CurveRule currentCurve = curveUp;

for (int i = 1; i <= maxLevel; i++) {
if (i <= level) {
int bitIndex = maxLevel - i;
int bitX = (int) ((longX & mask) >> bitIndex);
int bitY = (int) ((longY & mask) >> bitIndex);
int npoint = bitX << 1 | bitY;
int derivedIndex = currentCurve.indexForNPoint(npoint);
newValue = (newValue << 2) | derivedIndex;
mask = mask >> 1;
currentCurve = currentCurve.childAt(derivedIndex);
} else {
newValue = newValue << dimensions;
}
}
return newValue;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package org.neo4j.gis.spatial.index.hilbert;

import org.junit.Test;
import org.neo4j.gis.spatial.rtree.Envelope;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;

public class HilbertSpaceFillingCurveTest {

@Test
public void shouldCreateSimpleHilberCurveOfOneLevel() {
assertAtLevel(new Envelope(-8, 8, -8, 8), 1);
}

@Test
public void shouldCreateSimpleHilberCurveOfTwoLevels() {
assertAtLevel(new Envelope(-8, 8, -8, 8), 2);
}

@Test
public void shouldCreateSimpleHilberCurveOfThreeLevels() {
assertAtLevel(new Envelope(-8, 8, -8, 8), 3);
}

@Test
public void shouldCreateSimpleHilberCurveOfFourLevels() {
assertAtLevel(new Envelope(-8, 8, -8, 8), 4);
}

@Test
public void shouldCreateSimpleHilberCurveOfFiveLevels() {
assertAtLevel(new Envelope(-8, 8, -8, 8), 5);
}

@Test
public void shouldCreateSimpleHilberCurveOfTwentyFourLevels() {
assertAtLevel(new Envelope(-8, 8, -8, 8), 24);
}

@Test
public void shouldCreateSimpleHilberCurveOfDefaultLevels() {
Envelope envelope = new Envelope(-8, 8, -8, 8);
assertAtLevel(new HilbertSpaceFillingCurve(envelope), envelope);
}

@Test
public void shouldCreateHilbertCurveWithRectangularEnvelope() {
assertAtLevel(new Envelope(-8, 8, -20,20 ), 3);
}

@Test
public void shouldCreateHilbertCurveWithNonCenteredEnvelope() {
assertAtLevel(new Envelope(2, 7, 2,7 ), 3);
}

@Test
public void shouldCreateHilbertCurveOfThreeLevelsFromExampleInThePaper() {
HilbertSpaceFillingCurve curve = new HilbertSpaceFillingCurve(new Envelope(0, 8, 0, 8), 3);
assertThat("Example should evaluate to 101110", curve.longValueFor(6, 4), equalTo(46L));
}

@Test
public void shouldWorkWithNormalGPSCoordinates() {
Envelope envelope = new Envelope(-180, 180, -90, 90);
HilbertSpaceFillingCurve curve = new HilbertSpaceFillingCurve(envelope);
assertAtLevel(curve, envelope);
}

private Envelope getTileEnvelope(Envelope envelope, int xindex, int yindex, int divisor) {
double width = envelope.getWidth(0) / divisor;
double height = envelope.getWidth(1) / divisor;
return new Envelope(
envelope.getMinX() + xindex * width,
envelope.getMinX() + (xindex + 1) * width,
envelope.getMinY() + yindex * height,
envelope.getMinY() + (yindex + 1) * height
);
}

private void assertRange(String message, HilbertSpaceFillingCurve curve, Envelope range, long value) {
for (double x = range.getMinX(); x < range.getMaxX(); x += 1.0) {
for (double y = range.getMinY(); y < range.getMaxY(); y += 1.0) {
assertThat(message + ": (" + x + "," + y + ")", curve.longValueFor(x, y), equalTo(value));
}
}
}

private void assertAtLevel(Envelope envelope, int level) {
assertAtLevel(new HilbertSpaceFillingCurve(envelope, level), envelope);
}

private void assertAtLevel(HilbertSpaceFillingCurve curve, Envelope envelope) {
int level = curve.getMaxLevel();
long width = (long) Math.pow(2, level);
long maxValue = width * width - 1;
double justInsideMaxX = envelope.getMaxX() - curve.getTileWidth(0)/2.0;
double justInsideMaxY = envelope.getMaxY() - curve.getTileWidth(1)/2.0;
double midX = (envelope.getMinX() + envelope.getMaxX()) / 2.0;
double midY = (envelope.getMinY() + envelope.getMaxY()) / 2.0;

long topRight = 0L;
long topRightFactor = 2L;
StringBuilder topRightDescription = new StringBuilder();
for (int l = 0; l < level; l++) {
topRight += topRightFactor;
if (topRightDescription.length() == 0) {
topRightDescription.append(topRightFactor);
} else {
topRightDescription.append(" + ").append(topRightFactor);
}
topRightFactor *= 4;
}

assertThat("Level " + level + " should have width of " + width, curve.getWidth(), equalTo(width));
assertThat("Level " + level + " should have max value of " + maxValue, curve.getMaxValue(), equalTo(maxValue));

assertThat("Bottom-left should evaluate to zero", curve.longValueFor(envelope.getMinX(), envelope.getMinY()), equalTo(0L));
assertThat("Just inside right edge on the bottom should evaluate to max-value", curve.longValueFor(justInsideMaxX, envelope.getMinY()), equalTo(curve.getMaxValue()));
assertThat("Just inside top-right corner should evaluate to " + topRightDescription, curve.longValueFor(justInsideMaxX, justInsideMaxY), equalTo(topRight));
assertThat("Right on top-right corner should evaluate to " + topRightDescription, curve.longValueFor(envelope.getMaxX(), envelope.getMaxY()), equalTo(topRight));
assertThat("Bottom-right should evaluate to max-value", curve.longValueFor(envelope.getMaxX(), envelope.getMinY()), equalTo(curve.getMaxValue()));
assertThat("Middle value should evaluate to (max-value+1) / 2", curve.longValueFor(midX, midY), equalTo((curve.getMaxValue() + 1) / 2));
}

}

0 comments on commit 03456bf

Please sign in to comment.