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