View Javadoc
1   package nl.tudelft.simulation.dsol.animation.gis.osm;
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 OSM maps.
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 OsmRenderable2d 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 map to use.
67       */
68      public OsmRenderable2d(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 map to use.
77       * @param coordinateTransform CoordinateTransform; the transformation of (x, y) coordinates to (x', y') coordinates.
78       */
79      public OsmRenderable2d(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 an existing Map.
87       * @param contextProvider Contextualized; the object that can provide the context to store the animation objects
88       * @param map MapInterface; the 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 OsmRenderable2d(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             this.map.setDrawBackground(false);
135 
136             // is the extent or the screen size still the same
137             if (extent.equals(this.cachedExtent) && screen.equals(this.cachedScreenSize) && this.map.isSame())
138             {
139                 graphics.drawImage(this.cachedImage, 0, 0, null);
140                 return;
141             }
142             this.map.setExtent(extent);
143             this.map.getImage().setSize(screen);
144             this.cacheImage();
145             this.paintComponent(graphics, extent, screen, renderableScale, observer);
146         }
147         catch (Exception exception)
148         {
149             CategoryLogger.always().warn(exception, "paint");
150         }
151     }
152 
153     /** {@inheritDoc} */
154     @Override
155     public OsmRenderable2d getSource()
156     {
157         return this;
158     }
159 
160     /** {@inheritDoc} */
161     @Override
162     public Bounds3d getBounds()
163     {
164         return this.bounds;
165     }
166 
167     /** {@inheritDoc} */
168     @Override
169     public OrientedPoint3d getLocation()
170     {
171         return this.location;
172     }
173 
174     /**
175      * @return map the Shapefile map
176      */
177     @Override
178     public GisMapInterface getMap()
179     {
180         return this.map;
181     }
182 
183     /**
184      * caches the GIS map by creating an image. This prevents continuous rendering.
185      * @throws Exception on graphicsProblems and network connection failures.
186      */
187     private void cacheImage() throws Exception
188     {
189         this.cachedImage = new BufferedImage((int) this.map.getImage().getSize().getWidth(),
190                 (int) this.map.getImage().getSize().getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
191         Graphics2D bg = this.cachedImage.createGraphics();
192         this.map.drawMap(bg);
193         bg.dispose();
194         this.cachedScreenSize = (Dimension) this.map.getImage().getSize().clone();
195         this.cachedExtent = this.map.getExtent();
196         this.location = new OrientedPoint3d(this.cachedExtent.midPoint().getX(), this.cachedExtent.midPoint().getY(),
197                 -Double.MIN_VALUE);
198         this.bounds = new Bounds3d(this.cachedExtent.getDeltaX(), this.cachedExtent.getDeltaY(), 0.0);
199     }
200 
201     /** {@inheritDoc} */
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 }