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

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.Color;
31
import java.awt.Graphics2D;
32
import java.awt.geom.Point2D;
33
import java.awt.image.BufferedImage;
34
import java.util.Iterator;
35
import java.util.Map;
36
import java.util.Map.Entry;
37
import org.apache.commons.lang3.StringUtils;
38
import org.cresques.cts.ICoordTrans;
39
import org.cresques.cts.IProjection;
40
import org.gvsig.compat.CompatLocator;
41
import org.gvsig.compat.print.PrintAttributes;
42
import org.gvsig.expressionevaluator.ExpressionBuilder;
43
import org.gvsig.expressionevaluator.ExpressionUtils;
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.FeatureType;
52
import org.gvsig.fmap.dal.feature.exception.ConcurrentDataModificationException;
53
import org.gvsig.fmap.geom.Geometry;
54
import org.gvsig.fmap.geom.GeometryLocator;
55
import org.gvsig.fmap.geom.GeometryManager;
56
import org.gvsig.fmap.geom.aggregate.Aggregate;
57
import org.gvsig.fmap.geom.exception.CreateGeometryException;
58
import org.gvsig.fmap.geom.exception.ReprojectionRuntimeException;
59
import org.gvsig.fmap.geom.primitive.Envelope;
60
import org.gvsig.fmap.mapcontext.MapContext;
61
import org.gvsig.fmap.mapcontext.MapContextException;
62
import org.gvsig.fmap.mapcontext.ViewPort;
63
import org.gvsig.fmap.mapcontext.layers.vectorial.SpatialEvaluatorsFactory;
64
import org.gvsig.fmap.mapcontext.rendering.legend.ILegend;
65
import org.gvsig.fmap.mapcontext.rendering.legend.IVectorLegend;
66
import org.gvsig.fmap.mapcontext.rendering.legend.LegendException;
67
import org.gvsig.fmap.mapcontext.rendering.legend.ZSort;
68
import org.gvsig.fmap.mapcontext.rendering.symbols.CartographicSupport;
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.slf4j.Logger;
86
import org.slf4j.LoggerFactory;
87

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

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

    
101
    private static final int DRAW_MAX_ATTEMPTS = 5;
102

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

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

    
109
    private boolean drawSymbolInEeachPrimitive;
110

    
111
    protected ZSort zSort;
112

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

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

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

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

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

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

    
171
        int resolution = properties.getPrintQuality();
172

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

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

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

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

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

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

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

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

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

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

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

    
301
                            csSym = (CartographicSupport) sym;
302
                        }
303

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

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

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

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

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

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

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

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

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

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

    
421

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

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

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

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

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

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

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

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

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

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

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

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

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

    
506
        featureQuery.setScale(scale);
507

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

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

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

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

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

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

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

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

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

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

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

    
627
        ISymbol sym = getSymbol(feat, selection, viewPort.getSelectionColor());
628
        if (sym == null) {
629
            return;
630
        }
631

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
746
        ISymbol sym = getSymbol(feat, selection, viewPort.getSelectionColor());
747

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
925
    public static class RegisterPersistence implements Callable {
926

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

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

    
951
    }
952

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

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

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