Statistics
| Revision:

svn-gvsig-desktop / trunk / org.gvsig.desktop / org.gvsig.desktop.library / org.gvsig.symbology / org.gvsig.symbology.lib / org.gvsig.symbology.lib.impl / src / main / java / org / gvsig / symbology / fmap / mapcontext / rendering / legend / impl / AbstractVectorialLegend.java @ 41828

History | View | Annotate | Download (34.8 KB)

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

    
30
import java.awt.Graphics2D;
31
import java.awt.geom.Point2D;
32
import java.awt.image.BufferedImage;
33
import java.util.Iterator;
34
import java.util.Map;
35
import java.util.Map.Entry;
36

    
37
import org.cresques.cts.ICoordTrans;
38
import org.cresques.cts.IProjection;
39
import org.slf4j.Logger;
40
import org.slf4j.LoggerFactory;
41

    
42
import org.gvsig.compat.CompatLocator;
43
import org.gvsig.compat.print.PrintAttributes;
44
import org.gvsig.fmap.dal.exception.DataException;
45
import org.gvsig.fmap.dal.exception.ReadException;
46
import org.gvsig.fmap.dal.feature.Feature;
47
import org.gvsig.fmap.dal.feature.FeatureQuery;
48
import org.gvsig.fmap.dal.feature.FeatureSelection;
49
import org.gvsig.fmap.dal.feature.FeatureSet;
50
import org.gvsig.fmap.dal.feature.FeatureStore;
51
import org.gvsig.fmap.dal.feature.FeatureType;
52
import org.gvsig.fmap.dal.feature.exception.ConcurrentDataModificationException;
53
import org.gvsig.fmap.geom.Geometry;
54
import org.gvsig.fmap.geom.GeometryLocator;
55
import org.gvsig.fmap.geom.GeometryManager;
56
import org.gvsig.fmap.geom.aggregate.Aggregate;
57
import org.gvsig.fmap.geom.exception.CreateGeometryException;
58
import org.gvsig.fmap.geom.exception.ReprojectionRuntimeException;
59
import org.gvsig.fmap.geom.operation.DrawInts;
60
import org.gvsig.fmap.geom.operation.DrawOperationContext;
61
import org.gvsig.fmap.geom.operation.GeometryOperationException;
62
import org.gvsig.fmap.geom.operation.GeometryOperationNotSupportedException;
63
import org.gvsig.fmap.geom.primitive.Envelope;
64
import org.gvsig.fmap.mapcontext.MapContext;
65
import org.gvsig.fmap.mapcontext.MapContextException;
66
import org.gvsig.fmap.mapcontext.ViewPort;
67
import org.gvsig.fmap.mapcontext.layers.vectorial.IntersectsEnvelopeEvaluator;
68
import org.gvsig.fmap.mapcontext.rendering.legend.ILegend;
69
import org.gvsig.fmap.mapcontext.rendering.legend.IVectorLegend;
70
import org.gvsig.fmap.mapcontext.rendering.legend.LegendException;
71
import org.gvsig.fmap.mapcontext.rendering.legend.ZSort;
72
import org.gvsig.fmap.mapcontext.rendering.symbols.CartographicSupport;
73
import org.gvsig.fmap.mapcontext.rendering.symbols.IMultiLayerSymbol;
74
import org.gvsig.fmap.mapcontext.rendering.symbols.ISymbol;
75
import org.gvsig.tools.ToolsLocator;
76
import org.gvsig.tools.dispose.DisposableIterator;
77
import org.gvsig.tools.dynobject.DynStruct;
78
import org.gvsig.tools.exception.BaseException;
79
import org.gvsig.tools.persistence.PersistenceManager;
80
import org.gvsig.tools.persistence.PersistentState;
81
import org.gvsig.tools.persistence.exception.PersistenceException;
82
import org.gvsig.tools.task.Cancellable;
83
import org.gvsig.tools.task.SimpleTaskStatus;
84
import org.gvsig.tools.util.Callable;
85
import org.gvsig.tools.visitor.VisitCanceledException;
86
import org.gvsig.tools.visitor.Visitor;
87

    
88
/**
89
 * Base implementation for Vectorial data Legends.
90
 * 
91
 * Provides a draw method implementation which loads the {@link Feature}s and
92
 * uses the {@link ISymbol} objects to draw the {@link Geometry} objects.
93
 * 
94
 * @author 2009- <a href="cordinyana@gvsig.org">C?sar Ordi?ana</a> - gvSIG team
95
 */
96
public abstract class AbstractVectorialLegend extends AbstractLegend implements
97
IVectorLegend {
98
        
99
        private static final Logger LOG = LoggerFactory
100
                        .getLogger(AbstractVectorialLegend.class);
101

    
102
    private static final int DRAW_MAX_ATTEMPTS = 5;
103

    
104
        public static final String VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME = "VectorialLegend";
105

    
106
    private static final String FIELD_HAS_ZSORT = "hasZSort";
107
    private static final String FIELD_SHAPETYPE = "shapeType";
108
    private static final String FIELD_DEFAULT_SYMBOL = "defaultSymbol";
109

    
110
    private static final GeometryManager geomManager = GeometryLocator
111
    .getGeometryManager();
112

    
113
    protected ZSort zSort;
114

    
115
    public ZSort getZSort() {
116
        return zSort;
117
    }
118

    
119
    public void setZSort(ZSort zSort) {
120
        if (zSort == null) {
121
            removeLegendListener(this.zSort);
122
        }
123
        this.zSort = zSort;
124
        addLegendListener(zSort);
125
    }
126
    
127
    @SuppressWarnings("unchecked")
128
    public void draw(BufferedImage image, Graphics2D g, ViewPort viewPort,
129
        Cancellable cancel, double scale, Map queryParameters,
130
        ICoordTrans coordTrans, FeatureStore featureStore)
131
    throws LegendException {
132
        double dpi = viewPort.getDPI();
133
        draw(image, g, viewPort, cancel, scale, queryParameters, coordTrans,
134
            featureStore, null, dpi);
135
    }
136
    
137
    @SuppressWarnings("unchecked")
138
    public void draw(BufferedImage image, Graphics2D g, ViewPort viewPort,
139
        Cancellable cancel, double scale, Map queryParameters,
140
        ICoordTrans coordTrans, FeatureStore featureStore, FeatureQuery featureQuery)
141
    throws LegendException {
142
        double dpi = viewPort.getDPI();
143
        draw(image, g, viewPort, cancel, scale, queryParameters, coordTrans,
144
            featureStore, featureQuery, dpi);
145
    }
146

    
147
    @SuppressWarnings("unchecked")
148
    public void print(Graphics2D g, ViewPort viewPort, Cancellable cancel,
149
        double scale, Map queryParameters, ICoordTrans coordTrans,
150
        FeatureStore featureStore, PrintAttributes properties)
151
    throws LegendException {
152
        print(g, viewPort, cancel, scale, queryParameters, coordTrans,
153
            featureStore, null, properties);
154
    }
155
    
156
    @SuppressWarnings("unchecked")
157
    public void print(Graphics2D g, ViewPort viewPort, Cancellable cancel,
158
        double scale, Map queryParameters, ICoordTrans coordTrans,
159
        FeatureStore featureStore, FeatureQuery fquery, PrintAttributes properties)
160
    throws LegendException {
161
        double dpi = 72;
162

    
163
        int resolution = properties.getPrintQuality();
164

    
165
        if (resolution == PrintAttributes.PRINT_QUALITY_DRAFT
166
            || resolution == PrintAttributes.PRINT_QUALITY_NORMAL
167
            || resolution == PrintAttributes.PRINT_QUALITY_HIGH) {
168
            dpi = PrintAttributes.PRINT_QUALITY_DPI[resolution];
169
        }
170

    
171
        FeatureSet featureSet = null;
172
        DisposableIterator it = null;
173
        try {
174
            ZSort zSort = getZSort();
175

    
176
            // if layer has map levels it will use a ZSort
177
            boolean useZSort = zSort != null && zSort.isUsingZSort();
178

    
179
            int mapLevelCount = (useZSort) ? zSort.getLevelCount() : 1;
180
            for (int mapPass = 0; mapPass < mapLevelCount; mapPass++) {
181
                
182
                Envelope vp_env_in_store_crs = null;
183
                IProjection store_crs = null;
184
                if (coordTrans != null) {
185
                    // 'coordTrans' is from store crs to vp crs
186
                    ICoordTrans inv = coordTrans.getInverted();
187
                    Envelope aux = viewPort.getAdjustedEnvelope();
188
                    vp_env_in_store_crs = aux.convert(inv);
189
                    store_crs = coordTrans.getPOrig();
190
                } else {
191
                    vp_env_in_store_crs = viewPort.getAdjustedEnvelope();
192
                    store_crs = viewPort.getProjection();
193
                }
194
                
195
                FeatureQuery feat_query = fquery;
196
                Envelope store_env = featureStore.getEnvelope();
197
                boolean use_intersection_cond = false;
198
                if (store_env == null) {
199
                    // Store does not know its envelope, so we must:
200
                    use_intersection_cond = true;
201
                } else {
202
                    if (vp_env_in_store_crs.contains(store_env)) {
203
                        use_intersection_cond = false;
204
                    } else {
205
                        use_intersection_cond = true;
206
                    }
207
                }
208
                
209
                if (use_intersection_cond) {
210
                    FeatureType ft = featureStore.getDefaultFeatureType(); 
211
                    IntersectsEnvelopeEvaluator iee =
212
                        new IntersectsEnvelopeEvaluator(
213
                            vp_env_in_store_crs,
214
                            store_crs,
215
                            ft, ft.getDefaultGeometryAttributeName());
216
                    if (feat_query == null) {
217
                        feat_query = featureStore.createFeatureQuery();
218
                        String[] fns = getRequiredFeatureAttributeNames(featureStore);
219
                        feat_query.setAttributeNames(fns);
220
                    }
221
                    feat_query.addFilter(iee);
222
                }
223
                
224
                // 'feat_query' can still be NULL here, so we only filter
225
                // the featureStore if it's not null
226
                if (feat_query == null) {
227
                    featureSet = featureStore.getFeatureSet();
228
                } else {
229
                    featureSet = featureStore.getFeatureSet(feat_query);
230
                }
231
                it = featureSet.fastIterator();
232
                // Iteration over each feature
233
                while (!cancel.isCanceled() && it.hasNext()) {
234
                    Feature feat = (Feature) it.next();
235
                    Geometry geom = feat.getDefaultGeometry();
236
                    if (geom==null) {
237
                            continue;
238
                    }                    
239
                    // Reprojection if needed
240
                    if (coordTrans != null) {
241
                        geom = geom.cloneGeometry();
242
                        geom.reProject(coordTrans);
243
                    }
244

    
245
                    // retrieve the symbol associated to such feature
246
                    ISymbol sym = getSymbolByFeature(feat);
247
                    if (sym == null) {
248
                        continue;
249
                    }
250
                    if (useZSort) {
251
                        int[] symLevels = zSort.getLevels(sym);
252
                        if (symLevels != null) {
253

    
254
                            // Check if this symbol is a multilayer
255
                            if (sym instanceof IMultiLayerSymbol) {
256
                                // if so, get the layer corresponding to the
257
                                // current level. If none, continue to next
258
                                // iteration
259
                                IMultiLayerSymbol mlSym = (IMultiLayerSymbol) sym;
260
                                for (int i = 0; i < mlSym.getLayerCount(); i++) {
261
                                    ISymbol mySym = mlSym.getLayer(i);
262
                                    if (symLevels[i] == mapPass) {
263
                                        sym = mySym;
264
                                        break;
265
                                    }
266
                                }
267

    
268
                            } else {
269
                                // else, just draw the symbol in its level
270
                                if (symLevels[0] != mapPass) {
271
                                    continue;
272
                                }
273
                            }
274
                        }
275
                    }
276

    
277
                    // Check if this symbol is sized with CartographicSupport
278
                    CartographicSupport csSym = null;
279
                    int symbolType = sym.getSymbolType();
280

    
281
                    if (symbolType == Geometry.TYPES.POINT
282
                        || symbolType == Geometry.TYPES.CURVE
283
                        || sym instanceof CartographicSupport) {
284

    
285
                        csSym = (CartographicSupport) sym;
286
                    }
287

    
288
                    DrawOperationContext doc = new DrawOperationContext();
289
                    doc.setGraphics(g);
290
                    doc.setViewPort(viewPort);
291
                    if (csSym == null) {
292
                        doc.setSymbol(sym);
293
                    } else {
294
                        doc.setDPI(dpi);
295
                        doc.setCancellable(cancel);
296
                        doc.setSymbol((ISymbol) csSym);
297
                    }
298
                    geom.invokeOperation(DrawInts.CODE, doc);
299
                }
300
            }
301
        } catch (ReadException e) {
302
            throw new LegendDrawingException(e);
303
        } catch (GeometryOperationNotSupportedException e) {
304
            throw new LegendDrawingException(e);
305
        } catch (GeometryOperationException e) {
306
            throw new LegendDrawingException(e);
307
        } catch (DataException e) {
308
            throw new LegendDrawingException(e);
309
        } catch (MapContextException e) {
310
            throw new LegendDrawingException(e);
311
        } finally {
312
            if (it != null) {
313
                it.dispose();
314
            }
315
            if (featureSet != null) {
316
                featureSet.dispose();
317
            }
318
        }
319
    }
320

    
321
    /**
322
     * Draws the features from the {@link FeatureStore}, filtered with the scale
323
     * and the query parameters, with the symbols of the legend.
324
     */
325
    @SuppressWarnings("unchecked")
326
        protected void draw(BufferedImage image, Graphics2D g, ViewPort viewPort,
327
                        Cancellable cancel, double scale, Map queryParameters,
328
                        ICoordTrans coordTrans, FeatureStore featureStore,
329
                        FeatureQuery featureQuery, double dpi) throws LegendException {
330

    
331
                SimpleTaskStatus taskStatus = ToolsLocator.getTaskStatusManager()
332
                                .createDefaultSimpleTaskStatus(featureStore.getName());
333
                taskStatus.add();
334
                try {
335
                        // Avoid ConcurrentModificationException errors if
336
                        // while drawing another thread edits the store data.
337
                        synchronized (featureStore) {
338
                                internalDraw(image, g, viewPort, cancel, scale,
339
                                                queryParameters, coordTrans, featureStore,
340
                                                featureQuery, dpi, taskStatus);
341
                        }
342
                } finally {
343
                        if (taskStatus != null) {
344
                                taskStatus.terminate();
345
                                taskStatus.remove();
346
                                taskStatus = null;
347
                        }
348
                }
349
        }
350

    
351
        protected void internalDraw(BufferedImage image, Graphics2D g,
352
                        ViewPort viewPort, Cancellable cancel, double scale,
353
                        Map queryParameters, ICoordTrans coordTrans,
354
                        FeatureStore featureStore, FeatureQuery featureQuery, double dpi,
355
                        SimpleTaskStatus taskStatus) throws LegendDrawingException {
356

    
357
                if (!getDefaultSymbol().isShapeVisible()) {
358
                        return;
359
                }
360

    
361
                if (cancel.isCanceled()) {
362
                        return;
363
                }
364

    
365
                IProjection dataProjection;
366
                Envelope reprojectedDataEnvelop;
367

    
368
                try {
369
                        if (coordTrans == null) {
370
                                dataProjection = featureStore.getDefaultFeatureType()
371
                                                .getDefaultSRS();
372

    
373
                                // If the data does not provide a projection, use the
374
                                // current view one
375
                                if (dataProjection == null) {
376
                                        dataProjection = viewPort.getProjection();
377
                                }
378

    
379
                                reprojectedDataEnvelop = featureStore.getEnvelope();
380
                        } else {
381
                                dataProjection = coordTrans.getPOrig();
382

    
383
                                Envelope env = featureStore.getEnvelope();
384
                                if ( env!=null && !env.isEmpty()) {
385
                                        reprojectedDataEnvelop = env.convert(coordTrans);
386
                                } else {
387
                                        reprojectedDataEnvelop = env;
388
                                }
389
                        }
390
                } catch (DataException e) {
391
                        throw new LegendDrawingException(e);
392
                }
393

    
394
                // Gets the view envelope
395
                Envelope viewPortEnvelope = viewPort.getAdjustedEnvelope();
396

    
397
                // Gets the data envelope with the viewport SRS
398
                Envelope myEnvelope = reprojectedDataEnvelop;
399

    
400
                // TODO: in some cases, the legend may need a different check to
401
                // decide if the data must be drawn or not
402
                // Checks if the viewport envelope intersects with the data envelope
403
                if (!viewPortEnvelope.intersects(myEnvelope)) {
404
                        // The data is not visible in the current viewport, do nothing.
405
                        return;
406
                }
407

    
408
                // Check if all the data is contained into the viewport envelope
409
                boolean containsAll = viewPortEnvelope.contains(myEnvelope);
410

    
411
                // Create the drawing notification to be reused on each iteration
412
                DefaultFeatureDrawnNotification drawnNotification = new DefaultFeatureDrawnNotification();
413

    
414
                if (cancel.isCanceled()) {
415
                        return;
416
                }
417

    
418
                FeatureSet featureSet = null;
419
                try {
420
                        taskStatus.message("Retrieve selection");
421
                        FeatureSelection selection = featureStore.getFeatureSelection();
422

    
423
                        if (featureQuery == null) {
424
                                featureQuery = featureStore.createFeatureQuery();
425
                        }
426

    
427
                        completeQuery(featureStore, featureQuery, scale, queryParameters,
428
                                        coordTrans, dataProjection, viewPortEnvelope, containsAll);
429

    
430
                        taskStatus.message("Retrieve data");
431
                        featureSet = featureStore.getFeatureSet(featureQuery);
432

    
433
                        if (cancel.isCanceled()) {
434
                                return;
435
                        }
436

    
437
                        taskStatus.message("Drawing");
438
                        drawFeatures(image, g, viewPort, cancel, coordTrans, dpi,
439
                                        drawnNotification, featureSet, selection);
440

    
441
        } catch (RuntimeException e) {
442
            /*
443
             * Probably a reprojection exception (for example,
444
             * trying to reproject Canada to EPSG:23030)
445
             */
446
            throw new LegendDrawingException(e);
447
                } catch (BaseException e) {
448
                        throw new LegendDrawingException(e);
449
                } finally {
450
                        if (featureSet != null) {
451
                                featureSet.dispose();
452
                        }
453
                }
454
        }
455

    
456
    /**
457
     * Complete a {@link FeatureQuery} to load the {@link Feature}s to draw.
458
     */
459
    @SuppressWarnings("unchecked")
460
    private FeatureQuery completeQuery(FeatureStore featureStore, FeatureQuery featureQuery, double scale,
461
        Map queryParameters, ICoordTrans coordTrans,
462
        IProjection dataProjection, Envelope viewPortEnvelope,
463
        boolean containsAll) throws DataException {
464

    
465
        featureQuery.setScale(scale);
466
        
467
        //Adds the attributes
468
        String[] fieldNames = getRequiredFeatureAttributeNames(featureStore);
469
        for (int i=0 ; i<fieldNames.length ;i++){
470
            featureQuery.addAttributeName(fieldNames[i]);
471
        }       
472
        
473
        // TODO: Mobile has it's own IntersectsEnvelopeEvaluator
474
        if (!containsAll) {
475
            // Gets the viewport envelope with the data SRS
476
            Envelope viewPortEnvelopeInMyProj = viewPortEnvelope;
477
            // FIXME
478
            if (coordTrans != null) {
479
                viewPortEnvelopeInMyProj = viewPortEnvelope.convert(coordTrans
480
                        .getInverted());
481
            }
482

    
483
            if (dataProjection == null) {
484
                throw new IllegalArgumentException(
485
                "Error, the projection parameter value is null");
486
            }
487

    
488
            IntersectsEnvelopeEvaluator iee = new IntersectsEnvelopeEvaluator(
489
                viewPortEnvelopeInMyProj, dataProjection,
490
                featureStore.getDefaultFeatureType(), featureStore
491
                .getDefaultFeatureType()
492
                .getDefaultGeometryAttributeName());
493
            featureQuery.addFilter(iee);                        
494
        }
495
        if (queryParameters != null) {
496
            Iterator iterEntry = queryParameters.entrySet().iterator();
497
            Entry entry;
498
            while (iterEntry.hasNext()) {
499
                entry = (Entry) iterEntry.next();
500
                featureQuery.setQueryParameter((String) entry.getKey(),
501
                    entry.getValue());
502
            }
503
        }        
504
        return featureQuery;
505
    }
506

    
507
    /**
508
     * Draws the features from the {@link FeatureSet}, with the symbols of the
509
     * legend.
510
     */
511
    private void drawFeatures(BufferedImage image, Graphics2D g,
512
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
513
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
514
        FeatureSet featureSet, FeatureSelection selection)
515
    throws BaseException {
516
        if (isUseZSort()) {
517
            drawFeaturesMultiLayer(image, g, viewPort, cancel, coordTrans, dpi,
518
                drawnNotification, featureSet, selection);
519
        } else {
520
            drawFeaturesSingleLayer(image, g, viewPort, cancel, coordTrans,
521
                dpi, drawnNotification, featureSet, selection);
522
        }
523
    }
524

    
525
    /**
526
     * Draws the features from the {@link FeatureSet}, with the symbols of the
527
     * legend, using a single drawing layer.
528
     */
529
    private void drawFeaturesSingleLayer(final BufferedImage image,
530
        final Graphics2D g, final ViewPort viewPort,
531
        final Cancellable cancel, final ICoordTrans coordTrans,
532
        final double dpi,
533
        final DefaultFeatureDrawnNotification drawnNotification,
534
        FeatureSet featureSet, final FeatureSelection selection)
535
    throws BaseException {
536

    
537
        try {
538
            featureSet.accept(new Visitor() {
539
                public void visit(Object obj) throws VisitCanceledException,
540
                BaseException {
541
                    Feature feat = (Feature) obj;
542
                    drawFeatureSingleLayer(image, g, viewPort, cancel,
543
                        coordTrans, dpi, drawnNotification, feat, selection);
544
                }
545
            });
546

    
547
        } catch (ConcurrentDataModificationException e) {
548
            cancel.setCanceled(true);
549
            return;
550
        }
551
    }
552

    
553
    /**
554
     * Draws a Feature with the symbols of the legend, using a single drawing
555
     * layer.
556
     */
557
    private void drawFeatureSingleLayer(BufferedImage image, Graphics2D g,
558
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
559
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
560
        Feature feat, FeatureSelection selection)
561
    throws MapContextException, CreateGeometryException {
562

    
563
        Geometry geom = feat.getDefaultGeometry();
564
        if (geom == null) {
565
            return;
566
        }
567

    
568
        if (geom.getType() == Geometry.TYPES.NULL) {
569
            return;
570
        }
571

    
572
        ISymbol sym = getSymbol(feat, selection);
573
        if (sym == null) {
574
            return;
575
        }
576

    
577
        if (coordTrans != null) {
578
            geom = geom.cloneGeometry();
579
            try {
580
                geom.reProject(coordTrans);
581
            } catch (ReprojectionRuntimeException re) {
582
                /*
583
                 * Library was unable to reproject
584
                 * See reproject method in Point2D (geometry)
585
                 */
586
                throw new CreateGeometryException(
587
                    geom.getGeometryType().getType(),
588
                    geom.getGeometryType().getSubType(),
589
                    re);
590
            }
591
        }
592

    
593
        if (cancel.isCanceled()) {
594
            return;
595
        }
596

    
597
        drawGeometry(geom, image, feat, sym, viewPort, g, dpi, cancel);
598

    
599
        // Notify the drawing observers
600
        drawnNotification.setFeature(feat);
601
        drawnNotification.setDrawnGeometry(geom);
602
        notifyObservers(drawnNotification);
603
    }
604

    
605
    /**
606
     * Draws the features from the {@link FeatureSet}, with the symbols of the
607
     * legend, using a multiple drawing layer.
608
     */
609
    private void drawFeaturesMultiLayer(BufferedImage image, Graphics2D g,
610
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
611
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
612
        FeatureSet featureSet, FeatureSelection selection)
613
    throws MapContextException, CreateGeometryException, DataException {
614

    
615
        // -- visual FX stuff
616
        long time = System.currentTimeMillis();
617

    
618
        boolean bSymbolLevelError = false;
619
        // render temporary map each screenRefreshRate milliseconds;
620
        int screenRefreshDelay = (int) ((1D / MapContext.getDrawFrameRate()) * 3 * 1000);
621
        BufferedImage[] imageLevels = null;
622
        Graphics2D[] graphics = null;
623

    
624
        imageLevels = new BufferedImage[getZSort().getLevelCount()];
625
        graphics = new Graphics2D[imageLevels.length];
626
        for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
627

    
628
            imageLevels[i] = CompatLocator.getGraphicsUtils()
629
            .createBufferedImage(image.getWidth(), image.getHeight(),
630
                image.getType());
631

    
632
            graphics[i] = imageLevels[i].createGraphics();
633
            graphics[i].setTransform(g.getTransform());
634
            graphics[i].setRenderingHints(g.getRenderingHints());
635
        }
636
        // -- end visual FX stuff
637

    
638
        DisposableIterator it = null;
639
        try {
640
            it = featureSet.fastIterator();
641
            // Iteration over each feature
642
            while (it.hasNext()) {
643
                if (cancel.isCanceled()) {
644
                    return;
645
                }
646
                Feature feat = (Feature) it.next();
647

    
648
                bSymbolLevelError |= drawFeatureMultiLayer(image, g, viewPort,
649
                    cancel, coordTrans, dpi, drawnNotification, selection,
650
                    time, screenRefreshDelay, imageLevels, graphics, feat);
651

    
652
            }
653
        } catch (ConcurrentDataModificationException e) {
654
            cancel.setCanceled(true);
655
            return;
656
        } finally {
657
            if (it != null) {
658
                it.dispose();
659
            }
660
        }
661

    
662
        g.drawImage(image, 0, 0, null);
663

    
664
        Point2D offset = viewPort.getOffset();
665
        CompatLocator.getGraphicsUtils().translate(g, offset.getX(),
666
            offset.getY());
667

    
668
        for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
669
            g.drawImage(imageLevels[i], 0, 0, null);
670
            imageLevels[i] = null;
671
            graphics[i] = null;
672
        }
673

    
674
        CompatLocator.getGraphicsUtils().translate(g, -offset.getX(),
675
            -offset.getY());
676

    
677
        imageLevels = null;
678
        graphics = null;
679

    
680
        if (bSymbolLevelError) {
681
            setZSort(null);
682
        }
683
    }
684

    
685
    /**
686
     * Draws a Feature with the symbols of the legend, using a multiple drawing
687
     * layer.
688
     */
689
    private boolean drawFeatureMultiLayer(BufferedImage image, Graphics2D g,
690
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
691
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
692
        FeatureSelection selection, long time, int screenRefreshDelay,
693
        BufferedImage[] imageLevels, Graphics2D[] graphics, Feature feat)
694
    throws MapContextException, CreateGeometryException {
695

    
696
        Geometry geom = feat.getDefaultGeometry();
697
        boolean bSymbolLevelError = false;
698
        long drawingTime = time;
699

    
700
        if (geom==null && geom.getType() == Geometry.TYPES.NULL) {
701
            return false;
702
        }
703

    
704
        ISymbol sym = getSymbol(feat, selection);
705

    
706
        if (sym == null) {
707
            return false;
708
        }
709

    
710
        if (coordTrans != null) {
711
            geom = geom.cloneGeometry();
712
            geom.reProject(coordTrans);
713
        }
714

    
715
        if (cancel.isCanceled()) {
716
            return false;
717
        }
718

    
719
        // Check if this symbol is a multilayer
720
        int[] symLevels = getZSort().getLevels(sym);
721
        if (sym instanceof IMultiLayerSymbol) {
722
            // if so, treat each of its layers as a single
723
            // symbol
724
            // in its corresponding map level
725
            IMultiLayerSymbol mlSym = (IMultiLayerSymbol) sym;
726
            for (int i = 0; !cancel.isCanceled() && i < mlSym.getLayerCount(); i++) {
727
                ISymbol mySym = mlSym.getLayer(i);
728
                int symbolLevel = 0;
729
                if (symLevels != null) {
730
                    symbolLevel = symLevels[i];
731
                } else {
732
                    /*
733
                     * an error occured when managing symbol levels some of the
734
                     * legend changed events regarding the symbols did not
735
                     * finish satisfactory and the legend is now inconsistent.
736
                     * For this drawing, it will finish as it was at the bottom
737
                     * (level 0) but, when done, the ZSort will be reset to
738
                     * avoid app crashes. This is a bug that has to be fixed.
739
                     */
740
                    bSymbolLevelError = true;
741
                }
742
                drawGeometry(geom, imageLevels[symbolLevel], feat, mySym,
743
                    viewPort, graphics[symbolLevel], dpi, cancel);
744
            }
745
        } else {
746
            // else, just draw the symbol in its level
747
            int symbolLevel = 0;
748
            if (symLevels != null) {
749
                symbolLevel = symLevels[0];
750
            }
751
            drawGeometry(geom, imageLevels[symbolLevel], feat, sym, viewPort,
752
                graphics[symbolLevel], dpi, cancel);
753
        }
754

    
755
        // -- visual FX stuff
756
        // Cuando el offset!=0 se est? dibujando sobre el
757
        // Layout y por tanto no tiene que ejecutar el
758
        // siguiente c?digo.
759
        Point2D offset = viewPort.getOffset();
760
        if (offset.getX() == 0 && offset.getY() == 0) {
761
            if ((System.currentTimeMillis() - drawingTime) > screenRefreshDelay) {
762

    
763
                BufferedImage virtualBim = CompatLocator.getGraphicsUtils()
764
                .createBufferedImage(image.getWidth(),
765
                    image.getHeight(), BufferedImage.TYPE_INT_ARGB);
766

    
767
                Graphics2D virtualGraphics = virtualBim.createGraphics();
768
                virtualGraphics.drawImage(image, 0, 0, null);
769
                for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
770
                    virtualGraphics.drawImage(imageLevels[i], 0, 0, null);
771
                }
772
                g.clearRect(0, 0, image.getWidth(), image.getHeight());
773
                g.drawImage(virtualBim, 0, 0, null);
774
                drawingTime = System.currentTimeMillis();
775
            }
776
            // -- end visual FX stuff
777

    
778
        }
779
        // Notify the drawing observers
780
        drawnNotification.setFeature(feat);
781
        drawnNotification.setDrawnGeometry(geom);
782
        notifyObservers(drawnNotification);
783
        return bSymbolLevelError;
784
    }
785

    
786
    /**
787
     * Returns the symbol to use to draw a {@link Feature} taking into account
788
     * if it is selected.
789
     */
790
    private ISymbol getSymbol(Feature feat, FeatureSelection selection)
791
    throws MapContextException {
792
        // retrieve the symbol associated to such feature
793
        ISymbol sym = getSymbolByFeature(feat);
794

    
795
        if (sym != null && selection.isSelected(feat)) {
796
            sym = sym.getSymbolForSelection();
797
        }
798
        return sym;
799
    }
800

    
801
    /**
802
     * Returns if the legend is using a ZSort.
803
     */
804
    private boolean isUseZSort() {
805
        return getZSort() != null && getZSort().isUsingZSort();
806
    }
807

    
808
    /**
809
     * Draws a {@link Geometry} using the given {@link ISymbol}, over the
810
     * {@link Graphics2D} object.
811
     */
812
    private void drawGeometry(Geometry geom, BufferedImage image,
813
        Feature feature, ISymbol symbol, ViewPort viewPort,
814
        Graphics2D graphics, double dpi, Cancellable cancellable)
815
    throws CreateGeometryException {
816

    
817
        if (geom instanceof Aggregate) {
818
            drawAggregate((Aggregate) geom, image, feature, symbol, viewPort,
819
                graphics, dpi, cancellable);
820
        } else {
821
            boolean bDrawCartographicSupport = false;
822
            if (symbol instanceof CartographicSupport) {
823
                bDrawCartographicSupport = ((CartographicSupport) symbol)
824
                .getUnit() != -1;
825
            }
826

    
827
            double previousSize = 0.0d;
828

    
829
            if (bDrawCartographicSupport) {
830
                // make the symbol to resize itself with the current rendering
831
                // context
832
                previousSize = ((CartographicSupport) symbol)
833
                .toCartographicSize(viewPort, dpi, geom);
834
            }
835

    
836
            try {
837
                symbol.draw(graphics, viewPort.getAffineTransform(), geom,
838
                    feature, cancellable);
839
            } finally {
840
                if (bDrawCartographicSupport) {
841
                    // restore previous size
842
                    ((CartographicSupport) symbol).setCartographicSize(
843
                        previousSize, geom);
844
                }
845
            }
846
        }
847
    }
848

    
849
    /**
850
     * Draws an {@link Aggregate} using the given {@link ISymbol}, over the
851
     * {@link Graphics2D} object.
852
     */
853
    private void drawAggregate(Aggregate aggregate, BufferedImage image,
854
        Feature feature, ISymbol symbol, ViewPort viewPort,
855
        Graphics2D graphics, double dpi, Cancellable cancellable)
856
    throws CreateGeometryException {
857
        for (int i = 0; i < aggregate.getPrimitivesNumber(); i++) {
858
            Geometry prim = aggregate.getPrimitiveAt(i);
859
            drawGeometry(prim, image, feature, symbol, viewPort, graphics, dpi,
860
                cancellable);
861
        }
862
    }
863

    
864
    public Object clone() throws CloneNotSupportedException {
865
        AbstractVectorialLegend clone = (AbstractVectorialLegend) super.clone();
866

    
867
        // Clone zSort
868
        ZSort zSort = getZSort();
869
        if (zSort != null) {
870
            clone.setZSort(new ZSort(clone));
871
        }
872
        return clone;
873
    }
874

    
875
    public void loadFromState(PersistentState state)
876
    throws PersistenceException {
877
        // Set parent properties
878
        super.loadFromState(state);
879
        // Set own properties
880

    
881
        setShapeType(state.getInt(FIELD_SHAPETYPE));
882
        if (state.getBoolean(FIELD_HAS_ZSORT)) {
883
            setZSort(new ZSort(this));
884
        }
885
        setDefaultSymbol((ISymbol) state.get(FIELD_DEFAULT_SYMBOL));
886
    }
887

    
888
    public void saveToState(PersistentState state) throws PersistenceException {
889
        // Save parent properties
890
        super.saveToState(state);
891
        // Save own properties
892
        state.set(FIELD_SHAPETYPE, getShapeType());
893
        state.set(FIELD_HAS_ZSORT, this.zSort != null);
894
        state.set(FIELD_DEFAULT_SYMBOL, getDefaultSymbol());
895
    }
896

    
897
    public static class RegisterPersistence implements Callable {
898

    
899
        public Object call() throws Exception {
900
            PersistenceManager manager = ToolsLocator.getPersistenceManager();
901
            if (manager
902
                .getDefinition(VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME) == null) {
903
                DynStruct definition = manager.addDefinition(
904
                    AbstractVectorialLegend.class,
905
                    VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME,
906
                    VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME
907
                    + " persistence definition", null, null);
908
                // Extend the Legend base definition
909
                definition.extend(manager
910
                    .getDefinition(LEGEND_PERSISTENCE_DEFINITION_NAME));
911

    
912
                // Shapetype
913
                definition.addDynFieldInt(FIELD_SHAPETYPE).setMandatory(true);
914
                // ZSort
915
                definition.addDynFieldBoolean(FIELD_HAS_ZSORT).setMandatory(
916
                    true);
917
                // Default symbol
918
                definition.addDynFieldObject(FIELD_DEFAULT_SYMBOL)
919
                .setClassOfValue(ISymbol.class).setMandatory(true);
920
            }
921
            return Boolean.TRUE;
922
        }
923

    
924
    }
925

    
926
    /**
927
     * Returns the names of the {@link Feature} attributes required for the
928
     * {@link ILegend} to operate.
929
     * 
930
     * @param featureStore
931
     *            the store where the {@link Feature}s belong to
932
     * @return the names of required {@link Feature} attribute names
933
     * @throws DataException
934
     *             if there is an error getting the attribute names
935
     */
936
    protected abstract String[] getRequiredFeatureAttributeNames(
937
        FeatureStore featureStore) throws DataException;
938
}