Skip to content

Commit

Permalink
Merge pull request #10912 from sherfert/3.4-spatial-index-performance
Browse files Browse the repository at this point in the history
Improve performace of SpaceFillingCurve queries.
  • Loading branch information
Lojjs committed Feb 15, 2018
2 parents 80dd5e5 + 974521c commit 21e71a6
Show file tree
Hide file tree
Showing 10 changed files with 839 additions and 114 deletions.
Expand Up @@ -395,17 +395,15 @@ case class LogicalPlan2PlanDescription(readOnly: Boolean, cardinalities: Cardina
(name, PrefixIndex(label.name, propertyKey, range.prefix))
case InequalitySeekRangeWrapper(RangeLessThan(bounds)) =>
(name, InequalityIndex(label.name, propertyKey,
bounds.map(bound => s"<${bound.inequalitySignSuffix} ${bound.endPoint.asCanonicalStringVal}")
.toIndexedSeq))
bounds.map(bound => s"<${bound.inequalitySignSuffix} ${bound.endPoint.asCanonicalStringVal}").toIndexedSeq))
case InequalitySeekRangeWrapper(RangeGreaterThan(bounds)) =>
(name, InequalityIndex(label.name, propertyKey,
bounds.map(bound => s">${bound.inequalitySignSuffix} ${bound.endPoint.asCanonicalStringVal}")
.toIndexedSeq))
bounds.map(bound => s">${bound.inequalitySignSuffix} ${bound.endPoint.asCanonicalStringVal}").toIndexedSeq))
case InequalitySeekRangeWrapper(RangeBetween(greaterThanBounds, lessThanBounds)) =>
val greaterThanBoundsText = greaterThanBounds.bounds
.map(bound => s">${bound.inequalitySignSuffix} ${bound.endPoint.asCanonicalStringVal}").toIndexedSeq
val lessThanBoundsText = lessThanBounds.bounds
.map(bound => s"<${bound.inequalitySignSuffix} ${bound.endPoint.asCanonicalStringVal}").toIndexedSeq
val greaterThanBoundsText = greaterThanBounds.bounds.map(bound =>
s">${bound.inequalitySignSuffix} ${bound.endPoint.asCanonicalStringVal}").toIndexedSeq
val lessThanBoundsText = lessThanBounds.bounds.map(bound =>
s"<${bound.inequalitySignSuffix} ${bound.endPoint.asCanonicalStringVal}").toIndexedSeq
(name, InequalityIndex(label.name, propertyKey, greaterThanBoundsText ++ lessThanBoundsText))
case _ => throw new InternalException("This should never happen. Missing a case?")
}
Expand Down
@@ -0,0 +1,134 @@
/*
* Copyright (c) 2002-2018 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.gis.spatial.index.curves;

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

/**
* N-dimensional searchEnvelope
*/
class SearchEnvelope
{
private long[] min; // inclusive lower bounds
private long[] max; // exclusive upper bounds
private int nbrDim;

SearchEnvelope( SpaceFillingCurve curve, Envelope referenceEnvelope )
{
this.min = curve.getNormalizedCoord( referenceEnvelope.getMin() );
this.max = curve.getNormalizedCoord( referenceEnvelope.getMax() );
this.nbrDim = referenceEnvelope.getDimension();
for ( int i = 0; i < nbrDim; i++ )
{
// getNormalizedCoord gives inclusive bounds. Need to increment to make the upper exclusive.
this.max[i] += 1;
}
}

private SearchEnvelope( long[] min, long[] max )
{
this.min = min;
this.max = max;
this.nbrDim = min.length;
}

SearchEnvelope( long min, long max, int nbrDim )
{
this.nbrDim = nbrDim;
this.min = new long[nbrDim];
this.max = new long[nbrDim];

for ( int dim = 0; dim < nbrDim; dim++ )
{
this.min[dim] = min;
this.max[dim] = max;
}
}

SearchEnvelope quadrant( int[] quadNbrs )
{
long[] newMin = new long[nbrDim];
long[] newMax = new long[nbrDim];

for ( int dim = 0; dim < nbrDim; dim++ )
{
long extent = (max[dim] - min[dim]) / 2;
newMin[dim] = this.min[dim] + quadNbrs[dim] * extent;
newMax[dim] = this.min[dim] + (quadNbrs[dim] + 1) * extent;
}
return new SearchEnvelope( newMin, newMax );
}

boolean contains( long[] coord )
{
for ( int dim = 0; dim < nbrDim; dim++ )
{
if ( coord[dim] < min[dim] || coord[dim] >= max[dim] )
{
return false;
}
}
return true;
}

boolean intersects( SearchEnvelope other )
{
for ( int dim = 0; dim < nbrDim; dim++ )
{
if ( this.max[dim] <= other.min[dim] || other.max[dim] <= this.min[dim] )
{
return false;
}
}
return true;
}

/**
* Calculates the faction of the overlapping area between {@code this} and {@code} other compared
* to the area of {@code this}.
*
* Must only be called for intersecting envelopes
*/
double fractionOf( SearchEnvelope other )
{
double fraction = 1.0;
for ( int i = 0; i < nbrDim; i++ )
{
long min = Math.max( this.min[i], other.min[i] );
long max = Math.min( this.max[i], other.max[i] );
final double innerFraction = (double) (max - min) / (double) (other.max[i] - other.min[i]);
fraction *= innerFraction;
}
return fraction;
}

/**
* The smallest possible envelope has unit area 1
*/
public long getArea()
{
long area = 1;
for ( int i = 0; i < nbrDim; i++ )
{
area *= max[i] - min[i];
}
return area;
}
}
Expand Up @@ -257,15 +257,30 @@ long[] normalizedCoordinateFor( long derivedValue, int level )
*/
public List<LongRange> getTilesIntersectingEnvelope( Envelope referenceEnvelope )
{
SearchEnvelope search = new SearchEnvelope( referenceEnvelope );
ArrayList<LongRange> results = new ArrayList<>();
return getTilesIntersectingEnvelope( referenceEnvelope, new StandardConfiguration(), null );
}

/**
* Given an envelope, find a collection of LongRange of tiles intersecting it on maxLevel and merge adjacent ones
*/
List<LongRange> getTilesIntersectingEnvelope( Envelope referenceEnvelope, SpaceFillingCurveConfiguration config, SpaceFillingCurveMonitor monitor )
{
SearchEnvelope search = new SearchEnvelope( this, referenceEnvelope );
SearchEnvelope wholeExtent = new SearchEnvelope( 0, this.getWidth(), nbrDim );
ArrayList<LongRange> results = new ArrayList<>( config.initialRangesListCapacity() );

if ( monitor != null )
{
monitor.registerSearchArea( search.getArea() );
}

addTilesIntersectingEnvelopeAt( search, new SearchEnvelope( 0, this.getWidth(), nbrDim ), rootCurve(), 0, this.getValueWidth(), results );
addTilesIntersectingEnvelopeAt( config, monitor, 0, config.maxDepth( referenceEnvelope, this.range, nbrDim, maxLevel ), search,
wholeExtent, rootCurve(), 0, this.getValueWidth(), results );
return results;
}

private void addTilesIntersectingEnvelopeAt( SearchEnvelope search, SearchEnvelope currentExtent, CurveRule curve, long left, long right,
ArrayList<LongRange> results )
private void addTilesIntersectingEnvelopeAt( SpaceFillingCurveConfiguration config, SpaceFillingCurveMonitor monitor, int depth, int maxDepth,
SearchEnvelope search, SearchEnvelope currentExtent, CurveRule curve, long left, long right, ArrayList<LongRange> results )
{
if ( right - left == 1 )
{
Expand All @@ -282,17 +297,46 @@ private void addTilesIntersectingEnvelopeAt( SearchEnvelope search, SearchEnvelo
current = new LongRange( left );
results.add( current );
}
if ( monitor != null )
{
monitor.addRangeAtDepth( depth );
monitor.addToCoveredArea( currentExtent.getArea() );
}
}
}
else if ( search.intersects( currentExtent ) )
{
long width = (right - left) / quadFactor;
for ( int i = 0; i < quadFactor; i++ )
double overlap = search.fractionOf( currentExtent );
if ( config.stopAtThisDepth( overlap, depth, maxDepth ) )
{
int npoint = curve.npointForIndex( i );
// Note that LongRange upper bound is inclusive, hence the '-1' in several places
LongRange current = (results.size() > 0) ? results.get( results.size() - 1 ) : null;
if ( current != null && current.max == left - 1 )
{
current.expandToMax( right - 1 );
}
else
{
current = new LongRange( left, right - 1 );
results.add( current );
}
if ( monitor != null )
{
monitor.addRangeAtDepth( depth );
monitor.addToCoveredArea( currentExtent.getArea() );
}
}
else
{
long width = (right - left) / quadFactor;
for ( int i = 0; i < quadFactor; i++ )
{
int npoint = curve.npointForIndex( i );

SearchEnvelope quadrant = currentExtent.quadrant( bitValues( npoint ) );
addTilesIntersectingEnvelopeAt( search, quadrant, curve.childAt( i ), left + i * width, left + (i + 1) * width, results );
SearchEnvelope quadrant = currentExtent.quadrant( bitValues( npoint ) );
addTilesIntersectingEnvelopeAt( config, monitor, depth + 1, maxDepth, search, quadrant, curve.childAt( i ), left + i * width,
left + (i + 1) * width, results );
}
}
}
}
Expand All @@ -315,16 +359,17 @@ private int[] bitValues( int npoint )
/**
* Given a coordinate, find the corresponding normalized coordinate
*/
private long[] getNormalizedCoord( double[] coord )
long[] getNormalizedCoord( double[] coord )
{
long[] normalizedCoord = new long[nbrDim];

for ( int dim = 0; dim < nbrDim; dim++ )
{
double value = clamp( coord[dim], range.getMin( dim ), range.getMax( dim ) );
if ( value == range.getMax( dim ) )
// Avoiding awkward rounding errors
if ( value - range.getMin( dim ) == range.getMax( dim ) - range.getMin( dim ) )
{
normalizedCoord[dim] = valueWidth - 1;
normalizedCoord[dim] = width - 1;
}
else
{
Expand Down Expand Up @@ -420,79 +465,4 @@ public String toString()
return "LongRange(" + min + "," + max + ")";
}
}

/**
* N-dimensional searchEnvelope
*/
private class SearchEnvelope
{
long[] min;
long[] max;
int nbrDim;

private SearchEnvelope( Envelope referenceEnvelope )
{
this.min = getNormalizedCoord( referenceEnvelope.getMin() );
this.max = getNormalizedCoord( referenceEnvelope.getMax() );
this.nbrDim = referenceEnvelope.getDimension();
}

private SearchEnvelope( long[] min, long[] max )
{
this.min = min;
this.max = max;
this.nbrDim = min.length;
}

private SearchEnvelope( long min, long max, int nbrDim )
{
this.nbrDim = nbrDim;
this.min = new long[nbrDim];
this.max = new long[nbrDim];

for ( int dim = 0; dim < nbrDim; dim++ )
{
this.min[dim] = min;
this.max[dim] = max;
}
}

private SearchEnvelope quadrant( int[] quadNbrs )
{
long[] newMin = new long[nbrDim];
long[] newMax = new long[nbrDim];

for ( int dim = 0; dim < nbrDim; dim++ )
{
long extent = (max[dim] - min[dim]) / 2;
newMin[dim] = this.min[dim] + quadNbrs[dim] * extent;
newMax[dim] = this.min[dim] + (quadNbrs[dim] + 1) * extent;
}
return new SearchEnvelope( newMin, newMax );
}

private boolean contains( long[] coord )
{
for ( int dim = 0; dim < nbrDim; dim++ )
{
if ( coord[dim] < min[dim] || coord[dim] > max[dim] )
{
return false;
}
}
return true;
}

private boolean intersects( SearchEnvelope other )
{
for ( int dim = 0; dim < nbrDim; dim++ )
{
if ( max[dim] < other.min[dim] || other.max[dim] < min[dim] )
{
return false;
}
}
return true;
}
}
}

0 comments on commit 21e71a6

Please sign in to comment.