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

History | View | Annotate | Download (35.9 KB)

1
/**
2
 * gvSIG. Desktop Geographic Information System.
3
 *
4
 * Copyright (C) 2007-2013 gvSIG Association.
5
 *
6
 * This program is free software; you can redistribute it and/or
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.expressionevaluator.ExpressionBuilder;
45
import org.gvsig.expressionevaluator.ExpressionUtils;
46
import org.gvsig.fmap.dal.exception.DataException;
47
import org.gvsig.fmap.dal.feature.Feature;
48
import org.gvsig.fmap.dal.feature.FeatureQuery;
49
import org.gvsig.fmap.dal.feature.FeatureReference;
50
import org.gvsig.fmap.dal.feature.FeatureSelection;
51
import org.gvsig.fmap.dal.feature.FeatureSet;
52
import org.gvsig.fmap.dal.feature.FeatureStore;
53
import org.gvsig.fmap.dal.feature.FeatureType;
54
import org.gvsig.fmap.dal.feature.exception.ConcurrentDataModificationException;
55
import org.gvsig.fmap.geom.Geometry;
56
import org.gvsig.fmap.geom.GeometryLocator;
57
import org.gvsig.fmap.geom.GeometryManager;
58
import org.gvsig.fmap.geom.aggregate.Aggregate;
59
import org.gvsig.fmap.geom.exception.CreateGeometryException;
60
import org.gvsig.fmap.geom.exception.ReprojectionRuntimeException;
61
import org.gvsig.fmap.geom.primitive.Envelope;
62
import org.gvsig.fmap.mapcontext.MapContext;
63
import org.gvsig.fmap.mapcontext.MapContextException;
64
import org.gvsig.fmap.mapcontext.ViewPort;
65
import org.gvsig.fmap.mapcontext.layers.vectorial.SpatialEvaluatorsFactory;
66
import org.gvsig.fmap.mapcontext.rendering.legend.ILegend;
67
import org.gvsig.fmap.mapcontext.rendering.legend.IVectorLegend;
68
import org.gvsig.fmap.mapcontext.rendering.legend.LegendException;
69
import org.gvsig.fmap.mapcontext.rendering.legend.ZSort;
70
import org.gvsig.fmap.mapcontext.rendering.symbols.CartographicSupport;
71
import org.gvsig.fmap.mapcontext.rendering.symbols.IMultiLayerSymbol;
72
import org.gvsig.fmap.mapcontext.rendering.symbols.ISymbol;
73
import org.gvsig.tools.ToolsLocator;
74
import org.gvsig.tools.dispose.DisposableIterator;
75
import org.gvsig.tools.dispose.DisposeUtils;
76
import org.gvsig.tools.dynobject.DynStruct;
77
import org.gvsig.tools.evaluator.Evaluator;
78
import org.gvsig.tools.exception.BaseException;
79
import org.gvsig.tools.logger.FilteredLogger;
80
import org.gvsig.tools.persistence.PersistenceManager;
81
import org.gvsig.tools.persistence.PersistentState;
82
import org.gvsig.tools.persistence.exception.PersistenceException;
83
import org.gvsig.tools.task.Cancellable;
84
import org.gvsig.tools.task.SimpleTaskStatus;
85
import org.gvsig.tools.util.Callable;
86
import org.gvsig.tools.visitor.VisitCanceledException;
87
import org.gvsig.tools.visitor.Visitor;
88

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

    
99
        protected 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
    @Override
129
    public void draw(BufferedImage image, Graphics2D g, ViewPort viewPort,
130
        Cancellable cancel, double scale, Map queryParameters,
131
        ICoordTrans coordTrans, FeatureStore featureStore)
132
    throws LegendException {
133
        double dpi = viewPort.getDPI();
134
        draw(image, g, viewPort, cancel, scale, queryParameters, coordTrans,
135
            featureStore, null, dpi);
136
    }
137

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

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

    
168
        int resolution = properties.getPrintQuality();
169

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

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

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

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

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

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

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

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

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

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

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

    
298
                            csSym = (CartographicSupport) sym;
299
                        }
300

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

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

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

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

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

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

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

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

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

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

    
418

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

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

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

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

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

    
442
                FeatureSet featureSet = null;
443
                try {
444
                        taskStatus.message("Retrieve selection");
445
                        FeatureSelection selection = featureStore.getFeatureSelection();
446

    
447
                        if (featureQuery == null) {
448
                                featureQuery = featureStore.createFeatureQuery();
449
                        }
450

    
451
                        completeQuery(featureStore, featureQuery, scale, queryParameters,
452
                                        coordTrans, dataProjection, viewPortEnvelope, containsAll);
453

    
454
                        taskStatus.message("Retrieve data");
455
                        featureSet = featureStore.getFeatureSet(featureQuery);
456

    
457
                        if (cancel.isCanceled()) {
458
                                return;
459
                        }
460

    
461
                        taskStatus.message("Drawing");
462
                        drawFeatures(image, g, viewPort, cancel, coordTrans, dpi,
463
                                        drawnNotification, featureSet, selection);
464

    
465
        } catch (Throwable e) {
466
            /*
467
             * Probably a reprojection exception (for example,
468
             * trying to reproject Canada to EPSG:23030)
469
             */
470
            throw new LegendDrawingException(e);
471
                } finally {
472
                        if (featureSet != null) {
473
                                featureSet.dispose();
474
                        }
475
                }
476
        }
477

    
478
    /**
479
     * Complete a {@link FeatureQuery} to load the {@link Feature}s to draw.
480
     */
481
    @SuppressWarnings("unchecked")
482
    private FeatureQuery completeQuery(FeatureStore featureStore, FeatureQuery featureQuery, double scale,
483
        Map queryParameters, ICoordTrans coordTrans,
484
        IProjection dataProjection, Envelope viewPortEnvelope,
485
        boolean containsAll) throws DataException {
486

    
487
        featureQuery.setScale(scale);
488

    
489
        //Adds the attributes
490
        String[] fieldNames = getRequiredFeatureAttributeNames(featureStore);
491
        for (int i=0 ; i<fieldNames.length ;i++){
492
            featureQuery.addAttributeName(fieldNames[i]);
493
        }
494

    
495
        if (!containsAll) {
496
            // Gets the viewport envelope with the data SRS
497
            Envelope viewPortEnvelopeInMyProj = viewPortEnvelope;
498
            if (coordTrans != null) {
499
                viewPortEnvelopeInMyProj = viewPortEnvelope.convert(coordTrans
500
                        .getInverted());
501
            }
502

    
503
            if (dataProjection == null) {
504
                throw new IllegalArgumentException(
505
                "Error, the projection parameter value is null");
506
            }
507

    
508
            Evaluator iee = SpatialEvaluatorsFactory.getInstance().intersects(
509
                    viewPortEnvelopeInMyProj,
510
                    dataProjection,
511
                    featureStore
512
            );
513
            featureQuery.addFilter(iee);
514
        } else {
515
            FeatureType ft = featureStore.getDefaultFeatureType();
516
            ExpressionBuilder expbuilder = ExpressionUtils.createExpressionBuilder();
517
            featureQuery.addFilter(
518
                expbuilder.not_is_null(
519
                    expbuilder.column(
520
                            ft.getDefaultGeometryAttributeName()
521
                    )
522
                ).toString()
523
            );
524
        }
525
        
526
        if (queryParameters != null) {
527
            Iterator iterEntry = queryParameters.entrySet().iterator();
528
            Entry entry;
529
            while (iterEntry.hasNext()) {
530
                entry = (Entry) iterEntry.next();
531
                featureQuery.setQueryParameter((String) entry.getKey(),
532
                    entry.getValue());
533
            }
534
        }
535
        return featureQuery;
536
    }
537

    
538
    /**
539
     * Draws the features from the {@link FeatureSet}, with the symbols of the
540
     * legend.
541
     */
542
    protected void drawFeatures(BufferedImage image, Graphics2D g,
543
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
544
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
545
        FeatureSet featureSet, FeatureSelection selection)
546
    throws BaseException {
547
        if (isUseZSort()) {
548
            drawFeaturesMultiLayer(image, g, viewPort, cancel, coordTrans, dpi,
549
                drawnNotification, featureSet, selection);
550
        } else {
551
            drawFeaturesSingleLayer(image, g, viewPort, cancel, coordTrans,
552
                dpi, drawnNotification, featureSet, selection);
553
        }
554
    }
555

    
556
    /**
557
     * Draws the features from the {@link FeatureSet}, with the symbols of the
558
     * legend, using a single drawing layer.
559
     */
560
    private void drawFeaturesSingleLayer(final BufferedImage image,
561
        final Graphics2D g, final ViewPort viewPort,
562
        final Cancellable cancel, final ICoordTrans coordTrans,
563
        final double dpi,
564
        final DefaultFeatureDrawnNotification drawnNotification,
565
        FeatureSet featureSet, final FeatureSelection selection)
566
    throws BaseException {
567

    
568
        try {
569
            featureSet.accept(new Visitor() {
570
                @Override
571
                public void visit(Object obj) throws VisitCanceledException,
572
                BaseException {
573
                    if( cancel.isCanceled() )  {
574
                        throw new VisitCanceledException();
575
                    }
576
                    Feature feat = (Feature) obj;
577
                    drawFeatureSingleLayer(image, g, viewPort, cancel,
578
                        coordTrans, dpi, drawnNotification, feat, selection);
579
                }
580
            });
581

    
582
        } catch (ConcurrentDataModificationException e) {
583
            cancel.setCanceled(true);
584
        }
585
    }
586

    
587
    /**
588
     * Draws a Feature with the symbols of the legend, using a single drawing
589
     * layer.
590
     */
591
    private void drawFeatureSingleLayer(BufferedImage image, Graphics2D g,
592
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
593
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
594
        Feature feat, FeatureSelection selection)
595
    throws MapContextException, CreateGeometryException {
596

    
597
        Geometry geom = feat.getDefaultGeometry();
598
        if (geom == null) {
599
            return;
600
        }
601

    
602
        if (geom.getType() == Geometry.TYPES.NULL) {
603
            return;
604
        }
605

    
606
        ISymbol sym = getSymbol(feat, selection);
607
        if (sym == null) {
608
            return;
609
        }
610

    
611
        if (coordTrans != null) {
612
            geom = geom.cloneGeometry();
613
            try {
614
                geom.reProject(coordTrans);
615
            } catch (ReprojectionRuntimeException re) {
616
                LOG.warn("Can't reproject geometry "+geom.toString(), re);
617
                return;
618
            }
619
        }
620

    
621
        if (cancel.isCanceled()) {
622
            return;
623
        }
624

    
625
        drawGeometry(geom, image, feat, sym, viewPort, g, dpi, cancel);
626

    
627
        // Notify the drawing observers
628
        drawnNotification.setFeature(feat);
629
        drawnNotification.setDrawnGeometry(geom);
630
        notifyObservers(drawnNotification);
631
    }
632

    
633
    /**
634
     * Draws the features from the {@link FeatureSet}, with the symbols of the
635
     * legend, using a multiple drawing layer.
636
     */
637
    private void drawFeaturesMultiLayer(BufferedImage image, Graphics2D g,
638
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
639
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
640
        FeatureSet featureSet, FeatureSelection selection)
641
    throws MapContextException, CreateGeometryException, DataException {
642

    
643
        // -- visual FX stuff
644
        long time = System.currentTimeMillis();
645

    
646
        boolean bSymbolLevelError = false;
647
        int screenRefreshDelay = (int) ((1D / MapContext.getDrawFrameRate()) * 3 * 1000);
648
        BufferedImage[] imageLevels;
649
        Graphics2D[] graphics;
650

    
651
        imageLevels = new BufferedImage[getZSort().getLevelCount()];
652
        graphics = new Graphics2D[imageLevels.length];
653
        for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
654

    
655
            imageLevels[i] = CompatLocator.getGraphicsUtils()
656
            .createBufferedImage(image.getWidth(), image.getHeight(),
657
                image.getType());
658

    
659
            graphics[i] = imageLevels[i].createGraphics();
660
            graphics[i].setTransform(g.getTransform());
661
            graphics[i].setRenderingHints(g.getRenderingHints());
662
        }
663

    
664
        DisposableIterator it = null;
665
        try {
666
            it = featureSet.fastIterator();
667
            // Iteration over each feature
668
            while (it.hasNext()) {
669
                if (cancel.isCanceled()) {
670
                    return;
671
                }
672
                Feature feat = (Feature) it.next();
673

    
674
                bSymbolLevelError |= drawFeatureMultiLayer(image, g, viewPort,
675
                    cancel, coordTrans, dpi, drawnNotification, selection,
676
                    time, screenRefreshDelay, imageLevels, graphics, feat);
677

    
678
            }
679
        } catch (ConcurrentDataModificationException e) {
680
            cancel.setCanceled(true);
681
            return;
682
        } finally {
683
            DisposeUtils.dispose(it);
684
        }
685

    
686
        g.drawImage(image, 0, 0, null);
687

    
688
        Point2D offset = viewPort.getOffset();
689
        CompatLocator.getGraphicsUtils().translate(g, offset.getX(),
690
            offset.getY());
691

    
692
        for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
693
            g.drawImage(imageLevels[i], 0, 0, null);
694
            imageLevels[i] = null;
695
            graphics[i] = null;
696
        }
697

    
698
        CompatLocator.getGraphicsUtils().translate(g, -offset.getX(),
699
            -offset.getY());
700

    
701
        if (bSymbolLevelError) {
702
            setZSort(null);
703
        }
704
    }
705

    
706
    /**
707
     * Draws a Feature with the symbols of the legend, using a multiple drawing
708
     * layer.
709
     */
710
    private boolean drawFeatureMultiLayer(BufferedImage image, Graphics2D g,
711
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
712
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
713
        FeatureSelection selection, long time, int screenRefreshDelay,
714
        BufferedImage[] imageLevels, Graphics2D[] graphics, Feature feat)
715
    throws MapContextException, CreateGeometryException {
716

    
717
        Geometry geom = feat.getDefaultGeometry();
718
        boolean bSymbolLevelError = false;
719
        long drawingTime = time;
720

    
721
        if (geom==null || geom.getType() == Geometry.TYPES.NULL) {
722
            return false;
723
        }
724

    
725
        ISymbol sym = getSymbol(feat, selection);
726

    
727
        if (sym == null) {
728
            return false;
729
        }
730

    
731
        if (coordTrans != null) {
732
            geom = geom.cloneGeometry();
733
            geom.reProject(coordTrans);
734
        }
735

    
736
        if (cancel.isCanceled()) {
737
            return false;
738
        }
739

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

    
776
        // -- visual FX stuff
777
        // Cuando el offset!=0 se est? dibujando sobre el
778
        // Layout y por tanto no tiene que ejecutar el
779
        // siguiente c?digo.
780
        Point2D offset = viewPort.getOffset();
781
        if (offset.getX() == 0 && offset.getY() == 0) {
782
            if ((System.currentTimeMillis() - drawingTime) > screenRefreshDelay) {
783

    
784
                BufferedImage virtualBim = CompatLocator.getGraphicsUtils()
785
                .createBufferedImage(image.getWidth(),
786
                    image.getHeight(), BufferedImage.TYPE_INT_ARGB);
787

    
788
                Graphics2D virtualGraphics = virtualBim.createGraphics();
789
                virtualGraphics.drawImage(image, 0, 0, null);
790
                for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
791
                    virtualGraphics.drawImage(imageLevels[i], 0, 0, null);
792
                }
793
                g.clearRect(0, 0, image.getWidth(), image.getHeight());
794
                g.drawImage(virtualBim, 0, 0, null);
795
                drawingTime = System.currentTimeMillis();
796
            }
797
            // -- end visual FX stuff
798

    
799
        }
800
        // Notify the drawing observers
801
        drawnNotification.setFeature(feat);
802
        drawnNotification.setDrawnGeometry(geom);
803
        notifyObservers(drawnNotification);
804
        return bSymbolLevelError;
805
    }
806

    
807
    /**
808
     * Returns the symbol to use to draw a {@link Feature} taking into account
809
     * if it is selected.
810
     */
811
    private ISymbol getSymbol(Feature feat, FeatureSelection selection)
812
    throws MapContextException {
813
        // retrieve the symbol associated to such feature
814
        ISymbol sym = getSymbolByFeature(feat);
815

    
816
        if (sym != null && selection.isSelected(feat)) {
817
            sym = sym.getSymbolForSelection();
818
        }
819
        return sym;
820
    }
821

    
822
    /**
823
     * Returns if the legend is using a ZSort.
824
     */
825
    private boolean isUseZSort() {
826
        return getZSort() != null && getZSort().isUsingZSort();
827
    }
828

    
829
    /**
830
     * Draws a {@link Geometry} using the given {@link ISymbol}, over the
831
     * {@link Graphics2D} object.
832
     */
833
    private void drawGeometry(Geometry geom, BufferedImage image,
834
        Feature feature, ISymbol symbol, ViewPort viewPort,
835
        Graphics2D graphics, double dpi, Cancellable cancellable)
836
    throws CreateGeometryException {
837

    
838
        if (geom instanceof Aggregate) {
839
            drawAggregate((Aggregate) geom, image, feature, symbol, viewPort,
840
                graphics, dpi, cancellable);
841
        } else {
842
            boolean bDrawCartographicSupport = false;
843
            if (symbol instanceof CartographicSupport) {
844
                bDrawCartographicSupport = ((CartographicSupport) symbol)
845
                .getUnit() != -1;
846
            }
847

    
848
            double previousSize = 0.0d;
849

    
850
            if (bDrawCartographicSupport) {
851
                // make the symbol to resize itself with the current rendering
852
                // context
853
                previousSize = ((CartographicSupport) symbol)
854
                .toCartographicSize(viewPort, dpi, geom);
855
            }
856

    
857
            try {
858
                symbol.draw(graphics, viewPort.getAffineTransform(), geom,
859
                    feature, cancellable);
860
            } finally {
861
                if (bDrawCartographicSupport) {
862
                    // restore previous size
863
                    ((CartographicSupport) symbol).setCartographicSize(
864
                        previousSize, geom);
865
                }
866
            }
867
        }
868
    }
869

    
870
    /**
871
     * Draws an {@link Aggregate} using the given {@link ISymbol}, over the
872
     * {@link Graphics2D} object.
873
     */
874
    private void drawAggregate(Aggregate aggregate, BufferedImage image,
875
        Feature feature, ISymbol symbol, ViewPort viewPort,
876
        Graphics2D graphics, double dpi, Cancellable cancellable)
877
    throws CreateGeometryException {
878
        for (int i = 0; i < aggregate.getPrimitivesNumber(); i++) {
879
            Geometry prim = aggregate.getPrimitiveAt(i);
880
            drawGeometry(prim, image, feature, symbol, viewPort, graphics, dpi,
881
                cancellable);
882
        }
883
    }
884

    
885
    @Override
886
    public Object clone() throws CloneNotSupportedException {
887
        AbstractVectorialLegend clone = (AbstractVectorialLegend) super.clone();
888

    
889
        // Clone zSort
890
        ZSort zSort = getZSort();
891
        if (zSort != null) {
892
            clone.setZSort(new ZSort(clone));
893
        }
894
        return clone;
895
    }
896

    
897
    @Override
898
    public void loadFromState(PersistentState state)
899
    throws PersistenceException {
900
        // Set parent properties
901
        super.loadFromState(state);
902
        // Set own properties
903

    
904
        setShapeType(state.getInt(FIELD_SHAPETYPE));
905
        if (state.getBoolean(FIELD_HAS_ZSORT)) {
906
            setZSort(new ZSort(this));
907
        }
908
        setDefaultSymbol((ISymbol) state.get(FIELD_DEFAULT_SYMBOL));
909
    }
910

    
911
    @Override
912
    public void saveToState(PersistentState state) throws PersistenceException {
913
        // Save parent properties
914
        super.saveToState(state);
915
        // Save own properties
916
        state.set(FIELD_SHAPETYPE, getShapeType());
917
        state.set(FIELD_HAS_ZSORT, this.zSort != null);
918
        state.set(FIELD_DEFAULT_SYMBOL, getDefaultSymbol());
919
    }
920

    
921
    public static class RegisterPersistence implements Callable {
922

    
923
        @Override
924
        public Object call() throws Exception {
925
            PersistenceManager manager = ToolsLocator.getPersistenceManager();
926
            if (manager
927
                .getDefinition(VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME) == null) {
928
                DynStruct definition = manager.addDefinition(
929
                    AbstractVectorialLegend.class,
930
                    VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME,
931
                    VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME
932
                    + " persistence definition", null, null);
933
                // Extend the Legend base definition
934
                definition.extend(manager.getDefinition(LEGEND_PERSISTENCE_DEFINITION_NAME));
935

    
936
                // Shapetype
937
                definition.addDynFieldInt(FIELD_SHAPETYPE).setMandatory(true);
938
                // ZSort
939
                definition.addDynFieldBoolean(FIELD_HAS_ZSORT).setMandatory(true);
940
                // Default symbol
941
                definition.addDynFieldObject(FIELD_DEFAULT_SYMBOL)
942
                    .setClassOfValue(ISymbol.class).setMandatory(true);
943
            }
944
            return Boolean.TRUE;
945
        }
946

    
947
    }
948

    
949
    /**
950
     * Returns the names of the {@link Feature} attributes required for the
951
     * {@link ILegend} to operate.
952
     *
953
     * @param featureStore
954
     *            the store where the {@link Feature}s belong to
955
     * @return the names of required {@link Feature} attribute names
956
     * @throws DataException
957
     *             if there is an error getting the attribute names
958
     */
959
    protected abstract String[] getRequiredFeatureAttributeNames(
960
        FeatureStore featureStore) throws DataException;
961
}