Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ nbactions.xml
/store/graphstore/target/
.idea
*.iml
.vscode/**
42 changes: 40 additions & 2 deletions src/main/java/org/gephi/graph/api/Rect2D.java
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@ public String toString() {
}

private String toString(NumberFormat formatter) {
return "(" + formatter.format(minX) + " " + formatter.format(minY) + ") < " + "(" + formatter
.format(maxX) + " " + formatter.format(maxY) + ")";
return "min(x:" + formatter.format(minX) + " y:" + formatter.format(minY) + ") < " + "max(x:" + formatter
.format(maxX) + " y:" + formatter.format(maxY) + ")";
}

/**
Expand Down Expand Up @@ -174,6 +174,44 @@ public boolean intersects(float minX, float minY, float maxX, float maxY) {
return this.minX <= maxX && minX <= this.maxX && this.maxY >= minY && maxY >= this.minY;
}

/**
* Returns true if this rectangle contains or intersects with the given
* rectangle. This is equivalent to checking
* {@code this.contains(rect) || this.intersects(rect)} but more efficient as it
* performs the check in a single operation.
*
* @param rect the rectangle to check
* @return true if this rectangle contains or intersects with the given
* rectangle, false otherwise
*/
public boolean containsOrIntersects(Rect2D rect) {
if (rect == this) {
return true;
}

return containsOrIntersects(rect.minX, rect.minY, rect.maxX, rect.maxY);
}

/**
* Returns true if this rectangle contains or intersects with the given
* rectangle. This is equivalent to checking
* {@code this.contains(minX, minY, maxX, maxY) || this.intersects(minX, minY, maxX, maxY)}
* but more efficient as it performs the check in a single operation.
*
* @param minX the x coordinate of the minimum corner
* @param minY the y coordinate of the minimum corner
* @param maxX the x coordinate of the maximum corner
* @param maxY the y coordinate of the maximum corner
*
* @return true if this rectangle contains or intersects with the given
* rectangle, false otherwise
*/
public boolean containsOrIntersects(float minX, float minY, float maxX, float maxY) {
// Two rectangles have overlap if they intersect - containment is a subset of
// intersection
return this.minX <= maxX && minX <= this.maxX && this.maxY >= minY && maxY >= this.minY;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
Expand Down
54 changes: 52 additions & 2 deletions src/main/java/org/gephi/graph/api/SpatialIndex.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,19 @@
package org.gephi.graph.api;

/**
* Object to query the nodes and edges of the graph in a spatial context.
* Query the (quadtree-based) index based on the given rectangle area.
* <p>
* The spatial index is not enabled by default. To enable it, set the
* appropriate configuration:
* <code>@{@link Configuration.Builder#enableSpatialIndex(boolean)}</code>.
* <p>
* When nodes are moved, added or removed, the spatial index is automatically
* updated. Edges are not indexed, but they are queried based on whether their
* source or target nodes are in the given area.
* <p>
* The Z position is not taken into account when querying the spatial index,
* only X/Y are supported.
* </p>
*
* @author Eduardo Ramos
*/
Expand All @@ -31,18 +43,56 @@ public interface SpatialIndex {
NodeIterable getNodesInArea(Rect2D rect);

/**
* Returns the edges in the given area.
* Returns the nodes in the given area using a faster, but approximate method.
* <p>
* All nodes in the provided area are guaranteed to be returned, but some nodes
* outside the area may also be returned.
*
* @param rect area to query
* @return nodes in the area
*/
NodeIterable getApproximateNodesInArea(Rect2D rect);

/**
* Returns the edges in the given area. Edges may be returned twice.
*
* @param rect area to query
* @return edges in the area
*/
EdgeIterable getEdgesInArea(Rect2D rect);

/**
* Returns the edges in the given area using a faster, but approximate method.
* <p>
* All edges in the provided area are guaranteed to be returned, but some edges
* outside the area may also be returned. Edges may also be returned twice.
*
* @param rect area to query
* @return edges in the area
*/
EdgeIterable getApproximateEdgesInArea(Rect2D rect);

/**
* Returns the bounding rectangle that contains all nodes in the graph. The
* boundaries are calculated based on each node's position and size.
*
* @return the bounding rectangle, or null if there are no nodes
*/
Rect2D getBoundaries();

/**
* Acquires a read lock on the spatial index. This is recommended when using the
* query functions in a stream context, to avoid the spatial index being
* modified while being queried.
* <p>
* Every call to this method must be matched with a call to
* {@link #spatialIndexReadUnlock()}.
*/
void spatialIndexReadLock();

/**
* Releases a read lock on the spatial index. This must be called after a call
* to {@link #spatialIndexReadLock()}.
*/
void spatialIndexReadUnlock();
}
109 changes: 96 additions & 13 deletions src/main/java/org/gephi/graph/impl/EdgeStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -419,14 +419,18 @@ public EdgeInIterator edgeInIterator(final Node node) {
return new EdgeInIterator((NodeImpl) node);
}

public EdgeInOutIterator edgeIterator(final Node node) {
public EdgeInOutIterator edgeIterator(final Node node, boolean locking) {
checkValidNodeObject(node);
return new EdgeInOutIterator((NodeImpl) node);
return new EdgeInOutIterator((NodeImpl) node, locking);
}

public Iterator<Edge> edgeUndirectedIterator(final Node node) {
public EdgeInOutMultiIterator edgeIterator(final Iterator<NodeImpl> nodeIterator, boolean locking) {
return new EdgeInOutMultiIterator(nodeIterator, locking);
}

public Iterator<Edge> edgeUndirectedIterator(final Node node, boolean locking) {
checkValidNodeObject(node);
return undirectedIterator(new EdgeInOutIterator((NodeImpl) node));
return undirectedIterator(new EdgeInOutIterator((NodeImpl) node, locking));
}

public EdgeTypeOutIterator edgeOutIterator(final Node node, int type) {
Expand Down Expand Up @@ -471,7 +475,7 @@ public NeighborsIterator neighborInIterator(final Node node, int type) {

public NeighborsIterator neighborIterator(Node node) {
checkValidNodeObject(node);
return new NeighborsUndirectedIterator((NodeImpl) node, new EdgeInOutIterator((NodeImpl) node));
return new NeighborsUndirectedIterator((NodeImpl) node, new EdgeInOutIterator((NodeImpl) node, true));
}

public NeighborsIterator neighborIterator(final Node node, int type) {
Expand Down Expand Up @@ -1285,6 +1289,9 @@ private void incrementVersion() {
if (version != null) {
version.incrementAndGetEdgeVersion();
}
if (spatialIndex != null) {
spatialIndex.incrementVersion();
}
}

boolean isUndirectedToIgnore(EdgeImpl edge) {
Expand Down Expand Up @@ -1491,28 +1498,52 @@ public boolean hasNext() {
}
}

protected final class EdgeInOutIterator implements Iterator<Edge> {
/**
* Abstract base class for iterating over edges connected to nodes. Provides
* common logic for handling both incoming and outgoing edges.
*/
protected abstract class AbstractEdgeInOutIterator implements Iterator<Edge> {

protected final int outTypeLength;
protected final int inTypeLength;
protected final boolean locking;
protected int outTypeLength;
protected int inTypeLength;
protected EdgeImpl[] outArray;
protected EdgeImpl[] inArray;
protected int typeIndex = 0;
protected EdgeImpl pointer;
protected EdgeImpl lastEdge;
protected boolean out = true;

public EdgeInOutIterator(NodeImpl node) {
readLock();
protected AbstractEdgeInOutIterator(boolean locking) {
this.locking = locking;
if (locking) {
readLock();
}
}

/**
* Initialize arrays for the current node. Called when starting iteration for a
* new node.
*/
protected void initializeForNode(NodeImpl node) {
outArray = node.headOut;
outTypeLength = outArray.length;
inArray = node.headIn;
inTypeLength = inArray.length;
typeIndex = 0;
pointer = null;
out = true;
}

/**
* Called when the current node has no more edges. Should return true if there
* are more nodes to process, false otherwise.
*/
protected abstract boolean moveToNextNode();

@Override
public boolean hasNext() {
if (pointer == null) {
while (pointer == null) {
if (out) {
while (pointer == null && typeIndex < outTypeLength) {
pointer = outArray[typeIndex++];
Expand All @@ -1537,8 +1568,13 @@ public boolean hasNext() {
}

if (pointer == null) {
readUnlock();
return false;
// No more edges for current node, try next node
if (!moveToNextNode()) {
if (locking) {
readUnlock();
}
return false;
}
}
}
return true;
Expand Down Expand Up @@ -1579,6 +1615,53 @@ public void remove() {
}
}

/**
* Iterator for edges connected to a single node (both incoming and outgoing).
*/
protected final class EdgeInOutIterator extends AbstractEdgeInOutIterator {

public EdgeInOutIterator(NodeImpl node, boolean locking) {
super(locking);
initializeForNode(node);
}

@Override
protected boolean moveToNextNode() {
// Single node iterator - no more nodes to process
return false;
}
}

/**
* Iterator for edges connected to multiple nodes (both incoming and outgoing).
* Iterates through all edges of all provided nodes without creating separate
* iterators.
*/
protected final class EdgeInOutMultiIterator extends AbstractEdgeInOutIterator {

private final Iterator<NodeImpl> nodeIterator;

public EdgeInOutMultiIterator(Iterator<NodeImpl> nodeIterator, boolean locking) {
super(locking);
this.nodeIterator = nodeIterator;
// Initialize with first node if available
if (nodeIterator.hasNext()) {
NodeImpl node = nodeIterator.next();
checkValidNodeObject(node);
initializeForNode(node);
}
}

@Override
protected boolean moveToNextNode() {
if (nodeIterator.hasNext()) {
initializeForNode(nodeIterator.next());
return true;
}
return false;
}
}

protected final class EdgeOutIterator implements Iterator<Edge> {

protected final int typeLength;
Expand Down
10 changes: 6 additions & 4 deletions src/main/java/org/gephi/graph/impl/GraphStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,8 @@ public boolean removeNode(final Node node) {
autoWriteLock();
try {
nodeStore.checkNonNullNodeObject(node);
for (EdgeStore.EdgeInOutIterator edgeIterator = edgeStore.edgeIterator(node); edgeIterator.hasNext();) {
for (EdgeStore.EdgeInOutIterator edgeIterator = edgeStore.edgeIterator(node, false); edgeIterator
.hasNext();) {
edgeIterator.next();
edgeIterator.remove();
}
Expand All @@ -303,7 +304,8 @@ public boolean removeAllNodes(Collection<? extends Node> nodes) {
try {
for (Node node : nodes) {
nodeStore.checkNonNullNodeObject(node);
for (EdgeStore.EdgeInOutIterator edgeIterator = edgeStore.edgeIterator(node); edgeIterator.hasNext();) {
for (EdgeStore.EdgeInOutIterator edgeIterator = edgeStore.edgeIterator(node, false); edgeIterator
.hasNext();) {
edgeIterator.next();
edgeIterator.remove();
}
Expand Down Expand Up @@ -434,7 +436,7 @@ public NodeIterable getSuccessors(final Node node, final int type) {

@Override
public EdgeIterable getEdges(final Node node) {
return new EdgeIterableWrapper(() -> edgeStore.edgeIterator(node), getAutoLock());
return new EdgeIterableWrapper(() -> edgeStore.edgeIterator(node, true), getAutoLock());
}

@Override
Expand Down Expand Up @@ -575,7 +577,7 @@ public boolean isIncident(final Node node, final Edge edge) {
public void clearEdges(final Node node) {
autoWriteLock();
try {
EdgeStore.EdgeInOutIterator itr = edgeStore.edgeIterator(node);
EdgeStore.EdgeInOutIterator itr = edgeStore.edgeIterator(node, false);
for (; itr.hasNext();) {
itr.next();
itr.remove();
Expand Down
10 changes: 6 additions & 4 deletions src/main/java/org/gephi/graph/impl/GraphStoreConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ public final class GraphStoreConfiguration {
public static final int NODESTORE_DEFAULT_DICTIONARY_SIZE = 8192;
public final static float NODESTORE_DICTIONARY_LOAD_FACTOR = .7f;
// EdgeStore
public static final int EDGESTORE_BLOCK_SIZE = 8192;
public static final int EDGESTORE_DEFAULT_BLOCKS = 10;
public static final int EDGESTORE_BLOCK_SIZE = 32768;
public static final int EDGESTORE_DEFAULT_BLOCKS = 5;
public static final int EDGESTORE_DEFAULT_TYPE_COUNT = 1;
public static final int EDGESTORE_DEFAULT_DICTIONARY_SIZE = 1000;
public static final int EDGESTORE_DEFAULT_DICTIONARY_SIZE = 32768;
public static final float EDGESTORE_DICTIONARY_LOAD_FACTOR = .7f;
// GraphView
public static final int VIEW_DEFAULT_TYPE_COUNT = 1;
Expand Down Expand Up @@ -83,8 +83,10 @@ public final class GraphStoreConfiguration {
public static final TimeRepresentation DEFAULT_TIME_REPRESENTATION = TimeRepresentation.TIMESTAMP;
// Spatial index
public static final int SPATIAL_INDEX_MAX_LEVELS = 16;
public static final int SPATIAL_INDEX_MAX_OBJECTS_PER_NODE = 5000;
public static final int SPATIAL_INDEX_MAX_OBJECTS_PER_NODE = 8192;
public static final float SPATIAL_INDEX_DIMENSION_BOUNDARY = 1e6f;
public static final boolean SPATIAL_INDEX_APPROXIMATE_AREA_SEARCH = false;
public static final float SPATIAL_INDEX_LOCAL_ITERATOR_THRESHOLD = 0.3f;
// Miscellaneous
public static final double TIMESTAMP_STORE_GROWING_FACTOR = 1.1;
public static final double INTERVAL_STORE_GROWING_FACTOR = 1.1;
Expand Down
Loading