package nl.tudelft.simulation.dsol.animation.D2;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.net.URL;
import java.rmi.RemoteException;
import javax.media.j3d.Bounds;
import javax.naming.NamingException;
import nl.javel.gisbeans.io.esri.CoordinateTransform;
import nl.javel.gisbeans.map.MapInterface;
import nl.javel.gisbeans.map.mapfile.MapFileXMLParser;
import nl.tudelft.simulation.dsol.animation.Locatable;
import nl.tudelft.simulation.dsol.logger.SimLogger;
import nl.tudelft.simulation.dsol.simulators.AnimatorInterface;
import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
import nl.tudelft.simulation.language.d3.BoundingBox;
import nl.tudelft.simulation.language.d3.CartesianPoint;
import nl.tudelft.simulation.language.d3.DirectedPoint;
import nl.tudelft.simulation.naming.context.ContextInterface;
import nl.tudelft.simulation.naming.context.util.ContextUtil;
/**
* This renderable draws CAD/GIS objects.
*
* Copyright (c) 2002-2020 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
* for project information https://simulation.tudelft.nl. The DSOL
* project is distributed under a three-clause BSD-style license, which can be found at
*
* https://simulation.tudelft.nl/dsol/3.0/license.html.
*
* @author Peter Jacobs
* @since 1.5
*/
public class GisRenderable2D implements Renderable2DInterface, Locatable
{
/** */
private static final long serialVersionUID = 20200108L;
/** the map to display */
protected MapInterface map = null;
/** the image cached image. */
protected BufferedImage cachedImage = null;
/** the cached extent. */
protected Rectangle2D cachedExtent = new Rectangle2D.Double();
/** the cached screenSize. */
protected Dimension cachedScreenSize = new Dimension();
/** the location of the map. */
protected DirectedPoint location = null;
/** the bounds of the map. */
protected Bounds bounds = null;
/** the context for (un)binding. */
protected ContextInterface context;
/** the logger. */
private final SimLogger logger;
/**
* constructs a new GisRenderable2D.
* @param simulator SimulatorInterface<?,?,?>; the simulator.
* @param mapFile URL; the mapfile to use.
*/
public GisRenderable2D(final SimulatorInterface, ?, ?> simulator, final URL mapFile)
{
this(simulator, mapFile, new CoordinateTransform.NoTransform());
}
/**
* constructs a new GisRenderable2D.
* @param simulator SimulatorInterface<?,?,?>; the simulator.
* @param mapFile URL; the mapfile to use.
* @param coordinateTransform CoordinateTransform; the transformation of (x, y) coordinates to (x', y') coordinates.
*/
public GisRenderable2D(final SimulatorInterface, ?, ?> simulator, final URL mapFile,
final CoordinateTransform coordinateTransform)
{
this(simulator, mapFile, coordinateTransform, -Double.MAX_VALUE);
}
/**
* constructs a new GisRenderable2D.
* @param simulator SimulatorInterface<?,?,?>; the simulator.
* @param mapFile URL; the mapfile to use.
* @param coordinateTransform CoordinateTransform; the transformation of (x, y) coordinates to (x', y') coordinates.
* @param z double; the z-value to use
*/
public GisRenderable2D(final SimulatorInterface, ?, ?> simulator, final URL mapFile,
final CoordinateTransform coordinateTransform, final double z)
{
this.logger = simulator.getLogger();
if (!(simulator instanceof AnimatorInterface))
{
return;
}
try
{
this.map = MapFileXMLParser.parseMapFile(mapFile, coordinateTransform);
this.location =
new DirectedPoint(new CartesianPoint(this.cachedExtent.getCenterX(), this.cachedExtent.getCenterY(), z));
this.bounds = new BoundingBox(this.cachedExtent.getWidth(), this.cachedExtent.getHeight(), 0.0);
// XXX simulator.getReplication().getTreatment().getProperties().put("animationPanel.extent",
// XXX this.map.getExtent());
this.bind2Context(simulator);
}
catch (Exception exception)
{
simulator.getLogger().always().warn(exception, "");
}
}
/**
* constructs a new GisRenderable2D based on an existing Map.
* @param simulator SimulatorInterface<?,?,?>; the simulator.
* @param map MapInterface; the map to use.
* @param coordinateTransform CoordinateTransform; the transformation of (x, y) coordinates to (x', y') coordinates.
* @param z double; the z-value to use
*/
public GisRenderable2D(final SimulatorInterface, ?, ?> simulator, final MapInterface map,
final CoordinateTransform coordinateTransform, final double z)
{
this.logger = simulator.getLogger();
if (!(simulator instanceof AnimatorInterface))
{
return;
}
try
{
this.map = map;
this.location =
new DirectedPoint(new CartesianPoint(this.cachedExtent.getCenterX(), this.cachedExtent.getCenterY(), z));
this.bounds = new BoundingBox(this.cachedExtent.getWidth(), this.cachedExtent.getHeight(), 0.0);
// XXX simulator.getReplication().getTreatment().getProperties().put("animationPanel.extent",
// XXX this.map.getExtent());
this.bind2Context(simulator);
}
catch (Exception exception)
{
simulator.getLogger().always().warn(exception, "");
}
}
/**
* binds a renderable2D to the context. The reason for specifying this in an independent method instead of adding the code
* in the constructor is related to the RFE submitted by van Houten that in specific distributed context, such binding must
* be overwritten.
* @param simulator SimulatorInterface<?,?,?>; the simulator used for binding the object.
*/
protected void bind2Context(final SimulatorInterface, ?, ?> simulator)
{
try
{
this.context = ContextUtil.lookupOrCreateSubContext(simulator.getReplication().getContext(), "animation/2D");
this.context.bindObject(this);
}
catch (NamingException | RemoteException exception)
{
simulator.getLogger().always().warn(exception, "");
}
}
/** {@inheritDoc} */
@Override
public void paint(final Graphics2D graphics, final Rectangle2D extent, final Dimension screen, final ImageObserver observer)
{
try
{
// is the extent or the screensize still the same
if (extent.equals(this.cachedExtent) && screen.equals(this.cachedScreenSize) && this.map.isSame())
{
graphics.drawImage(this.cachedImage, 0, 0, null);
return;
}
this.map.setExtent((Rectangle2D) extent.clone());
this.map.getImage().setSize(screen);
this.cacheImage();
this.paint(graphics, extent, screen, observer);
}
catch (Exception exception)
{
this.logger.always().warn(exception, "paint");
}
}
/** {@inheritDoc} */
@Override
public GisRenderable2D getSource()
{
return this;
}
/** {@inheritDoc} */
@Override
public Bounds getBounds()
{
return this.bounds;
}
/** {@inheritDoc} */
@Override
public DirectedPoint getLocation()
{
return this.location;
}
/**
* @return map the Shapefile map
*/
public final MapInterface getMap()
{
return this.map;
}
/**
* caches the GIS map by creating an image. This prevents continuous rendering.
* @throws Exception on graphicsProblems and network connection failures.
*/
private void cacheImage() throws Exception
{
this.cachedImage = new BufferedImage((int) this.map.getImage().getSize().getWidth(),
(int) this.map.getImage().getSize().getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
Graphics2D bg = this.cachedImage.createGraphics();
this.map.drawMap(bg);
bg.dispose();
this.cachedScreenSize = (Dimension) this.map.getImage().getSize().clone();
this.cachedExtent = this.map.getExtent();
this.location = new DirectedPoint(
new CartesianPoint(this.cachedExtent.getCenterX(), this.cachedExtent.getCenterY(), -Double.MIN_VALUE));
this.bounds = new BoundingBox(this.cachedExtent.getWidth(), this.cachedExtent.getHeight(), 0.0);
}
/**
* destroys an RenderableObject by unsubscribing it from the context.
*/
@Override
public void destroy()
{
try
{
this.context.unbindObject(this.toString());
}
catch (Throwable throwable)
{
this.logger.always().warn(throwable, "finalize");
}
}
/** {@inheritDoc} */
@Override
public boolean contains(final Point2D pointWorldCoordinates, final Rectangle2D extent, final Dimension screenSize)
{
return false;
}
}