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 @ 47790

History | View | Annotate | Download (58.2 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 static org.gvsig.fmap.mapcontext.MapContext.DEFAULT_SELECTION_COLOR;
56
import org.gvsig.fmap.mapcontext.events.ColorEvent;
57
import org.gvsig.fmap.mapcontext.events.ExtentEvent;
58
import org.gvsig.fmap.mapcontext.events.ProjectionEvent;
59
import org.gvsig.fmap.mapcontext.events.listeners.ViewPortListener;
60
import org.gvsig.timesupport.Time;
61
import org.gvsig.tools.ToolsLocator;
62
import org.gvsig.tools.dynobject.DynStruct;
63
import org.gvsig.tools.lang.Cloneable;
64
import org.gvsig.tools.persistence.PersistenceManager;
65
import org.gvsig.tools.persistence.Persistent;
66
import org.gvsig.tools.persistence.PersistentState;
67
import org.gvsig.tools.persistence.exception.PersistenceException;
68
import org.gvsig.tools.util.Callable;
69

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
171
  protected Time time;
172

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

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

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

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

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

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

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

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

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

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

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

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

    
314
  /**
315
   * <p>
316
   * Selection color of this view.
317
   * </p>
318
   *
319
   * @see #getSelectionColor()
320
   * @see #setSelectionColor(Color)
321
   */
322
  private Color selectionColor = MapContext.DEFAULT_SELECTION_COLOR;
323

    
324
  /**
325
   * <p>
326
   * Information about the map projection used in this view.
327
   * </p>
328
   *
329
   * @see #getProjection()
330
   * @see #setProjection(IProjection)
331
   */
332
  private IProjection proj;
333

    
334
  /**
335
   * <p>
336
   * Represents the distance in <i>world coordinates</i> equivalent to 1 pixel
337
   * in the view with the current extent.
338
   * </p>
339
   *
340
   * @see #getDist1pixel()
341
   * @see #setDist1pixel(double)
342
   */
343
  private double dist1pixel;
344

    
345
  /**
346
   * <p>
347
   * Represents the distance in <i>world coordinates</i> equivalent to 3 pixels
348
   * in the view with the current extent.
349
   * </p>
350
   *
351
   * @see #getDist3pixel()
352
   * @see #setDist3pixel(double)
353
   */
354
  private double dist3pixel;
355

    
356
  /**
357
   * <p>
358
   * Ratio between the size of <code>imageSize</code> and <code>extent</code>: <br>
359
   * <i>
360
   *
361
   * <pre>
362
   * min{(imageSize.getHeight()/extent.getHeight(), imageSize.getWidth()/extent.getWidth())}
363
   * </pre>
364
   *
365
   * </i>
366
   * </p>
367
   */
368
  private double scale;
369

    
370
  /**
371
   * <p>
372
   * Clipping area.
373
   * </p>
374
   *
375
   * @see #setClipRect(Rectangle2D)
376
   */
377
  private Rectangle2D cliprect;
378

    
379
  /**
380
   * <p>
381
   * Enables or disables the <i>"adjustable extent"</i> mode.
382
   * </p>
383
   * <p>
384
   * When calculates the affine transform, if
385
   * <ul>
386
   * <li><i>enabled</i>: the new <code>adjustedExtent</code> will have the (X,
387
   * Y) coordinates of the <code>extent</code> and an area that will be an scale
388
   * of the image size. That area will have different height or width (not both)
389
   * of the extent according the least ratio (height or width) in
390
   *
391
   * <pre>
392
   * image.size/extent.size&quot;
393
   * </pre>.
394
   * <li><i>disabled</i>: the new <code>adjustedExtent</code> will be like
395
   * <code>extent</code>.
396
   * </ul>
397
   * </p>
398
   *
399
   * @see #setAdjustable(boolean)
400
   */
401
  private boolean adjustableExtent = true;
402

    
403
  /**
404
   * <p>
405
   * ViewPort resolution in <i>dots-per-inch</i>. Useful to calculate the
406
   * geographic scale of the view.
407
   * </p>
408
   *
409
   * @see Toolkit#getScreenResolution()
410
   * @see MapContext#getScaleView()
411
   */
412
  private Double dpi = null;
413

    
414
  public ViewPort() {
415

    
416
  }
417

    
418
  /**
419
   * <p>
420
   * Creates a new view port with the information of the projection in
421
   * <code>proj</code> argument, and default configuration:
422
   * </p>
423
   * <p>
424
   * <ul>
425
   * <li><i><code>distanceUnits</code></i> = meters
426
   * <li><i><code>mapUnits</code></i> = meters
427
   * <li><i><code>backColor</code></i> = <i>undefined</i>
428
   * <li><i><code>offset</code></i> = <code>new Point2D.Double(0, 0);</code>
429
   * </ul>
430
   * </p>
431
   *
432
   * @param proj information of the projection for this view port
433
   */
434
  public ViewPort(IProjection proj) {
435
    // Por defecto
436
    this.proj = proj;
437
  }
438

    
439
  /**
440
   * <p>
441
   * Changes the status of the <i>"adjustable extent"</i> option to enabled or
442
   * disabled.
443
   * </p>
444
   * <p>
445
   * If view port isn't adjustable, won't bear in mind the aspect ratio of the
446
   * available rectangular area to calculate the affine transform from the
447
   * original map in real coordinates. (Won't scale the image to adapt it to the
448
   * available rectangular area).
449
   * </p>
450
   *
451
   * @param boolean the boolean to be set
452
   */
453
  public void setAdjustable(boolean adjustable) {
454
    if (adjustable == adjustableExtent) {
455
      return;
456
    }
457
    adjustableExtent = adjustable;
458
    this.updateDrawVersion();
459
  }
460

    
461
    private Collection<ViewPortListener> getListeners() {
462
        List<ViewPortListener> ll = Collections.unmodifiableList(listeners);
463
        return ll;
464
    }
465
    
466
  /**
467
   * <p>
468
   * Appends the specified {@link ViewPortListener ViewPortListener} listener if
469
   * weren't.
470
   * </p>
471
   *
472
   * @param listener the listener to add
473
   * @return <code>true</code> if has been added successfully
474
   * @see #removeViewPortListener(ViewPortListener)
475
   */
476
    public boolean addViewPortListener(ViewPortListener listener) {
477
        if (listener == null) {
478
            return false;
479
        }
480
        if( this.listeners.contains(listener) )  {
481
            return false;
482
        }
483
        this.listeners.add(listener);
484
        return true;
485
    }
486

    
487
  /**
488
   * <p>
489
   * Removes the specified {@link ViewPortListener ViewPortListener} listener,
490
   * if existed.
491
   * </p>
492
   *
493
   * @param listener the listener to remove
494
   * @return <code>true</code> if the contained the specified listener.
495
   * @see #addViewPortListener(ViewPortListener)
496
   */
497
    public boolean removeViewPortListener(ViewPortListener listener) {
498
        if (listener == null) {
499
            return false;
500
        }
501
        return this.listeners.remove(listener);
502
    }
503

    
504
  /**
505
   * <p>
506
   * Converts and returns the distance <code>d</code>, that is in <i>map
507
   * coordinates</i> to <i>screen coordinates</i> using a <i>delta transform</i>
508
   * with the transformation affine information in the {@link #trans #trans}
509
   * attribute.
510
   * </p>
511
   *
512
   * @param d distance in <i>map coordinates</i>
513
   * @return distance equivalent in <i>screen coordinates</i>
514
   * @see #toMapDistance(int)
515
   * @see AffineTransform#deltaTransform(Point2D, Point2D)S
516
   */
517
  public int fromMapDistance(double d) {
518
    Point2D.Double pWorld = new Point2D.Double(1, 1);
519
    Point2D.Double pScreen = new Point2D.Double();
520

    
521
    try {
522
      trans.deltaTransform(pWorld, pScreen);
523
    }
524
    catch (Exception e) {
525
      System.err.print(e.getMessage());
526
    }
527

    
528
    return (int) (d * pScreen.x);
529
  }
530

    
531
  /**
532
   * <p>
533
   * Converts and returns the 2D point <code>(x,y)</code>, that is in <i>map
534
   * coordinates</i> to <i>screen coordinates</i> (pixels) using the affine
535
   * transformation in the {@link #trans #trans} attribute.
536
   * </p>
537
   *
538
   * @param x the <code>x</code> <i>map coordinate</i> of a 2D point
539
   * @param y the <code>y</code> <i>map coordinate</i> of a 2D point
540
   * @return 2D point equivalent in <i>screen coordinates</i> (pixels)
541
   * @see #fromMapPoint(Point2D)
542
   * @see AffineTransform#transform(Point2D, Point2D)
543
   */
544
  public Point2D fromMapPoint(double x, double y) {
545
    Point2D.Double pWorld = new Point2D.Double(x, y);
546
    Point2D.Double pScreen = new Point2D.Double();
547

    
548
    try {
549
      trans.transform(pWorld, pScreen);
550
    }
551
    catch (Exception e) {
552
      System.err.print(e.getMessage());
553
    }
554

    
555
    return pScreen;
556
  }
557

    
558
  /**
559
   * <p>
560
   * Converts and returns the 2D point argument, that is in <i>map
561
   * coordinates</i> to <i>screen coordinates</i> (pixels) using the affine
562
   * transformation in the {@link #trans #trans} attribute.
563
   * </p>
564
   *
565
   * @param point the 2D point in <i>map coordinates</i>
566
   * @return 2D point equivalent in <i>screen coordinates</i> (pixels)
567
   * @see #toMapPoint(Point2D)
568
   * @see #fromMapPoint(double, double)
569
   */
570
  public Point2D fromMapPoint(Point2D point) {
571
    return fromMapPoint(point.getX(), point.getY());
572
  }
573

    
574
  /**
575
   * <p>
576
   * Converts and returns the 2D point <code>(x,y)</code>, that is in <i>screen
577
   * coordinates</i> (pixels) to <i>map coordinates</i> using the affine
578
   * transformation in the {@link #trans #trans} attribute.
579
   * </p>
580
   *
581
   * @param x the <code>x</code> <i>screen coordinate</i> of a 2D point
582
   * @param y the <code>y</code> <i>screen coordinate</i> of a 2D point
583
   * @return 2D point equivalent in <i>map coordinates</i>
584
   * @see #toMapPoint(Point2D)
585
   * @see #fromMapPoint(double, double)
586
   * @deprecated use {@link #convertToMapPoint(int, int)}
587
   */
588
  public Point2D toMapPoint(int x, int y) {
589
    Point2D pScreen = new Point2D.Double(x, y);
590

    
591
    return toMapPoint(pScreen);
592
  }
593

    
594
  /**
595
   * <p>
596
   * Converts and returns the {@link Rectangle2D Rectangle2D}, that is in
597
   * <i>screen coordinates</i> (pixels) to <i>map coordinates</i> using
598
   * {@linkplain #toMapDistance(int)}, and {@linkplain #toMapPoint(int, int)}.
599
   * </p>
600
   *
601
   * @param r the 2D rectangle in <i>screen coordinates</i> (pixels)
602
   * @return 2D rectangle equivalent in <i>map coordinates</i>
603
   * @see #fromMapRectangle(Rectangle2D)
604
   * @see #toMapDistance(int)
605
   * @see #toMapPoint(int, int)
606
   */
607
  public Rectangle2D toMapRectangle(Rectangle2D r) {
608
    Rectangle2D rect = new Rectangle2D.Double();
609
    Point2D p1 = toMapPoint((int) r.getX(), (int) r.getY());
610
    Point2D p2 = toMapPoint((int) r.getMaxX(), (int) r.getMaxY());
611
    rect.setFrameFromDiagonal(p1, p2);
612
    return rect;
613
  }
614

    
615
  /**
616
   * <p>
617
   * Converts and returns the distance <code>d</code>, that is in <i>screen
618
   * coordinates</i> to <i>map coordinates</i> using the transformation affine
619
   * information in the {@link #trans #trans} attribute.
620
   * </p>
621
   *
622
   * @param d distance in pixels
623
   * @return distance equivalent in <i>map coordinates</i>
624
   * @see #fromMapDistance(double)
625
   * @see AffineTransform
626
   */
627
  public double toMapDistance(int d) {
628
    double dist = d / trans.getScaleX();
629

    
630
    return dist;
631
  }
632

    
633
  /**
634
   * <p>
635
   * Converts and returns the 2D point argument, that is in <i>screen
636
   * coordinates</i> (pixels) to <i>map coordinates</i> using the inverse affine
637
   * transformation of the {@link #trans #trans} attribute.
638
   * </p>
639
   *
640
   * @param pScreen the 2D point in <i>screen coordinates</i> (pixels)
641
   * @return 2D point equivalent in <i>map coordinates</i>
642
   * @see #toMapPoint(int, int)
643
   * @see AffineTransform#createInverse()
644
   * @see AffineTransform#transform(Point2D, Point2D)
645
   * @deprecated use {@link #convertToMapPoint(Point2D)}
646
   */
647
  public Point2D toMapPoint(Point2D pScreen) {
648
    Point2D.Double pWorld = new Point2D.Double();
649
    AffineTransform at;
650

    
651
    if( pScreen == null ) {
652
        return null;
653
    }
654
    try {
655
      at = trans.createInverse();
656
      at.transform(pScreen, pWorld);
657
    }
658
    catch (NoninvertibleTransformException e) {
659
      throw new RuntimeException("Non invertible transform Exception", e);
660
    }
661

    
662
    return pWorld;
663
  }
664

    
665
  public Point convertToMapPoint(Point2D pScreen) {
666
    Point2D p = toMapPoint(pScreen);
667
    try {
668
        Point point = geomManager.createPoint(
669
                p.getX(), 
670
                p.getY(),
671
                Geometry.SUBTYPES.GEOM2D
672
        );
673
        point.setProjection(this.getProjection());
674
        return point;
675
    }
676
    catch (CreateGeometryException e) {
677
      // FIXME: Use a most especific exception.
678
      throw new RuntimeException(e);
679
    }
680
  }
681

    
682
  public Point convertToMapPoint(int x, int y) {
683
    Point2D pScreen = new Point2D.Double(x, y);
684

    
685
    return convertToMapPoint(pScreen);
686
  }
687

    
688
  /**
689
   * <p>
690
   * Returns the real distance (in <i>world coordinates</i>) at the graphic
691
   * layers of two 2D points (in <i>map coordinates</i>) of the plane where is
692
   * selected the <i>extent</i>.
693
   * </p>
694
   * <p>
695
   * If the projection of this view is UTM, considers the Earth curvature.
696
   * </p>
697
   *
698
   * @param pt1 a 2D point in <i>map coordinates</i>
699
   * @param pt2 another 2D point in <i>map coordinates</i>
700
   * @return the distance in meters between the two points 2D
701
   * @see GeoCalcImpl#distanceVincenty(Point2D, Point2D)
702
   */
703
  public double distanceWorld(Point2D pt1, Point2D pt2) {
704

    
705
    double dist = 0;
706
    if (proj.isProjected()) {
707
      dist = pt1.distance(pt2);
708
      dist = dist * MapContext.getDistanceTrans2Meter()[getMapUnits()];
709
    }
710
    else {
711
      GeoCalc geocalc = new GeoCalc(proj);
712
      dist = geocalc.distanceVincenty(pt1, pt2);
713
    }
714
    return dist;
715
  }
716

    
717
  /**
718
   * <p>
719
   * Sets as extent and adjusted extent of this view port, the previous.
720
   * Recalculating its parameters.
721
   * </p>
722
   *
723
   * @see #getExtents()
724
   * @see #calculateAffineTransform()
725
   * @deprecated use {@link ViewPort#setPreviousEnvelope()}
726
   */
727
  public void setPreviousExtent() {
728
    setPreviousEnvelope();
729
  }
730

    
731
  /**
732
   * <p>
733
   * Sets as envelope and adjusted envelope of this view port, the previous.
734
   * Recalculating its parameters.
735
   * Stores the current extent in the next extents of the history.
736
   * </p>
737
   *
738
   * @see #getExtents()
739
   * @see #calculateAffineTransform()
740
   */
741
  public void setPreviousEnvelope() {
742
    this.updateDrawVersion();
743

    
744
//    extentsHistory.putNext(extent);
745
//    extent = extentsHistory.removePrev();
746
    extent = extentsHistory.setPreviousExtent();
747

    
748
    // Calcula la transformaci?n af?n
749
    calculateAffineTransform();
750

    
751
    // Lanzamos los eventos de extent cambiado
752
    callExtentChanged(getAdjustedExtent());
753
  }
754

    
755
  /**
756
   * <p>
757
   * Sets as envelope and adjusted envelope of this view port, the next.
758
   * Recalculating its parameters.
759
   * Stores the current extent in the previous extents of the history.
760
   * </p>
761
   *
762
   * @see #getExtents()
763
   * @see #calculateAffineTransform()
764
   */
765
  public void setNextEnvelope() {
766
    this.updateDrawVersion();
767

    
768
    extent = extentsHistory.setNextExtent();
769

    
770
    // Calcula la transformaci?n af?n
771
    calculateAffineTransform();
772

    
773
    // Lanzamos los eventos de extent cambiado
774
    callExtentChanged(getAdjustedExtent());
775
  }
776

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

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

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

    
895
    if (this.extent != null && this.extent.equals(newExtent)) {
896
      return;
897
    }
898

    
899
    this.updateDrawVersion();
900
    this.extent = newExtent;
901
    try {
902
        calculateAffineTransform();
903
    } catch(Exception ex) {
904
        this.extent = null;
905
        throw ex;
906
    }
907
    extentsHistory.put(extent);
908

    
909

    
910
    // Lanzamos los eventos de extent cambiado
911
    callExtentChanged(getAdjustedExtent());
912
  }
913

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

    
938
    // Calcula la transformaci?n af?n
939
    calculateAffineTransform();
940

    
941
    // Lanzamos los eventos de extent cambiado
942
    callExtentChanged(getAdjustedExtent());
943
  }
944

    
945
  /**
946
   * <p>
947
   * Calculates and returns using the current projection of this view port, the
948
   * scale that is the extent in <i>screen coordinates</i> from the image in
949
   * <i>map coordinates</i>.
950
   * </p>
951
   *
952
   * @return the scale <i>extent / image size</i> projected by this view port
953
   * @deprecated since 07/09/07, use {@linkplain MapContext#getScaleView()}
954
   */
955
  private double getScale() {
956

    
957
    double[] trans2Meter = MapContext.getDistanceTrans2Meter();
958
    if (proj == null) {
959
      double wmeters = ((getImageSize().width / this.getDPI()) * 0.0254);
960
      return (long) ((trans2Meter[getMapUnits()] * getAdjustedEnvelope()
961
          .getLength(0)) / wmeters);
962
    }
963
    else {
964
      return Math.round(proj.getScale(getAdjustedEnvelope().getMinimum(0)
965
          * trans2Meter[getMapUnits()], getAdjustedEnvelope().getMaximum(0)
966
          * trans2Meter[getMapUnits()], getImageSize().width, this.getDPI()));
967
    }
968

    
969
    /*
970
     * return proj.getScale(extent.getMinX(), extent.getMaxX(), imageSize.width,
971
     * dpi);
972
     */
973
  }
974

    
975
  /**
976
   * <p>
977
   * Affine transformation between <i>map 2D coordinates</i> to <i>screen 2D
978
   * coordinates</i> (pixels), preserving the "straightness" and "parallelism"
979
   * of the lines.
980
   * </p>
981
   *
982
   * @return the affine transformation
983
   * @see #setAffineTransform(AffineTransform)
984
   * @see #calculateAffineTransform()
985
   */
986
  public AffineTransform getAffineTransform() {
987
    return trans;
988
  }
989

    
990
  /**
991
   * <p>
992
   * Returns the size of the image projected.
993
   * </p>
994
   *
995
   * @return the image size
996
   * @see #setImageSize(Dimension)
997
   * @see #getImageHeight()
998
   * @see #getImageWidth()
999
   */
1000
  public Dimension getImageSize() {
1001
    return imageSize;
1002
  }
1003

    
1004
  /**
1005
   * <p>
1006
   * Sets the size of the image projected, recalculating the parameters of this
1007
   * view port.
1008
   * </p>
1009
   *
1010
   * @param imageSize the image size
1011
   * @see #getImageSize()
1012
   * @see #calculateAffineTransform()
1013
   */
1014
  public void setImageSize(Dimension imageSize) {
1015

    
1016
    if (this.imageSize == null || (!this.imageSize.equals(imageSize))) {
1017
      this.updateDrawVersion();
1018
      this.imageSize = imageSize;
1019
      calculateAffineTransform();
1020
    }
1021
  }
1022

    
1023
  /**
1024
   * <p>
1025
   * Notifies to all view port listeners registered, that the adjusted extent of
1026
   * this view port has changed.
1027
   * </p>
1028
   *
1029
   * @param newRect the new adjusted extend
1030
   * @see #refreshExtent()
1031
   * @see #setEnvelope(Envelope)
1032
   * @see #setPreviousExtent()
1033
   * @see ExtentEvent
1034
   * @see ViewPortListener
1035
   */
1036
    protected void callExtentChanged(Envelope newRect) {
1037
        ExtentEvent ev = ExtentEvent.createExtentEvent(newRect);
1038

    
1039
        for (ViewPortListener listener : this.getListeners()) {
1040
            if (listener != null) {
1041
                listener.extentChanged(ev);
1042
            }
1043
        }
1044
    }
1045

    
1046
  /**
1047
   * <p>
1048
   * Notifies to all view port listeners registered, that the time of this view
1049
   * port has changed.
1050
   * </p>
1051
   *
1052
   * @param newTime the new time
1053
   * @see #refreshExtent()
1054
   * @see #setTime(Time)
1055
   * @see ExtentEvent
1056
   * @see ViewPortListener
1057
   */
1058
    protected void callTimeChanged(Time newTime) {
1059
        ExtentEvent viewPortEvent = new ExtentEvent(newTime);
1060

    
1061
        for (ViewPortListener listener : this.getListeners()) {
1062
            if (listener != null) {
1063
                listener.extentChanged(viewPortEvent);
1064
            }
1065
        }
1066
    }
1067

    
1068
  /**
1069
   * <p>
1070
   * Notifies to all view port listeners registered, that the background color
1071
   * of this view port has changed.
1072
   * </p>
1073
   *
1074
   * @param c the new background color
1075
   * @see #setBackColor(Color)
1076
   * @see ColorEvent
1077
   * @see ViewPortListener
1078
   */
1079
    private void callColorChanged(Color c) {
1080
        ColorEvent ce = ColorEvent.createColorEvent(c);
1081

    
1082
        for (ViewPortListener listener : this.getListeners()) {
1083
            if (listener != null) {
1084
                listener.backColorChanged(ce);
1085
            }
1086
        }
1087
    }
1088

    
1089
  /**
1090
   * <p>
1091
   * Notifies to all view port listeners registered, that the projection of this
1092
   * view port has changed.
1093
   * </p>
1094
   *
1095
   * @param projection the new projection
1096
   * @see #setProjection(IProjection)
1097
   * @see ProjectionEvent
1098
   * @see ViewPortListener
1099
   */
1100
    private void callProjectionChanged(IProjection projection) {
1101
        ProjectionEvent ev = ProjectionEvent.createProjectionEvent(projection);
1102

    
1103
        for (ViewPortListener listener : this.getListeners()) {
1104
            if (listener != null) {
1105
                listener.projectionChanged(ev);
1106
            }
1107
        }
1108
    }
1109

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

    
1144
    AffineTransform escalado = new AffineTransform();
1145
    AffineTransform translacion = new AffineTransform();
1146

    
1147
    double escalaX;
1148
    double escalaY;
1149

    
1150
    escalaX = imageSize.width / extent.getWidth();
1151
    escalaY = imageSize.height / extent.getHeight();
1152

    
1153
    double xCenter = extent.getCenterX();
1154
    double yCenter = extent.getCenterY();
1155
    double newHeight;
1156
    double newWidth;
1157

    
1158
    adjustedExtent = new Rectangle2D.Double();
1159

    
1160
    if (adjustableExtent) {
1161
      if (escalaX < escalaY) {
1162
        scale = escalaX;
1163
        newHeight = imageSize.height / scale;
1164
        adjustedExtent.setRect(xCenter - (extent.getWidth() / 2.0), yCenter
1165
            - (newHeight / 2.0), extent.getWidth(), newHeight);
1166
      }
1167
      else {
1168
        scale = escalaY;
1169
        newWidth = imageSize.width / scale;
1170
        adjustedExtent.setRect(xCenter - (newWidth / 2.0),
1171
            yCenter - (extent.getHeight() / 2.0), newWidth, extent.getHeight());
1172
      }
1173
      escalado.setToScale(scale, -scale);
1174
    }
1175
    else { // adjusted is same as extent
1176
      scale = escalaX;
1177
      adjustedExtent.setFrame(extent);
1178
      escalado.setToScale(escalaX, -escalaY);
1179
    }
1180
    Envelope env = getAdjustedExtent();
1181
    if (env == null) {
1182
      return;
1183
    }
1184
    translacion.setToTranslation(-env.getMinimum(0), -env.getMinimum(1)
1185
        - getAdjustedExtent().getLength(1));
1186

    
1187
    AffineTransform offsetTrans = new AffineTransform();
1188
    offsetTrans.setToTranslation(offset.getX(), offset.getY());
1189

    
1190
    trans.setToIdentity();
1191
    trans.concatenate(offsetTrans);
1192
    trans.concatenate(escalado);
1193

    
1194
    trans.concatenate(translacion);
1195

    
1196
    // Calculamos las distancias de 1 pixel y 3 pixel con esa
1197
    // transformaci?n
1198
    // de coordenadas, de forma que est?n precalculadas para cuando las
1199
    // necesitemos
1200
    AffineTransform at;
1201

    
1202
    try {
1203
      at = trans.createInverse();
1204

    
1205
      Point2D pPixel = new Point2D.Float(1, 1);
1206

    
1207
      Point2D.Float pProv = new Point2D.Float();
1208
      at.deltaTransform(pPixel, pProv);
1209

    
1210
      dist1pixel = pProv.x;
1211
      dist3pixel = 3 * pProv.x;
1212
    }
1213
    catch (NoninvertibleTransformException e) {
1214
      String msg = "Can't calculate affine transform for the view port." + "\n" +
1215
        "The extent can be out of range for this projection." + "\n" +
1216
        "transformada afin: " + Objects.toString(trans) + "\n" +
1217
        "extent: " + Objects.toString(extent) + "\n" +
1218
        "imageSize: " + Objects.toString(imageSize) + "\n" +
1219
        "projection: " + Objects.toString(proj) + "\n" +
1220
        e.getLocalizedMessage() 
1221
      ;
1222
      logger.error(msg, e);
1223
      trans.setToIdentity();
1224
      scale = 0;
1225
      adjustedExtent = null;
1226
      adjustableExtent = true;
1227
      throw new RuntimeException(msg, e);
1228
    }
1229
  }
1230

    
1231
  /**
1232
   * <p>
1233
   * Sets the offset.
1234
   * </p>
1235
   * <p>
1236
   * The offset is the position where start drawing the map.
1237
   * </p>
1238
   *
1239
   * @param p 2D point that represents the offset in pixels
1240
   * @see #getOffset()
1241
   */
1242
  public void setOffset(Point2D p) {
1243
    if (!offset.equals(p)) {
1244
      this.updateDrawVersion();
1245
      offset = p;
1246
    }
1247
  }
1248

    
1249
  /**
1250
   * <p>
1251
   * Gets the offset.
1252
   * </p>
1253
   * <p>
1254
   * The offset is the position where start drawing the map.
1255
   * </p>
1256
   *
1257
   * @return 2D point that represents the offset in pixels
1258
   * @see #setOffset(Point2D)
1259
   */
1260
  public Point2D getOffset() {
1261
    return offset;
1262
  }
1263

    
1264
  /**
1265
   * <p>
1266
   * Sets the background color.
1267
   * </p>
1268
   *
1269
   * @param c the new background color
1270
   * @see #getBackColor()
1271
   */
1272
  public void setBackColor(Color c) {
1273
    if (!c.equals(this.backColor)) {
1274
      this.updateDrawVersion();
1275
      backColor = c;
1276
      callColorChanged(backColor);
1277
    }
1278
  }
1279

    
1280
  /**
1281
   * <p>
1282
   * Sets the selection color.
1283
   * </p>
1284
   *
1285
   * @param c the new selection color
1286
   * @see #getSelectionColor()
1287
   */
1288
  public void setSelectionColor(Color c) {
1289
    if (!c.equals(this.selectionColor)) {
1290
      this.updateDrawVersion();
1291
      selectionColor = c;
1292
      callColorChanged(selectionColor);
1293
    }
1294
  }
1295

    
1296
  /**
1297
   * <p>
1298
   * Gets the background color.
1299
   * </p>
1300
   *
1301
   * @return the background color of the view
1302
   * @see #setBackColor(Color)
1303
   */
1304
  public Color getBackColor() {
1305
    return backColor;
1306
  }
1307

    
1308
  /**
1309
   * <p>
1310
   * Gets the selection color.
1311
   * </p>
1312
   *
1313
   * @return the selection color of the view
1314
   * @see #setSelectionColor(Color)
1315
   */
1316
  public Color getSelectionColor() {
1317
    return selectionColor;
1318
  }
1319

    
1320
  /**
1321
   * <p>
1322
   * Returns the extent currently covered by the view adjusted (scaled) to the
1323
   * image size aspect.
1324
   * </p>
1325
   *
1326
   * @return extent of the view adjusted to the image size aspect
1327
   * @see #setAdjustable(boolean)
1328
   * @deprecated use {@link ViewPort#getAdjustedEnvelope()} instead
1329
   */
1330
  public Envelope getAdjustedExtent() {
1331
    return getAdjustedEnvelope();
1332
  }
1333

    
1334
  /**
1335
   * <p>
1336
   * Returns the envelope currently covered by the view adjusted (scaled) to the
1337
   * image size aspect.
1338
   * </p>
1339
   *
1340
   * @return envelope of the view adjusted to the image size aspect
1341
   * @see #setAdjustable(boolean)
1342
   */
1343
    public Envelope getAdjustedEnvelope() {
1344
        if (adjustedExtent == null) {
1345
            calculateAffineTransform();
1346
        }
1347
        if (cliprect != null) {
1348
            Rectangle2D r = adjustedExtent.createIntersection(cliprect);
1349
            try {
1350
                return geomManager.createEnvelope(r.getX(), r.getY(), r.getMaxX(), r.getMaxY(), SUBTYPES.GEOM2D);
1351
            } catch (CreateEnvelopeException e) {
1352
                e.printStackTrace();
1353
                logger.error("Error adjusting the extent", e);
1354
            }
1355
        }
1356
        if (adjustedExtent != null) {
1357
            try {
1358
                return geomManager.createEnvelope(adjustedExtent.getX(), adjustedExtent.getY(),
1359
                    adjustedExtent.getMaxX(), adjustedExtent.getMaxY(), SUBTYPES.GEOM2D);
1360
            } catch (CreateEnvelopeException e) {
1361
                e.printStackTrace();
1362
                logger.error("Error adjusting the extent", e);
1363
            }
1364
        }
1365
        return null;
1366
    }
1367

    
1368
  /**
1369
   * <p>
1370
   * Returns the measurement unit of this view port used for measuring distances
1371
   * and displaying information.
1372
   * </p>
1373
   *
1374
   * @return the measurement unit of this view used for measuring distances and
1375
   *         displaying information
1376
   * @see #setDistanceUnits(int)
1377
   */
1378
  public int getDistanceUnits() {
1379
    return distanceUnits;
1380
  }
1381

    
1382
  /**
1383
   * <p>
1384
   * Returns the measurement unit of this view port used for measuring areas and
1385
   * displaying information.
1386
   * </p>
1387
   *
1388
   * @return the measurement unit of this view used for measuring areas and
1389
   *         displaying information
1390
   * @see #setDistanceUnits(int)
1391
   */
1392
  public int getDistanceArea() {
1393
    return distanceArea;
1394
  }
1395

    
1396
  /**
1397
   * <p>
1398
   * Sets the measurement unit of this view port used for measuring distances
1399
   * and displaying information.
1400
   * </p>
1401
   *
1402
   * @param distanceUnits the measurement unit of this view used for measuring
1403
   *          distances and displaying information
1404
   * @see #getDistanceUnits()
1405
   */
1406
  public void setDistanceUnits(int distanceUnits) {
1407
    this.distanceUnits = distanceUnits;
1408
  }
1409

    
1410
  /**
1411
   * <p>
1412
   * Sets the measurement unit of this view port used for measuring areas and
1413
   * displaying information.
1414
   * </p>
1415
   *
1416
   * @param distanceUnits the measurement unit of this view used for measuring
1417
   *          areas and displaying information
1418
   * @see #getDistanceUnits()
1419
   */
1420
  public void setDistanceArea(int distanceArea) {
1421
    this.distanceArea = distanceArea;
1422
  }
1423

    
1424
  /**
1425
   * <p>
1426
   * Gets the measurement unit used by this view port for the map.
1427
   * </p>
1428
   *
1429
   * @return Returns the current map measure unit
1430
   * @see #setMapUnits(int)
1431
   */
1432
  public int getMapUnits() {
1433
    return mapUnits;
1434
  }
1435

    
1436
  /**
1437
   * <p>
1438
   * Sets the measurement unit used by this view port for the map.
1439
   * </p>
1440
   *
1441
   * @param mapUnits the new map measure unit
1442
   * @see #getMapUnits()
1443
   */
1444
  public void setMapUnits(int mapUnits) {
1445
    this.mapUnits = mapUnits;
1446
  }
1447

    
1448
  /**
1449
   * <p>
1450
   * Gets the width in <i>screen coordinates</i> of the rectangle where the
1451
   * image is displayed.
1452
   * </p>
1453
   * <p>
1454
   * Used by {@linkplain #calculateAffineTransform()} to calculate:<br>
1455
   * <ul>
1456
   * <li>The new {@link #scale scale} .
1457
   * <li>The new {@link #adjustedExtent adjustableExtent} .
1458
   * <li>The new {@link #trans trans} .
1459
   * <li>The new real world coordinates equivalent to 1 pixel (
1460
   * {@link #dist1pixel dist1pixel}) .
1461
   * <li>The new real world coordinates equivalent to 3 pixels (
1462
   * {@link #dist3pixel dist3pixel}) .
1463
   * </ul>
1464
   * </p>
1465
   *
1466
   * @see #getImageHeight()
1467
   * @see #getImageSize()
1468
   * @see #setImageSize(Dimension)
1469
   */
1470
  public int getImageWidth() {
1471
    return imageSize.width;
1472
  }
1473

    
1474
  /**
1475
   * <p>
1476
   * Gets the height in <i>screen coordinates</i> of the rectangle where the
1477
   * image is displayed.
1478
   * </p>
1479
   * <p>
1480
   * Used by {@linkplain #calculateAffineTransform()} to calculate:<br>
1481
   * <ul>
1482
   * <li>The new {@link #scale scale} .
1483
   * <li>The new {@link #adjustedExtent adjustableExtent} .
1484
   * <li>The new {@link #trans trans} .
1485
   * <li>The new real world coordinates equivalent to 1 pixel (
1486
   * {@link #dist1pixel dist1pixel}) .
1487
   * <li>The new real world coordinates equivalent to 3 pixels (
1488
   * {@link #dist3pixel dist3pixel}) .
1489
   * </ul>
1490
   * </p>
1491
   *
1492
   * @see #getImageWidth()
1493
   * @see #getImageSize()
1494
   * @see #setImageSize(Dimension)
1495
   */
1496
  public int getImageHeight() {
1497
    return imageSize.height;
1498
  }
1499

    
1500
  /**
1501
   * <p>
1502
   * Gets the distance in <i>world coordinates</i> equivalent to 1 pixel in the
1503
   * view with the current extent.
1504
   * </p>
1505
   *
1506
   * @return the distance
1507
   * @see #setDist1pixel(double)
1508
   */
1509
  public double getDist1pixel() {
1510
    return dist1pixel;
1511
  }
1512

    
1513
  /**
1514
   * <p>
1515
   * Sets the distance in <i>world coordinates</i> equivalent to 1 pixel in the
1516
   * view with the current extent.
1517
   * </p>
1518
   *
1519
   * @param dist1pixel the distance
1520
   * @see #getDist1pixel()
1521
   */
1522
  public void setDist1pixel(double dist1pixel) {
1523
    if (dist1pixel == this.dist1pixel) {
1524
      return;
1525
    }
1526
    this.updateDrawVersion();
1527
    this.dist1pixel = dist1pixel;
1528
  }
1529

    
1530
  /**
1531
   * <p>
1532
   * Gets the distance in <i>world coordinates</i> equivalent to 3 pixels in the
1533
   * view with the current extent.
1534
   * </p>
1535
   *
1536
   * @return the distance
1537
   * @see #setDist3pixel(double)
1538
   */
1539
  public double getDist3pixel() {
1540
    return dist3pixel;
1541
  }
1542

    
1543
  /**
1544
   * <p>
1545
   * Sets the distance in <i>world coordinates</i> equivalent to 3 pixels in the
1546
   * view with the current extent.
1547
   * </p>
1548
   *
1549
   * @param dist3pixel the distance
1550
   * @see #getDist3pixel()
1551
   */
1552
  public void setDist3pixel(double dist3pixel) {
1553
    if (this.dist3pixel == dist3pixel) {
1554
      return;
1555
    }
1556
    this.updateDrawVersion();
1557
    this.dist3pixel = dist3pixel;
1558
  }
1559

    
1560
  /**
1561
   * <p>
1562
   * Returns the last previous extents of this view port.
1563
   * </p>
1564
   *
1565
   * @return the last previous extents of this view port
1566
   * @see #setPreviousExtent()
1567
   * @deprecated use {@link ViewPort#getEnvelopes()}
1568
   */
1569
  public ExtentHistory getExtents() {
1570
    return getEnvelopes();
1571
  }
1572

    
1573
  /**
1574
   * <p>
1575
   * Returns the last previous extents of this view port.
1576
   * </p>
1577
   *
1578
   * @return the last previous extents of this view port
1579
   * @see #setPreviousExtent()
1580
   */
1581
  public ExtentHistory getEnvelopes() {
1582
    return extentsHistory;
1583
  }
1584

    
1585
  /**
1586
   * <p>
1587
   * Gets the projection used in this view port.
1588
   * </p>
1589
   *
1590
   * @return projection used in this view port
1591
   * @see #setProjection(IProjection)
1592
   */
1593
  public IProjection getProjection() {
1594
    return proj;
1595
  }
1596

    
1597
  /**
1598
   * <p>
1599
   * Sets the projection to this view port.
1600
   * </p>
1601
   *
1602
   * @param proj the new projection
1603
   * @see #getProjection()
1604
   */
1605
  public void setProjection(IProjection proj) {
1606
    if (this.proj == null || !this.proj.getAbrev().equals(proj.getAbrev())) {
1607
        this.updateDrawVersion();
1608
        this.proj = proj;
1609
        int metros = MapContext.getDistancePosition("Metros");
1610
        int grados = MapContext.getDistancePosition("Grados");
1611
        if (proj.isProjected()) {
1612
            if (this.getMapUnits() == grados) {
1613
                this.setMapUnits(metros);
1614
            }
1615
        } else {
1616
            this.setMapUnits(grados);
1617
        }
1618

    
1619
        callProjectionChanged(proj);
1620
    }
1621
  }
1622

    
1623
  // -----------------------------------------------------------------------------------------------------------
1624
  // NOTA PARA DESARROLLADORES SOBRE EL M?TODO
1625
  // "public void setAffineTransform(AffineTransform at)"
1626
  // ==============================================================================================
1627
  // Only used for print, should be removed, redefining the {@link
1628
  // RasterAdapter RasterAdapter} interface,
1629
  // allowing it to receive a {@link ViewPortData ViewPortData} .
1630
  // -----------------------------------------------------------------------------------------------------------
1631

    
1632
  /**
1633
   * <p>
1634
   * Sets only the affine transform to this view port, without updating
1635
   * dependent attributes.
1636
   * </p>
1637
   * <p>
1638
   * <b><i>This method could be problematic!</i></b>
1639
   * </p>
1640
   *
1641
   * @param at the affine transform to set
1642
   * @see #getAffineTransform()
1643
   * @see #calculateAffineTransform()
1644
   */
1645
  public void setAffineTransform(AffineTransform at) {
1646
    this.trans = at;
1647
  }
1648

    
1649
  /**
1650
   * <p>
1651
   * Returns an XML entity that represents this view port instance:<br>
1652
   * <ul>
1653
   * <li>Properties:
1654
   * <ul>
1655
   * <li><i>className</i>: name of this class.
1656
   * <li>If defined, the adjusted extent:
1657
   * <ul>
1658
   * <li><i>adjustedExtentX</i>: X coordinate of the adjusted extent.
1659
   * <li><i>adjustedExtentY</i>: Y coordinate of the adjusted extent.
1660
   * <li><i>adjustedExtentW</i>: width of the adjusted extent.
1661
   * <li><i>adjustedExtentH</i>: height of the adjusted extent.
1662
   * </ul>
1663
   * <li>If defined, the background color:
1664
   * <ul>
1665
   * <li><i>backColor</i>: background color.
1666
   * </ul>
1667
   * <li>If defined, the clip:
1668
   * <ul>
1669
   * <li><i>clipX</i>: X coordinate of the clip.
1670
   * <li><i>clipY</i>: Y coordinate of clip.
1671
   * <li><i>clipW</i>: width of the clip.
1672
   * <li><i>clipH</i>: height of the clip.
1673
   * </ul>
1674
   * <li><i>dist1pixel</i>: the distance in world coordinates equivalent to 1
1675
   * pixel in the view.
1676
   * <li><i>dist3pixel</i>: the distance in world coordinates equivalent to 3
1677
   * pixels in the view.
1678
   * <li><i>distanceUnits</i>: the distance measurement unit.
1679
   * <li>If defined, the extent:
1680
   * <ul>
1681
   * <li><i>extentX</i>: X coordinate of the extent.
1682
   * <li><i>extentY</i>: Y coordinate of the extent.
1683
   * <li><i>extentW</i>: width of the extent.
1684
   * <li><i>extentH</i>: height of the extent.
1685
   * </ul>
1686
   * <li><i>mapUnits</i>: the map measurement unit.
1687
   * <li><i>offsetX</i>: X coordinate of the offset.
1688
   * <li><i>offsetY</i>: Y coordinate of the offset.
1689
   * <li>If defined, the projection:
1690
   * <ul>
1691
   * <li>If its defined, the projection:
1692
   * <ul>
1693
   * <li><i>proj</i>: the projection.</li>
1694
   * </ul>
1695
   * </ul>
1696
   * <li><i>scale</i>: ratio between the size of <code>imageSize</code> and
1697
   * <code>extent</code>.
1698
   * </ul>
1699
   * <li>Child branches:
1700
   * <ul>
1701
   * <li>XML entity of the internal {@link ExtentHistory ExtentHistory} .
1702
   * </ul>
1703
   * </ul>
1704
   *
1705
   * @return the XML entity
1706
   * @see #createFromXML(XMLEntity)
1707
   */
1708
  public void saveToState(PersistentState state) throws PersistenceException {
1709

    
1710
    state.set(FIELD_ADJUSTED_EXTENT, adjustedExtent);
1711
    state.set(FIELD_BACK_COLOR, backColor);
1712
    state.set(FIELD_CLIP, cliprect);
1713
    state.set(FIELD_DIST1PIXEL, dist1pixel);
1714
    state.set(FIELD_DIST3PIXEL, dist3pixel);
1715
    state.set(FIELD_DISTANCE_UNITS, distanceUnits);
1716
    state.set(FIELD_DISTANCE_AREA, distanceArea);
1717

    
1718
    state.set(FIELD_EXTENT, extent);
1719
    state.set(FIELD_EXTENTS, extentsHistory);
1720

    
1721
    state.set(FIELD_MAP_UNITS, mapUnits);
1722
    state.set(FIELD_OFFSET, offset);
1723

    
1724
    state.set(FIELD_PROJ, proj);
1725

    
1726
    state.set(FIELD_IMAGE_SIZE, imageSize);
1727
    
1728
    state.set("selectionColor", this.getSelectionColor());
1729

    
1730
  }
1731

    
1732
  public void loadFromState(PersistentState state) throws PersistenceException {
1733

    
1734
    adjustedExtent = (Rectangle2D) state.get(FIELD_ADJUSTED_EXTENT);
1735
    backColor = (Color) state.get(FIELD_BACK_COLOR);
1736
    cliprect = (Rectangle2D) state.get(FIELD_CLIP);
1737
    dist1pixel = state.getDouble(FIELD_DIST1PIXEL);
1738
    dist3pixel = state.getDouble(FIELD_DIST3PIXEL);
1739
    distanceUnits = state.getInt(FIELD_DISTANCE_UNITS);
1740
    extentsHistory = (ExtentHistory) state.get(FIELD_EXTENTS);
1741
    extent = (Rectangle2D) state.get(FIELD_EXTENT);
1742
    mapUnits = state.getInt(FIELD_MAP_UNITS);
1743
    offset = (Point2D) state.get(FIELD_OFFSET);
1744
    proj = (IProjection) state.get(FIELD_PROJ);
1745
    imageSize = (Dimension) state.get(FIELD_IMAGE_SIZE);
1746
    distanceArea = state.getInt(FIELD_DISTANCE_AREA);
1747

    
1748
    this.selectionColor = (Color) state.get("selectionColor");
1749
    if (this.selectionColor == null) {
1750
        this.selectionColor = DEFAULT_SELECTION_COLOR;
1751
    }
1752
    
1753
    refreshExtent();
1754
  }
1755

    
1756
  public static class RegisterPersistence implements Callable {
1757

    
1758
    public Object call() throws Exception {
1759
      PersistenceManager manager = ToolsLocator.getPersistenceManager();
1760
      if (manager.getDefinition("ViewPort") == null) {
1761
        DynStruct definition = manager.addDefinition(ViewPort.class,
1762
            "ViewPort", "ViewPort Persistence definition", null, null);
1763

    
1764
        definition.addDynFieldObject(FIELD_ADJUSTED_EXTENT)
1765
            .setClassOfValue(Rectangle2D.class).setMandatory(false);
1766

    
1767
        definition.addDynFieldObject(FIELD_BACK_COLOR)
1768
            .setClassOfValue(Color.class).setMandatory(false);
1769

    
1770
        definition.addDynFieldObject(FIELD_CLIP)
1771
            .setClassOfValue(Rectangle2D.class).setMandatory(false);
1772

    
1773
        definition.addDynFieldDouble(FIELD_DIST1PIXEL).setMandatory(true);
1774

    
1775
        definition.addDynFieldDouble(FIELD_DIST3PIXEL).setMandatory(true);
1776

    
1777
        definition.addDynFieldInt(FIELD_DISTANCE_UNITS).setMandatory(true);
1778

    
1779
        definition.addDynFieldInt(FIELD_DISTANCE_AREA).setMandatory(false);
1780

    
1781
        definition.addDynFieldObject(FIELD_EXTENT)
1782
            .setClassOfValue(Rectangle2D.class).setMandatory(false);
1783

    
1784
        definition.addDynFieldObject(FIELD_EXTENTS)
1785
            .setClassOfValue(ExtentHistory.class).setMandatory(true);
1786

    
1787
        definition.addDynFieldInt(FIELD_MAP_UNITS).setMandatory(true);
1788

    
1789
        definition.addDynFieldObject(FIELD_OFFSET)
1790
            .setClassOfValue(Point2D.class).setMandatory(false);
1791

    
1792
        definition.addDynFieldObject(FIELD_PROJ)
1793
            .setClassOfValue(IProjection.class).setMandatory(true);
1794

    
1795
        definition.addDynFieldObject(FIELD_IMAGE_SIZE)
1796
            .setClassOfValue(Dimension.class).setMandatory(false);
1797

    
1798
        definition.addDynFieldObject("selectionColor")
1799
            .setClassOfValue(Color.class)
1800
            .setMandatory(false);
1801

    
1802
      }
1803
      return Boolean.TRUE;
1804
    }
1805

    
1806
  }
1807

    
1808
  /**
1809
   * Clone the view port without clone the listeners nor the extent history.
1810
   *
1811
   * @return the cloned view port
1812
   */
1813
  public Object clone() throws CloneNotSupportedException {
1814

    
1815
    ViewPort clonedViewPort = (ViewPort) super.clone();
1816
    clonedViewPort.listeners = new ArrayList<>();
1817
    clonedViewPort.extentsHistory = new ExtentHistory();
1818

    
1819
    if (this.adjustedExtent != null) {
1820
      clonedViewPort.adjustedExtent = (Rectangle2D) this.adjustedExtent.clone();
1821
    }
1822

    
1823
    if (this.cliprect != null) {
1824
      clonedViewPort.cliprect = (Rectangle2D) this.cliprect.clone();
1825
    }
1826

    
1827
    if (this.extent != null) {
1828
      clonedViewPort.extent = (Rectangle2D) this.extent.clone();
1829
    }
1830
    if (this.imageSize != null) {
1831
      clonedViewPort.imageSize = (Dimension) this.imageSize.clone();
1832
    }
1833

    
1834
    if (this.offset != null) {
1835
      clonedViewPort.offset = (Point2D) this.offset.clone();
1836
    }
1837
    if (proj != null) {
1838
      clonedViewPort.proj = (IProjection) this.proj.clone();
1839
    }
1840

    
1841
    clonedViewPort.trans = (AffineTransform) this.trans.clone();
1842

    
1843
    return clonedViewPort;
1844
  }
1845

    
1846
  /**
1847
   * <p>
1848
   * Returns a <code>String</code> representation of the main values of this
1849
   * view port: <code>{@linkplain #extent}</code>,
1850
   * <code>{@linkplain #adjustedExtent}</code>,
1851
   * <code>{@linkplain #imageSize}</code>, <code>{@linkplain #scale}</code>, and
1852
   * <code>{@linkplain #trans}</code>.
1853
   * </p>
1854
   *
1855
   * @return a <code>string</code> representation of the main values of this
1856
   *         view port
1857
   */
1858
  public String toString() {
1859

    
1860
    String str;
1861
    str = "Datos del viewPort:\nExtent=" + extent + "\nadjustedExtent="
1862
        + adjustedExtent + "\nimageSize=" + imageSize + "\nescale=" + scale
1863
        + "\ntrans=" + trans;
1864

    
1865
    return str;
1866
  }
1867

    
1868
  /**
1869
   * <p>
1870
   * Sets the position and size of the clipping rectangle.
1871
   * </p>
1872
   *
1873
   * @param rectView the clipping rectangle to set
1874
   */
1875
  public void setClipRect(Rectangle2D rectView) {
1876
    this.updateDrawVersion();
1877
    cliprect = rectView;
1878
  }
1879

    
1880
  /**
1881
   * <p>
1882
   * Converts and returns the {@link Rectangle2D Rectangle2D}, that is in <i>map
1883
   * coordinates</i> to <i>screen coordinates</i> (pixels) using an <i>inverse
1884
   * transform</i> with the transformation affine information in the
1885
   * {@link #trans #trans} attribute.
1886
   * </p>
1887
   *
1888
   * @param r the 2D rectangle in <i>map coordinates</i>
1889
   * @return 2D rectangle equivalent in <i>screen coordinates</i> (pixels)
1890
   * @see #toMapRectangle(Rectangle2D)
1891
   * @see #fromMapDistance(double)
1892
   * @see #fromMapPoint(Point2D)
1893
   */
1894
  public Rectangle2D fromMapRectangle(Rectangle2D r) {
1895
    Rectangle2D rect = new Rectangle2D.Double();
1896
    Point2D p1 = fromMapPoint((int) r.getX(), (int) r.getY());
1897
    Point2D p2 = fromMapPoint((int) r.getMaxX(), (int) r.getMaxY());
1898
    rect.setFrameFromDiagonal(p1, p2);
1899
    return rect;
1900
  }
1901

    
1902
  /**
1903
   * <p>
1904
   * Recalculates the current <code>{@linkplain #extent}</code> using an scale.
1905
   * It's necessary execute {@linkplain #refreshExtent()} after.
1906
   * </p>
1907
   *
1908
   * @param s the scale to set
1909
   * @deprecated since 07/09/07, use {@linkplain MapContext#setScaleView(long)}
1910
   */
1911
  public void setScale(long s) {
1912
    double x = extent.getX();
1913
    double y = extent.getY();
1914
    double escalaX = imageSize.width / extent.getWidth();
1915
    // double w = imageSize.width / s;
1916
    // double h = imageSize.height / s;
1917
    double difw = escalaX / s;
1918

    
1919
    double x1 = (-x * difw) - x + extent.getWidth() / 2;
1920
    double y1 = (-y * difw) - y + extent.getHeight() / 2;
1921
    double w1 = extent.getWidth() * difw;
1922
    double h1 = extent.getHeight() * difw;
1923
    extent.setRect(-x1, -y1, w1, h1);
1924
  }
1925

    
1926
  public long getDrawVersion() {
1927
    return this.drawVersion;
1928
  }
1929

    
1930
  protected void updateDrawVersion() {
1931
    this.drawVersion++;
1932
  }
1933

    
1934
  public Time getTime() {
1935
    return time;
1936
  }
1937

    
1938
  public void setTime(Time time) {
1939
    this.time = time;
1940
    this.updateDrawVersion();
1941
    callTimeChanged(time);
1942
  }
1943

    
1944
  public double getDPI() {
1945
    if (this.dpi == null) {
1946
      return CompatLocator.getGraphicsUtils().getScreenDPI();
1947
    }
1948
    return this.dpi.doubleValue();
1949
  }
1950

    
1951
  public void setDPI(double dpi) {
1952
    this.dpi = new Double(dpi);
1953
  }
1954

    
1955
  public void setDPIToScreenDPI() {
1956
    this.dpi = null;
1957
  }
1958
}