/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2005-2008, Open Source Geospatial Foundation (OSGeo)
* (C) 2009-2010, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library 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
* Lesser General Public License for more details.
*/
package org.geotoolkit.index.quadtree;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.NoSuchElementException;
import com.vividsolutions.jts.geom.Envelope;
import java.io.IOException;
import java.util.Arrays;
import org.geotoolkit.index.Data;
/**
* Iterator that search the quad tree depth first. And return each node that match
* the given bounding box.
*
* @author Jesse
* @author Johann Sorel (Geomatys)
* @module
*/
public class LazySearchIterator implements SearchIterator<AbstractNode> {
private static class Segment{
private final AbstractNode node;
int childIndex;
private Segment(final AbstractNode node, final int childIndex){
this.node = node;
this.childIndex = childIndex;
}
}
private final Envelope bounds;
private boolean closed;
//the current path where we are, Integer is the current visited child node index.
private final Deque<Segment> path = new ArrayDeque<Segment>(10);
//curent visited node
private AbstractNode current = null;
public LazySearchIterator(final AbstractNode node, final Envelope bounds, final double[] minRes) {
this.bounds = bounds;
this.closed = false;
if(node.intersects(bounds)){
path.add(new Segment(node, -1));
}
}
@Override
public boolean hasNext() {
findNext();
return current != null;
}
@Override
public AbstractNode next() {
findNext();
if (current == null){
throw new NoSuchElementException("No more elements available");
}
final AbstractNode temp = current;
current = null;
return temp;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public void close() throws StoreException {
this.closed = true;
}
private void findNext(){
if (closed){
throw new IllegalStateException("Iterator has been closed!");
}
if(current != null){
//we already have the next one
return;
}
try {
//search the next node that has shpIds
findNextNode();
} catch (StoreException ex) {
throw new RuntimeException(ex);
}
}
private void findNextNode() throws StoreException {
nodeLoop:
do{
final Segment segment = path.peekLast();
if(segment == null){
current = null;
return;
}
if(segment.childIndex == -1){
//prepare for next node search
segment.childIndex = 0;
//we have not yet explore this node ids
//we use the node shpIds if he has some
final int nb = segment.node.getNumShapeIds();
if(nb>0){
//we have some ids in this node
current = segment.node;
return;
}
}
final int nbNodes = segment.node.getNumSubNodes();
childLoop:
while (segment.childIndex < nbNodes) {
final AbstractNode child = segment.node.getSubNode(segment.childIndex);
//prepare next node search
segment.childIndex++;
if (!child.intersects(bounds)) {
//not in the area we requested
continue childLoop;
}
path.addLast(new Segment(child, -1));
//explore the sub node
continue nodeLoop;
}
//we have nothing left to explore in this node, go back to the parent
//to explore next branch
path.removeLast();
}while(current == null);
}
/**
* Holds a buffer of values to avoid open files to often.
*/
public static final class Buffered<T extends Data> implements SearchIterator<T>{
private final int bufferSize;
private final LazySearchIterator ite;
private final DataReader reader;
//the last node we retrieved
private AbstractNode node = null;
private int[] nodeIds = null;
private int inc = 0;
private final int[] indices;
private final Data[] datas;
private int indexSize = 0;
private int index = 0;
private T next = null;
public Buffered(final AbstractNode node, final Envelope bounds, final double[] minRes, final DataReader reader, final int bufferSize){
this.bufferSize = bufferSize;
this.reader = reader;
this.ite = new LazySearchIterator(node, bounds, minRes);
indices = new int[bufferSize];
datas = new Data[bufferSize];
}
@Override
public boolean hasNext() {
findNext();
return next != null;
}
@Override
public T next() {
findNext();
final T temp = next;
next = null;
return temp;
}
public void findNext(){
//next one already prepared
if(next!= null){
return;
}
//next one in the cache
if(index<indexSize){
next = (T) datas[index];
//prepare next one
index++;
return;
}
index = 0;
fillIndices();
// sort so offset lookup is faster
Arrays.sort(indices,0,indexSize);
try{
reader.read(indices, datas, indexSize);
}catch(IOException ex) {
throw new RuntimeException(ex);
}
if(indexSize>0){
next = (T) datas[0];
index++;
}
}
private void fillIndices(){
indexSize=0;
//copy the remaining ids from the last node
if(node != null){
if(fillWithNode()){
return;
}
}
while(ite.hasNext()){
node = ite.next();
nodeIds = node.getShapesId();
inc=0;
if(fillWithNode()){
//we reach buffer limit
return;
}
}
}
/**
* @return true if the buffer is at max capacity
*/
private boolean fillWithNode(){
int remaining = nodeIds.length-inc;
if(bufferSize-indexSize > remaining){
//we have enough space to store all remaining ids
System.arraycopy(nodeIds, inc, indices, indexSize, remaining);
indexSize += remaining;
node = null;
return false;
}else{
//copy part of the ids
remaining = bufferSize-indexSize;
System.arraycopy(nodeIds, inc, indices, indexSize, remaining);
indexSize += remaining;
inc += remaining;
return true;
}
}
@Override
public void close() throws StoreException{
ite.close();
}
@Override
public void remove() {
throw new UnsupportedOperationException("Not supported.");
}
}
}