GisMap.java
package nl.tudelft.simulation.dsol.animation.gis.map;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.djutils.draw.bounds.Bounds2d;
import org.djutils.immutablecollections.ImmutableArrayList;
import org.djutils.immutablecollections.ImmutableHashMap;
import org.djutils.immutablecollections.ImmutableList;
import org.djutils.immutablecollections.ImmutableMap;
import org.djutils.logger.CategoryLogger;
import nl.tudelft.simulation.dsol.animation.gis.DsolGisException;
import nl.tudelft.simulation.dsol.animation.gis.FeatureInterface;
import nl.tudelft.simulation.dsol.animation.gis.GisMapInterface;
import nl.tudelft.simulation.dsol.animation.gis.GisObject;
import nl.tudelft.simulation.dsol.animation.gis.LayerInterface;
import nl.tudelft.simulation.dsol.animation.gis.MapImageInterface;
import nl.tudelft.simulation.dsol.animation.gis.MapUnits;
import nl.tudelft.simulation.dsol.animation.gis.SerializablePath;
import nl.tudelft.simulation.dsol.animation.gis.SerializableRectangle2d;
/**
* Provides the implementation of a Map.
* <p>
* Copyright (c) 2020-2024 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://https://simulation.tudelft.nl/dsol/docs/latest/license.html" target="_blank">DSOL License</a>.
* </p>
* <p>
* The dsol-animation-gis project is based on the gisbeans project that has been part of DSOL since 2002, originally by Peter
* Jacobs and Paul Jacobs.
* </p>
* @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
*/
public class GisMap implements GisMapInterface
{
/** */
private static final long serialVersionUID = 1L;
/** the extent of the map. */
private Bounds2d extent;
/** the map of layer names to layers. */
private Map<String, LayerInterface> layerMap = new LinkedHashMap<>();
/** the total list of layers of the map. */
private List<LayerInterface> allLayers = new ArrayList<>();
/** the visible layers of the map. */
private List<LayerInterface> visibleLayers = new ArrayList<>();
/** same set to false after layer change. */
private boolean same = false;
/** the mapfileImage. */
private MapImageInterface image = new MapImage();
/** the name of the mapFile. */
private String name;
/** the map units. */
private MapUnits units = MapUnits.METERS;
/** draw the background? */
private boolean drawBackground = true;
/** the screen resolution. */
private static final int RESOLUTION = 72;
/**
* constructs a new Map.
*/
public GisMap()
{
super();
}
/** {@inheritDoc} */
@Override
public void addLayer(final LayerInterface layer)
{
this.visibleLayers.add(layer);
this.allLayers.add(layer);
this.layerMap.put(layer.getName(), layer);
this.same = false;
}
/** {@inheritDoc} */
@Override
public void setLayers(final List<LayerInterface> layers)
{
this.allLayers = new ArrayList<>(layers);
this.visibleLayers = new ArrayList<>(layers);
this.layerMap.clear();
for (LayerInterface layer : layers)
{
this.layerMap.put(layer.getName(), layer);
}
this.same = false;
}
/** {@inheritDoc} */
@Override
public void setLayer(final int index, final LayerInterface layer)
{
this.allLayers.set(index, layer);
if (this.allLayers.size() == this.visibleLayers.size())
{
this.visibleLayers.add(index, layer);
}
else
{
this.visibleLayers.add(layer);
}
this.layerMap.put(layer.getName(), layer);
this.same = false;
}
/** {@inheritDoc} */
@Override
public void hideLayer(final LayerInterface layer)
{
this.visibleLayers.remove(layer);
this.same = false;
}
/** {@inheritDoc} */
@Override
public void hideLayer(final String layerName) throws RemoteException
{
if (this.layerMap.keySet().contains(layerName))
{
hideLayer(this.layerMap.get(layerName));
}
this.same = false;
}
/** {@inheritDoc} */
@Override
public void showLayer(final LayerInterface layer)
{
this.visibleLayers.add(layer);
this.same = false;
}
/** {@inheritDoc} */
@Override
public void showLayer(final String layerName) throws RemoteException
{
if (this.layerMap.keySet().contains(layerName))
{
showLayer(this.layerMap.get(layerName));
}
this.same = false;
}
/** {@inheritDoc} */
@Override
public boolean isSame() throws RemoteException
{
boolean ret = this.same;
this.same = true;
return ret;
}
/** {@inheritDoc} */
@Override
@SuppressWarnings("checkstyle:methodlength")
public Graphics2D drawMap(final Graphics2D graphics) throws DsolGisException
{
if (this.drawBackground)
{
// We fill the background.
graphics.setColor(this.getImage().getBackgroundColor());
graphics.fillRect(0, 0, (int) this.getImage().getSize().getWidth(), (int) this.getImage().getSize().getHeight());
}
// We compute the transform of the map
AffineTransform transform = new AffineTransform();
transform.scale(this.getImage().getSize().getWidth() / this.extent.getDeltaX(),
-this.getImage().getSize().getHeight() / this.extent.getDeltaY());
transform.translate(-this.extent.getMinX(), -this.extent.getMinY() - this.extent.getDeltaY());
AffineTransform antiTransform = null;
try
{
antiTransform = transform.createInverse();
}
catch (NoninvertibleTransformException e)
{
CategoryLogger.always().error(e);
}
// we cache the scale
double scale = this.getScale();
// XXX: define how we use this -- System.out.println("scale = " + scale);
// we set the rendering hints
graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
// We loop over the layers
for (Iterator<LayerInterface> i = this.visibleLayers.iterator(); i.hasNext();)
{
Layer layer = (Layer) i.next();
try
{
if (layer.isDisplay()) // TODO: && layer.getMaxScale() < scale && layer.getMinScale() > scale)
{
for (FeatureInterface feature : layer.getFeatures())
{
List<GisObject> shapes = feature.getShapes(this.extent);
SerializablePath shape = null;
for (Iterator<GisObject> shapeIterator = shapes.iterator(); shapeIterator.hasNext();)
{
GisObject gisObject = shapeIterator.next();
// if (feature.getDataSource().getType() == POINT)
// {
// shape = new SerializablePath();
// Point2D point = (Point2D) gisObject.getShape();
// shape.moveTo((float) point.getX(), (float) point.getY());
// // TODO: points are not drawn -- we have to do this differently
// }
// else
// {
shape = (SerializablePath) gisObject.getShape();
// }
if (layer.isTransform())
{
shape.transform(transform);
}
if (/*feature.getDataSource().getType() == POLYGON &&*/ feature.getFillColor() != null)
{
graphics.setColor(feature.getFillColor());
graphics.fill(shape);
}
if (feature.getOutlineColor() != null)
{
graphics.setColor(feature.getOutlineColor());
graphics.draw(shape);
}
if (layer.isTransform())
{
shape.transform(antiTransform);
}
}
}
}
}
catch (Exception exception)
{
CategoryLogger.always().error(exception);
throw new DsolGisException(exception.getMessage());
}
}
return graphics;
}
/** {@inheritDoc} */
@Override
public Bounds2d getExtent()
{
return this.extent;
}
/** {@inheritDoc} */
@Override
public MapImageInterface getImage()
{
return this.image;
}
/** {@inheritDoc} */
@Override
public ImmutableList<LayerInterface> getAllLayers()
{
return new ImmutableArrayList<>(this.allLayers);
}
/** {@inheritDoc} */
@Override
public ImmutableList<LayerInterface> getVisibleLayers()
{
return new ImmutableArrayList<>(this.visibleLayers);
}
/** {@inheritDoc} */
@Override
public ImmutableMap<String, LayerInterface> getLayerMap() throws RemoteException
{
return new ImmutableHashMap<>(this.layerMap);
}
/** {@inheritDoc} */
@Override
public String getName()
{
return this.name;
}
/** {@inheritDoc} */
@Override
public double getScale()
{
return (this.getImage().getSize().getWidth() / (2.54 * RESOLUTION)) * this.extent.getDeltaX();
}
/**
* returns the scale of the Image.
* @return double the unitPerPixel
*/
@Override
public double getUnitImageRatio()
{
return Math.min(this.extent.getDeltaX() / this.image.getSize().getWidth(),
this.extent.getDeltaY() / this.image.getSize().getHeight());
}
/** {@inheritDoc} */
@Override
public MapUnits getUnits()
{
return this.units;
}
/** {@inheritDoc} */
@Override
public void setExtent(final Bounds2d extent)
{
this.extent = extent;
}
/** {@inheritDoc} */
@Override
public void setImage(final MapImageInterface image)
{
this.image = image;
}
/** {@inheritDoc} */
@Override
public void setName(final String name)
{
this.name = name;
}
/** {@inheritDoc} */
@Override
public void setUnits(final MapUnits units)
{
this.units = units;
}
/** {@inheritDoc} */
@Override
public void zoom(final double zoomFactor)
{
double correcteddZoomFactor = (zoomFactor == 0) ? 1 : zoomFactor;
double maxX = (getUnitImageRatio() * this.getImage().getSize().getWidth()) + this.extent.getMinX();
double maxY = (getUnitImageRatio() * this.getImage().getSize().getHeight()) + this.extent.getMinY();
double centerX = (maxX - this.extent.getMinX()) / 2 + this.extent.getMinX();
double centerY = (maxY - this.extent.getMinY()) / 2 + this.extent.getMinY();
double width = (1.0 / correcteddZoomFactor) * (maxX - this.extent.getMinX());
double height = (1.0 / correcteddZoomFactor) * (maxY - this.extent.getMinY());
this.extent =
new Bounds2d(centerX - 0.5 * width, centerX + 0.5 * width, centerY - 0.5 * height, centerY + 0.5 * height);
}
/** {@inheritDoc} */
@Override
public void zoomPoint(final Point2D pixelPosition, final double zoomFactor)
{
double correcteddZoomFactor = (zoomFactor == 0) ? 1 : zoomFactor;
double maxX = (getUnitImageRatio() * this.getImage().getSize().getWidth()) + this.extent.getMinX();
double maxY = (getUnitImageRatio() * this.getImage().getSize().getHeight()) + this.extent.getMinY();
double centerX = (pixelPosition.getX() / this.getImage().getSize().getWidth()) * (maxX - this.extent.getMinX())
+ this.extent.getMinX();
double centerY = maxY - (pixelPosition.getY() / this.getImage().getSize().getHeight()) * (maxY - this.extent.getMinY());
double width = (1.0 / correcteddZoomFactor) * (maxX - this.extent.getMinX());
double height = (1.0 / correcteddZoomFactor) * (maxY - this.getExtent().getMinY());
this.extent =
new Bounds2d(centerX - 0.5 * width, centerX + 0.5 * width, centerY - 0.5 * height, centerY + 0.5 * height);
}
/** {@inheritDoc} */
@Override
public void zoomRectangle(final SerializableRectangle2d rectangle)
{
double maxX = (getUnitImageRatio() * this.getImage().getSize().getWidth()) + this.extent.getMinX();
double maxY = (getUnitImageRatio() * this.getImage().getSize().getHeight()) + this.extent.getMinY();
double width = maxX - this.extent.getMinX();
double height = maxY - this.extent.getMinY();
double minX = this.extent.getMinX() + (rectangle.getMinX() / this.getImage().getSize().getWidth()) * width;
double minY = this.extent.getMinY()
+ ((this.getImage().getSize().getHeight() - rectangle.getMaxY()) / this.getImage().getSize().getHeight())
* height;
maxX = minX + (rectangle.getWidth() / this.getImage().getSize().getWidth()) * width;
maxY = minY + ((this.getImage().getSize().getHeight() - rectangle.getHeight()) / this.getImage().getSize().getHeight())
* height;
this.extent = new Bounds2d(minX, maxX, minY, maxY);
}
/** {@inheritDoc} */
@Override
public boolean isDrawBackground()
{
return this.drawBackground;
}
/** {@inheritDoc} */
@Override
public void setDrawBackground(final boolean drawBackground)
{
this.drawBackground = drawBackground;
}
}