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

History | View | Annotate | Download (64.5 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 java.util.function.Predicate;
39
import org.apache.commons.lang3.ArrayUtils;
40

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

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

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

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

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

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

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

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

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

    
168
    }
169

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
880
    }
881

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

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

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

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

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

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

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

    
950
    }
951

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

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

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

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

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

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

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

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

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

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

    
1072
    public Envelope getSelectionBounds() throws BaseException {
1073

    
1074
        SelectedEnvelopeVisitor visitor = new SelectedEnvelopeVisitor();
1075

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1384
            lyr.removeLayerListener(eventBuffer);
1385

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

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

    
1396
        }
1397

    
1398
    }
1399

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

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

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

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

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

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

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

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

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

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

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

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

    
1573
    /**
1574
     * Adds the layers to the current "layersToSnap" without 
1575
     * replacing the current instance of "layersToSnap"
1576
     * 
1577
     * @param layers 
1578
     */
1579
    public void setLayersToSnap(Set<FLyrVect> layers) {
1580
        this.layersToSnap.removeIf((FLyrVect t) -> !layers.contains(t));
1581
        this.layersToSnap.addAll(layers);
1582

    
1583
    }
1584

    
1585
    public void update(Observable observable, Object notification) {
1586
        // TODO REVISAR ESTO!!!
1587
        String ntype = null;
1588
        if (notification instanceof FeatureStoreNotification) {
1589
            FeatureStoreNotification fsNotification = (FeatureStoreNotification) notification;
1590
            ntype = fsNotification.getType();
1591
            if (ntype.equals(FeatureStoreNotification.LOAD_FINISHED)
1592
                    || ntype.equals(FeatureStoreNotification.SELECTION_CHANGE)) {
1593
                getLayers().moveTo(0, 0);
1594
            }
1595
        }
1596
    }
1597

    
1598
    public long getDrawVersion() {
1599
        if (getViewPort().getDrawVersion() > this.viewPortVersion
1600
                || getLayers().getDrawVersion() > this.layersVersion
1601
                || getGraphicsLayer().getDrawVersion() > graphicsLayerVersion) {
1602
            updateDrawVersion();
1603
        }
1604
        return this.drawVersion;
1605
    }
1606

    
1607
    protected void updateDrawVersion() {
1608
        this.layersVersion = getLayers().getDrawVersion();
1609
        this.viewPortVersion = getViewPort().getDrawVersion();
1610
        this.graphicsLayerVersion = getGraphicsLayer().getDrawVersion();
1611
        this.drawVersion++;
1612
    }
1613

    
1614
    public MapContextDrawer getMapContextDrawer() throws ReadException,
1615
            MapContextException {
1616
        if (this.mapContextDrawer == null) {
1617
            if (mapContextDrawerClass == null) {
1618
                this.mapContextDrawer = MAPCONTEXT_MANAGER
1619
                        .createDefaultMapContextDrawerInstance();
1620
            } else {
1621
                this.mapContextDrawer = MAPCONTEXT_MANAGER
1622
                        .createMapContextDrawerInstance(mapContextDrawerClass);
1623
            }
1624
            this.mapContextDrawer.setMapContext(this);
1625
            this.mapContextDrawer.setViewPort(viewPort);
1626
        }
1627

    
1628
        return this.mapContextDrawer;
1629
    }
1630

    
1631
    public void setMapContextDrawerClass(Class mapContextDrawerClass)
1632
            throws MapContextException {
1633
        MAPCONTEXT_MANAGER.validateMapContextDrawer(mapContextDrawerClass);
1634
        this.mapContextDrawerClass = mapContextDrawerClass;
1635
        if (this.mapContextDrawer != null) {
1636
            this.mapContextDrawer.dispose();
1637
            this.mapContextDrawer = null;
1638
        }
1639
    }
1640

    
1641
    public void setMapContextDrawer(MapContextDrawer drawer) {
1642
        if (this.mapContextDrawer != null) {
1643
            this.mapContextDrawer.dispose();
1644
            this.mapContextDrawer = null;
1645
        }
1646
        this.mapContextDrawer = drawer;
1647
        if (this.mapContextDrawer != null) {
1648
            this.mapContextDrawer.setMapContext(this);
1649
            this.mapContextDrawer.setViewPort(viewPort);
1650
        }
1651
    }
1652

    
1653
    public void loadFromState(PersistentState state)
1654
            throws PersistenceException {
1655

    
1656
        ViewPort vp = (ViewPort) state.get("ViewPort");
1657
        setViewPort(vp);
1658

    
1659
        layers = (FLayers) state.get("layers");
1660
        layers.setName("root layer");
1661
        loadLayers(layers);
1662
        layers.setMapContext(this);
1663

    
1664
        layerEventListener = new LayerEventListener();
1665
        layers.addLayerCollectionListener(layerEventListener);
1666

    
1667
        layers.addLayerCollectionListener(eventBuffer);
1668
        layers.setProjection(vp.getProjection());
1669

    
1670
        //Add the listener for the selection
1671
        addSelectionListener(layers);
1672

    
1673
        // ======================
1674
        if (state.hasValue("orderManager")) {
1675
            LayerOrderManager lom = (LayerOrderManager) state.get("orderManager");
1676
            this.setOrderManager(lom);
1677
        }
1678
        DefaultMapContextManager manager = (DefaultMapContextManager) MapContextLocator.getMapContextManager();
1679
        manager.notifyLoadMapContext(this);
1680
    }
1681

    
1682
    private void loadLayers(FLayers lyrs) {
1683

    
1684
        int sz = lyrs.getLayersCount();
1685
        for (int i = 0; i < sz; i++) {
1686
            try {
1687
                lyrs.getLayer(i).load();
1688
            } catch (LoadLayerException e) {
1689
                LOGGER.error("While loading layer: " + lyrs.getLayer(i).getName());
1690
            }
1691
        }
1692
    }
1693

    
1694
    public void saveToState(PersistentState state) throws PersistenceException {
1695
        state.set("ViewPort", viewPort);
1696
        state.set("layers", (Persistent)layers);
1697
        state.set("orderManager", this.getOrderManager());
1698
    }
1699

    
1700
    public static class RegisterPersistence implements Callable {
1701

    
1702
        public Object call() {
1703
            PersistenceManager manager = ToolsLocator.getPersistenceManager();
1704
            DynStruct definition = manager.addDefinition(
1705
                    MapContext.class,
1706
                    "MapContext",
1707
                    "MapContext Persistence definition",
1708
                    null,
1709
                    null
1710
            );
1711
            definition.addDynFieldObject("ViewPort")
1712
                    .setClassOfValue(ViewPort.class)
1713
                    .setMandatory(true);
1714

    
1715
            definition.addDynFieldObject("layers")
1716
                    .setClassOfValue(FLayers.class)
1717
                    .setMandatory(true);
1718

    
1719
            definition.addDynFieldObject("orderManager")
1720
                    .setClassOfValue(LayerOrderManager.class)
1721
                    .setMandatory(false);
1722

    
1723
            return Boolean.TRUE;
1724
        }
1725
    }
1726

    
1727
    protected void doDispose() throws BaseException {
1728
        dispose(layers);
1729
        for (VectorLayer tracLayer : this.tracLayers.values()) {
1730
            dispose(tracLayer);
1731
        }
1732
    }
1733

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

    
1750
        if (lyr instanceof Classifiable) {
1751
            Classifiable c = (Classifiable) lyr;
1752
            c.addLegendListener(eventBuffer);
1753
        }
1754

    
1755
        if (lyr instanceof FLayers) {
1756
            FLayers lyrs = (FLayers) lyr;
1757
            for (int i = 0; i < lyrs.getLayersCount(); i++) {
1758
                addSelectionListener(lyrs.getLayer(i));
1759
            }
1760
        }
1761
        if (lyr instanceof SingleLayer) {
1762
            if (((SingleLayer) lyr).getDataStore() != null) {
1763
                ((SingleLayer) lyr).getDataStore().addObserver(
1764
                        MapContext.this);
1765
            }
1766
        }
1767
    }
1768

    
1769
    public void setOrderManager(LayerOrderManager lom) {
1770
        orderManager = lom;
1771
    }
1772

    
1773
    public LayerOrderManager getOrderManager() {
1774

    
1775
        if (orderManager == null) {
1776
            orderManager = MapContextLocator.getDefaultOrderManager();
1777
        }
1778
        return orderManager;
1779
    }
1780

    
1781
    public boolean hasVectorLayers() {
1782
        return this.hasVectorLayers(this.getLayers());
1783
    }
1784

    
1785
    public boolean hasActiveVectorLayers() {
1786
        FLayer[] layers = this.getLayers().getActives();
1787
        for (int i = 0; i < layers.length; i++) {
1788
            FLayer layer = layers[i];
1789
            if (layer.isAvailable() && layer instanceof FLyrVect) {
1790
                return true;
1791
            }
1792
        }
1793
        return false;
1794
    }
1795

    
1796
    public boolean hasActiveLayers() {
1797
        FLayer[] layers = this.getLayers().getActives();
1798
        return !ArrayUtils.isEmpty(layers);
1799
    }
1800

    
1801
    public boolean hasLayers() {
1802
        return !this.getLayers().isEmpty();
1803
    }
1804
    
1805
    private boolean hasVectorLayers(FLayers layers) {
1806
        for (int i = 0; i < layers.getLayersCount(); i++) {
1807
            FLayer lyr = layers.getLayer(i);
1808
            if (lyr instanceof FLayers) {
1809
                if (hasVectorLayers((FLayers) lyr)) {
1810
                    return true;
1811
                }
1812
            } else if (lyr instanceof FLyrVect) {
1813
                return true;
1814
            }
1815
        }
1816
        return false;
1817
    }
1818
    
1819
    @Override
1820
    public Iterator<FLayer> iterator() {
1821
        return this.layers.iterator();
1822
    }
1823

    
1824
    public Iterator deepiterator() {
1825
        return this.layers.deepiterator();
1826
    }
1827

    
1828
    public MapTimeContext getTimeContext() {
1829
        TimeSupportManager timeSupportManager = TimeSupportLocator.getManager();
1830
        Interval interval = null;
1831
        final List<Instant> times = new ArrayList<>();
1832
        Instant minInstant = null;
1833
        Instant maxInstant = null;
1834

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

    
1860
        if( minInstant != null ) {
1861
            if( minInstant.isAbsolute() ) {
1862
                try {
1863
                    interval = timeSupportManager.createAbsoluteInterval((AbsoluteInstant) minInstant, (AbsoluteInstant) maxInstant);
1864
                } catch (AbsoluteIntervalTypeNotRegisteredException e) {
1865
                    LOGGER.warn("Error creating the time interval", e);
1866
                }
1867
            } else {
1868
                interval = timeSupportManager.createRelativeInterval(((RelativeInstant) minInstant).toMillis(), ((RelativeInstant) maxInstant).toMillis());
1869
            }
1870
        }
1871
        final Interval tmp_interval = interval;
1872
        return new MapTimeContext() {
1873
            @Override
1874
            public Interval getInterval() {
1875
                return tmp_interval;
1876
            }
1877

    
1878
            @Override
1879
            public List<Instant> getTimes() {
1880
                return times;
1881
            }
1882
        };
1883
    }
1884

    
1885
    public long getScaleToUseWhenEnvelopeCollapse() {
1886
        return this.scaleToUseWhenEnvelopeCollapse;
1887
    }
1888

    
1889
    public void setScaleToUseWhenEnvelopeCollapse(long scale) {
1890
        this.scaleToUseWhenEnvelopeCollapse = scale;
1891
    }
1892
    
1893
    public void setPrintGraphicsLayer(boolean b){
1894
        this.printGraphicsLayer = b;
1895
    }
1896

    
1897
    public boolean isPrintGraphicsLayer(){
1898
        return this.printGraphicsLayer;
1899
    }
1900
    
1901
}