Statistics
| Revision:

svn-gvsig-desktop / branches / v2_0_0_prep / libraries / 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 @ 39274

History | View | Annotate | Download (32.2 KB)

1
/* gvSIG. Geographic Information System of the Valencian Government
2
 *
3
 * Copyright (C) 2007-2008 Infrastructures and Transports Department
4
 * of the Valencian Government (CIT)
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 2
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
 */
22

    
23
/*
24
 * AUTHORS (In addition to CIT):
25
 * 2009 {DiSiD Technologies}  {{Task}}
26
 */
27
package org.gvsig.symbology.fmap.mapcontext.rendering.legend.impl;
28

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

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

    
41
import org.gvsig.compat.CompatLocator;
42
import org.gvsig.compat.print.PrintAttributes;
43
import org.gvsig.fmap.dal.exception.DataException;
44
import org.gvsig.fmap.dal.exception.ReadException;
45
import org.gvsig.fmap.dal.feature.Feature;
46
import org.gvsig.fmap.dal.feature.FeatureQuery;
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.exception.ConcurrentDataModificationException;
51
import org.gvsig.fmap.geom.Geometry;
52
import org.gvsig.fmap.geom.GeometryLocator;
53
import org.gvsig.fmap.geom.GeometryManager;
54
import org.gvsig.fmap.geom.aggregate.Aggregate;
55
import org.gvsig.fmap.geom.exception.CreateGeometryException;
56
import org.gvsig.fmap.geom.operation.DrawInts;
57
import org.gvsig.fmap.geom.operation.DrawOperationContext;
58
import org.gvsig.fmap.geom.operation.GeometryOperationException;
59
import org.gvsig.fmap.geom.operation.GeometryOperationNotSupportedException;
60
import org.gvsig.fmap.geom.primitive.Envelope;
61
import org.gvsig.fmap.mapcontext.MapContext;
62
import org.gvsig.fmap.mapcontext.MapContextException;
63
import org.gvsig.fmap.mapcontext.ViewPort;
64
import org.gvsig.fmap.mapcontext.layers.vectorial.IntersectsEnvelopeEvaluator;
65
import org.gvsig.fmap.mapcontext.rendering.legend.ILegend;
66
import org.gvsig.fmap.mapcontext.rendering.legend.IVectorLegend;
67
import org.gvsig.fmap.mapcontext.rendering.legend.LegendException;
68
import org.gvsig.fmap.mapcontext.rendering.legend.ZSort;
69
import org.gvsig.fmap.mapcontext.rendering.symbols.CartographicSupport;
70
import org.gvsig.fmap.mapcontext.rendering.symbols.IMultiLayerSymbol;
71
import org.gvsig.fmap.mapcontext.rendering.symbols.ISymbol;
72
import org.gvsig.tools.ToolsLocator;
73
import org.gvsig.tools.dispose.DisposableIterator;
74
import org.gvsig.tools.dynobject.DynStruct;
75
import org.gvsig.tools.exception.BaseException;
76
import org.gvsig.tools.persistence.PersistenceManager;
77
import org.gvsig.tools.persistence.PersistentState;
78
import org.gvsig.tools.persistence.exception.PersistenceException;
79
import org.gvsig.tools.task.Cancellable;
80
import org.gvsig.tools.task.SimpleTaskStatus;
81
import org.gvsig.tools.util.Callable;
82
import org.gvsig.tools.visitor.VisitCanceledException;
83
import org.gvsig.tools.visitor.Visitor;
84

    
85
/**
86
 * Base implementation for Vectorial data Legends.
87
 * 
88
 * Provides a draw method implementation which loads the {@link Feature}s and
89
 * uses the {@link ISymbol} objects to draw the {@link Geometry} objects.
90
 * 
91
 * @author 2009- <a href="cordinyana@gvsig.org">C?sar Ordi?ana</a> - gvSIG team
92
 */
93
public abstract class AbstractVectorialLegend extends AbstractLegend implements
94
IVectorLegend {
95
        
96
        private 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
    public void draw(BufferedImage image, Graphics2D g, ViewPort viewPort,
126
        Cancellable cancel, double scale, Map queryParameters,
127
        ICoordTrans coordTrans, FeatureStore featureStore)
128
    throws LegendException {
129
        double dpi = MapContext.getScreenDPI();
130
        draw(image, g, viewPort, cancel, scale, queryParameters, coordTrans,
131
            featureStore, null, dpi);
132
    }
133
    
134
    @SuppressWarnings("unchecked")
135
    public void draw(BufferedImage image, Graphics2D g, ViewPort viewPort,
136
        Cancellable cancel, double scale, Map queryParameters,
137
        ICoordTrans coordTrans, FeatureStore featureStore, FeatureQuery featureQuery)
138
    throws LegendException {
139
        double dpi = MapContext.getScreenDPI();
140
        draw(image, g, viewPort, cancel, scale, queryParameters, coordTrans,
141
            featureStore, featureQuery, dpi);
142
    }
143

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

    
160
        int resolution = properties.getPrintQuality();
161

    
162
        if (resolution == PrintAttributes.PRINT_QUALITY_DRAFT
163
            || resolution == PrintAttributes.PRINT_QUALITY_NORMAL
164
            || resolution == PrintAttributes.PRINT_QUALITY_HIGH) {
165
            dpi = PrintAttributes.PRINT_QUALITY_DPI[resolution];
166
        }
167

    
168
        FeatureSet featureSet = null;
169
        DisposableIterator it = null;
170
        try {
171
            ZSort zSort = getZSort();
172

    
173
            // if layer has map levels it will use a ZSort
174
            boolean useZSort = zSort != null && zSort.isUsingZSort();
175

    
176
            int mapLevelCount = (useZSort) ? zSort.getLevelCount() : 1;
177
            for (int mapPass = 0; mapPass < mapLevelCount; mapPass++) {
178
                // Get the iterator over the visible features
179
                String[] fieldNames = getRequiredFeatureAttributeNames(featureStore);
180

    
181
                if (featureQuery == null){
182
                    featureQuery = featureStore.createFeatureQuery();
183
                }
184
                featureQuery.setAttributeNames(fieldNames);
185
                featureQuery.setScale(scale);
186

    
187
                IntersectsEnvelopeEvaluator iee = new IntersectsEnvelopeEvaluator(
188
                    viewPort.getAdjustedEnvelope(),
189
                    viewPort.getProjection(),
190
                    featureStore.getDefaultFeatureType(), featureStore
191
                    .getDefaultFeatureType()
192
                    .getDefaultGeometryAttributeName());
193
                featureQuery.addFilter(iee); 
194
                featureSet = featureStore.getFeatureSet(featureQuery);
195
                it = featureSet.fastIterator();
196

    
197
                // Iteration over each feature
198
                while (!cancel.isCanceled() && it.hasNext()) {
199
                    Feature feat = (Feature) it.next();
200
                    Geometry geom = feat.getDefaultGeometry();
201

    
202
                    // retrieve the symbol associated to such feature
203
                    ISymbol sym = getSymbolByFeature(feat);
204
                    if (sym == null) {
205
                        continue;
206
                    }
207
                    if (useZSort) {
208
                        int[] symLevels = zSort.getLevels(sym);
209
                        if (symLevels != null) {
210

    
211
                            // Check if this symbol is a multilayer
212
                            if (sym instanceof IMultiLayerSymbol) {
213
                                // if so, get the layer corresponding to the
214
                                // current level. If none, continue to next
215
                                // iteration
216
                                IMultiLayerSymbol mlSym = (IMultiLayerSymbol) sym;
217
                                for (int i = 0; i < mlSym.getLayerCount(); i++) {
218
                                    ISymbol mySym = mlSym.getLayer(i);
219
                                    if (symLevels[i] == mapPass) {
220
                                        sym = mySym;
221
                                        break;
222
                                    }
223
                                }
224

    
225
                            } else {
226
                                // else, just draw the symbol in its level
227
                                if (symLevels[0] != mapPass) {
228
                                    continue;
229
                                }
230
                            }
231
                        }
232
                    }
233

    
234
                    // Check if this symbol is sized with CartographicSupport
235
                    CartographicSupport csSym = null;
236
                    int symbolType = sym.getSymbolType();
237

    
238
                    if (symbolType == Geometry.TYPES.POINT
239
                        || symbolType == Geometry.TYPES.CURVE
240
                        || sym instanceof CartographicSupport) {
241

    
242
                        csSym = (CartographicSupport) sym;
243
                    }
244

    
245
                    DrawOperationContext doc = new DrawOperationContext();
246
                    doc.setGraphics(g);
247
                    doc.setViewPort(viewPort);
248
                    if (csSym == null) {
249
                        doc.setSymbol(sym);
250
                    } else {
251
                        doc.setDPI(dpi);
252
                        doc.setCancellable(cancel);
253
                        doc.setSymbol((ISymbol) csSym);
254
                    }
255
                    geom.invokeOperation(DrawInts.CODE, doc);
256
                }
257
            }
258
        } catch (ReadException e) {
259
            throw new LegendDrawingException(e);
260
        } catch (GeometryOperationNotSupportedException e) {
261
            throw new LegendDrawingException(e);
262
        } catch (GeometryOperationException e) {
263
            throw new LegendDrawingException(e);
264
        } catch (DataException e) {
265
            throw new LegendDrawingException(e);
266
        } catch (MapContextException e) {
267
            throw new LegendDrawingException(e);
268
        } finally {
269
            if (it != null) {
270
                it.dispose();
271
            }
272
            if (featureSet != null) {
273
                featureSet.dispose();
274
            }
275
        }
276
    }
277

    
278
    /**
279
     * Draws the features from the {@link FeatureStore}, filtered with the scale
280
     * and the query parameters, with the symbols of the legend.
281
     */
282
    @SuppressWarnings("unchecked")
283
        protected void draw(BufferedImage image, Graphics2D g, ViewPort viewPort,
284
                        Cancellable cancel, double scale, Map queryParameters,
285
                        ICoordTrans coordTrans, FeatureStore featureStore,
286
                        FeatureQuery featureQuery, double dpi) throws LegendException {
287

    
288
                SimpleTaskStatus taskStatus = ToolsLocator.getTaskStatusManager()
289
                                .createDefaultSimpleTaskStatus(featureStore.getName());
290
                taskStatus.add();
291
                try {
292
                        // Avoid ConcurrentModificationException errors if
293
                        // while drawing another thread edits the store data.
294
                        synchronized (featureStore) {
295
                                internalDraw(image, g, viewPort, cancel, scale,
296
                                                queryParameters, coordTrans, featureStore,
297
                                                featureQuery, dpi, taskStatus);
298
                        }
299
                } finally {
300
                        if (taskStatus != null) {
301
                                taskStatus.terminate();
302
                                taskStatus.remove();
303
                                taskStatus = null;
304
                        }
305
                }
306
        }
307

    
308
        protected void internalDraw(BufferedImage image, Graphics2D g,
309
                        ViewPort viewPort, Cancellable cancel, double scale,
310
                        Map queryParameters, ICoordTrans coordTrans,
311
                        FeatureStore featureStore, FeatureQuery featureQuery, double dpi,
312
                        SimpleTaskStatus taskStatus) throws LegendDrawingException {
313

    
314
                if (!getDefaultSymbol().isShapeVisible()) {
315
                        return;
316
                }
317

    
318
                if (cancel.isCanceled()) {
319
                        return;
320
                }
321

    
322
                IProjection dataProjection;
323
                Envelope reprojectedDataEnvelop;
324

    
325
                try {
326
                        if (coordTrans == null) {
327
                                dataProjection = featureStore.getDefaultFeatureType()
328
                                                .getDefaultSRS();
329

    
330
                                // If the data does not provide a projection, use the
331
                                // current view one
332
                                if (dataProjection == null) {
333
                                        dataProjection = viewPort.getProjection();
334
                                }
335

    
336
                                reprojectedDataEnvelop = featureStore.getEnvelope();
337
                        } else {
338
                                dataProjection = coordTrans.getPOrig();
339

    
340
                                Envelope env = featureStore.getEnvelope();
341
                                if (!env.isEmpty()) {
342
                                        reprojectedDataEnvelop = env.convert(coordTrans);
343
                                } else {
344
                                        reprojectedDataEnvelop = env;
345
                                }
346
                        }
347
                } catch (DataException e) {
348
                        throw new LegendDrawingException(e);
349
                }
350

    
351
                // Gets the view envelope
352
                Envelope viewPortEnvelope = viewPort.getAdjustedEnvelope();
353

    
354
                // Gets the data envelope with the viewport SRS
355
                Envelope myEnvelope = reprojectedDataEnvelop;
356

    
357
                // TODO: in some cases, the legend may need a different check to
358
                // decide if the data must be drawn or not
359
                // Checks if the viewport envelope intersects with the data envelope
360
                if (!viewPortEnvelope.intersects(myEnvelope)) {
361
                        // The data is not visible in the current viewport, do nothing.
362
                        return;
363
                }
364

    
365
                // Check if all the data is contained into the viewport envelope
366
                boolean containsAll = viewPortEnvelope.contains(myEnvelope);
367

    
368
                // Create the drawing notification to be reused on each iteration
369
                DefaultFeatureDrawnNotification drawnNotification = new DefaultFeatureDrawnNotification();
370

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

    
375
                FeatureSet featureSet = null;
376
                try {
377
                        taskStatus.message("Retrieve selection");
378
                        FeatureSelection selection = featureStore.getFeatureSelection();
379

    
380
                        if (featureQuery == null) {
381
                                featureQuery = featureStore.createFeatureQuery();
382
                        }
383

    
384
                        completeQuery(featureStore, featureQuery, scale, queryParameters,
385
                                        coordTrans, dataProjection, viewPortEnvelope, containsAll);
386

    
387
                        taskStatus.message("Retrieve data");
388
                        featureSet = featureStore.getFeatureSet(featureQuery);
389

    
390
                        if (cancel.isCanceled()) {
391
                                return;
392
                        }
393

    
394
                        taskStatus.message("Drawing");
395
                        drawFeatures(image, g, viewPort, cancel, coordTrans, dpi,
396
                                        drawnNotification, featureSet, selection);
397

    
398
                } catch (BaseException e) {
399
                        throw new LegendDrawingException(e);
400
                } finally {
401
                        if (featureSet != null) {
402
                                featureSet.dispose();
403
                        }
404
                }
405
        }
406

    
407
    /**
408
     * Complete a {@link FeatureQuery} to load the {@link Feature}s to draw.
409
     */
410
    @SuppressWarnings("unchecked")
411
    private FeatureQuery completeQuery(FeatureStore featureStore, FeatureQuery featureQuery, double scale,
412
        Map queryParameters, ICoordTrans coordTrans,
413
        IProjection dataProjection, Envelope viewPortEnvelope,
414
        boolean containsAll) throws DataException {
415

    
416
        featureQuery.setScale(scale);
417
        
418
        //Adds the attributes
419
        String[] fieldNames = getRequiredFeatureAttributeNames(featureStore);
420
        for (int i=0 ; i<fieldNames.length ;i++){
421
            featureQuery.addAttributeName(fieldNames[i]);
422
        }       
423
        
424
        // TODO: Mobile has it's own IntersectsEnvelopeEvaluator
425
        if (!containsAll) {
426
            // Gets the viewport envelope with the data SRS
427
            Envelope viewPortEnvelopeInMyProj = viewPortEnvelope;
428
            // FIXME
429
            if (coordTrans != null) {
430
                viewPortEnvelopeInMyProj = viewPortEnvelope.convert(coordTrans
431
                    .getInverted());
432
            }
433

    
434
            if (dataProjection == null) {
435
                throw new IllegalArgumentException(
436
                "Error, the projection parameter value is null");
437
            }
438

    
439
            IntersectsEnvelopeEvaluator iee = new IntersectsEnvelopeEvaluator(
440
                viewPortEnvelopeInMyProj, dataProjection,
441
                featureStore.getDefaultFeatureType(), featureStore
442
                .getDefaultFeatureType()
443
                .getDefaultGeometryAttributeName());
444
            featureQuery.addFilter(iee);                        
445
        }
446
        if (queryParameters != null) {
447
            Iterator iterEntry = queryParameters.entrySet().iterator();
448
            Entry entry;
449
            while (iterEntry.hasNext()) {
450
                entry = (Entry) iterEntry.next();
451
                featureQuery.setQueryParameter((String) entry.getKey(),
452
                    entry.getValue());
453
            }
454
        }        
455
        return featureQuery;
456
    }
457

    
458
    /**
459
     * Draws the features from the {@link FeatureSet}, with the symbols of the
460
     * legend.
461
     */
462
    private void drawFeatures(BufferedImage image, Graphics2D g,
463
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
464
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
465
        FeatureSet featureSet, FeatureSelection selection)
466
    throws BaseException {
467
        if (isUseZSort()) {
468
            drawFeaturesMultiLayer(image, g, viewPort, cancel, coordTrans, dpi,
469
                drawnNotification, featureSet, selection);
470
        } else {
471
            drawFeaturesSingleLayer(image, g, viewPort, cancel, coordTrans,
472
                dpi, drawnNotification, featureSet, selection);
473
        }
474
    }
475

    
476
    /**
477
     * Draws the features from the {@link FeatureSet}, with the symbols of the
478
     * legend, using a single drawing layer.
479
     */
480
    private void drawFeaturesSingleLayer(final BufferedImage image,
481
        final Graphics2D g, final ViewPort viewPort,
482
        final Cancellable cancel, final ICoordTrans coordTrans,
483
        final double dpi,
484
        final DefaultFeatureDrawnNotification drawnNotification,
485
        FeatureSet featureSet, final FeatureSelection selection)
486
    throws BaseException {
487

    
488
        try {
489
            featureSet.accept(new Visitor() {
490
                public void visit(Object obj) throws VisitCanceledException,
491
                BaseException {
492
                    Feature feat = (Feature) obj;
493
                    drawFeatureSingleLayer(image, g, viewPort, cancel,
494
                        coordTrans, dpi, drawnNotification, feat, selection);
495
                }
496
            });
497

    
498
        } catch (ConcurrentDataModificationException e) {
499
            cancel.setCanceled(true);
500
            return;
501
        }
502
    }
503

    
504
    /**
505
     * Draws a Feature with the symbols of the legend, using a single drawing
506
     * layer.
507
     */
508
    private void drawFeatureSingleLayer(BufferedImage image, Graphics2D g,
509
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
510
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
511
        Feature feat, FeatureSelection selection)
512
    throws MapContextException, CreateGeometryException {
513

    
514
        Geometry geom = feat.getDefaultGeometry();
515
        if (geom == null) {
516
            return;
517
        }
518

    
519
        if (geom.getType() == Geometry.TYPES.NULL) {
520
            return;
521
        }
522

    
523
        ISymbol sym = getSymbol(feat, selection);
524
        if (sym == null) {
525
            return;
526
        }
527

    
528
        if (coordTrans != null) {
529
            geom = geom.cloneGeometry();
530
            geom.reProject(coordTrans);
531
        }
532

    
533
        if (cancel.isCanceled()) {
534
            return;
535
        }
536

    
537
        drawGeometry(geom, image, feat, sym, viewPort, g, dpi, cancel);
538

    
539
        // Notify the drawing observers
540
        drawnNotification.setFeature(feat);
541
        drawnNotification.setDrawnGeometry(geom);
542
        notifyObservers(drawnNotification);
543
    }
544

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

    
555
        // -- visual FX stuff
556
        long time = System.currentTimeMillis();
557

    
558
        boolean bSymbolLevelError = false;
559
        // render temporary map each screenRefreshRate milliseconds;
560
        int screenRefreshDelay = (int) ((1D / MapContext.getDrawFrameRate()) * 3 * 1000);
561
        BufferedImage[] imageLevels = null;
562
        Graphics2D[] graphics = null;
563

    
564
        imageLevels = new BufferedImage[getZSort().getLevelCount()];
565
        graphics = new Graphics2D[imageLevels.length];
566
        for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
567

    
568
            imageLevels[i] = CompatLocator.getGraphicsUtils()
569
            .createBufferedImage(image.getWidth(), image.getHeight(),
570
                image.getType());
571

    
572
            graphics[i] = imageLevels[i].createGraphics();
573
            graphics[i].setTransform(g.getTransform());
574
            graphics[i].setRenderingHints(g.getRenderingHints());
575
        }
576
        // -- end visual FX stuff
577

    
578
        DisposableIterator it = null;
579
        try {
580
            it = featureSet.fastIterator();
581
            // Iteration over each feature
582
            while (it.hasNext()) {
583
                if (cancel.isCanceled()) {
584
                    return;
585
                }
586
                Feature feat = (Feature) it.next();
587

    
588
                bSymbolLevelError |= drawFeatureMultiLayer(image, g, viewPort,
589
                    cancel, coordTrans, dpi, drawnNotification, selection,
590
                    time, screenRefreshDelay, imageLevels, graphics, feat);
591

    
592
            }
593
        } catch (ConcurrentDataModificationException e) {
594
            cancel.setCanceled(true);
595
            return;
596
        } finally {
597
            if (it != null) {
598
                it.dispose();
599
            }
600
        }
601

    
602
        g.drawImage(image, 0, 0, null);
603

    
604
        Point2D offset = viewPort.getOffset();
605
        CompatLocator.getGraphicsUtils().translate(g, offset.getX(),
606
            offset.getY());
607

    
608
        for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
609
            g.drawImage(imageLevels[i], 0, 0, null);
610
            imageLevels[i] = null;
611
            graphics[i] = null;
612
        }
613

    
614
        CompatLocator.getGraphicsUtils().translate(g, -offset.getX(),
615
            -offset.getY());
616

    
617
        imageLevels = null;
618
        graphics = null;
619

    
620
        if (bSymbolLevelError) {
621
            setZSort(null);
622
        }
623
    }
624

    
625
    /**
626
     * Draws a Feature with the symbols of the legend, using a multiple drawing
627
     * layer.
628
     */
629
    private boolean drawFeatureMultiLayer(BufferedImage image, Graphics2D g,
630
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
631
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
632
        FeatureSelection selection, long time, int screenRefreshDelay,
633
        BufferedImage[] imageLevels, Graphics2D[] graphics, Feature feat)
634
    throws MapContextException, CreateGeometryException {
635

    
636
        Geometry geom = feat.getDefaultGeometry();
637
        boolean bSymbolLevelError = false;
638
        long drawingTime = time;
639

    
640
        if (geom.getType() == Geometry.TYPES.NULL) {
641
            return false;
642
        }
643

    
644
        ISymbol sym = getSymbol(feat, selection);
645

    
646
        if (sym == null) {
647
            return false;
648
        }
649

    
650
        if (coordTrans != null) {
651
            geom = geom.cloneGeometry();
652
            geom.reProject(coordTrans);
653
        }
654

    
655
        if (cancel.isCanceled()) {
656
            return false;
657
        }
658

    
659
        // Check if this symbol is a multilayer
660
        int[] symLevels = getZSort().getLevels(sym);
661
        if (sym instanceof IMultiLayerSymbol) {
662
            // if so, treat each of its layers as a single
663
            // symbol
664
            // in its corresponding map level
665
            IMultiLayerSymbol mlSym = (IMultiLayerSymbol) sym;
666
            for (int i = 0; !cancel.isCanceled() && i < mlSym.getLayerCount(); i++) {
667
                ISymbol mySym = mlSym.getLayer(i);
668
                int symbolLevel = 0;
669
                if (symLevels != null) {
670
                    symbolLevel = symLevels[i];
671
                } else {
672
                    /*
673
                     * an error occured when managing symbol levels some of the
674
                     * legend changed events regarding the symbols did not
675
                     * finish satisfactory and the legend is now inconsistent.
676
                     * For this drawing, it will finish as it was at the bottom
677
                     * (level 0) but, when done, the ZSort will be reset to
678
                     * avoid app crashes. This is a bug that has to be fixed.
679
                     */
680
                    bSymbolLevelError = true;
681
                }
682
                drawGeometry(geom, imageLevels[symbolLevel], feat, mySym,
683
                    viewPort, graphics[symbolLevel], dpi, cancel);
684
            }
685
        } else {
686
            // else, just draw the symbol in its level
687
            int symbolLevel = 0;
688
            if (symLevels != null) {
689
                symbolLevel = symLevels[0];
690
            }
691
            drawGeometry(geom, imageLevels[symbolLevel], feat, sym, viewPort,
692
                graphics[symbolLevel], dpi, cancel);
693
        }
694

    
695
        // -- visual FX stuff
696
        // Cuando el offset!=0 se est? dibujando sobre el
697
        // Layout y por tanto no tiene que ejecutar el
698
        // siguiente c?digo.
699
        Point2D offset = viewPort.getOffset();
700
        if (offset.getX() == 0 && offset.getY() == 0) {
701
            if ((System.currentTimeMillis() - drawingTime) > screenRefreshDelay) {
702

    
703
                BufferedImage virtualBim = CompatLocator.getGraphicsUtils()
704
                .createBufferedImage(image.getWidth(),
705
                    image.getHeight(), BufferedImage.TYPE_INT_ARGB);
706

    
707
                Graphics2D virtualGraphics = virtualBim.createGraphics();
708
                virtualGraphics.drawImage(image, 0, 0, null);
709
                for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
710
                    virtualGraphics.drawImage(imageLevels[i], 0, 0, null);
711
                }
712
                g.clearRect(0, 0, image.getWidth(), image.getHeight());
713
                g.drawImage(virtualBim, 0, 0, null);
714
                drawingTime = System.currentTimeMillis();
715
            }
716
            // -- end visual FX stuff
717

    
718
        }
719
        // Notify the drawing observers
720
        drawnNotification.setFeature(feat);
721
        drawnNotification.setDrawnGeometry(geom);
722
        notifyObservers(drawnNotification);
723
        return bSymbolLevelError;
724
    }
725

    
726
    /**
727
     * Returns the symbol to use to draw a {@link Feature} taking into account
728
     * if it is selected.
729
     */
730
    private ISymbol getSymbol(Feature feat, FeatureSelection selection)
731
    throws MapContextException {
732
        // retrieve the symbol associated to such feature
733
        ISymbol sym = getSymbolByFeature(feat);
734

    
735
        if (sym != null && selection.isSelected(feat)) {
736
            sym = sym.getSymbolForSelection();
737
        }
738
        return sym;
739
    }
740

    
741
    /**
742
     * Returns if the legend is using a ZSort.
743
     */
744
    private boolean isUseZSort() {
745
        return getZSort() != null && getZSort().isUsingZSort();
746
    }
747

    
748
    /**
749
     * Draws a {@link Geometry} using the given {@link ISymbol}, over the
750
     * {@link Graphics2D} object.
751
     */
752
    private void drawGeometry(Geometry geom, BufferedImage image,
753
        Feature feature, ISymbol symbol, ViewPort viewPort,
754
        Graphics2D graphics, double dpi, Cancellable cancellable)
755
    throws CreateGeometryException {
756

    
757
        if (geom instanceof Aggregate) {
758
            drawAggregate((Aggregate) geom, image, feature, symbol, viewPort,
759
                graphics, dpi, cancellable);
760
        } else {
761
            boolean bDrawCartographicSupport = false;
762
            if (symbol instanceof CartographicSupport) {
763
                bDrawCartographicSupport = ((CartographicSupport) symbol)
764
                .getUnit() != -1;
765
            }
766

    
767
            double previousSize = 0.0d;
768

    
769
            if (bDrawCartographicSupport) {
770
                // make the symbol to resize itself with the current rendering
771
                // context
772
                previousSize = ((CartographicSupport) symbol)
773
                .toCartographicSize(viewPort, dpi, geom);
774
            }
775

    
776
            try {
777
                symbol.draw(graphics, viewPort.getAffineTransform(), geom,
778
                    feature, cancellable);
779
            } finally {
780
                if (bDrawCartographicSupport) {
781
                    // restore previous size
782
                    ((CartographicSupport) symbol).setCartographicSize(
783
                        previousSize, geom);
784
                }
785
            }
786
        }
787
    }
788

    
789
    /**
790
     * Draws an {@link Aggregate} using the given {@link ISymbol}, over the
791
     * {@link Graphics2D} object.
792
     */
793
    private void drawAggregate(Aggregate aggregate, BufferedImage image,
794
        Feature feature, ISymbol symbol, ViewPort viewPort,
795
        Graphics2D graphics, double dpi, Cancellable cancellable)
796
    throws CreateGeometryException {
797
        for (int i = 0; i < aggregate.getPrimitivesNumber(); i++) {
798
            Geometry prim = aggregate.getPrimitiveAt(i);
799
            drawGeometry(prim, image, feature, symbol, viewPort, graphics, dpi,
800
                cancellable);
801
        }
802
    }
803

    
804
    public Object clone() throws CloneNotSupportedException {
805
        AbstractVectorialLegend clone = (AbstractVectorialLegend) super.clone();
806

    
807
        // Clone zSort
808
        ZSort zSort = getZSort();
809
        if (zSort != null) {
810
            clone.setZSort(new ZSort(clone));
811
        }
812
        return clone;
813
    }
814

    
815
    public void loadFromState(PersistentState state)
816
    throws PersistenceException {
817
        // Set parent properties
818
        super.loadFromState(state);
819
        // Set own properties
820

    
821
        setShapeType(state.getInt(FIELD_SHAPETYPE));
822
        if (state.getBoolean(FIELD_HAS_ZSORT)) {
823
            setZSort(new ZSort(this));
824
        }
825
        setDefaultSymbol((ISymbol) state.get(FIELD_DEFAULT_SYMBOL));
826
    }
827

    
828
    public void saveToState(PersistentState state) throws PersistenceException {
829
        // Save parent properties
830
        super.saveToState(state);
831
        // Save own properties
832
        state.set(FIELD_SHAPETYPE, getShapeType());
833
        state.set(FIELD_HAS_ZSORT, this.zSort != null);
834
        state.set(FIELD_DEFAULT_SYMBOL, getDefaultSymbol());
835
    }
836

    
837
    public static class RegisterPersistence implements Callable {
838

    
839
        public Object call() throws Exception {
840
            PersistenceManager manager = ToolsLocator.getPersistenceManager();
841
            if (manager
842
                .getDefinition(VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME) == null) {
843
                DynStruct definition = manager.addDefinition(
844
                    AbstractVectorialLegend.class,
845
                    VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME,
846
                    VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME
847
                    + " persistence definition", null, null);
848
                // Extend the Legend base definition
849
                definition.extend(manager
850
                    .getDefinition(LEGEND_PERSISTENCE_DEFINITION_NAME));
851

    
852
                // Shapetype
853
                definition.addDynFieldInt(FIELD_SHAPETYPE).setMandatory(true);
854
                // ZSort
855
                definition.addDynFieldBoolean(FIELD_HAS_ZSORT).setMandatory(
856
                    true);
857
                // Default symbol
858
                definition.addDynFieldObject(FIELD_DEFAULT_SYMBOL)
859
                .setClassOfValue(ISymbol.class).setMandatory(true);
860
            }
861
            return Boolean.TRUE;
862
        }
863

    
864
    }
865

    
866
    /**
867
     * Returns the names of the {@link Feature} attributes required for the
868
     * {@link ILegend} to operate.
869
     * 
870
     * @param featureStore
871
     *            the store where the {@link Feature}s belong to
872
     * @return the names of required {@link Feature} attribute names
873
     * @throws DataException
874
     *             if there is an error getting the attribute names
875
     */
876
    protected abstract String[] getRequiredFeatureAttributeNames(
877
        FeatureStore featureStore) throws DataException;
878
}