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 / MapContext.java @ 47430

History | View | Annotate | Download (64.3 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 modify it under
7
 * the terms of the GNU General Public License as published by the Free Software
8
 * Foundation; either version 3 of the License, or (at your option) any later
9
 * version.
10
 *
11
 * This program is distributed in the hope that it will be useful, but WITHOUT
12
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14
 * details.
15
 *
16
 * You should have received a copy of the GNU General Public License along with
17
 * this program; if not, write to the Free Software Foundation, Inc., 51
18
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
 *
20
 * For any additional information, do not hesitate to contact us at info AT
21
 * gvsig.com, or visit our website www.gvsig.com.
22
 */
23
package org.gvsig.fmap.mapcontext;
24

    
25
import java.awt.Color;
26
import java.awt.Graphics;
27
import java.awt.Graphics2D;
28
import java.awt.geom.Rectangle2D;
29
import java.awt.image.BufferedImage;
30
import java.util.ArrayList;
31
import java.util.Collection;
32
import java.util.HashSet;
33
import java.util.Iterator;
34
import java.util.LinkedHashMap;
35
import java.util.List;
36
import java.util.Map;
37
import java.util.Set;
38
import org.apache.commons.lang3.ArrayUtils;
39

    
40
import org.cresques.cts.ICoordTrans;
41
import org.cresques.cts.IProjection;
42
import org.cresques.geo.Projected;
43
import org.slf4j.Logger;
44
import org.slf4j.LoggerFactory;
45

    
46
import org.gvsig.compat.CompatLocator;
47
import org.gvsig.compat.print.PrintAttributes;
48
import org.gvsig.fmap.dal.DataStore;
49
import org.gvsig.fmap.dal.exception.ReadException;
50
import org.gvsig.fmap.dal.feature.FeatureStoreNotification;
51
import org.gvsig.fmap.geom.Geometry;
52
import org.gvsig.fmap.geom.Geometry.SUBTYPES;
53
import org.gvsig.fmap.geom.GeometryLocator;
54
import org.gvsig.fmap.geom.GeometryManager;
55
import org.gvsig.fmap.geom.GeometryUtils;
56
import org.gvsig.fmap.geom.exception.CreateEnvelopeException;
57
import org.gvsig.fmap.geom.primitive.Envelope;
58
import org.gvsig.fmap.geom.primitive.Point;
59
import org.gvsig.fmap.mapcontext.events.ErrorEvent;
60
import org.gvsig.fmap.mapcontext.events.listeners.AtomicEventListener;
61
import org.gvsig.fmap.mapcontext.events.listeners.ErrorListener;
62
import org.gvsig.fmap.mapcontext.events.listeners.EventBuffer;
63
import org.gvsig.fmap.mapcontext.exceptions.LoadLayerException;
64
import org.gvsig.fmap.mapcontext.impl.DefaultMapContextManager;
65
import org.gvsig.fmap.mapcontext.layers.BaseLayerCollectionListener;
66
import org.gvsig.fmap.mapcontext.layers.FLayer;
67
import org.gvsig.fmap.mapcontext.layers.FLayers;
68
import org.gvsig.fmap.mapcontext.layers.LayerCollectionEvent;
69
import org.gvsig.fmap.mapcontext.layers.LayerCollectionListener;
70
import org.gvsig.fmap.mapcontext.layers.LayerDrawEvent;
71
import org.gvsig.fmap.mapcontext.layers.LayerDrawingListener;
72
import org.gvsig.fmap.mapcontext.layers.LayerPositionEvent;
73
import org.gvsig.fmap.mapcontext.layers.operations.Classifiable;
74
import org.gvsig.fmap.mapcontext.layers.operations.SingleLayer;
75
import org.gvsig.fmap.mapcontext.layers.order.LayerOrderManager;
76
import org.gvsig.fmap.mapcontext.layers.vectorial.FLyrVect;
77
import org.gvsig.fmap.mapcontext.layers.vectorial.GraphicLayer;
78
import org.gvsig.fmap.mapcontext.layers.vectorial.VectorLayer;
79
import org.gvsig.fmap.mapcontext.rendering.legend.events.listeners.LegendListener;
80
import org.gvsig.fmap.mapcontext.rendering.strategies.SelectedEnvelopeVisitor;
81
import org.gvsig.timesupport.AbsoluteInstant;
82
import org.gvsig.timesupport.AbsoluteIntervalTypeNotRegisteredException;
83
import org.gvsig.timesupport.Instant;
84
import org.gvsig.timesupport.Interval;
85
import org.gvsig.timesupport.RelativeInstant;
86
import org.gvsig.timesupport.TimeSupportLocator;
87
import org.gvsig.timesupport.TimeSupportManager;
88
import org.gvsig.tools.ToolsLocator;
89
import org.gvsig.tools.dispose.Disposable;
90
import org.gvsig.tools.dispose.DisposeUtils;
91
import org.gvsig.tools.dispose.impl.AbstractDisposable;
92
import org.gvsig.tools.dynobject.DynStruct;
93
import org.gvsig.tools.exception.BaseException;
94
import org.gvsig.tools.observer.Observable;
95
import org.gvsig.tools.observer.Observer;
96
import org.gvsig.tools.persistence.PersistenceManager;
97
import org.gvsig.tools.persistence.Persistent;
98
import org.gvsig.tools.persistence.PersistentState;
99
import org.gvsig.tools.persistence.exception.PersistenceException;
100
import org.gvsig.tools.task.Cancellable;
101
import org.gvsig.tools.util.Callable;
102
import org.gvsig.tools.visitor.Visitor;
103

    
104
/**
105
 * <p>
106
 * The <code>MapContext</code> class represents the model and a part of the
107
 * control and view around graphical layers used by
108
 * {@link MapControl MapControl}.</p>
109
 *
110
 * <p>
111
 * An instance of <code>MapContext</code> is made up with:
112
 * <ul>
113
 * <li>a hierarchy of {@link FLayers FLayers} nodes
114
 * <li>a {@link GraphicLayer GraphicLayer} layer
115
 * <li>a {@link ViewPort ViewPort}
116
 * <li>an {@link EventBuffer EventButter}
117
 * <li>some
118
 * {@link com.iver.cit.gvsig.fmap.layers.LegendListener LegendListener}s
119
 * <li>some {@link LayerDrawingListener LayerDrawingListener}s
120
 * <li>some {@link ErrorListener ErrorListener}s
121
 * </ul>
122
 * </p>
123
 */
124
public class MapContext extends AbstractDisposable implements Projected,
125
        Persistent, Observer, Iterable<FLayer> {
126

    
127
    public interface MapTimeContext {
128
        public Interval getInterval();
129
        public List<Instant> getTimes();
130
    }    
131

    
132
    /**
133
     * @deprecated use getDistanceTrans2Meter()
134
     */
135
    public static final double[] CHANGEM = {1000, 1, 0.01, 0.001, 1609.344,
136
        0.9144, 0.3048, 0.0254, 1 / 8.983152841195214E-6};
137

    
138
    public static ArrayList AREANAMES = new ArrayList();
139
    public static ArrayList AREAABBR = new ArrayList();
140
    public static ArrayList AREATRANS2METER = new ArrayList();
141

    
142
    public static ArrayList DISTANCENAMES = new ArrayList();
143
    public static ArrayList DISTANCEABBR = new ArrayList();
144
    public static ArrayList DISTANCETRANS2METER = new ArrayList();
145

    
146
    static {
147
        MapContext.addDistanceUnit("Kilometros", "Km", 1000);
148
        MapContext.addDistanceUnit("Metros", "m", 1);
149
        MapContext.addDistanceUnit("Centimetros", "cm", 0.01);
150
        MapContext.addDistanceUnit("Milimetros", "mm", 0.001);
151
        MapContext.addDistanceUnit("Millas", "mi", 1609.344);
152
        MapContext.addDistanceUnit("Yardas", "Ya", 0.9144);
153
        MapContext.addDistanceUnit("Pies", "ft", 0.3048);
154
        MapContext.addDistanceUnit("Pulgadas", "inche", 0.0254);
155
        MapContext.addDistanceUnit("Grados", "?", 1 / 8.983152841195214E-6);
156

    
157
        MapContext.addAreaUnit("Kilometros", "Km", true, 1000);
158
        MapContext.addAreaUnit("Metros", "m", true, 1);
159
        MapContext.addAreaUnit("Centimetros", "cm", true, 0.01);
160
        MapContext.addAreaUnit("Milimetros", "mm", true, 0.001);
161
        MapContext.addAreaUnit("Millas", "mi", true, 1609.344);
162
        MapContext.addAreaUnit("Yardas", "Ya", true, 0.9144);
163
        MapContext.addAreaUnit("Pies", "ft", true, 0.3048);
164
        MapContext.addAreaUnit("Pulgadas", "inche", true, 0.0254);
165
        MapContext.addAreaUnit("Grados", "?", true, 1 / 8.983152841195214E-6);
166

    
167
    }
168

    
169
    private static final GeometryManager GEOM_MANAGER = GeometryLocator.getGeometryManager();
170

    
171
    private static final MapContextManager MAPCONTEXT_MANAGER = MapContextLocator.getMapContextManager();
172

    
173
    private static final Logger LOGGER = LoggerFactory.getLogger(MapContext.class);
174

    
175
    private long scaleToUseWhenEnvelopeCollapse = 10000000;
176
    
177
    private boolean printGraphicsLayer;
178

    
179
    /**
180
     * <p>
181
     * Determines the number of frames.</p>
182
     *
183
     * <p>
184
     * Number of updates per second that the timer will invoke repaint this
185
     * component.</p>
186
     */
187
    private static int drawFrameRate = 3;
188

    
189
    /**
190
     * <p>
191
     * Returns the draw frame rate.</p>
192
     *
193
     * <p>
194
     * Draw frame rate is the number of repaints of this <code>MapControl</code>
195
     * instance that timer invokes per second.</p>
196
     *
197
     * @return number of repaints of this <code>MapControl</code> instance that
198
     * timer invokes per second
199
     *
200
     * @see #applyFrameRate()
201
     * @see #setDrawFrameRate(int)
202
     */
203
    public static int getDrawFrameRate() {
204
        return drawFrameRate;
205
    }
206

    
207
    /**
208
     * <p>
209
     * Sets the draw frame rate.</p>
210
     *
211
     * <p>
212
     * Draw frame rate is the number of repaints of this <code>MapControl</code>
213
     * instance that timer invokes per second.</p>
214
     *
215
     * @param theDrawFrameRate number of repaints of this <code>MapControl</code>
216
     * instance that timer invokes per second
217
     *
218
     * @see #applyFrameRate()
219
     * @see #getDrawFrameRate()
220
     */
221
    public static void setDrawFrameRate(int theDrawFrameRate) {
222
        drawFrameRate = theDrawFrameRate;
223
    }
224

    
225
    public static void addAreaUnit(String name, String abbr, boolean isLinear, double trans2meter) {
226
        if (!AREANAMES.contains(name)) {
227
            AREANAMES.add(name);
228
            String pow = "";
229
            if (isLinear) {
230
                pow = String.valueOf((char) 178);
231
            }
232
            AREAABBR.add(abbr + pow);
233
            AREATRANS2METER.add(trans2meter);
234
        }
235
    }
236

    
237
    public static String[] getAreaNames() {
238
        return (String[]) AREANAMES.toArray(new String[0]);
239
    }
240

    
241
    public static String[] getAreaAbbr() {
242
        return (String[]) AREAABBR.toArray(new String[0]);
243
    }
244

    
245
    public static double[] getAreaTrans2Meter() {
246
        int size = AREATRANS2METER.size();
247
        double[] trans2meters = new double[size];
248
        for (int i = 0; i < size; i++) {
249
            trans2meters[i] = ((Double) AREATRANS2METER.get(i));
250
        }
251
        return trans2meters;
252
    }
253

    
254
    public static String getOfLinear(int i) {
255
        if (((String) AREAABBR.get(i)).toLowerCase().endsWith(String.valueOf((char) 178))) {
256
            return String.valueOf((char) 178);
257
        }
258
        return "";
259
    }
260

    
261
    public static void addDistanceUnit(String name, String abbr, double trans2meter) {
262
        if (!DISTANCENAMES.contains(name)) {
263
            DISTANCENAMES.add(name);
264
            DISTANCEABBR.add(abbr);
265
            DISTANCETRANS2METER.add(trans2meter);
266
        }
267
    }
268

    
269
    public static String[] getDistanceNames() {
270
        return (String[]) DISTANCENAMES.toArray(new String[0]);
271
    }
272

    
273
    public String getDistanceName() {
274
        return (String) DISTANCENAMES.get(this.getViewPort().getDistanceUnits());
275
    }
276

    
277
    public static String[] getDistanceAbbr() {
278
        return (String[]) DISTANCEABBR.toArray(new String[0]);
279
    }
280

    
281
    public static double[] getDistanceTrans2Meter() {
282
        int size = DISTANCETRANS2METER.size();
283
        double[] trans2meters = new double[size];
284
        for (int i = 0; i < size; i++) {
285
            trans2meters[i] = ((Double) DISTANCETRANS2METER.get(i));
286
        }
287
        return trans2meters;
288
    }
289

    
290
    public static int getDistancePosition(String s) {
291
        for (int i = 0; i < DISTANCENAMES.size(); i++) {
292
            if (DISTANCENAMES.get(i).equals(s)) {
293
                return i;
294
            }
295
        }
296
        return 0;
297
    }
298

    
299
    /**
300
     * <p>
301
     * Defines the value which a unit of a distance measurement must be divided
302
     * to obtain its equivalent <b>in centimeters</b>.</p>
303
     *
304
     * <p>
305
     * <b><i>Conversion values of distance measurements:</i></b>
306
     * <ul>
307
     * <li><code>MapContext.CHANGE[0]</code>: kilometer
308
     * <li><code>MapContext.CHANGE[1]</code>: meter
309
     * <li><code>MapContext.CHANGE[2]</code>: centimeter
310
     * <li><code>MapContext.CHANGE[3]</code>: millimeter
311
     * <li><code>MapContext.CHANGE[4]</code>: international statute mile
312
     * <li><code>MapContext.CHANGE[5]</code>: yard
313
     * <li><code>MapContext.CHANGE[6]</code>: foot
314
     * <li><code>MapContext.CHANGE[7]</code>: inch
315
     * <li><code>MapContext.CHANGE[8]</code>: grade
316
     * </ul>
317
     *
318
     * <p>
319
     * <h3>Examples:</h3>
320
     * <pre>1 international statute mile / MapContext.CHANGE[4] = X centimeters</pre>
321
     * <pre>1 kilometer / MapContext.CHANGE[0] = X centimeters</pre>
322
     * <pre>1 grade / MapContext.CHANGE[8] = X centimeters</pre>
323
     * </p>
324
     *
325
     * <p>
326
     * <h3>Grade conversion value: <code>MapContext.CHANGE[8]</code></h3>
327
     * The value of <code>MapContext.CHANGE[8]</code> represents the centimeters
328
     * of a straight line between two points on the Earth surface that are 1
329
     * grade far each other of the center of the Earth. This value has been
330
     * calculated using a radius approximated of
331
     * R<sub>Earth</sub>=6.37846082678100774672e6 meters, according these
332
     * equations:
333
     * <pre>D = 2 * (sin (1)) * R<sub>Earth</sub></pre>
334
     * <pre>MapContext.CHANGE[8] = 1 / D</pre>
335
     * <h4>Explanation:</h4>
336
     * We get an isosceles triangle with the center of the Earth and the 2
337
     * points on the surface. This triangle can be divided into two rectangle
338
     * triangles. We know two values, the angle of 1 grade, that will be 0.50
339
     * grades in each triangle, and the Earth radius that is the hypotenuse.
340
     * Then we apply trigonometry and get the distance <i>D</i> between both
341
     * points on the Earth surface.</p>
342
     * <p>
343
     * Now we only must invert that value to obtain
344
     * <code>MapContext.CHANGE[8]</code>.</p>
345
     *
346
     * @deprecated use getDistanceTrans2Meter() * 100
347
     */
348
    public static final double[] CHANGE = {100000, 100, 1, 0.1, 160934.4,
349
        91.44, 30.48, 2.54, 1 / 8.983152841195214E-4};
350

    
351
    public static final int EQUALS = 0;
352
    public static final int DISJOINT = 1;
353
    public static final int INTERSECTS = 2;
354
    public static final int TOUCHES = 3;
355
    public static final int CROSSES = 4;
356
    public static final int WITHIN = 5;
357
    public static final int CONTAINS = 6;
358
    public static final int OVERLAPS = 7;
359

    
360
    /**
361
     * A hierarchy of {@link FLayers FLayers} nodes.
362
     */
363
    protected FLayers layers;
364

    
365
    /**
366
     * A List of layers with graphical items: geometries and symbols.
367
     */
368
    private static final String DEFAULT_TRACTLAYER = "Default";
369
    private Map<String, VectorLayer> tracLayers;
370

    
371
    /**
372
     * Information for draw layers in a view.
373
     *
374
     * @see #getViewPort()
375
     * @see #setViewPort(ViewPort)
376
     */
377
    private ViewPort viewPort;
378
    // private ArrayList invalidationListeners = new ArrayList();
379
    /**
380
     * Array list with all {@link LegendListener LegendListener} registered to
381
     * this map.
382
     *
383
     * @see #addLayerListener(LegendListener)
384
     * @see #removeLayerListener(LegendListener)
385
     * @see #callLegendChanged()
386
     */
387
    private List legendListeners;
388

    
389
    /**
390
     * Array list with all {@link LayerDrawingListener LayerDrawingListener}
391
     * registered to this map.
392
     *
393
     * @see #addLayerDrawingListener(LayerDrawingListener)
394
     * @see #removeLayerDrawListener(LayerDrawingListener)
395
     * @see #fireLayerDrawingEvent(LayerDrawEvent)
396
     */
397
    private ArrayList layerDrawingListeners = new ArrayList();
398

    
399
    /**
400
     * <p>
401
     * Buffer that is used to store and eject events produced on this map:
402
     * <ul>
403
     * <li>Layer collection events.
404
     * <li>View port events.
405
     * <li>Atomic events.
406
     * <li>Layer events.
407
     * <li>Legend events on a {@link Classificable Classificable} layer.
408
     * <li>Selection events on an {@link AlphanumericData AlphanumericData} data
409
     * layer.
410
     * </ul>
411
     * </p>
412
     *
413
     * @see #addAtomicEventListener(AtomicEventListener)
414
     * @see #removeAtomicEventListener(AtomicEventListener)
415
     * @see #beginAtomicEvent()
416
     * @see #endAtomicEvent()
417
     */
418
    private EventBuffer eventBuffer = new EventBuffer();
419

    
420
    /**
421
     * Event listener for the collection of layers of this map.
422
     */
423
    private LayerEventListener layerEventListener = null;
424

    
425
    /**
426
     * List with information of all errors produced on all layers.
427
     *
428
     * @see #addLayerError(String)
429
     * @see #getLayersError()
430
     * @see #clearErrors()
431
     */
432
    private ArrayList layersError = new ArrayList();
433

    
434
    /**
435
     * Array list with all {@link ErrorListener ErrorListener} registered to
436
     * this map.
437
     *
438
     * @see #addErrorListener(ErrorListener)
439
     * @see #removeErrorListener(LegendListener)
440
     * @see #reportDriverExceptions(String, List)
441
     */
442
    private ArrayList errorListeners = new ArrayList();
443

    
444
    /**
445
     * Layer order manager decides position of layers added to this map context.
446
     */
447
    private LayerOrderManager orderManager = null;
448

    
449
        // public static ResourceBundle myResourceBundle =
450
    // ResourceBundle.getBundle("FMap");
451
    /**
452
     * <p>
453
     * Default <i>zoom in</i> factor.</p>
454
     * <p>
455
     * Doing a <i>zoom in</i> operation, decreases the focal distance and
456
     * increases the eyesight angle to the surface. This allows view an smaller
457
     * area but with the items bigger.</p>
458
     */
459
    public static double ZOOMINFACTOR = 2;
460

    
461
    /**
462
     * <p>
463
     * Default <i>zoom out</i> factor.</p>
464
     * <p>
465
     * Doing a <i>zoom out</i> operation, increases the focal distance and
466
     * decreases the eyesight angle to the surface. This allows view a bigger
467
     * area but with the items smaller.</p>
468
     */
469
    public static double ZOOMOUTFACTOR = 0.5;
470

    
471
    /**
472
     *          * Draw version of the context. It's used for know when de componend has
473
     * changed any visualization property
474
     *
475
     * @see getDrawVersion
476
     * @see updateDrawVersion
477
     */
478
    private long drawVersion = 0L;
479

    
480
    private long layersVersion = 0L;
481
    private long viewPortVersion = 0L;
482
    private long graphicsLayerVersion = 0L;
483

    
484
    /**
485
     * Object to Manage Draw of MapContext
486
     */
487
    private MapContextDrawer mapContextDrawer = null;
488

    
489
    /**
490
     * Object to Manage Draw of MapContext
491
     */
492
    private Class mapContextDrawerClass = null;
493

    
494
    /**
495
     * <p>
496
     * Color used to represent the selections.</p>
497
     */
498
    private static Color selectionColor = new Color(Color.YELLOW.getRed(), Color.YELLOW.getGreen(), Color.YELLOW.getBlue(), 127);
499
    private Set<FLyrVect> layersToSnap = new HashSet<>();
500

    
501
    /**
502
     * <p>
503
     * Gets the color used to represent the selections.</p>
504
     *
505
     * @return color used to represent the selections
506
     */
507
    public static Color getSelectionColor() {
508
        return selectionColor;
509
    }
510

    
511
    /**
512
     * <p>
513
     * Sets the color used to represent the selections.</p>
514
     *
515
     * @param selectionColor color used to represent the selections
516
     */
517
    public static void setSelectionColor(Color selectionColor) {
518
        MapContext.selectionColor = selectionColor;
519
    }
520

    
521
    /**
522
     * <p>
523
     * Creates a new map context with the drawing information defined in the
524
     * view port argument, and without layers.</p>
525
     *
526
     * @param vp information for drawing the layers of this map in the available
527
     * rectangular area according a projection
528
     */
529
    public MapContext(ViewPort vp) {
530
        this(new FLayers(), vp);
531
    }
532

    
533
    public MapContext() {
534
        this.legendListeners = new ArrayList();
535
        this.tracLayers = new LinkedHashMap<>();
536
        this.layerEventListener = new LayerEventListener();
537
    }
538

    
539
    /**
540
     * <p>
541
     * Creates a new map context with the layers and the drawing information
542
     * defined in the view port arguments.</p>
543
     *
544
     * @param fLayers the initial hierarchy of nodes of layers that this map
545
     * will have
546
     * @param vp information for drawing the layers of this map in the available
547
     * rectangular area according a projection
548
     */
549
    public MapContext(FLayers fLayers, ViewPort vp) {
550
        this();
551
        this.layers = fLayers;
552
        if (layers != null) {
553
            layers.setMapContext(this);
554
            layers.addLayerCollectionListener(layerEventListener);
555
            layers.addLayerCollectionListener(eventBuffer);
556
        }
557
        setViewPort(vp);
558
    }
559

    
560
    /**
561
     * <p>
562
     * Reports to all driver error listeners registered of a bundle of driver
563
     * exceptions caused in the same map atomic transaction.</p>
564
     *
565
     * @param introductoryText introductory text specified by developer. If
566
     * <code>null</code>, use ""
567
     * @param driverExceptions list with a bundle of driver exceptions caught
568
     * during an atomic event
569
     *
570
     * @see #addErrorListener(ErrorListener)
571
     * @see #removeErrorListener(LegendListener)
572
     */
573
    public synchronized void reportDriverExceptions(String introductoryText,
574
            List driverExceptions) {
575
        for (int i = 0; i < errorListeners.size(); i++) {
576
            ((ErrorListener) errorListeners.get(i)).
577
                    reportDriverExceptions(introductoryText, driverExceptions);
578
        }
579
    }
580

    
581
    /**
582
     * <p>
583
     * Adds the specified legend listener (if didn't exist) to receive legend
584
     * events from this map.</p>
585
     *
586
     * @param listener the legend listener
587
     *
588
     * @see #removeLayerListener(LegendListener)
589
     * @see #callLegendChanged()
590
     */
591
    public void addLayerListener(LegendListener listener) {
592
        if (!legendListeners.contains(listener)) {
593
            legendListeners.add(listener);
594
        }
595
    }
596

    
597
    /**
598
     * <p>
599
     * Adds the specified layer drawing listener to catch and handle drawing
600
     * events from layers of this map.</p>
601
     *
602
     * @param listener the listener to add
603
     *
604
     * @see #removeLayerDrawListener(LayerDrawingListener)
605
     * @see #fireLayerDrawingEvent(LayerDrawEvent)
606
     */
607
    public void addLayerDrawingListener(LayerDrawingListener listener) {
608
        layerDrawingListeners.add(listener);
609
    }
610

    
611
    /**
612
     * <p>
613
     * Removes the specified layer drawing listener from this map.</p>
614
     *
615
     * @param listener the listener to remove
616
     *
617
     * @see #addLayerDrawingListener(LayerDrawingListener)
618
     * @see #fireLayerDrawingEvent(LayerDrawEvent)
619
     */
620
    public void removeLayerDrawListener(LayerDrawingListener listener) {
621
        layerDrawingListeners.remove(listener);
622
    }
623

    
624
    /**
625
     * <p>
626
     * Adds the specified error listener to receive error events from this
627
     * map.</p>
628
     *
629
     * @param listener the listener to add
630
     *
631
     * @see #removeErrorListener(LegendListener)
632
     * @see #reportDriverExceptions(String, List)
633
     */
634
    public void addErrorListener(ErrorListener listener) {
635
        errorListeners.add(listener);
636
    }
637

    
638
    /**
639
     * <p>
640
     * Removes the specified error listener from this map.</p>
641
     *
642
     * @param listener the listener to remove
643
     *
644
     * @see #addErrorListener(ErrorListener)
645
     * @see #reportDriverExceptions(String, List)
646
     */
647
    public void removeErrorListener(LegendListener listener) {
648
        legendListeners.remove(listener);
649
    }
650

    
651
    /**
652
     * Notifies to all legend listeners registered, that one legend has
653
     * changed.
654
     * 
655
     * This method must be called only if it's wanted to reflect a legend
656
     * change.
657
     *
658
     * @see #addLayerListener(LegendListener)
659
     * @see #removeLayerListener(LegendListener)
660
     */
661
    public synchronized void callLegendChanged() {
662
        for (int i = 0; i < legendListeners.size(); i++) {
663
            ((LegendListener) legendListeners.get(i)).legendChanged(null);
664
        }
665
        // getLayers().moveTo(0,0);
666
    }
667

    
668
    /**
669
     * <p>
670
     * Fires a layer drawing event to all
671
     * {@link LayerDrawingListener LayerDrawingListener} listeners registered,
672
     * distinguishing the kind of event.</p>
673
     *
674
     * @param e the event
675
     *
676
     * @see #addLayerDrawingListener(LayerDrawingListener)
677
     * @see #removeLayerDrawListener(LayerDrawingListener)
678
     */
679
    public synchronized void fireLayerDrawingEvent(LayerDrawEvent e) {
680
        for (int i = 0; i < layerDrawingListeners.size(); i++) {
681
            LayerDrawingListener listener = (LayerDrawingListener) layerDrawingListeners.get(i);
682
            switch (e.getEventType()) {
683
                case LayerDrawEvent.LAYER_BEFORE_DRAW:
684
                    listener.beforeLayerDraw(e);
685
                    break;
686
                case LayerDrawEvent.LAYER_AFTER_DRAW:
687
                    listener.afterLayerDraw(e);
688
                    break;
689
                case LayerDrawEvent.GRAPHICLAYER_BEFORE_DRAW:
690
                    listener.beforeGraphicLayerDraw(e);
691
                    break;
692
                case LayerDrawEvent.GRAPHICLAYER_AFTER_DRAW:
693
                    listener.afterLayerGraphicDraw(e);
694
                    break;
695
            }
696
        }
697
        // getLayers().moveTo(0,0);
698
    }
699

    
700
    /**
701
     * <p>
702
     * Notifies to all error listeners registered, that one error has been
703
     * produced.</p>
704
     *
705
     * @param e the event with information of the error
706
     *
707
     * @see #addErrorListener(ErrorListener)
708
     * @see #removeErrorListener(LegendListener)
709
     * @see #reportDriverExceptions(String, List)
710
     */
711
    public synchronized void callNewErrorEvent(ErrorEvent e) {
712
        for (int i = 0; i < errorListeners.size(); i++) {
713
            ((ErrorListener) errorListeners.get(i)).errorThrown(e);
714
        }
715
        errorListeners.clear();
716
        // getLayers().moveTo(0,0);
717
    }
718

    
719
    /**
720
     * <p>
721
     * Removes the specified layer listener from this map.</p>
722
     *
723
     * @param listener the listener to remove
724
     *
725
     * @see #addLayerListener(LegendListener)
726
     * @see #callLegendChanged()
727
     */
728
    public void removeLayerListener(LegendListener listener) {
729
        legendListeners.remove(listener);
730
    }
731

    
732
    /**
733
     * <p>
734
     * Returns the hierarchy of {@link FLayers FLayers} nodes stored in this
735
     * map.</p>
736
     *
737
     * @return the hierarchy of nodes of layers stored in this map
738
     */
739
    public FLayers getLayers() {
740
        return layers;
741
    }
742

    
743
    /**
744
     * <p>
745
     * Draws the visible layers of this map according its view port, on the
746
     * image parameter.</p>
747
     *
748
     * @param b image with an accessible buffer of image data
749
     */
750
    public void drawLabels(BufferedImage b) {
751
    }
752

    
753
    /**
754
     * @see #redraw()
755
     */
756
    public void invalidate() {
757
        updateDrawVersion();
758
        this.viewPort.updateDrawVersion();
759
        // Small hack to let the MapControl receive an event and repaint
760
        FLayer layer;
761
        if (layers.getLayersCount() > 0) {
762
            layer = layers.getLayer(layers.getLayersCount() - 1);
763
        } else {
764
            layer = getGraphicsLayer();
765
        }
766
        LayerPositionEvent event = LayerPositionEvent.createLayerMovedEvent(layer, 0, 0);
767
        eventBuffer.layerMoved(event);
768
    }
769

    
770
    /**
771
     * <p>
772
     * Prints the layers of this map using the {@link Graphics2D Graphics2D}
773
     * argument, that usually is the {@link Graphics Graphics} of the printer.
774
     * </p>
775
     *
776
     * @param g for rendering 2-dimensional shapes, text and images on the
777
     * Java(tm) platform
778
     * @param scale the scale of the view. Must be between
779
     * {@linkplain FLayer#getMinScale()} and {@linkplain FLayer#getMaxScale()}.
780
     * @param properties a set with the settings to be applied to a whole print
781
     * job and to all the documents in the print job
782
     * @throws MapContextException if there is an error getting the instance of
783
     * MapContextDrawer
784
     *
785
     * @throws ReadDriverException if fails reading with driver.
786
     *
787
     * @see GraphicLayer#draw(BufferedImage, Graphics2D, ViewPort, Cancellable,
788
     * double)
789
     */
790
    public void print(Graphics2D g, double scale,
791
            PrintAttributes properties) throws ReadException,
792
            MapContextException {
793

    
794
        CompatLocator.getGraphicsUtils().setRenderingHintsForPrinting(g);
795

    
796
        Cancellable cancel = new Cancellable() {
797
            public boolean isCanceled() {
798
                return false;
799
            }
800

    
801
            public void setCanceled(boolean canceled) {
802
                // No queremos que se pueda cancelar la impresi?n.
803

    
804
            }
805
        };
806
        MapContextDrawer drawer = this.getMapContextDrawer();
807
        drawer.setPrintGraphicsLayer(this.isPrintGraphicsLayer());
808
        drawer.print(this.layers, g, cancel, scale, properties);
809
    }
810

    
811
    /**
812
     * <p>
813
     * Returns a new <code>MapContext</code> instance with the information of
814
     * the <code>vp</code> argument, and the layers of this map.</p>
815
     *
816
     * @param vp information for drawing the layers of this map in the available
817
     * rectangular area according a projection
818
     *
819
     * @return a new <code>MapContext</code> instance projected by
820
     * <code>vp</code>
821
     */
822
    public MapContext createNewFMap(ViewPort vp) {
823
        MapContext ret = new MapContext(vp);
824
        ret.layers = this.layers;
825
        DisposeUtils.bind((Disposable)ret.layers);
826
        DisposeUtils.bind((List)ret.layers);
827
        
828
        return ret;
829
    }
830

    
831
    /**
832
     * <p>
833
     * Creates a new independent <code>MapContext</code> instance, that has a
834
     * clone of the layers and the view port of this one.</p>
835
     * <p>
836
     * The new map will have the same data source drivers to avoid waste memory,
837
     * and work faster.</p>
838
     *
839
     * @return the new <code>MapContext</code> instance
840
     *
841
     * @throws XMLException if fails cloning the view port or a layer
842
     *
843
     * @see FLayer#cloneLayer()
844
     * @see ViewPort#cloneViewPort()
845
     */
846
    public MapContext cloneFMap() {
847
        ViewPort vp;
848
        try {
849
            vp = (ViewPort) getViewPort().clone();
850
        } catch (CloneNotSupportedException e) {
851
            throw new RuntimeException(e);
852
        }
853
        FLayers antLayers = getLayers();
854
        MapContext ret = new MapContext(vp);
855

    
856
        /*
857
         * Cloning order manager
858
         */
859
        LayerOrderManager lom = this.getOrderManager();
860
        try {
861
            lom = (LayerOrderManager) lom.clone();
862
            ret.setOrderManager(lom);
863
        } catch (CloneNotSupportedException e1) {
864
            LOGGER.error("While cloning order manager", e1);
865
        }
866

    
867
        for (int i = 0; i < antLayers.getLayersCount(); i++) {
868
            FLayer lyr = antLayers.getLayer(i);
869
            try {
870
                FLayer auxLayer = lyr.cloneLayer();
871
                ret.layers.addLayer(auxLayer);
872
                auxLayer.dispose();
873
            } catch (Exception e) {
874
                throw new RuntimeException(e);
875
            }
876
        }
877
        return ret;
878

    
879
    }
880

    
881
    /**
882
     * Like {@linkplain #cloneFMap()}, but now doesn't clone the layers, rather
883
     * copies them.
884
     *
885
     * @return the new map
886
     */
887
    public MapContext cloneToDraw() {
888
        ViewPort vp;
889
        try {
890
            vp = (ViewPort) getViewPort().clone();
891
            MapContext mapContext = new MapContext(getLayers(), vp);
892
            return mapContext;
893
        } catch (CloneNotSupportedException e) {
894
            throw new RuntimeException(e);
895
        }
896
    }
897

    
898
    /**
899
     * <p>
900
     * Adds a layer to the group of layers that are at a upper level in the
901
     * tree.</p>
902
     *
903
     * @param vectorial the layer to add
904
     */
905
    public void addToTrackLayer(FLayer vectorial) {
906
    }
907

    
908
    /**
909
     * <p>
910
     * Returns the scale of the view in the screen.</p>
911
     *
912
     * @return one of this values:
913
     * <ul>
914
     * <li>the scale of the adjusted extent scale of the view in the screen
915
     * <li><code>-1</code> if there is no image
916
     * <li><code>0</code> if there is no extent defined for the image
917
     * </ul>
918
     *
919
     * @see #setScaleView(long)
920
     * @see ViewPort#getAdjustedExtent()
921
     * @see IProjection#getScale(double, double, double, double)
922
     */
923
    public long getScaleView() {
924
        double dpi = this.getViewPort().getDPI();
925
        IProjection proj = viewPort.getProjection();
926

    
927
        if (viewPort.getImageSize() == null) {
928
            return -1;
929
        }
930

    
931
        if (viewPort.getAdjustedEnvelope() == null) {
932
            return 0;
933
        }
934
        double[] trans2Meter = getDistanceTrans2Meter();
935
        int mUnits = getViewPort().getMapUnits();
936

    
937
        if (proj == null) {
938
            double w = ((viewPort.getImageSize().width / dpi) * 0.0254);
939
            return (long) (viewPort.getAdjustedEnvelope().getLength(0)
940
                    / w * trans2Meter[mUnits]);
941
        }
942

    
943
        return Math.round(proj.getScale(
944
                viewPort.getAdjustedEnvelope().getMinimum(0) * trans2Meter[mUnits],
945
                viewPort.getAdjustedEnvelope().getMaximum(0) * trans2Meter[mUnits],
946
                viewPort.getImageSize().width,
947
                dpi));
948

    
949
    }
950

    
951
    /**
952
     * <p>
953
     * Sets the new extent of the view, calculated using the scale argument.</p>
954
     * <p>
955
     * Doesn't updates the scale if there isn't information about the dimension
956
     * of the image or the adjusted extent.</p>
957
     *
958
     * @param scale the new scale for the view
959
     *
960
     * @see ViewPort#setProjection(IProjection)
961
     * @see #getScaleView()
962
     */
963
    public void setScaleView(long scale) {
964
        double dpi = this.getViewPort().getDPI();
965
        if (viewPort.getImageSize() == null) {
966
            return;
967
        }
968
        IProjection proj = viewPort.getProjection();
969
        if (viewPort.getAdjustedExtent() == null) {
970
            return;
971
        }
972
        double[] trans2Meter = getDistanceTrans2Meter();
973
        Envelope env = viewPort.getAdjustedExtent();
974
        Rectangle2D r = new Rectangle2D.Double(env.getMinimum(0), env.getMinimum(1), env.getLength(0), env.getLength(1));
975
        Rectangle2D rec = proj.getExtent(r, scale, viewPort.getImageWidth(), viewPort.getImageHeight(), 100 * getDistanceTrans2Meter()[getViewPort().getMapUnits()], trans2Meter[getViewPort().getDistanceUnits()], dpi);
976
        try {
977
            getViewPort().setEnvelope(GEOM_MANAGER.createEnvelope(rec.getX(), rec.getY(), rec.getMaxX(), rec.getMaxY(), SUBTYPES.GEOM2D));
978
        } catch (CreateEnvelopeException e) {
979
            LOGGER.error("Error seting the bounding box");
980
        }
981
    }
982

    
983
    /**
984
     * <p>
985
     * Returns the screen resolution (Dots Per Inch) as it was defined by the
986
     * user's preference, or by default as it is defined in the default
987
     * Toolkit.</p>
988
     *
989
     * Be care, use ViewPort#getDPI to ensure are using the corrects DPI.
990
     *
991
     * @return double with the screen's dpi
992
     */
993
    public static double getScreenDPI() {
994
        return CompatLocator.getGraphicsUtils().getScreenDPI();
995
    }
996

    
997
    /**
998
     * @see
999
     * org.gvsig.fmap.mapcontext.rendering.strategies.Strategy#process(com.iver.cit.gvsig.fmap.FeatureSelectorVisitor)
1000
     */
1001
    public void process(Visitor visitor) {
1002
    }
1003

    
1004
    /**
1005
     * @see
1006
     * org.gvsig.fmap.mapcontext.rendering.strategies.Strategy#processSelected(com.iver.cit.gvsig.fmap.FeatureVisitor)
1007
     */
1008
    public void processSelected(Visitor visitor) {
1009
    }
1010

    
1011
    /**
1012
     * @see
1013
     * org.gvsig.fmap.mapcontext.rendering.strategies.Strategy#select(com.iver.cit.gvsig.fmap.FeatureSelectorVisitor,
1014
     * VectorialSubSet)
1015
     */
1016
    public void select(Visitor visitor) {
1017
    }
1018

    
1019
    /**
1020
     * @see
1021
     * org.gvsig.fmap.mapcontext.rendering.strategies.Strategy#selectFromSelection()
1022
     */
1023
    public void selectFromSelection() {
1024
    }
1025

    
1026
    /**
1027
     * @see
1028
     * org.gvsig.fmap.mapcontext.rendering.strategies.Strategy#createIndex()
1029
     */
1030
    public void createIndex() {
1031
    }
1032

    
1033
    /**
1034
     * @see org.cresques.geo.Projected#getProjection()
1035
     *
1036
     * @see ViewPort#getProjection()
1037
     * @see #setProjection(IProjection)
1038
     * @see #reProject(ICoordTrans)
1039
     */
1040
    public IProjection getProjection() {
1041
        ViewPort vp = this.getViewPort();
1042
        if( vp == null ) {
1043
            return null;
1044
        }
1045
        return vp.getProjection();
1046
    }
1047

    
1048
    /**
1049
     * <p>
1050
     * Sets the new projection.</p>
1051
     *
1052
     * @param proj the new projection
1053
     *
1054
     * @see #getProjection()
1055
     * @see ViewPort#setProjection(IProjection)
1056
     * @see #reProject(ICoordTrans)
1057
     */
1058
    public void setProjection(IProjection proj) {
1059
        if (getViewPort() != null) {
1060
            getViewPort().setProjection(proj);
1061
        }
1062
    }
1063

    
1064
    /**
1065
     * @see org.cresques.geo.Projected#reProject(org.cresques.cts.ICoordTrans)
1066
     */
1067
    public void reProject(ICoordTrans arg0) {
1068
        // TODO implementar reprojecci?n (lo que sea eso)
1069
    }
1070

    
1071
    public Envelope getSelectionBounds() throws BaseException {
1072

    
1073
        SelectedEnvelopeVisitor visitor = new SelectedEnvelopeVisitor();
1074

    
1075
        layers.accept(visitor);
1076
        Envelope env_in_data_crs = visitor.getSelectioEnvelope();
1077
        return env_in_data_crs;
1078
    }
1079

    
1080
    /**
1081
     * <p>
1082
     * Draws this map if its {@link ViewPort ViewPort} has an extent
1083
     * defined:<br>
1084
     * <ol>
1085
     * <li>Selects only the layers that have to be drawn:
1086
     * {@linkplain #prepareDrawing(BufferedImage, Graphics2D, double)}.
1087
     * <li>Sets quality: antialiasing by text and images, and quality rendering.
1088
     * <li>Draws the layers.
1089
     * <li>Fires a <code>LayerDrawEvent.GRAPHICLAYER_BEFORE_DRAW</code>.
1090
     * <li>Draws the graphic layer.
1091
     * <li>Fires a <code>LayerDrawEvent.GRAPHICLAYER_AFTER_DRAW</code>.
1092
     * <li>Invokes the garbage collector and memory clean.
1093
     * </ol>
1094
     * </p>
1095
     *
1096
     * @param image buffer used sometimes instead <code>g</code> to accelerate
1097
     * the draw. For example, if two points are as closed that can't be
1098
     * distinguished, draws only one.
1099
     * @param g for rendering 2-dimensional shapes, text and images on the
1100
     * Java(tm) platform
1101
     * @param cancel shared object that determines if this layer can continue
1102
     * being drawn
1103
     * @param scale the scale of the view. Must be between
1104
     * {@linkplain FLayer#getMinScale()} and {@linkplain FLayer#getMaxScale()}.
1105
     * @throws MapContextException if there is an error getting the instance of
1106
     * MapContextDrawer
1107
     * @throws ReadDriverException if fails reading with the driver.
1108
     */
1109
    public void draw(BufferedImage image, Graphics2D g, Cancellable cancel,
1110
            double scale) throws ReadException, MapContextException {
1111
        if (viewPort.getEnvelope() == null) {
1112
            return;
1113
        }
1114

    
1115
        CompatLocator.getGraphicsUtils().setRenderingHintsForDrawing(g);
1116

    
1117
        this.getMapContextDrawer().draw(this.layers, image, g, cancel, scale);
1118
    }
1119

    
1120
    /**
1121
     * <p>
1122
     * Draws only the internal graphic layer using the information of the
1123
     * {@link ViewPort ViewPort} of this map.</p>
1124
     *
1125
     * @param image image used to accelerate the screen draw
1126
     * @param g for rendering 2-dimensional shapes, text and images on the
1127
     * Java(tm) platform
1128
     * @param cancel shared object that determines if this layer can continue
1129
     * being drawn
1130
     * @param scale value that represents the scale
1131
     * @throws ReadDriverException if fails reading with the driver.
1132
     * @deprecated use
1133
     * {@link #draw(BufferedImage, Graphics2D, Cancellable, double)} instead
1134
     * @see GraphicLayer#draw(BufferedImage, Graphics2D, ViewPort, Cancellable,
1135
     * double)
1136
     */
1137
    public void drawGraphics(BufferedImage image, Graphics2D g,
1138
            Cancellable cancel, double scale) throws ReadException {
1139

    
1140
                // From now on the graphics layer is handled by the MapContextDrawer,
1141
        // so call the draw method instead.
1142
        try {
1143
            draw(image, g, cancel, scale);
1144
        } catch (MapContextException e) {
1145
            throw new RuntimeException(e);
1146
        }
1147
    }
1148

    
1149
    /**
1150
     * <p>
1151
     * Like
1152
     * {@linkplain MapContext#draw(BufferedImage, Graphics2D, Cancellable, double)}
1153
     * , but creating the task as cancellable.
1154
     * </p>
1155
     *
1156
     * @param image buffer used sometimes instead <code>g</code> to accelerate
1157
     * the draw. For example, if two points are as closed that can't be
1158
     * distinguished, draws only one.
1159
     * @param g for rendering 2-dimensional shapes, text and images on the
1160
     * Java(tm) platform
1161
     * @param scale the scale of the view. Must be between
1162
     * {@linkplain FLayer#getMinScale()} and {@linkplain FLayer#getMaxScale()}.
1163
     * @throws MapContextException if there is an error getting the instance of
1164
     * MapContextDrawer
1165
     *
1166
     * @throws ReadDriverException if the driver fails reading.
1167
     *
1168
     * @see #draw(BufferedImage, Graphics2D, Cancellable, double)
1169
     */
1170
    public void draw(BufferedImage image, Graphics2D g, double scale)
1171
            throws ReadException, MapContextException {
1172
        draw(image, g, new Cancellable() {
1173
            /**
1174
             * @see org.gvsig.utils.swing.threads.Cancellable#isCanceled()
1175
             */
1176
            public boolean isCanceled() {
1177
                return false;
1178
            }
1179

    
1180
            public void setCanceled(boolean canceled) {
1181
                // Do nothing
1182
            }
1183
        }, scale);
1184
    }
1185

    
1186
    /**
1187
     * <p>
1188
     * Gets the {@link ViewPort ViewPort} associated to this map.</p>
1189
     *
1190
     * @return the view port
1191
     *
1192
     * @see #setViewPort(ViewPort)
1193
     */
1194
    public ViewPort getViewPort() {
1195
        return viewPort;
1196
    }
1197
    
1198
    /**
1199
     * <p>
1200
     * Sets a {@link ViewPort ViewPort} with the drawing information of this
1201
     * map.</p>
1202
     * <p>
1203
     * If there was a previous view port, removes its
1204
     * {@link EventBuffer EventBuffer} and adds the new one.</p>
1205
     *
1206
     * @param viewPort the viewPort
1207
     *
1208
     * @see #getViewPort()
1209
     */
1210
    public void setViewPort(ViewPort viewPort) {
1211
        if (this.viewPort != null) {
1212
            this.viewPort.removeViewPortListener(eventBuffer);
1213
        }
1214

    
1215
        if (this.mapContextDrawer != null) {
1216
            this.mapContextDrawer.setViewPort(viewPort);
1217
        }
1218

    
1219
        this.viewPort = viewPort;
1220
        if (viewPort != null) {
1221
            viewPort.addViewPortListener(eventBuffer);
1222
        }
1223
    }
1224

    
1225
    /**
1226
     * <p>
1227
     * Sets the given extent to the {@link ViewPort ViewPort} and updates the
1228
     * view with the new zoom.</p>
1229
     *
1230
     * @param extent the extent of the new zoom
1231
     */
1232
    public void zoomToEnvelope(Envelope env) {
1233
        if (env == null || env.isEmpty()) {
1234
            return;
1235
        }
1236
        if( env.isCollapsed(Geometry.SUBTYPES.GEOM2D) ) {
1237
            // El envelop ha colapsado en X e Y.
1238
            // Fijamos la escala por defecto para cuando colapsa y centramos
1239
            // sobre el centro del envelop.
1240
            
1241
            this.setScaleView(this.getScaleToUseWhenEnvelopeCollapse());
1242
            
1243
            Envelope curenv = viewPort.getEnvelope();
1244
            double halfWidth = curenv.getLength(Geometry.DIMENSIONS.X) / 2;
1245
            double halfHeight = curenv.getLength(Geometry.DIMENSIONS.Y) / 2;
1246
            double centerx = env.getCenter(Geometry.DIMENSIONS.X);
1247
            double centery = env.getCenter(Geometry.DIMENSIONS.Y);
1248

    
1249
            Point lowerCorner = GeometryUtils.createPoint(halfWidth + centerx, halfHeight + centery);
1250
            Point upperCorner = GeometryUtils.createPoint(halfWidth - centerx, halfHeight - centery);
1251

    
1252
            env.setLowerCorner(lowerCorner);
1253
            env.setUpperCorner(upperCorner);            
1254
        }
1255
        this.viewPort.setEnvelope(env);
1256
    }
1257

    
1258
    /**
1259
     * <p>
1260
     * Returns the union of all extents of all layers of this map.</p>
1261
     *
1262
     * @return full extent of layers of this map
1263
     * @throws ReadDriverException if the driver fails reading.
1264
     *
1265
     * @see FLayers#getFullEnvelope()
1266
     */
1267
    public Envelope getFullEnvelope() throws ReadException {
1268
        Envelope envelope = layers.getFullEnvelope();
1269

    
1270
        for (VectorLayer tracLayer : this.tracLayers.values()) {
1271
            if (tracLayer != null) {
1272
                Envelope graphicsEnvelope = tracLayer.getFullEnvelope();
1273
                if( graphicsEnvelope==null ) {
1274
                    continue;
1275
                }
1276
                if (envelope == null) {
1277
                    try {
1278
                        envelope =  (Envelope) graphicsEnvelope.clone();
1279
                    } catch (CloneNotSupportedException ex) {
1280
                    }
1281
                } else if (graphicsEnvelope != null) {
1282
                    envelope.add(graphicsEnvelope);
1283
                }
1284
            }
1285
        }
1286
        return envelope;
1287
    }
1288

    
1289
    /**
1290
     * <p>
1291
     * Adds a listener of atomic events to the internal
1292
     * {@link EventBuffer EventBuffer}.</p>
1293
     *
1294
     * @param listener the new listener
1295
     *
1296
     * @return <code>true</code> if has added the listener successfully
1297
     *
1298
     * @see #removeAtomicEventListener(AtomicEventListener)
1299
     * @see EventBuffer#addAtomicEventListener(AtomicEventListener)
1300
     */
1301
    public boolean addAtomicEventListener(AtomicEventListener listener) {
1302
        return eventBuffer.addAtomicEventListener(listener);
1303
    }
1304

    
1305
    /**
1306
     * <p>
1307
     * Removes a listener of atomic events from the internal
1308
     * {@link EventBuffer EventBuffer}.</p>
1309
     *
1310
     * @param listener the listener to remove
1311
     *
1312
     * @return <tt>true</tt> if the list contained the specified element
1313
     *
1314
     * @see #addAtomicEventListener(AtomicEventListener)
1315
     * @see EventBuffer#removeAtomicEventListener(AtomicEventListener)
1316
     */
1317
    public boolean removeAtomicEventListener(AtomicEventListener listener) {
1318
        return eventBuffer.removeAtomicEventListener(listener);
1319
    }
1320

    
1321
    /**
1322
     * @see EventBuffer#beginAtomicEvent()
1323
     *
1324
     * @see #endAtomicEvent()
1325
     */
1326
    public void beginAtomicEvent() {
1327
        eventBuffer.beginAtomicEvent();
1328
    }
1329

    
1330
    /**
1331
     * @see EventBuffer#endAtomicEvent()
1332
     *
1333
     * @see #beginAtomicEvent()
1334
     */
1335
    public void endAtomicEvent() {
1336
        eventBuffer.endAtomicEvent();
1337
    }
1338

    
1339
    /**
1340
     * <p>
1341
     * The class <code>LayerEventListener</code> implements the methods of
1342
     * {@link LayerCollectionListener LayerCollectionListener} that handles the
1343
     * "layer added" or "layer removed" events in a map.</p>
1344
     * <p>
1345
     * Is designed as a listener for all layers in a
1346
     * {@link MapContext MapContext}.</p>
1347
     *
1348
     * @author Fernando Gonz?lez Cort?s
1349
     */
1350
    public class LayerEventListener extends BaseLayerCollectionListener {
1351

    
1352
        @Override
1353
        public void layerAdded(LayerCollectionEvent e) {
1354
            // Voy a mover todo esto a la vista
1355
//            // Si aun no tenemos envelope, problablemente sera la primera capa,
1356
//            // asi que asignamos el envelope de la capa al mapcontext.
1357
//            if (getViewPort().getEnvelope() == null) {
1358
//                FLayer lyr = e.getAffectedLayer();
1359
//                if (lyr.isAvailable()) {
1360
//                    try {
1361
//                        getViewPort().setEnvelope(lyr.getFullEnvelope());
1362
//                    } catch (Exception ex) {
1363
//                        logger.warn(
1364
//                                MessageFormat.format(
1365
//                                    "Can''t set envelope to view port from layer ''{0}''",
1366
//                                    new Object[]{lyr.getName()}
1367
//                                ),
1368
//                                ex
1369
//                        );
1370
//                    }
1371
//                }
1372
//            }
1373

    
1374
            // Registramos al FMap como listener del legend de las capas
1375
            FLayer lyr = e.getAffectedLayer();
1376
            addSelectionListener(lyr);
1377
        }
1378

    
1379
        @Override
1380
        public void layerRemoved(LayerCollectionEvent e) {
1381
            FLayer lyr = e.getAffectedLayer();
1382

    
1383
            lyr.removeLayerListener(eventBuffer);
1384

    
1385
            if (lyr instanceof Classifiable) {
1386
                Classifiable c = (Classifiable) lyr;
1387
                c.removeLegendListener(eventBuffer);
1388
            }
1389

    
1390
            if (lyr instanceof SingleLayer && ((SingleLayer) lyr).getDataStore() != null) {
1391
                ((SingleLayer) lyr).getDataStore().deleteObserver(
1392
                        MapContext.this);
1393
            }
1394

    
1395
        }
1396

    
1397
    }
1398

    
1399
    /**
1400
     * <p>
1401
     * Adds the {@link LayerEventListener LayerEventListener} of this map to the
1402
     * collection of layers argument.</p>
1403
     *
1404
     * @param a collection of layers
1405
     */
1406
    public void addAsCollectionListener(FLayers layers2) {
1407
        layers2.addLayerCollectionListener(layerEventListener);
1408
        layers2.addLayerCollectionListener(eventBuffer);
1409
    }
1410

    
1411
    public VectorLayer getGraphicsLayer(String name) {
1412
        return this.tracLayers.get(name);
1413
    }
1414
    
1415
    public Collection<VectorLayer> getGraphicsLayers() {
1416
        return this.tracLayers.values();
1417
    }
1418
    
1419
    public void setGraphicsLayer(String name, FLyrVect layer) {
1420
        this.tracLayers.put(name, layer);
1421
    }
1422
    
1423
    public void removeGraphicsLayer(String name) {
1424
        this.tracLayers.remove(name);
1425
    }
1426
    
1427
    /**
1428
     * <p>
1429
     * Returns the internal {@link GraphicLayer GraphicLayer}.</p>
1430
     *
1431
     * @return the graphic layer of this map
1432
     *
1433
     * @see #setGraphicsLayer(GraphicLayer)
1434
     */
1435
    public GraphicLayer getGraphicsLayer() {
1436
        GraphicLayer tracLayer = (GraphicLayer) this.tracLayers.get(DEFAULT_TRACTLAYER);
1437
        if (tracLayer == null) {
1438
            if (getViewPort() != null) {
1439
                tracLayer
1440
                        = MapContextLocator.getMapContextManager()
1441
                        .createGraphicsLayer(
1442
                                getViewPort().getProjection());
1443
            } else {
1444
                tracLayer
1445
                        = MapContextLocator.getMapContextManager()
1446
                        .createGraphicsLayer(null);
1447
            }
1448
            this.tracLayers.put(DEFAULT_TRACTLAYER, tracLayer);
1449
        }
1450
        return tracLayer;
1451
    }
1452

    
1453
    /**
1454
     * <p>
1455
     * Sets a new {@link GraphicLayer GraphicLayer} to this map.</p>
1456
     *
1457
     * @param graphicLayer the new graphic layer
1458
     *
1459
     * @see #getGraphicsLayer()
1460
     */
1461
    public void setGraphicsLayer(GraphicLayer graphicLayer) {
1462
        this.tracLayers.put(DEFAULT_TRACTLAYER, graphicLayer);
1463
    }
1464

    
1465
    /**
1466
     * <p>
1467
     * Indicates whether some other object is "equal to" this map.</p>
1468
     * <p>
1469
     * Returns <code>true</code> if success one of this options:
1470
     * <ul>
1471
     * <li>Both objects are equal according to
1472
     * {@linkplain Object#equals(Object)}.
1473
     * <li>Both maps have the same layers.
1474
     * <li>Both maps have the same number of layers and with the same name.
1475
     * </ul>
1476
     * </p>
1477
     *
1478
     * @param obj the reference object with which to compare.
1479
     * @return <code>true</code> if this object is the same as the
1480
     * <code>arg0</code> argument; otherwise <code>false</code>.
1481
     *
1482
     * @see Object#equals(Object)
1483
     */
1484
    public boolean equals(Object arg0) {
1485
        if (!(arg0 instanceof MapContext)) {
1486
            return false;
1487
        }
1488
        MapContext map = (MapContext) arg0;
1489
        if (super.equals(arg0)) {
1490
            return true;
1491
        }
1492
        if (getLayers() == map.getLayers()) {
1493
            return true;
1494
        }
1495
        boolean isEqual = true;
1496
        if (map.getLayers().getLayersCount() == getLayers().getLayersCount()) {
1497
            for (int i = 0; i < getLayers().getLayersCount(); i++) {
1498

    
1499
                if (!getLayers().getLayer(i).getName().equals(
1500
                        map.getLayers().getLayer(i).getName())) {
1501
                    isEqual = false;
1502
                }
1503

    
1504
            }
1505
        } else {
1506
            isEqual = false;
1507
        }
1508
        return isEqual;
1509
    }
1510

    
1511
    /**
1512
     * <p>
1513
     * Registers the message of an error associated to this map.</p>
1514
     *
1515
     * @param stringProperty the error message
1516
     *
1517
     * @see #getLayersError()
1518
     * @see #clearErrors()
1519
     */
1520
    public void addLayerError(String stringProperty) {
1521
        layersError.add(stringProperty);
1522
    }
1523

    
1524
    /**
1525
     * <p>
1526
     * Gets the list with all error messages registered to this map.</p>
1527
     *
1528
     * @return the list of errors registered to this map
1529
     *
1530
     * @see #addLayerError(String)
1531
     * @see #clearErrors()
1532
     */
1533
    public ArrayList getLayersError() {
1534
        return layersError;
1535
    }
1536

    
1537
    /**
1538
     * <p>
1539
     * Removes all error messages associated to this map.</p>
1540
     *
1541
     * @see #addLayerError(String)
1542
     * @see #getLayersError()
1543
     */
1544
    public void clearErrors() {
1545
        layersError.clear();
1546
    }
1547

    
1548
    /**
1549
     * <p>
1550
     * Creates and returns a new group of layers that belongs to this
1551
     * <code>MapContext</code>.</p>
1552
     *
1553
     * @param parent layer node in this <code>MapContexte</code> that will be
1554
     * the parent of the new node
1555
     * @return the new layer node
1556
     */
1557
    public FLayers getNewGroupLayer(FLayers parent) {
1558
        FLayers group1 = new FLayers();//(this,parent);
1559
        group1.setMapContext(this);
1560
        group1.setParentLayer(parent);
1561
        return group1;
1562
    }
1563

    
1564
    public String getClassName() {
1565
        return null;
1566
    }
1567

    
1568
    public Set<FLyrVect> getLayersToSnap() {
1569
        return layersToSnap;
1570
    }
1571

    
1572
    public void setLayersToSnap(Set<FLyrVect> layersToSnap) {
1573
        this.layersToSnap = layersToSnap;
1574

    
1575
    }
1576

    
1577
    public void update(Observable observable, Object notification) {
1578
        // TODO REVISAR ESTO!!!
1579
        String ntype = null;
1580
        if (notification instanceof FeatureStoreNotification) {
1581
            FeatureStoreNotification fsNotification = (FeatureStoreNotification) notification;
1582
            ntype = fsNotification.getType();
1583
            if (ntype.equals(FeatureStoreNotification.LOAD_FINISHED)
1584
                    || ntype.equals(FeatureStoreNotification.SELECTION_CHANGE)) {
1585
                getLayers().moveTo(0, 0);
1586
            }
1587
        }
1588
    }
1589

    
1590
    public long getDrawVersion() {
1591
        if (getViewPort().getDrawVersion() > this.viewPortVersion
1592
                || getLayers().getDrawVersion() > this.layersVersion
1593
                || getGraphicsLayer().getDrawVersion() > graphicsLayerVersion) {
1594
            updateDrawVersion();
1595
        }
1596
        return this.drawVersion;
1597
    }
1598

    
1599
    protected void updateDrawVersion() {
1600
        this.layersVersion = getLayers().getDrawVersion();
1601
        this.viewPortVersion = getViewPort().getDrawVersion();
1602
        this.graphicsLayerVersion = getGraphicsLayer().getDrawVersion();
1603
        this.drawVersion++;
1604
    }
1605

    
1606
    public MapContextDrawer getMapContextDrawer() throws ReadException,
1607
            MapContextException {
1608
        if (this.mapContextDrawer == null) {
1609
            if (mapContextDrawerClass == null) {
1610
                this.mapContextDrawer = MAPCONTEXT_MANAGER
1611
                        .createDefaultMapContextDrawerInstance();
1612
            } else {
1613
                this.mapContextDrawer = MAPCONTEXT_MANAGER
1614
                        .createMapContextDrawerInstance(mapContextDrawerClass);
1615
            }
1616
            this.mapContextDrawer.setMapContext(this);
1617
            this.mapContextDrawer.setViewPort(viewPort);
1618
        }
1619

    
1620
        return this.mapContextDrawer;
1621
    }
1622

    
1623
    public void setMapContextDrawerClass(Class mapContextDrawerClass)
1624
            throws MapContextException {
1625
        MAPCONTEXT_MANAGER.validateMapContextDrawer(mapContextDrawerClass);
1626
        this.mapContextDrawerClass = mapContextDrawerClass;
1627
        if (this.mapContextDrawer != null) {
1628
            this.mapContextDrawer.dispose();
1629
            this.mapContextDrawer = null;
1630
        }
1631
    }
1632

    
1633
    public void setMapContextDrawer(MapContextDrawer drawer) {
1634
        if (this.mapContextDrawer != null) {
1635
            this.mapContextDrawer.dispose();
1636
            this.mapContextDrawer = null;
1637
        }
1638
        this.mapContextDrawer = drawer;
1639
        if (this.mapContextDrawer != null) {
1640
            this.mapContextDrawer.setMapContext(this);
1641
            this.mapContextDrawer.setViewPort(viewPort);
1642
        }
1643
    }
1644

    
1645
    public void loadFromState(PersistentState state)
1646
            throws PersistenceException {
1647

    
1648
        ViewPort vp = (ViewPort) state.get("ViewPort");
1649
        setViewPort(vp);
1650

    
1651
        layers = (FLayers) state.get("layers");
1652
        layers.setName("root layer");
1653
        loadLayers(layers);
1654
        layers.setMapContext(this);
1655

    
1656
        layerEventListener = new LayerEventListener();
1657
        layers.addLayerCollectionListener(layerEventListener);
1658

    
1659
        layers.addLayerCollectionListener(eventBuffer);
1660
        layers.setProjection(vp.getProjection());
1661

    
1662
        //Add the listener for the selection
1663
        addSelectionListener(layers);
1664

    
1665
        // ======================
1666
        if (state.hasValue("orderManager")) {
1667
            LayerOrderManager lom = (LayerOrderManager) state.get("orderManager");
1668
            this.setOrderManager(lom);
1669
        }
1670
        DefaultMapContextManager manager = (DefaultMapContextManager) MapContextLocator.getMapContextManager();
1671
        manager.notifyLoadMapContext(this);
1672
    }
1673

    
1674
    private void loadLayers(FLayers lyrs) {
1675

    
1676
        int sz = lyrs.getLayersCount();
1677
        for (int i = 0; i < sz; i++) {
1678
            try {
1679
                lyrs.getLayer(i).load();
1680
            } catch (LoadLayerException e) {
1681
                LOGGER.error("While loading layer: " + lyrs.getLayer(i).getName());
1682
            }
1683
        }
1684
    }
1685

    
1686
    public void saveToState(PersistentState state) throws PersistenceException {
1687
        state.set("ViewPort", viewPort);
1688
        state.set("layers", (Persistent)layers);
1689
        state.set("orderManager", this.getOrderManager());
1690
    }
1691

    
1692
    public static class RegisterPersistence implements Callable {
1693

    
1694
        public Object call() {
1695
            PersistenceManager manager = ToolsLocator.getPersistenceManager();
1696
            DynStruct definition = manager.addDefinition(
1697
                    MapContext.class,
1698
                    "MapContext",
1699
                    "MapContext Persistence definition",
1700
                    null,
1701
                    null
1702
            );
1703
            definition.addDynFieldObject("ViewPort")
1704
                    .setClassOfValue(ViewPort.class)
1705
                    .setMandatory(true);
1706

    
1707
            definition.addDynFieldObject("layers")
1708
                    .setClassOfValue(FLayers.class)
1709
                    .setMandatory(true);
1710

    
1711
            definition.addDynFieldObject("orderManager")
1712
                    .setClassOfValue(LayerOrderManager.class)
1713
                    .setMandatory(false);
1714

    
1715
            return Boolean.TRUE;
1716
        }
1717
    }
1718

    
1719
    protected void doDispose() throws BaseException {
1720
        dispose(layers);
1721
        for (VectorLayer tracLayer : this.tracLayers.values()) {
1722
            dispose(tracLayer);
1723
        }
1724
    }
1725

    
1726
    /**
1727
     * <p>
1728
     * Registers an event buffer as a listener for all layers as argument.</p>
1729
     *
1730
     * <p>
1731
     * Each {@link FLayer FLayer} of this map must have an event buffer for all
1732
     * kind of specific listeners of that layer. This method distinguish between {@link Classifiable Classifiable},
1733
     * {@link AlphanumericData AlphanumericData}, and {@link FLayers FLayers}
1734
     * layers, and for each one, registers, for their specific listeners, the
1735
     * <code>eventBuffer</code> as a listener.</p>
1736
     *
1737
     * @param the layer or layers
1738
     */
1739
    public void addSelectionListener(FLayer lyr) {
1740
        lyr.addLayerListener(eventBuffer);
1741

    
1742
        if (lyr instanceof Classifiable) {
1743
            Classifiable c = (Classifiable) lyr;
1744
            c.addLegendListener(eventBuffer);
1745
        }
1746

    
1747
        if (lyr instanceof FLayers) {
1748
            FLayers lyrs = (FLayers) lyr;
1749
            for (int i = 0; i < lyrs.getLayersCount(); i++) {
1750
                addSelectionListener(lyrs.getLayer(i));
1751
            }
1752
        }
1753
        if (lyr instanceof SingleLayer) {
1754
            if (((SingleLayer) lyr).getDataStore() != null) {
1755
                ((SingleLayer) lyr).getDataStore().addObserver(
1756
                        MapContext.this);
1757
            }
1758
        }
1759
    }
1760

    
1761
    public void setOrderManager(LayerOrderManager lom) {
1762
        orderManager = lom;
1763
    }
1764

    
1765
    public LayerOrderManager getOrderManager() {
1766

    
1767
        if (orderManager == null) {
1768
            orderManager = MapContextLocator.getDefaultOrderManager();
1769
        }
1770
        return orderManager;
1771
    }
1772

    
1773
    public boolean hasVectorLayers() {
1774
        return this.hasVectorLayers(this.getLayers());
1775
    }
1776

    
1777
    public boolean hasActiveVectorLayers() {
1778
        FLayer[] layers = this.getLayers().getActives();
1779
        for (int i = 0; i < layers.length; i++) {
1780
            FLayer layer = layers[i];
1781
            if (layer.isAvailable() && layer instanceof FLyrVect) {
1782
                return true;
1783
            }
1784
        }
1785
        return false;
1786
    }
1787

    
1788
    public boolean hasActiveLayers() {
1789
        FLayer[] layers = this.getLayers().getActives();
1790
        return !ArrayUtils.isEmpty(layers);
1791
    }
1792

    
1793
    public boolean hasLayers() {
1794
        return !this.getLayers().isEmpty();
1795
    }
1796
    
1797
    private boolean hasVectorLayers(FLayers layers) {
1798
        for (int i = 0; i < layers.getLayersCount(); i++) {
1799
            FLayer lyr = layers.getLayer(i);
1800
            if (lyr instanceof FLayers) {
1801
                if (hasVectorLayers((FLayers) lyr)) {
1802
                    return true;
1803
                }
1804
            } else if (lyr instanceof FLyrVect) {
1805
                return true;
1806
            }
1807
        }
1808
        return false;
1809
    }
1810
    
1811
    @Override
1812
    public Iterator<FLayer> iterator() {
1813
        return this.layers.iterator();
1814
    }
1815

    
1816
    public Iterator deepiterator() {
1817
        return this.layers.deepiterator();
1818
    }
1819

    
1820
    public MapTimeContext getTimeContext() {
1821
        TimeSupportManager timeSupportManager = TimeSupportLocator.getManager();
1822
        Interval interval = null;
1823
        final List<Instant> times = new ArrayList<>();
1824
        Instant minInstant = null;
1825
        Instant maxInstant = null;
1826

    
1827
        //TODO Separate absolute and relative time
1828
        FLayers layers = this.getLayers();
1829
        for( Iterator<FLayer> iterator = layers.deepiterator(); iterator.hasNext(); ) {
1830
            FLayer layer = iterator.next();
1831
            if( layer instanceof SingleLayer ) {
1832
                DataStore dataStore = ((SingleLayer) layer).getDataStore();
1833
                if( dataStore.getInterval() != null ) {
1834
                    Instant startInstant = dataStore.getInterval().getStart();
1835
                    Instant endInstant = dataStore.getInterval().getEnd();
1836
                    times.addAll(dataStore.getTimes());
1837
                    if( minInstant == null ) {
1838
                        minInstant = startInstant;
1839
                        maxInstant = endInstant;
1840
                    } else {
1841
                        if( minInstant.isAfter(startInstant) ) {
1842
                            minInstant = startInstant;
1843
                        }
1844
                        if( maxInstant.isBefore(endInstant) ) {
1845
                            maxInstant = endInstant;
1846
                        }
1847
                    }
1848
                }
1849
            }
1850
        }
1851

    
1852
        if( minInstant != null ) {
1853
            if( minInstant.isAbsolute() ) {
1854
                try {
1855
                    interval = timeSupportManager.createAbsoluteInterval((AbsoluteInstant) minInstant, (AbsoluteInstant) maxInstant);
1856
                } catch (AbsoluteIntervalTypeNotRegisteredException e) {
1857
                    LOGGER.warn("Error creating the time interval", e);
1858
                }
1859
            } else {
1860
                interval = timeSupportManager.createRelativeInterval(((RelativeInstant) minInstant).toMillis(), ((RelativeInstant) maxInstant).toMillis());
1861
            }
1862
        }
1863
        final Interval tmp_interval = interval;
1864
        return new MapTimeContext() {
1865
            @Override
1866
            public Interval getInterval() {
1867
                return tmp_interval;
1868
            }
1869

    
1870
            @Override
1871
            public List<Instant> getTimes() {
1872
                return times;
1873
            }
1874
        };
1875
    }
1876

    
1877
    public long getScaleToUseWhenEnvelopeCollapse() {
1878
        return this.scaleToUseWhenEnvelopeCollapse;
1879
    }
1880

    
1881
    public void setScaleToUseWhenEnvelopeCollapse(long scale) {
1882
        this.scaleToUseWhenEnvelopeCollapse = scale;
1883
    }
1884
    
1885
    public void setPrintGraphicsLayer(boolean b){
1886
        this.printGraphicsLayer = b;
1887
    }
1888

    
1889
    public boolean isPrintGraphicsLayer(){
1890
        return this.printGraphicsLayer;
1891
    }
1892
    
1893
}