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

History | View | Annotate | Download (36.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
import org.apache.commons.lang3.StringUtils;
37

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

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

    
90
/**
91
 * Base implementation for Vectorial data Legends.
92
 *
93
 * Provides a draw method implementation which loads the {@link Feature}s and
94
 * uses the {@link ISymbol} objects to draw the {@link Geometry} objects.
95
 *
96
 */
97
public abstract class AbstractVectorialLegend extends AbstractLegend implements
98
IVectorLegend {
99

    
100
        protected static final Logger LOG = LoggerFactory
101
                        .getLogger(AbstractVectorialLegend.class);
102

    
103
    private static final int DRAW_MAX_ATTEMPTS = 5;
104

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

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

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

    
114
    protected ZSort zSort;
115

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

    
120
    public void setZSort(ZSort zSort) {
121
        if (zSort == null) {
122
            removeLegendListener(this.zSort);
123
        }
124
        this.zSort = zSort;
125
        addLegendListener(zSort);
126
    }
127

    
128
    @SuppressWarnings("unchecked")
129
    @Override
130
    public void draw(BufferedImage image, Graphics2D g, ViewPort viewPort,
131
        Cancellable cancel, double scale, Map queryParameters,
132
        ICoordTrans coordTrans, FeatureStore featureStore)
133
    throws LegendException {
134
        double dpi = viewPort.getDPI();
135
        draw(image, g, viewPort, cancel, scale, queryParameters, coordTrans,
136
            featureStore, null, dpi);
137
    }
138

    
139
    @SuppressWarnings("unchecked")
140
    @Override
141
    public void draw(BufferedImage image, Graphics2D g, ViewPort viewPort,
142
        Cancellable cancel, double scale, Map queryParameters,
143
        ICoordTrans coordTrans, FeatureStore featureStore, FeatureQuery featureQuery)
144
    throws LegendException {
145
        double dpi = viewPort.getDPI();
146
        draw(image, g, viewPort, cancel, scale, queryParameters, coordTrans,
147
            featureStore, featureQuery, dpi);
148
    }
149
    
150
    @SuppressWarnings("unchecked")
151
    @Override
152
    public void print(Graphics2D g, ViewPort viewPort, Cancellable cancel,
153
        double scale, Map queryParameters, ICoordTrans coordTrans,
154
        FeatureStore featureStore, PrintAttributes properties)
155
    throws LegendException {
156
        print(g, viewPort, cancel, scale, queryParameters, coordTrans,
157
            featureStore, null, properties);
158
    }
159

    
160
    @SuppressWarnings("unchecked")
161
    @Override
162
    public void print(Graphics2D g, ViewPort viewPort, Cancellable cancel,
163
        double scale, Map queryParameters, ICoordTrans coordTrans,
164
        FeatureStore featureStore, FeatureQuery fquery, PrintAttributes properties)
165
    throws LegendException {
166
        
167
        double dpi = 72;
168

    
169
        int resolution = properties.getPrintQuality();
170

    
171
        if (resolution == PrintAttributes.PRINT_QUALITY_DRAFT
172
            || resolution == PrintAttributes.PRINT_QUALITY_NORMAL
173
            || resolution == PrintAttributes.PRINT_QUALITY_HIGH) {
174
            dpi = PrintAttributes.PRINT_QUALITY_DPI[resolution];
175
        }
176

    
177
        FeatureSet featureSet = null;
178
        DisposableIterator it = null;
179
        try {
180
            GeometryManager geomManager = GeometryLocator.getGeometryManager();
181
            ZSort zSort = getZSort();
182
            boolean useZSort = false;
183
            int mapLevelCount = 1;
184
            
185
            if( zSort != null ) {
186
                useZSort = zSort.isUsingZSort();
187
                if( useZSort ) {
188
                    mapLevelCount = zSort.getLevelCount();
189
                }
190
            }
191
            for (int mapPass = 0; mapPass < mapLevelCount; mapPass++) {
192

    
193
                Envelope vp_env_in_store_crs;
194
                IProjection store_crs;
195
                if (coordTrans != null) {
196
                    // 'coordTrans' is from store crs to vp crs
197
                    ICoordTrans inv = coordTrans.getInverted();
198
                    Envelope aux = viewPort.getAdjustedEnvelope();
199
                    vp_env_in_store_crs = aux.convert(inv);
200
                    store_crs = coordTrans.getPOrig();
201
                } else {
202
                    vp_env_in_store_crs = viewPort.getAdjustedEnvelope();
203
                    store_crs = viewPort.getProjection();
204
                }
205

    
206
                FeatureQuery feat_query = fquery;
207
                Envelope store_env = featureStore.getEnvelope();
208
                boolean use_intersection_cond;
209
                if (store_env == null) {
210
                    // Store does not know its envelope, so we must:
211
                    use_intersection_cond = true;
212
                } else {
213
                    if (vp_env_in_store_crs.contains(store_env)) {
214
                        use_intersection_cond = false;
215
                    } else {
216
                        use_intersection_cond = true;
217
                    }
218
                }
219

    
220
                if (use_intersection_cond) {
221
                    Evaluator iee = SpatialEvaluatorsFactory.getInstance().intersects(
222
                            vp_env_in_store_crs,
223
                            store_crs,
224
                            featureStore
225
                    );
226
                    if (feat_query == null) {
227
                        feat_query = featureStore.createFeatureQuery();
228
                    }
229
                    String[] fns = getRequiredFeatureAttributeNames(featureStore);
230
                    for (int i = 0; i < fns.length; i++) {
231
                        feat_query.addAttributeName(fns[i]);
232
                    }
233
                    feat_query.addFilter(iee);
234
                }
235

    
236
                // 'feat_query' can still be NULL here, so we only filter
237
                // the featureStore if it's not null
238
                if (feat_query == null) {
239
                    featureSet = featureStore.getFeatureSet();
240
                } else {
241
                    featureSet = featureStore.getFeatureSet(feat_query);
242
                }
243
                it = featureSet.fastIterator();
244
                // Iteration over each feature
245
                FilteredLogger logger = new FilteredLogger(LOG,"Drawing "+featureStore.getName(), 10);
246
                while (!cancel.isCanceled() && it.hasNext()) {
247
                    Feature feat = (Feature) it.next();
248
                    try {
249
                        Geometry geom = feat.getDefaultGeometry();
250
                        if (geom==null) {
251
                            continue;
252
                        }
253
                        // Reprojection if needed
254
                        if (coordTrans != null) {
255
                            geom = geom.cloneGeometry();
256
                            geom.reProject(coordTrans);
257
                        }
258

    
259
                        // retrieve the symbol associated to such feature
260
                        ISymbol sym = getSymbolByFeature(feat);
261
                        if (sym == null) {
262
                            continue;
263
                        }
264
                        if (useZSort) {
265
                            int[] symLevels = zSort.getLevels(sym);
266
                            if (symLevels != null) {
267

    
268
                                // Check if this symbol is a multilayer
269
                                if (sym instanceof IMultiLayerSymbol) {
270
                                    // if so, get the layer corresponding to the
271
                                    // current level. If none, continue to next
272
                                    // iteration
273
                                    IMultiLayerSymbol mlSym = (IMultiLayerSymbol) sym;
274
                                    for (int i = 0; i < mlSym.getLayerCount(); i++) {
275
                                        ISymbol mySym = mlSym.getLayer(i);
276
                                        if (symLevels[i] == mapPass) {
277
                                            sym = mySym;
278
                                            break;
279
                                        }
280
                                    }
281

    
282
                                } else {
283
                                    // else, just draw the symbol in its level
284
                                    if (symLevels[0] != mapPass) {
285
                                        continue;
286
                                    }
287
                                }
288
                            }
289
                        }
290

    
291
                        // Check if this symbol is sized with CartographicSupport
292
                        CartographicSupport csSym = null;
293
                        int symbolType = sym.getSymbolType();
294

    
295
                        if (symbolType == Geometry.TYPES.POINT
296
                            ||  geomManager.isSubtype(symbolType, Geometry.TYPES.CURVE)
297
                            || sym instanceof CartographicSupport) {
298

    
299
                            csSym = (CartographicSupport) sym;
300
                        }
301

    
302
                        if (csSym == null) {
303
                            DrawUtils.drawInts(g, viewPort, sym, feat, geom);
304
                        } else {
305
                            DrawUtils.drawInts(g, viewPort, cancel, dpi, sym, feat, geom);
306
                        }
307
                        
308
                    } catch(Exception ex) {
309
                        FeatureReference ref = null;
310
                        if( feat!=null ) {
311
                            ref = feat.getReference();
312
                        }
313
                        logger.warn("Can't draw feature ("+ref+").", ex);
314
                    }
315
                }
316
            }
317
        } catch (DataException e) {
318
            throw new LegendDrawingException(e);
319
        } finally {
320
            if (it != null) {
321
                it.dispose();
322
            }
323
            if (featureSet != null) {
324
                featureSet.dispose();
325
            }
326
        }
327
    }
328

    
329
    /**
330
     * Draws the features from the {@link FeatureStore}, filtered with the scale
331
     * and the query parameters, with the symbols of the legend.
332
     * @param image
333
     * @param g
334
     * @param viewPort
335
     * @param cancel
336
     * @param scale
337
     * @param queryParameters
338
     * @param coordTrans
339
     * @param featureStore
340
     * @param featureQuery
341
     * @param dpi
342
     * @throws org.gvsig.fmap.mapcontext.rendering.legend.LegendException
343
     */
344
    @SuppressWarnings("unchecked")
345
        protected void draw(BufferedImage image, Graphics2D g, ViewPort viewPort,
346
                        Cancellable cancel, double scale, Map queryParameters,
347
                        ICoordTrans coordTrans, FeatureStore featureStore,
348
                        FeatureQuery featureQuery, double dpi) throws LegendException {
349

    
350
                SimpleTaskStatus taskStatus = ToolsLocator.getTaskStatusManager()
351
                                .createDefaultSimpleTaskStatus(featureStore.getName());
352
                taskStatus.add();
353
                try {
354
                        // Avoid ConcurrentModificationException errors if
355
                        // while drawing another thread edits the store data.
356
                        synchronized (featureStore) {
357
                                internalDraw(image, g, viewPort, cancel, scale,
358
                                                queryParameters, coordTrans, featureStore,
359
                                                featureQuery, dpi, taskStatus);
360
                        }
361
                } finally {
362
            taskStatus.terminate();
363
            taskStatus.remove();
364
                }
365
        }
366

    
367
        protected void internalDraw(BufferedImage image, Graphics2D g,
368
                        ViewPort viewPort, Cancellable cancel, double scale,
369
                        Map queryParameters, ICoordTrans coordTrans,
370
                        FeatureStore featureStore, FeatureQuery featureQuery, double dpi,
371
                        SimpleTaskStatus taskStatus) throws LegendDrawingException {
372

    
373
                if (!getDefaultSymbol().isShapeVisible()) {
374
                        return;
375
                }
376

    
377
                if (cancel.isCanceled()) {
378
                        return;
379
                }
380

    
381
                IProjection dataProjection;
382
                Envelope dataEnvelope;
383
                Envelope reprojectedDataEnvelope;
384
                // Gets the view envelope
385
                Envelope viewPortEnvelope = viewPort.getAdjustedEnvelope();
386
        Envelope reprojectedViewPortEnvelope;
387
                try {
388
                    dataEnvelope = featureStore.getEnvelope();
389
                        if (coordTrans == null) {
390
                                dataProjection = featureStore.getDefaultFeatureType()
391
                                                .getDefaultSRS();
392

    
393
                                // If the data does not provide a projection, use the
394
                                // current view one
395
                                if (dataProjection == null) {
396
                                        dataProjection = viewPort.getProjection();
397
                                }
398

    
399
                                reprojectedDataEnvelope = dataEnvelope;
400
                reprojectedViewPortEnvelope = viewPortEnvelope;
401
                        } else {
402
                                dataProjection = coordTrans.getPOrig();
403

    
404
                                if ( dataEnvelope!=null && !dataEnvelope.isEmpty()) {
405
                                        reprojectedDataEnvelope = dataEnvelope.convert(coordTrans);
406
                                } else {
407
                                        reprojectedDataEnvelope = dataEnvelope;
408
                                }
409
                if ( viewPortEnvelope!=null && !viewPortEnvelope.isEmpty()) {
410
                    reprojectedViewPortEnvelope = viewPortEnvelope.convert(coordTrans.getInverted());
411
                } else {
412
                    reprojectedViewPortEnvelope = viewPortEnvelope;
413
                }
414
                        }
415
                } catch (DataException e) {
416
                        throw new LegendDrawingException(e);
417
                }
418

    
419

    
420
                // Gets the data envelope with the viewport SRS
421
//                Envelope myEnvelope = reprojectedDataEnvelope;
422

    
423
                // TODO: in some cases, the legend may need a different check to
424
                // decide if the data must be drawn or not
425
                // Checks if the viewport envelope intersects with the data envelope
426
                // This condition may seem redundant, but sometimes the transformations may fail and cause false negatives.
427
                if ((viewPortEnvelope==null || !viewPortEnvelope.intersects(reprojectedDataEnvelope)) && !(reprojectedViewPortEnvelope!=null && reprojectedViewPortEnvelope.intersects(dataEnvelope))) {
428
                        // The data is not visible in the current viewport, do nothing.
429
                        return;
430
                }
431

    
432
                // Check if all the data is contained into the viewport envelope
433
        // This condition may seem redundant, but sometimes the transformations may fail and cause false negatives.
434
                boolean containsAll = viewPortEnvelope.contains(reprojectedDataEnvelope) || (reprojectedViewPortEnvelope!=null && reprojectedViewPortEnvelope.contains(dataEnvelope));
435

    
436
                // Create the drawing notification to be reused on each iteration
437
                DefaultFeatureDrawnNotification drawnNotification = new DefaultFeatureDrawnNotification();
438

    
439
                if (cancel.isCanceled()) {
440
                        return;
441
                }
442

    
443
                FeatureSet featureSet = null;
444
                try {
445
                        taskStatus.message("Retrieve selection");
446
                        FeatureSelection selection;
447
                        if( featureStore.isFeatureSelectionEmpty() ) {
448
                            // No hay seleccion, asi que creamos una vacia que consuma pocos recursos.
449
                            selection = featureStore.createMemoryFeatureSelection();
450
                        } else {
451
                            // Ojo, que esta seleccion puede acabar haciendo un count(*) sobre la tabla.
452
                            selection = featureStore.getFeatureSelection();
453
                        }
454

    
455
                        if (featureQuery == null) {
456
                                featureQuery = featureStore.createFeatureQuery();
457
                        }
458

    
459
                        completeQuery(featureStore, featureQuery, scale, queryParameters,
460
                                        coordTrans, dataProjection, viewPortEnvelope, containsAll);
461

    
462
                        taskStatus.message("Retrieve data");
463
                        featureSet = featureStore.getFeatureSet(featureQuery);
464

    
465
                        if (cancel.isCanceled()) {
466
                                return;
467
                        }
468

    
469
                        taskStatus.message("Drawing");
470
                        drawFeatures(image, g, viewPort, cancel, coordTrans, dpi,
471
                                        drawnNotification, featureSet, selection);
472

    
473
        } catch (Throwable e) {
474
            /*
475
             * Probably a reprojection exception (for example,
476
             * trying to reproject Canada to EPSG:23030)
477
             */
478
            throw new LegendDrawingException(e);
479
                } finally {
480
                        if (featureSet != null) {
481
                                featureSet.dispose();
482
                        }
483
                }
484
        }
485

    
486
    /**
487
     * Complete a {@link FeatureQuery} to load the {@link Feature}s to draw.
488
     */
489
    @SuppressWarnings("unchecked")
490
    private FeatureQuery completeQuery(FeatureStore featureStore, FeatureQuery featureQuery, double scale,
491
        Map queryParameters, ICoordTrans coordTrans,
492
        IProjection dataProjection, Envelope viewPortEnvelope,
493
        boolean containsAll) throws DataException {
494
        boolean retrievesAllAttributes = featureQuery.hasFilter() && !featureQuery.hasAttributeNames();
495

    
496
        featureQuery.setScale(scale);
497

    
498
        //Adds the attributes
499
        String[] fieldNames = getRequiredFeatureAttributeNames(featureStore);
500
        for (int i=0 ; i<fieldNames.length ;i++){
501
            String fieldName = fieldNames[i];
502
            if( StringUtils.isNotBlank(fieldName) ) {
503
                featureQuery.addAttributeName(fieldName);
504
            }
505
        }
506

    
507
        if (!containsAll) {
508
            // Gets the viewport envelope with the data SRS
509
            Envelope viewPortEnvelopeInMyProj = viewPortEnvelope;
510
            if (coordTrans != null) {
511
                viewPortEnvelopeInMyProj = viewPortEnvelope.convert(coordTrans
512
                        .getInverted());
513
            }
514

    
515
            if (dataProjection == null) {
516
                throw new IllegalArgumentException(
517
                "Error, the projection parameter value is null");
518
            }
519

    
520
            Evaluator iee = SpatialEvaluatorsFactory.getInstance().intersects(
521
                    viewPortEnvelopeInMyProj,
522
                    dataProjection,
523
                    featureStore
524
            );
525
            featureQuery.addFilter(iee);
526
        } else {
527
            FeatureType ft = featureStore.getDefaultFeatureType();
528
            ExpressionBuilder expbuilder = ExpressionUtils.createExpressionBuilder();
529
            featureQuery.addFilter(
530
                expbuilder.not_is_null(
531
                    expbuilder.column(
532
                            ft.getDefaultGeometryAttributeName()
533
                    )
534
                ).toString()
535
            );
536
        }
537
        
538
        if (queryParameters != null) {
539
            Iterator iterEntry = queryParameters.entrySet().iterator();
540
            Entry entry;
541
            while (iterEntry.hasNext()) {
542
                entry = (Entry) iterEntry.next();
543
                featureQuery.setQueryParameter((String) entry.getKey(),
544
                    entry.getValue());
545
            }
546
        }
547
        if( retrievesAllAttributes ) {
548
            featureQuery.retrievesAllAttributes();
549
        }
550
        return featureQuery;
551
    }
552

    
553
    /**
554
     * Draws the features from the {@link FeatureSet}, with the symbols of the
555
     * legend.
556
     */
557
    protected void drawFeatures(BufferedImage image, Graphics2D g,
558
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
559
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
560
        FeatureSet featureSet, FeatureSelection selection)
561
    throws BaseException {
562
        if (isUseZSort()) {
563
            drawFeaturesMultiLayer(image, g, viewPort, cancel, coordTrans, dpi,
564
                drawnNotification, featureSet, selection);
565
        } else {
566
            drawFeaturesSingleLayer(image, g, viewPort, cancel, coordTrans,
567
                dpi, drawnNotification, featureSet, selection);
568
        }
569
    }
570

    
571
    /**
572
     * Draws the features from the {@link FeatureSet}, with the symbols of the
573
     * legend, using a single drawing layer.
574
     */
575
    private void drawFeaturesSingleLayer(final BufferedImage image,
576
        final Graphics2D g, final ViewPort viewPort,
577
        final Cancellable cancel, final ICoordTrans coordTrans,
578
        final double dpi,
579
        final DefaultFeatureDrawnNotification drawnNotification,
580
        FeatureSet featureSet, final FeatureSelection selection)
581
    throws BaseException {
582

    
583
        try {
584
            featureSet.accept(new Visitor() {
585
                @Override
586
                public void visit(Object obj) throws VisitCanceledException,
587
                BaseException {
588
                    if( cancel.isCanceled() )  {
589
                        throw new VisitCanceledException();
590
                    }
591
                    Feature feat = (Feature) obj;
592
                    drawFeatureSingleLayer(image, g, viewPort, cancel,
593
                        coordTrans, dpi, drawnNotification, feat, selection);
594
                }
595
            });
596

    
597
        } catch (ConcurrentDataModificationException e) {
598
            cancel.setCanceled(true);
599
        }
600
    }
601

    
602
    /**
603
     * Draws a Feature with the symbols of the legend, using a single drawing
604
     * layer.
605
     */
606
    private void drawFeatureSingleLayer(BufferedImage image, Graphics2D g,
607
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
608
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
609
        Feature feat, FeatureSelection selection)
610
    throws MapContextException, CreateGeometryException {
611

    
612
        Geometry geom = feat.getDefaultGeometry();
613
        if (geom == null) {
614
            return;
615
        }
616

    
617
        if (geom.getType() == Geometry.TYPES.NULL) {
618
            return;
619
        }
620

    
621
        ISymbol sym = getSymbol(feat, selection);
622
        if (sym == null) {
623
            return;
624
        }
625

    
626
        if (coordTrans != null) {
627
            geom = geom.cloneGeometry();
628
            try {
629
                geom.reProject(coordTrans);
630
            } catch (ReprojectionRuntimeException re) {
631
                LOG.warn("Can't reproject geometry "+geom.toString(), re);
632
                return;
633
            }
634
        }
635

    
636
        if (cancel.isCanceled()) {
637
            return;
638
        }
639

    
640
        drawGeometry(geom, image, feat, sym, viewPort, g, dpi, cancel);
641

    
642
        // Notify the drawing observers
643
        drawnNotification.setFeature(feat);
644
        drawnNotification.setDrawnGeometry(geom);
645
        notifyObservers(drawnNotification);
646
    }
647

    
648
    /**
649
     * Draws the features from the {@link FeatureSet}, with the symbols of the
650
     * legend, using a multiple drawing layer.
651
     */
652
    private void drawFeaturesMultiLayer(BufferedImage image, Graphics2D g,
653
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
654
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
655
        FeatureSet featureSet, FeatureSelection selection)
656
    throws MapContextException, CreateGeometryException, DataException {
657

    
658
        // -- visual FX stuff
659
        long time = System.currentTimeMillis();
660

    
661
        boolean bSymbolLevelError = false;
662
        int screenRefreshDelay = (int) ((1D / MapContext.getDrawFrameRate()) * 3 * 1000);
663
        BufferedImage[] imageLevels;
664
        Graphics2D[] graphics;
665

    
666
        imageLevels = new BufferedImage[getZSort().getLevelCount()];
667
        graphics = new Graphics2D[imageLevels.length];
668
        for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
669

    
670
            imageLevels[i] = CompatLocator.getGraphicsUtils()
671
            .createBufferedImage(image.getWidth(), image.getHeight(),
672
                image.getType());
673

    
674
            graphics[i] = imageLevels[i].createGraphics();
675
            graphics[i].setTransform(g.getTransform());
676
            graphics[i].setRenderingHints(g.getRenderingHints());
677
        }
678

    
679
        DisposableIterator it = null;
680
        try {
681
            it = featureSet.fastIterator();
682
            // Iteration over each feature
683
            while (it.hasNext()) {
684
                if (cancel.isCanceled()) {
685
                    return;
686
                }
687
                Feature feat = (Feature) it.next();
688

    
689
                bSymbolLevelError |= drawFeatureMultiLayer(image, g, viewPort,
690
                    cancel, coordTrans, dpi, drawnNotification, selection,
691
                    time, screenRefreshDelay, imageLevels, graphics, feat);
692

    
693
            }
694
        } catch (ConcurrentDataModificationException e) {
695
            cancel.setCanceled(true);
696
            return;
697
        } finally {
698
            DisposeUtils.dispose(it);
699
        }
700

    
701
        g.drawImage(image, 0, 0, null);
702

    
703
        Point2D offset = viewPort.getOffset();
704
        CompatLocator.getGraphicsUtils().translate(g, offset.getX(),
705
            offset.getY());
706

    
707
        for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
708
            g.drawImage(imageLevels[i], 0, 0, null);
709
            imageLevels[i] = null;
710
            graphics[i] = null;
711
        }
712

    
713
        CompatLocator.getGraphicsUtils().translate(g, -offset.getX(),
714
            -offset.getY());
715

    
716
        if (bSymbolLevelError) {
717
            setZSort(null);
718
        }
719
    }
720

    
721
    /**
722
     * Draws a Feature with the symbols of the legend, using a multiple drawing
723
     * layer.
724
     */
725
    private boolean drawFeatureMultiLayer(BufferedImage image, Graphics2D g,
726
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
727
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
728
        FeatureSelection selection, long time, int screenRefreshDelay,
729
        BufferedImage[] imageLevels, Graphics2D[] graphics, Feature feat)
730
    throws MapContextException, CreateGeometryException {
731

    
732
        Geometry geom = feat.getDefaultGeometry();
733
        boolean bSymbolLevelError = false;
734
        long drawingTime = time;
735

    
736
        if (geom==null || geom.getType() == Geometry.TYPES.NULL) {
737
            return false;
738
        }
739

    
740
        ISymbol sym = getSymbol(feat, selection);
741

    
742
        if (sym == null) {
743
            return false;
744
        }
745

    
746
        if (coordTrans != null) {
747
            geom = geom.cloneGeometry();
748
            geom.reProject(coordTrans);
749
        }
750

    
751
        if (cancel.isCanceled()) {
752
            return false;
753
        }
754

    
755
        // Check if this symbol is a multilayer
756
        int[] symLevels = getZSort().getLevels(sym);
757
        if (sym instanceof IMultiLayerSymbol) {
758
            // if so, treat each of its layers as a single
759
            // symbol
760
            // in its corresponding map level
761
            IMultiLayerSymbol mlSym = (IMultiLayerSymbol) sym;
762
            for (int i = 0; !cancel.isCanceled() && i < mlSym.getLayerCount(); i++) {
763
                ISymbol mySym = mlSym.getLayer(i);
764
                int symbolLevel = 0;
765
                if (symLevels != null) {
766
                    symbolLevel = symLevels[i];
767
                } else {
768
                    /*
769
                     * an error occured when managing symbol levels some of the
770
                     * legend changed events regarding the symbols did not
771
                     * finish satisfactory and the legend is now inconsistent.
772
                     * For this drawing, it will finish as it was at the bottom
773
                     * (level 0) but, when done, the ZSort will be reset to
774
                     * avoid app crashes. This is a bug that has to be fixed.
775
                     */
776
                    bSymbolLevelError = true;
777
                }
778
                drawGeometry(geom, imageLevels[symbolLevel], feat, mySym,
779
                    viewPort, graphics[symbolLevel], dpi, cancel);
780
            }
781
        } else {
782
            // else, just draw the symbol in its level
783
            int symbolLevel = 0;
784
            if (symLevels != null) {
785
                symbolLevel = symLevels[0];
786
            }
787
            drawGeometry(geom, imageLevels[symbolLevel], feat, sym, viewPort,
788
                graphics[symbolLevel], dpi, cancel);
789
        }
790

    
791
        // -- visual FX stuff
792
        // Cuando el offset!=0 se est? dibujando sobre el
793
        // Layout y por tanto no tiene que ejecutar el
794
        // siguiente c?digo.
795
        Point2D offset = viewPort.getOffset();
796
        if (offset.getX() == 0 && offset.getY() == 0) {
797
            if ((System.currentTimeMillis() - drawingTime) > screenRefreshDelay) {
798

    
799
                BufferedImage virtualBim = CompatLocator.getGraphicsUtils()
800
                .createBufferedImage(image.getWidth(),
801
                    image.getHeight(), BufferedImage.TYPE_INT_ARGB);
802

    
803
                Graphics2D virtualGraphics = virtualBim.createGraphics();
804
                virtualGraphics.drawImage(image, 0, 0, null);
805
                for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
806
                    virtualGraphics.drawImage(imageLevels[i], 0, 0, null);
807
                }
808
                g.clearRect(0, 0, image.getWidth(), image.getHeight());
809
                g.drawImage(virtualBim, 0, 0, null);
810
                drawingTime = System.currentTimeMillis();
811
            }
812
            // -- end visual FX stuff
813

    
814
        }
815
        // Notify the drawing observers
816
        drawnNotification.setFeature(feat);
817
        drawnNotification.setDrawnGeometry(geom);
818
        notifyObservers(drawnNotification);
819
        return bSymbolLevelError;
820
    }
821

    
822
    /**
823
     * Returns the symbol to use to draw a {@link Feature} taking into account
824
     * if it is selected.
825
     */
826
    private ISymbol getSymbol(Feature feat, FeatureSelection selection)
827
    throws MapContextException {
828
        // retrieve the symbol associated to such feature
829
        ISymbol sym = getSymbolByFeature(feat);
830

    
831
        if (sym != null && selection.isSelected(feat)) {
832
            sym = sym.getSymbolForSelection();
833
        }
834
        return sym;
835
    }
836

    
837
    /**
838
     * Returns if the legend is using a ZSort.
839
     */
840
    private boolean isUseZSort() {
841
        return getZSort() != null && getZSort().isUsingZSort();
842
    }
843

    
844
    /**
845
     * Draws a {@link Geometry} using the given {@link ISymbol}, over the
846
     * {@link Graphics2D} object.
847
     */
848
    private void drawGeometry(Geometry geom, BufferedImage image,
849
        Feature feature, ISymbol symbol, ViewPort viewPort,
850
        Graphics2D graphics, double dpi, Cancellable cancellable)
851
    throws CreateGeometryException {
852

    
853
        if (geom instanceof Aggregate) {
854
            drawAggregate((Aggregate) geom, image, feature, symbol, viewPort,
855
                graphics, dpi, cancellable);
856
        } else {
857
            boolean bDrawCartographicSupport = false;
858
            if (symbol instanceof CartographicSupport) {
859
                bDrawCartographicSupport = ((CartographicSupport) symbol)
860
                .getUnit() != -1;
861
            }
862

    
863
            double previousSize = 0.0d;
864

    
865
            if (bDrawCartographicSupport) {
866
                // make the symbol to resize itself with the current rendering
867
                // context
868
                previousSize = ((CartographicSupport) symbol)
869
                .toCartographicSize(viewPort, dpi, geom);
870
            }
871

    
872
            try {
873
                symbol.draw(graphics, viewPort.getAffineTransform(), geom,
874
                    feature, cancellable);
875
            } finally {
876
                if (bDrawCartographicSupport) {
877
                    // restore previous size
878
                    ((CartographicSupport) symbol).setCartographicSize(
879
                        previousSize, geom);
880
                }
881
            }
882
        }
883
    }
884

    
885
    /**
886
     * Draws an {@link Aggregate} using the given {@link ISymbol}, over the
887
     * {@link Graphics2D} object.
888
     */
889
    private void drawAggregate(Aggregate aggregate, BufferedImage image,
890
        Feature feature, ISymbol symbol, ViewPort viewPort,
891
        Graphics2D graphics, double dpi, Cancellable cancellable)
892
    throws CreateGeometryException {
893
        for (int i = 0; i < aggregate.getPrimitivesNumber(); i++) {
894
            Geometry prim = aggregate.getPrimitiveAt(i);
895
            drawGeometry(prim, image, feature, symbol, viewPort, graphics, dpi,
896
                cancellable);
897
        }
898
    }
899

    
900
    @Override
901
    public Object clone() throws CloneNotSupportedException {
902
        AbstractVectorialLegend clone = (AbstractVectorialLegend) super.clone();
903

    
904
        // Clone zSort
905
        ZSort zSort = getZSort();
906
        if (zSort != null) {
907
            clone.setZSort(new ZSort(clone));
908
        }
909
        return clone;
910
    }
911

    
912
    @Override
913
    public void loadFromState(PersistentState state)
914
    throws PersistenceException {
915
        // Set parent properties
916
        super.loadFromState(state);
917
        // Set own properties
918

    
919
        setShapeType(state.getInt(FIELD_SHAPETYPE));
920
        if (state.getBoolean(FIELD_HAS_ZSORT)) {
921
            setZSort(new ZSort(this));
922
        }
923
        setDefaultSymbol((ISymbol) state.get(FIELD_DEFAULT_SYMBOL));
924
    }
925

    
926
    @Override
927
    public void saveToState(PersistentState state) throws PersistenceException {
928
        // Save parent properties
929
        super.saveToState(state);
930
        // Save own properties
931
        state.set(FIELD_SHAPETYPE, getShapeType());
932
        state.set(FIELD_HAS_ZSORT, this.zSort != null);
933
        state.set(FIELD_DEFAULT_SYMBOL, getDefaultSymbol());
934
    }
935

    
936
    public static class RegisterPersistence implements Callable {
937

    
938
        @Override
939
        public Object call() throws Exception {
940
            PersistenceManager manager = ToolsLocator.getPersistenceManager();
941
            if (manager
942
                .getDefinition(VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME) == null) {
943
                DynStruct definition = manager.addDefinition(
944
                    AbstractVectorialLegend.class,
945
                    VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME,
946
                    VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME
947
                    + " persistence definition", null, null);
948
                // Extend the Legend base definition
949
                definition.extend(manager.getDefinition(LEGEND_PERSISTENCE_DEFINITION_NAME));
950

    
951
                // Shapetype
952
                definition.addDynFieldInt(FIELD_SHAPETYPE).setMandatory(true);
953
                // ZSort
954
                definition.addDynFieldBoolean(FIELD_HAS_ZSORT).setMandatory(true);
955
                // Default symbol
956
                definition.addDynFieldObject(FIELD_DEFAULT_SYMBOL)
957
                    .setClassOfValue(ISymbol.class).setMandatory(true);
958
            }
959
            return Boolean.TRUE;
960
        }
961

    
962
    }
963

    
964
    /**
965
     * Returns the names of the {@link Feature} attributes required for the
966
     * {@link ILegend} to operate.
967
     *
968
     * @param featureStore
969
     *            the store where the {@link Feature}s belong to
970
     * @return the names of required {@link Feature} attribute names
971
     * @throws DataException
972
     *             if there is an error getting the attribute names
973
     */
974
    protected abstract String[] getRequiredFeatureAttributeNames(
975
        FeatureStore featureStore) throws DataException;
976
}