kinow commented on a change in pull request #194:
URL: https://github.com/apache/commons-geometry/pull/194#discussion_r825373065



##########
File path: 
commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/PointMap3DImpl.java
##########
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.commons.geometry.euclidean;
+
+import java.util.List;
+
+import org.apache.commons.geometry.core.collection.PointMap;
+import org.apache.commons.geometry.core.internal.AbstractBucketPointMap;
+import org.apache.commons.geometry.euclidean.threed.Vector3D;
+import org.apache.commons.numbers.core.Precision;
+
+/** Internal {@link PointMap3D} implementation.
+ * @param <V> Map value type
+ */
+final class PointMap3DImpl<V>
+    extends AbstractBucketPointMap<Vector3D, V>
+    implements PointMap<Vector3D, V> {
+
+    /** Number of children per node. */
+    private static final int NODE_CHILD_COUNT = 8;
+
+    /** Max entries per node. This value was determined empirically was chosen 
to
+     * provide a balance between having a small number of entries in each node 
when
+     * searching and having a large number of samples to provide a good split 
point
+     * during insertion.

Review comment:
       Maybe link to the JMH tests, or just point the reader to the JIRA number 
(I remember reading the comments where you detailed your findings trying 
different algorithms.) This could save someone curious about this statement 
some time, I think.

##########
File path: 
commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/PointMapAsSetAdapter.java
##########
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.commons.geometry.core.internal;
+
+import java.util.AbstractSet;
+import java.util.Iterator;
+
+import org.apache.commons.geometry.core.Point;
+import org.apache.commons.geometry.core.collection.PointMap;
+import org.apache.commons.geometry.core.collection.PointSet;
+
+/** Class that exposes a {@link PointMap} as a {@link PointSet}.
+ * @param <P> Point type
+ * @param <M> Map type
+ */
+public class PointMapAsSetAdapter<P extends Point<P>, M extends PointMap<P, 
Object>>
+    extends AbstractSet<P>
+    implements PointSet<P> {
+
+    /** Dummy map value used to indicate presence in the set. */
+    private static final Object PRESENT = new Object();
+
+    /** Backing map. */
+    private final M map;
+
+    /** Construct a new instance that use the argument as its backing map.
+     * @param backingMap backing map
+     */
+    public PointMapAsSetAdapter(final M backingMap) {
+        this.map = backingMap;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public P resolve(final P pt) {
+        return map.resolveKey(pt);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Iterator<P> iterator() {
+        return map.keySet().iterator();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int size() {
+        return map.size();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean contains(final Object obj) {
+        return map.containsKey(obj);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean add(final P pt) {
+        return map.put(pt, PRESENT) == null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean remove(final Object obj) {
+        final Object prev = map.remove(obj);
+        return GeometryInternalUtils.sameInstance(prev, PRESENT);

Review comment:
       BTW, I've created a gist with the test files I'm using (I had older 
files on my working copy, that I accidentally deleted after re-syncing the 
branches :sweat_smile:, so saving it there in case I'm on another computer or 
deleted them again ): 
https://gist.github.com/kinow/c590789efba00d4449eb1f5e5a7047ec

##########
File path: 
commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/PointMapAsSetAdapter.java
##########
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.commons.geometry.core.internal;
+
+import java.util.AbstractSet;
+import java.util.Iterator;
+
+import org.apache.commons.geometry.core.Point;
+import org.apache.commons.geometry.core.collection.PointMap;
+import org.apache.commons.geometry.core.collection.PointSet;
+
+/** Class that exposes a {@link PointMap} as a {@link PointSet}.
+ * @param <P> Point type
+ * @param <M> Map type
+ */
+public class PointMapAsSetAdapter<P extends Point<P>, M extends PointMap<P, 
Object>>
+    extends AbstractSet<P>
+    implements PointSet<P> {
+
+    /** Dummy map value used to indicate presence in the set. */
+    private static final Object PRESENT = new Object();
+
+    /** Backing map. */
+    private final M map;
+
+    /** Construct a new instance that use the argument as its backing map.
+     * @param backingMap backing map
+     */
+    public PointMapAsSetAdapter(final M backingMap) {
+        this.map = backingMap;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public P resolve(final P pt) {
+        return map.resolveKey(pt);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Iterator<P> iterator() {
+        return map.keySet().iterator();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int size() {
+        return map.size();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean contains(final Object obj) {
+        return map.containsKey(obj);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean add(final P pt) {
+        return map.put(pt, PRESENT) == null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean remove(final Object obj) {
+        final Object prev = map.remove(obj);
+        return GeometryInternalUtils.sameInstance(prev, PRESENT);

Review comment:
       @darkma773r I think this can cause some issues, so we should either 
change it or document it for users. For example, if I have
   
   ```java
   Vector1D point = Vector1D.of(10d);
   PointMap<Vector1D, Object> map = 
EuclideanCollections.pointMap1D(Precision.doubleEquivalenceOfEpsilon(0.01d));
   map.put(point, 10_000);
   ```
   
   and create a `set` with this adapter,
   
   ```java
   PointMapAsSetAdapter<Vector1D, PointMap<Vector1D, Object>> fakeSet =
                   new PointMapAsSetAdapter<Vector1D, PointMap<Vector1D, 
Object>>(map);
   ```
   
   then if I try to remove the point that was previously put into the map, this 
method will return `false`, as it only returns `true` — IIUC — for entries 
added using the `add` method of the set created by the adapter (which uses the 
`INSTANCE` member.)
   
   ```
   boolean b = fakeSet.remove(point);
   System.out.println(b); // false
   ```
   
   Finally, if I add the point already used in the map, again to the `set`
   
   ```java
   fakeSet.add(point);
   ```
   
   Now `remove` would return `true`, as the `add` replaces the previously 
created entry. I know the `PointSet` interface documents that `PointSet` is not 
strictly a `Set`, but **I think** we are using `@inheritDocs` here which will 
bring the docs from `Set`? … if so I think we would need to document the 
behavior of these methods now (tedious/boring but I think needed?).

##########
File path: 
commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/PointMap3DImpl.java
##########
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.commons.geometry.euclidean;
+
+import java.util.List;
+
+import org.apache.commons.geometry.core.collection.PointMap;
+import org.apache.commons.geometry.core.internal.AbstractBucketPointMap;
+import org.apache.commons.geometry.euclidean.threed.Vector3D;
+import org.apache.commons.numbers.core.Precision;
+
+/** Internal {@link PointMap3D} implementation.
+ * @param <V> Map value type
+ */
+final class PointMap3DImpl<V>
+    extends AbstractBucketPointMap<Vector3D, V>
+    implements PointMap<Vector3D, V> {
+
+    /** Number of children per node. */
+    private static final int NODE_CHILD_COUNT = 8;
+
+    /** Max entries per node. This value was determined empirically was chosen 
to

Review comment:
       >was determined empirically was chosen
   
   and was chosen?

##########
File path: 
commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/PointMap3DImpl.java
##########
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.commons.geometry.euclidean;
+
+import java.util.List;
+
+import org.apache.commons.geometry.core.collection.PointMap;
+import org.apache.commons.geometry.core.internal.AbstractBucketPointMap;
+import org.apache.commons.geometry.euclidean.threed.Vector3D;
+import org.apache.commons.numbers.core.Precision;
+
+/** Internal {@link PointMap3D} implementation.
+ * @param <V> Map value type
+ */
+final class PointMap3DImpl<V>
+    extends AbstractBucketPointMap<Vector3D, V>
+    implements PointMap<Vector3D, V> {
+
+    /** Number of children per node. */
+    private static final int NODE_CHILD_COUNT = 8;
+
+    /** Max entries per node. This value was determined empirically was chosen 
to
+     * provide a balance between having a small number of entries in each node 
when
+     * searching and having a large number of samples to provide a good split 
point
+     * during insertion.
+     */
+    private static final int MAX_ENTRIES_PER_NODE = 32;
+
+    /** X negative octant flag. */
+    private static final int XNEG = 1 << 5;
+
+    /** X postive octant flag. */
+    private static final int XPOS = 1 << 4;
+
+    /** Y negative octant flag. */
+    private static final int YNEG = 1 << 3;
+
+    /** Y positive octant flag. */
+    private static final int YPOS = 1 << 2;
+
+    /** Z negative octant flag. */
+    private static final int ZNEG = 1 << 1;
+
+    /** Z positive octant flag. */
+    private static final int ZPOS = 1;
+
+    /** Octant location flags for child nodes. */
+    private static final int[] CHILD_LOCATIONS = {
+        XNEG | YNEG | ZNEG,
+        XNEG | YNEG | ZPOS,
+        XNEG | YPOS | ZNEG,
+        XNEG | YPOS | ZPOS,
+
+        XPOS | YNEG | ZNEG,
+        XPOS | YNEG | ZPOS,
+        XPOS | YPOS | ZNEG,
+        XPOS | YPOS | ZPOS
+    };
+
+    /** Construct a new instance using the given precision context to determine
+     * floating point equality.
+     * @param precision precision context
+     */
+    PointMap3DImpl(final Precision.DoubleEquivalence precision) {
+        super(MapNode3D::new,

Review comment:
       Ha! I remember you showing me this trick in that Commons Imaging PR. 
Good to see that again to fixate that pattern on my :brain: :+1: 

##########
File path: 
commons-geometry-examples/examples-jmh/src/main/java/org/apache/commons/geometry/examples/jmh/euclidean/pointmap/RebuildingKDTree.java
##########
@@ -0,0 +1,259 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.commons.geometry.examples.jmh.euclidean.pointmap;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+import org.apache.commons.geometry.euclidean.threed.Vector3D;
+import org.apache.commons.numbers.core.Precision.DoubleEquivalence;
+
+/** {@link KDTree} subclass that rebuilds the tree periodically in an
+ * attempt to restrict the overall height of the tree.
+ * @param <V> Value type
+ */
+public class RebuildingKDTree<V> extends KDTree<V> {
+
+    /** Default rebuild maximum. */
+    private static final int DEFAULT_REBUILD_MAX = 16;
+
+    /** Maximum size of the tree before it is rebuilt. */
+    private int rebuildMax = DEFAULT_REBUILD_MAX;
+
+    /** Minimum size of the tree before it is rebuilt. */
+    private int rebuildMin;
+
+    /** Construct a new instance.
+     * @param precision precision context
+     */
+    public RebuildingKDTree(final DoubleEquivalence precision) {
+        super(precision);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public V put(final Vector3D key, final V value) {
+        final V result = super.put(key, value);
+
+        if (size() >= rebuildMax) {
+            rebuild();
+        }
+
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public V remove(final Object key) {
+        final V result = super.remove(key);
+
+        if (size() <= rebuildMin) {
+            rebuild();
+        }
+
+        return result;
+    }
+
+    /**
+     * Rebuild the tree, attempting to reduce the tree depth.
+     */
+    public void rebuild() {
+        int n = size();
+        if (n > 0) {
+            // construct an array list containing all of the tree nodes
+            final List<KDTreeNode<V>> nodes = collectNodes();
+
+            // rebuild recursively and set the new root
+            final KDTreeNode<V> newRoot = rebuildRecursive(nodes, 0, n, 0);
+
+            setRoot(newRoot);
+        }
+
+        rebuildMax = Math.max(DEFAULT_REBUILD_MAX, 2 * n);
+        rebuildMin = n / 2;
+    }
+
+    /** Get a list containing all nodes in the tree. The node connections are 
all cleared.
+     * @return list containing all nodes in the tree
+     */
+    protected List<KDTreeNode<V>> collectNodes() {
+        final List<KDTreeNode<V>> nodes = new ArrayList<>(size());
+        collectNodesRecursive(getRoot(), nodes);
+
+        return nodes;
+    }
+
+    /** Add nodes in the subtree rooted at {@code curr} to {@code nodes}. The 
node connection
+     * references are all cleared.
+     * @param curr subtree root node
+     * @param nodes node list
+     */
+    protected void collectNodesRecursive(final KDTreeNode<V> curr, final 
List<KDTreeNode<V>> nodes) {
+        if (curr != null) {
+            collectNodesRecursive(curr.getLeft(), nodes);
+            nodes.add(curr);
+            collectNodesRecursive(curr.getRight(), nodes);
+
+            curr.setParent(null);
+            curr.setLeft(null);
+            curr.setRight(null);
+        }
+    }
+
+    /** Recursively rebuild the tree using the specified node sublist.
+     * @param nodes node list
+     * @param startIdx sub list start index (inclusive)
+     * @param endIdx sub list end index (exclusive)
+     * @param depth node depth
+     * @return the root of the subtree containing the nodes between {@code 
startIdx} and {@code endIdx}
+     */
+    protected KDTreeNode<V> rebuildRecursive(final List<KDTreeNode<V>> nodes, 
final int startIdx, final int endIdx,
+            final int depth) {
+        final CutDimension cutDimension = getCutDimensionForDepth(depth);
+
+        final KDTreeNode<V> node;
+        if ((endIdx - startIdx) < 2) {
+            // only a single node here
+            node = nodes.get(startIdx);
+        } else {
+            final int splitIdx = partition(nodes, startIdx, endIdx, 
cutDimension);
+
+            node = nodes.get(splitIdx);
+
+            if (startIdx < splitIdx) {
+                node.setLeft(rebuildRecursive(nodes, startIdx, splitIdx, depth 
+ 1));
+                node.getLeft().setParent(node);
+            }
+
+            if (splitIdx < endIdx - 1) {
+                node.setRight(rebuildRecursive(nodes, splitIdx + 1, endIdx, 
depth + 1));
+                node.getRight().setParent(node);
+            }
+        }
+
+        node.setCutDimension(cutDimension);
+
+        return node;
+    }
+
+    /** Partition the given sublist into values below the median and values 
above. The
+     * index of the median is returned.
+     * @param nodes node list
+     * @param startIdx start index (inclusive) of the sublist to partition
+     * @param endIdx end index (exclusive) of the sublist to partition
+     * @param cutDimension cut dimension
+     * @return index of the sublist median
+     */
+    protected int partition(final List<KDTreeNode<V>> nodes, final int 
startIdx, final int endIdx,
+            final CutDimension cutDimension) {
+        int n = endIdx - startIdx;
+        if (n < 2) {
+            return startIdx;
+        } else if (n == 2) {
+            final int bIdx = endIdx - 1;
+
+            final double a = 
cutDimension.getCoordinate(nodes.get(startIdx).getKey());
+            final double b = 
cutDimension.getCoordinate(nodes.get(bIdx).getKey());
+            if (a <= b) {
+                return startIdx;
+            } else {
+                swap(nodes, startIdx, bIdx);
+                return bIdx;
+            }
+        } else {
+            return findMedianStart(nodes, startIdx, endIdx, cutDimension);
+        }
+    }
+
+    /** Find the starting index of the node median value in the given list. 
The list is
+     * partially sorted and value less than the median come before the 
returned index while
+     * values greater than or equal come after.
+     * @param nodes list of node
+     * @param startIdx sublist start index (inclusive)
+     * @param endIdx sublist end index (exclusive)
+     * @param cutDimension cut dimension
+     * @return index of the median in the specific sublist of {@code nodes} 
along the cut dimension
+     */
+    protected int findMedianStart(final List<KDTreeNode<V>> nodes, final int 
startIdx, final int endIdx,
+            final CutDimension cutDimension) {
+        int k = startIdx + ((endIdx - startIdx) / 2);
+        int low = startIdx;
+        int high = endIdx - 1;
+        int lowTemp;
+        int highTemp;
+        double x;
+        while (low < high) {
+            x = cutDimension.getCoordinate(nodes.get(k).getKey());
+            lowTemp = low;
+            highTemp = high;
+            do {
+                while (cutDimension.getCoordinate(nodes.get(lowTemp).getKey()) 
< x) {
+                    ++lowTemp;
+                }
+                while 
(cutDimension.getCoordinate(nodes.get(highTemp).getKey()) > x) {
+                    --highTemp;
+                }
+
+                if (lowTemp <= highTemp) {
+                    swap(nodes, lowTemp, highTemp);
+
+                    ++lowTemp;
+                    --highTemp;
+                }
+            } while (lowTemp <= highTemp);
+
+            if (k < lowTemp) {
+                // search low part
+                high = highTemp;
+            }
+            if (k > highTemp) {
+                // search high part
+                low = lowTemp;
+            }
+        }
+
+        // back up to the start of the median value
+        x = cutDimension.getCoordinate(nodes.get(k).getKey());
+        while (k > startIdx &&
+                cutDimension.getCoordinate(nodes.get(k - 1).getKey()) == x) {
+            --k;
+        }
+
+        return k;
+    }
+
+    /** Construct a comparator the sorts by the given cut dimension.
+     * @param cutDimension cut dimension to sort by
+     * @return comparator along the cut dimension
+     */
+    protected Comparator<KDTreeNode<V>> comparator(final CutDimension 
cutDimension) {
+        return (a, b) -> 
Double.compare(cutDimension.getCoordinate(a.getKey()), 
cutDimension.getCoordinate(b.getKey()));
+    }
+
+    /** Swap two elements in {@code list}.
+     * @param <T> List element type
+     * @param list input list
+     * @param aIdx index of the first element
+     * @param bIdx index of the second element
+     */
+    private static <T> void swap(final List<T> list, final int aIdx, final int 
bIdx) {
+        final T temp = list.get(aIdx);
+        list.set(aIdx, list.get(bIdx));
+        list.set(bIdx, temp);

Review comment:
       Couldn't we use `Collections.swap(list, aIdx, bIdx);` here?

##########
File path: 
commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/PointMap2SImpl.java
##########
@@ -0,0 +1,181 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.commons.geometry.spherical;
+
+import java.util.List;
+
+import org.apache.commons.geometry.core.collection.PointMap;
+import org.apache.commons.geometry.core.internal.AbstractBucketPointMap;
+import org.apache.commons.geometry.core.partitioning.HyperplaneLocation;
+import org.apache.commons.geometry.euclidean.threed.Vector3D;
+import org.apache.commons.geometry.spherical.twod.GreatCircle;
+import org.apache.commons.geometry.spherical.twod.GreatCircles;
+import org.apache.commons.geometry.spherical.twod.Point2S;
+import org.apache.commons.numbers.core.Precision;
+
+/** Internal implementation of {@link PointMap2S}.
+ * @param <V> Map value type
+ */
+final class PointMap2SImpl<V>
+    extends AbstractBucketPointMap<Point2S, V>
+    implements PointMap<Point2S, V> {
+
+    /** Number of children per node. */
+    private static final int NODE_CHILD_COUNT = 4;
+
+    /** Max entries per node. */
+    private static final int MAX_ENTRIES_PER_NODE = 16;
+
+    /** First negative quadrant flag. */
+    private static final int NEG1 = 1 << 3;
+
+    /** First positive quadrant flag. */
+    private static final int POS1 = 1 << 2;
+
+    /** Second negative quadrant flag. */
+    private static final int NEG2 = 1 << 1;
+
+    /** Second positive quadrant flag. */
+    private static final int POS2 = 1;
+
+    /** Location flags for child nodes. */
+    private static final int[] CHILD_LOCATIONS = {
+        NEG1 | NEG2,
+        NEG1 | POS2,
+        POS1 | NEG2,
+        POS1 | POS2
+    };
+
+    PointMap2SImpl(final Precision.DoubleEquivalence precision) {
+        super(MapNode2S::new,
+                MAX_ENTRIES_PER_NODE,
+                NODE_CHILD_COUNT,
+                precision);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected boolean pointsEq(final Point2S a, final Point2S b) {
+        return a.eq(b, getPrecision());
+    }
+
+    /** Tree node class for {@link PointMap2SImpl}.
+     * @param <V> Map value type
+     */
+    private static final class MapNode2S<V>
+        extends BucketNode<Point2S, V> {
+
+        /** First hyperplane split. */
+        private GreatCircle firstSplit;
+
+        /** Second hyperplane split. */
+        private GreatCircle secondSplit;
+
+        MapNode2S(
+                final AbstractBucketPointMap<Point2S, V> map,
+                final BucketNode<Point2S, V> parent) {
+            super(map, parent);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        protected void computeSplit() {
+            final Vector3D.Sum sum = Vector3D.Sum.create();
+
+            for (Entry<Point2S, V> entry : this) {
+                sum.add(entry.getKey().getVector());
+            }
+
+            // construct an orthonormal basis
+            Vector3D.Unit u = sum.get().multiply(1.0 / MAX_ENTRIES_PER_NODE)
+                    .normalizeOrNull();
+            if (u == null) {
+                u = Vector3D.Unit.PLUS_X;
+            }
+
+            Vector3D.Unit v =  Vector3D.Unit.PLUS_Z.cross(u)
+                    .normalizeOrNull();
+            if (v == null) {
+                v = Vector3D.Unit.PLUS_Y.cross(u)
+                        .normalizeOrNull();
+            }
+            final Vector3D.Unit w = u.cross(v).normalize();
+
+            // construct the two great circles
+            firstSplit = GreatCircles.fromPole(v.add(w), getPrecision());
+            secondSplit = GreatCircles.fromPole(v.negate().add(w), 
getPrecision());

Review comment:
       Had to google orthonormality to remind me of the concept. Wish I had 
more time to dive deeper into the code & concepts here, and really understand 
everything that's going on here :nerd_face: :neckbeard: but looking at the code 
everything looks OK IMO :+1: (it's fun to be able to read code like this, one 
of the benefits of participating in ASF & Commons development)




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscr...@commons.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


Reply via email to