/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2007-2008, Open Source Geospatial Foundation (OSGeo)
*
* 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.geotools.caching.spatialindex;
import java.util.Stack;
/** This is a base class for implementing spatial indexes.
* It provides common routines useful for every type of indexes.
*
* @author Christophe Rousson, SoC 2007, CRG-ULAVAL
*
*
* @source $URL$
*/
public abstract class AbstractSpatialIndex implements SpatialIndex {
public static final int ContainmentQuery = 1;
public static final int IntersectionQuery = 2;
/**
* The node at the root of index.
* All others nodes should be direct or indirect children of this one.
*/
protected NodeIdentifier root;
protected Node rootNode = null;
protected Storage store;
/**
* Indexes can be n-dimensional, but queries and data should be consistent with regards to dimensions.
* This is the dimension of data shapes in the index, and should be considered final.
* (It is not because it makes things easier to initialize index from a serialized form).
*/
protected int dimension;
protected Region infiniteRegion;
protected SpatialIndexStatistics stats = new SpatialIndexStatistics();
public Storage getStorage(){
return this.store;
}
public void intersectionQuery(Shape query, Visitor v) {
if (query.getDimension() != dimension) {
throw new IllegalArgumentException(
"intersectionQuery: Shape has the wrong number of dimensions.");
}
rangeQuery(IntersectionQuery, query, v);
}
public void containmentQuery(Shape query, Visitor v) {
if (query.getDimension() != dimension) {
throw new IllegalArgumentException(
"containmentQuery: Shape has the wrong number of dimensions.");
}
rangeQuery(ContainmentQuery, query, v);
}
public void pointLocationQuery(Point query, Visitor v) {
if (query.getDimension() != dimension) {
throw new IllegalArgumentException(
"pointLocationQuery: Shape has the wrong number of dimensions.");
}
/*Region r = null;
if (query instanceof Point) {
r = new Region((Point) query, (Point) query);
} else if (query instanceof Region) {
r = (Region) query;
} else {
throw new IllegalArgumentException(
"pointLocationQuery: Shape can be Point or Region only.");
}*/
Region r = new Region(query, query);
rangeQuery(IntersectionQuery, r, v);
}
/** Common algorithm used by both intersection and containment queries.
*
* @param type
* @param query
* @param v
*
*/
protected void rangeQuery(int type, Shape query, Visitor v) {
NodePointer current = null;
Stack<NodePointer> notYetVisitedNodes = new Stack<NodePointer>();
Stack<NodePointer> visitedNodes = new Stack<NodePointer>();
if (query.intersects(this.root.getShape())) {
current = new NodePointer(readNode(this.root));
notYetVisitedNodes.push(current);
}
while (!notYetVisitedNodes.isEmpty() || !visitedNodes.isEmpty()) {
if (!notYetVisitedNodes.isEmpty()) {
current = notYetVisitedNodes.pop();
v.visitNode(current.node);
if (v.isDataVisitor()) { // skip if visitor does nothing with data
// visitData check for actual containment or intersection
visitData(current.node, v, query, type);
}
} else {
current = visitedNodes.pop();
}
while (current.hasNext()) {
NodeIdentifier child = current.next();
if (query.intersects(child.getShape())) {
Node n = readNode(child);
NodePointer np = new NodePointer(n);
notYetVisitedNodes.push(np);
visitedNodes.push(current);
break;
}
}
}
}
/** Visit data associated with a node using given visitor.
* At this stage, we only know that node's MBR intersects query.
* This method is reponsible for iterating over node's data, if any,
* and for checking if data is actually part of the query result.
* Then it uses the visitor's visit() method on the selected data.
*
* @param node to visit
* @param visitor for callback
* @param query
* @param type of query, either containement or intersection (@see AbstractSpatialIndex)
*/
protected abstract void visitData(Node n, Visitor v, Shape query, int type);
public void nearestNeighborQuery(int k, Shape query, Visitor v, NearestNeighborComparator nnc) {
// TODO Auto-generated method stub
}
public void nearestNeighborQuery(int k, Shape query, Visitor v) {
// TODO Auto-generated method stub
}
/**
* Inserts data into the spatial index.
*
* <p>Items with the same "data" and "shape"
* will be considered equal and only one copy of them will be added to the
* cache.
* </p>
*
* @param data to insert
* @param shape associated with data
* @param id the id of the data
*
*
*/
public void insertData(Object data, Shape shape) {
if (shape.getDimension() != dimension) {
throw new IllegalArgumentException(
"insertData: Shape has the wrong number of dimensions.");
}
if (this.root.getShape().contains(shape)) {
insertData(this.root, data, shape);
} else {
insertDataOutOfBounds(data, shape);
}
}
/** Insert new data into target node. Node may delegate to child nodes, if required.
* Implementation note : it is assumed arguments verify :
* <code>node.getShape().contains(shape)</code>
* So this must be checked before calling this method.
*
* @param node where to insert data
* @param data
* @param shape of data
* @param id of data
*/
protected abstract void insertData(NodeIdentifier n, Object data, Shape shape);
/** Insert new data with shape not contained in the current index.
* Some indexes may require to recreate the root or the index,
* depending on the type of index ...
*
* @param data
* @param shape
* @param id
*/
protected abstract void insertDataOutOfBounds(Object data, Shape shape);
public Statistics getStatistics() {
return stats;
}
protected Node readNode(NodeIdentifier id) {
Node ret;
if ((rootNode != null) && (id.equals(this.root))) {
return rootNode;
}
ret = id.getNode();
if (ret != null){
return ret;
}
ret = store.get(id);
stats.stats_reads++;
id.setNode(ret);
return ret;
}
protected void writeNode(Node node) {
if (node.getIdentifier().equals(this.root)) {
this.rootNode = node;
return;
}
store.put(node);
stats.stats_writes++;
}
protected void deleteNode(NodeIdentifier id) {
store.remove(id);
}
public synchronized void flush() {
if (this.rootNode != null) {
store.put(this.rootNode);
}
store.flush();
}
class NodePointer {
Node node;
int nextidx = 0;
NodePointer(Node n) {
this.node = n;
}
boolean hasNext() {
return (nextidx < node.getChildrenCount());
}
NodeIdentifier next() {
if (hasNext()) {
NodeIdentifier next = node.getChildIdentifier(nextidx);
nextidx++;
return next;
} else {
return null;
}
}
}
}