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

History | View | Annotate | Download (36.3 KB)

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

    
30
import java.awt.Graphics2D;
31
import java.awt.geom.Point2D;
32
import java.awt.image.BufferedImage;
33
import java.util.Iterator;
34
import java.util.Map;
35
import java.util.Map.Entry;
36
import org.apache.commons.lang3.StringUtils;
37

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

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

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

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

    
103
    private static final int DRAW_MAX_ATTEMPTS = 5;
104

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

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

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

    
114
    protected ZSort zSort;
115

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

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

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

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

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

    
169
        int resolution = properties.getPrintQuality();
170

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

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

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

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

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

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

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

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

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

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

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

    
299
                            csSym = (CartographicSupport) sym;
300
                        }
301

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

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

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

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

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

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

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

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

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

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

    
419

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

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

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

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

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

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

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

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

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

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

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

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

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

    
489
        featureQuery.setScale(scale);
490

    
491
        //Adds the attributes
492
        String[] fieldNames = getRequiredFeatureAttributeNames(featureStore);
493
        for (int i=0 ; i<fieldNames.length ;i++){
494
            String fieldName = fieldNames[i];
495
            if( StringUtils.isNotBlank(fieldName) ) {
496
                featureQuery.addAttributeName(fieldName);
497
            }
498
        }
499

    
500
        if (!containsAll) {
501
            // Gets the viewport envelope with the data SRS
502
            Envelope viewPortEnvelopeInMyProj = viewPortEnvelope;
503
            if (coordTrans != null) {
504
                viewPortEnvelopeInMyProj = viewPortEnvelope.convert(coordTrans
505
                        .getInverted());
506
            }
507

    
508
            if (dataProjection == null) {
509
                throw new IllegalArgumentException(
510
                "Error, the projection parameter value is null");
511
            }
512

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

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

    
564
    /**
565
     * Draws the features from the {@link FeatureSet}, with the symbols of the
566
     * legend, using a single drawing layer.
567
     */
568
    private void drawFeaturesSingleLayer(final BufferedImage image,
569
        final Graphics2D g, final ViewPort viewPort,
570
        final Cancellable cancel, final ICoordTrans coordTrans,
571
        final double dpi,
572
        final DefaultFeatureDrawnNotification drawnNotification,
573
        FeatureSet featureSet, final FeatureSelection selection)
574
    throws BaseException {
575

    
576
        try {
577
            featureSet.accept(new Visitor() {
578
                @Override
579
                public void visit(Object obj) throws VisitCanceledException,
580
                BaseException {
581
                    if( cancel.isCanceled() )  {
582
                        throw new VisitCanceledException();
583
                    }
584
                    Feature feat = (Feature) obj;
585
                    drawFeatureSingleLayer(image, g, viewPort, cancel,
586
                        coordTrans, dpi, drawnNotification, feat, selection);
587
                }
588
            });
589

    
590
        } catch (ConcurrentDataModificationException e) {
591
            cancel.setCanceled(true);
592
        }
593
    }
594

    
595
    /**
596
     * Draws a Feature with the symbols of the legend, using a single drawing
597
     * layer.
598
     */
599
    private void drawFeatureSingleLayer(BufferedImage image, Graphics2D g,
600
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
601
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
602
        Feature feat, FeatureSelection selection)
603
    throws MapContextException, CreateGeometryException {
604

    
605
        Geometry geom = feat.getDefaultGeometry();
606
        if (geom == null) {
607
            return;
608
        }
609

    
610
        if (geom.getType() == Geometry.TYPES.NULL) {
611
            return;
612
        }
613

    
614
        ISymbol sym = getSymbol(feat, selection);
615
        if (sym == null) {
616
            return;
617
        }
618

    
619
        if (coordTrans != null) {
620
            geom = geom.cloneGeometry();
621
            try {
622
                geom.reProject(coordTrans);
623
            } catch (ReprojectionRuntimeException re) {
624
                LOG.warn("Can't reproject geometry "+geom.toString(), re);
625
                return;
626
            }
627
        }
628

    
629
        if (cancel.isCanceled()) {
630
            return;
631
        }
632

    
633
        drawGeometry(geom, image, feat, sym, viewPort, g, dpi, cancel);
634

    
635
        // Notify the drawing observers
636
        drawnNotification.setFeature(feat);
637
        drawnNotification.setDrawnGeometry(geom);
638
        notifyObservers(drawnNotification);
639
    }
640

    
641
    /**
642
     * Draws the features from the {@link FeatureSet}, with the symbols of the
643
     * legend, using a multiple drawing layer.
644
     */
645
    private void drawFeaturesMultiLayer(BufferedImage image, Graphics2D g,
646
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
647
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
648
        FeatureSet featureSet, FeatureSelection selection)
649
    throws MapContextException, CreateGeometryException, DataException {
650

    
651
        // -- visual FX stuff
652
        long time = System.currentTimeMillis();
653

    
654
        boolean bSymbolLevelError = false;
655
        int screenRefreshDelay = (int) ((1D / MapContext.getDrawFrameRate()) * 3 * 1000);
656
        BufferedImage[] imageLevels;
657
        Graphics2D[] graphics;
658

    
659
        imageLevels = new BufferedImage[getZSort().getLevelCount()];
660
        graphics = new Graphics2D[imageLevels.length];
661
        for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
662

    
663
            imageLevels[i] = CompatLocator.getGraphicsUtils()
664
            .createBufferedImage(image.getWidth(), image.getHeight(),
665
                image.getType());
666

    
667
            graphics[i] = imageLevels[i].createGraphics();
668
            graphics[i].setTransform(g.getTransform());
669
            graphics[i].setRenderingHints(g.getRenderingHints());
670
        }
671

    
672
        DisposableIterator it = null;
673
        try {
674
            it = featureSet.fastIterator();
675
            // Iteration over each feature
676
            while (it.hasNext()) {
677
                if (cancel.isCanceled()) {
678
                    return;
679
                }
680
                Feature feat = (Feature) it.next();
681

    
682
                bSymbolLevelError |= drawFeatureMultiLayer(image, g, viewPort,
683
                    cancel, coordTrans, dpi, drawnNotification, selection,
684
                    time, screenRefreshDelay, imageLevels, graphics, feat);
685

    
686
            }
687
        } catch (ConcurrentDataModificationException e) {
688
            cancel.setCanceled(true);
689
            return;
690
        } finally {
691
            DisposeUtils.dispose(it);
692
        }
693

    
694
        g.drawImage(image, 0, 0, null);
695

    
696
        Point2D offset = viewPort.getOffset();
697
        CompatLocator.getGraphicsUtils().translate(g, offset.getX(),
698
            offset.getY());
699

    
700
        for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
701
            g.drawImage(imageLevels[i], 0, 0, null);
702
            imageLevels[i] = null;
703
            graphics[i] = null;
704
        }
705

    
706
        CompatLocator.getGraphicsUtils().translate(g, -offset.getX(),
707
            -offset.getY());
708

    
709
        if (bSymbolLevelError) {
710
            setZSort(null);
711
        }
712
    }
713

    
714
    /**
715
     * Draws a Feature with the symbols of the legend, using a multiple drawing
716
     * layer.
717
     */
718
    private boolean drawFeatureMultiLayer(BufferedImage image, Graphics2D g,
719
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
720
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
721
        FeatureSelection selection, long time, int screenRefreshDelay,
722
        BufferedImage[] imageLevels, Graphics2D[] graphics, Feature feat)
723
    throws MapContextException, CreateGeometryException {
724

    
725
        Geometry geom = feat.getDefaultGeometry();
726
        boolean bSymbolLevelError = false;
727
        long drawingTime = time;
728

    
729
        if (geom==null || geom.getType() == Geometry.TYPES.NULL) {
730
            return false;
731
        }
732

    
733
        ISymbol sym = getSymbol(feat, selection);
734

    
735
        if (sym == null) {
736
            return false;
737
        }
738

    
739
        if (coordTrans != null) {
740
            geom = geom.cloneGeometry();
741
            geom.reProject(coordTrans);
742
        }
743

    
744
        if (cancel.isCanceled()) {
745
            return false;
746
        }
747

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

    
784
        // -- visual FX stuff
785
        // Cuando el offset!=0 se est? dibujando sobre el
786
        // Layout y por tanto no tiene que ejecutar el
787
        // siguiente c?digo.
788
        Point2D offset = viewPort.getOffset();
789
        if (offset.getX() == 0 && offset.getY() == 0) {
790
            if ((System.currentTimeMillis() - drawingTime) > screenRefreshDelay) {
791

    
792
                BufferedImage virtualBim = CompatLocator.getGraphicsUtils()
793
                .createBufferedImage(image.getWidth(),
794
                    image.getHeight(), BufferedImage.TYPE_INT_ARGB);
795

    
796
                Graphics2D virtualGraphics = virtualBim.createGraphics();
797
                virtualGraphics.drawImage(image, 0, 0, null);
798
                for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
799
                    virtualGraphics.drawImage(imageLevels[i], 0, 0, null);
800
                }
801
                g.clearRect(0, 0, image.getWidth(), image.getHeight());
802
                g.drawImage(virtualBim, 0, 0, null);
803
                drawingTime = System.currentTimeMillis();
804
            }
805
            // -- end visual FX stuff
806

    
807
        }
808
        // Notify the drawing observers
809
        drawnNotification.setFeature(feat);
810
        drawnNotification.setDrawnGeometry(geom);
811
        notifyObservers(drawnNotification);
812
        return bSymbolLevelError;
813
    }
814

    
815
    /**
816
     * Returns the symbol to use to draw a {@link Feature} taking into account
817
     * if it is selected.
818
     */
819
    private ISymbol getSymbol(Feature feat, FeatureSelection selection)
820
    throws MapContextException {
821
        // retrieve the symbol associated to such feature
822
        ISymbol sym = getSymbolByFeature(feat);
823

    
824
        if (sym != null && selection.isSelected(feat)) {
825
            sym = sym.getSymbolForSelection();
826
        }
827
        return sym;
828
    }
829

    
830
    /**
831
     * Returns if the legend is using a ZSort.
832
     */
833
    private boolean isUseZSort() {
834
        return getZSort() != null && getZSort().isUsingZSort();
835
    }
836

    
837
    /**
838
     * Draws a {@link Geometry} using the given {@link ISymbol}, over the
839
     * {@link Graphics2D} object.
840
     */
841
    private void drawGeometry(Geometry geom, BufferedImage image,
842
        Feature feature, ISymbol symbol, ViewPort viewPort,
843
        Graphics2D graphics, double dpi, Cancellable cancellable)
844
    throws CreateGeometryException {
845

    
846
        if (geom instanceof Aggregate) {
847
            drawAggregate((Aggregate) geom, image, feature, symbol, viewPort,
848
                graphics, dpi, cancellable);
849
        } else {
850
            boolean bDrawCartographicSupport = false;
851
            if (symbol instanceof CartographicSupport) {
852
                bDrawCartographicSupport = ((CartographicSupport) symbol)
853
                .getUnit() != -1;
854
            }
855

    
856
            double previousSize = 0.0d;
857

    
858
            if (bDrawCartographicSupport) {
859
                // make the symbol to resize itself with the current rendering
860
                // context
861
                previousSize = ((CartographicSupport) symbol)
862
                .toCartographicSize(viewPort, dpi, geom);
863
            }
864

    
865
            try {
866
                symbol.draw(graphics, viewPort.getAffineTransform(), geom,
867
                    feature, cancellable);
868
            } finally {
869
                if (bDrawCartographicSupport) {
870
                    // restore previous size
871
                    ((CartographicSupport) symbol).setCartographicSize(
872
                        previousSize, geom);
873
                }
874
            }
875
        }
876
    }
877

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

    
893
    @Override
894
    public Object clone() throws CloneNotSupportedException {
895
        AbstractVectorialLegend clone = (AbstractVectorialLegend) super.clone();
896

    
897
        // Clone zSort
898
        ZSort zSort = getZSort();
899
        if (zSort != null) {
900
            clone.setZSort(new ZSort(clone));
901
        }
902
        return clone;
903
    }
904

    
905
    @Override
906
    public void loadFromState(PersistentState state)
907
    throws PersistenceException {
908
        // Set parent properties
909
        super.loadFromState(state);
910
        // Set own properties
911

    
912
        setShapeType(state.getInt(FIELD_SHAPETYPE));
913
        if (state.getBoolean(FIELD_HAS_ZSORT)) {
914
            setZSort(new ZSort(this));
915
        }
916
        setDefaultSymbol((ISymbol) state.get(FIELD_DEFAULT_SYMBOL));
917
    }
918

    
919
    @Override
920
    public void saveToState(PersistentState state) throws PersistenceException {
921
        // Save parent properties
922
        super.saveToState(state);
923
        // Save own properties
924
        state.set(FIELD_SHAPETYPE, getShapeType());
925
        state.set(FIELD_HAS_ZSORT, this.zSort != null);
926
        state.set(FIELD_DEFAULT_SYMBOL, getDefaultSymbol());
927
    }
928

    
929
    public static class RegisterPersistence implements Callable {
930

    
931
        @Override
932
        public Object call() throws Exception {
933
            PersistenceManager manager = ToolsLocator.getPersistenceManager();
934
            if (manager
935
                .getDefinition(VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME) == null) {
936
                DynStruct definition = manager.addDefinition(
937
                    AbstractVectorialLegend.class,
938
                    VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME,
939
                    VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME
940
                    + " persistence definition", null, null);
941
                // Extend the Legend base definition
942
                definition.extend(manager.getDefinition(LEGEND_PERSISTENCE_DEFINITION_NAME));
943

    
944
                // Shapetype
945
                definition.addDynFieldInt(FIELD_SHAPETYPE).setMandatory(true);
946
                // ZSort
947
                definition.addDynFieldBoolean(FIELD_HAS_ZSORT).setMandatory(true);
948
                // Default symbol
949
                definition.addDynFieldObject(FIELD_DEFAULT_SYMBOL)
950
                    .setClassOfValue(ISymbol.class).setMandatory(true);
951
            }
952
            return Boolean.TRUE;
953
        }
954

    
955
    }
956

    
957
    /**
958
     * Returns the names of the {@link Feature} attributes required for the
959
     * {@link ILegend} to operate.
960
     *
961
     * @param featureStore
962
     *            the store where the {@link Feature}s belong to
963
     * @return the names of required {@link Feature} attribute names
964
     * @throws DataException
965
     *             if there is an error getting the attribute names
966
     */
967
    protected abstract String[] getRequiredFeatureAttributeNames(
968
        FeatureStore featureStore) throws DataException;
969
}