286 lines
7.9 KiB
Java
286 lines
7.9 KiB
Java
|
import java.util.Arrays;
|
||
|
import java.util.Random;
|
||
|
|
||
|
// https://en.wikipedia.org/wiki/R-tree
|
||
|
public class PointLocationRtree {
|
||
|
|
||
|
static Random random = new Random(1);
|
||
|
|
||
|
static class Polygon {
|
||
|
final int[] x;
|
||
|
final int[] y;
|
||
|
final int centerX;
|
||
|
final int centerY;
|
||
|
final int id;
|
||
|
|
||
|
public Polygon(int[] x, int[] y, int id) {
|
||
|
this.x = x;
|
||
|
this.y = y;
|
||
|
this.centerX = average(x);
|
||
|
this.centerY = average(y);
|
||
|
this.id = id;
|
||
|
}
|
||
|
|
||
|
static int average(int[] a) {
|
||
|
long res = 0;
|
||
|
for (int v : a)
|
||
|
res += v;
|
||
|
return (int) (res / a.length);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static class RTree {
|
||
|
final Polygon[] polygons;
|
||
|
final int[] minx, maxx, miny, maxy;
|
||
|
|
||
|
public RTree(Polygon[] polygons) {
|
||
|
int n = polygons.length;
|
||
|
this.polygons = polygons;
|
||
|
minx = new int[n];
|
||
|
maxx = new int[n];
|
||
|
miny = new int[n];
|
||
|
maxy = new int[n];
|
||
|
Arrays.fill(minx, Integer.MAX_VALUE);
|
||
|
Arrays.fill(maxx, Integer.MIN_VALUE);
|
||
|
Arrays.fill(miny, Integer.MAX_VALUE);
|
||
|
Arrays.fill(maxy, Integer.MIN_VALUE);
|
||
|
build(0, n, true);
|
||
|
}
|
||
|
|
||
|
void build(int low, int high, boolean divX) {
|
||
|
if (low >= high)
|
||
|
return;
|
||
|
int mid = (low + high) >>> 1;
|
||
|
nth_element(polygons, low, high, mid, divX);
|
||
|
|
||
|
for (int i = low; i < high; i++) {
|
||
|
for (int j = 0; j < polygons[i].x.length; j++) {
|
||
|
minx[mid] = Math.min(minx[mid], polygons[i].x[j]);
|
||
|
miny[mid] = Math.min(miny[mid], polygons[i].y[j]);
|
||
|
maxx[mid] = Math.max(maxx[mid], polygons[i].x[j]);
|
||
|
maxy[mid] = Math.max(maxy[mid], polygons[i].y[j]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
build(low, mid, !divX);
|
||
|
build(mid + 1, high, !divX);
|
||
|
}
|
||
|
|
||
|
// See: http://www.cplusplus.com/reference/algorithm/nth_element
|
||
|
static void nth_element(Polygon[] a, int low, int high, int n, boolean divX) {
|
||
|
while (true) {
|
||
|
int k = partition(a, low, high, low + random.nextInt(high - low), divX);
|
||
|
if (n < k)
|
||
|
high = k;
|
||
|
else if (n > k)
|
||
|
low = k + 1;
|
||
|
else
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int partition(Polygon[] a, int fromInclusive, int toExclusive, int separatorIndex, boolean divX) {
|
||
|
int i = fromInclusive;
|
||
|
int j = toExclusive - 1;
|
||
|
if (i >= j) return j;
|
||
|
double separator = divX ? a[separatorIndex].centerX : a[separatorIndex].centerY;
|
||
|
swap(a, i++, separatorIndex);
|
||
|
while (i <= j) {
|
||
|
while (i <= j && (divX ? a[i].centerX : a[i].centerY) < separator)
|
||
|
++i;
|
||
|
while (i <= j && (divX ? a[j].centerX : a[j].centerY) > separator)
|
||
|
--j;
|
||
|
if (i >= j)
|
||
|
break;
|
||
|
swap(a, i++, j--);
|
||
|
}
|
||
|
swap(a, j, fromInclusive);
|
||
|
return j;
|
||
|
}
|
||
|
|
||
|
static void swap(Polygon[] a, int i, int j) {
|
||
|
Polygon t = a[i];
|
||
|
a[i] = a[j];
|
||
|
a[j] = t;
|
||
|
}
|
||
|
|
||
|
int foundPolygon;
|
||
|
|
||
|
public int findEnclosingPolygon(int x, int y) {
|
||
|
foundPolygon = -1;
|
||
|
findEnclosingPolygon(0, polygons.length, x, y, true);
|
||
|
return foundPolygon;
|
||
|
}
|
||
|
|
||
|
void findEnclosingPolygon(int low, int high, int x, int y, boolean divX) {
|
||
|
if (low >= high)
|
||
|
return;
|
||
|
int mid = (low + high) >>> 1;
|
||
|
if (x < minx[mid] || x > maxx[mid] || y < miny[mid] || y > maxy[mid])
|
||
|
return;
|
||
|
int location = pointInPolygon(x, y, polygons[mid].x, polygons[mid].y);
|
||
|
if (location != -1) {
|
||
|
foundPolygon = polygons[mid].id;
|
||
|
return;
|
||
|
}
|
||
|
findEnclosingPolygon(low, mid, x, y, !divX);
|
||
|
findEnclosingPolygon(mid + 1, high, x, y, !divX);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static int[] locatePoints(int[][] polygonX, int[][] polygonY, int[] qx, int[] qy) {
|
||
|
int n = polygonX.length;
|
||
|
Polygon[] polygons = new Polygon[n];
|
||
|
for (int i = 0; i < n; i++) {
|
||
|
polygons[i] = new Polygon(polygonX[i], polygonY[i], i);
|
||
|
}
|
||
|
RTree rTree = new RTree(polygons);
|
||
|
int[] res = new int[qx.length];
|
||
|
for (int i = 0; i < qx.length; i++) {
|
||
|
res[i] = rTree.findEnclosingPolygon(qx[i], qy[i]);
|
||
|
}
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
// random test
|
||
|
public static void main(String[] args) {
|
||
|
for (int step = 0; step < 10_000; step++) {
|
||
|
int range = 10;
|
||
|
int R = 3;
|
||
|
int C = 3;
|
||
|
int[][] polygonX = new int[R * C][];
|
||
|
int[][] polygonY = new int[R * C][];
|
||
|
for (int r = 0; r < R; r++) {
|
||
|
for (int c = 0; c < C; c++) {
|
||
|
int n = random.nextInt(10) + 3;
|
||
|
int[][] xy = getRandomPolygon(n, range, range);
|
||
|
polygonX[r * C + c] = xy[0];
|
||
|
polygonY[r * C + c] = xy[1];
|
||
|
for (int i = 0; i < n; i++) {
|
||
|
polygonX[r * C + c][i] += c * (range + 1);
|
||
|
polygonY[r * C + c][i] += r * (range + 1);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
int queries = random.nextInt(10) + 1;
|
||
|
queries = 1;
|
||
|
int[] qx = new int[queries];
|
||
|
int[] qy = new int[queries];
|
||
|
int[] res1 = new int[queries];
|
||
|
Arrays.fill(res1, -1);
|
||
|
for (int i = 0; i < queries; i++) {
|
||
|
qx[i] = random.nextInt((C + 2) * (range + 1)) - range;
|
||
|
qy[i] = random.nextInt((R + 2) * (range + 1)) - range;
|
||
|
for (int j = 0; j < polygonX.length; j++) {
|
||
|
int v = pointInPolygon(qx[i], qy[i], polygonX[j], polygonY[j]);
|
||
|
if (v >= 0) {
|
||
|
res1[i] = j;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
int[] res2 = locatePoints(polygonX, polygonY, qx, qy);
|
||
|
if (!Arrays.equals(res1, res2))
|
||
|
throw new RuntimeException();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int pointInPolygon(int qx, int qy, int[] x, int[] y) {
|
||
|
int n = x.length;
|
||
|
int cnt = 0;
|
||
|
for (int i = 0, j = n - 1; i < n; j = i++) {
|
||
|
if (y[i] == qy && (x[i] == qx || y[j] == qy && (x[i] <= qx || x[j] <= qx) && (x[i] >= qx || x[j] >= qx)))
|
||
|
return 0; // boundary
|
||
|
if ((y[i] > qy) != (y[j] > qy)) {
|
||
|
long det = (long) (x[i] - qx) * (y[j] - qy) - (long) (x[j] - qx) * (y[i] - qy);
|
||
|
if (det == 0)
|
||
|
return 0; // boundary
|
||
|
if ((det > 0) != (y[j] - y[i] > 0))
|
||
|
++cnt;
|
||
|
}
|
||
|
}
|
||
|
return cnt % 2 == 0 ? -1 /* exterior */ : 1 /* interior */;
|
||
|
}
|
||
|
|
||
|
static int[][] getRandomPolygon(int n, int maxWidth, int maxHeight) {
|
||
|
int[] x = new int[n];
|
||
|
int[] y = new int[n];
|
||
|
int[] p = new int[n];
|
||
|
while (true) {
|
||
|
for (int i = 0; i < n; i++) {
|
||
|
x[i] = random.nextInt(maxWidth);
|
||
|
y[i] = random.nextInt(maxHeight);
|
||
|
p[i] = i;
|
||
|
}
|
||
|
for (boolean improved = true; improved; ) {
|
||
|
improved = false;
|
||
|
for (int i = 0; i < n; i++) {
|
||
|
for (int j = 0; j < n; j++) {
|
||
|
int[] p1 = p.clone();
|
||
|
reverse(p1, i, j);
|
||
|
if (len(x, y, p) > len(x, y, p1)) {
|
||
|
p = p1;
|
||
|
improved = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
int[] tx = x.clone();
|
||
|
int[] ty = y.clone();
|
||
|
for (int i = 0; i < n; i++) {
|
||
|
x[i] = tx[p[i]];
|
||
|
y[i] = ty[p[i]];
|
||
|
}
|
||
|
boolean ok = true;
|
||
|
for (int i = 0; i < n; i++) {
|
||
|
long x1 = x[(i - 1 + n) % n] - x[i];
|
||
|
long y1 = y[(i - 1 + n) % n] - y[i];
|
||
|
long x2 = x[(i + 1) % n] - x[i];
|
||
|
long y2 = y[(i + 1) % n] - y[i];
|
||
|
ok &= x1 * y2 - x2 * y1 != 0 || x1 * x2 + y1 * y2 <= 0;
|
||
|
}
|
||
|
for (int i2 = 0, i1 = p.length - 1; i2 < p.length; i1 = i2++)
|
||
|
for (int j2 = 0, j1 = p.length - 1; j2 < p.length; j1 = j2++)
|
||
|
ok &= i1 == j1 || i1 == j2 || i2 == j1
|
||
|
|| !isCrossOrTouchIntersect(x[i1], y[i1], x[i2], y[i2], x[j1], y[j1], x[j2], y[j2]);
|
||
|
if (ok)
|
||
|
return new int[][]{x, y};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// http://en.wikipedia.org/wiki/2-opt
|
||
|
static void reverse(int[] p, int i, int j) {
|
||
|
int n = p.length;
|
||
|
// reverse order from i to j
|
||
|
while (i != j) {
|
||
|
int t = p[j];
|
||
|
p[j] = p[i];
|
||
|
p[i] = t;
|
||
|
i = (i + 1) % n;
|
||
|
if (i == j) break;
|
||
|
j = (j - 1 + n) % n;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static double len(int[] x, int[] y, int[] p) {
|
||
|
double res = 0;
|
||
|
for (int i = 0, j = p.length - 1; i < p.length; j = i++) {
|
||
|
double dx = x[p[i]] - x[p[j]];
|
||
|
double dy = y[p[i]] - y[p[j]];
|
||
|
res += Math.sqrt(dx * dx + dy * dy);
|
||
|
}
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
static boolean isCrossOrTouchIntersect(long x1, long y1, long x2, long y2, long x3, long y3, long x4, long y4) {
|
||
|
if (Math.max(x1, x2) < Math.min(x3, x4) || Math.max(x3, x4) < Math.min(x1, x2)
|
||
|
|| Math.max(y1, y2) < Math.min(y3, y4) || Math.max(y3, y4) < Math.min(y1, y2))
|
||
|
return false;
|
||
|
long z1 = (x2 - x1) * (y3 - y1) - (y2 - y1) * (x3 - x1);
|
||
|
long z2 = (x2 - x1) * (y4 - y1) - (y2 - y1) * (x4 - x1);
|
||
|
long z3 = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
|
||
|
long z4 = (x4 - x3) * (y2 - y3) - (y4 - y3) * (x2 - x3);
|
||
|
return (z1 <= 0 || z2 <= 0) && (z1 >= 0 || z2 >= 0) && (z3 <= 0 || z4 <= 0) && (z3 >= 0 || z4 >= 0);
|
||
|
}
|
||
|
}
|