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

History | View | Annotate | Download (64.9 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.Iterator;
33
import java.util.LinkedHashMap;
34
import java.util.List;
35
import java.util.Map;
36
import java.util.Set;
37
import org.apache.commons.lang3.ArrayUtils;
38
import org.cresques.cts.ICoordTrans;
39
import org.cresques.cts.IProjection;
40
import org.cresques.geo.Projected;
41
import org.gvsig.compat.CompatLocator;
42
import org.gvsig.compat.print.PrintAttributes;
43
import org.gvsig.fmap.dal.DataStore;
44
import org.gvsig.fmap.dal.exception.ReadException;
45
import org.gvsig.fmap.dal.feature.FeatureStoreNotification;
46
import org.gvsig.fmap.geom.Geometry;
47
import org.gvsig.fmap.geom.Geometry.SUBTYPES;
48
import org.gvsig.fmap.geom.GeometryLocator;
49
import org.gvsig.fmap.geom.GeometryManager;
50
import org.gvsig.fmap.geom.GeometryUtils;
51
import org.gvsig.fmap.geom.exception.CreateEnvelopeException;
52
import org.gvsig.fmap.geom.primitive.Envelope;
53
import org.gvsig.fmap.geom.primitive.Point;
54
import org.gvsig.fmap.mapcontext.events.ErrorEvent;
55
import org.gvsig.fmap.mapcontext.events.listeners.AtomicEventListener;
56
import org.gvsig.fmap.mapcontext.events.listeners.ErrorListener;
57
import org.gvsig.fmap.mapcontext.events.listeners.EventBuffer;
58
import org.gvsig.fmap.mapcontext.exceptions.LoadLayerException;
59
import org.gvsig.fmap.mapcontext.impl.DefaultMapContextManager;
60
import org.gvsig.fmap.mapcontext.layers.BaseLayerCollectionListener;
61
import org.gvsig.fmap.mapcontext.layers.FLayer;
62
import org.gvsig.fmap.mapcontext.layers.FLayers;
63
import org.gvsig.fmap.mapcontext.layers.LayerCollectionEvent;
64
import org.gvsig.fmap.mapcontext.layers.LayerCollectionListener;
65
import org.gvsig.fmap.mapcontext.layers.LayerDrawEvent;
66
import org.gvsig.fmap.mapcontext.layers.LayerDrawingListener;
67
import org.gvsig.fmap.mapcontext.layers.LayerPositionEvent;
68
import org.gvsig.fmap.mapcontext.layers.operations.Classifiable;
69
import org.gvsig.fmap.mapcontext.layers.operations.SingleLayer;
70
import org.gvsig.fmap.mapcontext.layers.order.LayerOrderManager;
71
import org.gvsig.fmap.mapcontext.layers.vectorial.FLyrVect;
72
import org.gvsig.fmap.mapcontext.layers.vectorial.GraphicLayer;
73
import org.gvsig.fmap.mapcontext.layers.vectorial.VectorLayer;
74
import org.gvsig.fmap.mapcontext.rendering.legend.events.listeners.LegendListener;
75
import org.gvsig.fmap.mapcontext.rendering.strategies.SelectedEnvelopeVisitor;
76
import org.gvsig.timesupport.AbsoluteInstant;
77
import org.gvsig.timesupport.AbsoluteIntervalTypeNotRegisteredException;
78
import org.gvsig.timesupport.Instant;
79
import org.gvsig.timesupport.Interval;
80
import org.gvsig.timesupport.RelativeInstant;
81
import org.gvsig.timesupport.TimeSupportLocator;
82
import org.gvsig.timesupport.TimeSupportManager;
83
import org.gvsig.tools.ToolsLocator;
84
import org.gvsig.tools.dispose.Disposable;
85
import org.gvsig.tools.dispose.DisposeUtils;
86
import org.gvsig.tools.dispose.impl.AbstractDisposable;
87
import org.gvsig.tools.dynobject.DynStruct;
88
import org.gvsig.tools.exception.BaseException;
89
import org.gvsig.tools.observer.Observable;
90
import org.gvsig.tools.observer.Observer;
91
import org.gvsig.tools.persistence.PersistenceManager;
92
import org.gvsig.tools.persistence.Persistent;
93
import org.gvsig.tools.persistence.PersistentState;
94
import org.gvsig.tools.persistence.exception.PersistenceException;
95
import org.gvsig.tools.task.Cancellable;
96
import org.gvsig.tools.util.Callable;
97
import org.gvsig.tools.visitor.Visitor;
98
import org.slf4j.Logger;
99
import org.slf4j.LoggerFactory;
100

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

    
124
    public interface MapTimeContext {
125
        public Interval getInterval();
126
        public List<Instant> getTimes();
127
    }    
128

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

    
135
    public static ArrayList AREANAMES = new ArrayList();
136
    public static ArrayList AREAABBR = new ArrayList();
137
    public static ArrayList AREATRANS2METER = new ArrayList();
138

    
139
    public static ArrayList DISTANCENAMES = new ArrayList();
140
    public static ArrayList DISTANCEABBR = new ArrayList();
141
    public static ArrayList DISTANCETRANS2METER = new ArrayList();
142

    
143
    public static final Color DEFAULT_SELECTION_COLOR =  new Color(Color.YELLOW.getRed(), Color.YELLOW.getGreen(), Color.YELLOW.getBlue(), 127);
144

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

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

    
166
    }
167

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
493
    /**
494
     * <p>
495
     * Color used to represent the selections.</p>
496
     */
497
//    private Color selectionColor = DEFAULT_SELECTION_COLOR;
498
    private Set<FLyrVect> layersToSnap = new LayersToSnap();
499

    
500
    /**
501
     * <p>
502
     * Gets the color used to represent the selections.</p>
503
     *
504
     * @return color used to represent the selections
505
     */
506
    public Color getSelectionColor() {
507
        if(this.viewPort == null) {
508
            return MapContext.DEFAULT_SELECTION_COLOR;
509
        }
510
        return this.viewPort.getSelectionColor();
511
    }
512

    
513
    /**
514
     * <p>
515
     * Sets the color used to represent the selections.</p>
516
     *
517
     * @param selectionColor color used to represent the selections
518
     */
519
    public void setSelectionColor(Color selectionColor) {
520
        if(this.viewPort == null){
521
            return;
522
        }
523
        this.viewPort.setSelectionColor(selectionColor);
524
    }
525

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

    
538
    public MapContext() {
539
        this.legendListeners = new ArrayList();
540
        this.tracLayers = new LinkedHashMap<>();
541
        this.layerEventListener = new LayerEventListener();
542
    }
543

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

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

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

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

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

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

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

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

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

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

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

    
737
    /**
738
     * <p>
739
     * Returns the hierarchy of {@link FLayers FLayers} nodes stored in this
740
     * map.</p>
741
     *
742
     * @return the hierarchy of nodes of layers stored in this map
743
     */
744
    public FLayers getLayers() {
745
        return layers;
746
    }
747

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

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

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

    
799
        CompatLocator.getGraphicsUtils().setRenderingHintsForPrinting(g);
800

    
801
        Cancellable cancel = new Cancellable() {
802
            public boolean isCanceled() {
803
                return false;
804
            }
805

    
806
            public void setCanceled(boolean canceled) {
807
                // No queremos que se pueda cancelar la impresi?n.
808

    
809
            }
810
        };
811
        MapContextDrawer drawer = this.getMapContextDrawer();
812
        drawer.setPrintGraphicsLayer(this.isPrintGraphicsLayer());
813
        drawer.print(this.layers, g, cancel, scale, properties);
814
    }
815

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

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

    
861
        /*
862
         * Cloning order manager
863
         */
864
        LayerOrderManager lom = this.getOrderManager();
865
        try {
866
            lom = (LayerOrderManager) lom.clone();
867
            ret.setOrderManager(lom);
868
        } catch (CloneNotSupportedException e1) {
869
            LOGGER.error("While cloning order manager", e1);
870
        }
871

    
872
        for (int i = 0; i < antLayers.getLayersCount(); i++) {
873
            FLayer lyr = antLayers.getLayer(i);
874
            try {
875
                FLayer auxLayer = lyr.cloneLayer();
876
                ret.layers.addLayer(auxLayer);
877
                auxLayer.dispose();
878
            } catch (Exception e) {
879
                throw new RuntimeException(e);
880
            }
881
        }
882
        return ret;
883

    
884
    }
885

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

    
903
    /**
904
     * <p>
905
     * Adds a layer to the group of layers that are at a upper level in the
906
     * tree.</p>
907
     *
908
     * @param vectorial the layer to add
909
     */
910
    public void addToTrackLayer(FLayer vectorial) {
911
    }
912

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

    
932
        if (viewPort.getImageSize() == null) {
933
            return -1;
934
        }
935

    
936
        if (viewPort.getAdjustedEnvelope() == null) {
937
            return 0;
938
        }
939
        double[] trans2Meter = getDistanceTrans2Meter();
940
        int mUnits = getViewPort().getMapUnits();
941

    
942
        if (proj == null) {
943
            double w = ((viewPort.getImageSize().width / dpi) * 0.0254);
944
            return (long) (viewPort.getAdjustedEnvelope().getLength(0)
945
                    / w * trans2Meter[mUnits]);
946
        }
947

    
948
        return Math.round(proj.getScale(
949
                viewPort.getAdjustedEnvelope().getMinimum(0) * trans2Meter[mUnits],
950
                viewPort.getAdjustedEnvelope().getMaximum(0) * trans2Meter[mUnits],
951
                viewPort.getImageSize().width,
952
                dpi));
953

    
954
    }
955

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

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

    
1002
    /**
1003
     * @see
1004
     * org.gvsig.fmap.mapcontext.rendering.strategies.Strategy#process(com.iver.cit.gvsig.fmap.FeatureSelectorVisitor)
1005
     */
1006
    public void process(Visitor visitor) {
1007
    }
1008

    
1009
    /**
1010
     * @see
1011
     * org.gvsig.fmap.mapcontext.rendering.strategies.Strategy#processSelected(com.iver.cit.gvsig.fmap.FeatureVisitor)
1012
     */
1013
    public void processSelected(Visitor visitor) {
1014
    }
1015

    
1016
    /**
1017
     * @see
1018
     * org.gvsig.fmap.mapcontext.rendering.strategies.Strategy#select(com.iver.cit.gvsig.fmap.FeatureSelectorVisitor,
1019
     * VectorialSubSet)
1020
     */
1021
    public void select(Visitor visitor) {
1022
    }
1023

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

    
1031
    /**
1032
     * @see
1033
     * org.gvsig.fmap.mapcontext.rendering.strategies.Strategy#createIndex()
1034
     */
1035
    public void createIndex() {
1036
    }
1037

    
1038
    /**
1039
     * @see org.cresques.geo.Projected#getProjection()
1040
     *
1041
     * @see ViewPort#getProjection()
1042
     * @see #setProjection(IProjection)
1043
     * @see #reProject(ICoordTrans)
1044
     */
1045
    public IProjection getProjection() {
1046
        ViewPort vp = this.getViewPort();
1047
        if( vp == null ) {
1048
            return null;
1049
        }
1050
        return vp.getProjection();
1051
    }
1052

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

    
1069
    /**
1070
     * @see org.cresques.geo.Projected#reProject(org.cresques.cts.ICoordTrans)
1071
     */
1072
    public void reProject(ICoordTrans arg0) {
1073
        // TODO implementar reprojecci?n (lo que sea eso)
1074
    }
1075

    
1076
    public Envelope getSelectionBounds() throws BaseException {
1077

    
1078
        SelectedEnvelopeVisitor visitor = new SelectedEnvelopeVisitor();
1079

    
1080
        layers.accept(visitor);
1081
        Envelope env_in_data_crs = visitor.getSelectioEnvelope();
1082
        return env_in_data_crs;
1083
    }
1084

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

    
1120
        CompatLocator.getGraphicsUtils().setRenderingHintsForDrawing(g);
1121

    
1122
        this.getMapContextDrawer().draw(this.layers, image, g, cancel, scale);
1123
    }
1124

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

    
1145
                // From now on the graphics layer is handled by the MapContextDrawer,
1146
        // so call the draw method instead.
1147
        try {
1148
            draw(image, g, cancel, scale);
1149
        } catch (MapContextException e) {
1150
            throw new RuntimeException(e);
1151
        }
1152
    }
1153

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

    
1186
            @Override
1187
            public void setCanceled(boolean canceled) {
1188
                // Do nothing
1189
            }
1190
        }, scale);
1191
    }
1192

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

    
1222
        if (this.mapContextDrawer != null) {
1223
            this.mapContextDrawer.setViewPort(viewPort);
1224
        }
1225

    
1226
        this.viewPort = viewPort;
1227
        if (viewPort != null) {
1228
            viewPort.addViewPortListener(eventBuffer);
1229
        }
1230
    }
1231

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

    
1256
            Point lowerCorner = GeometryUtils.createPoint(halfWidth + centerx, halfHeight + centery);
1257
            Point upperCorner = GeometryUtils.createPoint(halfWidth - centerx, halfHeight - centery);
1258

    
1259
            env.setLowerCorner(lowerCorner);
1260
            env.setUpperCorner(upperCorner);            
1261
        }
1262
        this.viewPort.setEnvelope(env);
1263
    }
1264

    
1265
    /**
1266
     * <p>
1267
     * Returns the union of all extents of all layers of this map.</p>
1268
     *
1269
     * @return full extent of layers of this map
1270
     * @throws ReadDriverException if the driver fails reading.
1271
     *
1272
     * @see FLayers#getFullEnvelope()
1273
     */
1274
    public Envelope getFullEnvelope() throws ReadException {
1275
        Envelope envelope = layers.getFullEnvelope();
1276

    
1277
        for (VectorLayer tracLayer : this.tracLayers.values()) {
1278
            if (tracLayer != null) {
1279
                Envelope graphicsEnvelope = tracLayer.getFullEnvelope();
1280
                if( graphicsEnvelope==null ) {
1281
                    continue;
1282
                }
1283
                if (envelope == null) {
1284
                    try {
1285
                        envelope =  (Envelope) graphicsEnvelope.clone();
1286
                    } catch (CloneNotSupportedException ex) {
1287
                    }
1288
                } else if (graphicsEnvelope != null) {
1289
                    envelope.add(graphicsEnvelope);
1290
                }
1291
            }
1292
        }
1293
        return envelope;
1294
    }
1295

    
1296
    /**
1297
     * <p>
1298
     * Adds a listener of atomic events to the internal
1299
     * {@link EventBuffer EventBuffer}.</p>
1300
     *
1301
     * @param listener the new listener
1302
     *
1303
     * @return <code>true</code> if has added the listener successfully
1304
     *
1305
     * @see #removeAtomicEventListener(AtomicEventListener)
1306
     * @see EventBuffer#addAtomicEventListener(AtomicEventListener)
1307
     */
1308
    public boolean addAtomicEventListener(AtomicEventListener listener) {
1309
        return eventBuffer.addAtomicEventListener(listener);
1310
    }
1311

    
1312
    /**
1313
     * <p>
1314
     * Removes a listener of atomic events from the internal
1315
     * {@link EventBuffer EventBuffer}.</p>
1316
     *
1317
     * @param listener the listener to remove
1318
     *
1319
     * @return <tt>true</tt> if the list contained the specified element
1320
     *
1321
     * @see #addAtomicEventListener(AtomicEventListener)
1322
     * @see EventBuffer#removeAtomicEventListener(AtomicEventListener)
1323
     */
1324
    public boolean removeAtomicEventListener(AtomicEventListener listener) {
1325
        return eventBuffer.removeAtomicEventListener(listener);
1326
    }
1327

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

    
1337
    /**
1338
     * @see EventBuffer#endAtomicEvent()
1339
     *
1340
     * @see #beginAtomicEvent()
1341
     */
1342
    public void endAtomicEvent() {
1343
        eventBuffer.endAtomicEvent();
1344
    }
1345

    
1346
    /**
1347
     * <p>
1348
     * The class <code>LayerEventListener</code> implements the methods of
1349
     * {@link LayerCollectionListener LayerCollectionListener} that handles the
1350
     * "layer added" or "layer removed" events in a map.</p>
1351
     * <p>
1352
     * Is designed as a listener for all layers in a
1353
     * {@link MapContext MapContext}.</p>
1354
     *
1355
     * @author Fernando Gonz?lez Cort?s
1356
     */
1357
    public class LayerEventListener extends BaseLayerCollectionListener {
1358

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

    
1381
            // Registramos al FMap como listener del legend de las capas
1382
            FLayer lyr = e.getAffectedLayer();
1383
            addSelectionListener(lyr);
1384
        }
1385

    
1386
        @Override
1387
        public void layerRemoved(LayerCollectionEvent e) {
1388
            FLayer lyr = e.getAffectedLayer();
1389

    
1390
            lyr.removeLayerListener(eventBuffer);
1391

    
1392
            if (lyr instanceof Classifiable) {
1393
                Classifiable c = (Classifiable) lyr;
1394
                c.removeLegendListener(eventBuffer);
1395
            }
1396

    
1397
            if (lyr instanceof SingleLayer && ((SingleLayer) lyr).getDataStore() != null) {
1398
                ((SingleLayer) lyr).getDataStore().deleteObserver(
1399
                        MapContext.this);
1400
            }
1401

    
1402
        }
1403

    
1404
    }
1405

    
1406
    /**
1407
     * <p>
1408
     * Adds the {@link LayerEventListener LayerEventListener} of this map to the
1409
     * collection of layers argument.</p>
1410
     *
1411
     * @param a collection of layers
1412
     */
1413
    public void addAsCollectionListener(FLayers layers2) {
1414
        layers2.addLayerCollectionListener(layerEventListener);
1415
        layers2.addLayerCollectionListener(eventBuffer);
1416
    }
1417

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

    
1460
    /**
1461
     * <p>
1462
     * Sets a new {@link GraphicLayer GraphicLayer} to this map.</p>
1463
     *
1464
     * @param graphicLayer the new graphic layer
1465
     *
1466
     * @see #getGraphicsLayer()
1467
     */
1468
    public void setGraphicsLayer(GraphicLayer graphicLayer) {
1469
        this.tracLayers.put(DEFAULT_TRACTLAYER, graphicLayer);
1470
    }
1471

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

    
1507
                if (!getLayers().getLayer(i).getName().equals(
1508
                        map.getLayers().getLayer(i).getName())) {
1509
                    isEqual = false;
1510
                }
1511

    
1512
            }
1513
        } else {
1514
            isEqual = false;
1515
        }
1516
        return isEqual;
1517
    }
1518

    
1519
    /**
1520
     * <p>
1521
     * Registers the message of an error associated to this map.</p>
1522
     *
1523
     * @param stringProperty the error message
1524
     *
1525
     * @see #getLayersError()
1526
     * @see #clearErrors()
1527
     */
1528
    public void addLayerError(String stringProperty) {
1529
        layersError.add(stringProperty);
1530
    }
1531

    
1532
    /**
1533
     * <p>
1534
     * Gets the list with all error messages registered to this map.</p>
1535
     *
1536
     * @return the list of errors registered to this map
1537
     *
1538
     * @see #addLayerError(String)
1539
     * @see #clearErrors()
1540
     */
1541
    public ArrayList getLayersError() {
1542
        return layersError;
1543
    }
1544

    
1545
    /**
1546
     * <p>
1547
     * Removes all error messages associated to this map.</p>
1548
     *
1549
     * @see #addLayerError(String)
1550
     * @see #getLayersError()
1551
     */
1552
    public void clearErrors() {
1553
        layersError.clear();
1554
    }
1555

    
1556
    /**
1557
     * <p>
1558
     * Creates and returns a new group of layers that belongs to this
1559
     * <code>MapContext</code>.</p>
1560
     *
1561
     * @param parent layer node in this <code>MapContexte</code> that will be
1562
     * the parent of the new node
1563
     * @return the new layer node
1564
     */
1565
    public FLayers getNewGroupLayer(FLayers parent) {
1566
        FLayers group1 = new FLayers();//(this,parent);
1567
        group1.setMapContext(this);
1568
        group1.setParentLayer(parent);
1569
        return group1;
1570
    }
1571

    
1572
    public String getClassName() {
1573
        return null;
1574
    }
1575

    
1576
    public Set<FLyrVect> getLayersToSnap() {
1577
        return layersToSnap;
1578
    }
1579

    
1580
    /**
1581
     * Adds the layers to the current "layersToSnap" without 
1582
     * replacing the current instance of "layersToSnap"
1583
     * 
1584
     * @param layers 
1585
     */
1586
    public void setLayersToSnap(Set<FLyrVect> layers) {
1587
        this.layersToSnap.removeIf((FLyrVect t) -> !layers.contains(t));
1588
        this.layersToSnap.addAll(layers);
1589

    
1590
    }
1591

    
1592
    @Override
1593
    public void update(Observable observable, Object notification) {
1594
        // TODO REVISAR ESTO!!!
1595
        String ntype = null;
1596
        if (notification instanceof FeatureStoreNotification) {
1597
            FeatureStoreNotification fsNotification = (FeatureStoreNotification) notification;
1598
            ntype = fsNotification.getType();
1599
            if (ntype.equals(FeatureStoreNotification.LOAD_FINISHED)
1600
                    || ntype.equals(FeatureStoreNotification.SELECTION_CHANGE)) {
1601
                getLayers().moveTo(0, 0);
1602
            }
1603
        }
1604
    }
1605

    
1606
    public long getDrawVersion() {
1607
        if (getViewPort().getDrawVersion() > this.viewPortVersion
1608
                || getLayers().getDrawVersion() > this.layersVersion
1609
                || getGraphicsLayer().getDrawVersion() > graphicsLayerVersion) {
1610
            updateDrawVersion();
1611
        }
1612
        return this.drawVersion;
1613
    }
1614

    
1615
    protected void updateDrawVersion() {
1616
        this.layersVersion = getLayers().getDrawVersion();
1617
        this.viewPortVersion = getViewPort().getDrawVersion();
1618
        this.graphicsLayerVersion = getGraphicsLayer().getDrawVersion();
1619
        this.drawVersion++;
1620
    }
1621

    
1622
    public MapContextDrawer getMapContextDrawer() throws ReadException,
1623
            MapContextException {
1624
        if (this.mapContextDrawer == null) {
1625
            if (mapContextDrawerClass == null) {
1626
                this.mapContextDrawer = MAPCONTEXT_MANAGER
1627
                        .createDefaultMapContextDrawerInstance();
1628
            } else {
1629
                this.mapContextDrawer = MAPCONTEXT_MANAGER
1630
                        .createMapContextDrawerInstance(mapContextDrawerClass);
1631
            }
1632
            this.mapContextDrawer.setMapContext(this);
1633
            this.mapContextDrawer.setViewPort(viewPort);
1634
        }
1635

    
1636
        return this.mapContextDrawer;
1637
    }
1638

    
1639
    public void setMapContextDrawerClass(Class mapContextDrawerClass)
1640
            throws MapContextException {
1641
        MAPCONTEXT_MANAGER.validateMapContextDrawer(mapContextDrawerClass);
1642
        this.mapContextDrawerClass = mapContextDrawerClass;
1643
        if (this.mapContextDrawer != null) {
1644
            this.mapContextDrawer.dispose();
1645
            this.mapContextDrawer = null;
1646
        }
1647
    }
1648

    
1649
    public void setMapContextDrawer(MapContextDrawer drawer) {
1650
        if (this.mapContextDrawer != null) {
1651
            this.mapContextDrawer.dispose();
1652
            this.mapContextDrawer = null;
1653
        }
1654
        this.mapContextDrawer = drawer;
1655
        if (this.mapContextDrawer != null) {
1656
            this.mapContextDrawer.setMapContext(this);
1657
            this.mapContextDrawer.setViewPort(viewPort);
1658
        }
1659
    }
1660

    
1661
    @Override
1662
    public void loadFromState(PersistentState state)
1663
            throws PersistenceException {
1664

    
1665
        ViewPort vp = (ViewPort) state.get("ViewPort");
1666
        setViewPort(vp);
1667
        
1668
        layers = (FLayers) state.get("layers");
1669
        layers.setName("root layer");
1670
        loadLayers(layers);
1671
        layers.setMapContext(this);
1672

    
1673
        layerEventListener = new LayerEventListener();
1674
        layers.addLayerCollectionListener(layerEventListener);
1675

    
1676
        layers.addLayerCollectionListener(eventBuffer);
1677
        layers.setProjection(vp.getProjection());
1678

    
1679
        //Add the listener for the selection
1680
        addSelectionListener(layers);
1681

    
1682
        // ======================
1683
        if (state.hasValue("orderManager")) {
1684
            LayerOrderManager lom = (LayerOrderManager) state.get("orderManager");
1685
            this.setOrderManager(lom);
1686
        }
1687
        DefaultMapContextManager manager = (DefaultMapContextManager) MapContextLocator.getMapContextManager();
1688
        manager.notifyLoadMapContext(this);
1689
    }
1690

    
1691
    private void loadLayers(FLayers lyrs) {
1692

    
1693
        int sz = lyrs.getLayersCount();
1694
        for (int i = 0; i < sz; i++) {
1695
            try {
1696
                lyrs.getLayer(i).load();
1697
            } catch (LoadLayerException e) {
1698
                LOGGER.error("While loading layer: " + lyrs.getLayer(i).getName());
1699
            }
1700
        }
1701
    }
1702

    
1703
    @Override
1704
    public void saveToState(PersistentState state) throws PersistenceException {
1705
        state.set("ViewPort", viewPort);
1706
        state.set("layers", (Persistent)layers);
1707
        state.set("orderManager", this.getOrderManager());
1708
    }
1709

    
1710
    public static class RegisterPersistence implements Callable {
1711

    
1712
        @Override
1713
        public Object call() {
1714
            PersistenceManager manager = ToolsLocator.getPersistenceManager();
1715
            DynStruct definition = manager.addDefinition(
1716
                    MapContext.class,
1717
                    "MapContext",
1718
                    "MapContext Persistence definition",
1719
                    null,
1720
                    null
1721
            );
1722
            definition.addDynFieldObject("ViewPort")
1723
                    .setClassOfValue(ViewPort.class)
1724
                    .setMandatory(true);
1725

    
1726
            definition.addDynFieldObject("layers")
1727
                    .setClassOfValue(FLayers.class)
1728
                    .setMandatory(true);
1729

    
1730
            definition.addDynFieldObject("orderManager")
1731
                    .setClassOfValue(LayerOrderManager.class)
1732
                    .setMandatory(false);
1733

    
1734
            return Boolean.TRUE;
1735
        }
1736
    }
1737

    
1738
    @Override
1739
    protected void doDispose() throws BaseException {
1740
        dispose(layers);
1741
        for (VectorLayer tracLayer : this.tracLayers.values()) {
1742
            dispose(tracLayer);
1743
        }
1744
    }
1745

    
1746
    /**
1747
     * <p>
1748
     * Registers an event buffer as a listener for all layers as argument.</p>
1749
     *
1750
     * <p>
1751
     * Each {@link FLayer FLayer} of this map must have an event buffer for all
1752
     * kind of specific listeners of that layer. This method distinguish between {@link Classifiable Classifiable},
1753
     * {@link AlphanumericData AlphanumericData}, and {@link FLayers FLayers}
1754
     * layers, and for each one, registers, for their specific listeners, the
1755
     * <code>eventBuffer</code> as a listener.</p>
1756
     *
1757
     * @param the layer or layers
1758
     */
1759
    public void addSelectionListener(FLayer lyr) {
1760
        lyr.addLayerListener(eventBuffer);
1761

    
1762
        if (lyr instanceof Classifiable) {
1763
            Classifiable c = (Classifiable) lyr;
1764
            c.addLegendListener(eventBuffer);
1765
        }
1766

    
1767
        if (lyr instanceof FLayers) {
1768
            FLayers lyrs = (FLayers) lyr;
1769
            for (int i = 0; i < lyrs.getLayersCount(); i++) {
1770
                addSelectionListener(lyrs.getLayer(i));
1771
            }
1772
        }
1773
        if (lyr instanceof SingleLayer) {
1774
            if (((SingleLayer) lyr).getDataStore() != null) {
1775
                ((SingleLayer) lyr).getDataStore().addObserver(
1776
                        MapContext.this);
1777
            }
1778
        }
1779
    }
1780

    
1781
    public void setOrderManager(LayerOrderManager lom) {
1782
        orderManager = lom;
1783
    }
1784

    
1785
    public LayerOrderManager getOrderManager() {
1786

    
1787
        if (orderManager == null) {
1788
            orderManager = MapContextLocator.getDefaultOrderManager();
1789
        }
1790
        return orderManager;
1791
    }
1792

    
1793
    public boolean hasVectorLayers() {
1794
        return this.hasVectorLayers(this.getLayers());
1795
    }
1796

    
1797
    public boolean hasActiveVectorLayers() {
1798
        FLayer[] layers = this.getLayers().getActives();
1799
        for (FLayer layer : layers) {
1800
            if (layer.isAvailable() && layer instanceof FLyrVect) {
1801
                return true;
1802
            }
1803
        }
1804
        return false;
1805
    }
1806

    
1807
    public boolean hasActiveLayers() {
1808
        FLayer[] layers = this.getLayers().getActives();
1809
        return !ArrayUtils.isEmpty(layers);
1810
    }
1811

    
1812
    public boolean hasLayers() {
1813
        return !this.getLayers().isEmpty();
1814
    }
1815
    
1816
    private boolean hasVectorLayers(FLayers layers) {
1817
        for (int i = 0; i < layers.getLayersCount(); i++) {
1818
            FLayer lyr = layers.getLayer(i);
1819
            if (lyr instanceof FLayers) {
1820
                if (hasVectorLayers((FLayers) lyr)) {
1821
                    return true;
1822
                }
1823
            } else if (lyr instanceof FLyrVect) {
1824
                return true;
1825
            }
1826
        }
1827
        return false;
1828
    }
1829
    
1830
    @Override
1831
    public Iterator<FLayer> iterator() {
1832
        return this.layers.iterator();
1833
    }
1834

    
1835
    public Iterator deepiterator() {
1836
        return this.layers.deepiterator();
1837
    }
1838

    
1839
    public MapTimeContext getTimeContext() {
1840
        TimeSupportManager timeSupportManager = TimeSupportLocator.getManager();
1841
        Interval interval = null;
1842
        final List<Instant> times = new ArrayList<>();
1843
        Instant minInstant = null;
1844
        Instant maxInstant = null;
1845

    
1846
        //TODO Separate absolute and relative time
1847
        FLayers layers = this.getLayers();
1848
        for( Iterator<FLayer> iterator = layers.deepiterator(); iterator.hasNext(); ) {
1849
            FLayer layer = iterator.next();
1850
            if( layer instanceof SingleLayer ) {
1851
                DataStore dataStore = ((SingleLayer) layer).getDataStore();
1852
                if( dataStore.getInterval() != null ) {
1853
                    Instant startInstant = dataStore.getInterval().getStart();
1854
                    Instant endInstant = dataStore.getInterval().getEnd();
1855
                    times.addAll(dataStore.getTimes());
1856
                    if( minInstant == null ) {
1857
                        minInstant = startInstant;
1858
                        maxInstant = endInstant;
1859
                    } else {
1860
                        if( minInstant.isAfter(startInstant) ) {
1861
                            minInstant = startInstant;
1862
                        }
1863
                        if( maxInstant.isBefore(endInstant) ) {
1864
                            maxInstant = endInstant;
1865
                        }
1866
                    }
1867
                }
1868
            }
1869
        }
1870

    
1871
        if( minInstant != null ) {
1872
            if( minInstant.isAbsolute() ) {
1873
                try {
1874
                    interval = timeSupportManager.createAbsoluteInterval((AbsoluteInstant) minInstant, (AbsoluteInstant) maxInstant);
1875
                } catch (AbsoluteIntervalTypeNotRegisteredException e) {
1876
                    LOGGER.warn("Error creating the time interval", e);
1877
                }
1878
            } else {
1879
                interval = timeSupportManager.createRelativeInterval(((RelativeInstant) minInstant).toMillis(), ((RelativeInstant) maxInstant).toMillis());
1880
            }
1881
        }
1882
        final Interval tmp_interval = interval;
1883
        return new MapTimeContext() {
1884
            @Override
1885
            public Interval getInterval() {
1886
                return tmp_interval;
1887
            }
1888

    
1889
            @Override
1890
            public List<Instant> getTimes() {
1891
                return times;
1892
            }
1893
        };
1894
    }
1895

    
1896
    public long getScaleToUseWhenEnvelopeCollapse() {
1897
        return this.scaleToUseWhenEnvelopeCollapse;
1898
    }
1899

    
1900
    public void setScaleToUseWhenEnvelopeCollapse(long scale) {
1901
        this.scaleToUseWhenEnvelopeCollapse = scale;
1902
    }
1903
    
1904
    public void setPrintGraphicsLayer(boolean b){
1905
        this.printGraphicsLayer = b;
1906
    }
1907

    
1908
    public boolean isPrintGraphicsLayer(){
1909
        return this.printGraphicsLayer;
1910
    }
1911
    
1912
}