package de.danielbasedow.prospecter.core.index; import de.danielbasedow.prospecter.core.Matcher; import de.danielbasedow.prospecter.core.Token; import de.danielbasedow.prospecter.core.document.Field; import de.danielbasedow.prospecter.core.geo.GeoPerimeter; import de.danielbasedow.prospecter.core.geo.LatLng; import gnu.trove.list.TLongList; import gnu.trove.list.array.TLongArrayList; import gnu.trove.procedure.TLongProcedure; import net.sf.jsi.SpatialIndex; import net.sf.jsi.rtree.RTree; import java.util.*; /** * Enables geo distance search queries. * <p/> * Indexing a geo distance Token results in entries in sorted maps for all four cardinal directions (N, E, S, W). * <p/> * To reduce range searches and simplifying calculations this implementation add 360° to the earth's longitudes. As in * reality, 180W equals 180E. Additionally 360E equals 180W and 360W. Same goes for the other direction. * <p/> * This allows simple calculations of eastern and western limits by adding or subtracting from the center. For searches * spanning 180° a second posting in all maps is added on the other side of the system. */ public class GeoDistanceIndex extends AbstractFieldIndex { /** * Tracks maximum distance seen during indexing. Allows reducing the area searched during matching */ private final SpatialIndex index = new RTree(); public GeoDistanceIndex(String name) { super(name); index.init(null); } @Override public void match(Field field, Matcher matcher) { List<Token> tokens = field.getTokens(); for (Token token : tokens) { LatLng latLng = (LatLng) token.getToken(); GeoPerimeter perimeter = new GeoPerimeter(latLng.getLatitude(), latLng.getLongitude(), 0); index.intersects(perimeter.getRectangle(), new MatchCollectionProcedure(matcher)); } } @Override public void addPosting(Token token, Long posting) { GeoPerimeter perimeter = (GeoPerimeter) token.getToken(); index.add(perimeter.getRectangle(), posting); if (perimeter.spans180Longitude()) { //if it spans 180° add fake posting on other side of earth GeoPerimeter bizarroPerimeter = perimeter.mirrorInFakeSpace(); index.add(bizarroPerimeter.getRectangle(), posting); } } @Override public FieldType getFieldType() { return FieldType.GEO_DISTANCE; } private class MatchCollectionProcedure implements TLongProcedure { private final Matcher matcher; public MatchCollectionProcedure(Matcher matcher) { this.matcher = matcher; } @Override public boolean execute(long i) { matcher.addHit(i); return true; } } }