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

History | View | Annotate | Download (37.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
import org.apache.commons.lang3.StringUtils;
37
import org.cresques.cts.ICoordTrans;
38
import org.cresques.cts.IProjection;
39
import org.gvsig.compat.CompatLocator;
40
import org.gvsig.compat.print.PrintAttributes;
41
import org.gvsig.expressionevaluator.ExpressionBuilder;
42
import org.gvsig.expressionevaluator.ExpressionUtils;
43
import org.gvsig.fmap.dal.exception.DataException;
44
import org.gvsig.fmap.dal.feature.Feature;
45
import org.gvsig.fmap.dal.feature.FeatureQuery;
46
import org.gvsig.fmap.dal.feature.FeatureReference;
47
import org.gvsig.fmap.dal.feature.FeatureSelection;
48
import org.gvsig.fmap.dal.feature.FeatureSet;
49
import org.gvsig.fmap.dal.feature.FeatureStore;
50
import org.gvsig.fmap.dal.feature.FeatureType;
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.CartographicSupport.CartographicContext;
69
import org.gvsig.fmap.mapcontext.rendering.symbols.IMultiLayerSymbol;
70
import org.gvsig.fmap.mapcontext.rendering.symbols.ISymbol;
71
import org.gvsig.tools.ToolsLocator;
72
import org.gvsig.tools.dispose.DisposableIterator;
73
import org.gvsig.tools.dispose.DisposeUtils;
74
import org.gvsig.tools.dynobject.DynStruct;
75
import org.gvsig.tools.evaluator.Evaluator;
76
import org.gvsig.tools.exception.BaseException;
77
import org.gvsig.tools.logger.FilteredLogger;
78
import org.gvsig.tools.persistence.PersistenceManager;
79
import org.gvsig.tools.persistence.PersistentState;
80
import org.gvsig.tools.persistence.exception.PersistenceException;
81
import org.gvsig.tools.task.Cancellable;
82
import org.gvsig.tools.task.SimpleTaskStatus;
83
import org.gvsig.tools.util.Callable;
84
import org.gvsig.tools.visitor.VisitCanceledException;
85
import org.gvsig.tools.visitor.Visitor;
86
import org.slf4j.Logger;
87
import org.slf4j.LoggerFactory;
88

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

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

    
102
    private static final int DRAW_MAX_ATTEMPTS = 5;
103

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

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

    
110
    private boolean drawSymbolInEeachPrimitive;
111

    
112
    protected ZSort zSort;
113

    
114
    protected AbstractVectorialLegend() {
115
        this.drawSymbolInEeachPrimitive = true;
116
    }
117

    
118
    
119
    public ZSort getZSort() {
120
        return zSort;
121
    }
122

    
123
    public void setZSort(ZSort zSort) {
124
        if (zSort == null) {
125
            removeLegendListener(this.zSort);
126
        }
127
        this.zSort = zSort;
128
        addLegendListener(zSort);
129
    }
130

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

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

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

    
172
        int resolution = properties.getPrintQuality();
173

    
174
        if (resolution == PrintAttributes.PRINT_QUALITY_DRAFT
175
            || resolution == PrintAttributes.PRINT_QUALITY_NORMAL
176
            || resolution == PrintAttributes.PRINT_QUALITY_HIGH) {
177
            dpi = PrintAttributes.PRINT_QUALITY_DPI[resolution];
178
        }
179

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

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

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

    
223
                if (use_intersection_cond) {
224
                    Evaluator iee = SpatialEvaluatorsFactory.getInstance().intersects(
225
                            vp_env_in_store_crs,
226
                            store_crs,
227
                            featureStore
228
                    );
229
                    if (feat_query == null) {
230
                        feat_query = featureStore.createFeatureQuery();
231
                    }
232
                    String[] fns = getRequiredFeatureAttributeNames(featureStore);
233
                    for (String fn : fns) {
234
                        feat_query.addAttributeName(fn);
235
                    }
236
                    feat_query.addFilter(iee);
237
                }
238

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

    
262
                        // retrieve the symbol associated to such feature
263
                        ISymbol sym = getSymbolByFeature(feat);
264
                        if (sym == null) {
265
                            continue;
266
                        }
267
                        if (useZSort && theZSort != null) {
268
                            int[] symLevels = theZSort.getLevels(sym);
269
                            if (symLevels != null) {
270

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

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

    
294
                        // Check if this symbol is sized with CartographicSupport
295
                        CartographicSupport csSym = null;
296
                        int symbolType = sym.getSymbolType();
297

    
298
                        if (symbolType == Geometry.TYPES.POINT
299
                            ||  geomManager.isSubtype(symbolType, Geometry.TYPES.CURVE)
300
                            || sym instanceof CartographicSupport) {
301

    
302
                            csSym = (CartographicSupport) sym;
303
                        }
304

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

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

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

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

    
376
                if (!getDefaultSymbol().isShapeVisible()) {
377
                        return;
378
                }
379

    
380
                if (cancel.isCanceled()) {
381
                        return;
382
                }
383

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

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

    
402
                                reprojectedDataEnvelope = dataEnvelope;
403
                reprojectedViewPortEnvelope = viewPortEnvelope;
404
                        } else {
405
                                dataProjection = coordTrans.getPOrig();
406

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

    
422

    
423
                // Gets the data envelope with the viewport SRS
424
//                Envelope myEnvelope = reprojectedDataEnvelope;
425

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

    
435
                // Check if all the data is contained into the viewport envelope
436
        // This condition may seem redundant, but sometimes the transformations may fail and cause false negatives.
437
        //The Envelope.contains method checks that the parameter is null or not
438
                boolean containsAll = viewPortEnvelope.contains(reprojectedDataEnvelope) 
439
                        || (reprojectedViewPortEnvelope!=null && reprojectedViewPortEnvelope.contains(dataEnvelope));
440

    
441
                // Create the drawing notification to be reused on each iteration
442
                DefaultFeatureDrawnNotification drawnNotification = new DefaultFeatureDrawnNotification();
443

    
444
                if (cancel.isCanceled()) {
445
                        return;
446
                }
447

    
448
                FeatureSet featureSet = null;
449
                FeatureSelection selection = null;
450
                boolean mustDisposeSelection = false;
451
                try {
452
                        taskStatus.message("Retrieve selection");
453
                        if( featureStore.isFeatureSelectionEmpty() ) {
454
                            // No hay seleccion, asi que creamos una vacia que consuma pocos recursos.
455
                            selection = featureStore.createMemoryFeatureSelection();
456
                            mustDisposeSelection = true;
457
                        } else {
458
                            // Ojo, que esta seleccion puede acabar haciendo un count(*) sobre la tabla.
459
                            selection = featureStore.getFeatureSelection();
460
                            mustDisposeSelection = false;
461
                        }
462

    
463
                        if (featureQuery == null) {
464
                                featureQuery = featureStore.createFeatureQuery();
465
                        }
466

    
467
                        completeQuery(featureStore, featureQuery, scale, queryParameters,
468
                                        coordTrans, dataProjection, viewPortEnvelope, containsAll);
469

    
470
                        taskStatus.message("Retrieve data");
471
                        featureSet = featureStore.getFeatureSet(featureQuery);
472

    
473
                        if (cancel.isCanceled()) {
474
                                return;
475
                        }
476

    
477
                        taskStatus.message("Drawing");
478
                        drawFeatures(image, g, viewPort, cancel, coordTrans, dpi,
479
                                        drawnNotification, featureSet, selection);
480

    
481
        } catch (Throwable e) {
482
            /*
483
             * Probably a reprojection exception (for example,
484
             * trying to reproject Canada to EPSG:23030)
485
             */
486
            throw new LegendDrawingException(e);
487
        } finally {
488
                if (featureSet != null) {
489
                        featureSet.dispose();
490
                }
491
                if(mustDisposeSelection) {
492
                    DisposeUtils.disposeQuietly(selection);
493
                }
494
        }
495
    }
496

    
497
    /**
498
     * Complete a {@link FeatureQuery} to load the {@link Feature}s to draw.
499
     */
500
    @SuppressWarnings("unchecked")
501
    private FeatureQuery completeQuery(FeatureStore featureStore, FeatureQuery featureQuery, double scale,
502
        Map queryParameters, ICoordTrans coordTrans,
503
        IProjection dataProjection, Envelope viewPortEnvelope,
504
        boolean containsAll) throws DataException {
505
        boolean retrievesAllAttributes = ((featureQuery.hasFilter() && !featureQuery.hasAttributeNames()));
506

    
507
        featureQuery.setScale(scale);
508

    
509
        //Adds the attributes
510
        String[] fieldNames = getRequiredFeatureAttributeNames(featureStore);
511
            for (String fieldName : fieldNames) {
512
                if( StringUtils.isNotBlank(fieldName) ) {
513
                    featureQuery.addAttributeName(fieldName);
514
                }
515
            }
516

    
517
        if (!containsAll) {
518
            // Gets the viewport envelope with the data SRS
519
            Envelope viewPortEnvelopeInMyProj = viewPortEnvelope;
520
            if (coordTrans != null) {
521
                viewPortEnvelopeInMyProj = viewPortEnvelope.convert(coordTrans
522
                        .getInverted());
523
            }
524

    
525
            if (dataProjection == null) {
526
                throw new IllegalArgumentException(
527
                "Error, the projection parameter value is null");
528
            }
529

    
530
            Evaluator iee = SpatialEvaluatorsFactory.getInstance().intersects(
531
                    viewPortEnvelopeInMyProj,
532
                    dataProjection,
533
                    featureStore
534
            );
535
            featureQuery.addFilter(iee);
536
        } else {
537
            FeatureType ft = featureStore.getDefaultFeatureType();
538
            ExpressionBuilder expbuilder = ExpressionUtils.createExpressionBuilder();
539
            featureQuery.addFilter(
540
                expbuilder.not_is_null(
541
                    expbuilder.column(
542
                            ft.getDefaultGeometryAttributeName()
543
                    )
544
                ).toString()
545
            );
546
        }
547
        
548
        if (queryParameters != null) {
549
            Iterator iterEntry = queryParameters.entrySet().iterator();
550
            Entry entry;
551
            while (iterEntry.hasNext()) {
552
                entry = (Entry) iterEntry.next();
553
                featureQuery.setQueryParameter((String) entry.getKey(),
554
                    entry.getValue());
555
            }
556
        }
557
//        featureStore.addRequiredAttributes(featureQuery);
558
        if( retrievesAllAttributes ) {
559
            featureQuery.retrievesAllAttributes();
560
        }
561
        return featureQuery;
562
    }
563

    
564
    /**
565
     * Draws the features from the {@link FeatureSet}, with the symbols of the
566
     * legend.
567
     */
568
    protected void drawFeatures(BufferedImage image, Graphics2D g,
569
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
570
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
571
        FeatureSet featureSet, FeatureSelection selection)
572
    throws BaseException {
573
        if (isUseZSort()) {
574
            drawFeaturesMultiLayer(image, g, viewPort, cancel, coordTrans, dpi,
575
                drawnNotification, featureSet, selection);
576
        } else {
577
            drawFeaturesSingleLayer(image, g, viewPort, cancel, coordTrans,
578
                dpi, drawnNotification, featureSet, selection);
579
        }
580
    }
581

    
582
    /**
583
     * Draws the features from the {@link FeatureSet}, with the symbols of the
584
     * legend, using a single drawing layer.
585
     */
586
    private void drawFeaturesSingleLayer(final BufferedImage image,
587
        final Graphics2D g, final ViewPort viewPort,
588
        final Cancellable cancel, final ICoordTrans coordTrans,
589
        final double dpi,
590
        final DefaultFeatureDrawnNotification drawnNotification,
591
        FeatureSet featureSet, final FeatureSelection selection)
592
    throws BaseException {
593

    
594
        try {
595
            featureSet.accept((Object obj) -> {
596
                if( cancel.isCanceled() )  {
597
                    throw new VisitCanceledException();
598
                }
599
                Feature feat = (Feature) obj;
600
                drawFeatureSingleLayer(image, g, viewPort, cancel,
601
                        coordTrans, dpi, drawnNotification, feat, selection);
602
            });
603

    
604
        } catch (ConcurrentDataModificationException e) {
605
            cancel.setCanceled(true);
606
        }
607
    }
608

    
609
    /**
610
     * Draws a Feature with the symbols of the legend, using a single drawing
611
     * layer.
612
     */
613
    private void drawFeatureSingleLayer(BufferedImage image, Graphics2D g,
614
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
615
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
616
        Feature feat, FeatureSelection selection)
617
    throws MapContextException, CreateGeometryException {
618

    
619
        Geometry geom = feat.getDefaultGeometry();
620
        if (geom == null) {
621
            return;
622
        }
623

    
624
        if (geom.getType() == Geometry.TYPES.NULL) {
625
            return;
626
        }
627

    
628
        ISymbol sym = getSymbol(feat, selection);
629
        if (sym == null) {
630
            return;
631
        }
632

    
633
        if (coordTrans != null) {
634
            geom = geom.cloneGeometry();
635
            try {
636
                geom.reProject(coordTrans);
637
            } catch (ReprojectionRuntimeException re) {
638
                LOG.warn("Can't reproject geometry "+geom.toString(), re);
639
                return;
640
            }
641
        }
642

    
643
        if (cancel.isCanceled()) {
644
            return;
645
        }
646

    
647
        drawGeometry(geom, image, feat, sym, viewPort, g, dpi, cancel);
648

    
649
        // Notify the drawing observers
650
        drawnNotification.setFeature(feat);
651
        drawnNotification.setDrawnGeometry(geom);
652
        notifyObservers(drawnNotification);
653
    }
654

    
655
    /**
656
     * Draws the features from the {@link FeatureSet}, with the symbols of the
657
     * legend, using a multiple drawing layer.
658
     */
659
    private void drawFeaturesMultiLayer(BufferedImage image, Graphics2D g,
660
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
661
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
662
        FeatureSet featureSet, FeatureSelection selection)
663
    throws MapContextException, CreateGeometryException, DataException {
664

    
665
        // -- visual FX stuff
666
        long time = System.currentTimeMillis();
667

    
668
        boolean bSymbolLevelError = false;
669
        int screenRefreshDelay = (int) ((1D / MapContext.getDrawFrameRate()) * 3 * 1000);
670
        BufferedImage[] imageLevels;
671
        Graphics2D[] graphics;
672

    
673
        imageLevels = new BufferedImage[getZSort().getLevelCount()];
674
        graphics = new Graphics2D[imageLevels.length];
675
        for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
676

    
677
            imageLevels[i] = CompatLocator.getGraphicsUtils()
678
            .createBufferedImage(image.getWidth(), image.getHeight(),
679
                image.getType());
680

    
681
            graphics[i] = imageLevels[i].createGraphics();
682
            graphics[i].setTransform(g.getTransform());
683
            graphics[i].setRenderingHints(g.getRenderingHints());
684
        }
685

    
686
        DisposableIterator it = null;
687
        try {
688
            it = featureSet.fastIterator();
689
            // Iteration over each feature
690
            while (it.hasNext()) {
691
                if (cancel.isCanceled()) {
692
                    return;
693
                }
694
                Feature feat = (Feature) it.next();
695

    
696
                bSymbolLevelError |= drawFeatureMultiLayer(image, g, viewPort,
697
                    cancel, coordTrans, dpi, drawnNotification, selection,
698
                    time, screenRefreshDelay, imageLevels, graphics, feat);
699

    
700
            }
701
        } catch (ConcurrentDataModificationException e) {
702
            cancel.setCanceled(true);
703
            return;
704
        } finally {
705
            DisposeUtils.dispose(it);
706
        }
707

    
708
        g.drawImage(image, 0, 0, null);
709

    
710
        Point2D offset = viewPort.getOffset();
711
        CompatLocator.getGraphicsUtils().translate(g, offset.getX(),
712
            offset.getY());
713

    
714
        for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
715
            g.drawImage(imageLevels[i], 0, 0, null);
716
            imageLevels[i] = null;
717
            graphics[i] = null;
718
        }
719

    
720
        CompatLocator.getGraphicsUtils().translate(g, -offset.getX(),
721
            -offset.getY());
722

    
723
        if (bSymbolLevelError) {
724
            setZSort(null);
725
        }
726
    }
727

    
728
    /**
729
     * Draws a Feature with the symbols of the legend, using a multiple drawing
730
     * layer.
731
     */
732
    private boolean drawFeatureMultiLayer(BufferedImage image, Graphics2D g,
733
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
734
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
735
        FeatureSelection selection, long time, int screenRefreshDelay,
736
        BufferedImage[] imageLevels, Graphics2D[] graphics, Feature feat)
737
    throws MapContextException, CreateGeometryException {
738

    
739
        Geometry geom = feat.getDefaultGeometry();
740
        boolean bSymbolLevelError = false;
741
        long drawingTime = time;
742

    
743
        if (geom==null || geom.getType() == Geometry.TYPES.NULL) {
744
            return false;
745
        }
746

    
747
        ISymbol sym = getSymbol(feat, selection);
748

    
749
        if (sym == null) {
750
            return false;
751
        }
752

    
753
        if (coordTrans != null) {
754
            geom = geom.cloneGeometry();
755
            geom.reProject(coordTrans);
756
        }
757

    
758
        if (cancel.isCanceled()) {
759
            return false;
760
        }
761

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

    
798
        // -- visual FX stuff
799
        // Cuando el offset!=0 se est? dibujando sobre el
800
        // Layout y por tanto no tiene que ejecutar el
801
        // siguiente c?digo.
802
        Point2D offset = viewPort.getOffset();
803
        if (offset.getX() == 0 && offset.getY() == 0) {
804
            if ((System.currentTimeMillis() - drawingTime) > screenRefreshDelay) {
805

    
806
                BufferedImage virtualBim = CompatLocator.getGraphicsUtils()
807
                .createBufferedImage(image.getWidth(),
808
                    image.getHeight(), BufferedImage.TYPE_INT_ARGB);
809

    
810
                Graphics2D virtualGraphics = virtualBim.createGraphics();
811
                virtualGraphics.drawImage(image, 0, 0, null);
812
                for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
813
                    virtualGraphics.drawImage(imageLevels[i], 0, 0, null);
814
                }
815
                g.clearRect(0, 0, image.getWidth(), image.getHeight());
816
                g.drawImage(virtualBim, 0, 0, null);
817
                drawingTime = System.currentTimeMillis();
818
            }
819
            // -- end visual FX stuff
820

    
821
        }
822
        // Notify the drawing observers
823
        drawnNotification.setFeature(feat);
824
        drawnNotification.setDrawnGeometry(geom);
825
        notifyObservers(drawnNotification);
826
        return bSymbolLevelError;
827
    }
828

    
829
    /**
830
     * Returns the symbol to use to draw a {@link Feature} taking into account
831
     * if it is selected.
832
     */
833
    private ISymbol getSymbol(Feature feat, FeatureSelection selection)
834
    throws MapContextException {
835
        // retrieve the symbol associated to such feature
836
        ISymbol sym = getSymbolByFeature(feat);
837

    
838
        if (sym != null && selection.isSelected(feat)) {
839
            sym = sym.getSymbolForSelection();
840
        }
841
        return sym;
842
    }
843

    
844
    /**
845
     * Returns if the legend is using a ZSort.
846
     */
847
    private boolean isUseZSort() {
848
        return getZSort() != null && getZSort().isUsingZSort();
849
    }
850

    
851
    /**
852
     * Draws a {@link Geometry} using the given {@link ISymbol}, over the
853
     * {@link Graphics2D} object.
854
     */
855
    private void drawGeometry(Geometry geom, BufferedImage image,
856
        Feature feature, ISymbol symbol, ViewPort viewPort,
857
        Graphics2D graphics, double dpi, Cancellable cancellable)
858
    throws CreateGeometryException {
859

    
860
        if (geom instanceof Aggregate && this.isDrawSymbolInEeachPrimitive()) {
861
            drawAggregate((Aggregate) geom, image, feature, symbol, viewPort,
862
                graphics, dpi, cancellable);
863
        } else {
864
            if (symbol instanceof CartographicSupport) {
865
//                if(((CartographicSupport) symbol).getUnit() == -1){
866
//                    ((CartographicSupport) symbol).setCartographicContext((CartographicContext)null);
867
//                } else {
868
                    ((CartographicSupport) symbol).setCartographicContext(viewPort, dpi, geom);
869
//                }
870
            }
871
            symbol.draw(graphics, viewPort.getAffineTransform(), geom, feature, cancellable);
872
        }
873
    }
874

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

    
890
    @Override
891
    public Object clone() throws CloneNotSupportedException {
892
        AbstractVectorialLegend clone = (AbstractVectorialLegend) super.clone();
893

    
894
        // Clone zSort
895
        ZSort theZSort = getZSort();
896
        if (theZSort != null) {
897
            clone.setZSort(new ZSort(clone));
898
        }
899
        return clone;
900
    }
901

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

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

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

    
926
    public static class RegisterPersistence implements Callable {
927

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

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

    
952
    }
953

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

    
967
    /**
968
     * @return the drawSymbolInEeachPrimitive
969
     */
970
    public boolean isDrawSymbolInEeachPrimitive() {
971
        return drawSymbolInEeachPrimitive;
972
    }
973

    
974
    /**
975
     * @param drawSymbolInEeachPrimitive the drawSymbolInEeachPrimitive to set
976
     */
977
    public void setDrawSymbolInEeachPrimitive(boolean drawSymbolInEeachPrimitive) {
978
        this.drawSymbolInEeachPrimitive = drawSymbolInEeachPrimitive;
979
    }
980
}