Feature.java
package nl.tudelft.simulation.dsol.animation.gis.map;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.stream.IntStream;
import org.djutils.draw.bounds.Bounds2d;
import com.carrotsearch.hppc.FloatArrayList;
import nl.tudelft.simulation.dsol.animation.gis.FeatureInterface;
import nl.tudelft.simulation.dsol.animation.gis.LayerInterface;
import nl.tudelft.simulation.dsol.animation.gis.MarkerInterface;
import nl.tudelft.simulation.dsol.animation.gis.Style;
/**
* Feature contains an element of a layer, defined by a key value combination, with its own colors.
* <p>
* Copyright (c) 2021-2025 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
* for project information <a href="https://simulation.tudelft.nl/dsol/manual/" target="_blank">DSOL Manual</a>. The DSOL
* project is distributed under a three-clause BSD-style license, which can be found at
* <a href="https://simulation.tudelft.nl/dsol/docs/latest/license.html" target="_blank">DSOL License</a>.
* </p>
* @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
*/
public class Feature implements FeatureInterface
{
/** the layer to which this feature belongs. */
private final LayerInterface layer;
/** the key that defines a feature in a layer, can be "*" if no features are defined. */
private String key = "*";
/**
* the value belonging to the key that defines the feature in a layer, can be "*" if all elements in the data source that
* have the correct key have to be drawn.
*/
private String value = "*";
/** the list of shapes that have been retrieved for this feature. */
private List<Path2D> shapes = new ArrayList<>();
/** the shape style. */
private Style shapeStyle;
/** The names of the shape attributes per column. */
private List<String> shapeAttributeNames;
/** the shape attributes. */
private List<String[]> shapeAttributes = new ArrayList<>();
/** the list of x-coordinates of the points that have been retrieved for this feature. */
private FloatArrayList xList = new FloatArrayList();
/** the list of y-coordinates of the points that have been retrieved for this feature. */
private FloatArrayList yList = new FloatArrayList();
/** the marker to use to draw points. */
private MarkerInterface pointMarker;
/** the style for the point markers. */
private Style markerStyle;
/** The names of the point attributes per column. */
private List<String> pointAttributeNames;
/** the point attributes. */
private List<String[]> pointAttributes = new ArrayList<>();
/** the z-index of this feature. The z-index indicates the drawing order, from low to high. */
private double zIndex = 0.0;
/** whether the shapes have been read or not. */
private boolean initialized = false;
/**
* Create a feature as part of a layer.
* @param layer the layer to which this feature belongs
*/
public Feature(final LayerInterface layer)
{
super();
this.layer = layer;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////// METHODS FOR SHAPES (Path2D) /////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@Override
public void clearShapes()
{
this.shapes.clear();
}
@Override
public void addShape(final Path2D shape)
{
this.shapes.add(shape);
}
@Override
public void addShape(final Path2D shape, final String[] attributes)
{
this.shapes.add(shape);
this.shapeAttributes.add(attributes);
}
@Override
public int getNumShapes()
{
return this.shapes.size();
}
@Override
public Path2D getShape(final int index) throws IndexOutOfBoundsException
{
return this.shapes.get(index);
}
@Override
public Iterator<Path2D> shapeIterator()
{
return this.shapes.iterator();
}
@Override
public Iterator<Path2D> shapeIterator(final Bounds2d rectangle)
{
final Rectangle2D view2D = rectangle.toRectangle2D();
// return this.shapes.iterator();
return this.shapes.stream().filter((s) -> view2D.intersects(s.getBounds2D())).iterator();
}
@Override
public Style getShapeStyle()
{
return this.shapeStyle;
}
@Override
public void setShapeStyle(final Style shapeStyle)
{
this.shapeStyle = shapeStyle;
}
@Override
public String getShapeAttribute(final int index, final int column) throws IndexOutOfBoundsException
{
return this.shapeAttributes.get(index)[column];
}
@Override
public String[] getShapeAttributes(final int index) throws IndexOutOfBoundsException
{
return this.shapeAttributes.get(index);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////// METHODS FOR POINTS //////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@Override
public int getNumPoints()
{
return this.xList.size();
}
@Override
public void addPoint(final Point2D point)
{
this.xList.add((float) point.getX());
this.yList.add((float) point.getY());
}
@Override
public void addPoint(final Point2D point, final String[] attributes)
{
addPoint(point);
this.pointAttributes.add(attributes);
}
@Override
public Style getMarkerStyle()
{
return this.markerStyle;
}
@Override
public void setMarkerStyle(final Style marker)
{
this.markerStyle = marker;
}
@Override
public MarkerInterface getPointMarker()
{
return this.pointMarker;
}
@Override
public void setPointMarker(final MarkerInterface pointMarker)
{
this.pointMarker = pointMarker;
}
@Override
public Iterator<Point2D> pointIterator()
{
return IntStream.range(0, getNumPoints() - 1)
.mapToObj((i) -> (Point2D) new Point2D.Float(this.xList.get(i), this.yList.get(i))).iterator();
}
@Override
public Iterator<Point2D> pointIterator(final Bounds2d rectangle)
{
final Rectangle2D view2D = rectangle.toRectangle2D();
return IntStream.range(0, getNumPoints() - 1)
.mapToObj((i) -> (Point2D) new Point2D.Float(this.xList.get(i), this.yList.get(i)))
.filter((p) -> view2D.contains(p)).iterator();
}
@Override
public Point2D getPoint(final int index) throws IndexOutOfBoundsException
{
return new Point2D.Float(this.xList.get(index), this.yList.get(index));
}
@Override
public String getPointAttribute(final int index, final int column) throws IndexOutOfBoundsException
{
return this.pointAttributes.get(index)[column];
}
@Override
public String[] getPointAttributes(final int index) throws IndexOutOfBoundsException
{
return this.pointAttributes.get(index);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////// GENERIC METHODS FOR FEATURE /////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@Override
public final String getKey()
{
return this.key;
}
@Override
public final void setKey(final String key)
{
this.key = key;
}
@Override
public final String getValue()
{
return this.value;
}
@Override
public LayerInterface getLayer()
{
return this.layer;
}
@Override
public boolean isInitialized()
{
return this.initialized;
}
@Override
public void setInitialized(final boolean initialized)
{
this.initialized = initialized;
}
@Override
public final void setValue(final String value)
{
this.value = value;
}
@Override
public double getZIndex()
{
return this.zIndex;
}
@Override
public void setZIndex(final double zIndex)
{
this.zIndex = zIndex;
}
@Override
public String toString()
{
return "Feature [key=" + this.key + ", value=" + this.value + "]";
}
}