/*
* 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.Arrays;
import java.io.IOException;
import org.geotoolkit.index.Data;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.NoSuchElementException;
import com.vividsolutions.jts.geom.Envelope;
import static org.geotoolkit.index.quadtree.AbstractNode.*;
/**
* Iterator that search the quad tree depth first. And return each node that match
* the given bounding box. It stores an addition information to know if each node
* is contained or just intersect, which mean a more accurate call to intersect
* on the geometry will be needed.
*
* @author Johann Sorel (Geomatys)
* @module
*/
public class LazyTyleSearchIterator implements SearchIterator<AbstractNode> {
private static class Segment{
private final AbstractNode node;
int childIndex;
int relation;
private Segment(final AbstractNode node, final int childIndex, final int relation){
this.node = node;
this.childIndex = childIndex;
this.relation = relation;
}
}
private final Envelope bounds;
private final double[] minRes;
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;
private boolean safe = false;
public LazyTyleSearchIterator(final AbstractNode node, final Envelope bounds, final double[] minRes) {
this.bounds = bounds;
this.minRes = minRes;
this.closed = false;
final int relation = node.relation(bounds);
if(relation != NONE){
path.add(new Segment(node, -1, relation));
}
}
@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;
if(current != null){
safe = path.getLast().relation == CONTAINED;
}else{
safe = false;
}
current = null;
return temp;
}
/**
* @return true if we are sure that the node in contained in the bbox.
*/
public boolean isSafe(){
return safe;
}
@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++;
//check the node size
if(!child.isBigger(minRes)){
continue childLoop;
}
if(segment.relation == CONTAINED){
//safe to add all child without test
path.addLast(new Segment(child, -1, CONTAINED));
}else{
final int relation = child.relation(bounds);
if (relation == NONE) {
//not in the area we requested
continue childLoop;
}else{
path.addLast(new Segment(child, -1,relation));
}
}
//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>{
//first bit contain the boolean safe value
private static final int SAFE_MASK = 0x1;
//we append this value on each id to preserve sort order when we add the safe bit
private static final int LB = 1<<31;
private final int bufferSize;
private final LazyTyleSearchIterator 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;
private boolean safe = false;
public Buffered(final AbstractNode node, final Envelope bounds, final double[] minRes, final DataReader reader, final int bufferSize){
this.ite = new LazyTyleSearchIterator(node, bounds, minRes);
this.bufferSize = bufferSize;
this.reader = reader;
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 boolean isSafe() {
return safe;
}
public void findNext(){
//next one already prepared
if(next!= null){
return;
}
//next one in the cache
if(index<indexSize){
next = (T) datas[index];
//first bit contain the boolean safe value
safe = (indices[index] & SAFE_MASK) != 0;
//prepare next one
index++;
return;
}
index = 0;
fillIndices();
// sort so offset lookup is faster
Arrays.sort(indices,0,indexSize);
try{
for(int i=0;i<indexSize;i++){
//extract the id from the value
datas[i] = reader.read( (indices[i]^LB) >>> 1);
}
}catch(IOException ex) {
throw new RuntimeException(ex);
}
if(indexSize>0){
next = (T) datas[0];
//first bit contain the boolean safe value
safe = (indices[0] & SAFE_MASK) != 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(){
final boolean nodeSafe = ite.isSafe();
int remaining = nodeIds.length-inc;
if(bufferSize-indexSize > remaining){
//we have enough space to store all remaining ids
if(nodeSafe){
for(int i=inc,j=indexSize,n=inc+remaining; i<n; i++,j++){
indices[j] = (nodeIds[i]<<1)^LB + 1;
}
}else{
for(int i=inc,j=indexSize,n=inc+remaining; i<n; i++,j++){
indices[j] = (nodeIds[i]<<1)^LB;
}
}
indexSize += remaining;
node = null;
return false;
}else{
//copy part of the ids
remaining = bufferSize-indexSize;
if(nodeSafe){
for(int i=inc,j=indexSize,n=inc+remaining; i<n; i++,j++){
indices[j] = (nodeIds[i]<<1)^LB + 1;
}
}else{
for(int i=inc,j=indexSize,n=inc+remaining; i<n; i++,j++){
indices[j] = (nodeIds[i]<<1)^LB;
}
}
indexSize += remaining;
inc += remaining;
return true;
}
}
@Override
public void close() throws StoreException{
ite.close();
}
@Override
public void remove() {
throw new UnsupportedOperationException("Not supported.");
}
}
}