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

History | View | Annotate | Download (35.4 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
        
163
        double dpi = 72;
164

    
165
        int resolution = properties.getPrintQuality();
166

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

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

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

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

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

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

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

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

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

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

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

    
295
                            csSym = (CartographicSupport) sym;
296
                        }
297

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

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

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

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

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

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

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

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

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

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

    
415

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

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

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

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

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

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

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

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

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

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

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

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

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

    
484
        featureQuery.setScale(scale);
485

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

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

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

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

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

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

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

    
568
        } catch (ConcurrentDataModificationException e) {
569
            cancel.setCanceled(true);
570
        }
571
    }
572

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

    
583
        Geometry geom = feat.getDefaultGeometry();
584
        if (geom == null) {
585
            return;
586
        }
587

    
588
        if (geom.getType() == Geometry.TYPES.NULL) {
589
            return;
590
        }
591

    
592
        ISymbol sym = getSymbol(feat, selection);
593
        if (sym == null) {
594
            return;
595
        }
596

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

    
607
        if (cancel.isCanceled()) {
608
            return;
609
        }
610

    
611
        drawGeometry(geom, image, feat, sym, viewPort, g, dpi, cancel);
612

    
613
        // Notify the drawing observers
614
        drawnNotification.setFeature(feat);
615
        drawnNotification.setDrawnGeometry(geom);
616
        notifyObservers(drawnNotification);
617
    }
618

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

    
629
        // -- visual FX stuff
630
        long time = System.currentTimeMillis();
631

    
632
        boolean bSymbolLevelError = false;
633
        int screenRefreshDelay = (int) ((1D / MapContext.getDrawFrameRate()) * 3 * 1000);
634
        BufferedImage[] imageLevels;
635
        Graphics2D[] graphics;
636

    
637
        imageLevels = new BufferedImage[getZSort().getLevelCount()];
638
        graphics = new Graphics2D[imageLevels.length];
639
        for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
640

    
641
            imageLevels[i] = CompatLocator.getGraphicsUtils()
642
            .createBufferedImage(image.getWidth(), image.getHeight(),
643
                image.getType());
644

    
645
            graphics[i] = imageLevels[i].createGraphics();
646
            graphics[i].setTransform(g.getTransform());
647
            graphics[i].setRenderingHints(g.getRenderingHints());
648
        }
649

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

    
660
                bSymbolLevelError |= drawFeatureMultiLayer(image, g, viewPort,
661
                    cancel, coordTrans, dpi, drawnNotification, selection,
662
                    time, screenRefreshDelay, imageLevels, graphics, feat);
663

    
664
            }
665
        } catch (ConcurrentDataModificationException e) {
666
            cancel.setCanceled(true);
667
            return;
668
        } finally {
669
            DisposeUtils.dispose(it);
670
        }
671

    
672
        g.drawImage(image, 0, 0, null);
673

    
674
        Point2D offset = viewPort.getOffset();
675
        CompatLocator.getGraphicsUtils().translate(g, offset.getX(),
676
            offset.getY());
677

    
678
        for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
679
            g.drawImage(imageLevels[i], 0, 0, null);
680
            imageLevels[i] = null;
681
            graphics[i] = null;
682
        }
683

    
684
        CompatLocator.getGraphicsUtils().translate(g, -offset.getX(),
685
            -offset.getY());
686

    
687
        if (bSymbolLevelError) {
688
            setZSort(null);
689
        }
690
    }
691

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

    
703
        Geometry geom = feat.getDefaultGeometry();
704
        boolean bSymbolLevelError = false;
705
        long drawingTime = time;
706

    
707
        if (geom==null || geom.getType() == Geometry.TYPES.NULL) {
708
            return false;
709
        }
710

    
711
        ISymbol sym = getSymbol(feat, selection);
712

    
713
        if (sym == null) {
714
            return false;
715
        }
716

    
717
        if (coordTrans != null) {
718
            geom = geom.cloneGeometry();
719
            geom.reProject(coordTrans);
720
        }
721

    
722
        if (cancel.isCanceled()) {
723
            return false;
724
        }
725

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

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

    
770
                BufferedImage virtualBim = CompatLocator.getGraphicsUtils()
771
                .createBufferedImage(image.getWidth(),
772
                    image.getHeight(), BufferedImage.TYPE_INT_ARGB);
773

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

    
785
        }
786
        // Notify the drawing observers
787
        drawnNotification.setFeature(feat);
788
        drawnNotification.setDrawnGeometry(geom);
789
        notifyObservers(drawnNotification);
790
        return bSymbolLevelError;
791
    }
792

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

    
802
        if (sym != null && selection.isSelected(feat)) {
803
            sym = sym.getSymbolForSelection();
804
        }
805
        return sym;
806
    }
807

    
808
    /**
809
     * Returns if the legend is using a ZSort.
810
     */
811
    private boolean isUseZSort() {
812
        return getZSort() != null && getZSort().isUsingZSort();
813
    }
814

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

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

    
834
            double previousSize = 0.0d;
835

    
836
            if (bDrawCartographicSupport) {
837
                // make the symbol to resize itself with the current rendering
838
                // context
839
                previousSize = ((CartographicSupport) symbol)
840
                .toCartographicSize(viewPort, dpi, geom);
841
            }
842

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

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

    
871
    @Override
872
    public Object clone() throws CloneNotSupportedException {
873
        AbstractVectorialLegend clone = (AbstractVectorialLegend) super.clone();
874

    
875
        // Clone zSort
876
        ZSort zSort = getZSort();
877
        if (zSort != null) {
878
            clone.setZSort(new ZSort(clone));
879
        }
880
        return clone;
881
    }
882

    
883
    @Override
884
    public void loadFromState(PersistentState state)
885
    throws PersistenceException {
886
        // Set parent properties
887
        super.loadFromState(state);
888
        // Set own properties
889

    
890
        setShapeType(state.getInt(FIELD_SHAPETYPE));
891
        if (state.getBoolean(FIELD_HAS_ZSORT)) {
892
            setZSort(new ZSort(this));
893
        }
894
        setDefaultSymbol((ISymbol) state.get(FIELD_DEFAULT_SYMBOL));
895
    }
896

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

    
907
    public static class RegisterPersistence implements Callable {
908

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

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

    
933
    }
934

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