Skip to content

Commit 00f5a60

Browse files
committed
introduce EdgeTree writer and reader (#40810)
This commit introduces a new data-structure for reading and writing EdgeTrees that write/read serialized versions of the tree. This tree is the basis of Polygon trees that will contain representation of any holes in the more complex polygon
1 parent be412ca commit 00f5a60

File tree

4 files changed

+498
-0
lines changed

4 files changed

+498
-0
lines changed
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.elasticsearch.common.geo;
20+
21+
import org.apache.lucene.util.BytesRef;
22+
import org.elasticsearch.common.io.stream.ByteBufferStreamInput;
23+
24+
import java.io.IOException;
25+
import java.nio.ByteBuffer;
26+
27+
import static org.apache.lucene.geo.GeoUtils.lineCrossesLine;
28+
29+
public class EdgeTreeReader {
30+
final BytesRef bytesRef;
31+
32+
public EdgeTreeReader(BytesRef bytesRef) {
33+
this.bytesRef = bytesRef;
34+
}
35+
36+
/**
37+
* Returns true if the rectangle query and the edge tree's shape overlap
38+
*/
39+
public boolean containedInOrCrosses(int minX, int minY, int maxX, int maxY) throws IOException {
40+
return this.containsBottomLeft(minX, minY, maxX, maxY) || this.crosses(minX, minY, maxX, maxY);
41+
}
42+
43+
boolean containsBottomLeft(int minX, int minY, int maxX, int maxY) throws IOException {
44+
ByteBufferStreamInput input = new ByteBufferStreamInput(ByteBuffer.wrap(bytesRef.bytes, bytesRef.offset, bytesRef.length));
45+
int thisMinX = input.readInt();
46+
int thisMinY = input.readInt();
47+
int thisMaxX = input.readInt();
48+
int thisMaxY = input.readInt();
49+
50+
if (thisMinY > maxY || thisMaxX < minX || thisMaxY < minY || thisMinX > maxX) {
51+
return false; // tree and bbox-query are disjoint
52+
}
53+
54+
if (minX <= thisMinX && minY <= thisMinY && maxX >= thisMaxX && maxY >= thisMaxY) {
55+
return true; // bbox-query fully contains tree's extent.
56+
}
57+
58+
return containsBottomLeft(input, readRoot(input, input.position()), minX, minY, maxX, maxY);
59+
}
60+
61+
public boolean crosses(int minX, int minY, int maxX, int maxY) throws IOException {
62+
ByteBufferStreamInput input = new ByteBufferStreamInput(ByteBuffer.wrap(bytesRef.bytes, bytesRef.offset, bytesRef.length));
63+
int thisMinX = input.readInt();
64+
int thisMinY = input.readInt();
65+
int thisMaxX = input.readInt();
66+
int thisMaxY = input.readInt();
67+
68+
if (thisMinY > maxY || thisMaxX < minX || thisMaxY < minY || thisMinX > maxX) {
69+
return false; // tree and bbox-query are disjoint
70+
}
71+
72+
if (minX <= thisMinX && minY <= thisMinY && maxX >= thisMaxX && maxY >= thisMaxY) {
73+
return true; // bbox-query fully contains tree's extent.
74+
}
75+
76+
return crosses(input, readRoot(input, input.position()), minX, minY, maxX, maxY);
77+
}
78+
79+
public Edge readRoot(ByteBufferStreamInput input, int position) throws IOException {
80+
return readEdge(input, position);
81+
}
82+
83+
private static Edge readEdge(ByteBufferStreamInput input, int position) throws IOException {
84+
input.position(position);
85+
int minY = input.readInt();
86+
int maxY = input.readInt();
87+
int x1 = input.readInt();
88+
int y1 = input.readInt();
89+
int x2 = input.readInt();
90+
int y2 = input.readInt();
91+
int rightOffset = input.readInt();
92+
return new Edge(input.position(), x1, y1, x2, y2, minY, maxY, rightOffset);
93+
}
94+
95+
96+
Edge readLeft(ByteBufferStreamInput input, Edge root) throws IOException {
97+
return readEdge(input, root.streamOffset);
98+
}
99+
100+
Edge readRight(ByteBufferStreamInput input, Edge root) throws IOException {
101+
return readEdge(input, root.streamOffset + root.rightOffset);
102+
}
103+
104+
/**
105+
* Returns true if the bottom-left point of the rectangle query is contained within the
106+
* tree's edges.
107+
*/
108+
private boolean containsBottomLeft(ByteBufferStreamInput input, Edge root, int minX, int minY, int maxX, int maxY) throws IOException {
109+
boolean res = false;
110+
if (root.maxY >= minY) {
111+
// is bbox-query contained within linearRing
112+
// cast infinite ray to the right from bottom-left of bbox-query to see if it intersects edge
113+
if (lineCrossesLine(root.x1, root.y1, root.x2, root.y2,minX, minY, Integer.MAX_VALUE, minY)) {
114+
res = true;
115+
}
116+
117+
if (root.rightOffset > 0) { /* has left node */
118+
res ^= containsBottomLeft(input, readLeft(input, root), minX, minY, maxX, maxY);
119+
}
120+
121+
if (root.rightOffset > 0 && maxY >= root.minY) { /* no right node if rightOffset == -1 */
122+
res ^= containsBottomLeft(input, readRight(input, root), minX, minY, maxX, maxY);
123+
}
124+
}
125+
return res;
126+
}
127+
128+
/**
129+
* Returns true if the box crosses any edge in this edge subtree
130+
* */
131+
private boolean crosses(ByteBufferStreamInput input, Edge root, int minX, int minY, int maxX, int maxY) throws IOException {
132+
boolean res = false;
133+
// we just have to cross one edge to answer the question, so we descend the tree and return when we do.
134+
if (root.maxY >= minY) {
135+
136+
// does rectangle's edges intersect or reside inside polygon's edge
137+
if (lineCrossesLine(root.x1, root.y1, root.x2, root.y2, minX, minY, maxX, minY) ||
138+
lineCrossesLine(root.x1, root.y1, root.x2, root.y2, maxX, minY, maxX, maxY) ||
139+
lineCrossesLine(root.x1, root.y1, root.x2, root.y2, maxX, maxY, minX, maxY) ||
140+
lineCrossesLine(root.x1, root.y1, root.x2, root.y2, minX, maxY, minX, minY)) {
141+
return true;
142+
}
143+
144+
if (root.rightOffset > 0) { /* has left node */
145+
if (crosses(input, readLeft(input, root), minX, minY, maxX, maxY)) {
146+
return true;
147+
}
148+
}
149+
150+
if (root.rightOffset > 0 && maxY >= root.minY) { /* no right node if rightOffset == -1 */
151+
if (crosses(input, readRight(input, root), minX, minY, maxX, maxY)) {
152+
return true;
153+
}
154+
}
155+
}
156+
return false;
157+
}
158+
159+
160+
private static class Edge {
161+
int streamOffset;
162+
int x1;
163+
int y1;
164+
int x2;
165+
int y2;
166+
int minY;
167+
int maxY;
168+
int rightOffset;
169+
170+
/**
171+
* Object representing an edge node read from bytes
172+
*
173+
* @param streamOffset offset in byte-reference where edge terminates
174+
* @param x1 x-coordinate of first point in segment
175+
* @param y1 y-coordinate of first point in segment
176+
* @param x2 x-coordinate of second point in segment
177+
* @param y2 y-coordinate of second point in segment
178+
* @param minY minimum y-coordinate in this edge-node's tree
179+
* @param maxY maximum y-coordinate in this edge-node's tree
180+
* @param rightOffset the start offset in the byte-reference of the right edge-node
181+
*/
182+
Edge(int streamOffset, int x1, int y1, int x2, int y2, int minY, int maxY, int rightOffset) {
183+
this.streamOffset = streamOffset;
184+
this.x1 = x1;
185+
this.y1 = y1;
186+
this.x2 = x2;
187+
this.y2 = y2;
188+
this.minY = minY;
189+
this.maxY = maxY;
190+
this.rightOffset = rightOffset;
191+
}
192+
}
193+
}
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.elasticsearch.common.geo;
20+
21+
import org.apache.lucene.util.BytesRef;
22+
import org.elasticsearch.common.io.stream.BytesStreamOutput;
23+
import org.elasticsearch.common.io.stream.StreamOutput;
24+
25+
import java.io.IOException;
26+
import java.util.Arrays;
27+
28+
/**
29+
* Shape edge-tree writer for use in doc-values
30+
*/
31+
public class EdgeTreeWriter {
32+
33+
/**
34+
* | minY | maxY | x1 | y1 | x2 | y2 | right_offset |
35+
*/
36+
static final int EDGE_SIZE_IN_BYTES = 28;
37+
38+
int minX;
39+
int minY;
40+
int maxX;
41+
int maxY;
42+
final Edge tree;
43+
44+
public EdgeTreeWriter(int[] x, int[] y) {
45+
minX = minY = Integer.MAX_VALUE;
46+
maxX = maxY = Integer.MIN_VALUE;
47+
Edge edges[] = new Edge[y.length - 1];
48+
for (int i = 1; i < y.length; i++) {
49+
int y1 = y[i-1];
50+
int x1 = x[i-1];
51+
int y2 = y[i];
52+
int x2 = x[i];
53+
int minY, maxY;
54+
if (y1 < y2) {
55+
minY = y1;
56+
maxY = y2;
57+
} else {
58+
minY = y2;
59+
maxY = y1;
60+
}
61+
edges[i - 1] = new Edge(x1, y1, x2, y2, minY, maxY);
62+
this.minX = Math.min(this.minX, Math.min(x1, x2));
63+
this.minY = Math.min(this.minY, Math.min(y1, y2));
64+
this.maxX = Math.max(this.maxX, Math.max(x1, x2));
65+
this.maxY = Math.max(this.maxY, Math.max(y1, y2));
66+
}
67+
Arrays.sort(edges);
68+
this.tree = createTree(edges, 0, edges.length - 1);
69+
}
70+
71+
public BytesRef toBytesRef() throws IOException {
72+
BytesStreamOutput output = new BytesStreamOutput(4 * 4 + EDGE_SIZE_IN_BYTES * tree.size);
73+
// write extent of edges
74+
output.writeInt(minX);
75+
output.writeInt(minY);
76+
output.writeInt(maxX);
77+
output.writeInt(maxY);
78+
// write edge-tree itself
79+
writeTree(tree, output);
80+
output.close();
81+
return output.bytes().toBytesRef();
82+
}
83+
84+
private void writeTree(Edge edge, StreamOutput output) throws IOException {
85+
if (edge == null) {
86+
return;
87+
}
88+
output.writeInt(edge.minY);
89+
output.writeInt(edge.maxY);
90+
output.writeInt(edge.x1);
91+
output.writeInt(edge.y1);
92+
output.writeInt(edge.x2);
93+
output.writeInt(edge.y2);
94+
// left node is next node, write offset of right node
95+
if (edge.left != null) {
96+
output.writeInt(edge.left.size * EDGE_SIZE_IN_BYTES);
97+
} else if (edge.right == null){
98+
output.writeInt(-1);
99+
} else {
100+
output.writeInt(0);
101+
}
102+
writeTree(edge.left, output);
103+
writeTree(edge.right, output);
104+
}
105+
106+
private static Edge createTree(Edge edges[], int low, int high) {
107+
if (low > high) {
108+
return null;
109+
}
110+
// add midpoint
111+
int mid = (low + high) >>> 1;
112+
Edge newNode = edges[mid];
113+
newNode.size = 1;
114+
// add children
115+
newNode.left = createTree(edges, low, mid - 1);
116+
newNode.right = createTree(edges, mid + 1, high);
117+
// pull up max values to this node
118+
// and node count
119+
if (newNode.left != null) {
120+
newNode.maxY = Math.max(newNode.maxY, newNode.left.maxY);
121+
newNode.size += newNode.left.size;
122+
}
123+
if (newNode.right != null) {
124+
newNode.maxY = Math.max(newNode.maxY, newNode.right.maxY);
125+
newNode.size += newNode.right.size;
126+
}
127+
return newNode;
128+
}
129+
130+
/**
131+
* Object representing an in-memory edge-tree to be serialized
132+
*/
133+
static class Edge implements Comparable<Edge> {
134+
final int x1;
135+
final int y1;
136+
final int x2;
137+
final int y2;
138+
int minY;
139+
int maxY;
140+
int size;
141+
Edge left;
142+
Edge right;
143+
144+
Edge(int x1, int y1, int x2, int y2, int minY, int maxY) {
145+
this.x1 = x1;
146+
this.y1 = y1;
147+
this.x2 = x2;
148+
this.y2 = y2;
149+
this.minY = minY;
150+
this.maxY = maxY;
151+
}
152+
153+
@Override
154+
public int compareTo(Edge other) {
155+
int ret = Integer.compare(minY, other.minY);
156+
if (ret == 0) {
157+
ret = Integer.compare(maxY, other.maxY);
158+
}
159+
return ret;
160+
}
161+
}
162+
}

server/src/main/java/org/elasticsearch/common/io/stream/ByteBufferStreamInput.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,14 @@ public long readLong() throws IOException {
110110
}
111111
}
112112

113+
public void position(int newPosition) throws IOException {
114+
buffer.position(newPosition);
115+
}
116+
117+
public int position() throws IOException {
118+
return buffer.position();
119+
}
120+
113121
@Override
114122
public void reset() throws IOException {
115123
buffer.reset();

0 commit comments

Comments
 (0)