Commit ef3f2f34 authored by Nico Mack's avatar Nico Mack

Finalized implementation of KdTree

Implementation of Clusterable interface at Marker Level
parent 15d9a29c
...@@ -24,6 +24,7 @@ CENTER_ON_ZOOM_NODE=centerOnZoom ...@@ -24,6 +24,7 @@ CENTER_ON_ZOOM_NODE=centerOnZoom
CENTRE_NODE=centre CENTRE_NODE=centre
CENTRED_NODE=centred CENTRED_NODE=centred
CIRCLE_SIZE_NODE=circleSize CIRCLE_SIZE_NODE=circleSize
CLUSTERABLE_NODE=clusterable
COLOUR_NODE=colour COLOUR_NODE=colour
COLOUR_PALETTE_NODE=colourPalette COLOUR_PALETTE_NODE=colourPalette
COLOURSC_NODE=colourscheme COLOURSC_NODE=colourscheme
......
...@@ -44,11 +44,11 @@ import org.slf4j.LoggerFactory; ...@@ -44,11 +44,11 @@ import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.Vector;
/** /**
* Abstract class holding the basic necessities to have in a tangible application. The class * Abstract class holding the basic necessities to have in a tangible application. The class
...@@ -175,6 +175,9 @@ public abstract class TangibleApplication { ...@@ -175,6 +175,9 @@ public abstract class TangibleApplication {
return document; return document;
} }
/**
*
*/
public void setupAnimation() { public void setupAnimation() {
TridentConfig.getInstance().setPulseSource(new TridentConfig.FixedRatePulseSource(1000 / ANIMATION_FRAME_RATE)); TridentConfig.getInstance().setPulseSource(new TridentConfig.FixedRatePulseSource(1000 / ANIMATION_FRAME_RATE));
} }
...@@ -221,16 +224,6 @@ public abstract class TangibleApplication { ...@@ -221,16 +224,6 @@ public abstract class TangibleApplication {
return objectManager; return objectManager;
} }
/**
* Simple setter method for interfaceManager.
*
* @param interfaceManager
* The value to set interfaceManager to.
*/
// public void setInterfaceManager(TangibleInterfaceManager interfaceManager) {
// TangibleApplication.interfaceManager = interfaceManager;
// }
/** /**
* Simple setter method for objectManager. * Simple setter method for objectManager.
* *
...@@ -261,7 +254,6 @@ public abstract class TangibleApplication { ...@@ -261,7 +254,6 @@ public abstract class TangibleApplication {
public void setContentManager(TangibleContentManager contentManager) { public void setContentManager(TangibleContentManager contentManager) {
this.contentManager = contentManager; this.contentManager = contentManager;
stageManager.setContentManager(contentManager); stageManager.setContentManager(contentManager);
// stageManager.setContent(contentManager.getContents());
} }
/** /**
...@@ -273,11 +265,13 @@ public abstract class TangibleApplication { ...@@ -273,11 +265,13 @@ public abstract class TangibleApplication {
stageManager.repaint(); stageManager.repaint();
} }
/**
*
*/
public void shutdown() { public void shutdown() {
this.disconnect(); this.disconnect();
} }
/** /**
* Method for retrieving all active {@link TangibleObject} cursors from the {@link TuiAdapter} * Method for retrieving all active {@link TangibleObject} cursors from the {@link TuiAdapter}
* client managing the global state of the application. * client managing the global state of the application.
...@@ -286,7 +280,7 @@ public abstract class TangibleApplication { ...@@ -286,7 +280,7 @@ public abstract class TangibleApplication {
* instances. * instances.
*/ */
public Collection<TangibleObject> getActiveCursors() { public Collection<TangibleObject> getActiveCursors() {
return new Vector<>(adapter.getActiveCursors()); return new ArrayList<>(adapter.getActiveCursors());
} }
/** /**
...@@ -297,7 +291,7 @@ public abstract class TangibleApplication { ...@@ -297,7 +291,7 @@ public abstract class TangibleApplication {
* instances. * instances.
*/ */
public Collection<TangibleObject> getActiveObjects() { public Collection<TangibleObject> getActiveObjects() {
return new Vector<>(adapter.getActiveObjects()); return new ArrayList<>(adapter.getActiveObjects());
} }
/** /**
......
/**
* Copyright Luxembourg Institute of Science and Technology, 2019. All rights reserved.
*
* This file is part of TULIP.
*
* TULIP is free software: you can redistribute it and/or modify it under the terms of the GNU
* Lesser General Public License as published by the Free Software Foundation, version 3 of the
* License.
*
* TULIP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with TULIP. If
* not, see <http://www.gnu.org/licenses/lgpl-3.0.html>.
*/
package lu.list.itis.dkd.tui.feature.cluster;
import lu.list.itis.dkd.tui.utility.Positionable;
import lu.list.itis.dkd.tui.utility.kdtree.KdTree;
import java.util.ArrayList;
import java.util.List;
/**
* @author nico.mack@list.lu
* @since 2.6
* @version 1.0.0
*/
// ***************************************************************************
// * Class Definition and Members *
// ***************************************************************************
public class ClusterManager {
private KdTree<Positionable> kdTree;
private double radius;
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Constructor(s)
// ***************************************************************************
// ---------------------------------------------------------------------------
/**
* @param radius
*/
// ---------------------------------------------------------------------------
public ClusterManager(double radius) {
this.radius = radius;
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Primitive(s)
// ***************************************************************************
// ---------------------------------------------------------------------------
private synchronized List<Clusterable> getClusterable(List<Positionable> candidates) {
List<Clusterable> results = new ArrayList<>();
for (Positionable candidate : candidates) {
if (Clusterable.class.isAssignableFrom(candidate.getClass())) {
results.add((Clusterable) candidate);
}
}
return results;
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Class Body
// ***************************************************************************
// ---------------------------------------------------------------------------
/**
* @param positionables
*/
// ---------------------------------------------------------------------------
public void refresh(List<Positionable> positionables) {
kdTree = new KdTree<>(positionables, 2, new PositionableComparator());
List<Positionable> coallesced = new ArrayList<>();
List<Clusterable> clusters = new ArrayList<>();
for (Positionable candidate : positionables) {
if ((candidate instanceof Clusterable) && coallesced.contains(candidate)) {
List<Positionable> coallescing = kdTree.findNearest(candidate, radius);
List<Clusterable> clusterable = this.getClusterable(coallescing);
((Clusterable) candidate).coalesce(clusterable);
coallesced.addAll(coallescing);
clusters.add((Clusterable) candidate);
}
}
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * End of Class
// ***************************************************************************
// ---------------------------------------------------------------------------
}
/**
* Copyright Luxembourg Institute of Science and Technology, 2019. All rights reserved.
*
* This file is part of TULIP.
*
* TULIP is free software: you can redistribute it and/or modify it under the terms of the GNU
* Lesser General Public License as published by the Free Software Foundation, version 3 of the
* License.
*
* TULIP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with TULIP. If
* not, see <http://www.gnu.org/licenses/lgpl-3.0.html>.
*/
package lu.list.itis.dkd.tui.feature.cluster;
import java.util.List;
/**
* @author nico.mack@list.lu
* @since 2.6
* @version 1.0.0
*/
public interface Clusterable {
/**
* Allows whether is theoretically clusterable object is currently setup to allow clustering
*
* @return <code>true</code> if clustering is supported, <code>false</code> otherwise
*/
public boolean isClusterable();
/**
* Checks whether is this clusterable object is currently in coalesced state.
*
* @return <code>true</code> if object is currently in coalesced state, <code>false</code> otherwise
*/
public boolean isCoalesced();
/**
* Tells clusterable items to collaesce into a single clustered item.
*
* @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);
}
/**
* Copyright Luxembourg Institute of Science and Technology, 2019. All rights reserved.
*
* This file is part of TULIP.
*
* TULIP is free software: you can redistribute it and/or modify it under the terms of the GNU
* Lesser General Public License as published by the Free Software Foundation, version 3 of the
* License.
*
* TULIP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with TULIP. If
* not, see <http://www.gnu.org/licenses/lgpl-3.0.html>.
*/
package lu.list.itis.dkd.tui.feature.cluster;
import lu.list.itis.dkd.tui.utility.Point;
import lu.list.itis.dkd.tui.utility.Positionable;
import lu.list.itis.dkd.tui.utility.kdtree.KdComparator;
/**
* @author mack
* @since [major].[minor]
* @version [major].[minor].[micro]
*/
public class PositionableComparator implements KdComparator<Positionable> {
/** {@inheritDoc} */
@Override
public int compare(Positionable o1, Positionable o2, int axis) {
Point pos1 = o1.getPosition();
Point pos2 = o2.getPosition();
return pos1.compareTo(pos2, axis);
}
/** {@inheritDoc} */
@Override
public double getDistance(Positionable o1, Positionable o2) {
Point pos1 = o1.getPosition();
Point pos2 = o2.getPosition();
return pos1.getDistance(pos2);
}
/** {@inheritDoc} */
@Override
public double getDistance(Positionable o1, Positionable o2, int axis) {
Point pos1 = o1.getPosition();
Point pos2 = o2.getPosition();
return pos1.getDistance(pos2, axis);
}
}
package lu.list.itis.dkd.tui.marker; package lu.list.itis.dkd.tui.marker;
import lu.list.itis.dkd.dbc.annotation.Nullable; import lu.list.itis.dkd.dbc.annotation.Nullable;
import lu.list.itis.dkd.tui.feature.cluster.Clusterable;
import lu.list.itis.dkd.tui.marker.builder.MarkerBuilder; import lu.list.itis.dkd.tui.marker.builder.MarkerBuilder;
import lu.list.itis.dkd.tui.utility.CoronaComparator; import lu.list.itis.dkd.tui.utility.CoronaComparator;
import lu.list.itis.dkd.tui.utility.Point; import lu.list.itis.dkd.tui.utility.Point;
...@@ -23,7 +24,7 @@ import java.util.Objects; ...@@ -23,7 +24,7 @@ import java.util.Objects;
// * Class Definition and Members * // * Class Definition and Members *
// *************************************************************************** // ***************************************************************************
public abstract class Marker implements Positionable, Cloneable { public abstract class Marker implements Positionable, Clusterable, Cloneable {
/** The name given to this marker. */ /** The name given to this marker. */
protected String name; protected String name;
...@@ -34,6 +35,10 @@ public abstract class Marker implements Positionable, Cloneable { ...@@ -34,6 +35,10 @@ public abstract class Marker implements Positionable, Cloneable {
protected boolean active; protected boolean active;
protected boolean isClusterable;
protected boolean isCoalesced;
protected List<Clusterable> clustered;
private CoronaComparator coronaComparator; private CoronaComparator coronaComparator;
// *************************************************************************** // ***************************************************************************
...@@ -50,18 +55,18 @@ public abstract class Marker implements Positionable, Cloneable { ...@@ -50,18 +55,18 @@ public abstract class Marker implements Positionable, Cloneable {
public Marker(MarkerBuilder<?> builder) { public Marker(MarkerBuilder<?> builder) {
this.name = builder.name; this.name = builder.name;
this.isClusterable = builder.isClusterable;
this.coronas = builder.coronas; this.coronas = builder.coronas;
this.positions = builder.positions; this.positions = builder.positions;
// this.drawPriority = builder.drawPriority;
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
public Marker(Marker original) { public Marker(Marker original) {
this.name = original.name; this.name = original.name;
this.isClusterable = original.isClusterable;
this.coronas = original.cloneCoronas(); this.coronas = original.cloneCoronas();
this.positions = original.clonePositions(); this.positions = original.clonePositions();
// this.drawPriority = builder.drawPriority;
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
...@@ -293,6 +298,10 @@ public abstract class Marker implements Positionable, Cloneable { ...@@ -293,6 +298,10 @@ public abstract class Marker implements Positionable, Cloneable {
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
/**
* updates the positions of the associated coronas.
*/
// ---------------------------------------------------------------------------
public void updatePositions() { public void updatePositions() {
for (Integer handleId : positions.keySet()) { for (Integer handleId : positions.keySet()) {
...@@ -302,9 +311,58 @@ public abstract class Marker implements Positionable, Cloneable { ...@@ -302,9 +311,58 @@ public abstract class Marker implements Positionable, Cloneable {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@Override
public boolean isClusterable() {
return this.isClusterable;
}
// ---------------------------------------------------------------------------
@Override
public boolean isCoalesced() {
return this.isCoalesced;
}
// ---------------------------------------------------------------------------
@Override
public Clusterable coalesce(List<Clusterable> disocciated) {
this.clustered.addAll(disocciated);
this.isCoalesced = true;
return this;
}
// ---------------------------------------------------------------------------
@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;
}
// ---------------------------------------------------------------------------
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if ((o != null) && (o instanceof Marker)) { if (o instanceof Marker) {
return this.getHandleId().equals(((Marker) o).getHandleId()); return this.getHandleId().equals(((Marker) o).getHandleId());
} }
return false; return false;
......
...@@ -37,7 +37,7 @@ public abstract class MarkerBuilder<B extends MarkerBuilder<B>> { ...@@ -37,7 +37,7 @@ public abstract class MarkerBuilder<B extends MarkerBuilder<B>> {
/** The positions where the markers handles are at. */ /** The positions where the markers handles are at. */
public HashMap<Integer, Point> positions; public HashMap<Integer, Point> positions;
/** */ /** */
public int drawPriority; public boolean isClusterable;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// *************************************************************************** // ***************************************************************************
...@@ -50,7 +50,7 @@ public abstract class MarkerBuilder<B extends MarkerBuilder<B>> { ...@@ -50,7 +50,7 @@ public abstract class MarkerBuilder<B extends MarkerBuilder<B>> {
coronas = TreeMultimap.create(); coronas = TreeMultimap.create();
positions = new HashMap<>(); positions = new HashMap<>();
name = Externalization.EMPTY_STRING; name = Externalization.EMPTY_STRING;
drawPriority = 0; isClusterable = false;
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
...@@ -91,6 +91,7 @@ public abstract class MarkerBuilder<B extends MarkerBuilder<B>> { ...@@ -91,6 +91,7 @@ public abstract class MarkerBuilder<B extends MarkerBuilder<B>> {
} }
name = BootstrappingUtils.getContentAsString(rootElement, Externalization.NAME_NODE, BootstrappingUtils.OPTIONAL, Externalization.EMPTY_STRING, context); name = BootstrappingUtils.getContentAsString(rootElement, Externalization.NAME_NODE, BootstrappingUtils.OPTIONAL, Externalization.EMPTY_STRING, context);
isClusterable = BootstrappingUtils.getContentAsBoolean(rootElement, Externalization.CLUSTERABLE_NODE, BootstrappingUtils.OPTIONAL, Boolean.FALSE, context);
coronas = CoronaBootstrapper.getCoronas(rootElement.getChild(Externalization.CORONAS_NODE), context, callback); coronas = CoronaBootstrapper.getCoronas(rootElement.getChild(Externalization.CORONAS_NODE), context, callback);
} }
......
...@@ -59,6 +59,7 @@ public class Externalization extends NLS { ...@@ -59,6 +59,7 @@ public class Externalization extends NLS {
public static String CENTRE_NODE; public static String CENTRE_NODE;
public static String CENTRED_NODE; public static String CENTRED_NODE;
public static String CIRCLE_SIZE_NODE; public static String CIRCLE_SIZE_NODE;
public static String CLUSTERABLE_NODE;
public static String COLOUR_NODE; public static String COLOUR_NODE;
public static String COLOUR_PALETTE_NODE; public static String COLOUR_PALETTE_NODE;
public static String COLOURSC_NODE; public static String COLOURSC_NODE;
......
/**
* Copyright Luxembourg Institute of Science and Technology, 2016.
*
* This file is part of TULIP.
*
* TULIP is licensed under a dual-licensing scheme. For non-commercial purposes, the LGPL version 3,
* as stated below, is applicable. For all commercial purposes TULIP is licensed under a LIST
* proprietary license. Please contact LIST at tto@list.lu to obtain a commercial license.
*
* For all non-commercial purposes, TULIP is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the Free Software
* Foundation, version 3 of the License.
*
* TULIP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with TULIP. If
* not, see <http://www.gnu.org/licenses/lgpl-3.0.html>.
*/
package lu.list.itis.dkd.tui.utility;
import lu.list.itis.dkd.dbc.annotation.NonNull;
import com.google.common.base.Preconditions;
/**
* Class implementing a {@link CoordinateState} for screen coordinates. The class features methods
* to transition to all other states.
*
* @author Eric Tobias [eric.tobias@list.lu]
* @since 1.0
* @version 2.3.0
*/
public class GridCoordinates extends CoordinateState {
private double horizonalCellMultiplier = 1 / DEFAULT_CELL_SIZE;
private double verticalCellMultiplier = 1 / DEFAULT_CELL_SIZE;
private Bounds2D bounds = DEFAULT_BOUNDS;
public static final double DEFAULT_CELL_SIZE = 0.1d;
private static final Bounds2D DEFAULT_BOUNDS = new Bounds2D(0, 0, 1, 1);
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Constructor(s)
// ***************************************************************************
// ---------------------------------------------------------------------------
/**
* Constructing an instance with the provided points.
*
* @param context
* The context of the state; the feature the state gives meaning.
* @param x
* The X coordinate of the feature containing this state.
* @param y
* The Y coordinate of the feature containing this state.
*/
public GridCoordinates(@NonNull Point context, float x, float y) {
context.setLocation(x, y);
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Class Body
// ***************************************************************************
// ---------------------------------------------------------------------------
/**
* @param horizonalCellSize
* @param verticalCellSize
*/
public void setCellSize(double horizonalCellSize, double verticalCellSize) {
Preconditions.checkArgument((horizonalCellSize > 0) && (horizonalCellSize < 0.5));
Preconditions.checkArgument((verticalCellSize > 0) && (verticalCellSize < 0.5));
this.horizonalCellMultiplier = 1 / horizonalCellSize;
this.verticalCellMultiplier = 1 / verticalCellSize;
}
/**
* {@inheritDoc}
*/
@Override
@Deprecated
public void toTable(@NonNull Point context) {
context.setState(new TableCoordinates(context, Calibration.screenToTableX(context.x), Calibration.screenToTableY(context.y)));
}
/**
* {@inheritDoc}
*/
@Override
@Deprecated
public void toScreen(@NonNull Point context) {
/**
* The conversion is naught as the point's coordinates are already given in this reference scheme!
*/
}
/**
* {@inheritDoc}
*/
@Override
@Deprecated
public void toCamera(@NonNull Point context) {
context.setState(new CameraCoordinates(context, Calibration.screenToCameraX(context.x), Calibration.screenToCameraY(context.y)));
}
@Override
public boolean withinBounds(Point context) {
return DEFAULT_BOUNDS.includes(context);
}
/** {@inheritDoc} */
@Override
public Point normalize(Point context) {
double normx = (context.x - bounds.x1) / bounds.getWidth();
double normy = (context.y - bounds.y1) / bounds.getHeight();
return new Point((float) normx, (float) normy, context.getAngle(), NormalizedCoordinates.class);
}
/** {@inheritDoc} */
@Override
public Point transform(Point normalized) {
double gridx = bounds.x1 + (normalized.x * bounds.getWidth());
double gridy = bounds.y1 + (normalized.y * bounds.getHeight());
gridx = Math.floor(gridx * this.horizonalCellMultiplier + 0.5) / this.horizonalCellMultiplier;
gridy = Math.floor(gridy * this.verticalCellMultiplier + 0.5) / this.verticalCellMultiplier;
return new Point((float) gridx, (float) gridy, normalized.getAngle(), this.getClass());
}
}
\ No newline at end of file
...@@ -27,7 +27,6 @@ import lu.list.itis.dkd.tui.bootstrapping.BootstrapContext; ...@@ -27,7 +27,6 @@ import lu.list.itis.dkd.tui.bootstrapping.BootstrapContext;
import lu.list.itis.dkd.tui.bootstrapping.BootstrappingUtils; import lu.list.itis.dkd.tui.bootstrapping.BootstrappingUtils;
import lu.list.itis.dkd.tui.bootstrapping.CoordinateStateBootstrapper; import lu.list.itis.dkd.tui.bootstrapping.CoordinateStateBootstrapper;
import lu.list.itis.dkd.tui.exception.BuildException; import lu.list.itis.dkd.tui.exception.BuildException;
import lu.list.itis.dkd.tui.utility.kdtree.KdComparator;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;