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

History | View | Annotate | Download (35.2 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.feature.Feature;
46
import org.gvsig.fmap.dal.feature.FeatureQuery;
47
import org.gvsig.fmap.dal.feature.FeatureReference;
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.exception.ConcurrentDataModificationException;
52
import org.gvsig.fmap.geom.Geometry;
53
import org.gvsig.fmap.geom.GeometryLocator;
54
import org.gvsig.fmap.geom.GeometryManager;
55
import org.gvsig.fmap.geom.aggregate.Aggregate;
56
import org.gvsig.fmap.geom.exception.CreateGeometryException;
57
import org.gvsig.fmap.geom.exception.ReprojectionRuntimeException;
58
import org.gvsig.fmap.geom.primitive.Envelope;
59
import org.gvsig.fmap.mapcontext.MapContext;
60
import org.gvsig.fmap.mapcontext.MapContextException;
61
import org.gvsig.fmap.mapcontext.ViewPort;
62
import org.gvsig.fmap.mapcontext.layers.vectorial.SpatialEvaluatorsFactory;
63
import org.gvsig.fmap.mapcontext.rendering.legend.ILegend;
64
import org.gvsig.fmap.mapcontext.rendering.legend.IVectorLegend;
65
import org.gvsig.fmap.mapcontext.rendering.legend.LegendException;
66
import org.gvsig.fmap.mapcontext.rendering.legend.ZSort;
67
import org.gvsig.fmap.mapcontext.rendering.symbols.CartographicSupport;
68
import org.gvsig.fmap.mapcontext.rendering.symbols.IMultiLayerSymbol;
69
import org.gvsig.fmap.mapcontext.rendering.symbols.ISymbol;
70
import org.gvsig.tools.ToolsLocator;
71
import org.gvsig.tools.dispose.DisposableIterator;
72
import org.gvsig.tools.dispose.DisposeUtils;
73
import org.gvsig.tools.dynobject.DynStruct;
74
import org.gvsig.tools.evaluator.Evaluator;
75
import org.gvsig.tools.exception.BaseException;
76
import org.gvsig.tools.logger.FilteredLogger;
77
import org.gvsig.tools.persistence.PersistenceManager;
78
import org.gvsig.tools.persistence.PersistentState;
79
import org.gvsig.tools.persistence.exception.PersistenceException;
80
import org.gvsig.tools.task.Cancellable;
81
import org.gvsig.tools.task.SimpleTaskStatus;
82
import org.gvsig.tools.util.Callable;
83
import org.gvsig.tools.visitor.VisitCanceledException;
84
import org.gvsig.tools.visitor.Visitor;
85

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

    
96
        protected static final Logger LOG = LoggerFactory
97
                        .getLogger(AbstractVectorialLegend.class);
98

    
99
    private static final int DRAW_MAX_ATTEMPTS = 5;
100

    
101
        public static final String VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME = "VectorialLegend";
102

    
103
    private static final String FIELD_HAS_ZSORT = "hasZSort";
104
    private static final String FIELD_SHAPETYPE = "shapeType";
105
    private static final String FIELD_DEFAULT_SYMBOL = "defaultSymbol";
106

    
107
//    private static final GeometryManager geomManager = GeometryLocator
108
//    .getGeometryManager();
109

    
110
    protected ZSort zSort;
111

    
112
    public ZSort getZSort() {
113
        return zSort;
114
    }
115

    
116
    public void setZSort(ZSort zSort) {
117
        if (zSort == null) {
118
            removeLegendListener(this.zSort);
119
        }
120
        this.zSort = zSort;
121
        addLegendListener(zSort);
122
    }
123

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

    
135
    @SuppressWarnings("unchecked")
136
    @Override
137
    public void draw(BufferedImage image, Graphics2D g, ViewPort viewPort,
138
        Cancellable cancel, double scale, Map queryParameters,
139
        ICoordTrans coordTrans, FeatureStore featureStore, FeatureQuery featureQuery)
140
    throws LegendException {
141
        double dpi = viewPort.getDPI();
142
        draw(image, g, viewPort, cancel, scale, queryParameters, coordTrans,
143
            featureStore, featureQuery, dpi);
144
    }
145
    
146
    @SuppressWarnings("unchecked")
147
    @Override
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
    @Override
158
    public void print(Graphics2D g, ViewPort viewPort, Cancellable cancel,
159
        double scale, Map queryParameters, ICoordTrans coordTrans,
160
        FeatureStore featureStore, FeatureQuery fquery, PrintAttributes properties)
161
    throws LegendException {
162
        double dpi = 72;
163

    
164
        int resolution = properties.getPrintQuality();
165

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

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

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

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

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

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

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

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

    
277
                                } else {
278
                                    // else, just draw the symbol in its level
279
                                    if (symLevels[0] != mapPass) {
280
                                        continue;
281
                                    }
282
                                }
283
                            }
284
                        }
285

    
286
                        // Check if this symbol is sized with CartographicSupport
287
                        CartographicSupport csSym = null;
288
                        int symbolType = sym.getSymbolType();
289

    
290
                        if (symbolType == Geometry.TYPES.POINT
291
                            ||  geomManager.isSubtype(symbolType, Geometry.TYPES.CURVE)
292
                            || sym instanceof CartographicSupport) {
293

    
294
                            csSym = (CartographicSupport) sym;
295
                        }
296

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

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

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

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

    
368
                if (!getDefaultSymbol().isShapeVisible()) {
369
                        return;
370
                }
371

    
372
                if (cancel.isCanceled()) {
373
                        return;
374
                }
375

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

    
388
                                // If the data does not provide a projection, use the
389
                                // current view one
390
                                if (dataProjection == null) {
391
                                        dataProjection = viewPort.getProjection();
392
                                }
393

    
394
                                reprojectedDataEnvelope = dataEnvelope;
395
                reprojectedViewPortEnvelope = viewPortEnvelope;
396
                        } else {
397
                                dataProjection = coordTrans.getPOrig();
398

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

    
414

    
415
                // Gets the data envelope with the viewport SRS
416
//                Envelope myEnvelope = reprojectedDataEnvelope;
417

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

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

    
431
                // Create the drawing notification to be reused on each iteration
432
                DefaultFeatureDrawnNotification drawnNotification = new DefaultFeatureDrawnNotification();
433

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

    
438
                FeatureSet featureSet = null;
439
                try {
440
                        taskStatus.message("Retrieve selection");
441
                        FeatureSelection selection = featureStore.getFeatureSelection();
442

    
443
                        if (featureQuery == null) {
444
                                featureQuery = featureStore.createFeatureQuery();
445
                        }
446

    
447
                        completeQuery(featureStore, featureQuery, scale, queryParameters,
448
                                        coordTrans, dataProjection, viewPortEnvelope, containsAll);
449

    
450
                        taskStatus.message("Retrieve data");
451
                        featureSet = featureStore.getFeatureSet(featureQuery);
452

    
453
                        if (cancel.isCanceled()) {
454
                                return;
455
                        }
456

    
457
                        taskStatus.message("Drawing");
458
                        drawFeatures(image, g, viewPort, cancel, coordTrans, dpi,
459
                                        drawnNotification, featureSet, selection);
460

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

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

    
483
        featureQuery.setScale(scale);
484

    
485
        //Adds the attributes
486
        String[] fieldNames = getRequiredFeatureAttributeNames(featureStore);
487
        for (int i=0 ; i<fieldNames.length ;i++){
488
            featureQuery.addAttributeName(fieldNames[i]);
489
        }
490

    
491
        if (!containsAll) {
492
            // Gets the viewport envelope with the data SRS
493
            Envelope viewPortEnvelopeInMyProj = viewPortEnvelope;
494
            if (coordTrans != null) {
495
                viewPortEnvelopeInMyProj = viewPortEnvelope.convert(coordTrans
496
                        .getInverted());
497
            }
498

    
499
            if (dataProjection == null) {
500
                throw new IllegalArgumentException(
501
                "Error, the projection parameter value is null");
502
            }
503

    
504
            Evaluator iee = SpatialEvaluatorsFactory.getInstance().intersects(
505
                    viewPortEnvelopeInMyProj,
506
                    dataProjection,
507
                    featureStore
508
            );
509
            featureQuery.addFilter(iee);
510
        }
511
        if (queryParameters != null) {
512
            Iterator iterEntry = queryParameters.entrySet().iterator();
513
            Entry entry;
514
            while (iterEntry.hasNext()) {
515
                entry = (Entry) iterEntry.next();
516
                featureQuery.setQueryParameter((String) entry.getKey(),
517
                    entry.getValue());
518
            }
519
        }
520
        return featureQuery;
521
    }
522

    
523
    /**
524
     * Draws the features from the {@link FeatureSet}, with the symbols of the
525
     * legend.
526
     */
527
    protected void drawFeatures(BufferedImage image, Graphics2D g,
528
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
529
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
530
        FeatureSet featureSet, FeatureSelection selection)
531
    throws BaseException {
532
        if (isUseZSort()) {
533
            drawFeaturesMultiLayer(image, g, viewPort, cancel, coordTrans, dpi,
534
                drawnNotification, featureSet, selection);
535
        } else {
536
            drawFeaturesSingleLayer(image, g, viewPort, cancel, coordTrans,
537
                dpi, drawnNotification, featureSet, selection);
538
        }
539
    }
540

    
541
    /**
542
     * Draws the features from the {@link FeatureSet}, with the symbols of the
543
     * legend, using a single drawing layer.
544
     */
545
    private void drawFeaturesSingleLayer(final BufferedImage image,
546
        final Graphics2D g, final ViewPort viewPort,
547
        final Cancellable cancel, final ICoordTrans coordTrans,
548
        final double dpi,
549
        final DefaultFeatureDrawnNotification drawnNotification,
550
        FeatureSet featureSet, final FeatureSelection selection)
551
    throws BaseException {
552

    
553
        try {
554
            featureSet.accept(new Visitor() {
555
                @Override
556
                public void visit(Object obj) throws VisitCanceledException,
557
                BaseException {
558
                    Feature feat = (Feature) obj;
559
                    drawFeatureSingleLayer(image, g, viewPort, cancel,
560
                        coordTrans, dpi, drawnNotification, feat, selection);
561
                }
562
            });
563

    
564
        } catch (ConcurrentDataModificationException e) {
565
            cancel.setCanceled(true);
566
        }
567
    }
568

    
569
    /**
570
     * Draws a Feature with the symbols of the legend, using a single drawing
571
     * layer.
572
     */
573
    private void drawFeatureSingleLayer(BufferedImage image, Graphics2D g,
574
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
575
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
576
        Feature feat, FeatureSelection selection)
577
    throws MapContextException, CreateGeometryException {
578

    
579
        Geometry geom = feat.getDefaultGeometry();
580
        if (geom == null) {
581
            return;
582
        }
583

    
584
        if (geom.getType() == Geometry.TYPES.NULL) {
585
            return;
586
        }
587

    
588
        ISymbol sym = getSymbol(feat, selection);
589
        if (sym == null) {
590
            return;
591
        }
592

    
593
        if (coordTrans != null) {
594
            geom = geom.cloneGeometry();
595
            try {
596
                geom.reProject(coordTrans);
597
            } catch (ReprojectionRuntimeException re) {
598
                LOG.warn("Can't reproject geometry "+geom.toString(), re);
599
                return;
600
            }
601
        }
602

    
603
        if (cancel.isCanceled()) {
604
            return;
605
        }
606

    
607
        drawGeometry(geom, image, feat, sym, viewPort, g, dpi, cancel);
608

    
609
        // Notify the drawing observers
610
        drawnNotification.setFeature(feat);
611
        drawnNotification.setDrawnGeometry(geom);
612
        notifyObservers(drawnNotification);
613
    }
614

    
615
    /**
616
     * Draws the features from the {@link FeatureSet}, with the symbols of the
617
     * legend, using a multiple drawing layer.
618
     */
619
    private void drawFeaturesMultiLayer(BufferedImage image, Graphics2D g,
620
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
621
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
622
        FeatureSet featureSet, FeatureSelection selection)
623
    throws MapContextException, CreateGeometryException, DataException {
624

    
625
        // -- visual FX stuff
626
        long time = System.currentTimeMillis();
627

    
628
        boolean bSymbolLevelError = false;
629
        int screenRefreshDelay = (int) ((1D / MapContext.getDrawFrameRate()) * 3 * 1000);
630
        BufferedImage[] imageLevels;
631
        Graphics2D[] graphics;
632

    
633
        imageLevels = new BufferedImage[getZSort().getLevelCount()];
634
        graphics = new Graphics2D[imageLevels.length];
635
        for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
636

    
637
            imageLevels[i] = CompatLocator.getGraphicsUtils()
638
            .createBufferedImage(image.getWidth(), image.getHeight(),
639
                image.getType());
640

    
641
            graphics[i] = imageLevels[i].createGraphics();
642
            graphics[i].setTransform(g.getTransform());
643
            graphics[i].setRenderingHints(g.getRenderingHints());
644
        }
645

    
646
        DisposableIterator it = null;
647
        try {
648
            it = featureSet.fastIterator();
649
            // Iteration over each feature
650
            while (it.hasNext()) {
651
                if (cancel.isCanceled()) {
652
                    return;
653
                }
654
                Feature feat = (Feature) it.next();
655

    
656
                bSymbolLevelError |= drawFeatureMultiLayer(image, g, viewPort,
657
                    cancel, coordTrans, dpi, drawnNotification, selection,
658
                    time, screenRefreshDelay, imageLevels, graphics, feat);
659

    
660
            }
661
        } catch (ConcurrentDataModificationException e) {
662
            cancel.setCanceled(true);
663
            return;
664
        } finally {
665
            DisposeUtils.dispose(it);
666
        }
667

    
668
        g.drawImage(image, 0, 0, null);
669

    
670
        Point2D offset = viewPort.getOffset();
671
        CompatLocator.getGraphicsUtils().translate(g, offset.getX(),
672
            offset.getY());
673

    
674
        for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
675
            g.drawImage(imageLevels[i], 0, 0, null);
676
            imageLevels[i] = null;
677
            graphics[i] = null;
678
        }
679

    
680
        CompatLocator.getGraphicsUtils().translate(g, -offset.getX(),
681
            -offset.getY());
682

    
683
        if (bSymbolLevelError) {
684
            setZSort(null);
685
        }
686
    }
687

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

    
699
        Geometry geom = feat.getDefaultGeometry();
700
        boolean bSymbolLevelError = false;
701
        long drawingTime = time;
702

    
703
        if (geom==null || geom.getType() == Geometry.TYPES.NULL) {
704
            return false;
705
        }
706

    
707
        ISymbol sym = getSymbol(feat, selection);
708

    
709
        if (sym == null) {
710
            return false;
711
        }
712

    
713
        if (coordTrans != null) {
714
            geom = geom.cloneGeometry();
715
            geom.reProject(coordTrans);
716
        }
717

    
718
        if (cancel.isCanceled()) {
719
            return false;
720
        }
721

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

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

    
766
                BufferedImage virtualBim = CompatLocator.getGraphicsUtils()
767
                .createBufferedImage(image.getWidth(),
768
                    image.getHeight(), BufferedImage.TYPE_INT_ARGB);
769

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

    
781
        }
782
        // Notify the drawing observers
783
        drawnNotification.setFeature(feat);
784
        drawnNotification.setDrawnGeometry(geom);
785
        notifyObservers(drawnNotification);
786
        return bSymbolLevelError;
787
    }
788

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

    
798
        if (sym != null && selection.isSelected(feat)) {
799
            sym = sym.getSymbolForSelection();
800
        }
801
        return sym;
802
    }
803

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

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

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

    
830
            double previousSize = 0.0d;
831

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

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

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

    
867
    @Override
868
    public Object clone() throws CloneNotSupportedException {
869
        AbstractVectorialLegend clone = (AbstractVectorialLegend) super.clone();
870

    
871
        // Clone zSort
872
        ZSort zSort = getZSort();
873
        if (zSort != null) {
874
            clone.setZSort(new ZSort(clone));
875
        }
876
        return clone;
877
    }
878

    
879
    @Override
880
    public void loadFromState(PersistentState state)
881
    throws PersistenceException {
882
        // Set parent properties
883
        super.loadFromState(state);
884
        // Set own properties
885

    
886
        setShapeType(state.getInt(FIELD_SHAPETYPE));
887
        if (state.getBoolean(FIELD_HAS_ZSORT)) {
888
            setZSort(new ZSort(this));
889
        }
890
        setDefaultSymbol((ISymbol) state.get(FIELD_DEFAULT_SYMBOL));
891
    }
892

    
893
    @Override
894
    public void saveToState(PersistentState state) throws PersistenceException {
895
        // Save parent properties
896
        super.saveToState(state);
897
        // Save own properties
898
        state.set(FIELD_SHAPETYPE, getShapeType());
899
        state.set(FIELD_HAS_ZSORT, this.zSort != null);
900
        state.set(FIELD_DEFAULT_SYMBOL, getDefaultSymbol());
901
    }
902

    
903
    public static class RegisterPersistence implements Callable {
904

    
905
        @Override
906
        public Object call() throws Exception {
907
            PersistenceManager manager = ToolsLocator.getPersistenceManager();
908
            if (manager
909
                .getDefinition(VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME) == null) {
910
                DynStruct definition = manager.addDefinition(
911
                    AbstractVectorialLegend.class,
912
                    VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME,
913
                    VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME
914
                    + " persistence definition", null, null);
915
                // Extend the Legend base definition
916
                definition.extend(manager.getDefinition(LEGEND_PERSISTENCE_DEFINITION_NAME));
917

    
918
                // Shapetype
919
                definition.addDynFieldInt(FIELD_SHAPETYPE).setMandatory(true);
920
                // ZSort
921
                definition.addDynFieldBoolean(FIELD_HAS_ZSORT).setMandatory(true);
922
                // Default symbol
923
                definition.addDynFieldObject(FIELD_DEFAULT_SYMBOL)
924
                    .setClassOfValue(ISymbol.class).setMandatory(true);
925
            }
926
            return Boolean.TRUE;
927
        }
928

    
929
    }
930

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