Skip to content

Commit c318d2e

Browse files
committed
v1.2
完成最邻近搜索功能
1 parent a0afc8c commit c318d2e

File tree

8 files changed

+375
-6
lines changed

8 files changed

+375
-6
lines changed

README.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Node node = tx.createNode(testLabel);//新建节点
1616
Point geo = wkbReader.read("POINT(10 20)");
1717
byte[] wkb = wkbWriter.write(geo);//转为wkb
1818
node.setProperty("geometry", wkb);//设置空间字段值,必须为wkb格式
19-
rTreeIndex.add(node,tx);//加入索引
19+
rTreeIndex.add(node,tx);//加入索引(效率起见,多个node的话用list add,详见测试用例)
2020

2121
~~~
2222

@@ -43,9 +43,21 @@ try (Transaction tx = db.beginTx()) {
4343

4444
最邻近搜索
4545
~~~java
46-
施工中...
46+
//查询距离点(10.2, 13.2)最近的5个node
47+
try (Transaction tx = db.beginTx()) {
48+
List<DistanceResult> res = RtreeNearestQuery.queryNearestN(tx, rTreeIndex, 10.2, 13.2, 5, (node, geometry) -> true);
49+
System.out.println(res);
50+
}
4751

4852
~~~
53+
~~~java
54+
//查询满足约束条件且距离点(10.2, 13.2)最近的5个node
55+
try (Transaction tx = db.beginTx()) {
56+
List<DistanceResult> res = RtreeNearestQuery.queryNearestN(tx, rTreeIndex, 10.2, 13.2, 5, (node, geometry) -> geometry.getCoordinate().x<10);
57+
System.out.println(res);//DistanceResult里包含了node、距离以及geometry,详见测试用例
58+
}
59+
~~~
60+
4961

5062

5163
## install
@@ -56,7 +68,7 @@ maven import in your project
5668
<dependency>
5769
<groupId>org.wowtools</groupId>
5870
<artifactId>neo4j-rtree</artifactId>
59-
<version>1.1</version>
71+
<version>1.2</version>
6072
</dependency>
6173
```
6274
注意,maven中央库的依赖用jdk11编译,所以如果你的项目使用了jdk8,你需要自己编译一份适合于你的jdk的:

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
</properties>
1313
<groupId>org.wowtools</groupId>
1414
<artifactId>neo4j-rtree</artifactId>
15-
<version>1.1</version>
15+
<version>1.2</version>
1616
<name>neo4j-rtree</name>
1717

1818
<description>a spatial index for neo4j 4.x.</description>

src/main/java/org/wowtools/neo4j/rtree/RtreeNearestQuery.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
package org.wowtools.neo4j.rtree;
22

3+
import org.locationtech.jts.geom.Geometry;
34
import org.neo4j.graphdb.Node;
45
import org.neo4j.graphdb.Transaction;
6+
import org.wowtools.neo4j.rtree.nearest.DistanceResult;
7+
import org.wowtools.neo4j.rtree.nearest.NearestNeighbour;
58
import org.wowtools.neo4j.rtree.spatial.RTreeIndex;
69

10+
import java.util.List;
11+
12+
import static org.bouncycastle.asn1.x500.style.RFC4519Style.dc;
13+
714
/**
815
* @author liuyu
916
* @date 2020/6/10
@@ -21,7 +28,7 @@ public interface NodeFilter {
2128
* @param node 节点
2229
* @return 返回false则忽略此节点
2330
*/
24-
boolean accept(Node node);
31+
boolean accept(Node node, Geometry geometry);
2532
}
2633

2734
/**
@@ -35,6 +42,10 @@ public interface NodeFilter {
3542
* @param n 最大返回node数
3643
* @param nodeFilter 节点过滤器
3744
*/
38-
public static void queryNearestN(Transaction tx, RTreeIndex rTreeIndex, double x, double y, int n, NodeFilter nodeFilter) {
45+
public static List<DistanceResult> queryNearestN(Transaction tx, RTreeIndex rTreeIndex, double x, double y, int n, NodeFilter nodeFilter) {
46+
Node rtreeNode = rTreeIndex.getIndexRoot(tx);
47+
NearestNeighbour nn =
48+
new NearestNeighbour(nodeFilter, n, rtreeNode, x, y, rTreeIndex.getGeometryFieldName());
49+
return nn.find();
3950
}
4051
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package org.wowtools.neo4j.rtree.nearest;
2+
3+
import org.locationtech.jts.geom.Geometry;
4+
import org.neo4j.graphdb.Node;
5+
6+
/**
7+
* 距离查询结果
8+
*
9+
* @author liuyu
10+
* @date 2020/6/12
11+
*/
12+
public class DistanceResult {
13+
private final Node t;
14+
private final double dist;
15+
private final Geometry geometry;
16+
17+
public DistanceResult(Node t, double dist, Geometry geometry) {
18+
this.t = t;
19+
this.dist = dist;
20+
this.geometry = geometry;
21+
}
22+
23+
public Node getNode() {
24+
return t;
25+
}
26+
27+
public double getDist() {
28+
return dist;
29+
}
30+
31+
public Geometry getGeometry() {
32+
return geometry;
33+
}
34+
35+
@Override
36+
public String toString() {
37+
return "DistanceResult{" +
38+
"node=" + t.getId() +
39+
", dist=" + dist +
40+
", geometry=" + geometry.toText() +
41+
'}';
42+
}
43+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package org.wowtools.neo4j.rtree.nearest;
2+
3+
/**
4+
* Class that can calculate the MINDIST between a point and a rectangle
5+
*/
6+
public class MinDist {
7+
/**
8+
* Do not instantiate
9+
*/
10+
private MinDist() {
11+
// empty
12+
}
13+
14+
/**
15+
* 距离的平方
16+
* Calculate the MINDIST between the given MBRND and the given point
17+
*
18+
* @param bbox the bounding box to use
19+
* @param x the point x
20+
* @param y the point y
21+
* @return the squared distance
22+
*/
23+
public static double get(double[] bbox, double x, double y) {
24+
double res =
25+
dd(x, bbox[0], bbox[2]) +
26+
dd(y, bbox[1], bbox[3]);
27+
return res;
28+
}
29+
30+
/**
31+
* 某个维度上距离的平方
32+
*
33+
* @param o
34+
* @param min
35+
* @param max
36+
* @return
37+
*/
38+
private static double dd(double o, double min, double max) {
39+
double rv = r(o, min, max);
40+
double dr = o - rv;
41+
return dr * dr;
42+
}
43+
44+
private static double r(double x, double min, double max) {
45+
double r = x;
46+
if (x < min)
47+
r = min;
48+
if (x > max)
49+
r = max;
50+
return r;
51+
}
52+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package org.wowtools.neo4j.rtree.nearest;
2+
3+
import org.neo4j.graphdb.Node;
4+
import org.wowtools.neo4j.rtree.Constant;
5+
6+
import java.util.Comparator;
7+
8+
/**
9+
* A comparator that uses the MINDIST metrics to sort Nodes
10+
*
11+
* @author liuyu
12+
* @date 2020/6/12
13+
*/
14+
public class MinDistComparator implements Comparator<Node> {
15+
public final double x;
16+
public final double y;
17+
18+
public MinDistComparator(double x, double y) {
19+
this.x = x;
20+
this.y = y;
21+
}
22+
23+
24+
@Override
25+
public int compare(Node n1, Node n2) {
26+
double[] bbox1 = (double[]) n1.getProperty(Constant.RtreeProperty.bbox);
27+
double[] bbox2 = (double[]) n2.getProperty(Constant.RtreeProperty.bbox);
28+
return Double.compare(MinDist.get(bbox1, x, y),
29+
MinDist.get(bbox2, x, y));
30+
}
31+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package org.wowtools.neo4j.rtree.nearest;
2+
3+
/**
4+
* @author liuyu
5+
* @date 2020/6/12
6+
*/
7+
8+
import org.locationtech.jts.geom.Coordinate;
9+
import org.locationtech.jts.geom.Geometry;
10+
import org.locationtech.jts.geom.GeometryFactory;
11+
import org.locationtech.jts.geom.Point;
12+
import org.locationtech.jts.io.ParseException;
13+
import org.locationtech.jts.io.WKBReader;
14+
import org.neo4j.graphdb.Direction;
15+
import org.neo4j.graphdb.Node;
16+
import org.neo4j.graphdb.Relationship;
17+
import org.wowtools.neo4j.rtree.Constant;
18+
import org.wowtools.neo4j.rtree.RtreeNearestQuery;
19+
20+
import java.util.*;
21+
22+
public class NearestNeighbour {
23+
24+
private final RtreeNearestQuery.NodeFilter filter;
25+
private final int maxHits;
26+
private final Node root;
27+
private final double x;
28+
private final double y;
29+
private final Point point;
30+
private final String getGeometryFieldName;
31+
private final WKBReader wkbReader = new WKBReader();
32+
33+
public NearestNeighbour(RtreeNearestQuery.NodeFilter filter, int maxHits, Node root, double x, double y, String getGeometryFieldName) {
34+
this.filter = filter;
35+
this.maxHits = maxHits;
36+
this.root = root;
37+
this.x = x;
38+
this.y = y;
39+
this.getGeometryFieldName = getGeometryFieldName;
40+
point = new GeometryFactory().createPoint(new Coordinate(x, y));
41+
}
42+
43+
/**
44+
* @return the nearest neighbour
45+
*/
46+
public List<DistanceResult> find() {
47+
List<DistanceResult> ret =
48+
new ArrayList<>(maxHits);
49+
MinDistComparator nc =
50+
new MinDistComparator(x, y);
51+
PriorityQueue<Node> queue = new PriorityQueue<Node>(20, nc);
52+
queue.add(root);
53+
while (!queue.isEmpty()) {
54+
Node n = queue.remove();
55+
if (n.hasRelationship(Direction.OUTGOING, Constant.Relationship.RTREE_CHILD)) {
56+
nnExpandInternal(n, ret, maxHits, queue, nc);
57+
} else if (n.hasRelationship(Direction.OUTGOING, Constant.Relationship.RTREE_REFERENCE)) {
58+
nnExpandLeaf(n, filter, ret, maxHits);
59+
}
60+
}
61+
return ret;
62+
}
63+
64+
65+
/**
66+
* 访问索引上的非叶子节点
67+
* @param node
68+
* @param drs
69+
* @param maxHits
70+
* @param queue
71+
* @param mdc
72+
*/
73+
private void nnExpandInternal(Node node,
74+
List<DistanceResult> drs,
75+
int maxHits,
76+
PriorityQueue<Node> queue,
77+
MinDistComparator mdc) {
78+
double[] bbox = (double[]) node.getProperty(Constant.RtreeProperty.bbox);
79+
for (Relationship relationship : node.getRelationships(Direction.OUTGOING, Constant.Relationship.RTREE_CHILD)) {
80+
Node n = relationship.getEndNode();
81+
double minDist = MinDist.get(bbox, x, y);
82+
int t = drs.size();
83+
// drs is sorted so we can check only the last entry
84+
if (t < maxHits || minDist <= drs.get(t - 1).getDist())
85+
queue.add(n);
86+
}
87+
}
88+
89+
/**
90+
* 访问索引上的叶子节点
91+
* @param node
92+
* @param filter
93+
* @param drs
94+
* @param maxHits
95+
*/
96+
private void nnExpandLeaf(
97+
Node node,
98+
RtreeNearestQuery.NodeFilter filter,
99+
List<DistanceResult> drs,
100+
int maxHits) {
101+
for (Relationship relationship : node.getRelationships(Direction.OUTGOING, Constant.Relationship.RTREE_REFERENCE)) {
102+
Node objNode = relationship.getEndNode();
103+
Geometry geometry;
104+
byte[] wkb = (byte[]) objNode.getProperty(getGeometryFieldName);
105+
try {
106+
geometry = wkbReader.read(wkb);
107+
} catch (ParseException e) {
108+
throw new RuntimeException("parse wkb error", e);
109+
}
110+
if (filter.accept(objNode,geometry)) {
111+
double dist = geometry.distance(point);
112+
int n = drs.size();
113+
if (n < maxHits || dist < drs.get(n - 1).getDist()) {
114+
add(drs, new DistanceResult(objNode, dist, geometry), maxHits);
115+
}
116+
}
117+
}
118+
}
119+
120+
private void add(List<DistanceResult> drs,
121+
DistanceResult dr,
122+
int maxHits) {
123+
int n = drs.size();
124+
if (n == maxHits)
125+
drs.remove(n - 1);
126+
int pos = Collections.binarySearch(drs, dr, comp);
127+
if (pos < 0) {
128+
// binarySearch return -(pos + 1) for new entries
129+
pos = -(pos + 1);
130+
}
131+
drs.add(pos, dr);
132+
}
133+
134+
private static final Comparator<DistanceResult> comp =
135+
new Comparator<DistanceResult>() {
136+
public int compare(DistanceResult d1, DistanceResult d2) {
137+
return Double.compare(d1.getDist(), d2.getDist());
138+
}
139+
};
140+
141+
}

0 commit comments

Comments
 (0)