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

History | View | Annotate | Download (32.9 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.exception.ReprojectionRuntimeException;
57
import org.gvsig.fmap.geom.operation.DrawInts;
58
import org.gvsig.fmap.geom.operation.DrawOperationContext;
59
import org.gvsig.fmap.geom.operation.GeometryOperationException;
60
import org.gvsig.fmap.geom.operation.GeometryOperationNotSupportedException;
61
import org.gvsig.fmap.geom.primitive.Envelope;
62
import org.gvsig.fmap.mapcontext.MapContext;
63
import org.gvsig.fmap.mapcontext.MapContextException;
64
import org.gvsig.fmap.mapcontext.ViewPort;
65
import org.gvsig.fmap.mapcontext.layers.vectorial.IntersectsEnvelopeEvaluator;
66
import org.gvsig.fmap.mapcontext.rendering.legend.ILegend;
67
import org.gvsig.fmap.mapcontext.rendering.legend.IVectorLegend;
68
import org.gvsig.fmap.mapcontext.rendering.legend.LegendException;
69
import org.gvsig.fmap.mapcontext.rendering.legend.ZSort;
70
import org.gvsig.fmap.mapcontext.rendering.symbols.CartographicSupport;
71
import org.gvsig.fmap.mapcontext.rendering.symbols.IMultiLayerSymbol;
72
import org.gvsig.fmap.mapcontext.rendering.symbols.ISymbol;
73
import org.gvsig.tools.ToolsLocator;
74
import org.gvsig.tools.dispose.DisposableIterator;
75
import org.gvsig.tools.dynobject.DynStruct;
76
import org.gvsig.tools.exception.BaseException;
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
 * @author 2009- <a href="cordinyana@gvsig.org">C?sar Ordi?ana</a> - gvSIG team
93
 */
94
public abstract class AbstractVectorialLegend extends AbstractLegend implements
95
IVectorLegend {
96
        
97
        private static final Logger LOG = LoggerFactory
98
                        .getLogger(AbstractVectorialLegend.class);
99

    
100
    private static final int DRAW_MAX_ATTEMPTS = 5;
101

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

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

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

    
111
    protected ZSort zSort;
112

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

    
117
    public void setZSort(ZSort zSort) {
118
        if (zSort == null) {
119
            removeLegendListener(this.zSort);
120
        }
121
        this.zSort = zSort;
122
        addLegendListener(zSort);
123
    }
124
    
125
    @SuppressWarnings("unchecked")
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 = MapContext.getScreenDPI();
131
        draw(image, g, viewPort, cancel, scale, queryParameters, coordTrans,
132
            featureStore, null, dpi);
133
    }
134
    
135
    @SuppressWarnings("unchecked")
136
    public void draw(BufferedImage image, Graphics2D g, ViewPort viewPort,
137
        Cancellable cancel, double scale, Map queryParameters,
138
        ICoordTrans coordTrans, FeatureStore featureStore, FeatureQuery featureQuery)
139
    throws LegendException {
140
        double dpi = MapContext.getScreenDPI();
141
        draw(image, g, viewPort, cancel, scale, queryParameters, coordTrans,
142
            featureStore, featureQuery, dpi);
143
    }
144

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

    
161
        int resolution = properties.getPrintQuality();
162

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

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

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

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

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

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

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

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

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

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

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

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

    
243
                        csSym = (CartographicSupport) sym;
244
                    }
245

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

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

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

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

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

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

    
323
                IProjection dataProjection;
324
                Envelope reprojectedDataEnvelop;
325

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
399
        } catch (RuntimeException e) {
400
            /*
401
             * Probably a reprojection exception (for example,
402
             * trying to reproject Canada to EPSG:23030)
403
             */
404
            throw new LegendDrawingException(e);
405
                } catch (BaseException e) {
406
                        throw new LegendDrawingException(e);
407
                } finally {
408
                        if (featureSet != null) {
409
                                featureSet.dispose();
410
                        }
411
                }
412
        }
413

    
414
    /**
415
     * Complete a {@link FeatureQuery} to load the {@link Feature}s to draw.
416
     */
417
    @SuppressWarnings("unchecked")
418
    private FeatureQuery completeQuery(FeatureStore featureStore, FeatureQuery featureQuery, double scale,
419
        Map queryParameters, ICoordTrans coordTrans,
420
        IProjection dataProjection, Envelope viewPortEnvelope,
421
        boolean containsAll) throws DataException {
422

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

    
441
            if (dataProjection == null) {
442
                throw new IllegalArgumentException(
443
                "Error, the projection parameter value is null");
444
            }
445

    
446
            IntersectsEnvelopeEvaluator iee = new IntersectsEnvelopeEvaluator(
447
                viewPortEnvelopeInMyProj, dataProjection,
448
                featureStore.getDefaultFeatureType(), featureStore
449
                .getDefaultFeatureType()
450
                .getDefaultGeometryAttributeName());
451
            featureQuery.addFilter(iee);                        
452
        }
453
        if (queryParameters != null) {
454
            Iterator iterEntry = queryParameters.entrySet().iterator();
455
            Entry entry;
456
            while (iterEntry.hasNext()) {
457
                entry = (Entry) iterEntry.next();
458
                featureQuery.setQueryParameter((String) entry.getKey(),
459
                    entry.getValue());
460
            }
461
        }        
462
        return featureQuery;
463
    }
464

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

    
483
    /**
484
     * Draws the features from the {@link FeatureSet}, with the symbols of the
485
     * legend, using a single drawing layer.
486
     */
487
    private void drawFeaturesSingleLayer(final BufferedImage image,
488
        final Graphics2D g, final ViewPort viewPort,
489
        final Cancellable cancel, final ICoordTrans coordTrans,
490
        final double dpi,
491
        final DefaultFeatureDrawnNotification drawnNotification,
492
        FeatureSet featureSet, final FeatureSelection selection)
493
    throws BaseException {
494

    
495
        try {
496
            featureSet.accept(new Visitor() {
497
                public void visit(Object obj) throws VisitCanceledException,
498
                BaseException {
499
                    Feature feat = (Feature) obj;
500
                    drawFeatureSingleLayer(image, g, viewPort, cancel,
501
                        coordTrans, dpi, drawnNotification, feat, selection);
502
                }
503
            });
504

    
505
        } catch (ConcurrentDataModificationException e) {
506
            cancel.setCanceled(true);
507
            return;
508
        }
509
    }
510

    
511
    /**
512
     * Draws a Feature with the symbols of the legend, using a single drawing
513
     * layer.
514
     */
515
    private void drawFeatureSingleLayer(BufferedImage image, Graphics2D g,
516
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
517
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
518
        Feature feat, FeatureSelection selection)
519
    throws MapContextException, CreateGeometryException {
520

    
521
        Geometry geom = feat.getDefaultGeometry();
522
        if (geom == null) {
523
            return;
524
        }
525

    
526
        if (geom.getType() == Geometry.TYPES.NULL) {
527
            return;
528
        }
529

    
530
        ISymbol sym = getSymbol(feat, selection);
531
        if (sym == null) {
532
            return;
533
        }
534

    
535
        if (coordTrans != null) {
536
            geom = geom.cloneGeometry();
537
            try {
538
                geom.reProject(coordTrans);
539
            } catch (ReprojectionRuntimeException re) {
540
                /*
541
                 * Library was unable to reproject
542
                 * See reproject method in Point2D (geometry)
543
                 */
544
                throw new CreateGeometryException(
545
                    geom.getGeometryType().getType(),
546
                    geom.getGeometryType().getSubType(),
547
                    re);
548
            }
549
        }
550

    
551
        if (cancel.isCanceled()) {
552
            return;
553
        }
554

    
555
        drawGeometry(geom, image, feat, sym, viewPort, g, dpi, cancel);
556

    
557
        // Notify the drawing observers
558
        drawnNotification.setFeature(feat);
559
        drawnNotification.setDrawnGeometry(geom);
560
        notifyObservers(drawnNotification);
561
    }
562

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

    
573
        // -- visual FX stuff
574
        long time = System.currentTimeMillis();
575

    
576
        boolean bSymbolLevelError = false;
577
        // render temporary map each screenRefreshRate milliseconds;
578
        int screenRefreshDelay = (int) ((1D / MapContext.getDrawFrameRate()) * 3 * 1000);
579
        BufferedImage[] imageLevels = null;
580
        Graphics2D[] graphics = null;
581

    
582
        imageLevels = new BufferedImage[getZSort().getLevelCount()];
583
        graphics = new Graphics2D[imageLevels.length];
584
        for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
585

    
586
            imageLevels[i] = CompatLocator.getGraphicsUtils()
587
            .createBufferedImage(image.getWidth(), image.getHeight(),
588
                image.getType());
589

    
590
            graphics[i] = imageLevels[i].createGraphics();
591
            graphics[i].setTransform(g.getTransform());
592
            graphics[i].setRenderingHints(g.getRenderingHints());
593
        }
594
        // -- end visual FX stuff
595

    
596
        DisposableIterator it = null;
597
        try {
598
            it = featureSet.fastIterator();
599
            // Iteration over each feature
600
            while (it.hasNext()) {
601
                if (cancel.isCanceled()) {
602
                    return;
603
                }
604
                Feature feat = (Feature) it.next();
605

    
606
                bSymbolLevelError |= drawFeatureMultiLayer(image, g, viewPort,
607
                    cancel, coordTrans, dpi, drawnNotification, selection,
608
                    time, screenRefreshDelay, imageLevels, graphics, feat);
609

    
610
            }
611
        } catch (ConcurrentDataModificationException e) {
612
            cancel.setCanceled(true);
613
            return;
614
        } finally {
615
            if (it != null) {
616
                it.dispose();
617
            }
618
        }
619

    
620
        g.drawImage(image, 0, 0, null);
621

    
622
        Point2D offset = viewPort.getOffset();
623
        CompatLocator.getGraphicsUtils().translate(g, offset.getX(),
624
            offset.getY());
625

    
626
        for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
627
            g.drawImage(imageLevels[i], 0, 0, null);
628
            imageLevels[i] = null;
629
            graphics[i] = null;
630
        }
631

    
632
        CompatLocator.getGraphicsUtils().translate(g, -offset.getX(),
633
            -offset.getY());
634

    
635
        imageLevels = null;
636
        graphics = null;
637

    
638
        if (bSymbolLevelError) {
639
            setZSort(null);
640
        }
641
    }
642

    
643
    /**
644
     * Draws a Feature with the symbols of the legend, using a multiple drawing
645
     * layer.
646
     */
647
    private boolean drawFeatureMultiLayer(BufferedImage image, Graphics2D g,
648
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
649
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
650
        FeatureSelection selection, long time, int screenRefreshDelay,
651
        BufferedImage[] imageLevels, Graphics2D[] graphics, Feature feat)
652
    throws MapContextException, CreateGeometryException {
653

    
654
        Geometry geom = feat.getDefaultGeometry();
655
        boolean bSymbolLevelError = false;
656
        long drawingTime = time;
657

    
658
        if (geom.getType() == Geometry.TYPES.NULL) {
659
            return false;
660
        }
661

    
662
        ISymbol sym = getSymbol(feat, selection);
663

    
664
        if (sym == null) {
665
            return false;
666
        }
667

    
668
        if (coordTrans != null) {
669
            geom = geom.cloneGeometry();
670
            geom.reProject(coordTrans);
671
        }
672

    
673
        if (cancel.isCanceled()) {
674
            return false;
675
        }
676

    
677
        // Check if this symbol is a multilayer
678
        int[] symLevels = getZSort().getLevels(sym);
679
        if (sym instanceof IMultiLayerSymbol) {
680
            // if so, treat each of its layers as a single
681
            // symbol
682
            // in its corresponding map level
683
            IMultiLayerSymbol mlSym = (IMultiLayerSymbol) sym;
684
            for (int i = 0; !cancel.isCanceled() && i < mlSym.getLayerCount(); i++) {
685
                ISymbol mySym = mlSym.getLayer(i);
686
                int symbolLevel = 0;
687
                if (symLevels != null) {
688
                    symbolLevel = symLevels[i];
689
                } else {
690
                    /*
691
                     * an error occured when managing symbol levels some of the
692
                     * legend changed events regarding the symbols did not
693
                     * finish satisfactory and the legend is now inconsistent.
694
                     * For this drawing, it will finish as it was at the bottom
695
                     * (level 0) but, when done, the ZSort will be reset to
696
                     * avoid app crashes. This is a bug that has to be fixed.
697
                     */
698
                    bSymbolLevelError = true;
699
                }
700
                drawGeometry(geom, imageLevels[symbolLevel], feat, mySym,
701
                    viewPort, graphics[symbolLevel], dpi, cancel);
702
            }
703
        } else {
704
            // else, just draw the symbol in its level
705
            int symbolLevel = 0;
706
            if (symLevels != null) {
707
                symbolLevel = symLevels[0];
708
            }
709
            drawGeometry(geom, imageLevels[symbolLevel], feat, sym, viewPort,
710
                graphics[symbolLevel], dpi, cancel);
711
        }
712

    
713
        // -- visual FX stuff
714
        // Cuando el offset!=0 se est? dibujando sobre el
715
        // Layout y por tanto no tiene que ejecutar el
716
        // siguiente c?digo.
717
        Point2D offset = viewPort.getOffset();
718
        if (offset.getX() == 0 && offset.getY() == 0) {
719
            if ((System.currentTimeMillis() - drawingTime) > screenRefreshDelay) {
720

    
721
                BufferedImage virtualBim = CompatLocator.getGraphicsUtils()
722
                .createBufferedImage(image.getWidth(),
723
                    image.getHeight(), BufferedImage.TYPE_INT_ARGB);
724

    
725
                Graphics2D virtualGraphics = virtualBim.createGraphics();
726
                virtualGraphics.drawImage(image, 0, 0, null);
727
                for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
728
                    virtualGraphics.drawImage(imageLevels[i], 0, 0, null);
729
                }
730
                g.clearRect(0, 0, image.getWidth(), image.getHeight());
731
                g.drawImage(virtualBim, 0, 0, null);
732
                drawingTime = System.currentTimeMillis();
733
            }
734
            // -- end visual FX stuff
735

    
736
        }
737
        // Notify the drawing observers
738
        drawnNotification.setFeature(feat);
739
        drawnNotification.setDrawnGeometry(geom);
740
        notifyObservers(drawnNotification);
741
        return bSymbolLevelError;
742
    }
743

    
744
    /**
745
     * Returns the symbol to use to draw a {@link Feature} taking into account
746
     * if it is selected.
747
     */
748
    private ISymbol getSymbol(Feature feat, FeatureSelection selection)
749
    throws MapContextException {
750
        // retrieve the symbol associated to such feature
751
        ISymbol sym = getSymbolByFeature(feat);
752

    
753
        if (sym != null && selection.isSelected(feat)) {
754
            sym = sym.getSymbolForSelection();
755
        }
756
        return sym;
757
    }
758

    
759
    /**
760
     * Returns if the legend is using a ZSort.
761
     */
762
    private boolean isUseZSort() {
763
        return getZSort() != null && getZSort().isUsingZSort();
764
    }
765

    
766
    /**
767
     * Draws a {@link Geometry} using the given {@link ISymbol}, over the
768
     * {@link Graphics2D} object.
769
     */
770
    private void drawGeometry(Geometry geom, BufferedImage image,
771
        Feature feature, ISymbol symbol, ViewPort viewPort,
772
        Graphics2D graphics, double dpi, Cancellable cancellable)
773
    throws CreateGeometryException {
774

    
775
        if (geom instanceof Aggregate) {
776
            drawAggregate((Aggregate) geom, image, feature, symbol, viewPort,
777
                graphics, dpi, cancellable);
778
        } else {
779
            boolean bDrawCartographicSupport = false;
780
            if (symbol instanceof CartographicSupport) {
781
                bDrawCartographicSupport = ((CartographicSupport) symbol)
782
                .getUnit() != -1;
783
            }
784

    
785
            double previousSize = 0.0d;
786

    
787
            if (bDrawCartographicSupport) {
788
                // make the symbol to resize itself with the current rendering
789
                // context
790
                previousSize = ((CartographicSupport) symbol)
791
                .toCartographicSize(viewPort, dpi, geom);
792
            }
793

    
794
            try {
795
                symbol.draw(graphics, viewPort.getAffineTransform(), geom,
796
                    feature, cancellable);
797
            } finally {
798
                if (bDrawCartographicSupport) {
799
                    // restore previous size
800
                    ((CartographicSupport) symbol).setCartographicSize(
801
                        previousSize, geom);
802
                }
803
            }
804
        }
805
    }
806

    
807
    /**
808
     * Draws an {@link Aggregate} using the given {@link ISymbol}, over the
809
     * {@link Graphics2D} object.
810
     */
811
    private void drawAggregate(Aggregate aggregate, BufferedImage image,
812
        Feature feature, ISymbol symbol, ViewPort viewPort,
813
        Graphics2D graphics, double dpi, Cancellable cancellable)
814
    throws CreateGeometryException {
815
        for (int i = 0; i < aggregate.getPrimitivesNumber(); i++) {
816
            Geometry prim = aggregate.getPrimitiveAt(i);
817
            drawGeometry(prim, image, feature, symbol, viewPort, graphics, dpi,
818
                cancellable);
819
        }
820
    }
821

    
822
    public Object clone() throws CloneNotSupportedException {
823
        AbstractVectorialLegend clone = (AbstractVectorialLegend) super.clone();
824

    
825
        // Clone zSort
826
        ZSort zSort = getZSort();
827
        if (zSort != null) {
828
            clone.setZSort(new ZSort(clone));
829
        }
830
        return clone;
831
    }
832

    
833
    public void loadFromState(PersistentState state)
834
    throws PersistenceException {
835
        // Set parent properties
836
        super.loadFromState(state);
837
        // Set own properties
838

    
839
        setShapeType(state.getInt(FIELD_SHAPETYPE));
840
        if (state.getBoolean(FIELD_HAS_ZSORT)) {
841
            setZSort(new ZSort(this));
842
        }
843
        setDefaultSymbol((ISymbol) state.get(FIELD_DEFAULT_SYMBOL));
844
    }
845

    
846
    public void saveToState(PersistentState state) throws PersistenceException {
847
        // Save parent properties
848
        super.saveToState(state);
849
        // Save own properties
850
        state.set(FIELD_SHAPETYPE, getShapeType());
851
        state.set(FIELD_HAS_ZSORT, this.zSort != null);
852
        state.set(FIELD_DEFAULT_SYMBOL, getDefaultSymbol());
853
    }
854

    
855
    public static class RegisterPersistence implements Callable {
856

    
857
        public Object call() throws Exception {
858
            PersistenceManager manager = ToolsLocator.getPersistenceManager();
859
            if (manager
860
                .getDefinition(VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME) == null) {
861
                DynStruct definition = manager.addDefinition(
862
                    AbstractVectorialLegend.class,
863
                    VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME,
864
                    VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME
865
                    + " persistence definition", null, null);
866
                // Extend the Legend base definition
867
                definition.extend(manager
868
                    .getDefinition(LEGEND_PERSISTENCE_DEFINITION_NAME));
869

    
870
                // Shapetype
871
                definition.addDynFieldInt(FIELD_SHAPETYPE).setMandatory(true);
872
                // ZSort
873
                definition.addDynFieldBoolean(FIELD_HAS_ZSORT).setMandatory(
874
                    true);
875
                // Default symbol
876
                definition.addDynFieldObject(FIELD_DEFAULT_SYMBOL)
877
                .setClassOfValue(ISymbol.class).setMandatory(true);
878
            }
879
            return Boolean.TRUE;
880
        }
881

    
882
    }
883

    
884
    /**
885
     * Returns the names of the {@link Feature} attributes required for the
886
     * {@link ILegend} to operate.
887
     * 
888
     * @param featureStore
889
     *            the store where the {@link Feature}s belong to
890
     * @return the names of required {@link Feature} attribute names
891
     * @throws DataException
892
     *             if there is an error getting the attribute names
893
     */
894
    protected abstract String[] getRequiredFeatureAttributeNames(
895
        FeatureStore featureStore) throws DataException;
896
}