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

History | View | Annotate | Download (35.4 KB)

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

    
30
import java.awt.Graphics2D;
31
import java.awt.geom.Point2D;
32
import java.awt.image.BufferedImage;
33
import java.util.Iterator;
34
import java.util.Map;
35
import java.util.Map.Entry;
36

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

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

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

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

    
99
    private static final int DRAW_MAX_ATTEMPTS = 5;
100

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

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

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

    
110
    protected ZSort zSort;
111

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

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

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

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

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

    
164
        int resolution = properties.getPrintQuality();
165

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

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

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

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

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

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

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

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

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

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

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

    
294
                            csSym = (CartographicSupport) sym;
295
                        }
296

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

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

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

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

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

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

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

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

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

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

    
414

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

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

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

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

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

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

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

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

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

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

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

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

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

    
483
        featureQuery.setScale(scale);
484

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
833
            double previousSize = 0.0d;
834

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

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

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

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

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

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

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

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

    
906
    public static class RegisterPersistence implements Callable {
907

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

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

    
932
    }
933

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