Dear users, Please note that, from Monday, August 16, 2019, RSA keys shorter than 2048bit will no longer be accepted for security reasons. Please update your keys as needed before this date. If you need assistance with regard to this process, please contact sia@list.lu

Thank you for your understanding.

Commit 1837b3b1 authored by Nico Mack's avatar Nico Mack

Fixes to KdTree and ClusterManager implementation.

Changes to POM and Eclipse project setup
parent 24907935
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="config"/>
<classpathentry excluding="main/java/|test/java/" kind="src" output="target/classes" path="src"/>
<classpathentry kind="src" output="target/test-classes" path="test"/>
<classpathentry kind="src" output="target/classes" path="src">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="test">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
<accessrules>
<accessrule kind="accessible" pattern="sun/java2d/**"/>
</accessrules>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
......
......@@ -11,12 +11,12 @@
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<name>org.fusesource.ide.project.RiderProjectBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.fusesource.ide.project.RiderProjectBuilder</name>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
......
eclipse.preferences.version=1
encoding/<project>=UTF-8
encoding/src=UTF-8
encoding/test=UTF-8
......@@ -25,6 +25,7 @@ CENTRE_NODE=centre
CENTRED_NODE=centred
CIRCLE_SIZE_NODE=circleSize
CLUSTERABLE_NODE=clusterable
CLUSTERED_BUNDLE_NODE=clusteredBundle
COLOUR_NODE=colour
COLOUR_PALETTE_NODE=colourPalette
COLOURSC_NODE=colourscheme
......
......@@ -25,8 +25,8 @@
</scm>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<sourceDirectory>src</sourceDirectory>
<testSourceDirectory>test</testSourceDirectory>
<plugins>
<plugin>
......@@ -70,6 +70,7 @@
<manifest>
<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
</manifest>
<manifestFile>config/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
......
......@@ -21,6 +21,9 @@ import lu.list.itis.dkd.tui.utility.ScreenCoordinates;
import lu.list.itis.dkd.tui.utility.kdtree.KdComparator;
import lu.list.itis.dkd.tui.utility.kdtree.KdTree;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
......@@ -44,6 +47,12 @@ public class ClusterManager<P extends Positionable> {
private KdTree<P> kdTree;
// ***************************************************************************
// * Constants *
// ***************************************************************************
private static final Logger LOGGER = LoggerFactory.getLogger(ClusterManager.class.getSimpleName());
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Constructor(s)
......@@ -69,17 +78,6 @@ public class ClusterManager<P extends Positionable> {
// ***************************************************************************
// ---------------------------------------------------------------------------
private synchronized List<Clusterable> getClusterable(List<P> candidates) {
List<Clusterable> results = new ArrayList<>();
for (P candidate : candidates) {
if (Clusterable.class.isAssignableFrom(candidate.getClass())) {
results.add((Clusterable) candidate);
}
}
return results;
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Class Body
......@@ -107,6 +105,16 @@ public class ClusterManager<P extends Positionable> {
}
}
// ---------------------------------------------------------------------------
public void setup() {
if (!this.constituents.isEmpty()) {
kdTree = new KdTree<>(this.constituents, 2, this.comparator);
} else {
LOGGER.info("No clusterable items available! Clustering is disabled!");
}
}
// ---------------------------------------------------------------------------
/**
* @param positionables
......@@ -114,19 +122,47 @@ public class ClusterManager<P extends Positionable> {
// ---------------------------------------------------------------------------
public void refresh() {
kdTree = new KdTree<>(this.constituents, 2, this.comparator);
clustered.clear();
List<P> coallesced = new ArrayList<>();
for (P candidate : this.constituents) {
if ((candidate instanceof Clusterable) && !coallesced.contains(candidate)) {
List<P> coallescing = kdTree.findNearest(candidate, radius);
if (coallescing.size() > 1) {
List<Clusterable> clusterable = this.getClusterable(coallescing);
coallesced.addAll(coallescing);
((Clusterable) candidate).coalesce(clusterable);
if (kdTree == null)
return;
List<P> retained = new ArrayList<>();
List<P> coalesced = new ArrayList<>();
List<P> candidates = new ArrayList<>(this.constituents);
boolean clusterDetected;
int iterations = 0;
do {
clusterDetected = false;
for (P candidate : candidates) {
if ((candidate instanceof Clusterable) && !coalesced.contains(candidate)) {
List<P> coalescing = kdTree.findNearest(candidate, radius);
boolean isClustered = (!coalescing.isEmpty());
coalescing.removeAll(coalesced);
clusterDetected |= (!coalescing.isEmpty());
if (isClustered) {
coalesced.addAll(coalescing);
retained.removeAll(coalescing);
}
((Clusterable) candidate).setClustered(isClustered);
if (!retained.contains(candidate)) {
retained.add(candidate);
}
}
clustered.add(candidate);
}
candidates.clear();
candidates.addAll(retained);
iterations++;
} while (clusterDetected);
synchronized (clustered) {
clustered.clear();
clustered.addAll(retained);
LOGGER.info("Clustering completed in {} iteration(s)! {} Marker(s) remaining!", iterations, clustered.size());
}
}
......@@ -138,8 +174,8 @@ public class ClusterManager<P extends Positionable> {
// ---------------------------------------------------------------------------
public List<P> getClusters() {
return this.clustered;
public synchronized List<P> getClusters() {
return new ArrayList<>(this.clustered);
}
// ---------------------------------------------------------------------------
......
......@@ -16,8 +16,6 @@
*/
package lu.list.itis.dkd.tui.feature.cluster;
import java.util.List;
/**
* @author nico.mack@list.lu
* @since 2.6
......@@ -37,7 +35,7 @@ public interface Clusterable {
*
* @return <code>true</code> if object is currently in coalesced state, <code>false</code> otherwise
*/
public boolean isCoalesced();
public boolean isClustered();
/**
* Tells clusterable items to collaesce into a single clustered item.
......@@ -45,13 +43,5 @@ public interface Clusterable {
* @param disocciated
* @return
*/
public Clusterable coalesce(List<Clusterable> disocciated);
/**
* Tells a previously clustered item to break up into its constituting parts.
*
* @param coallesced
* @return
*/
public List<Clusterable> dissociate(Clusterable coallesced);
public void setClustered(boolean isClustered);
}
\ No newline at end of file
......@@ -22,6 +22,9 @@ import lu.list.itis.dkd.tui.utility.Positionable;
import lu.list.itis.dkd.tui.utility.ScreenCoordinates;
import lu.list.itis.dkd.tui.utility.kdtree.KdComparator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author mack
* @since [major].[minor]
......@@ -31,6 +34,15 @@ public class PositionableComparator implements KdComparator<Positionable> {
private Class<? extends CoordinateState> referenceState = ScreenCoordinates.class;
// ***************************************************************************
// * Constants *
// ***************************************************************************
private static final Logger LOGGER = LoggerFactory.getLogger(PositionableComparator.class.getSimpleName());
/**
* @param state
*/
public void setReferenceState(Class<? extends CoordinateState> state) {
this.referenceState = state;
}
......@@ -38,31 +50,34 @@ public class PositionableComparator implements KdComparator<Positionable> {
/** {@inheritDoc} */
@Override
public int compare(Positionable o1, Positionable o2, int axis) {
Point pos1 = o1.getPosition().clone();
Point pos2 = o2.getPosition().clone();
pos1.toCoordinates(this.referenceState);
pos2.toCoordinates(this.referenceState);
if (o1.equals(o2))
return 0;
Point pos1 = o1.getPosition().toCoordinates(this.referenceState);
Point pos2 = o2.getPosition().toCoordinates(this.referenceState);
return pos1.compareTo(pos2, axis);
}
/** {@inheritDoc} */
@Override
public double getDistance(Positionable o1, Positionable o2) {
Point pos1 = o1.getPosition().clone();
Point pos2 = o2.getPosition().clone();
pos1.toCoordinates(this.referenceState);
pos2.toCoordinates(this.referenceState);
return pos1.getDistance(pos2);
if (o1.equals(o2))
return 0;
Point pos1 = o1.getPosition().toCoordinates(this.referenceState);
Point pos2 = o2.getPosition().toCoordinates(this.referenceState);
return Math.abs(pos1.getDistance(pos2));
}
/** {@inheritDoc} */
@Override
public double getDistance(Positionable o1, Positionable o2, int axis) {
Point pos1 = o1.getPosition().clone();
Point pos2 = o2.getPosition().clone();
pos1.toCoordinates(this.referenceState);
pos2.toCoordinates(this.referenceState);
return pos1.getDistance(pos2, axis);
if (o1.equals(o2))
return 0;
Point pos1 = o1.getPosition().toCoordinates(this.referenceState);
Point pos2 = o2.getPosition().toCoordinates(this.referenceState);
return Math.abs(pos1.getDistance(pos2, axis));
}
}
......@@ -36,8 +36,7 @@ public abstract class Marker implements Positionable, Clusterable, Cloneable {
protected boolean active;
protected boolean isClusterable;
protected boolean isCoalesced;
protected List<Clusterable> clustered;
protected boolean isClustered;
private CoronaComparator coronaComparator;
......@@ -58,7 +57,7 @@ public abstract class Marker implements Positionable, Clusterable, Cloneable {
this.isClusterable = builder.isClusterable;
this.coronas = builder.coronas;
this.positions = builder.positions;
this.clustered = new ArrayList<>();
this.isClustered = false;
}
// ---------------------------------------------------------------------------
......@@ -68,7 +67,7 @@ public abstract class Marker implements Positionable, Clusterable, Cloneable {
this.isClusterable = original.isClusterable;
this.coronas = original.cloneCoronas();
this.positions = original.clonePositions();
this.clustered = new ArrayList<>();
this.isClustered = false;
}
// ---------------------------------------------------------------------------
......@@ -208,14 +207,17 @@ public abstract class Marker implements Positionable, Clusterable, Cloneable {
* @return
*/
public @Nullable Point getPosition(Integer handleId) {
return positions.get(handleId);
if (positions.containsKey(handleId)) {
return positions.get(handleId).clone();
}
return null;
}
// ---------------------------------------------------------------------------
@Override
public @Nullable Point getPosition() {
return positions.get(this.getHandleId());
return this.getPosition(this.getHandleId());
}
// ---------------------------------------------------------------------------
......@@ -321,49 +323,25 @@ public abstract class Marker implements Positionable, Clusterable, Cloneable {
// ---------------------------------------------------------------------------
@Override
public boolean isCoalesced() {
return this.isCoalesced;
public boolean isClustered() {
return this.isClustered;
}
// ---------------------------------------------------------------------------
@Override
public Clusterable coalesce(List<Clusterable> disocciated) {
this.clustered.addAll(disocciated);
this.isCoalesced = true;
return this;
public void setClustered(boolean isClustered) {
this.isClustered = isClustered;
}
// ---------------------------------------------------------------------------
@Override
public List<Clusterable> dissociate(Clusterable coalesced) {
List<Clusterable> dissociated = new ArrayList<>();
if (coalesced.equals(this)) {
dissociated.addAll(this.clustered);
this.clustered.clear();
this.isCoalesced = false;
} else {
List<Clusterable> affected = new ArrayList<>();
for (Clusterable member : this.clustered) {
if (member.isCoalesced()) {
List<Clusterable> returned = member.dissociate(coalesced);
if (!returned.isEmpty()) {
dissociated.addAll(returned);
affected.add(member);
}
}
}
this.clustered.removeAll(affected);
}
return dissociated;
public boolean equals(Object o) {
if (o == this) {
return true;
}
// ---------------------------------------------------------------------------
@Override
public boolean equals(Object o) {
if (o instanceof Marker) {
return this.getHandleId().equals(((Marker) o).getHandleId());
}
......
......@@ -26,6 +26,7 @@ import lu.list.itis.dkd.tui.widget.corona.bundle.CoronaBundle;
import com.google.common.collect.Multimap;
import com.google.common.collect.TreeMultimap;
import com.jgoodies.common.base.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -49,6 +50,7 @@ public class ModalMarker extends TetherableMarker {
protected Multimap<String, CoronaBundle> coronaBundles;
protected List<String> activeBundles;
protected String clusteredBundle;
// ***************************************************************************
// * Constants *
......@@ -70,8 +72,9 @@ public class ModalMarker extends TetherableMarker {
super(builder);
coronaBundles = builder.coronaBundles;
activeBundles = new ArrayList<>();
clusteredBundle = builder.clusteredBundle;
activeBundles = new ArrayList<>();
this.activateDefaults();
}
......@@ -84,6 +87,7 @@ public class ModalMarker extends TetherableMarker {
public ModalMarker(ModalMarker original) {
super(original);
clusteredBundle = original.clusteredBundle;
coronaBundles = TreeMultimap.create();
coronaBundles.putAll(original.coronaBundles);
activeBundles = new ArrayList<>();
......@@ -283,6 +287,16 @@ public class ModalMarker extends TetherableMarker {
// ---------------------------------------------------------------------------
@Override
public void setClustered(boolean isClustered) {
super.setClustered(isClustered);
if (Strings.isNotBlank(clusteredBundle)) {
this.activateBundle(clusteredBundle, isClustered);
}
}
// ---------------------------------------------------------------------------
public ModalMarker clone() {
return new ModalMarker(this);
}
......
......@@ -19,6 +19,7 @@ package lu.list.itis.dkd.tui.marker.builder;
import lu.list.itis.dkd.dbc.annotation.Nullable;
import lu.list.itis.dkd.tui.bootstrapping.BootstrapCallback;
import lu.list.itis.dkd.tui.bootstrapping.BootstrapContext;
import lu.list.itis.dkd.tui.bootstrapping.BootstrappingUtils;
import lu.list.itis.dkd.tui.exception.BuildException;
import lu.list.itis.dkd.tui.marker.ModalMarker;
import lu.list.itis.dkd.tui.utility.Externalization;
......@@ -43,6 +44,7 @@ import org.jdom2.Element;
public abstract class BaseModalMarkerBuilder<B extends BaseModalMarkerBuilder<B>> extends BaseTetherableMarkerBuilder<B> {
public Multimap<String, CoronaBundle> coronaBundles;
public String clusteredBundle;
// ***************************************************************************
// * Constants *
......@@ -88,6 +90,7 @@ public abstract class BaseModalMarkerBuilder<B extends BaseModalMarkerBuilder<B>
coronaBundles.put(bundle.getName(), bundle);
}
}
this.clusteredBundle = BootstrappingUtils.getContentAsString(rootElement, Externalization.CLUSTERED_BUNDLE_NODE, BootstrappingUtils.OPTIONAL, null, context);
}
// ---------------------------------------------------------------------------
......
......@@ -60,6 +60,7 @@ public class Externalization extends NLS {
public static String CENTRED_NODE;
public static String CIRCLE_SIZE_NODE;
public static String CLUSTERABLE_NODE;
public static String CLUSTERED_BUNDLE_NODE;
public static String COLOUR_NODE;
public static String COLOUR_PALETTE_NODE;
public static String COLOURSC_NODE;
......
......@@ -66,13 +66,12 @@ public class Point extends Float implements Comparable<Point> {
private static final Logger logger = LoggerFactory.getLogger(Point.class.getSimpleName());
private static final int NUMBER_OF_DIMENSIONS = 2;
/** Constant identifying the X Axis */
public static final int X_AXIS = 0;
/** Constant identifying the Y Axis */
public static final int Y_AXIS = 1;
protected static enum PointComparator implements Comparator<Point> {
protected enum PointComparator implements Comparator<Point> {
x {
@Override
public int compare(Point a, Point b) {
......@@ -84,6 +83,12 @@ public class Point extends Float implements Comparable<Point> {
public int compare(Point a, Point b) {
return java.lang.Double.compare(a.y, b.y);
}
},
angle {
@Override
public int compare(Point a, Point b) {
return java.lang.Double.compare(AngleUtils.moduloTwoPi(a.getAngle()), AngleUtils.moduloTwoPi(b.getAngle()));
}
}
}
......@@ -167,22 +172,6 @@ public class Point extends Float implements Comparable<Point> {
public Point(float x, float y, float angle, Class<? extends CoordinateState> initialState) {
super(x, y);
this.angle = angle;
// switch (initialState.getSimpleName()) {
// case "CameraCoordinates": //$NON-NLS-1$
// state = new CameraCoordinates(this, x, y);
// break;
// case "ScreenCoordinates": //$NON-NLS-1$
// state = new ScreenCoordinates(this, x, y);
// break;
// case "TableCoordinates": //$NON-NLS-1$
// state = new TableCoordinates(this, x, y);
// break;
// default:
// state = new CameraCoordinates(this, x, y);
// break;
// }
state = CoordinateStateFactory.newInstance(initialState, this, x, y);
}
......@@ -580,6 +569,28 @@ public class Point extends Float implements Comparable<Point> {
return comparison;
}
/**
* @param other
* @return
*/
@Override
public boolean equals(Object other) {
if (!(other instanceof Point))
return false;
Point otherPoint = (Point) other;
boolean isEqual = true;
isEqual &= (this.x == otherPoint.x);
isEqual &= (this.y == otherPoint.y);
isEqual &= (this.angle == otherPoint.angle);
isEqual &= (this.state.equals(otherPoint.state));
return isEqual;
}
/**
* Checks whether the specified point is within the acceptable bounds of its coordinate state.
*
......@@ -591,6 +602,11 @@ public class Point extends Float implements Comparable<Point> {
return state.withinBounds(this);
}
@Override
public String toString() {
return StringUtils.build("Point [x={},y={},a={}, coord={}]", x, y, angle, state.getClass().getSimpleName()); //$NON-NLS-1$
}
}
......
......@@ -60,19 +60,20 @@ public class DistanceComparator<T> implements Comparator<KdNode<T>> {
double dist1 = comparator.getDistance(origin, node1.element);
double dist2 = comparator.getDistance(origin, node2.element);
if (dist1 < dist2) {
return -1;
} else if (dist1 > dist2) {
return 1;
} else {
if (node1.hashCode() < node2.hashCode()) {
return -1;
} else if (node1.hashCode() > node2.hashCode()) {
return 1;
}
}
// if (dist1 < dist2) {
// return -1;
// } else if (dist1 > dist2) {
// return 1;
// } else {
// if (node1.hashCode() < node2.hashCode()) {
// return -1;
// } else if (node1.hashCode() > node2.hashCode()) {
// return 1;
// }
// }
// return 0;
return 0;
return Double.compare(dist1, dist2);
}
// ---------------------------------------------------------------------------
......
......@@ -28,12 +28,19 @@ package lu.list.itis.dkd.tui.utility.kdtree;
public interface KdComparator<T> {
/**
* @param o1
* @param o2
* @param axis
* @return
*/
public int compare(T o1, T o2, int axis);
/**
* returns the (squared) distance between this instance of T and the one specified.
*
* @param other
* @param o1
* @param o2
* specifies the other instance of T to get the distance from
* @return the squared distance along all axes.
*/
......@@ -42,8 +49,8 @@ public interface KdComparator<T> {
/**
* returns the (squared) distance along one axis between this instance of T and the one specified.
*
* @param other
* specifies the other instance of T to get the distance from
* @param o1
* @param o2
* @param axis
* specifies the axis to measure the distance along
* @return the squared distance along the specified axis.
......
......@@ -35,7 +35,6 @@ import java.util.TreeSet;
public class KdTree<T> {
private KdNode<T> root;
private int dimensions;
private KdComparator<T> comparator;
private Comparator<T> axisComparators[];
......@@ -52,9 +51,8 @@ public class KdTree<T> {
// ---------------------------------------------------------------------------
public KdTree(List<T> elements, int dimensions, KdComparator<T> comparator) {
this.dimensions = dimensions;
this.comparator = comparator;
this.buildAxisComparators();
this.buildAxisComparators(dimensions);
this.root = this.createNode(elements, dimensions, 0);
}
......@@ -65,9 +63,9 @@ public class KdTree<T> {
// ---------------------------------------------------------------------------
@SuppressWarnings("unchecked")
private void buildAxisComparators() {
axisComparators = new AxisComparator[this.dimensions];
for (int i = 0; i < this.dimensions; i++) {
private void buildAxisComparators(int dimensions) {
axisComparators = new AxisComparator[dimensions];
for (int i = 0; i < dimensions; i++) {
axisComparators[i] = new AxisComparator<>(i, comparator);
}
}
......@@ -127,7 +125,7 @@ public class KdTree<T> {
// ---------------------------------------------------------------------------
private void searchNode(T candidate, KdNode<T> current, KdNode<T> previous, TreeSet<KdNode<T>> nearest, HashSet<KdNode<T>> alreadySeen, double radius) {
private void searchNode(T candidate, KdNode<T> current, TreeSet<KdNode<T>> nearest, HashSet<KdNode<T>> alreadySeen, double radius) {
alreadySeen.add(current);
KdNode<T> lastNode = null;
......@@ -161,11 +159,11 @@ public class KdTree<T> {
boolean isRight = (direction >= 0);
if (left != null && !alreadySeen.contains(left) && (intersect || isLeft)) {
searchNode(candidate, left, null, nearest, alreadySeen, radius);
searchNode(candidate, left, nearest, alreadySeen, radius);
}
if (right != null && !alreadySeen.contains(right) && (intersect || isRight)) {
searchNode(candidate, right, null, nearest, alreadySeen, radius);
searchNode(candidate, right, nearest, alreadySeen, radius);
}
}
......@@ -195,20 +193,21 @@ public class KdTree<T> {
current = (current.compareTo(candidate, comparator) <= 0) ? current.right : current.left;
}