diff --git a/src/main/java/org/tinspin/index/Index.java b/src/main/java/org/tinspin/index/Index.java
index 01cba75..5bed4d7 100644
--- a/src/main/java/org/tinspin/index/Index.java
+++ b/src/main/java/org/tinspin/index/Index.java
@@ -41,6 +41,7 @@ public interface Index {
/**
* @return Collect and return some index statistics. Note that indexes are not required
* to fill all fields. Also, individual indexes may use subclasses with additional fields.
+ * Many indexes also perform consistency checks while gathering stats.
*/
Stats getStats();
diff --git a/src/main/java/org/tinspin/index/IndexConfig.java b/src/main/java/org/tinspin/index/IndexConfig.java
index e5d9303..ce9970e 100644
--- a/src/main/java/org/tinspin/index/IndexConfig.java
+++ b/src/main/java/org/tinspin/index/IndexConfig.java
@@ -25,7 +25,7 @@ protected IndexConfig(int dimensions) {
this.dimensions = dimensions;
}
- public IndexConfig create(int dimensions) {
+ public static IndexConfig create(int dimensions) {
return new IndexConfig(dimensions);
}
@@ -33,22 +33,26 @@ public IndexConfig create(int dimensions) {
/**
* Number of dimensions.
* @param dimensions Number of dimensions of keys.
+ * @return this
*/
- public void setDimensions(int dimensions) {
+ public IndexConfig setDimensions(int dimensions) {
this.dimensions = dimensions;
+ return this;
}
/**
* @param defensiveKeyCopy
- * Defensive keys copying. If `false`, the kd-tree will store the passed in
+ * Defensive keys copying. If 'false', the kd-tree will store the passed in
* double[] keys internally (this reduces required memory).
- * If `true`, the keys are copied in order to avoid accidental modification.
- * The latter obviously requires more memory.
+ * If 'true', the keys are copied in order to avoid accidental modification.
+ * The latter obviously requires more memory. Default is 'true'.
*
* This setting works only for kd-trees.
+ * @return this
*/
- public void setDefensiveKeyCopy(boolean defensiveKeyCopy) {
+ public IndexConfig setDefensiveKeyCopy(boolean defensiveKeyCopy) {
this.defensiveKeyCopy = defensiveKeyCopy;
+ return this;
}
diff --git a/src/main/java/org/tinspin/index/Stats.java b/src/main/java/org/tinspin/index/Stats.java
index 5270d51..312739b 100644
--- a/src/main/java/org/tinspin/index/Stats.java
+++ b/src/main/java/org/tinspin/index/Stats.java
@@ -27,6 +27,7 @@ public class Stats {
public int minLevel = Integer.MAX_VALUE;
public int maxLevel = -1;
public int maxDepth = 0;
+ public int maxValuesInNode = 0;
public double sumLevel;
public int maxNodeSize = -1;
public int nLeaf;
@@ -49,7 +50,9 @@ public String toString() {
";nNodes=" + nNodes +
";nLeaf=" + nLeaf +
";nInner=" + nInner +
- ";minLevel=" + minLevel +
+ ";maxDepth=" + maxDepth +
+ ";maxValues=" + maxValuesInNode +
+ ";minLevel=" + minLevel +
";maxLevel=" + maxLevel +
";avgLevel=" + (sumLevel/nEntries) +
";maxNodeSize=" + maxNodeSize;
diff --git a/src/main/java/org/tinspin/index/array/RectArray.java b/src/main/java/org/tinspin/index/array/RectArray.java
index 1a511ab..7c62fa5 100644
--- a/src/main/java/org/tinspin/index/array/RectArray.java
+++ b/src/main/java/org/tinspin/index/array/RectArray.java
@@ -238,6 +238,9 @@ private ArrayList> knnQuery(double[] center, int k) {
for (int i = 0; i < phc.length/2; i++) {
double[] min = phc[i*2];
double[] max = phc[i*2+1];
+ if (min == null) {
+ continue; // Entry has been removed
+ }
double dist = distREdge(center, min, max);
if (ret.size() < k) {
ret.add(new BoxEntryKnn<>(min, max, values[i].value(), dist));
diff --git a/src/main/java/org/tinspin/index/qthypercube/QNode.java b/src/main/java/org/tinspin/index/qthypercube/QNode.java
index b2e8d39..393e3ad 100644
--- a/src/main/java/org/tinspin/index/qthypercube/QNode.java
+++ b/src/main/java/org/tinspin/index/qthypercube/QNode.java
@@ -303,6 +303,7 @@ void checkNode(QStats s, QNode parent, int depth) {
s.nLeaf++;
s.nEntries += values.size();
s.histoValues[values.size()]++;
+ s.maxValuesInNode = Math.max(s.maxValuesInNode, values.size());
for (int i = 0; i < values.size(); i++) {
PointEntry e = values.get(i);
if (!QUtil.fitsIntoNode(e.point(), center, radius*QUtil.EPS_MUL)) {
diff --git a/src/main/java/org/tinspin/index/qthypercube/QRNode.java b/src/main/java/org/tinspin/index/qthypercube/QRNode.java
index 9a297a6..d07cda3 100644
--- a/src/main/java/org/tinspin/index/qthypercube/QRNode.java
+++ b/src/main/java/org/tinspin/index/qthypercube/QRNode.java
@@ -350,6 +350,7 @@ void checkNode(QStats s, QRNode parent, int depth) {
}
}
if (values != null) {
+ s.maxValuesInNode = Math.max(s.maxValuesInNode, values.size());
for (int i = 0; i < values.size(); i++) {
BoxEntry e = values.get(i);
if (!QUtil.fitsIntoNode(e.min(), e.max(), center, radius*QUtil.EPS_MUL)) {
diff --git a/src/main/java/org/tinspin/index/qthypercube/QuadTreeKD.java b/src/main/java/org/tinspin/index/qthypercube/QuadTreeKD.java
index 575ead4..cbe1993 100644
--- a/src/main/java/org/tinspin/index/qthypercube/QuadTreeKD.java
+++ b/src/main/java/org/tinspin/index/qthypercube/QuadTreeKD.java
@@ -407,7 +407,7 @@ public QStats getStats() {
* Statistics container class.
*/
public static class QStats extends Stats {
- final int[] histoValues = new int[100];
+ final int[] histoValues = new int[1000];
final int[] histoSubs;
public QStats(int dims) {
diff --git a/src/main/java/org/tinspin/index/qthypercube2/QIteratorKnn.java b/src/main/java/org/tinspin/index/qthypercube2/QIteratorKnn.java
index fa321e8..89c2107 100644
--- a/src/main/java/org/tinspin/index/qthypercube2/QIteratorKnn.java
+++ b/src/main/java/org/tinspin/index/qthypercube2/QIteratorKnn.java
@@ -112,8 +112,8 @@ private void findNextElement() {
}
if (node.isLeaf()) {
- for (PointEntry entry : node.getValues()) {
- processEntry(entry);
+ for (int i = 0; i < node.getValueCount(); i++) {
+ processEntry(node.getValues()[i]);
}
} else {
for (Object o : node.getEntries()) {
diff --git a/src/main/java/org/tinspin/index/qthypercube2/QNode.java b/src/main/java/org/tinspin/index/qthypercube2/QNode.java
index 4817749..53150ea 100644
--- a/src/main/java/org/tinspin/index/qthypercube2/QNode.java
+++ b/src/main/java/org/tinspin/index/qthypercube2/QNode.java
@@ -26,7 +26,14 @@
/**
* Node class for the quadtree.
- *
+ *
+ * A node can be in one of two modes: "directory node" and "leaf node".
+ * Directory nodes have an 2^dim array of "subs", where ich entry can be one of: a point, a subnode, or null.
+ * Leaf nodes have an array of "values" which are all points.
+ *
+ * A new subnode is inserted in "subs" if a slot in "subs" already contains a point.
+ *
+ *
* @author ztilmann
*
* @param Value type.
@@ -127,6 +134,7 @@ private void removeValue(int pos) {
if (pos < --nValues) {
System.arraycopy(getValues(), pos+1, getValues(), pos, nValues-pos);
}
+ getValues()[nValues] = null;
} else {
nValues--;
subs[pos] = null;
@@ -202,7 +210,11 @@ PointEntry remove(QNode parent, double[] key, int maxNodeSize, Predicate)o).remove(this, key, maxNodeSize, pred);
+ PointEntry removed = ((QNode)o).remove(this, key, maxNodeSize, pred);
+ if (removed != null) {
+ checkAndMergeLeafNodesInParent(parent, maxNodeSize);
+ }
+ return removed;
} else if (o instanceof PointEntry) {
PointEntry e = (PointEntry) o;
if (removeSub(parent, key, pos, e, maxNodeSize, pred)) {
@@ -227,9 +239,7 @@ private boolean removeSub(
removeValue(pos);
//TODO provide threshold for re-insert
// i.e. do not always merge.
- if (parent != null) {
- parent.checkAndMergeLeafNodes(maxNodeSize);
- }
+ checkAndMergeLeafNodesInParent(parent, maxNodeSize);
return true;
}
return false;
@@ -272,13 +282,11 @@ PointEntry update(QNode parent, double[] keyOld, double[] keyNew, int maxN
requiresReinsert[0] = false;
} else {
requiresReinsert[0] = true;
- if (parent != null) {
- parent.checkAndMergeLeafNodes(maxNodeSize);
- }
+ checkAndMergeLeafNodesInParent(parent, maxNodeSize);
}
return qe;
}
- throw new IllegalStateException();
+ return null;
}
for (int i = 0; i < nValues; i++) {
@@ -303,16 +311,37 @@ private void updateSub(double[] keyNew, PointEntry e, QNode parent, int ma
requiresReinsert[0] = true;
//TODO provide threshold for re-insert
//i.e. do not always merge.
- if (parent != null) {
- parent.checkAndMergeLeafNodes(maxNodeSize);
+ checkAndMergeLeafNodesInParent(parent, maxNodeSize);
+ }
+ }
+
+ private boolean checkMergeSingleLeaf(QNode parent) {
+ // Merge single value into parent if possible
+ if (!isLeaf() || parent == null || nValues > 1) {
+ return false;
+ }
+ for (int i = 0; i < parent.subs.length; i++) {
+ if (parent.subs[i] == this) {
+ parent.subs[i] = values[0];
+ parent.nValues++;
+ nValues = 0;
+ return true;
}
}
+ throw new IllegalStateException();
+ }
+
+ @SuppressWarnings("unchecked")
+ private void checkAndMergeLeafNodesInParent(QNode parent, int maxNodeSize) {
+ if (!checkMergeSingleLeaf(parent) && parent != null) {
+ parent.checkAndMergeLeafNodes(maxNodeSize);
+ }
}
@SuppressWarnings("unchecked")
private void checkAndMergeLeafNodes(int maxNodeSize) {
//check: We start with including all local values: nValues
- int nTotal = nValues;
+ int nTotal = 0;
for (int i = 0; i < subs.length; i++) {
Object e = subs[i];
if (e instanceof QNode) {
@@ -323,14 +352,16 @@ private void checkAndMergeLeafNodes(int maxNodeSize) {
return;
}
nTotal += sub.getValueCount();
- if (nTotal > maxNodeSize) {
- //too many children
- return;
- }
+ } else if (e instanceof PointEntry) {
+ nTotal++;
}
}
-
- //okay, let's merge
+ if (nTotal > maxNodeSize) {
+ //too many children
+ return;
+ }
+
+ //okay, let's merge.
values = new PointEntry[nTotal];
nValues = 0;
for (int i = 0; i < subs.length; i++) {
@@ -365,7 +396,7 @@ PointEntry getExact(double[] key, Predicate> pred) {
return ((QNode)sub).getExact(key, pred);
} else if (sub != null) {
PointEntry e = (PointEntry) sub;
- if (QUtil.isPointEqual(e.point(), key)) {
+ if (QUtil.isPointEqual(e.point(), key) && pred.test(e)) {
return e;
}
}
@@ -401,18 +432,23 @@ void checkNode(QStats s, QNode parent, int depth) {
if (parent != null) {
if (!QUtil.isNodeEnclosed(center, radius, parent.center, parent.radius*QUtil.EPS_MUL)) {
+ System.out.println("Outer: " + parent.radius + " " + Arrays.toString(parent.center));
+ System.out.println("Child(" + depth + "): " + radius + " " + Arrays.toString(center));
for (int d = 0; d < center.length; d++) {
-// if ((centerOuter[d]+radiusOuter) / (centerEnclosed[d]+radiusEnclosed) < 0.9999999 ||
-// (centerOuter[d]-radiusOuter) / (centerEnclosed[d]-radiusEnclosed) > 1.0000001) {
-// return false;
-// }
- System.out.println("Outer: " + parent.radius + " " +
- Arrays.toString(parent.center));
- System.out.println("Child: " + radius + " " + Arrays.toString(center));
- System.out.println((parent.center[d]+parent.radius) + " vs " + (center[d]+radius));
- System.out.println("r=" + (parent.center[d]+parent.radius) / (center[d]+radius));
- System.out.println((parent.center[d]-parent.radius) + " vs " + (center[d]-radius));
- System.out.println("r=" + (parent.center[d]-parent.radius) / (center[d]-radius));
+ double parentMax = parent.center[d] + parent.radius;
+ double childMax = center[d] + radius;
+ double parentMin = parent.center[d] - parent.radius;
+ double childMin = center[d] - radius;
+ if (parentMax < childMax) {
+ System.out.println("DIM: " + d);
+ System.out.println("max: " + parentMax + " vs " + childMax);
+ System.out.println(" r: " + parentMax / childMax);
+ }
+ if (parentMin > childMin) {
+ System.out.println("DIM: " + d);
+ System.out.println("min: " + parentMin + " vs " + childMin);
+ System.out.println(" r: " + parentMin / childMin);
+ }
}
throw new IllegalStateException();
}
@@ -421,6 +457,7 @@ void checkNode(QStats s, QNode parent, int depth) {
s.nLeaf++;
s.nEntries += nValues;
s.histoValues[nValues]++;
+ s.maxValuesInNode = Math.max(s.maxValuesInNode, nValues);
for (int i = 0; i < nValues; i++) {
PointEntry e = values[i];
checkEntry(e);
@@ -428,12 +465,17 @@ void checkNode(QStats s, QNode parent, int depth) {
if (subs != null) {
throw new IllegalStateException();
}
+ if (nValues < 2 && parent != null) {
+ // Leaf nodes (except the root) must contain at least two values.
+ throw new IllegalStateException();
+ }
} else {
s.nInner++;
if (subs.length != 1L< parent, int depth) {
((QNode)n).checkNode(s, this, depth+1);
} else if (n != null) {
s.nEntries++;
+ nFoundValues++;
checkEntry(n);
}
}
+ if (nValues != nFoundValues) {
+ throw new IllegalStateException();
+ }
+ s.histoValues[nFoundValues]++;
s.histo(nSubs);
}
}
@@ -456,10 +503,6 @@ private void checkEntry(Object o) {
System.out.println("Node: " + radius + " " + Arrays.toString(center));
System.out.println("Child: " + Arrays.toString(e.point()));
for (int d = 0; d < center.length; d++) {
-// if ((centerOuter[d]+radiusOuter) / (centerEnclosed[d]+radiusEnclosed) < 0.9999999 ||
-// (centerOuter[d]-radiusOuter) / (centerEnclosed[d]-radiusEnclosed) > 1.0000001) {
-// return false;
-// }
System.out.println("min/max for " + d);
System.out.println("min: " + (center[d]-radius) + " vs " + (e.point()[d]));
System.out.println("r=" + (center[d]-radius) / (e.point()[d]));
diff --git a/src/main/java/org/tinspin/index/qthypercube2/QuadTreeKD2.java b/src/main/java/org/tinspin/index/qthypercube2/QuadTreeKD2.java
index 018ae45..01d50d4 100644
--- a/src/main/java/org/tinspin/index/qthypercube2/QuadTreeKD2.java
+++ b/src/main/java/org/tinspin/index/qthypercube2/QuadTreeKD2.java
@@ -21,6 +21,7 @@
import java.util.function.Predicate;
import org.tinspin.index.*;
+import org.tinspin.index.util.MathTools;
import org.tinspin.index.util.StringBuilderLn;
/**
@@ -42,7 +43,7 @@
* With version 1, there were many nodes with just one data entry because the parent directory
* node could not hold date entries.
* With version two a directory node (which contains a hypercube array) will only
- * create a subnode if a quadrant has to mhold more than one data entry.
+ * create a subnode if a quadrant has to hold more than one data entry.
*
*
* @author ztilmann
@@ -108,7 +109,7 @@ public void insert(double[] key, T value) {
PointEntry e = new PointEntry<>(key, value);
if (root == null) {
// We calculate a better radius when adding a second point.
- root = new QNode<>(key.clone(), INITIAL_RADIUS);
+ root = new QNode<>(MathTools.floorPowerOfTwoCopy(key), INITIAL_RADIUS);
}
if (root.getRadius() == INITIAL_RADIUS) {
adjustRootSize(key);
@@ -127,16 +128,20 @@ private void adjustRootSize(double[] key) {
return;
}
if (root.getRadius() == INITIAL_RADIUS) {
- double dist = PointDistance.L2.dist(key, root.getCenter());
- if (dist > 0) {
- root.adjustRadius(2 * dist);
+ double dMax = MathTools.maxDelta(key, root.getCenter());
+ for (int i = 0; i < root.getValueCount(); i++) {
+ dMax = Math.max(dMax, MathTools.maxDelta(root.getValues()[i].point(), root.getCenter()));
+ }
+ double radius = MathTools.ceilPowerOfTwo(dMax + QUtil.EPS_MUL);
+ if (radius > 0) {
+ root.adjustRadius(radius);
} else if (root.getValueCount() >= maxNodeSize - 1) {
- // we just set an arbitrary radius here
+ // all entries have (approximately?) the same coordinates. We just set an arbitrary radius here.
root.adjustRadius(1000);
}
}
}
-
+
/**
* Check whether a given key exists.
* @param key the key to check
@@ -378,6 +383,7 @@ private void toStringTree(StringBuilderLn sb, QNode node,
int depth, int posInParent) {
String prefix = ".".repeat(depth);
sb.append(prefix + posInParent + " d=" + depth);
+ sb.append(" nV=" + node.getValueCount());
sb.append(" " + Arrays.toString(node.getCenter()));
sb.appendLn("/" + node.getRadius());
prefix += " ";
@@ -418,7 +424,7 @@ public QStats getStats() {
* Statistics container class.
*/
public static class QStats extends Stats {
- final int[] histoValues = new int[100];
+ final int[] histoValues = new int[1000];
final int[] histoSubs;
static final int HISTO_MAX = (1 << 10) + 1;
public QStats(int dims) {
diff --git a/src/main/java/org/tinspin/index/qtplain/QNode.java b/src/main/java/org/tinspin/index/qtplain/QNode.java
index 6c386e1..2a801fe 100644
--- a/src/main/java/org/tinspin/index/qtplain/QNode.java
+++ b/src/main/java/org/tinspin/index/qtplain/QNode.java
@@ -296,6 +296,7 @@ void checkNode(QStats s, QNode parent, int depth) {
}
}
if (values != null) {
+ s.maxValuesInNode = Math.max(s.maxValuesInNode, values.size());
for (int i = 0; i < values.size(); i++) {
PointEntry e = values.get(i);
if (!QUtil.fitsIntoNode(e.point(), center, radius*QUtil.EPS_MUL)) {
diff --git a/src/main/java/org/tinspin/index/qtplain/QRNode.java b/src/main/java/org/tinspin/index/qtplain/QRNode.java
index bdf942d..766b5c8 100644
--- a/src/main/java/org/tinspin/index/qtplain/QRNode.java
+++ b/src/main/java/org/tinspin/index/qtplain/QRNode.java
@@ -389,6 +389,7 @@ void checkNode(QStats s, QRNode parent, int depth) {
}
}
if (values != null) {
+ s.maxValuesInNode = Math.max(s.maxValuesInNode, values.size());
for (int i = 0; i < values.size(); i++) {
BoxEntry e = values.get(i);
if (!QUtil.fitsIntoNode(e.min(), e.max(), center, radius*QUtil.EPS_MUL)) {
diff --git a/src/main/java/org/tinspin/index/util/MathTools.java b/src/main/java/org/tinspin/index/util/MathTools.java
new file mode 100644
index 0000000..2507478
--- /dev/null
+++ b/src/main/java/org/tinspin/index/util/MathTools.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2016-2024 Tilmann Zaeschke
+ *
+ * This file is part of TinSpin.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.tinspin.index.util;
+
+public class MathTools {
+
+ private MathTools() {}
+
+ /**
+ * Similar to Math.ceil() with the ceiling being the next higher power of 2.
+ * The resulting number can repeatedly and (almost) always be divided by two without loss of precision.
+ * @param d input
+ * @return next power of two above or equal to 'input'
+ */
+ public static double ceilPowerOfTwo(double d) {
+ double ceil = floorPowerOfTwo(d);
+ return ceil == d ? ceil : ceil * 2;
+ }
+
+ /**
+ * Similar to Math.floor() with the floor being the next lower power of 2.
+ * The resulting number can repeatedly and (almost) always be divided by two without loss of precision.
+ * We calculate the "floor" by setting the "fraction" of the bit representation to 0.
+ * @param d input
+ * @return next power of two below or equal to 'input'
+ */
+ public static double floorPowerOfTwo(double d) {
+ // Set fraction to "0".
+ return Double.longBitsToDouble(Double.doubleToRawLongBits(d) & 0xFFF0_0000_0000_0000L);
+ }
+
+ /**
+ * Calculates the {@link #floorPowerOfTwo(double)} of an array.
+ * @param d input vector
+ * @return copied vector with next lower power of two below 'input'
+ * @see #floorPowerOfTwo(double)
+ */
+ public static double[] floorPowerOfTwoCopy(double[] d) {
+ double[] d2 = new double[d.length];
+ for (int i = 0; i < d.length; i++) {
+ d2[i] = floorPowerOfTwo(d[i]);
+ }
+ return d2;
+ }
+
+ /**
+ * Returns the maximal delta between any pair of scalars in the vector.
+ * @param v1 vector 1
+ * @param v2 vector 2
+ * @return maximal delta (positive or zero).
+ */
+ public static double maxDelta(double[] v1, double[] v2) {
+ double dMax = 0;
+ for (int i = 0; i < v1.length; i++) {
+ dMax = Math.max(dMax, Math.abs(v1[i] - v2[i]));
+ }
+ return dMax;
+ }
+}
diff --git a/src/test/java/org/tinspin/index/kdtree/KDTreeConfigTest.java b/src/test/java/org/tinspin/index/kdtree/KDTreeConfigTest.java
new file mode 100644
index 0000000..5425b27
--- /dev/null
+++ b/src/test/java/org/tinspin/index/kdtree/KDTreeConfigTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2009-2017 Tilmann Zaeschke. All rights reserved.
+ *
+ * This file is part of TinSpin.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.tinspin.index.kdtree;
+
+import org.junit.Test;
+import org.tinspin.index.IndexConfig;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+
+import static org.junit.Assert.*;
+import static org.tinspin.index.Index.PointIterator;
+import static org.tinspin.index.Index.PointIteratorKnn;
+
+public class KDTreeConfigTest {
+
+ private static final int N_DUP = 4;
+ private static final int BOUND = 100;
+
+ private List createInt(long seed, int n, int dim) {
+ List data = new ArrayList<>(n);
+ Random R = new Random(seed);
+ for (int i = 0; i < n; i += N_DUP) {
+ Entry e = new Entry(dim, i);
+ data.add(e);
+ Arrays.setAll(e.p, (x) -> R.nextInt(BOUND));
+ for (int i2 = 1; i2 < N_DUP; ++i2) {
+ Entry e2 = new Entry(dim, i + i2);
+ data.add(e2);
+ System.arraycopy(e.p, 0, e2.p, 0, e.p.length);
+ }
+ }
+ return data;
+ }
+
+ @Test
+ public void smokeTest2D() {
+ smokeTest(createInt(0, 100_000, 2));
+ }
+
+ @Test
+ public void smokeTest3D() {
+ smokeTest(createInt(0, 10_000, 3));
+ }
+
+ @Test
+ public void testDefensiveCopyingDefault() {
+ IndexConfig config = IndexConfig.create(42);
+ assertEquals(42, config.getDimensions());
+ assertTrue(config.getDefensiveKeyCopy());
+ config.setDefensiveKeyCopy(false);
+ assertFalse(config.getDefensiveKeyCopy());
+ }
+
+ @Test
+ public void testDefensiveCopyingFalse() {
+ IndexConfig config = IndexConfig.create(3).setDefensiveKeyCopy(false);
+ KDTree tree = KDTree.create(config);
+
+ double[] point = new double[]{1, 2, 3};
+ double[] pointOriginal = point.clone();
+ Entry e = new Entry(point, 42);
+ tree.insert(point, e);
+
+ // Never do this! We just verify that the key is not copied.
+ point[1] = 15;
+ assertTrue(tree.contains(point));
+ assertFalse(tree.contains(pointOriginal));
+ }
+
+ @Test
+ public void testDefensiveCopyingTrue() {
+ IndexConfig config = IndexConfig.create(3).setDefensiveKeyCopy(true);
+ KDTree tree = KDTree.create(config);
+
+ double[] point = new double[]{1, 2, 3};
+ double[] pointOriginal = point.clone();
+ Entry e = new Entry(point, 42);
+ tree.insert(point, e);
+
+ // Never do this! We just verify that the key is not copied.
+ point[1] = 15;
+ assertFalse(tree.contains(point));
+ assertTrue(tree.contains(pointOriginal));
+ }
+
+ private void smokeTest(List data) {
+ int dim = data.get(0).p.length;
+ IndexConfig config = IndexConfig.create(dim).setDefensiveKeyCopy(false);
+ KDTree tree = KDTree.create(config);
+ for (Entry e : data) {
+ tree.insert(e.p, e);
+ }
+// System.out.println(tree.toStringTree());
+ for (Entry e : data) {
+ if (!tree.contains(e.p)) {
+ throw new IllegalStateException(Arrays.toString(e.p));
+ }
+ }
+
+ for (Entry e : data) {
+ // System.out.println("kNN query: " + e);
+ PointIteratorKnn iter = tree.queryKnn(e.p, N_DUP);
+ if (!iter.hasNext()) {
+ throw new IllegalStateException("kNN() failed: " + Arrays.toString(e.p));
+ }
+ Entry answer = iter.next().value();
+ if (answer.p != e.p && !Arrays.equals(answer.p, e.p)) {
+ throw new IllegalStateException("Expected " + Arrays.toString(e.p) + " but got " + Arrays.toString(answer.p));
+ }
+ }
+
+ for (Entry e : data) {
+ // System.out.println("query: " + Arrays.toString(e.p));
+ PointIterator iter = tree.query(e.p, e.p);
+ if (!iter.hasNext()) {
+ throw new IllegalStateException("query() failed: " + Arrays.toString(e.p));
+ }
+ for (int i = 0; i < N_DUP; ++i) {
+ // System.out.println(" found: " + i + " " + e);
+ Entry answer = iter.next().value();
+ if (!Arrays.equals(answer.p, e.p)) {
+ throw new IllegalStateException("Expected " + e + " but got " + answer);
+ }
+ }
+ }
+
+ for (Entry e : data) {
+// System.out.println(tree.toStringTree());
+// System.out.println("Removing: " + Arrays.toString(key));
+ if (!tree.contains(e.p)) {
+ throw new IllegalStateException("containsExact() failed: " + Arrays.toString(e.p));
+ }
+ Entry answer = tree.remove(e.p);
+ if (answer.p != e.p && !Arrays.equals(answer.p, e.p)) {
+ throw new IllegalStateException("Expected " + Arrays.toString(e.p) + " but got " + Arrays.toString(answer.p));
+ }
+ }
+ }
+
+ private static class Entry {
+ double[] p;
+ int id;
+
+ public Entry(int dim, int id) {
+ this.p = new double[dim];
+ this.id = id;
+ }
+
+ public Entry(double[] key, int id) {
+ this.p = key;
+ this.id = id;
+ }
+
+ boolean equals(Entry e) {
+ return id == e.id && Arrays.equals(p, e.p);
+ }
+
+ @Override
+ public String toString() {
+ return "id=" + id + ":" + Arrays.toString(p);
+ }
+ }
+}
diff --git a/src/test/java/org/tinspin/index/test/BoxMapTest.java b/src/test/java/org/tinspin/index/test/BoxMapTest.java
index a85dd0a..be7f9d5 100644
--- a/src/test/java/org/tinspin/index/test/BoxMapTest.java
+++ b/src/test/java/org/tinspin/index/test/BoxMapTest.java
@@ -21,6 +21,7 @@
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.tinspin.index.BoxMap;
+import org.tinspin.index.Index;
import java.util.*;
@@ -265,6 +266,66 @@ public void testRemove() {
assertEquals(0, tree.size());
}
+ @Test
+ public void testIssueKnnRemove() {
+ if (candidate == IDX.COVER) {
+ return;
+ }
+ Random r = new Random(0);
+ int dim = 3;
+ int n = 1000;
+ int nDelete = n/2;
+ ArrayList data = createInt(0, n, 3);
+ BoxMap tree = createTree(data.size(), dim);
+
+ Collections.shuffle(data, r);
+
+ for (Entry e : data) {
+ tree.insert(e.p1, e.p2, e);
+ }
+
+ // remove 1st half
+ for (int i = 0; i < nDelete; ++i) {
+ Entry e = data.get(i);
+ assertNotNull(tree.remove(e.p1, e.p2));
+ assertNull(tree.remove(e.p1, e.p2));
+ assertFalse(containsExact(tree, e.p1, e.p2, e.id));
+ assertFalse(tree.contains(e.p1, e.p2));
+ }
+
+ // check contains() & kNN
+ for (int i = 0; i < nDelete; ++i) {
+ Entry e = data.get(i);
+ assertFalse(containsExact(tree, e.p1, e.p2, e.id));
+ assertFalse(tree.contains(e.p1, e.p2));
+ Index.BoxEntryKnn eKnn = tree.query1nn(e.p1);
+ assertTrue(tree.contains(eKnn.min(), eKnn.max()));
+ }
+
+ // Issue #37: Entries remained referenced in arrays after removal.
+ // Moreover, the entry count was ignored by kNN, so it looked at the whole
+ // array instead of just the valid entries.
+ Index.BoxIteratorKnn itKnn = tree.queryKnn(data.get(0).p1, n);
+ Set kNNResult = new HashSet<>();
+ while (itKnn.hasNext()) {
+ Index.BoxEntryKnn eKnn = itKnn.next();
+ kNNResult.add(eKnn.value().id);
+ }
+ for (int i = 0; i < n; i++) {
+ Entry e = data.get(i);
+ if (i < nDelete) {
+ assertFalse(containsExact(tree, e.p1, e.p2, e.id));
+ assertFalse(tree.contains(e.p1, e.p2));
+ assertFalse(kNNResult.contains(e.id));
+ } else {
+ assertTrue(containsExact(tree, e.p1, e.p2, e.id));
+ assertTrue(tree.contains(e.p1, e.p2));
+ assertTrue(kNNResult.contains(e.id));
+ }
+ }
+ assertEquals(n - nDelete, kNNResult.size());
+ }
+
private BoxMap createTree(int size, int dims) {
switch (candidate) {
case ARRAY:
diff --git a/src/test/java/org/tinspin/index/test/BoxMultimapTest.java b/src/test/java/org/tinspin/index/test/BoxMultimapTest.java
index 3ce7dc2..75017d8 100644
--- a/src/test/java/org/tinspin/index/test/BoxMultimapTest.java
+++ b/src/test/java/org/tinspin/index/test/BoxMultimapTest.java
@@ -21,6 +21,7 @@
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.tinspin.index.BoxMultimap;
+import org.tinspin.index.Index;
import org.tinspin.index.util.MutableInt;
import java.util.*;
@@ -326,6 +327,66 @@ public void testRemoveIf() {
assertEquals(0, tree.size());
}
+ @Test
+ public void testIssueKnnRemove() {
+ if (candidate == IDX.COVER) {
+ return;
+ }
+ Random r = new Random(0);
+ int dim = 3;
+ int n = 1000;
+ int nDelete = n/2;
+ ArrayList data = createInt(0, n, 3);
+ BoxMultimap tree = createTree(data.size(), dim);
+
+ Collections.shuffle(data, r);
+
+ for (Entry e : data) {
+ tree.insert(e.p1, e.p2, e);
+ }
+
+ // remove 1st half
+ for (int i = 0; i < nDelete; ++i) {
+ Entry e = data.get(i);
+ assertTrue(tree.remove(e.p1, e.p2, e));
+ assertFalse(tree.remove(e.p1, e.p2, e));
+ assertFalse(containsExact(tree, e.p1, e.p2, e.id));
+ assertFalse(tree.contains(e.p1, e.p2, e));
+ }
+
+ // check contains() & kNN
+ for (int i = 0; i < nDelete; ++i) {
+ Entry e = data.get(i);
+ assertFalse(containsExact(tree, e.p1, e.p2, e.id));
+ assertFalse(tree.contains(e.p1, e.p2, e));
+ Index.BoxEntryKnn eKnn = tree.query1nn(e.p1);
+ assertTrue(tree.contains(eKnn.min(), eKnn.max(), eKnn.value()));
+ }
+
+ // Issue #37: Entries remained referenced in arrays after removal.
+ // Moreover, the entry count was ignored by kNN, so it looked at the whole
+ // array instead of just the valid entries.
+ Index.BoxIteratorKnn itKnn = tree.queryKnn(data.get(0).p1, n);
+ Set kNNResult = new HashSet<>();
+ while (itKnn.hasNext()) {
+ Index.BoxEntryKnn eKnn = itKnn.next();
+ kNNResult.add(eKnn.value().id);
+ }
+ for (int i = 0; i < n; i++) {
+ Entry e = data.get(i);
+ if (i < nDelete) {
+ assertFalse(containsExact(tree, e.p1, e.p2, e.id));
+ assertFalse(tree.contains(e.p1, e.p2, e));
+ assertFalse(kNNResult.contains(e.id));
+ } else {
+ assertTrue(containsExact(tree, e.p1, e.p2, e.id));
+ assertTrue(tree.contains(e.p1, e.p2, e));
+ assertTrue(kNNResult.contains(e.id));
+ }
+ }
+ assertEquals(n - nDelete, kNNResult.size());
+ }
+
private BoxMultimap createTree(int size, int dims) {
switch (candidate) {
case ARRAY:
diff --git a/src/test/java/org/tinspin/index/test/Issue0037Test.java b/src/test/java/org/tinspin/index/test/Issue0037Test.java
new file mode 100644
index 0000000..d288eb2
--- /dev/null
+++ b/src/test/java/org/tinspin/index/test/Issue0037Test.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2009-2017 Tilmann Zaeschke. All rights reserved.
+ *
+ * This file is part of TinSpin.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.tinspin.index.test;
+
+import org.junit.Test;
+import org.tinspin.index.PointMap;
+import org.tinspin.index.Stats;
+import org.tinspin.index.qthypercube.QuadTreeKD;
+import org.tinspin.index.qthypercube2.QuadTreeKD2;
+import org.tinspin.index.qtplain.QuadTreeKD0;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Random;
+
+import static org.junit.Assert.assertTrue;
+
+public class Issue0037Test {
+
+ @Test
+ public void testQT() throws IOException {
+ QuadTreeKD tree = QuadTreeKD.create(2);
+ testTree(tree);
+ }
+
+ @Test
+ public void testQT0() throws IOException {
+ QuadTreeKD0 tree = QuadTreeKD0.create(2);
+ testTree(tree);
+ }
+
+ @Test
+ public void testQT2() throws IOException {
+ QuadTreeKD2 tree = QuadTreeKD2.create(2);
+ testTree(tree);
+ }
+
+ /**
+ * p=20.7747859954834, -335.053844928741
+ * knn=[13.7747859954834, -335.053844928741]
+ * 1476
+ * true
+ * p=20.7747859954834, -335.053844928741
+ * knn=[24.7677965164185, -335.507710456848]
+ * 1476
+ * false
+ *
+ *
+ * p=20.7747859954834, -335.053844928741
+ * knn=[13.7747859954834, -335.053844928741]
+ * 1476
+ * true
+ */
+ private void testTree(PointMap tree) throws IOException {
+ File file = new File("src/test/resources/issue0037.txt");
+
+ BufferedReader reader = new BufferedReader(new FileReader(file));
+ int n = Integer.parseInt(reader.readLine());
+ for (int i = 0; i < n; i++) {
+ String[] sp = reader.readLine().split(" ");
+ double d1 = Double.parseDouble(sp[0]), d2 = Double.parseDouble(sp[1]);
+
+ tree.insert(new double[]{d1, d2}, i);
+ }
+ int k = Integer.parseInt(reader.readLine());
+ for (int i = 0; i < k; i++) {
+ String[] sp = reader.readLine().split(" ");
+ double d1 = Double.parseDouble(sp[0]), d2 = Double.parseDouble(sp[1]);
+ System.out.println("Remove: " + d1 + ", " + d2);
+ tree.remove(new double[]{d1, d2});
+ }
+ Stats stats = tree.getStats();
+ System.out.println("Stats: n=" + stats.nEntries);
+ {
+ String[] sp = reader.readLine().split(" ");
+ double d1 = Double.parseDouble(sp[0]), d2 = Double.parseDouble(sp[1]);
+ System.out.println("p=" + d1 + ", " + d2);
+ boolean has13_335 = tree.contains(new double[]{13.7747859954834, -335.053844928741});
+ System.out.println("contains: 13.7747859954834, -335.053844928741 ? " + has13_335);
+ var node = tree.query1nn(new double[]{d1, d2});
+ System.out.println("knn=" + Arrays.toString(node.point()));
+ boolean has = tree.contains(node.point());
+ System.out.println(n);
+
+ System.out.println(has); // Should print true, but prints false with QuadTreeKD2
+ assertTrue(has);
+ }
+ }
+
+ private void testTree2(PointMap tree) throws IOException {
+ Random R = new Random(0);
+ int n = 100;
+ int k = n/2;
+
+ ArrayList array = new ArrayList<>();
+ for (int i = 0; i < n; i++) {
+ double d1 = R.nextDouble(), d2 = R.nextDouble();
+ double[] p = new double[]{d1, d2};
+ array.add(p);
+ tree.insert(p, i);
+ }
+ for (int i = 0; i < k; i++) {
+ double d1 = R.nextDouble(), d2 = R.nextDouble();
+ tree.remove(new double[]{d1, d2});
+ }
+// {
+// String[] sp = reader.readLine().split(" ");
+// double d1 = Double.parseDouble(sp[0]), d2 = Double.parseDouble(sp[1]);
+// var node = tree.query1nn(new double[]{d1, d2});
+// boolean has = tree.contains(node.point());
+// System.out.println(n);
+//
+// System.out.println(has); // Should print true, but prints false with QuadTreeKD2
+// assertTrue(has);
+// }
+ for (int i = 0; i < n; i++) {
+ var node = tree.query1nn(array.get(i));
+ boolean has = tree.contains(node.point());
+ System.out.println(n);
+
+ System.out.println(has); // Should print true, but prints false with QuadTreeKD2
+ assertTrue(has);
+ }
+ }
+}
diff --git a/src/test/java/org/tinspin/index/test/PointMapTest.java b/src/test/java/org/tinspin/index/test/PointMapTest.java
index 8b2bfa0..224a0de 100644
--- a/src/test/java/org/tinspin/index/test/PointMapTest.java
+++ b/src/test/java/org/tinspin/index/test/PointMapTest.java
@@ -20,6 +20,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import org.tinspin.index.Index;
import org.tinspin.index.PointMap;
import java.util.*;
@@ -126,6 +127,7 @@ private void smokeTest(List data) {
tree.insert(e.p, e);
}
// System.out.println(tree.toStringTree());
+ tree.getStats();
for (Entry e : data) {
assertTrue("contains(point) failed: " + e, tree.contains(e.p));
Entry e2 = tree.queryExact(e.p);
@@ -258,6 +260,67 @@ public void testRemove() {
assertEquals(0, tree.size());
}
+ @Test
+ public void testIssueKnnRemove() {
+ if (candidate == IDX.COVER) {
+ return;
+ }
+ Random r = new Random(0);
+ int dim = 3;
+ int n = 1000;
+ int nDelete = n/2;
+ ArrayList data = createInt(0, n, 3);
+ PointMap tree = createTree(data.size(), dim);
+
+ Collections.shuffle(data, r);
+
+ for (Entry e : data) {
+ tree.insert(e.p, e);
+ }
+
+ // remove 1st half
+ for (int i = 0; i < nDelete; ++i) {
+ Entry e = data.get(i);
+ assertNotNull(tree.remove(e.p));
+ assertNull(tree.remove(e.p));
+ assertFalse(containsExact(tree, e.p, e.id));
+ assertFalse(tree.contains(e.p));
+ }
+
+ // check contains() & kNN
+ for (int i = 0; i < nDelete; ++i) {
+ Entry e = data.get(i);
+ assertFalse(containsExact(tree, e.p, e.id));
+ assertFalse(tree.contains(e.p));
+ Index.PointEntryKnn eKnn = tree.query1nn(e.p);
+ assertFalse(Arrays.equals(e.p, eKnn.point()));
+ assertTrue(tree.contains(eKnn.point()));
+ }
+
+ // Issue #37: Entries remained referenced in arrays after removal.
+ // Moreover, the entry count was ignored by kNN, so it looked at the whole
+ // array instead of just the valid entries.
+ Index.PointIteratorKnn itKnn = tree.queryKnn(data.get(0).p, n);
+ Set kNNResult = new HashSet<>();
+ while (itKnn.hasNext()) {
+ Index.PointEntryKnn eKnn = itKnn.next();
+ kNNResult.add(eKnn.value().id);
+ }
+ for (int i = 0; i < n; i++) {
+ Entry e = data.get(i);
+ if (i < nDelete) {
+ assertFalse(containsExact(tree, e.p, e.id));
+ assertFalse(tree.contains(e.p));
+ assertFalse(kNNResult.contains(e.id));
+ } else {
+ assertTrue(containsExact(tree, e.p, e.id));
+ assertTrue(tree.contains(e.p));
+ assertTrue(kNNResult.contains(e.id));
+ }
+ }
+ assertEquals(n - nDelete, kNNResult.size());
+ }
+
private PointMap createTree(int size, int dims) {
switch (candidate) {
case ARRAY:
diff --git a/src/test/java/org/tinspin/index/test/PointMultimapTest.java b/src/test/java/org/tinspin/index/test/PointMultimapTest.java
index 6669b02..a6a7c6e 100644
--- a/src/test/java/org/tinspin/index/test/PointMultimapTest.java
+++ b/src/test/java/org/tinspin/index/test/PointMultimapTest.java
@@ -20,6 +20,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import org.tinspin.index.Index;
import org.tinspin.index.PointMultimap;
import org.tinspin.index.test.util.TestInstances;
@@ -140,7 +141,10 @@ private void smokeTest(List data) {
for (Entry e : data) {
tree.insert(e.p, e);
}
- // System.out.println(tree.toStringTree());
+
+ // Check consistency
+ tree.getStats();
+
for (Entry e : data) {
PointIterator it = tree.queryExactPoint(e.p);
assertTrue("query(point) failed: " + e, it.hasNext());
@@ -166,6 +170,8 @@ private void smokeTest(List data) {
assertEquals(data.size(), nExtent);
}
+ tree.getStats();
+
for (Entry e : data) {
// System.out.println("query: " + Arrays.toString(e.p));
PointIterator iter = tree.query(e.p, e.p);
@@ -210,6 +216,7 @@ public void testUpdate() {
assertFalse(containsExact(tree, pOld, e.id));
assertTrue(containsExact(tree, e.p, e.id));
}
+ tree.getStats();
for (int i = 0; i < data.size(); ++i) {
Entry e = data.get(i);
@@ -309,6 +316,68 @@ public void testRemoveIf() {
assertEquals(0, tree.size());
}
+ @Test
+ public void testIssueKnnRemove() {
+ if (candidate == IDX.COVER) {
+ return;
+ }
+ Random r = new Random(0);
+ int dim = 3;
+ int n = 1000;
+ int nDelete = n/2;
+ ArrayList data = createInt(0, n, 3);
+ PointMultimap tree = createTree(data.size(), dim);
+
+ Collections.shuffle(data, r);
+
+ for (Entry e : data) {
+ tree.insert(e.p, e);
+ }
+ tree.getStats();
+
+ // remove 1st half
+ for (int i = 0; i < nDelete; ++i) {
+ Entry e = data.get(i);
+ assertTrue(tree.remove(e.p, e));
+ assertFalse(tree.remove(e.p, e));
+ assertFalse(containsExact(tree, e.p, e.id));
+ assertFalse(tree.contains(e.p, e));
+ }
+ tree.getStats();
+
+ // check contains() & kNN
+ for (int i = 0; i < nDelete; ++i) {
+ Entry e = data.get(i);
+ assertFalse(containsExact(tree, e.p, e.id));
+ assertFalse(tree.contains(e.p, e));
+ Index.PointEntryKnn eKnn = tree.query1nn(e.p);
+ assertTrue(tree.contains(eKnn.point(), eKnn.value()));
+ }
+
+ // Issue #37: Entries remained referenced in arrays after removal.
+ // Moreover, the entry count was ignored by kNN, so it looked at the whole
+ // array instead of just the valid entries.
+ Index.PointIteratorKnn itKnn = tree.queryKnn(data.get(0).p, n);
+ Set kNNResult = new HashSet<>();
+ while (itKnn.hasNext()) {
+ Index.PointEntryKnn eKnn = itKnn.next();
+ kNNResult.add(eKnn.value().id);
+ }
+ for (int i = 0; i < n; i++) {
+ Entry e = data.get(i);
+ if (i < nDelete) {
+ assertFalse(containsExact(tree, e.p, e.id));
+ assertFalse(tree.contains(e.p, e));
+ assertFalse(kNNResult.contains(e.id));
+ } else {
+ assertTrue(containsExact(tree, e.p, e.id));
+ assertTrue(tree.contains(e.p, e));
+ assertTrue(kNNResult.contains(e.id));
+ }
+ }
+ assertEquals(n - nDelete, kNNResult.size());
+ }
+
private PointMultimap createTree(int size, int dims) {
switch (candidate) {
case ARRAY:
@@ -356,4 +425,42 @@ public String toString() {
return "id=" + id + ":" + Arrays.toString(p);
}
}
+
+ @Test
+ public void testIssue0040_remove() {
+ double[][] data = new double[][] {
+ new double[]{-49.0949020385742, -2.05027413368225, 819588127, 0},
+ new double[]{-49.0949020385742, -2.05027389526367, 819588127, 0},
+ new double[]{-45.6938514709473, 32.9847145080566, -2056090140, 0},
+ new double[]{-45.6938514709473, 32.9847145080566, -2056090140, 0},
+ new double[]{-1.7595032453537, 112.097793579102, -267989921, 0},
+ new double[]{-1.75950336456299, 112.097793579102, -267989921, 0},
+ new double[]{45.6938438415527, 32.9847145080566, 1591613824, 0},
+ new double[]{45.6938438415527, 32.9847145080566, 1591613824, 0},
+ new double[]{49.0948944091797, -2.05027413368225, 14481734, 0},
+ new double[]{49.0948944091797, -2.05027389526367, 14481734, 0},
+ new double[]{-49.0949020385742, -2.05027413368225, 819588127, 1},
+ new double[]{-49.0949020385742, -2.05027389526367, 819588127, 1},
+ new double[]{-49.0949020385742, -2.05027413368225, 916603126, 0},
+ };
+
+ PointMultimap tree = createTree(100,2);
+ for (int i = 0; i < data.length; i++) {
+ if (data[i][3] == 0) {
+ tree.insert(Arrays.copyOf(data[i], 2), (int)data[i][2]);
+ } else {
+ tree.remove(Arrays.copyOf(data[i], 2), (int)data[i][2]);
+ }
+ }
+
+ assertEquals(9, tree.size());
+ int n = 0;
+ double[] min = new double[]{-50, -3};
+ double[] max = new double[]{50, 113};
+ for (Index.PointIterator it = tree.query(min, max); it.hasNext(); it.next()) {
+ n++;
+ }
+ assertEquals(9, n);
+ }
+
}
diff --git a/src/test/java/org/tinspin/util/MathToolsTest.java b/src/test/java/org/tinspin/util/MathToolsTest.java
new file mode 100644
index 0000000..c33150f
--- /dev/null
+++ b/src/test/java/org/tinspin/util/MathToolsTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2016-2024 Tilmann Zaeschke
+ *
+ * This file is part of TinSpin.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.tinspin.util;
+
+import static org.junit.Assert.*;
+import org.junit.Test;
+import org.tinspin.index.util.MathTools;
+
+import java.util.Arrays;
+
+public class MathToolsTest {
+
+ @Test
+ public void powerOfTwoCeil() {
+ assertEquals(1./32., MathTools.ceilPowerOfTwo(0.03), 0.0);
+ assertEquals(0.5, MathTools.ceilPowerOfTwo(0.3), 0.0);
+ assertEquals(4, MathTools.ceilPowerOfTwo(3), 0.0);
+ assertEquals(32, MathTools.ceilPowerOfTwo(30), 0.0);
+ assertEquals(512, MathTools.ceilPowerOfTwo(300), 0.0);
+
+ assertEquals(-0.5, MathTools.ceilPowerOfTwo(-0.3), 0.0);
+ assertEquals(-4, MathTools.ceilPowerOfTwo(-3), 0.0);
+ assertEquals(-32, MathTools.ceilPowerOfTwo(-30), 0.0);
+
+ // identity
+ assertEquals(0, MathTools.ceilPowerOfTwo(0), 0.0);
+ assertEquals(-0.5, MathTools.ceilPowerOfTwo(-0.5), 0.0);
+ assertEquals(0.5, MathTools.ceilPowerOfTwo(0.5), 0.0);
+ assertEquals(-1, MathTools.ceilPowerOfTwo(-1), 0.0);
+ assertEquals(1, MathTools.ceilPowerOfTwo(1), 0.0);
+ assertEquals(-2, MathTools.ceilPowerOfTwo(-2), 0.0);
+ assertEquals(2, MathTools.ceilPowerOfTwo(2), 0.0);
+ }
+
+ @Test
+ public void powerOfTwoFloor() {
+ assertEquals(1./64., MathTools.floorPowerOfTwo(0.03), 0.0);
+ assertEquals(0.25, MathTools.floorPowerOfTwo(0.3), 0.0);
+ assertEquals(2, MathTools.floorPowerOfTwo(3), 0.0);
+ assertEquals(16, MathTools.floorPowerOfTwo(30), 0.0);
+ assertEquals(256, MathTools.floorPowerOfTwo(300), 0.0);
+
+ assertEquals(-0.25, MathTools.floorPowerOfTwo(-0.3), 0.0);
+ assertEquals(-2, MathTools.floorPowerOfTwo(-3), 0.0);
+ assertEquals(-16, MathTools.floorPowerOfTwo(-30), 0.0);
+
+ // identity
+ assertEquals(0, MathTools.ceilPowerOfTwo(0), 0.0);
+ assertEquals(-0.5, MathTools.ceilPowerOfTwo(-0.5), 0.0);
+ assertEquals(0.5, MathTools.ceilPowerOfTwo(0.5), 0.0);
+ assertEquals(-1, MathTools.ceilPowerOfTwo(-1), 0.0);
+ assertEquals(1, MathTools.ceilPowerOfTwo(1), 0.0);
+ assertEquals(-2, MathTools.ceilPowerOfTwo(-2), 0.0);
+ assertEquals(2, MathTools.ceilPowerOfTwo(2), 0.0);
+ }
+
+ @Test
+ public void powerOfTwoFloor_vector() {
+ double[] d = {0.03, 0.3, 3, 30, 300};
+ double[] dCopy = MathTools.floorPowerOfTwoCopy(d);
+ assertFalse(Arrays.equals(d, dCopy));
+ assertEquals(1./64., dCopy[0], 0.0);
+ assertEquals(0.25, dCopy[1], 0.0);
+ assertEquals(2, dCopy[2], 0.0);
+ assertEquals(16, dCopy[3], 0.0);
+ assertEquals(256, dCopy[4], 0.0);
+ }
+
+ @Test
+ public void maxDelta() {
+ assertEquals(12, MathTools.maxDelta(new double[]{-4.}, new double[]{8.}), 0.0);
+ assertEquals(12, MathTools.maxDelta(new double[]{8.}, new double[]{-4.}), 0.0);
+
+ assertEquals(4, MathTools.maxDelta(new double[]{2, 4, 2}, new double[]{3, 8, 4}), 0.0);
+ }
+}
diff --git a/src/test/java/org/tinspin/MinHeapTest.java b/src/test/java/org/tinspin/util/MinHeapTest.java
similarity index 99%
rename from src/test/java/org/tinspin/MinHeapTest.java
rename to src/test/java/org/tinspin/util/MinHeapTest.java
index f729c0e..8eff04a 100644
--- a/src/test/java/org/tinspin/MinHeapTest.java
+++ b/src/test/java/org/tinspin/util/MinHeapTest.java
@@ -14,7 +14,8 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- */package org.tinspin;
+ */
+package org.tinspin.util;
import org.junit.Test;
import org.tinspin.index.util.MinHeapI;
diff --git a/src/test/java/org/tinspin/MinMaxHeapTest.java b/src/test/java/org/tinspin/util/MinMaxHeapTest.java
similarity index 99%
rename from src/test/java/org/tinspin/MinMaxHeapTest.java
rename to src/test/java/org/tinspin/util/MinMaxHeapTest.java
index a8bc46d..c812671 100644
--- a/src/test/java/org/tinspin/MinMaxHeapTest.java
+++ b/src/test/java/org/tinspin/util/MinMaxHeapTest.java
@@ -14,7 +14,8 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- */package org.tinspin;
+ */
+package org.tinspin.util;
import static org.junit.Assert.*;
import org.junit.Test;
diff --git a/src/test/resources/issue0037.txt b/src/test/resources/issue0037.txt
new file mode 100644
index 0000000..76c3cc5
--- /dev/null
+++ b/src/test/resources/issue0037.txt
@@ -0,0 +1,1491 @@
+1476
+31.7677965164185 -335.507710456848
+31.0062980651855 19.9242215156555
+-1.0154333114624 -333.677649497986
+-1.96946239471436 18.9298062324524
+9.97757720947266 -334.131515026093
+8.82774639129639 18.0074763298035
+64.7435569763184 -334.513295173645
+63.7895278930664 18.0941605567932
+42.9533376693726 -333.137099742889
+41.9993085861206 19.4703559875488
+53.9463481903076 -333.590965270996
+52.7965173721313 18.5480260848999
+109.707783699036 -334.397708415985
+108.753754615784 18.2097473144531
+120.700794219971 -334.851573944092
+119.939295768738 20.5803580284119
+87.9175643920898 -333.021512985229
+86.9635353088379 19.5859427452087
+98.9105749130249 -333.475378513336
+97.7607440948486 18.6636128425598
+153.676554679871 -333.857158660889
+152.722525596619 18.7502970695496
+131.886335372925 -332.480963230133
+130.932306289673 20.1264925003052
+142.87934589386 -332.93482875824
+141.729515075684 19.2041625976563
+199.085423469543 -335.160625934601
+198.131394386292 17.4468297958374
+210.078433990479 -335.614491462708
+209.316935539246 19.8174405097961
+177.295204162598 -333.784430503845
+176.341175079346 18.823025226593
+188.288214683533 -334.238296031952
+187.138383865356 17.9006953239441
+243.054194450378 -334.620076179504
+242.100165367126 17.9873795509338
+221.263975143433 -333.243880748749
+220.309946060181 19.3635749816895
+232.256985664368 -333.697746276855
+231.107154846191 18.4412450790405
+288.018421173096 -334.504489421844
+287.064392089844 18.1029663085938
+299.011431694031 -334.958354949951
+298.249933242798 20.4735770225525
+266.22820186615 -333.128293991089
+265.274172782898 19.4791617393494
+277.221212387085 -333.582159519196
+276.071381568909 18.5568318367004
+331.987192153931 -333.963939666748
+331.033163070679 18.6435160636902
+310.196972846985 -332.587744235992
+309.242943763733 20.0197114944458
+321.18998336792 -333.041609764099
+320.040152549744 19.0973815917969
+375.944495201111 -336.525193214417
+374.990466117859 16.0822625160217
+386.937505722046 -336.979058742523
+386.176007270813 18.4528732299805
+354.154275894165 -335.148997783661
+353.200246810913 17.4584579467773
+365.1472864151 -335.602863311768
+363.997455596924 16.5361280441284
+419.913266181946 -335.98464345932
+418.959237098694 16.6228122711182
+398.123046875 -334.608448028564
+397.169017791748 17.9990077018738
+409.116057395935 -335.062313556671
+407.966226577759 17.0766777992249
+464.877492904663 -335.86905670166
+463.923463821411 16.7383990287781
+475.870503425598 -336.322922229767
+475.109004974365 19.1090097427368
+443.087273597717 -334.492861270905
+442.133244514465 18.1145944595337
+454.080284118652 -334.946726799011
+452.930453300476 17.1922645568848
+508.846263885498 -335.328506946564
+507.892234802246 17.2789487838745
+487.056044578552 -333.952311515808
+486.1020154953 18.6551442146301
+498.049055099487 -334.406177043915
+496.899224281311 17.7328143119812
+554.255132675171 -336.631974220276
+553.301103591919 15.9754815101624
+565.248143196106 -337.085839748383
+564.486644744873 18.3460922241211
+532.464913368225 -335.25577878952
+531.510884284973 17.351676940918
+543.45792388916 -335.709644317627
+542.308093070984 16.429347038269
+598.223903656006 -336.091424465179
+597.269874572754 16.5160312652588
+576.43368434906 -334.715229034424
+575.479655265808 17.8922266960144
+587.426694869995 -335.169094562531
+586.276864051819 16.9698967933655
+643.188130378723 -335.97583770752
+642.234101295471 16.6316180229187
+654.181140899658 -336.429703235626
+653.419642448425 19.0022287368774
+621.397911071777 -334.599642276764
+620.443881988525 18.0078134536743
+632.390921592712 -335.053507804871
+631.241090774536 17.0854835510254
+687.156901359558 -335.435287952423
+686.202872276306 17.1721677780151
+665.366682052612 -334.059092521667
+664.41265296936 18.5483632087708
+676.359692573547 -334.512958049774
+675.209861755371 17.6260333061218
+14.7762746810913 -291.039459228516
+13.8207569122314 17.5536108016968
+25.7692852020264 -291.493324756622
+25.0062980651855 19.9242215156555
+-7.01394462585449 -289.66326379776
+-7.96946239471436 18.9298062324524
+3.97906589508057 -290.117129325867
+2.82774639129639 18.0074763298035
+58.7450456619263 -290.498909473419
+57.7895278930664 18.0941605567932
+36.9548263549805 -289.122714042664
+35.9993085861206 19.4703559875488
+47.9478368759155 -289.57657957077
+46.7965173721313 18.5480260848999
+16.7747859954834 -335.053844928741
+15.8192682266235 -26.4607748985291
+27.7677965164185 -335.507710456848
+27.0048093795776 -24.0901641845703
+-5.0154333114624 -333.677649497986
+-5.97095108032227 -25.0845794677734
+5.97757720947266 -334.131515026093
+4.82625770568848 -26.0069093704224
+60.7435569763184 -334.513295173645
+59.7880392074585 -25.9202251434326
+38.9533376693726 -333.137099742889
+37.9978199005127 -24.544029712677
+49.9463481903076 -333.590965270996
+48.7950286865234 -25.4663596153259
+103.709272384644 -290.383322715759
+102.753754615784 18.2097473144531
+114.702282905579 -290.837188243866
+113.939295768738 20.5803580284119
+81.9190530776978 -289.007127285004
+80.9635353088379 19.5859427452087
+92.9120635986328 -289.46099281311
+91.7607440948486 18.6636128425598
+147.678043365479 -289.842772960663
+146.722525596619 18.7502970695496
+125.887824058533 -288.466577529907
+124.932306289673 20.1264925003052
+136.880834579468 -288.920443058014
+135.729515075684 19.2041625976563
+105.707783699036 -334.397708415985
+104.752265930176 -25.8046383857727
+116.700794219971 -334.851573944092
+115.93780708313 -23.434027671814
+83.9175643920898 -333.021512985229
+82.96204662323 -24.4284429550171
+94.9105749130249 -333.475378513336
+93.7592554092407 -25.350772857666
+149.676554679871 -333.857158660889
+148.721036911011 -25.2640886306763
+127.886335372925 -332.480963230133
+126.930817604065 -23.8878931999207
+138.87934589386 -332.93482875824
+137.728026390076 -24.8102231025696
+193.086912155151 -291.146240234375
+192.131394386292 17.4468297958374
+204.079922676086 -291.600105762482
+203.316935539246 19.8174405097961
+171.296692848206 -289.770044803619
+170.341175079346 18.823025226593
+182.289703369141 -290.223910331726
+181.138383865356 17.9006953239441
+237.055683135986 -290.605690479279
+236.100165367126 17.9873795509338
+215.265463829041 -289.229495048523
+214.309946060181 19.3635749816895
+226.258474349976 -289.68336057663
+225.107154846191 18.4412450790405
+195.085423469543 -335.160625934601
+194.129905700684 -26.5675559043884
+206.078433990479 -335.614491462708
+205.315446853638 -24.1969451904297
+173.295204162598 -333.784430503845
+172.339686393738 -25.1913604736328
+184.288214683533 -334.238296031952
+183.136895179749 -26.1136903762817
+239.054194450378 -334.620076179504
+238.098676681519 -26.027006149292
+217.263975143433 -333.243880748749
+216.308457374573 -24.6508107185364
+228.256985664368 -333.697746276855
+227.105666160583 -25.5731406211853
+282.019909858704 -290.490103721619
+281.064392089844 18.1029663085938
+293.012920379639 -290.943969249725
+292.249933242798 20.4735770225525
+260.229690551758 -289.113908290863
+259.274172782898 19.4791617393494
+271.222701072693 -289.56777381897
+270.071381568909 18.5568318367004
+325.988680839539 -289.949553966522
+325.033163070679 18.6435160636902
+304.198461532593 -288.573358535767
+303.242943763733 20.0197114944458
+315.191472053528 -289.027224063873
+314.040152549744 19.0973815917969
+284.018421173096 -334.504489421844
+283.062903404236 -25.9114193916321
+295.011431694031 -334.958354949951
+294.24844455719 -23.5408086776733
+262.22820186615 -333.128293991089
+261.27268409729 -24.5352239608765
+273.221212387085 -333.582159519196
+272.069892883301 -25.4575538635254
+327.987192153931 -333.963939666748
+327.031674385071 -25.3708696365356
+306.196972846985 -332.587744235992
+305.241455078125 -23.99467420578
+317.18998336792 -333.041609764099
+316.038663864136 -24.917004108429
+369.945983886719 -292.510807514191
+368.990466117859 16.0822625160217
+380.938994407654 -292.964673042297
+380.176007270813 18.4528732299805
+348.155764579773 -291.134612083435
+347.200246810913 17.4584579467773
+359.148775100708 -291.588477611542
+357.997455596924 16.5361280441284
+413.914754867554 -291.970257759094
+412.959237098694 16.6228122711182
+392.124535560608 -290.594062328339
+391.169017791748 17.9990077018738
+403.117546081543 -291.047927856445
+401.966226577759 17.0766777992249
+371.944495201111 -336.525193214417
+370.988977432251 -27.9321231842041
+382.937505722046 -336.979058742523
+382.174518585205 -25.5615124702454
+350.154275894165 -335.148997783661
+349.198758125305 -26.5559277534485
+361.1472864151 -335.602863311768
+359.995966911316 -27.4782576560974
+415.913266181946 -335.98464345932
+414.957748413086 -27.3915734291077
+394.123046875 -334.608448028564
+393.16752910614 -26.0153779983521
+405.116057395935 -335.062313556671
+403.964737892151 -26.937707901001
+458.878981590271 -291.854671001434
+457.923463821411 16.7383990287781
+469.871992111206 -292.308536529541
+469.109004974365 19.1090097427368
+437.088762283325 -290.478475570679
+436.133244514465 18.1145944595337
+448.08177280426 -290.932341098785
+446.930453300476 17.1922645568848
+502.847752571106 -291.314121246338
+501.892234802246 17.2789487838745
+481.05753326416 -289.937925815582
+480.1020154953 18.6551442146301
+492.050543785095 -290.391791343689
+490.899224281311 17.7328143119812
+460.877492904663 -335.86905670166
+459.921975135803 -27.2759866714478
+471.870503425598 -336.322922229767
+471.107516288757 -24.905375957489
+439.087273597717 -334.492861270905
+438.131755828857 -25.8997912406921
+450.080284118652 -334.946726799011
+448.928964614868 -26.8221211433411
+504.846263885498 -335.328506946564
+503.890746116638 -26.7354369163513
+483.056044578552 -333.952311515808
+482.100526809692 -25.3592414855957
+494.049055099487 -334.406177043915
+492.897735595703 -26.2815713882446
+548.256621360779 -292.61758852005
+547.301103591919 15.9754815101624
+559.249631881714 -293.071454048157
+558.486644744873 18.3460922241211
+526.466402053833 -291.241393089294
+525.510884284973 17.351676940918
+537.459412574768 -291.695258617401
+536.308093070984 16.429347038269
+592.225392341614 -292.077038764954
+591.269874572754 16.5160312652588
+570.435173034668 -290.700843334198
+569.479655265808 17.8922266960144
+581.428183555603 -291.154708862305
+580.276864051819 16.9698967933655
+550.255132675171 -336.631974220276
+549.299614906311 -28.0389041900635
+561.248143196106 -337.085839748383
+560.485156059265 -25.6682934761047
+528.464913368225 -335.25577878952
+527.509395599365 -26.6627087593079
+539.45792388916 -335.709644317627
+538.306604385376 -27.5850386619568
+594.223903656006 -336.091424465179
+593.268385887146 -27.498354434967
+572.43368434906 -334.715229034424
+571.4781665802 -26.1221590042114
+583.426694869995 -335.169094562531
+582.275375366211 -27.0444889068604
+637.189619064331 -291.961452007294
+636.234101295471 16.6316180229187
+648.182629585266 -292.4153175354
+647.419642448425 19.0022287368774
+615.399399757385 -290.585256576538
+614.443881988525 18.0078134536743
+626.39241027832 -291.039122104645
+625.241090774536 17.0854835510254
+681.158390045166 -291.420902252197
+680.202872276306 17.1721677780151
+659.36817073822 -290.044706821442
+658.41265296936 18.5483632087708
+670.361181259155 -290.498572349548
+669.209861755371 17.6260333061218
+639.188130378723 -335.97583770752
+638.232612609863 -27.3827676773071
+650.181140899658 -336.429703235626
+649.418153762817 -25.0121569633484
+617.397911071777 -334.599642276764
+616.442393302917 -26.0065722465515
+628.390921592712 -335.053507804871
+627.239602088928 -26.9289021492004
+683.156901359558 -335.435287952423
+682.201383590698 -26.8422179222107
+661.366682052612 -334.059092521667
+660.411164283752 -25.4660224914551
+672.359692573547 -334.512958049774
+671.208373069763 -26.388352394104
+69.7380561828613 -290.952775001526
+68.7880392074585 -25.9202251434326
+158.671053886414 -290.29663848877
+157.721036911011 -25.2640886306763
+248.048693656921 -291.059556007385
+247.098676681519 -26.027006149292
+336.981691360474 -290.403419494629
+336.031674385071 -25.3708696365356
+424.907765388489 -292.424123287201
+423.957748413086 -27.3915734291077
+513.840763092041 -291.767986774445
+512.890746116638 -26.7354369163513
+603.218402862549 -292.53090429306
+602.268385887146 -27.498354434967
+692.151400566101 -291.874767780304
+691.201383590698 -26.8422179222107
+12.9273357391357 -201.649954795837
+9.82774639129639 18.0074763298035
+23.9203462600708 -202.103820323944
+20.8207569122314 17.5536108016968
+-11.8628835678101 -200.273759365082
+-12.9624729156494 19.3836717605591
+-8.86288356781006 -200.273759365082
+-11.9624729156494 19.3836717605591
+2.130126953125 -200.727624893188
+-0.969462394714355 18.9298062324524
+56.8961067199707 -201.109405040741
+53.7965173721313 18.5480260848999
+67.8891172409058 -201.563270568848
+64.7895278930664 18.0941605567932
+35.1058874130249 -199.733209609985
+32.0062980651855 19.9242215156555
+46.09889793396 -200.187075138092
+42.9993085861206 19.4703559875488
+101.860333442688 -200.993818283081
+98.7607440948486 18.6636128425598
+112.853343963623 -201.447683811188
+109.753754615784 18.2097473144531
+77.0701141357422 -199.617622852325
+75.9705247879028 20.0398082733154
+80.0701141357422 -199.617622852325
+76.9705247879028 20.0398082733154
+91.0631246566772 -200.071488380432
+87.9635353088379 19.5859427452087
+145.829104423523 -200.453268527985
+142.729515075684 19.2041625976563
+156.822114944458 -200.907134056091
+153.722525596619 18.7502970695496
+124.038885116577 -199.077073097229
+120.939295768738 20.5803580284119
+135.031895637512 -199.530938625336
+131.932306289673 20.1264925003052
+19.7747859954834 -335.053844928741
+17.6682071685791 -115.850279331207
+30.7677965164185 -335.507710456848
+28.8537483215332 -113.479668617249
+-2.0154333114624 -333.677649497986
+-4.1220121383667 -114.474083900452
+8.97757720947266 -334.131515026093
+6.67519664764404 -115.396413803101
+63.7435569763184 -334.513295173645
+61.6369781494141 -115.309729576111
+71.7365674972534 -334.967160701752
+70.6369781494141 -115.309729576111
+74.7365674972534 -334.967160701752
+73.8179750442505 -113.364081859589
+41.9533376693726 -333.137099742889
+39.8467588424683 -113.933534145355
+52.9463481903076 -333.590965270996
+50.643967628479 -114.855864048004
+108.707783699036 -334.397708415985
+106.601204872131 -115.194142818451
+119.700794219971 -334.851573944092
+117.786746025085 -112.823532104492
+86.9175643920898 -333.021512985229
+84.8109855651855 -113.817947387695
+97.9105749130249 -333.475378513336
+95.6081943511963 -114.740277290344
+152.676554679871 -333.857158660889
+150.569975852966 -114.653593063354
+160.669565200806 -334.311024188995
+159.569975852966 -114.653593063354
+130.886335372925 -332.480963230133
+128.779756546021 -113.277397632599
+141.87934589386 -332.93482875824
+139.576965332031 -114.199727535248
+191.237973213196 -201.756735801697
+188.138383865356 17.9006953239441
+202.230983734131 -202.210601329803
+199.131394386292 17.4468297958374
+166.44775390625 -200.380540370941
+165.348164558411 19.2768907546997
+169.44775390625 -200.380540370941
+166.348164558411 19.2768907546997
+180.440764427185 -200.834405899048
+177.341175079346 18.823025226593
+235.206744194031 -201.2161860466
+232.107154846191 18.4412450790405
+246.199754714966 -201.670051574707
+243.100165367126 17.9873795509338
+213.416524887085 -199.839990615845
+210.316935539246 19.8174405097961
+224.40953540802 -200.293856143951
+221.309946060181 19.3635749816895
+280.170970916748 -201.10059928894
+277.071381568909 18.5568318367004
+291.163981437683 -201.554464817047
+288.064392089844 18.1029663085938
+255.380751609802 -199.724403858185
+254.281162261963 19.9330272674561
+258.380751609802 -199.724403858185
+255.281162261963 19.9330272674561
+269.373762130737 -200.178269386292
+266.274172782898 19.4791617393494
+324.139741897583 -200.560049533844
+321.040152549744 19.0973815917969
+335.132752418518 -201.013915061951
+332.033163070679 18.6435160636902
+302.349522590637 -199.183854103088
+299.249933242798 20.4735770225525
+313.342533111572 -199.637719631195
+310.242943763733 20.0197114944458
+198.085423469543 -335.160625934601
+195.978844642639 -115.957060337067
+209.078433990479 -335.614491462708
+207.164385795593 -113.586449623108
+176.295204162598 -333.784430503845
+174.188625335693 -114.580864906311
+187.288214683533 -334.238296031952
+184.985834121704 -115.50319480896
+242.054194450378 -334.620076179504
+239.947615623474 -115.41651058197
+250.047204971313 -335.073941707611
+248.947615623474 -115.41651058197
+253.047204971313 -335.073941707611
+252.128612518311 -113.470862865448
+220.263975143433 -333.243880748749
+218.157396316528 -114.040315151215
+231.256985664368 -333.697746276855
+228.954605102539 -114.962645053864
+287.018421173096 -334.504489421844
+284.911842346191 -115.30092382431
+298.011431694031 -334.958354949951
+296.097383499146 -112.930313110352
+265.22820186615 -333.128293991089
+263.121623039246 -113.924728393555
+276.221212387085 -333.582159519196
+273.918831825256 -114.847058296204
+330.987192153931 -333.963939666748
+328.880613327026 -114.760374069214
+338.980202674866 -334.417805194855
+337.880613327026 -114.760374069214
+341.980202674866 -334.417805194855
+340.054686546326 -115.49156665802
+309.196972846985 -332.587744235992
+307.090394020081 -113.384178638458
+320.18998336792 -333.041609764099
+317.887602806091 -114.306508541107
+368.097044944763 -203.121303081512
+364.997455596924 16.5361280441284
+379.090055465698 -203.575168609619
+375.990466117859 16.0822625160217
+343.306825637817 -201.745107650757
+342.207236289978 17.912323474884
+346.306825637817 -201.745107650757
+343.207236289978 17.912323474884
+357.299836158752 -202.198973178864
+354.200246810913 17.4584579467773
+412.065815925598 -202.580753326416
+408.966226577759 17.0766777992249
+423.058826446533 -203.034618854523
+419.959237098694 16.6228122711182
+390.275596618652 -201.20455789566
+387.176007270813 18.4528732299805
+401.268607139587 -201.658423423767
+398.169017791748 17.9990077018738
+457.030042648315 -202.465166568756
+453.930453300476 17.1922645568848
+468.02305316925 -202.919032096863
+464.923463821411 16.7383990287781
+432.23982334137 -201.088971138
+431.14023399353 18.5684599876404
+435.23982334137 -201.088971138
+432.14023399353 18.5684599876404
+446.232833862305 -201.542836666107
+443.133244514465 18.1145944595337
+500.99881362915 -201.92461681366
+497.899224281311 17.7328143119812
+511.991824150085 -202.378482341766
+508.892234802246 17.2789487838745
+479.208594322205 -200.548421382904
+476.109004974365 19.1090097427368
+490.20160484314 -201.002286911011
+487.1020154953 18.6551442146301
+374.944495201111 -336.525193214417
+372.837916374207 -117.321627616882
+385.937505722046 -336.979058742523
+384.023457527161 -114.951016902924
+353.154275894165 -335.148997783661
+351.047697067261 -115.945432186127
+364.1472864151 -335.602863311768
+361.844905853271 -116.867762088776
+418.913266181946 -335.98464345932
+416.806687355042 -116.781077861786
+426.906276702881 -336.438508987427
+425.806687355042 -116.781077861786
+429.906276702881 -336.438508987427
+428.987684249878 -114.835430145264
+397.123046875 -334.608448028564
+395.016468048096 -115.40488243103
+408.116057395935 -335.062313556671
+405.813676834106 -116.327212333679
+463.877492904663 -335.86905670166
+461.770914077759 -116.665491104126
+474.870503425598 -336.322922229767
+472.956455230713 -114.294880390167
+442.087273597717 -334.492861270905
+439.980694770813 -115.28929567337
+453.080284118652 -334.946726799011
+450.777903556824 -116.211625576019
+507.846263885498 -335.328506946564
+505.739685058594 -116.12494134903
+515.839274406433 -335.78237247467
+514.739685058594 -116.12494134903
+486.056044578552 -333.952311515808
+483.949465751648 -114.748745918274
+497.049055099487 -334.406177043915
+494.746674537659 -115.671075820923
+546.407682418823 -203.228084087372
+543.308093070984 16.429347038269
+557.400692939758 -203.681949615479
+554.301103591919 15.9754815101624
+521.617463111877 -201.851888656616
+520.517873764038 17.8055424690247
+524.617463111877 -201.851888656616
+521.517873764038 17.8055424690247
+535.610473632813 -202.305754184723
+532.510884284973 17.351676940918
+590.376453399658 -202.687534332275
+587.276864051819 16.9698967933655
+601.369463920593 -203.141399860382
+598.269874572754 16.5160312652588
+568.586234092712 -201.31133890152
+565.486644744873 18.3460922241211
+579.579244613647 -201.765204429626
+576.479655265808 17.8922266960144
+635.340680122375 -202.571947574615
+632.241090774536 17.0854835510254
+646.333690643311 -203.025813102722
+643.234101295471 16.6316180229187
+610.55046081543 -201.19575214386
+609.45087146759 18.461678981781
+613.55046081543 -201.19575214386
+610.45087146759 18.461678981781
+624.543471336365 -201.649617671967
+621.443881988525 18.0078134536743
+679.30945110321 -202.031397819519
+676.209861755371 17.6260333061218
+690.302461624146 -202.485263347626
+687.202872276306 17.1721677780151
+657.519231796265 -200.655202388763
+654.419642448425 19.0022287368774
+668.5122423172 -201.10906791687
+665.41265296936 18.5483632087708
+553.255132675171 -336.631974220276
+551.148553848267 -117.428408622742
+564.248143196106 -337.085839748383
+562.334095001221 -115.057797908783
+531.464913368225 -335.25577878952
+529.358334541321 -116.052213191986
+542.45792388916 -335.709644317627
+540.155543327332 -116.974543094635
+597.223903656006 -336.091424465179
+595.117324829102 -116.887858867645
+605.216914176941 -336.545289993286
+604.117324829102 -116.887858867645
+608.216914176941 -336.545289993286
+607.298321723938 -114.942211151123
+575.43368434906 -334.715229034424
+573.327105522156 -115.51166343689
+586.426694869995 -335.169094562531
+584.124314308167 -116.433993339539
+642.188130378723 -335.97583770752
+640.081551551819 -116.772272109985
+653.181140899658 -336.429703235626
+651.267092704773 -114.401661396027
+620.397911071777 -334.599642276764
+618.291332244873 -115.39607667923
+631.390921592712 -335.053507804871
+629.088541030884 -116.318406581879
+686.156901359558 -335.435287952423
+684.050322532654 -116.231722354889
+694.149911880493 -335.88915348053
+693.050322532654 -116.231722354889
+664.366682052612 -334.059092521667
+662.260103225708 -114.855526924133
+675.359692573547 -334.512958049774
+673.057312011719 -115.777856826782
+13.2203102111816 -156.710306167603
+11.8277463912964 18.0074763298035
+24.2133207321167 -157.164171695709
+22.8207569122314 17.5536108016968
+-8.56990909576416 -155.334110736847
+-9.96247291564941 19.3836717605591
+2.4231014251709 -155.787976264954
+1.03053760528564 18.9298062324524
+57.1890811920166 -156.169756412506
+55.7965173721313 18.5480260848999
+68.1820917129517 -156.623621940613
+66.7895278930664 18.0941605567932
+35.3988618850708 -154.79356098175
+34.0062980651855 19.9242215156555
+46.3918724060059 -155.247426509857
+44.9993085861206 19.4703559875488
+102.153307914734 -156.054169654846
+100.760744094849 18.6636128425598
+113.146318435669 -156.508035182953
+111.753754615784 18.2097473144531
+80.3630886077881 -154.677974224091
+78.9705247879028 20.0398082733154
+91.3560991287231 -155.131839752197
+89.9635353088379 19.5859427452087
+146.122078895569 -155.51361989975
+144.729515075684 19.2041625976563
+157.115089416504 -155.967485427856
+155.722525596619 18.7502970695496
+124.331859588623 -154.137424468994
+122.939295768738 20.5803580284119
+135.324870109558 -154.591289997101
+133.932306289673 20.1264925003052
+191.530947685242 -156.817087173462
+190.138383865356 17.9006953239441
+202.523958206177 -157.270952701569
+201.131394386292 17.4468297958374
+169.740728378296 -155.440891742706
+168.348164558411 19.2768907546997
+180.733738899231 -155.894757270813
+179.341175079346 18.823025226593
+235.499718666077 -156.276537418365
+234.107154846191 18.4412450790405
+246.492729187012 -156.730402946472
+245.100165367126 17.9873795509338
+213.709499359131 -154.90034198761
+212.316935539246 19.8174405097961
+224.702509880066 -155.354207515717
+223.309946060181 19.3635749816895
+280.463945388794 -156.160950660706
+279.071381568909 18.5568318367004
+291.456955909729 -156.614816188812
+290.064392089844 18.1029663085938
+258.673726081848 -154.78475522995
+257.281162261963 19.9330272674561
+269.666736602783 -155.238620758057
+268.274172782898 19.4791617393494
+324.432716369629 -155.620400905609
+323.040152549744 19.0973815917969
+335.425726890564 -156.074266433716
+334.033163070679 18.6435160636902
+302.642497062683 -154.244205474854
+301.249933242798 20.4735770225525
+313.635507583618 -154.69807100296
+312.242943763733 20.0197114944458
+18.7747859954834 -335.053844928741
+16.3822221755981 -160.336062431335
+29.7677965164185 -335.507710456848
+27.3752326965332 -160.789927959442
+-3.0154333114624 -333.677649497986
+-5.40799713134766 -158.95986700058
+7.97757720947266 -334.131515026093
+5.5850133895874 -159.413732528687
+62.7435569763184 -334.513295173645
+60.3509931564331 -159.795512676239
+73.7365674972534 -334.967160701752
+71.3440036773682 -160.249378204346
+40.9533376693726 -333.137099742889
+38.5607738494873 -158.419317245483
+51.9463481903076 -333.590965270996
+49.5537843704224 -158.87318277359
+107.707783699036 -334.397708415985
+105.31521987915 -159.679925918579
+118.700794219971 -334.851573944092
+116.308230400085 -160.133791446686
+85.9175643920898 -333.021512985229
+83.5250005722046 -158.303730487823
+96.9105749130249 -333.475378513336
+94.5180110931396 -158.75759601593
+151.676554679871 -333.857158660889
+149.283990859985 -159.139376163483
+163.669565200806 -334.311024188995
+162.27700138092 -159.593241691589
+162.669565200806 -334.311024188995
+160.27700138092 -159.593241691589
+129.886335372925 -332.480963230133
+127.49377155304 -157.763180732727
+140.87934589386 -332.93482875824
+138.486782073975 -158.217046260834
+197.085423469543 -335.160625934601
+194.692859649658 -160.442843437195
+208.078433990479 -335.614491462708
+205.685870170593 -160.896708965302
+175.295204162598 -333.784430503845
+172.902640342712 -159.066648006439
+186.288214683533 -334.238296031952
+183.895650863647 -159.520513534546
+241.054194450378 -334.620076179504
+238.661630630493 -159.902293682098
+252.047204971313 -335.073941707611
+249.654641151428 -160.356159210205
+219.263975143433 -333.243880748749
+216.871411323547 -158.526098251343
+230.256985664368 -333.697746276855
+227.864421844482 -158.979963779449
+286.018421173096 -334.504489421844
+283.62585735321 -159.786706924438
+297.011431694031 -334.958354949951
+294.618867874146 -160.240572452545
+264.22820186615 -333.128293991089
+261.835638046265 -158.410511493683
+275.221212387085 -333.582159519196
+272.8286485672 -158.86437702179
+329.987192153931 -333.963939666748
+327.594628334045 -159.246157169342
+340.980202674866 -334.417805194855
+338.58763885498 -159.700022697449
+308.196972846985 -332.587744235992
+305.8044090271 -157.869961738586
+319.18998336792 -333.041609764099
+316.797419548035 -158.323827266693
+368.390019416809 -158.181654453278
+366.997455596924 16.5361280441284
+379.383029937744 -158.635519981384
+377.990466117859 16.0822625160217
+346.599800109863 -156.805459022522
+345.207236289978 17.912323474884
+357.592810630798 -157.259324550629
+356.200246810913 17.4584579467773
+412.358790397644 -157.641104698181
+410.966226577759 17.0766777992249
+423.351800918579 -158.094970226288
+421.959237098694 16.6228122711182
+390.568571090698 -156.264909267426
+389.176007270813 18.4528732299805
+401.561581611633 -156.718774795532
+400.169017791748 17.9990077018738
+457.323017120361 -157.525517940521
+455.930453300476 17.1922645568848
+468.316027641296 -157.979383468628
+466.923463821411 16.7383990287781
+435.532797813416 -156.149322509766
+434.14023399353 18.5684599876404
+446.525808334351 -156.603188037872
+445.133244514465 18.1145944595337
+501.291788101196 -156.984968185425
+499.899224281311 17.7328143119812
+512.284798622131 -157.438833713531
+510.892234802246 17.2789487838745
+479.50156879425 -155.608772754669
+478.109004974365 19.1090097427368
+490.494579315186 -156.062638282776
+489.1020154953 18.6551442146301
+546.700656890869 -158.288435459137
+545.308093070984 16.429347038269
+557.693667411804 -158.742300987244
+556.301103591919 15.9754815101624
+524.910437583923 -156.912240028381
+523.517873764038 17.8055424690247
+535.903448104858 -157.366105556488
+534.510884284973 17.351676940918
+590.669427871704 -157.747885704041
+589.276864051819 16.9698967933655
+601.662438392639 -158.201751232147
+600.269874572754 16.5160312652588
+568.879208564758 -156.371690273285
+567.486644744873 18.3460922241211
+579.872219085693 -156.825555801392
+578.479655265808 17.8922266960144
+635.633654594421 -157.632298946381
+634.241090774536 17.0854835510254
+646.626665115356 -158.086164474487
+645.234101295471 16.6316180229187
+613.843435287476 -156.256103515625
+612.45087146759 18.461678981781
+624.836445808411 -156.709969043732
+623.443881988525 18.0078134536743
+679.602425575256 -157.091749191284
+678.209861755371 17.6260333061218
+690.595436096191 -157.545614719391
+689.202872276306 17.1721677780151
+657.812206268311 -155.715553760529
+656.419642448425 19.0022287368774
+668.805216789246 -156.169419288635
+667.41265296936 18.5483632087708
+373.944495201111 -336.525193214417
+371.551931381226 -161.80741071701
+384.937505722046 -336.979058742523
+382.544941902161 -162.261276245117
+352.154275894165 -335.148997783661
+349.76171207428 -160.431215286255
+363.1472864151 -335.602863311768
+360.754722595215 -160.885080814362
+417.913266181946 -335.98464345932
+415.520702362061 -161.266860961914
+428.906276702881 -336.438508987427
+426.513712882996 -161.720726490021
+396.123046875 -334.608448028564
+393.730483055115 -159.890665531158
+407.116057395935 -335.062313556671
+404.72349357605 -160.344531059265
+462.877492904663 -335.86905670166
+460.484929084778 -161.151274204254
+473.870503425598 -336.322922229767
+471.477939605713 -161.605139732361
+441.087273597717 -334.492861270905
+438.694709777832 -159.775078773499
+452.080284118652 -334.946726799011
+449.687720298767 -160.228944301605
+506.846263885498 -335.328506946564
+504.453700065613 -160.610724449158
+518.839274406433 -335.78237247467
+517.446710586548 -161.064589977264
+517.839274406433 -335.78237247467
+515.446710586548 -161.064589977264
+485.056044578552 -333.952311515808
+482.663480758667 -159.234529018402
+496.049055099487 -334.406177043915
+493.656491279602 -159.688394546509
+552.255132675171 -336.631974220276
+549.862568855286 -161.91419172287
+563.248143196106 -337.085839748383
+560.855579376221 -162.368057250977
+530.464913368225 -335.25577878952
+528.07234954834 -160.537996292114
+541.45792388916 -335.709644317627
+539.065360069275 -160.991861820221
+596.223903656006 -336.091424465179
+593.831339836121 -161.373641967773
+607.216914176941 -336.545289993286
+604.824350357056 -161.82750749588
+574.43368434906 -334.715229034424
+572.041120529175 -159.997446537018
+585.426694869995 -335.169094562531
+583.03413105011 -160.451312065125
+641.188130378723 -335.97583770752
+638.795566558838 -161.258055210114
+652.181140899658 -336.429703235626
+649.788577079773 -161.71192073822
+619.397911071777 -334.599642276764
+617.005347251892 -159.881859779358
+630.390921592712 -335.053507804871
+627.998357772827 -160.335725307465
+685.156901359558 -335.435287952423
+682.764337539673 -160.717505455017
+697.149911880493 -335.88915348053
+695.757348060608 -161.171370983124
+696.149911880493 -335.88915348053
+693.757348060608 -161.171370983124
+663.366682052612 -334.059092521667
+660.974118232727 -159.341310024261
+674.359692573547 -334.512958049774
+671.967128753662 -159.795175552368
+8.22179889678955 -112.695920467377
+7.82774639129639 18.0074763298035
+19.2148094177246 -113.149785995483
+18.8207569122314 17.5536108016968
+-13.5684204101563 -111.319725036621
+-13.9624729156494 19.3836717605591
+-2.57540988922119 -111.773590564728
+-2.96946239471436 18.9298062324524
+52.1905698776245 -112.15537071228
+51.7965173721313 18.5480260848999
+63.1835803985596 -112.609236240387
+62.7895278930664 18.0941605567932
+30.4003505706787 -110.779175281525
+30.0062980651855 19.9242215156555
+41.3933610916138 -111.233040809631
+40.9993085861206 19.4703559875488
+97.1547966003418 -112.03978395462
+96.7607440948486 18.6636128425598
+108.147807121277 -112.493649482727
+107.753754615784 18.2097473144531
+75.364577293396 -110.663588523865
+74.9705247879028 20.0398082733154
+86.3575878143311 -111.117454051971
+85.9635353088379 19.5859427452087
+141.123567581177 -111.499234199524
+140.729515075684 19.2041625976563
+152.116578102112 -111.953099727631
+151.722525596619 18.7502970695496
+119.333348274231 -110.123038768768
+118.939295768738 20.5803580284119
+130.326358795166 -110.576904296875
+129.932306289673 20.1264925003052
+186.53243637085 -112.802701473236
+186.138383865356 17.9006953239441
+197.525446891785 -113.256567001343
+197.131394386292 17.4468297958374
+164.742217063904 -111.42650604248
+164.348164558411 19.2768907546997
+175.735227584839 -111.880371570587
+175.341175079346 18.823025226593
+230.501207351685 -112.26215171814
+230.107154846191 18.4412450790405
+241.49421787262 -112.716017246246
+241.100165367126 17.9873795509338
+208.710988044739 -110.885956287384
+208.316935539246 19.8174405097961
+219.703998565674 -111.339821815491
+219.309946060181 19.3635749816895
+275.465434074402 -112.14656496048
+275.071381568909 18.5568318367004
+286.458444595337 -112.600430488586
+286.064392089844 18.1029663085938
+253.675214767456 -110.770369529724
+253.281162261963 19.9330272674561
+264.668225288391 -111.224235057831
+264.274172782898 19.4791617393494
+319.434205055237 -111.606015205383
+319.040152549744 19.0973815917969
+330.427215576172 -112.05988073349
+330.033163070679 18.6435160636902
+297.643985748291 -110.229819774628
+297.249933242798 20.4735770225525
+308.636996269226 -110.683685302734
+308.242943763733 20.0197114944458
+22.7747859954834 -335.053844928741
+22.3737440109253 -204.804313659668
+33.7677965164185 -335.507710456848
+33.5592851638794 -202.433702945709
+0.984566688537598 -333.677649497986
+0.583524703979492 -203.428118228912
+11.9775772094727 -334.131515026093
+11.3807334899902 -204.350448131561
+66.7435569763184 -334.513295173645
+66.3425149917603 -204.263763904572
+75.7365674972534 -334.967160701752
+75.3425149917603 -204.263763904572
+44.9533376693726 -333.137099742889
+44.5522956848145 -202.887568473816
+55.9463481903076 -333.590965270996
+55.3495044708252 -203.809898376465
+111.707783699036 -334.397708415985
+111.306741714478 -204.148177146912
+122.700794219971 -334.851573944092
+122.492282867432 -201.777566432953
+89.9175643920898 -333.021512985229
+89.5165224075317 -202.771981716156
+100.910574913025 -333.475378513336
+100.313731193542 -203.694311618805
+155.676554679871 -333.857158660889
+155.275512695313 -203.607627391815
+164.669565200806 -334.311024188995
+164.275512695313 -203.607627391815
+133.886335372925 -332.480963230133
+133.485293388367 -202.23143196106
+144.87934589386 -332.93482875824
+144.282502174377 -203.153761863708
+201.085423469543 -335.160625934601
+200.684381484985 -204.911094665527
+212.078433990479 -335.614491462708
+211.869922637939 -202.540483951569
+179.295204162598 -333.784430503845
+178.89416217804 -203.534899234772
+190.288214683533 -334.238296031952
+189.69137096405 -204.457229137421
+245.054194450378 -334.620076179504
+244.65315246582 -204.370544910431
+254.047204971313 -335.073941707611
+253.65315246582 -204.370544910431
+223.263975143433 -333.243880748749
+222.862933158875 -202.994349479675
+234.256985664368 -333.697746276855
+233.660141944885 -203.916679382324
+290.018421173096 -334.504489421844
+289.617379188538 -204.254958152771
+301.011431694031 -334.958354949951
+300.802920341492 -201.884347438812
+268.22820186615 -333.128293991089
+267.827159881592 -202.878762722015
+279.221212387085 -333.582159519196
+278.624368667603 -203.801092624664
+333.987192153931 -333.963939666748
+333.586150169373 -203.714408397675
+342.980202674866 -334.417805194855
+342.586150169373 -203.714408397675
+312.196972846985 -332.587744235992
+311.795930862427 -202.338212966919
+323.18998336792 -333.041609764099
+322.593139648438 -203.260542869568
+363.391508102417 -114.167268753052
+362.997455596924 16.5361280441284
+374.384518623352 -114.621134281158
+373.990466117859 16.0822625160217
+341.601288795471 -112.791073322296
+341.207236289978 17.912323474884
+352.594299316406 -113.244938850403
+352.200246810913 17.4584579467773
+407.360279083252 -113.626718997955
+406.966226577759 17.0766777992249
+418.353289604187 -114.080584526062
+417.959237098694 16.6228122711182
+385.570059776306 -112.2505235672
+385.176007270813 18.4528732299805
+396.563070297241 -112.704389095306
+396.169017791748 17.9990077018738
+452.324505805969 -113.511132240295
+451.930453300476 17.1922645568848
+463.317516326904 -113.964997768402
+462.923463821411 16.7383990287781
+430.534286499023 -112.13493680954
+430.14023399353 18.5684599876404
+441.527297019959 -112.588802337646
+441.133244514465 18.1145944595337
+496.293276786804 -112.970582485199
+495.899224281311 17.7328143119812
+507.286287307739 -113.424448013306
+506.892234802246 17.2789487838745
+474.503057479858 -111.594387054443
+474.109004974365 19.1090097427368
+485.496068000793 -112.04825258255
+485.1020154953 18.6551442146301
+541.702145576477 -114.274049758911
+541.308093070984 16.429347038269
+552.695156097412 -114.727915287018
+552.301103591919 15.9754815101624
+519.911926269531 -112.897854328156
+519.517873764038 17.8055424690247
+530.904936790466 -113.351719856262
+530.510884284973 17.351676940918
+585.670916557312 -113.733500003815
+585.276864051819 16.9698967933655
+596.663927078247 -114.187365531921
+596.269874572754 16.5160312652588
+563.880697250366 -112.357304573059
+563.486644744873 18.3460922241211
+574.873707771301 -112.811170101166
+574.479655265808 17.8922266960144
+630.635143280029 -113.617913246155
+630.241090774536 17.0854835510254
+641.628153800964 -114.071778774261
+641.234101295471 16.6316180229187
+608.844923973084 -112.241717815399
+608.45087146759 18.461678981781
+619.837934494019 -112.695583343506
+619.443881988525 18.0078134536743
+674.603914260864 -113.077363491058
+674.209861755371 17.6260333061218
+685.596924781799 -113.531229019165
+685.202872276306 17.1721677780151
+652.813694953918 -111.701168060303
+652.419642448425 19.0022287368774
+663.806705474854 -112.155033588409
+663.41265296936 18.5483632087708
+377.944495201111 -336.525193214417
+377.543453216553 -206.275661945343
+388.937505722046 -336.979058742523
+388.728994369507 -203.905051231384
+356.154275894165 -335.148997783661
+355.753233909607 -204.899466514587
+367.1472864151 -335.602863311768
+366.550442695618 -205.821796417236
+421.913266181946 -335.98464345932
+421.512224197388 -205.735112190247
+430.906276702881 -336.438508987427
+430.512224197388 -205.735112190247
+400.123046875 -334.608448028564
+399.722004890442 -204.358916759491
+411.116057395935 -335.062313556671
+410.519213676453 -205.28124666214
+466.877492904663 -335.86905670166
+466.476450920105 -205.619525432587
+477.870503425598 -336.322922229767
+477.661992073059 -203.248914718628
+445.087273597717 -334.492861270905
+444.686231613159 -204.243330001831
+456.080284118652 -334.946726799011
+455.48344039917 -205.16565990448
+510.846263885498 -335.328506946564
+510.44522190094 -205.07897567749
+519.839274406433 -335.78237247467
+519.44522190094 -205.07897567749
+489.056044578552 -333.952311515808
+488.655002593994 -203.702780246735
+500.049055099487 -334.406177043915
+499.452211380005 -204.625110149384
+556.255132675171 -336.631974220276
+555.854090690613 -206.382442951202
+567.248143196106 -337.085839748383
+567.039631843567 -204.011832237244
+534.464913368225 -335.25577878952
+534.063871383667 -205.006247520447
+545.45792388916 -335.709644317627
+544.861080169678 -205.928577423096
+600.223903656006 -336.091424465179
+599.822861671448 -205.841893196106
+609.216914176941 -336.545289993286
+608.822861671448 -205.841893196106
+578.43368434906 -334.715229034424
+578.032642364502 -204.46569776535
+589.426694869995 -335.169094562531
+588.829851150513 -205.388027667999
+645.188130378723 -335.97583770752
+644.787088394165 -205.726306438446
+656.181140899658 -336.429703235626
+655.972629547119 -203.355695724487
+623.397911071777 -334.599642276764
+622.996869087219 -204.35011100769
+634.390921592712 -335.053507804871
+633.79407787323 -205.272440910339
+689.156901359558 -335.435287952423
+688.755859375 -205.18575668335
+698.149911880493 -335.88915348053
+697.755859375 -205.18575668335
+667.366682052612 -334.059092521667
+666.965640068054 -203.809561252594
+678.359692573547 -334.512958049774
+677.762848854065 -204.731891155243
+-16.5684204101563 -111.319725036621
+-16.9639616012573 -24.6307139396667
+72.364577293396 -110.663588523865
+71.9690361022949 -23.9745774269104
+161.742217063904 -111.42650604248
+161.346675872803 -24.7374949455261
+250.675214767456 -110.770369529724
+250.279673576355 -24.0813584327698
+-10.0139446258545 -289.66326379776
+-10.4094858169556 -202.974252700806
+78.9190530776978 -289.007127285004
+78.5235118865967 -202.318116188049
+168.296692848206 -289.770044803619
+167.901151657104 -203.081033706665
+257.229690551758 -289.113908290863
+256.834149360657 -202.424897193909
+338.601288795471 -112.791073322296
+338.20574760437 -26.1020622253418
+427.534286499023 -112.13493680954
+427.138745307922 -25.4459257125854
+516.911926269531 -112.897854328156
+516.51638507843 -26.2088432312012
+605.844923973084 -112.241717815399
+605.449382781982 -25.5527067184448
+345.155764579773 -291.134612083435
+344.760223388672 -204.445600986481
+434.088762283325 -290.478475570679
+433.693221092224 -203.789464473724
+523.466402053833 -291.241393089294
+523.070860862732 -204.55238199234
+612.399399757385 -290.585256576538
+612.003858566284 -203.896245479584
+6.37285995483398 -23.3064160346985
+5.82774639129639 18.0074763298035
+17.365870475769 -23.7602815628052
+16.8207569122314 17.5536108016968
+-18.4173593521118 -21.9302206039429
+-18.9624729156494 19.3836717605591
+-15.4173593521118 -21.9302206039429
+-15.9624729156494 19.3836717605591
+-4.42434883117676 -22.3840861320496
+-4.96946239471436 18.9298062324524
+50.3416309356689 -22.7658662796021
+49.7965173721313 18.5480260848999
+61.334641456604 -23.2197318077087
+60.7895278930664 18.0941605567932
+28.5514116287231 -21.3896708488464
+28.0062980651855 19.9242215156555
+39.5444221496582 -21.8435363769531
+38.9993085861206 19.4703559875488
+95.3058576583862 -22.6502795219421
+94.7607440948486 18.6636128425598
+106.298868179321 -23.1041450500488
+105.753754615784 18.2097473144531
+70.5156383514404 -21.2740840911865
+69.9705247879028 20.0398082733154
+73.5156383514404 -21.2740840911865
+72.9705247879028 20.0398082733154
+84.5086488723755 -21.7279496192932
+83.9635353088379 19.5859427452087
+139.274628639221 -22.1097297668457
+138.729515075684 19.2041625976563
+150.267639160156 -22.5635952949524
+149.722525596619 18.7502970695496
+117.484409332275 -20.7335343360901
+116.939295768738 20.5803580284119
+128.47741985321 -21.1873998641968
+127.932306289673 20.1264925003052
+-14.5699090957642 -155.334110736847
+-15.1150226593018 -114.020218372345
+184.683497428894 -23.4131970405579
+184.138383865356 17.9006953239441
+195.676507949829 -23.8670625686646
+195.131394386292 17.4468297958374
+159.893278121948 -22.0370016098022
+159.348164558411 19.2768907546997
+162.893278121948 -22.0370016098022
+162.348164558411 19.2768907546997
+173.886288642883 -22.4908671379089
+173.341175079346 18.823025226593
+228.652268409729 -22.8726472854614
+228.107154846191 18.4412450790405
+239.645278930664 -23.3265128135681
+239.100165367126 17.9873795509338
+206.862049102783 -21.4964518547058
+206.316935539246 19.8174405097961
+217.855059623718 -21.9503173828125
+217.309946060181 19.3635749816895
+273.616495132446 -22.7570605278015
+273.071381568909 18.5568318367004
+284.609505653381 -23.2109260559082
+284.064392089844 18.1029663085938
+248.8262758255 -21.3808650970459
+248.281162261963 19.9330272674561
+251.8262758255 -21.3808650970459
+251.281162261963 19.9330272674561
+262.819286346436 -21.8347306251526
+262.274172782898 19.4791617393494
+317.585266113281 -22.2165107727051
+317.040152549744 19.0973815917969
+328.578276634216 -22.6703763008118
+328.033163070679 18.6435160636902
+295.795046806335 -20.8403153419495
+295.249933242798 20.4735770225525
+306.788057327271 -21.2941808700562
+306.242943763733 20.0197114944458
+163.740728378296 -155.440891742706
+163.195614814758 -114.126999378204
+13.7747859954834 -335.053844928741
+13.2296724319458 -293.73995256424
+24.7677965164185 -335.507710456848
+24.2226829528809 -294.193818092346
+-8.0154333114624 -333.677649497986
+-8.560546875 -292.363757133484
+2.97757720947266 -334.131515026093
+2.43246364593506 -292.817622661591
+57.7435569763184 -334.513295173645
+57.1984434127808 -293.199402809143
+68.7365674972534 -334.967160701752
+68.1914539337158 -293.65326833725
+77.7365674972534 -334.967160701752
+77.1914539337158 -293.65326833725
+35.9533376693726 -333.137099742889
+35.408224105835 -291.823207378387
+46.9463481903076 -333.590965270996
+46.40123462677 -292.277072906494
+102.707783699036 -334.397708415985
+102.162670135498 -293.083816051483
+113.700794219971 -334.851573944092
+113.155680656433 -293.53768157959
+80.9175643920898 -333.021512985229
+80.3724508285522 -291.707620620728
+91.9105749130249 -333.475378513336
+91.3654613494873 -292.161486148834
+146.676554679871 -333.857158660889
+146.131441116333 -292.543266296387
+157.669565200806 -334.311024188995
+157.124451637268 -292.997131824493
+166.669565200806 -334.311024188995
+166.124451637268 -292.997131824493
+124.886335372925 -332.480963230133
+124.341221809387 -291.167070865631
+135.87934589386 -332.93482875824
+135.334232330322 -291.620936393738
+192.085423469543 -335.160625934601
+191.540309906006 -293.846733570099
+203.078433990479 -335.614491462708
+202.533320426941 -294.300599098206
+170.295204162598 -333.784430503845
+169.75009059906 -292.470538139343
+181.288214683533 -334.238296031952
+180.743101119995 -292.92440366745
+236.054194450378 -334.620076179504
+235.509080886841 -293.306183815002
+247.047204971313 -335.073941707611
+246.502091407776 -293.760049343109
+256.047204971313 -335.073941707611
+255.502091407776 -293.760049343109
+214.263975143433 -333.243880748749
+213.718861579895 -291.929988384247
+225.256985664368 -333.697746276855
+224.71187210083 -292.383853912354
+281.018421173096 -334.504489421844
+280.473307609558 -293.190597057343
+292.011431694031 -334.958354949951
+291.466318130493 -293.644462585449
+259.22820186615 -333.128293991089
+258.683088302612 -291.814401626587
+270.221212387085 -333.582159519196
+269.676098823547 -292.268267154694
+324.987192153931 -333.963939666748
+324.442078590393 -292.650047302246
+335.980202674866 -334.417805194855
+335.435089111328 -293.103912830353
+344.980202674866 -334.417805194855
+344.435089111328 -293.103912830353
+303.196972846985 -332.587744235992
+302.651859283447 -291.27385187149
+314.18998336792 -333.041609764099
+313.644869804382 -291.727717399597
+361.542569160461 -24.7777643203735
+360.997455596924 16.5361280441284
+372.535579681396 -25.2316298484802
+371.990466117859 16.0822625160217
+336.752349853516 -23.4015688896179
+336.207236289978 17.912323474884
+339.752349853516 -23.4015688896179
+339.207236289978 17.912323474884
+350.745360374451 -23.8554344177246
+350.200246810913 17.4584579467773
+405.511340141296 -24.2372145652771
+404.966226577759 17.0766777992249
+416.504350662231 -24.6910800933838
+415.959237098694 16.6228122711182
+383.721120834351 -22.8610191345215
+383.176007270813 18.4528732299805
+394.714131355286 -23.3148846626282
+394.169017791748 17.9990077018738
+450.475566864014 -24.1216278076172
+449.930453300476 17.1922645568848
+461.468577384949 -24.5754933357239
+460.923463821411 16.7383990287781
+425.685347557068 -22.7454323768616
+425.14023399353 18.5684599876404
+428.685347557068 -22.7454323768616
+428.14023399353 18.5684599876404
+439.678358078003 -23.1992979049683
+439.133244514465 18.1145944595337
+494.444337844849 -23.5810780525208
+493.899224281311 17.7328143119812
+505.437348365784 -24.0349435806274
+504.892234802246 17.2789487838745
+472.654118537903 -22.2048826217651
+472.109004974365 19.1090097427368
+483.647129058838 -22.6587481498718
+483.1020154953 18.6551442146301
+539.853206634521 -24.8845453262329
+539.308093070984 16.429347038269
+550.846217155457 -25.3384108543396
+550.301103591919 15.9754815101624
+515.062987327576 -23.5083498954773
+514.517873764038 17.8055424690247
+518.062987327576 -23.5083498954773
+517.517873764038 17.8055424690247
+529.055997848511 -23.962215423584
+528.510884284973 17.351676940918
+583.821977615356 -24.3439955711365
+583.276864051819 16.9698967933655
+594.814988136292 -24.7978610992432
+594.269874572754 16.5160312652588
+562.031758308411 -22.9678001403809
+561.486644744873 18.3460922241211
+573.024768829346 -23.4216656684875
+572.479655265808 17.8922266960144
+628.786204338074 -24.2284088134766
+628.241090774536 17.0854835510254
+639.779214859009 -24.6822743415833
+639.234101295471 16.6316180229187
+603.995985031128 -22.8522133827209
+603.45087146759 18.461678981781
+606.995985031128 -22.8522133827209
+606.45087146759 18.461678981781
+617.988995552063 -23.3060789108276
+617.443881988525 18.0078134536743
+672.754975318909 -23.6878590583801
+672.209861755371 17.6260333061218
+683.747985839844 -24.1417245864868
+683.202872276306 17.1721677780151
+650.964756011963 -22.3116636276245
+650.419642448425 19.0022287368774
+661.957766532898 -22.7655291557312
+661.41265296936 18.5483632087708
+518.910437583923 -156.912240028381
+518.365324020386 -115.598347663879
+368.944495201111 -336.525193214417
+368.399381637573 -295.211300849915
+379.937505722046 -336.979058742523
+379.392392158508 -295.665166378021
+347.154275894165 -335.148997783661
+346.609162330627 -293.835105419159
+358.1472864151 -335.602863311768
+357.602172851563 -294.288970947266
+412.913266181946 -335.98464345932
+412.368152618408 -294.670751094818
+423.906276702881 -336.438508987427
+423.361163139343 -295.124616622925
+432.906276702881 -336.438508987427
+432.361163139343 -295.124616622925
+391.123046875 -334.608448028564
+390.577933311462 -293.294555664063
+402.116057395935 -335.062313556671
+401.570943832397 -293.748421192169
+457.877492904663 -335.86905670166
+457.332379341125 -294.555164337158
+468.870503425598 -336.322922229767
+468.325389862061 -295.009029865265
+436.087273597717 -334.492861270905
+435.54216003418 -293.178968906403
+447.080284118652 -334.946726799011
+446.535170555115 -293.632834434509
+501.846263885498 -335.328506946564
+501.30115032196 -294.014614582062
+512.839274406433 -335.78237247467
+512.294160842896 -294.468480110168
+521.839274406433 -335.78237247467
+521.294160842896 -294.468480110168
+480.056044578552 -333.952311515808
+479.510931015015 -292.638419151306
+491.049055099487 -334.406177043915
+490.50394153595 -293.092284679413
+547.255132675171 -336.631974220276
+546.710019111633 -295.318081855774
+558.248143196106 -337.085839748383
+557.703029632568 -295.771947383881
+525.464913368225 -335.25577878952
+524.919799804688 -293.941886425018
+536.45792388916 -335.709644317627
+535.912810325623 -294.395751953125
+591.223903656006 -336.091424465179
+590.678790092468 -294.777532100677
+602.216914176941 -336.545289993286
+601.671800613403 -295.231397628784
+611.216914176941 -336.545289993286
+610.671800613403 -295.231397628784
+569.43368434906 -334.715229034424
+568.888570785522 -293.401336669922
+580.426694869995 -335.169094562531
+579.881581306458 -293.855202198029
+636.188130378723 -335.97583770752
+635.643016815186 -294.661945343018
+647.181140899658 -336.429703235626
+646.636027336121 -295.115810871124
+614.397911071777 -334.599642276764
+613.85279750824 -293.285749912262
+625.390921592712 -335.053507804871
+624.845808029175 -293.739615440369
+680.156901359558 -335.435287952423
+679.611787796021 -294.121395587921
+691.149911880493 -335.88915348053
+690.604798316956 -294.575261116028
+700.149911880493 -335.88915348053
+699.604798316956 -294.575261116028
+658.366682052612 -334.059092521667
+657.821568489075 -292.745200157166
+669.359692573547 -334.512958049774
+668.81457901001 -293.199065685272
+12
+19.7747859954834 -335.053844928741
+17.6682071685791 -115.850279331207
+18.7747859954834 -335.053844928741
+16.3822221755981 -160.336062431335
+22.7747859954834 -335.053844928741
+22.3737440109253 -204.804313659668
+16.7747859954834 -335.053844928741
+15.8192682266235 -26.4607748985291
+24.7677965164185 -335.507710456848
+24.2226829528809 -294.193818092346
+24.7677965164185 -335.507710456848
+24.2226829528809 -294.193818092346
+20.7747859954834 -335.053844928741