Statistics
| Revision:

svn-gvsig-desktop / trunk / org.gvsig.desktop / org.gvsig.desktop.compat.cdc / org.gvsig.fmap.mapcontext / org.gvsig.fmap.mapcontext.api / src / main / java / org / gvsig / fmap / mapcontext / ViewPort.java @ 47560

History | View | Annotate | Download (57 KB)

1
/**
2
 * gvSIG. Desktop Geographic Information System.
3
 *
4
 * Copyright (C) 2007-2013 gvSIG Association.
5
 *
6
 * This program is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU General Public License
8
 * as published by the Free Software Foundation; either version 3
9
 * of the License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
 * MA  02110-1301, USA.
20
 *
21
 * For any additional information, do not hesitate to contact us
22
 * at info AT gvsig.com, or visit our website www.gvsig.com.
23
 */
24
package org.gvsig.fmap.mapcontext;
25

    
26
import java.awt.Color;
27
import java.awt.Dimension;
28
import java.awt.Toolkit;
29
import java.awt.geom.AffineTransform;
30
import java.awt.geom.NoninvertibleTransformException;
31
import java.awt.geom.Point2D;
32
import java.awt.geom.Rectangle2D;
33
import java.lang.ref.WeakReference;
34
import java.util.ArrayList;
35
import java.util.Collection;
36
import java.util.Collections;
37
import java.util.ConcurrentModificationException;
38
import java.util.List;
39
import java.util.Objects;
40

    
41
import org.cresques.cts.GeoCalc;
42
import org.cresques.cts.IProjection;
43
import org.gvsig.compat.CompatLocator;
44
import org.slf4j.Logger;
45
import org.slf4j.LoggerFactory;
46

    
47
import org.gvsig.fmap.geom.Geometry.SUBTYPES;
48
import org.gvsig.fmap.geom.Geometry;
49
import org.gvsig.fmap.geom.GeometryLocator;
50
import org.gvsig.fmap.geom.GeometryManager;
51
import org.gvsig.fmap.geom.exception.CreateEnvelopeException;
52
import org.gvsig.fmap.geom.exception.CreateGeometryException;
53
import org.gvsig.fmap.geom.primitive.Envelope;
54
import org.gvsig.fmap.geom.primitive.Point;
55
import org.gvsig.fmap.mapcontext.events.ColorEvent;
56
import org.gvsig.fmap.mapcontext.events.ExtentEvent;
57
import org.gvsig.fmap.mapcontext.events.ProjectionEvent;
58
import org.gvsig.fmap.mapcontext.events.listeners.ViewPortListener;
59
import org.gvsig.timesupport.Time;
60
import org.gvsig.tools.ToolsLocator;
61
import org.gvsig.tools.dynobject.DynStruct;
62
import org.gvsig.tools.lang.Cloneable;
63
import org.gvsig.tools.persistence.PersistenceManager;
64
import org.gvsig.tools.persistence.Persistent;
65
import org.gvsig.tools.persistence.PersistentState;
66
import org.gvsig.tools.persistence.exception.PersistenceException;
67
import org.gvsig.tools.util.Callable;
68

    
69
/**
70
 * <p>
71
 * <code>ViewPort</code> class represents the logic needed to transform a
72
 * rectangular area of a map to the available area in screen to display it.
73
 * </p>
74
 * <p>
75
 * Includes an affine transformation, between the rectangular area selected of
76
 * the external map, in its own <i>map coordinates</i>, to the rectangular area
77
 * available of a view in <i>screen coordinates</i>.
78
 * </p>
79
 * <p>
80
 * Elements:
81
 * <ul>
82
 * <li><i>extent</i>: the area selected of the map, in <i>map coordinates</i>.
83
 * <li><i>imageSize</i>: width and height in pixels (<i>screen coordinates</i>)
84
 * of the area available in screen to display the area selected of the map.
85
 * <li><i>adjustedExtent</i>: the area selected must be an scale of
86
 * <i>imageSize</i>.<br>
87
 * This implies adapt the extent, preserving and centering it, and adding around
88
 * the needed area to fill all the image size. That added area will be extracted
89
 * from the original map, wherever exists, and filled with the background color
90
 * wherever not.
91
 * <li><i>scale</i>: the scale between the adjusted extent and the image size.
92
 * <li><i>backColor</i>: the default background color in the view, if there is
93
 * no map.
94
 * <li><i>trans</i>: the affine transformation.
95
 * <li><i>proj</i>: map projection used in this view.
96
 * <li><i>distanceUnits</i>: distance measurement units, of data in screen.
97
 * <li><i>mapUnits</i>: measurement units, of data in map.
98
 * <li><i>extents</i>: an {@link ExtentHistory ExtentHistory} with the last
99
 * previous extents.
100
 * <li><i>offset</i>: position in pixels of the available rectangular area,
101
 * where start drawing the map.
102
 * <li><i>dist1pixel</i>: the distance in <i>world coordinates</i> equivalent to
103
 * 1 pixel in the view with the current extent.
104
 * <li><i>dist3pixel</i>: the distance in <i>world coordinates</i> equivalent to
105
 * 3 pixels in the view with the current extent.
106
 * <li><i>listeners</i>: list with the {@link ViewPortListener ViewPortListener}
107
 * registered.
108
 * </ul>
109
 * </p>
110
 *
111
 * @author Vicente Caballero Navarro
112
 */
113
public class ViewPort implements Persistent, Cloneable {
114

    
115
  private static final String FIELD_DISTANCE_AREA = "distanceArea";
116

    
117
  private static final String FIELD_IMAGE_SIZE = "imageSize";
118

    
119
  private static final String FIELD_PROJ = "proj";
120

    
121
  private static final String FIELD_OFFSET = "offset";
122

    
123
  private static final String FIELD_MAP_UNITS = "mapUnits";
124

    
125
  private static final String FIELD_EXTENT = "extent";
126

    
127
  private static final String FIELD_EXTENTS = "extents";
128

    
129
  private static final String FIELD_DISTANCE_UNITS = "distanceUnits";
130

    
131
  private static final String FIELD_DIST3PIXEL = "dist3pixel";
132

    
133
  private static final String FIELD_DIST1PIXEL = "dist1pixel";
134

    
135
  private static final String FIELD_CLIP = "clip";
136

    
137
  private static final String FIELD_BACK_COLOR = "backColor";
138

    
139
  private static final String FIELD_ADJUSTED_EXTENT = "adjustedExtent";
140

    
141
  private static final GeometryManager geomManager = GeometryLocator
142
      .getGeometryManager();
143

    
144
  private static final Logger logger = LoggerFactory.getLogger(ViewPort.class);
145

    
146
  /**
147
   * <p>
148
   * Area selected by user using some tool.
149
   * </p>
150
   * <p>
151
   * When the zoom changes (for instance when using the zoom in or zoom out
152
   * tools, but also zooming to a selected feature or shape) the extent that
153
   * covers that area is the value returned by this method. It is not the actual
154
   * area shown in the view because it does not care about the aspect ratio of
155
   * the available area. However, any part of the real world contained in this
156
   * extent is shown in the view.
157
   * </p>
158
   * <p>
159
   * Probably this is not what you are looking for. If you are looking for the
160
   * complete extent currently shown, you must use
161
   * {@linkplain #getAdjustedExtent()} method which returns the extent that
162
   * contains this one but regarding the current view's aspect ratio.
163
   * </p>
164
   *
165
   * @see #getExtent()
166
   * @see #setEnvelope(Envelope)
167
   */
168
  protected Rectangle2D extent;
169

    
170
  protected Time time;
171

    
172
  /**
173
   * <p>
174
   * Location and dimensions of the extent adjusted to the image size.
175
   * </p>
176
   *
177
   * @see #getAdjustedExtent()
178
   */
179
  protected Rectangle2D adjustedExtent;
180

    
181
  /**
182
   * Draw version of the context. It's used for know when de componend has
183
   * changed any visualization property
184
   *
185
   * @see getDrawVersion
186
   * @see updateDrawVersion
187
   */
188
  private long drawVersion = 0L;
189

    
190
  /**
191
   * <p>
192
   * History with the last extents of the view.
193
   * </p>
194
   *
195
   * @see #setPreviousExtent()
196
   * @see #getExtents()
197
   */
198
  protected ExtentHistory extentsHistory = new ExtentHistory();
199

    
200
  /**
201
   * <p>
202
   * Size in <i>screen coordinates</i> of the rectangle where the image is
203
   * displayed.
204
   * </p>
205
   * <p>
206
   * Used by {@linkplain #calculateAffineTransform()} to calculate:<br>
207
   * <ul>
208
   * <li>The new {@link #scale scale} .
209
   * <li>The new {@link #adjustedExtent adjustableExtent} .
210
   * <li>The new {@link #trans trans} .
211
   * <li>The new real world coordinates equivalent to 1 pixel (
212
   * {@link #dist1pixel dist1pixel}) .
213
   * <li>The new real world coordinates equivalent to 3 pixels (
214
   * {@link #dist3pixel dist3pixel}) .
215
   * </ul>
216
   * </p>
217
   *
218
   * @see #getImageSize()
219
   * @see #getImageHeight()
220
   * @see #getImageWidth()
221
   * @see #setImageSize(Dimension)
222
   */
223
  private Dimension imageSize;
224

    
225
  /**
226
   * <p>
227
   * the affine transformation between the {@link #extent extent} in <i>map 2D
228
   * coordinates</i> to the image area in the screen, in <i>screen 2D
229
   * coordinates</i> (pixels).
230
   * </p>
231
   *
232
   * @see AffineTransform
233
   * @see #getAffineTransform()
234
   * @see #setAffineTransform(AffineTransform)
235
   * @see #calculateAffineTransform()
236
   */
237
  private AffineTransform trans = new AffineTransform();
238

    
239
  /**
240
   * <p>
241
   * Measurement unit used for measuring distances and displaying information.
242
   * </p>
243
   *
244
   * @see #getDistanceUnits()
245
   * @see #setDistanceUnits(int)
246
   */
247
  private int distanceUnits = 1;
248

    
249
  /**
250
   * <p>
251
   * Measurement unit used for measuring areas and displaying information.
252
   * </p>
253
   *
254
   * @see #getDistanceArea()
255
   * @see #setDistanceArea(int)
256
   */
257
  private int distanceArea = 1;
258

    
259
  /**
260
   * <p>
261
   * Measurement unit used by this view port for the map.
262
   * </p>
263
   *
264
   * @see #getMapUnits()
265
   * @see #setMapUnits(int)
266
   */
267
  private int mapUnits = 1;
268

    
269
  /**
270
   * <p>
271
   * Array with the {@link ViewPortListener ViewPortListener}s registered to
272
   * this view port.
273
   * </p>
274
   *
275
   * @see #addViewPortListener(ViewPortListener)
276
   * @see #removeViewPortListener(ViewPortListener)
277
   */
278
  private List<ViewPortListener> listeners = new ArrayList<>();
279

    
280
  /**
281
   * <p>
282
   * The offset is the position where start drawing the map.
283
   * </p>
284
   * <p>
285
   * The offset of a <a href="http://www.gvsig.gva.es/">gvSIG</a>'s <i>View</i>
286
   * is always (0, 0) because the drawing area fits with the full window area.
287
   * But in a <a href="http://www.gvsig.gva.es/">gvSIG</a>'s <i>Layout</i> it's
288
   * up to the place where the <code>FFrameView</code> is located.
289
   * </p>
290
   *
291
   * @see #getOffset()
292
   * @see #setOffset(Point2D)
293
   */
294
  private Point2D offset = new Point2D.Double(0, 0);
295

    
296
  /**
297
   * <p>
298
   * Clipping area.
299
   * </p>
300
   */
301
  // private Rectangle2D clip;
302

    
303
  /**
304
   * <p>
305
   * Background color of this view.
306
   * </p>
307
   *
308
   * @see #getBackColor()
309
   * @see #setBackColor(Color)
310
   */
311
  private Color backColor = null; // Color.WHITE;
312

    
313
  /**
314
   * <p>
315
   * Information about the map projection used in this view.
316
   * </p>
317
   *
318
   * @see #getProjection()
319
   * @see #setProjection(IProjection)
320
   */
321
  private IProjection proj;
322

    
323
  /**
324
   * <p>
325
   * Represents the distance in <i>world coordinates</i> equivalent to 1 pixel
326
   * in the view with the current extent.
327
   * </p>
328
   *
329
   * @see #getDist1pixel()
330
   * @see #setDist1pixel(double)
331
   */
332
  private double dist1pixel;
333

    
334
  /**
335
   * <p>
336
   * Represents the distance in <i>world coordinates</i> equivalent to 3 pixels
337
   * in the view with the current extent.
338
   * </p>
339
   *
340
   * @see #getDist3pixel()
341
   * @see #setDist3pixel(double)
342
   */
343
  private double dist3pixel;
344

    
345
  /**
346
   * <p>
347
   * Ratio between the size of <code>imageSize</code> and <code>extent</code>: <br>
348
   * <i>
349
   *
350
   * <pre>
351
   * min{(imageSize.getHeight()/extent.getHeight(), imageSize.getWidth()/extent.getWidth())}
352
   * </pre>
353
   *
354
   * </i>
355
   * </p>
356
   */
357
  private double scale;
358

    
359
  /**
360
   * <p>
361
   * Clipping area.
362
   * </p>
363
   *
364
   * @see #setClipRect(Rectangle2D)
365
   */
366
  private Rectangle2D cliprect;
367

    
368
  /**
369
   * <p>
370
   * Enables or disables the <i>"adjustable extent"</i> mode.
371
   * </p>
372
   * <p>
373
   * When calculates the affine transform, if
374
   * <ul>
375
   * <li><i>enabled</i>: the new <code>adjustedExtent</code> will have the (X,
376
   * Y) coordinates of the <code>extent</code> and an area that will be an scale
377
   * of the image size. That area will have different height or width (not both)
378
   * of the extent according the least ratio (height or width) in
379
   *
380
   * <pre>
381
   * image.size/extent.size&quot;
382
   * </pre>.
383
   * <li><i>disabled</i>: the new <code>adjustedExtent</code> will be like
384
   * <code>extent</code>.
385
   * </ul>
386
   * </p>
387
   *
388
   * @see #setAdjustable(boolean)
389
   */
390
  private boolean adjustableExtent = true;
391

    
392
  /**
393
   * <p>
394
   * ViewPort resolution in <i>dots-per-inch</i>. Useful to calculate the
395
   * geographic scale of the view.
396
   * </p>
397
   *
398
   * @see Toolkit#getScreenResolution()
399
   * @see MapContext#getScaleView()
400
   */
401
  private Double dpi = null;
402

    
403
  public ViewPort() {
404

    
405
  }
406

    
407
  /**
408
   * <p>
409
   * Creates a new view port with the information of the projection in
410
   * <code>proj</code> argument, and default configuration:
411
   * </p>
412
   * <p>
413
   * <ul>
414
   * <li><i><code>distanceUnits</code></i> = meters
415
   * <li><i><code>mapUnits</code></i> = meters
416
   * <li><i><code>backColor</code></i> = <i>undefined</i>
417
   * <li><i><code>offset</code></i> = <code>new Point2D.Double(0, 0);</code>
418
   * </ul>
419
   * </p>
420
   *
421
   * @param proj information of the projection for this view port
422
   */
423
  public ViewPort(IProjection proj) {
424
    // Por defecto
425
    this.proj = proj;
426
  }
427

    
428
  /**
429
   * <p>
430
   * Changes the status of the <i>"adjustable extent"</i> option to enabled or
431
   * disabled.
432
   * </p>
433
   * <p>
434
   * If view port isn't adjustable, won't bear in mind the aspect ratio of the
435
   * available rectangular area to calculate the affine transform from the
436
   * original map in real coordinates. (Won't scale the image to adapt it to the
437
   * available rectangular area).
438
   * </p>
439
   *
440
   * @param boolean the boolean to be set
441
   */
442
  public void setAdjustable(boolean adjustable) {
443
    if (adjustable == adjustableExtent) {
444
      return;
445
    }
446
    adjustableExtent = adjustable;
447
    this.updateDrawVersion();
448
  }
449

    
450
    private Collection<ViewPortListener> getListeners() {
451
        List<ViewPortListener> ll = Collections.unmodifiableList(listeners);
452
        return ll;
453
    }
454
    
455
  /**
456
   * <p>
457
   * Appends the specified {@link ViewPortListener ViewPortListener} listener if
458
   * weren't.
459
   * </p>
460
   *
461
   * @param listener the listener to add
462
   * @return <code>true</code> if has been added successfully
463
   * @see #removeViewPortListener(ViewPortListener)
464
   */
465
    public boolean addViewPortListener(ViewPortListener listener) {
466
        if (listener == null) {
467
            return false;
468
        }
469
        if( this.listeners.contains(listener) )  {
470
            return false;
471
        }
472
        this.listeners.add(listener);
473
        return true;
474
    }
475

    
476
  /**
477
   * <p>
478
   * Removes the specified {@link ViewPortListener ViewPortListener} listener,
479
   * if existed.
480
   * </p>
481
   *
482
   * @param listener the listener to remove
483
   * @return <code>true</code> if the contained the specified listener.
484
   * @see #addViewPortListener(ViewPortListener)
485
   */
486
    public boolean removeViewPortListener(ViewPortListener listener) {
487
        if (listener == null) {
488
            return false;
489
        }
490
        return this.listeners.remove(listener);
491
    }
492

    
493
  /**
494
   * <p>
495
   * Converts and returns the distance <code>d</code>, that is in <i>map
496
   * coordinates</i> to <i>screen coordinates</i> using a <i>delta transform</i>
497
   * with the transformation affine information in the {@link #trans #trans}
498
   * attribute.
499
   * </p>
500
   *
501
   * @param d distance in <i>map coordinates</i>
502
   * @return distance equivalent in <i>screen coordinates</i>
503
   * @see #toMapDistance(int)
504
   * @see AffineTransform#deltaTransform(Point2D, Point2D)S
505
   */
506
  public int fromMapDistance(double d) {
507
    Point2D.Double pWorld = new Point2D.Double(1, 1);
508
    Point2D.Double pScreen = new Point2D.Double();
509

    
510
    try {
511
      trans.deltaTransform(pWorld, pScreen);
512
    }
513
    catch (Exception e) {
514
      System.err.print(e.getMessage());
515
    }
516

    
517
    return (int) (d * pScreen.x);
518
  }
519

    
520
  /**
521
   * <p>
522
   * Converts and returns the 2D point <code>(x,y)</code>, that is in <i>map
523
   * coordinates</i> to <i>screen coordinates</i> (pixels) using the affine
524
   * transformation in the {@link #trans #trans} attribute.
525
   * </p>
526
   *
527
   * @param x the <code>x</code> <i>map coordinate</i> of a 2D point
528
   * @param y the <code>y</code> <i>map coordinate</i> of a 2D point
529
   * @return 2D point equivalent in <i>screen coordinates</i> (pixels)
530
   * @see #fromMapPoint(Point2D)
531
   * @see AffineTransform#transform(Point2D, Point2D)
532
   */
533
  public Point2D fromMapPoint(double x, double y) {
534
    Point2D.Double pWorld = new Point2D.Double(x, y);
535
    Point2D.Double pScreen = new Point2D.Double();
536

    
537
    try {
538
      trans.transform(pWorld, pScreen);
539
    }
540
    catch (Exception e) {
541
      System.err.print(e.getMessage());
542
    }
543

    
544
    return pScreen;
545
  }
546

    
547
  /**
548
   * <p>
549
   * Converts and returns the 2D point argument, that is in <i>map
550
   * coordinates</i> to <i>screen coordinates</i> (pixels) using the affine
551
   * transformation in the {@link #trans #trans} attribute.
552
   * </p>
553
   *
554
   * @param point the 2D point in <i>map coordinates</i>
555
   * @return 2D point equivalent in <i>screen coordinates</i> (pixels)
556
   * @see #toMapPoint(Point2D)
557
   * @see #fromMapPoint(double, double)
558
   */
559
  public Point2D fromMapPoint(Point2D point) {
560
    return fromMapPoint(point.getX(), point.getY());
561
  }
562

    
563
  /**
564
   * <p>
565
   * Converts and returns the 2D point <code>(x,y)</code>, that is in <i>screen
566
   * coordinates</i> (pixels) to <i>map coordinates</i> using the affine
567
   * transformation in the {@link #trans #trans} attribute.
568
   * </p>
569
   *
570
   * @param x the <code>x</code> <i>screen coordinate</i> of a 2D point
571
   * @param y the <code>y</code> <i>screen coordinate</i> of a 2D point
572
   * @return 2D point equivalent in <i>map coordinates</i>
573
   * @see #toMapPoint(Point2D)
574
   * @see #fromMapPoint(double, double)
575
   * @deprecated use {@link #convertToMapPoint(int, int)}
576
   */
577
  public Point2D toMapPoint(int x, int y) {
578
    Point2D pScreen = new Point2D.Double(x, y);
579

    
580
    return toMapPoint(pScreen);
581
  }
582

    
583
  /**
584
   * <p>
585
   * Converts and returns the {@link Rectangle2D Rectangle2D}, that is in
586
   * <i>screen coordinates</i> (pixels) to <i>map coordinates</i> using
587
   * {@linkplain #toMapDistance(int)}, and {@linkplain #toMapPoint(int, int)}.
588
   * </p>
589
   *
590
   * @param r the 2D rectangle in <i>screen coordinates</i> (pixels)
591
   * @return 2D rectangle equivalent in <i>map coordinates</i>
592
   * @see #fromMapRectangle(Rectangle2D)
593
   * @see #toMapDistance(int)
594
   * @see #toMapPoint(int, int)
595
   */
596
  public Rectangle2D toMapRectangle(Rectangle2D r) {
597
    Rectangle2D rect = new Rectangle2D.Double();
598
    Point2D p1 = toMapPoint((int) r.getX(), (int) r.getY());
599
    Point2D p2 = toMapPoint((int) r.getMaxX(), (int) r.getMaxY());
600
    rect.setFrameFromDiagonal(p1, p2);
601
    return rect;
602
  }
603

    
604
  /**
605
   * <p>
606
   * Converts and returns the distance <code>d</code>, that is in <i>screen
607
   * coordinates</i> to <i>map coordinates</i> using the transformation affine
608
   * information in the {@link #trans #trans} attribute.
609
   * </p>
610
   *
611
   * @param d distance in pixels
612
   * @return distance equivalent in <i>map coordinates</i>
613
   * @see #fromMapDistance(double)
614
   * @see AffineTransform
615
   */
616
  public double toMapDistance(int d) {
617
    double dist = d / trans.getScaleX();
618

    
619
    return dist;
620
  }
621

    
622
  /**
623
   * <p>
624
   * Converts and returns the 2D point argument, that is in <i>screen
625
   * coordinates</i> (pixels) to <i>map coordinates</i> using the inverse affine
626
   * transformation of the {@link #trans #trans} attribute.
627
   * </p>
628
   *
629
   * @param pScreen the 2D point in <i>screen coordinates</i> (pixels)
630
   * @return 2D point equivalent in <i>map coordinates</i>
631
   * @see #toMapPoint(int, int)
632
   * @see AffineTransform#createInverse()
633
   * @see AffineTransform#transform(Point2D, Point2D)
634
   * @deprecated use {@link #convertToMapPoint(Point2D)}
635
   */
636
  public Point2D toMapPoint(Point2D pScreen) {
637
    Point2D.Double pWorld = new Point2D.Double();
638
    AffineTransform at;
639

    
640
    if( pScreen == null ) {
641
        return null;
642
    }
643
    try {
644
      at = trans.createInverse();
645
      at.transform(pScreen, pWorld);
646
    }
647
    catch (NoninvertibleTransformException e) {
648
      throw new RuntimeException("Non invertible transform Exception", e);
649
    }
650

    
651
    return pWorld;
652
  }
653

    
654
  public Point convertToMapPoint(Point2D pScreen) {
655
    Point2D p = toMapPoint(pScreen);
656
    try {
657
        Point point = geomManager.createPoint(
658
                p.getX(), 
659
                p.getY(),
660
                Geometry.SUBTYPES.GEOM2D
661
        );
662
        point.setProjection(this.getProjection());
663
        return point;
664
    }
665
    catch (CreateGeometryException e) {
666
      // FIXME: Use a most especific exception.
667
      throw new RuntimeException(e);
668
    }
669
  }
670

    
671
  public Point convertToMapPoint(int x, int y) {
672
    Point2D pScreen = new Point2D.Double(x, y);
673

    
674
    return convertToMapPoint(pScreen);
675
  }
676

    
677
  /**
678
   * <p>
679
   * Returns the real distance (in <i>world coordinates</i>) at the graphic
680
   * layers of two 2D points (in <i>map coordinates</i>) of the plane where is
681
   * selected the <i>extent</i>.
682
   * </p>
683
   * <p>
684
   * If the projection of this view is UTM, considers the Earth curvature.
685
   * </p>
686
   *
687
   * @param pt1 a 2D point in <i>map coordinates</i>
688
   * @param pt2 another 2D point in <i>map coordinates</i>
689
   * @return the distance in meters between the two points 2D
690
   * @see GeoCalcImpl#distanceVincenty(Point2D, Point2D)
691
   */
692
  public double distanceWorld(Point2D pt1, Point2D pt2) {
693

    
694
    double dist = 0;
695
    if (proj.isProjected()) {
696
      dist = pt1.distance(pt2);
697
      dist = dist * MapContext.getDistanceTrans2Meter()[getMapUnits()];
698
    }
699
    else {
700
      GeoCalc geocalc = new GeoCalc(proj);
701
      dist = geocalc.distanceVincenty(pt1, pt2);
702
    }
703
    return dist;
704
  }
705

    
706
  /**
707
   * <p>
708
   * Sets as extent and adjusted extent of this view port, the previous.
709
   * Recalculating its parameters.
710
   * </p>
711
   *
712
   * @see #getExtents()
713
   * @see #calculateAffineTransform()
714
   * @deprecated use {@link ViewPort#setPreviousEnvelope()}
715
   */
716
  public void setPreviousExtent() {
717
    setPreviousEnvelope();
718
  }
719

    
720
  /**
721
   * <p>
722
   * Sets as envelope and adjusted envelope of this view port, the previous.
723
   * Recalculating its parameters.
724
   * Stores the current extent in the next extents of the history.
725
   * </p>
726
   *
727
   * @see #getExtents()
728
   * @see #calculateAffineTransform()
729
   */
730
  public void setPreviousEnvelope() {
731
    this.updateDrawVersion();
732

    
733
//    extentsHistory.putNext(extent);
734
//    extent = extentsHistory.removePrev();
735
    extent = extentsHistory.setPreviousExtent();
736

    
737
    // Calcula la transformaci?n af?n
738
    calculateAffineTransform();
739

    
740
    // Lanzamos los eventos de extent cambiado
741
    callExtentChanged(getAdjustedExtent());
742
  }
743

    
744
  /**
745
   * <p>
746
   * Sets as envelope and adjusted envelope of this view port, the next.
747
   * Recalculating its parameters.
748
   * Stores the current extent in the previous extents of the history.
749
   * </p>
750
   *
751
   * @see #getExtents()
752
   * @see #calculateAffineTransform()
753
   */
754
  public void setNextEnvelope() {
755
    this.updateDrawVersion();
756

    
757
    extent = extentsHistory.setNextExtent();
758

    
759
    // Calcula la transformaci?n af?n
760
    calculateAffineTransform();
761

    
762
    // Lanzamos los eventos de extent cambiado
763
    callExtentChanged(getAdjustedExtent());
764
  }
765

    
766
  /**
767
   * <p>
768
   * Gets the area selected by user using some tool.
769
   * </p>
770
   * <p>
771
   * When the zoom changes (for instance using the <i>zoom in</i> or <i>zoom
772
   * out</i> tools, but also zooming to a selected feature or shape) the extent
773
   * that covers that area is the value returned by this method. It is not the
774
   * actual area shown because it doesn't care about the aspect ratio of the
775
   * image size of the view. However, any part of the real world contained in
776
   * this extent is shown in the view.
777
   * </p>
778
   * <p>
779
   * If you are looking for the complete extent currently shown, you must use
780
   * the {@linkplain #getAdjustedExtent()} method.
781
   * </p>
782
   *
783
   * @return the current extent
784
   * @see #setEnvelope(Envelope)
785
   * @see #getAdjustedExtent()
786
   * @see #setPreviousExtent()
787
   * @see #getExtents()
788
   * @deprecated use {@link ViewPort#getEnvelope()}
789
   */
790
  public Rectangle2D getExtent() {
791
    return extent;
792
  }
793

    
794
  /**
795
   * <p>
796
   * Gets the envelope selected by user using some tool.
797
   * </p>
798
   * <p>
799
   * When the zoom changes (for instance using the <i>zoom in</i> or <i>zoom
800
   * out</i> tools, but also zooming to a selected feature or shape) the
801
   * envelope that covers that area is the value returned by this method. It is
802
   * not the actual envelope shown because it doesn't care about the aspect
803
   * ratio of the image size of the view. However, any part of the real world
804
   * contained in this envelope is shown in the view.
805
   * </p>
806
   * <p>
807
   * If you are looking for the complete extent currently shown, you must use
808
   * the {@linkplain #getAdjustedEnvelope()} method.
809
   * </p>
810
   *
811
   * @return the current envelope
812
   * @see #setEnvelope(Envelope)
813
   * @see #getAdjustedEnvelope()
814
   * @see #setPreviousEnvelope()
815
   * @see #getEnvelopes()
816
   */
817
  public Envelope getEnvelope() {
818
    if (this.extent == null) {
819
      return null;
820
    }
821
    try {
822
        Envelope envelope = geomManager.createEnvelope(
823
                extent.getMinX(), 
824
                extent.getMinY(),
825
                extent.getMaxX(), 
826
                extent.getMaxY(), 
827
                SUBTYPES.GEOM2D
828
        );
829
        envelope.setProjection(this.proj);
830
        return envelope;
831
        // This class has to use Envelope instead of Rectangle2D. This catch
832
        // will disappear
833
    } catch (CreateEnvelopeException e) {
834
      logger.warn("Error creating the ViewPort envelope.", e);
835
    }
836
    return null;
837
  }
838

    
839
  /**
840
   * <p>
841
   * Changes the <i>extent</i> and <i>adjusted extent</i> of this view port:<br>
842
   * <ul>
843
   * <li>Stores the previous extent.
844
   * <li>Calculates the new extent using <code>r</code>:
845
   *
846
   * <pre>
847
   * extent = new Rectangle2D.Double(r.getMinX() - 0.1, r.getMinY() - 0.1,
848
   *     r.getWidth() + 0.2, r.getHeight() + 0.2);
849
   * </pre>
850
   * <li>Executes {@linkplain #calculateAffineTransform()}: getting the new
851
   * scale, adjusted extent, affine transformation between map and screen
852
   * coordinates, the real world coordinates equivalent to 1 pixel, and the real
853
   * world coordinates equivalent to 3 pixels.
854
   * <li>Notifies all {@link ViewPortListener ViewPortListener} registered that
855
   * the extent has changed.
856
   * </ul>
857
   * </p>
858
   *
859
   * @param r the new extent
860
   * @see #getExtent()
861
   * @see #getExtents()
862
   * @see #calculateAffineTransform()
863
   * @see #setPreviousExtent()
864
   * @see #clear()
865
   */
866
  public void setEnvelope(Envelope r) {
867
    Rectangle2D newExtent = null;
868
    // Esto comprueba que el extent no es de anchura o altura = "0"
869
    // y si es as? lo redimensiona.
870
    if (r != null) { 
871
      if ((r.getMaximum(0) - r.getMinimum(0) == 0)
872
          || (r.getMaximum(1) - r.getMinimum(1) == 0)) {
873
        newExtent = new Rectangle2D.Double(r.getMinimum(0) - 0.1,
874
            r.getMinimum(1) - 0.1, r.getMaximum(0) - r.getMinimum(0) + 0.2,
875
            r.getMaximum(1) - r.getMinimum(1) + 0.2);
876
      }
877
      else {
878
        newExtent = new Rectangle2D.Double(r.getMinimum(0), r.getMinimum(1),
879
            Math.abs(r.getMaximum(0) - r.getMinimum(0)), Math.abs(r
880
                .getMaximum(1) - r.getMinimum(1)));
881
      }
882
    }
883

    
884
    if (this.extent != null && this.extent.equals(newExtent)) {
885
      return;
886
    }
887

    
888
    this.updateDrawVersion();
889
    this.extent = newExtent;
890
    try {
891
        calculateAffineTransform();
892
    } catch(Exception ex) {
893
        this.extent = null;
894
        throw ex;
895
    }
896
    extentsHistory.put(extent);
897

    
898

    
899
    // Lanzamos los eventos de extent cambiado
900
    callExtentChanged(getAdjustedExtent());
901
  }
902

    
903
  /**
904
   * <p>
905
   * Changes the <i>extent</i> and <i>adjusted extent</i> of this view port:<br>
906
   * <ul>
907
   * <li>Executes {@linkplain #calculateAffineTransform()}: getting the new
908
   * scale, adjusted extent, affine transformation between map and screen
909
   * coordinates, the real world coordinates equivalent to 1 pixel, and the real
910
   * world coordinates equivalent to 3 pixels.
911
   * <li>Notifies to all {@link ViewPortListener ViewPortListener} registered
912
   * that the extent has changed.
913
   * </ul>
914
   * </p>
915
   *
916
   * @see #setEnvelope(Envelope)
917
   * @see #calculateAffineTransform()
918
   */
919
  public void refreshExtent() {
920
    //Por compatibilidad con versiones anteriores a la introducci?n de las lista de zooms siguientes
921
    if (extentsHistory.getCurrent() == null) {
922
      extentsHistory.put(extent);
923
    } else {
924
      extent = extentsHistory.getCurrent();
925
    }
926

    
927
    // Calcula la transformaci?n af?n
928
    calculateAffineTransform();
929

    
930
    // Lanzamos los eventos de extent cambiado
931
    callExtentChanged(getAdjustedExtent());
932
  }
933

    
934
  /**
935
   * <p>
936
   * Calculates and returns using the current projection of this view port, the
937
   * scale that is the extent in <i>screen coordinates</i> from the image in
938
   * <i>map coordinates</i>.
939
   * </p>
940
   *
941
   * @return the scale <i>extent / image size</i> projected by this view port
942
   * @deprecated since 07/09/07, use {@linkplain MapContext#getScaleView()}
943
   */
944
  private double getScale() {
945

    
946
    double[] trans2Meter = MapContext.getDistanceTrans2Meter();
947
    if (proj == null) {
948
      double wmeters = ((getImageSize().width / this.getDPI()) * 0.0254);
949
      return (long) ((trans2Meter[getMapUnits()] * getAdjustedEnvelope()
950
          .getLength(0)) / wmeters);
951
    }
952
    else {
953
      return Math.round(proj.getScale(getAdjustedEnvelope().getMinimum(0)
954
          * trans2Meter[getMapUnits()], getAdjustedEnvelope().getMaximum(0)
955
          * trans2Meter[getMapUnits()], getImageSize().width, this.getDPI()));
956
    }
957

    
958
    /*
959
     * return proj.getScale(extent.getMinX(), extent.getMaxX(), imageSize.width,
960
     * dpi);
961
     */
962
  }
963

    
964
  /**
965
   * <p>
966
   * Affine transformation between <i>map 2D coordinates</i> to <i>screen 2D
967
   * coordinates</i> (pixels), preserving the "straightness" and "parallelism"
968
   * of the lines.
969
   * </p>
970
   *
971
   * @return the affine transformation
972
   * @see #setAffineTransform(AffineTransform)
973
   * @see #calculateAffineTransform()
974
   */
975
  public AffineTransform getAffineTransform() {
976
    return trans;
977
  }
978

    
979
  /**
980
   * <p>
981
   * Returns the size of the image projected.
982
   * </p>
983
   *
984
   * @return the image size
985
   * @see #setImageSize(Dimension)
986
   * @see #getImageHeight()
987
   * @see #getImageWidth()
988
   */
989
  public Dimension getImageSize() {
990
    return imageSize;
991
  }
992

    
993
  /**
994
   * <p>
995
   * Sets the size of the image projected, recalculating the parameters of this
996
   * view port.
997
   * </p>
998
   *
999
   * @param imageSize the image size
1000
   * @see #getImageSize()
1001
   * @see #calculateAffineTransform()
1002
   */
1003
  public void setImageSize(Dimension imageSize) {
1004

    
1005
    if (this.imageSize == null || (!this.imageSize.equals(imageSize))) {
1006
      this.updateDrawVersion();
1007
      this.imageSize = imageSize;
1008
      calculateAffineTransform();
1009
    }
1010
  }
1011

    
1012
  /**
1013
   * <p>
1014
   * Notifies to all view port listeners registered, that the adjusted extent of
1015
   * this view port has changed.
1016
   * </p>
1017
   *
1018
   * @param newRect the new adjusted extend
1019
   * @see #refreshExtent()
1020
   * @see #setEnvelope(Envelope)
1021
   * @see #setPreviousExtent()
1022
   * @see ExtentEvent
1023
   * @see ViewPortListener
1024
   */
1025
    protected void callExtentChanged(Envelope newRect) {
1026
        ExtentEvent ev = ExtentEvent.createExtentEvent(newRect);
1027

    
1028
        for (ViewPortListener listener : this.getListeners()) {
1029
            if (listener != null) {
1030
                listener.extentChanged(ev);
1031
            }
1032
        }
1033
    }
1034

    
1035
  /**
1036
   * <p>
1037
   * Notifies to all view port listeners registered, that the time of this view
1038
   * port has changed.
1039
   * </p>
1040
   *
1041
   * @param newTime the new time
1042
   * @see #refreshExtent()
1043
   * @see #setTime(Time)
1044
   * @see ExtentEvent
1045
   * @see ViewPortListener
1046
   */
1047
    protected void callTimeChanged(Time newTime) {
1048
        ExtentEvent viewPortEvent = new ExtentEvent(newTime);
1049

    
1050
        for (ViewPortListener listener : this.getListeners()) {
1051
            if (listener != null) {
1052
                listener.extentChanged(viewPortEvent);
1053
            }
1054
        }
1055
    }
1056

    
1057
  /**
1058
   * <p>
1059
   * Notifies to all view port listeners registered, that the background color
1060
   * of this view port has changed.
1061
   * </p>
1062
   *
1063
   * @param c the new background color
1064
   * @see #setBackColor(Color)
1065
   * @see ColorEvent
1066
   * @see ViewPortListener
1067
   */
1068
    private void callColorChanged(Color c) {
1069
        ColorEvent ce = ColorEvent.createColorEvent(c);
1070

    
1071
        for (ViewPortListener listener : this.getListeners()) {
1072
            if (listener != null) {
1073
                listener.backColorChanged(ce);
1074
            }
1075
        }
1076
    }
1077

    
1078
  /**
1079
   * <p>
1080
   * Notifies to all view port listeners registered, that the projection of this
1081
   * view port has changed.
1082
   * </p>
1083
   *
1084
   * @param projection the new projection
1085
   * @see #setProjection(IProjection)
1086
   * @see ProjectionEvent
1087
   * @see ViewPortListener
1088
   */
1089
    private void callProjectionChanged(IProjection projection) {
1090
        ProjectionEvent ev = ProjectionEvent.createProjectionEvent(projection);
1091

    
1092
        for (ViewPortListener listener : this.getListeners()) {
1093
            if (listener != null) {
1094
                listener.projectionChanged(ev);
1095
            }
1096
        }
1097
    }
1098

    
1099
  /**
1100
   * <p>
1101
   * Calculates the affine transformation between the {@link #extent extent} in
1102
   * <i>map 2D coordinates</i> to the image area in the screen, in <i>screen 2D
1103
   * coordinates</i> (pixels).
1104
   * </p>
1105
   * <p>
1106
   * This process recalculates some parameters of this view port:<br>
1107
   * <ul>
1108
   * <li>The new {@link #scale scale} .
1109
   * <li>The new {@link #adjustedExtent adjustedExtent} .
1110
   * <li>The new {@link #trans trans} .
1111
   * <li>The new real world coordinates equivalent to 1 pixel (
1112
   * {@link #dist1pixel dist1pixel}) .
1113
   * <li>The new real world coordinates equivalent to 3 pixels (
1114
   * {@link #dist3pixel dist3pixel}) .
1115
   * </ul>
1116
   * </p>
1117
   *
1118
   * @see #getAffineTransform()
1119
   * @see #setAffineTransform(AffineTransform)
1120
   * @see #refreshExtent()
1121
   * @see #setEnvelope(Envelope)
1122
   * @see #setImageSize(Dimension)
1123
   * @see #setPreviousExtent()
1124
   * @see #createFromXML(XMLEntity)
1125
   * @see AffineTransform
1126
   */
1127
  private void calculateAffineTransform() {
1128
    if ((imageSize == null) || (extent == null) || (imageSize.width <= 0)
1129
        || (imageSize.height <= 0)) {
1130
      return;
1131
    }
1132

    
1133
    AffineTransform escalado = new AffineTransform();
1134
    AffineTransform translacion = new AffineTransform();
1135

    
1136
    double escalaX;
1137
    double escalaY;
1138

    
1139
    escalaX = imageSize.width / extent.getWidth();
1140
    escalaY = imageSize.height / extent.getHeight();
1141

    
1142
    double xCenter = extent.getCenterX();
1143
    double yCenter = extent.getCenterY();
1144
    double newHeight;
1145
    double newWidth;
1146

    
1147
    adjustedExtent = new Rectangle2D.Double();
1148

    
1149
    if (adjustableExtent) {
1150
      if (escalaX < escalaY) {
1151
        scale = escalaX;
1152
        newHeight = imageSize.height / scale;
1153
        adjustedExtent.setRect(xCenter - (extent.getWidth() / 2.0), yCenter
1154
            - (newHeight / 2.0), extent.getWidth(), newHeight);
1155
      }
1156
      else {
1157
        scale = escalaY;
1158
        newWidth = imageSize.width / scale;
1159
        adjustedExtent.setRect(xCenter - (newWidth / 2.0),
1160
            yCenter - (extent.getHeight() / 2.0), newWidth, extent.getHeight());
1161
      }
1162
      escalado.setToScale(scale, -scale);
1163
    }
1164
    else { // adjusted is same as extent
1165
      scale = escalaX;
1166
      adjustedExtent.setFrame(extent);
1167
      escalado.setToScale(escalaX, -escalaY);
1168
    }
1169
    Envelope env = getAdjustedExtent();
1170
    if (env == null) {
1171
      return;
1172
    }
1173
    translacion.setToTranslation(-env.getMinimum(0), -env.getMinimum(1)
1174
        - getAdjustedExtent().getLength(1));
1175

    
1176
    AffineTransform offsetTrans = new AffineTransform();
1177
    offsetTrans.setToTranslation(offset.getX(), offset.getY());
1178

    
1179
    trans.setToIdentity();
1180
    trans.concatenate(offsetTrans);
1181
    trans.concatenate(escalado);
1182

    
1183
    trans.concatenate(translacion);
1184

    
1185
    // Calculamos las distancias de 1 pixel y 3 pixel con esa
1186
    // transformaci?n
1187
    // de coordenadas, de forma que est?n precalculadas para cuando las
1188
    // necesitemos
1189
    AffineTransform at;
1190

    
1191
    try {
1192
      at = trans.createInverse();
1193

    
1194
      Point2D pPixel = new Point2D.Float(1, 1);
1195

    
1196
      Point2D.Float pProv = new Point2D.Float();
1197
      at.deltaTransform(pPixel, pProv);
1198

    
1199
      dist1pixel = pProv.x;
1200
      dist3pixel = 3 * pProv.x;
1201
    }
1202
    catch (NoninvertibleTransformException e) {
1203
      String msg = "Can't calculate affine transform for the view port." + "\n" +
1204
        "The extent can be out of range for this projection." + "\n" +
1205
        "transformada afin: " + Objects.toString(trans) + "\n" +
1206
        "extent: " + Objects.toString(extent) + "\n" +
1207
        "imageSize: " + Objects.toString(imageSize) + "\n" +
1208
        "projection: " + Objects.toString(proj) + "\n" +
1209
        e.getLocalizedMessage() 
1210
      ;
1211
      logger.error(msg, e);
1212
      trans.setToIdentity();
1213
      scale = 0;
1214
      adjustedExtent = null;
1215
      adjustableExtent = true;
1216
      throw new RuntimeException(msg, e);
1217
    }
1218
  }
1219

    
1220
  /**
1221
   * <p>
1222
   * Sets the offset.
1223
   * </p>
1224
   * <p>
1225
   * The offset is the position where start drawing the map.
1226
   * </p>
1227
   *
1228
   * @param p 2D point that represents the offset in pixels
1229
   * @see #getOffset()
1230
   */
1231
  public void setOffset(Point2D p) {
1232
    if (!offset.equals(p)) {
1233
      this.updateDrawVersion();
1234
      offset = p;
1235
    }
1236
  }
1237

    
1238
  /**
1239
   * <p>
1240
   * Gets the offset.
1241
   * </p>
1242
   * <p>
1243
   * The offset is the position where start drawing the map.
1244
   * </p>
1245
   *
1246
   * @return 2D point that represents the offset in pixels
1247
   * @see #setOffset(Point2D)
1248
   */
1249
  public Point2D getOffset() {
1250
    return offset;
1251
  }
1252

    
1253
  /**
1254
   * <p>
1255
   * Sets the background color.
1256
   * </p>
1257
   *
1258
   * @param c the new background color
1259
   * @see #getBackColor()
1260
   */
1261
  public void setBackColor(Color c) {
1262
    if (!c.equals(this.backColor)) {
1263
      this.updateDrawVersion();
1264
      backColor = c;
1265
      callColorChanged(backColor);
1266
    }
1267
  }
1268

    
1269
  /**
1270
   * <p>
1271
   * Gets the background color.
1272
   * </p>
1273
   *
1274
   * @return the background color of the view
1275
   * @see #setBackColor(Color)
1276
   */
1277
  public Color getBackColor() {
1278
    return backColor;
1279
  }
1280

    
1281
  /**
1282
   * <p>
1283
   * Returns the extent currently covered by the view adjusted (scaled) to the
1284
   * image size aspect.
1285
   * </p>
1286
   *
1287
   * @return extent of the view adjusted to the image size aspect
1288
   * @see #setAdjustable(boolean)
1289
   * @deprecated use {@link ViewPort#getAdjustedEnvelope()} instead
1290
   */
1291
  public Envelope getAdjustedExtent() {
1292
    return getAdjustedEnvelope();
1293
  }
1294

    
1295
  /**
1296
   * <p>
1297
   * Returns the envelope currently covered by the view adjusted (scaled) to the
1298
   * image size aspect.
1299
   * </p>
1300
   *
1301
   * @return envelope of the view adjusted to the image size aspect
1302
   * @see #setAdjustable(boolean)
1303
   */
1304
    public Envelope getAdjustedEnvelope() {
1305
        if (adjustedExtent == null) {
1306
            calculateAffineTransform();
1307
        }
1308
        if (cliprect != null) {
1309
            Rectangle2D r = adjustedExtent.createIntersection(cliprect);
1310
            try {
1311
                return geomManager.createEnvelope(r.getX(), r.getY(), r.getMaxX(), r.getMaxY(), SUBTYPES.GEOM2D);
1312
            } catch (CreateEnvelopeException e) {
1313
                e.printStackTrace();
1314
                logger.error("Error adjusting the extent", e);
1315
            }
1316
        }
1317
        if (adjustedExtent != null) {
1318
            try {
1319
                return geomManager.createEnvelope(adjustedExtent.getX(), adjustedExtent.getY(),
1320
                    adjustedExtent.getMaxX(), adjustedExtent.getMaxY(), SUBTYPES.GEOM2D);
1321
            } catch (CreateEnvelopeException e) {
1322
                e.printStackTrace();
1323
                logger.error("Error adjusting the extent", e);
1324
            }
1325
        }
1326
        return null;
1327
    }
1328

    
1329
  /**
1330
   * <p>
1331
   * Returns the measurement unit of this view port used for measuring distances
1332
   * and displaying information.
1333
   * </p>
1334
   *
1335
   * @return the measurement unit of this view used for measuring distances and
1336
   *         displaying information
1337
   * @see #setDistanceUnits(int)
1338
   */
1339
  public int getDistanceUnits() {
1340
    return distanceUnits;
1341
  }
1342

    
1343
  /**
1344
   * <p>
1345
   * Returns the measurement unit of this view port used for measuring areas and
1346
   * displaying information.
1347
   * </p>
1348
   *
1349
   * @return the measurement unit of this view used for measuring areas and
1350
   *         displaying information
1351
   * @see #setDistanceUnits(int)
1352
   */
1353
  public int getDistanceArea() {
1354
    return distanceArea;
1355
  }
1356

    
1357
  /**
1358
   * <p>
1359
   * Sets the measurement unit of this view port used for measuring distances
1360
   * and displaying information.
1361
   * </p>
1362
   *
1363
   * @param distanceUnits the measurement unit of this view used for measuring
1364
   *          distances and displaying information
1365
   * @see #getDistanceUnits()
1366
   */
1367
  public void setDistanceUnits(int distanceUnits) {
1368
    this.distanceUnits = distanceUnits;
1369
  }
1370

    
1371
  /**
1372
   * <p>
1373
   * Sets the measurement unit of this view port used for measuring areas and
1374
   * displaying information.
1375
   * </p>
1376
   *
1377
   * @param distanceUnits the measurement unit of this view used for measuring
1378
   *          areas and displaying information
1379
   * @see #getDistanceUnits()
1380
   */
1381
  public void setDistanceArea(int distanceArea) {
1382
    this.distanceArea = distanceArea;
1383
  }
1384

    
1385
  /**
1386
   * <p>
1387
   * Gets the measurement unit used by this view port for the map.
1388
   * </p>
1389
   *
1390
   * @return Returns the current map measure unit
1391
   * @see #setMapUnits(int)
1392
   */
1393
  public int getMapUnits() {
1394
    return mapUnits;
1395
  }
1396

    
1397
  /**
1398
   * <p>
1399
   * Sets the measurement unit used by this view port for the map.
1400
   * </p>
1401
   *
1402
   * @param mapUnits the new map measure unit
1403
   * @see #getMapUnits()
1404
   */
1405
  public void setMapUnits(int mapUnits) {
1406
    this.mapUnits = mapUnits;
1407
  }
1408

    
1409
  /**
1410
   * <p>
1411
   * Gets the width in <i>screen coordinates</i> of the rectangle where the
1412
   * image is displayed.
1413
   * </p>
1414
   * <p>
1415
   * Used by {@linkplain #calculateAffineTransform()} to calculate:<br>
1416
   * <ul>
1417
   * <li>The new {@link #scale scale} .
1418
   * <li>The new {@link #adjustedExtent adjustableExtent} .
1419
   * <li>The new {@link #trans trans} .
1420
   * <li>The new real world coordinates equivalent to 1 pixel (
1421
   * {@link #dist1pixel dist1pixel}) .
1422
   * <li>The new real world coordinates equivalent to 3 pixels (
1423
   * {@link #dist3pixel dist3pixel}) .
1424
   * </ul>
1425
   * </p>
1426
   *
1427
   * @see #getImageHeight()
1428
   * @see #getImageSize()
1429
   * @see #setImageSize(Dimension)
1430
   */
1431
  public int getImageWidth() {
1432
    return imageSize.width;
1433
  }
1434

    
1435
  /**
1436
   * <p>
1437
   * Gets the height in <i>screen coordinates</i> of the rectangle where the
1438
   * image is displayed.
1439
   * </p>
1440
   * <p>
1441
   * Used by {@linkplain #calculateAffineTransform()} to calculate:<br>
1442
   * <ul>
1443
   * <li>The new {@link #scale scale} .
1444
   * <li>The new {@link #adjustedExtent adjustableExtent} .
1445
   * <li>The new {@link #trans trans} .
1446
   * <li>The new real world coordinates equivalent to 1 pixel (
1447
   * {@link #dist1pixel dist1pixel}) .
1448
   * <li>The new real world coordinates equivalent to 3 pixels (
1449
   * {@link #dist3pixel dist3pixel}) .
1450
   * </ul>
1451
   * </p>
1452
   *
1453
   * @see #getImageWidth()
1454
   * @see #getImageSize()
1455
   * @see #setImageSize(Dimension)
1456
   */
1457
  public int getImageHeight() {
1458
    return imageSize.height;
1459
  }
1460

    
1461
  /**
1462
   * <p>
1463
   * Gets the distance in <i>world coordinates</i> equivalent to 1 pixel in the
1464
   * view with the current extent.
1465
   * </p>
1466
   *
1467
   * @return the distance
1468
   * @see #setDist1pixel(double)
1469
   */
1470
  public double getDist1pixel() {
1471
    return dist1pixel;
1472
  }
1473

    
1474
  /**
1475
   * <p>
1476
   * Sets the distance in <i>world coordinates</i> equivalent to 1 pixel in the
1477
   * view with the current extent.
1478
   * </p>
1479
   *
1480
   * @param dist1pixel the distance
1481
   * @see #getDist1pixel()
1482
   */
1483
  public void setDist1pixel(double dist1pixel) {
1484
    if (dist1pixel == this.dist1pixel) {
1485
      return;
1486
    }
1487
    this.updateDrawVersion();
1488
    this.dist1pixel = dist1pixel;
1489
  }
1490

    
1491
  /**
1492
   * <p>
1493
   * Gets the distance in <i>world coordinates</i> equivalent to 3 pixels in the
1494
   * view with the current extent.
1495
   * </p>
1496
   *
1497
   * @return the distance
1498
   * @see #setDist3pixel(double)
1499
   */
1500
  public double getDist3pixel() {
1501
    return dist3pixel;
1502
  }
1503

    
1504
  /**
1505
   * <p>
1506
   * Sets the distance in <i>world coordinates</i> equivalent to 3 pixels in the
1507
   * view with the current extent.
1508
   * </p>
1509
   *
1510
   * @param dist3pixel the distance
1511
   * @see #getDist3pixel()
1512
   */
1513
  public void setDist3pixel(double dist3pixel) {
1514
    if (this.dist3pixel == dist3pixel) {
1515
      return;
1516
    }
1517
    this.updateDrawVersion();
1518
    this.dist3pixel = dist3pixel;
1519
  }
1520

    
1521
  /**
1522
   * <p>
1523
   * Returns the last previous extents of this view port.
1524
   * </p>
1525
   *
1526
   * @return the last previous extents of this view port
1527
   * @see #setPreviousExtent()
1528
   * @deprecated use {@link ViewPort#getEnvelopes()}
1529
   */
1530
  public ExtentHistory getExtents() {
1531
    return getEnvelopes();
1532
  }
1533

    
1534
  /**
1535
   * <p>
1536
   * Returns the last previous extents of this view port.
1537
   * </p>
1538
   *
1539
   * @return the last previous extents of this view port
1540
   * @see #setPreviousExtent()
1541
   */
1542
  public ExtentHistory getEnvelopes() {
1543
    return extentsHistory;
1544
  }
1545

    
1546
  /**
1547
   * <p>
1548
   * Gets the projection used in this view port.
1549
   * </p>
1550
   *
1551
   * @return projection used in this view port
1552
   * @see #setProjection(IProjection)
1553
   */
1554
  public IProjection getProjection() {
1555
    return proj;
1556
  }
1557

    
1558
  /**
1559
   * <p>
1560
   * Sets the projection to this view port.
1561
   * </p>
1562
   *
1563
   * @param proj the new projection
1564
   * @see #getProjection()
1565
   */
1566
  public void setProjection(IProjection proj) {
1567
    if (this.proj == null || !this.proj.getAbrev().equals(proj.getAbrev())) {
1568
        this.updateDrawVersion();
1569
        this.proj = proj;
1570
        int metros = MapContext.getDistancePosition("Metros");
1571
        int grados = MapContext.getDistancePosition("Grados");
1572
        if (proj.isProjected()) {
1573
            if (this.getMapUnits() == grados) {
1574
                this.setMapUnits(metros);
1575
            }
1576
        } else {
1577
            this.setMapUnits(grados);
1578
        }
1579

    
1580
        callProjectionChanged(proj);
1581
    }
1582
  }
1583

    
1584
  // -----------------------------------------------------------------------------------------------------------
1585
  // NOTA PARA DESARROLLADORES SOBRE EL M?TODO
1586
  // "public void setAffineTransform(AffineTransform at)"
1587
  // ==============================================================================================
1588
  // Only used for print, should be removed, redefining the {@link
1589
  // RasterAdapter RasterAdapter} interface,
1590
  // allowing it to receive a {@link ViewPortData ViewPortData} .
1591
  // -----------------------------------------------------------------------------------------------------------
1592

    
1593
  /**
1594
   * <p>
1595
   * Sets only the affine transform to this view port, without updating
1596
   * dependent attributes.
1597
   * </p>
1598
   * <p>
1599
   * <b><i>This method could be problematic!</i></b>
1600
   * </p>
1601
   *
1602
   * @param at the affine transform to set
1603
   * @see #getAffineTransform()
1604
   * @see #calculateAffineTransform()
1605
   */
1606
  public void setAffineTransform(AffineTransform at) {
1607
    this.trans = at;
1608
  }
1609

    
1610
  /**
1611
   * <p>
1612
   * Returns an XML entity that represents this view port instance:<br>
1613
   * <ul>
1614
   * <li>Properties:
1615
   * <ul>
1616
   * <li><i>className</i>: name of this class.
1617
   * <li>If defined, the adjusted extent:
1618
   * <ul>
1619
   * <li><i>adjustedExtentX</i>: X coordinate of the adjusted extent.
1620
   * <li><i>adjustedExtentY</i>: Y coordinate of the adjusted extent.
1621
   * <li><i>adjustedExtentW</i>: width of the adjusted extent.
1622
   * <li><i>adjustedExtentH</i>: height of the adjusted extent.
1623
   * </ul>
1624
   * <li>If defined, the background color:
1625
   * <ul>
1626
   * <li><i>backColor</i>: background color.
1627
   * </ul>
1628
   * <li>If defined, the clip:
1629
   * <ul>
1630
   * <li><i>clipX</i>: X coordinate of the clip.
1631
   * <li><i>clipY</i>: Y coordinate of clip.
1632
   * <li><i>clipW</i>: width of the clip.
1633
   * <li><i>clipH</i>: height of the clip.
1634
   * </ul>
1635
   * <li><i>dist1pixel</i>: the distance in world coordinates equivalent to 1
1636
   * pixel in the view.
1637
   * <li><i>dist3pixel</i>: the distance in world coordinates equivalent to 3
1638
   * pixels in the view.
1639
   * <li><i>distanceUnits</i>: the distance measurement unit.
1640
   * <li>If defined, the extent:
1641
   * <ul>
1642
   * <li><i>extentX</i>: X coordinate of the extent.
1643
   * <li><i>extentY</i>: Y coordinate of the extent.
1644
   * <li><i>extentW</i>: width of the extent.
1645
   * <li><i>extentH</i>: height of the extent.
1646
   * </ul>
1647
   * <li><i>mapUnits</i>: the map measurement unit.
1648
   * <li><i>offsetX</i>: X coordinate of the offset.
1649
   * <li><i>offsetY</i>: Y coordinate of the offset.
1650
   * <li>If defined, the projection:
1651
   * <ul>
1652
   * <li>If its defined, the projection:
1653
   * <ul>
1654
   * <li><i>proj</i>: the projection.</li>
1655
   * </ul>
1656
   * </ul>
1657
   * <li><i>scale</i>: ratio between the size of <code>imageSize</code> and
1658
   * <code>extent</code>.
1659
   * </ul>
1660
   * <li>Child branches:
1661
   * <ul>
1662
   * <li>XML entity of the internal {@link ExtentHistory ExtentHistory} .
1663
   * </ul>
1664
   * </ul>
1665
   *
1666
   * @return the XML entity
1667
   * @see #createFromXML(XMLEntity)
1668
   */
1669
  public void saveToState(PersistentState state) throws PersistenceException {
1670

    
1671
    state.set(FIELD_ADJUSTED_EXTENT, adjustedExtent);
1672
    state.set(FIELD_BACK_COLOR, backColor);
1673
    state.set(FIELD_CLIP, cliprect);
1674
    state.set(FIELD_DIST1PIXEL, dist1pixel);
1675
    state.set(FIELD_DIST3PIXEL, dist3pixel);
1676
    state.set(FIELD_DISTANCE_UNITS, distanceUnits);
1677
    state.set(FIELD_DISTANCE_AREA, distanceArea);
1678

    
1679
    state.set(FIELD_EXTENT, extent);
1680
    state.set(FIELD_EXTENTS, extentsHistory);
1681

    
1682
    state.set(FIELD_MAP_UNITS, mapUnits);
1683
    state.set(FIELD_OFFSET, offset);
1684

    
1685
    state.set(FIELD_PROJ, proj);
1686

    
1687
    state.set(FIELD_IMAGE_SIZE, imageSize);
1688
  }
1689

    
1690
  public void loadFromState(PersistentState state) throws PersistenceException {
1691

    
1692
    adjustedExtent = (Rectangle2D) state.get(FIELD_ADJUSTED_EXTENT);
1693
    backColor = (Color) state.get(FIELD_BACK_COLOR);
1694
    cliprect = (Rectangle2D) state.get(FIELD_CLIP);
1695
    dist1pixel = state.getDouble(FIELD_DIST1PIXEL);
1696
    dist3pixel = state.getDouble(FIELD_DIST3PIXEL);
1697
    distanceUnits = state.getInt(FIELD_DISTANCE_UNITS);
1698
    extentsHistory = (ExtentHistory) state.get(FIELD_EXTENTS);
1699
    extent = (Rectangle2D) state.get(FIELD_EXTENT);
1700
    mapUnits = state.getInt(FIELD_MAP_UNITS);
1701
    offset = (Point2D) state.get(FIELD_OFFSET);
1702
    proj = (IProjection) state.get(FIELD_PROJ);
1703
    imageSize = (Dimension) state.get(FIELD_IMAGE_SIZE);
1704
    distanceArea = state.getInt(FIELD_DISTANCE_AREA);
1705

    
1706
    refreshExtent();
1707
  }
1708

    
1709
  public static class RegisterPersistence implements Callable {
1710

    
1711
    public Object call() throws Exception {
1712
      PersistenceManager manager = ToolsLocator.getPersistenceManager();
1713
      if (manager.getDefinition("ViewPort") == null) {
1714
        DynStruct definition = manager.addDefinition(ViewPort.class,
1715
            "ViewPort", "ViewPort Persistence definition", null, null);
1716

    
1717
        definition.addDynFieldObject(FIELD_ADJUSTED_EXTENT)
1718
            .setClassOfValue(Rectangle2D.class).setMandatory(false);
1719

    
1720
        definition.addDynFieldObject(FIELD_BACK_COLOR)
1721
            .setClassOfValue(Color.class).setMandatory(false);
1722

    
1723
        definition.addDynFieldObject(FIELD_CLIP)
1724
            .setClassOfValue(Rectangle2D.class).setMandatory(false);
1725

    
1726
        definition.addDynFieldDouble(FIELD_DIST1PIXEL).setMandatory(true);
1727

    
1728
        definition.addDynFieldDouble(FIELD_DIST3PIXEL).setMandatory(true);
1729

    
1730
        definition.addDynFieldInt(FIELD_DISTANCE_UNITS).setMandatory(true);
1731

    
1732
        definition.addDynFieldInt(FIELD_DISTANCE_AREA).setMandatory(false);
1733

    
1734
        definition.addDynFieldObject(FIELD_EXTENT)
1735
            .setClassOfValue(Rectangle2D.class).setMandatory(false);
1736

    
1737
        definition.addDynFieldObject(FIELD_EXTENTS)
1738
            .setClassOfValue(ExtentHistory.class).setMandatory(true);
1739

    
1740
        definition.addDynFieldInt(FIELD_MAP_UNITS).setMandatory(true);
1741

    
1742
        definition.addDynFieldObject(FIELD_OFFSET)
1743
            .setClassOfValue(Point2D.class).setMandatory(false);
1744

    
1745
        definition.addDynFieldObject(FIELD_PROJ)
1746
            .setClassOfValue(IProjection.class).setMandatory(true);
1747

    
1748
        definition.addDynFieldObject(FIELD_IMAGE_SIZE)
1749
            .setClassOfValue(Dimension.class).setMandatory(false);
1750
      }
1751
      return Boolean.TRUE;
1752
    }
1753

    
1754
  }
1755

    
1756
  /**
1757
   * Clone the view port without clone the listeners nor the extent history.
1758
   *
1759
   * @return the cloned view port
1760
   */
1761
  public Object clone() throws CloneNotSupportedException {
1762

    
1763
    ViewPort clonedViewPort = (ViewPort) super.clone();
1764
    clonedViewPort.listeners = new ArrayList<>();
1765
    clonedViewPort.extentsHistory = new ExtentHistory();
1766

    
1767
    if (this.adjustedExtent != null) {
1768
      clonedViewPort.adjustedExtent = (Rectangle2D) this.adjustedExtent.clone();
1769
    }
1770

    
1771
    if (this.cliprect != null) {
1772
      clonedViewPort.cliprect = (Rectangle2D) this.cliprect.clone();
1773
    }
1774

    
1775
    if (this.extent != null) {
1776
      clonedViewPort.extent = (Rectangle2D) this.extent.clone();
1777
    }
1778
    if (this.imageSize != null) {
1779
      clonedViewPort.imageSize = (Dimension) this.imageSize.clone();
1780
    }
1781

    
1782
    if (this.offset != null) {
1783
      clonedViewPort.offset = (Point2D) this.offset.clone();
1784
    }
1785
    if (proj != null) {
1786
      clonedViewPort.proj = (IProjection) this.proj.clone();
1787
    }
1788

    
1789
    clonedViewPort.trans = (AffineTransform) this.trans.clone();
1790

    
1791
    return clonedViewPort;
1792
  }
1793

    
1794
  /**
1795
   * <p>
1796
   * Returns a <code>String</code> representation of the main values of this
1797
   * view port: <code>{@linkplain #extent}</code>,
1798
   * <code>{@linkplain #adjustedExtent}</code>,
1799
   * <code>{@linkplain #imageSize}</code>, <code>{@linkplain #scale}</code>, and
1800
   * <code>{@linkplain #trans}</code>.
1801
   * </p>
1802
   *
1803
   * @return a <code>string</code> representation of the main values of this
1804
   *         view port
1805
   */
1806
  public String toString() {
1807

    
1808
    String str;
1809
    str = "Datos del viewPort:\nExtent=" + extent + "\nadjustedExtent="
1810
        + adjustedExtent + "\nimageSize=" + imageSize + "\nescale=" + scale
1811
        + "\ntrans=" + trans;
1812

    
1813
    return str;
1814
  }
1815

    
1816
  /**
1817
   * <p>
1818
   * Sets the position and size of the clipping rectangle.
1819
   * </p>
1820
   *
1821
   * @param rectView the clipping rectangle to set
1822
   */
1823
  public void setClipRect(Rectangle2D rectView) {
1824
    this.updateDrawVersion();
1825
    cliprect = rectView;
1826
  }
1827

    
1828
  /**
1829
   * <p>
1830
   * Converts and returns the {@link Rectangle2D Rectangle2D}, that is in <i>map
1831
   * coordinates</i> to <i>screen coordinates</i> (pixels) using an <i>inverse
1832
   * transform</i> with the transformation affine information in the
1833
   * {@link #trans #trans} attribute.
1834
   * </p>
1835
   *
1836
   * @param r the 2D rectangle in <i>map coordinates</i>
1837
   * @return 2D rectangle equivalent in <i>screen coordinates</i> (pixels)
1838
   * @see #toMapRectangle(Rectangle2D)
1839
   * @see #fromMapDistance(double)
1840
   * @see #fromMapPoint(Point2D)
1841
   */
1842
  public Rectangle2D fromMapRectangle(Rectangle2D r) {
1843
    Rectangle2D rect = new Rectangle2D.Double();
1844
    Point2D p1 = fromMapPoint((int) r.getX(), (int) r.getY());
1845
    Point2D p2 = fromMapPoint((int) r.getMaxX(), (int) r.getMaxY());
1846
    rect.setFrameFromDiagonal(p1, p2);
1847
    return rect;
1848
  }
1849

    
1850
  /**
1851
   * <p>
1852
   * Recalculates the current <code>{@linkplain #extent}</code> using an scale.
1853
   * It's necessary execute {@linkplain #refreshExtent()} after.
1854
   * </p>
1855
   *
1856
   * @param s the scale to set
1857
   * @deprecated since 07/09/07, use {@linkplain MapContext#setScaleView(long)}
1858
   */
1859
  public void setScale(long s) {
1860
    double x = extent.getX();
1861
    double y = extent.getY();
1862
    double escalaX = imageSize.width / extent.getWidth();
1863
    // double w = imageSize.width / s;
1864
    // double h = imageSize.height / s;
1865
    double difw = escalaX / s;
1866

    
1867
    double x1 = (-x * difw) - x + extent.getWidth() / 2;
1868
    double y1 = (-y * difw) - y + extent.getHeight() / 2;
1869
    double w1 = extent.getWidth() * difw;
1870
    double h1 = extent.getHeight() * difw;
1871
    extent.setRect(-x1, -y1, w1, h1);
1872
  }
1873

    
1874
  public long getDrawVersion() {
1875
    return this.drawVersion;
1876
  }
1877

    
1878
  protected void updateDrawVersion() {
1879
    this.drawVersion++;
1880
  }
1881

    
1882
  public Time getTime() {
1883
    return time;
1884
  }
1885

    
1886
  public void setTime(Time time) {
1887
    this.time = time;
1888
    this.updateDrawVersion();
1889
    callTimeChanged(time);
1890
  }
1891

    
1892
  public double getDPI() {
1893
    if (this.dpi == null) {
1894
      return CompatLocator.getGraphicsUtils().getScreenDPI();
1895
    }
1896
    return this.dpi.doubleValue();
1897
  }
1898

    
1899
  public void setDPI(double dpi) {
1900
    this.dpi = new Double(dpi);
1901
  }
1902

    
1903
  public void setDPIToScreenDPI() {
1904
    this.dpi = null;
1905
  }
1906
}