private void shouldNeverStepMoreThanDistanceOne( SpaceFillingCurve curve, int level, int badnessThresholdPercentage ) { int badCount = 0; long[] previous = null; for ( long derivedValue = 0; derivedValue < curve.getValueWidth(); derivedValue++ ) { long[] point = curve.normalizedCoordinateFor( derivedValue, level ); if ( previous != null ) { double distance = 0; for ( int i = 0; i < point.length; i++ ) { distance += Math.pow( point[i] - previous[i], 2 ); } distance = Math.sqrt( distance ); if ( distance > 1.0 ) { badCount++; } } previous = point; } int badness = (int) (100 * badCount / (curve.getValueWidth() - 1)); assertThat( "Bad distance percentage should never be greater than " + badnessThresholdPercentage + "%", badness, lessThanOrEqualTo( badnessThresholdPercentage ) ); logger.debug( String.format( "Bad distance count for level: %d (%d/%d = %d%%)", level, badCount, curve.getValueWidth() - 1, badness ) ); }
/** * This test can be uses to reproduce a bug with a single search envelope, if {@link #shouldHaveReasonableCoveredArea()} * fails an assertion. It should be disabled by default. */ @Disabled public void debugSingle() { final int xmin = -100; final int xmax = 100; final int ymin = -100; final int ymax = 100; final int level = 1; final double xStart = -0x1.9p6; final double xEnd = -0x1.8ffffd60e94eep6; final double yStart = 0x1.8ff5c28f5c28ep6; final double yEnd = 0x1.8ffffffffffffp6; Envelope envelope = new Envelope( xmin, xmax, ymin, ymax ); HilbertSpaceFillingCurve2D curve = new HilbertSpaceFillingCurve2D( envelope, level ); Envelope searchEnvelope = new Envelope( xStart, xEnd, yStart, yEnd ); HistogramMonitor monitor = new HistogramMonitor( curve.getMaxLevel() ); List<SpaceFillingCurve.LongRange> ranges = curve.getTilesIntersectingEnvelope( searchEnvelope, new StandardConfiguration(), monitor ); logger.debug( String.format( "Results for level %d, with x=[%f,%f] y=[%f,%f]\n", level, xStart, xEnd, yStart, yEnd ) ); logger.debug( String.format( "Search size vs covered size: %d vs %d\n", monitor.getSearchArea(), monitor.getCoveredArea() ) ); logger.debug( "Ranges: " + ranges.size() ); int[] counts = monitor.getCounts(); for ( int i = 0; i <= curve.getMaxLevel(); i++ ) { logger.debug( "\t" + i + "\t" + counts[i] ); } }
@Test void shouldGet2DHilbertSearchTilesForCenterRangeAndTraverseToBottom() { TraverseToBottomConfiguration configuration = new TraverseToBottomConfiguration(); Envelope envelope = new Envelope( -8, 8, -8, 8 ); for ( int level = 2; level <= 11; level++ ) // 12 takes 6s, 13 takes 25s, 14 takes 100s, 15 takes over 400s { HilbertSpaceFillingCurve2D curve = new HilbertSpaceFillingCurve2D( envelope, level ); double fullTile = curve.getTileWidth( 0, level ); double halfTile = fullTile / 2.0; Envelope centerWithoutOuterRing = new Envelope( envelope.getMin( 0 ) + fullTile + halfTile, envelope.getMax( 0 ) - fullTile - halfTile, envelope.getMin( 1 ) + fullTile + halfTile, envelope.getMax( 1 ) - fullTile - halfTile ); long start = System.currentTimeMillis(); List<SpaceFillingCurve.LongRange> result = curve.getTilesIntersectingEnvelope( centerWithoutOuterRing, configuration, null ); logger.debug( "Hilbert query at level " + level + " took " + (System.currentTimeMillis() - start) + "ms to produce " + result.size() + " tiles" ); assertTiles( result, tilesNotTouchingOuterRing( curve ).toArray( new SpaceFillingCurve.LongRange[0] ) ); } }
@Test void shouldGet3DSearchTilesForManyLevels() { Envelope envelope = new Envelope( new double[]{-8, -8, -8}, new double[]{8, 8, 8} ); for ( int level = 1; level <= HilbertSpaceFillingCurve3D.MAX_LEVEL; level++ ) { HilbertSpaceFillingCurve3D curve = new HilbertSpaceFillingCurve3D( envelope, level ); double halfTile = curve.getTileWidth( 0, level ) / 2.0; long start = System.currentTimeMillis(); assertTiles( curve.getTilesIntersectingEnvelope( new Envelope( new double[]{-8, -8, -8}, new double[]{-8 + halfTile, -8 + halfTile, -8 + halfTile} ) ), new SpaceFillingCurve.LongRange( 0, 0 ) ); assertTiles( curve.getTilesIntersectingEnvelope( new Envelope( new double[]{8 - halfTile, -8, -8}, new double[]{8, -8 + halfTile, -8 + halfTile} ) ), new SpaceFillingCurve.LongRange( curve.getValueWidth() - 1, curve.getValueWidth() - 1 ) ); //TODO: There is a performance issue building the full range when the search envelope hits a very wide part of the extent // Suggestion to fix this with shallower traversals //assertTiles(curve.getTilesIntersectingEnvelope(new Envelope(new double[]{-8, -8, -8}, new double[]{8, 8, 8})), // new HilbertSpaceFillingCurve.LongRange(0, curve.getValueWidth() - 1)); logger.debug( "Hilbert query at level " + level + " took " + (System.currentTimeMillis() - start) + "ms" ); } }
@Test void shouldCreateSimple2DHilbertCurveAtManyLevels() { Envelope envelope = new Envelope( new double[]{-8, -8}, new double[]{8, 8} ); for ( int level = 1; level <= HilbertSpaceFillingCurve2D.MAX_LEVEL; level++ ) { HilbertSpaceFillingCurve2D curve = new HilbertSpaceFillingCurve2D( envelope, level ); logger.debug( "Max value at level " + level + ": " + curve.getValueWidth() ); assertAtLevel( curve, envelope ); assertRange( (int) curve.getWidth(), 0, curve, 0, 0 ); assertRange( (int) curve.getWidth(), curve.getValueWidth() - 1, curve, (int) curve.getWidth() - 1, 0 ); } }
@Test void shouldCreateSimple3DHilbertCurveAtManyLevels() { Envelope envelope = new Envelope( new double[]{-8, -8, -8}, new double[]{8, 8, 8} ); for ( int level = 1; level <= HilbertSpaceFillingCurve3D.MAX_LEVEL; level++ ) { HilbertSpaceFillingCurve3D curve = new HilbertSpaceFillingCurve3D( envelope, level ); logger.debug( "Max value at level " + level + ": " + curve.getValueWidth() ); assertAtLevel( curve, envelope ); assertRange( (int) curve.getWidth(), 0, curve, 0, 0, 0 ); assertRange( (int) curve.getWidth(), curve.getValueWidth() - 1, curve, (int) curve.getWidth() - 1, 0, 0 ); } }
@Test void shouldCreateSimple2DZOrderCurveAtManyLevels() { Envelope envelope = new Envelope( new double[]{-8, -8}, new double[]{8, 8} ); for ( int level = 1; level <= ZOrderSpaceFillingCurve2D.MAX_LEVEL; level++ ) { ZOrderSpaceFillingCurve2D curve = new ZOrderSpaceFillingCurve2D( envelope, level ); logger.debug( "Max value at level " + level + ": " + curve.getValueWidth() ); assertAtLevel( curve, envelope ); assertRange( (int) curve.getWidth(), 0, curve, 0, (int) curve.getWidth() - 1 ); assertRange( (int) curve.getWidth(), curve.getValueWidth() - 1, curve, (int) curve.getWidth() - 1, 0 ); } }
@Test void shouldGet2DZOrderSearchTilesForManyLevels() { Envelope envelope = new Envelope( -8, 8, -8, 8 ); for ( int level = 1; level <= ZOrderSpaceFillingCurve2D.MAX_LEVEL; level++ ) { ZOrderSpaceFillingCurve2D curve = new ZOrderSpaceFillingCurve2D( envelope, level ); double halfTile = curve.getTileWidth( 0, level ) / 2.0; long start = System.currentTimeMillis(); assertTiles( curve.getTilesIntersectingEnvelope( new Envelope( -8, -8 + halfTile, 8 - halfTile, 8 ) ), new SpaceFillingCurve.LongRange( 0, 0 ) ); assertTiles( curve.getTilesIntersectingEnvelope( new Envelope( 8 - halfTile, 8, -8, -8 + halfTile ) ), new SpaceFillingCurve.LongRange( curve.getValueWidth() - 1, curve.getValueWidth() - 1 ) ); assertTiles( curve.getTilesIntersectingEnvelope( new Envelope( 8 - halfTile, 8, 0, 0 + halfTile ) ), new SpaceFillingCurve.LongRange( curve.getValueWidth() / 2 - 1, curve.getValueWidth() / 2 - 1 ) ); logger.debug( "Hilbert query at level " + level + " took " + (System.currentTimeMillis() - start) + "ms" ); } }
@Test void shouldGet2DHilbertSearchTilesForManyLevels() { Envelope envelope = new Envelope( -8, 8, -8, 8 ); for ( int level = 1; level <= HilbertSpaceFillingCurve2D.MAX_LEVEL; level++ ) { HilbertSpaceFillingCurve2D curve = new HilbertSpaceFillingCurve2D( envelope, level ); double halfTile = curve.getTileWidth( 0, level ) / 2.0; long start = System.currentTimeMillis(); assertTiles( curve.getTilesIntersectingEnvelope( new Envelope( -8, -8 + halfTile, -8, -8 + halfTile ) ), new SpaceFillingCurve.LongRange( 0, 0 ) ); assertTiles( curve.getTilesIntersectingEnvelope( new Envelope( 8 - halfTile, 8, -8, -8 + halfTile ) ), new SpaceFillingCurve.LongRange( curve.getValueWidth() - 1, curve.getValueWidth() - 1 ) ); assertTiles( curve.getTilesIntersectingEnvelope( new Envelope( 0, halfTile, 0, halfTile ) ), new SpaceFillingCurve.LongRange( curve.getValueWidth() / 2, curve.getValueWidth() / 2 ) ); logger.debug( "Hilbert query at level " + level + " took " + (System.currentTimeMillis() - start) + "ms" ); } }