View Javadoc
1   package nl.tudelft.simulation.dsol.animation.gis.esri;
2   
3   import java.awt.Dimension;
4   import java.awt.Graphics2D;
5   import java.awt.image.BufferedImage;
6   import java.awt.image.ImageObserver;
7   import java.rmi.RemoteException;
8   
9   import javax.naming.NamingException;
10  
11  import org.djutils.draw.bounds.Bounds2d;
12  import org.djutils.draw.bounds.Bounds3d;
13  import org.djutils.draw.point.OrientedPoint3d;
14  import org.djutils.draw.point.Point2d;
15  import org.djutils.logger.CategoryLogger;
16  
17  import nl.tudelft.simulation.dsol.animation.d2.RenderableScale;
18  import nl.tudelft.simulation.dsol.animation.gis.GisMapInterface;
19  import nl.tudelft.simulation.dsol.animation.gis.GisRenderable2d;
20  import nl.tudelft.simulation.dsol.animation.gis.transform.CoordinateTransform;
21  import nl.tudelft.simulation.naming.context.Contextualized;
22  import nl.tudelft.simulation.naming.context.util.ContextUtil;
23  
24  /**
25   * This renderable draws CAD/GIS objects.
26   * <p>
27   * Copyright (c) 2020-2024 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
28   * for project information <a href="https://simulation.tudelft.nl/dsol/manual/" target="_blank">DSOL Manual</a>. The DSOL
29   * project is distributed under a three-clause BSD-style license, which can be found at
30   * <a href="https://https://simulation.tudelft.nl/dsol/docs/latest/license.html" target="_blank">DSOL License</a>.
31   * </p>
32   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
33   */
34  public class EsriRenderable2d implements GisRenderable2d
35  {
36      /** */
37      private static final long serialVersionUID = 20200108L;
38  
39      /** the map to display. */
40      @SuppressWarnings("checkstyle:visibilitymodifier")
41      protected GisMapInterface map = null;
42  
43      /** the image cached image. */
44      @SuppressWarnings("checkstyle:visibilitymodifier")
45      protected BufferedImage cachedImage = null;
46  
47      /** the cached extent. */
48      @SuppressWarnings("checkstyle:visibilitymodifier")
49      protected Bounds2d cachedExtent = new Bounds2d(0, 0, 0, 0);
50  
51      /** the cached screenSize. */
52      @SuppressWarnings("checkstyle:visibilitymodifier")
53      protected Dimension cachedScreenSize = new Dimension();
54  
55      /** the location of the map. */
56      @SuppressWarnings("checkstyle:visibilitymodifier")
57      protected OrientedPoint3d location = null;
58  
59      /** the bounds of the map. */
60      @SuppressWarnings("checkstyle:visibilitymodifier")
61      protected Bounds3d bounds = null;
62  
63      /**
64       * constructs a new GisRenderable2d.
65       * @param contextProvider Contextualized; the object that can provide the context to store the animation objects
66       * @param map MapInterface; the parsed map to use
67       */
68      public EsriRenderable2d(final Contextualized contextProvider, final GisMapInterface map)
69      {
70          this(contextProvider, map, new CoordinateTransform.NoTransform());
71      }
72  
73      /**
74       * constructs a new GisRenderable2d.
75       * @param contextProvider Contextualized; the object that can provide the context to store the animation objects
76       * @param map MapInterface; the parsed map to use
77       * @param coordinateTransform CoordinateTransform; the transformation of (x, y) coordinates to (x', y') coordinates.
78       */
79      public EsriRenderable2d(final Contextualized contextProvider, final GisMapInterface map,
80              final CoordinateTransform coordinateTransform)
81      {
82          this(contextProvider, map, coordinateTransform, -Double.MAX_VALUE);
83      }
84  
85      /**
86       * constructs a new GisRenderable2d based on a parsed Map.
87       * @param contextProvider Contextualized; the object that can provide the context to store the animation objects
88       * @param map MapInterface; the parsed map to use
89       * @param coordinateTransform CoordinateTransform; the transformation of (x, y) coordinates to (x', y') coordinates
90       * @param z double; the z-value to use
91       */
92      public EsriRenderable2d(final Contextualized contextProvider, final GisMapInterface map,
93              final CoordinateTransform coordinateTransform, final double z)
94      {
95          try
96          {
97              this.map = map;
98              this.location = new OrientedPoint3d(this.cachedExtent.midPoint().getX(), this.cachedExtent.midPoint().getY(), z);
99              this.bounds = new Bounds3d(this.cachedExtent.getDeltaX(), this.cachedExtent.getDeltaY(), 0.0);
100             this.bind2Context(contextProvider);
101         }
102         catch (Exception exception)
103         {
104             CategoryLogger.always().warn(exception, "<init>");
105         }
106     }
107 
108     /**
109      * binds a renderable2D to the context. The reason for specifying this in an independent method instead of adding the code
110      * in the constructor is related to the RFE submitted by van Houten that in specific distributed context, such binding must
111      * be overwritten.
112      * @param contextProvider Contextualized; the object that can provide the context to store the animation objects
113      */
114     protected void bind2Context(final Contextualized contextProvider)
115     {
116         try
117         {
118             ContextUtil.lookupOrCreateSubContext(contextProvider.getContext(), "animation/2D")
119                     .bindObject(Integer.toString(System.identityHashCode(this)), this);
120         }
121         catch (NamingException | RemoteException exception)
122         {
123             CategoryLogger.always().warn(exception, "<init>");
124         }
125     }
126 
127     /** {@inheritDoc} */
128     @Override
129     public void paintComponent(final Graphics2D graphics, final Bounds2d extent, final Dimension screen,
130             final RenderableScale renderableScale, final ImageObserver observer)
131     {
132         try
133         {
134             // is the extent or the screen size still the same
135             if (extent.equals(this.cachedExtent) && screen.equals(this.cachedScreenSize) && this.map.isSame())
136             {
137                 graphics.drawImage(this.cachedImage, 0, 0, null);
138                 return;
139             }
140             this.map.setExtent(extent);
141             this.map.getImage().setSize(screen);
142             this.cacheImage();
143             this.paintComponent(graphics, extent, screen, renderableScale, observer);
144         }
145         catch (Exception exception)
146         {
147             CategoryLogger.always().warn(exception, "paint");
148         }
149     }
150 
151     /** {@inheritDoc} */
152     @Override
153     public EsriRenderable2d getSource()
154     {
155         return this;
156     }
157 
158     /** {@inheritDoc} */
159     @Override
160     public Bounds3d getBounds()
161     {
162         return this.bounds;
163     }
164 
165     /** {@inheritDoc} */
166     @Override
167     public OrientedPoint3d getLocation()
168     {
169         return this.location;
170     }
171 
172     /**
173      * @return map the Shapefile map
174      */
175     @Override
176     public GisMapInterface getMap()
177     {
178         return this.map;
179     }
180 
181     /**
182      * caches the GIS map by creating an image. This prevents continuous rendering.
183      * @throws Exception on graphicsProblems and network connection failures.
184      */
185     private void cacheImage() throws Exception
186     {
187         this.cachedImage = new BufferedImage((int) this.map.getImage().getSize().getWidth(),
188                 (int) this.map.getImage().getSize().getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
189         Graphics2D bg = this.cachedImage.createGraphics();
190         this.map.drawMap(bg);
191         bg.dispose();
192         this.cachedScreenSize = (Dimension) this.map.getImage().getSize().clone();
193         this.cachedExtent = this.map.getExtent();
194         this.location = new OrientedPoint3d(this.cachedExtent.midPoint().getX(), this.cachedExtent.midPoint().getY(),
195                 -Double.MIN_VALUE);
196         this.bounds = new Bounds3d(this.cachedExtent.getDeltaX(), this.cachedExtent.getDeltaY(), 0.0);
197     }
198 
199     /**
200      * destroys an RenderableObject by unsubscribing it from the context.
201      */
202     @Override
203     public void destroy(final Contextualized contextProvider)
204     {
205         try
206         {
207             ContextUtil.lookupOrCreateSubContext(contextProvider.getContext(), "animation/2D")
208                     .unbindObject(Integer.toString(System.identityHashCode(this)));
209         }
210         catch (Throwable throwable)
211         {
212             CategoryLogger.always().warn(throwable, "finalize");
213         }
214     }
215 
216     /** {@inheritDoc} */
217     @Override
218     public boolean contains(final Point2d pointWorldCoordinates, final Bounds2d extent)
219     {
220         return false;
221     }
222 
223     /** {@inheritDoc} */
224     @Override
225     public long getId()
226     {
227         return -1; // drawn before the rest in case all z-values are the same
228     }
229 
230 }