Statistics
| Revision:

root / tags / v2_0_0_Build_2047 / 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 @ 38311

History | View | Annotate | Download (33.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.ConcurrentModificationException;
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.gvsig.compat.CompatLocator;
40
import org.gvsig.compat.print.PrintAttributes;
41
import org.gvsig.fmap.dal.exception.DataException;
42
import org.gvsig.fmap.dal.exception.ReadException;
43
import org.gvsig.fmap.dal.feature.Feature;
44
import org.gvsig.fmap.dal.feature.FeatureQuery;
45
import org.gvsig.fmap.dal.feature.FeatureSelection;
46
import org.gvsig.fmap.dal.feature.FeatureSet;
47
import org.gvsig.fmap.dal.feature.FeatureStore;
48
import org.gvsig.fmap.dal.feature.exception.ConcurrentDataModificationException;
49
import org.gvsig.fmap.geom.Geometry;
50
import org.gvsig.fmap.geom.GeometryLocator;
51
import org.gvsig.fmap.geom.GeometryManager;
52
import org.gvsig.fmap.geom.aggregate.Aggregate;
53
import org.gvsig.fmap.geom.exception.CreateGeometryException;
54
import org.gvsig.fmap.geom.operation.DrawInts;
55
import org.gvsig.fmap.geom.operation.DrawOperationContext;
56
import org.gvsig.fmap.geom.operation.GeometryOperationException;
57
import org.gvsig.fmap.geom.operation.GeometryOperationNotSupportedException;
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.IntersectsEnvelopeEvaluator;
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.dynobject.DynStruct;
73
import org.gvsig.tools.exception.BaseException;
74
import org.gvsig.tools.persistence.PersistenceManager;
75
import org.gvsig.tools.persistence.PersistentState;
76
import org.gvsig.tools.persistence.exception.PersistenceException;
77
import org.gvsig.tools.task.Cancellable;
78
import org.gvsig.tools.task.SimpleTaskStatus;
79
import org.gvsig.tools.util.Callable;
80
import org.gvsig.tools.visitor.VisitCanceledException;
81
import org.gvsig.tools.visitor.Visitor;
82

    
83
/**
84
 * Base implementation for Vectorial data Legends.
85
 * 
86
 * Provides a draw method implementation which loads the {@link Feature}s and
87
 * uses the {@link ISymbol} objects to draw the {@link Geometry} objects.
88
 * 
89
 * @author 2009- <a href="cordinyana@gvsig.org">C?sar Ordi?ana</a> - gvSIG team
90
 */
91
public abstract class AbstractVectorialLegend extends AbstractLegend implements
92
IVectorLegend {
93

    
94
    private static final int DRAW_MAX_ATTEMPTS = 3;
95

    
96
        public static final String VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME = "VectorialLegend";
97

    
98
    private static final String FIELD_HAS_ZSORT = "hasZSort";
99
    private static final String FIELD_SHAPETYPE = "shapeType";
100
    private static final String FIELD_DEFAULT_SYMBOL = "defaultSymbol";
101

    
102
    private static final GeometryManager geomManager = GeometryLocator
103
    .getGeometryManager();
104

    
105
    protected ZSort zSort;
106

    
107
    public ZSort getZSort() {
108
        return zSort;
109
    }
110

    
111
    public void setZSort(ZSort zSort) {
112
        if (zSort == null) {
113
            removeLegendListener(this.zSort);
114
        }
115
        this.zSort = zSort;
116
        addLegendListener(zSort);
117
    }
118
    
119
    @SuppressWarnings("unchecked")
120
    public void draw(BufferedImage image, Graphics2D g, ViewPort viewPort,
121
        Cancellable cancel, double scale, Map queryParameters,
122
        ICoordTrans coordTrans, FeatureStore featureStore)
123
    throws LegendException {
124
        double dpi = MapContext.getScreenDPI();
125
        draw(image, g, viewPort, cancel, scale, queryParameters, coordTrans,
126
            featureStore, null, dpi);
127
    }
128
    
129
    @SuppressWarnings("unchecked")
130
    public void draw(BufferedImage image, Graphics2D g, ViewPort viewPort,
131
        Cancellable cancel, double scale, Map queryParameters,
132
        ICoordTrans coordTrans, FeatureStore featureStore, FeatureQuery featureQuery)
133
    throws LegendException {
134
        double dpi = MapContext.getScreenDPI();
135
        draw(image, g, viewPort, cancel, scale, queryParameters, coordTrans,
136
            featureStore, featureQuery, dpi);
137
    }
138

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

    
155
        int resolution = properties.getPrintQuality();
156

    
157
        if (resolution == PrintAttributes.PRINT_QUALITY_NORMAL) {
158
            dpi = 300;
159
        } else if (resolution == PrintAttributes.PRINT_QUALITY_HIGH) {
160
            dpi = 600;
161
        } else if (resolution == PrintAttributes.PRINT_QUALITY_DRAFT) {
162
            dpi = 72;
163
        }
164

    
165
        FeatureSet featureSet = null;
166
        DisposableIterator it = null;
167
        try {
168
            ZSort zSort = getZSort();
169

    
170
            // if layer has map levels it will use a ZSort
171
            boolean useZSort = zSort != null && zSort.isUsingZSort();
172

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

    
178
                if (featureQuery == null){
179
                    featureQuery = featureStore.createFeatureQuery();
180
                }
181
                featureQuery.setAttributeNames(fieldNames);
182
                featureQuery.setScale(scale);
183

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

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

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

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

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

    
231
                    // Check if this symbol is sized with CartographicSupport
232
                    CartographicSupport csSym = null;
233
                    int symbolType = sym.getSymbolType();
234

    
235
                    if (symbolType == Geometry.TYPES.POINT
236
                        || symbolType == Geometry.TYPES.CURVE
237
                        || sym instanceof CartographicSupport) {
238

    
239
                        csSym = (CartographicSupport) sym;
240
                    }
241

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

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

    
285
                int retryCount = 0;
286
                SimpleTaskStatus taskStatus = ToolsLocator.getTaskStatusManager()
287
                                .createDefaultSimpleTaskStatus(featureStore.getName());
288
                taskStatus.add();
289
                boolean drawPerformed = false;
290
                while (retryCount < DRAW_MAX_ATTEMPTS && !drawPerformed) {
291
                        try {
292
                                internalDraw(image, g, viewPort, cancel, scale,
293
                                                queryParameters, coordTrans, featureStore,
294
                                                featureQuery, dpi, taskStatus);
295
                                drawPerformed = true;
296
                        } catch (IllegalStateException e) {
297
                                retryCount++;
298
                                if (retryCount >= DRAW_MAX_ATTEMPTS) {
299
                                        throw e;
300
                                }
301
                    } catch (ConcurrentModificationException e) {
302
                                retryCount++;
303
                                if (retryCount >= DRAW_MAX_ATTEMPTS) {
304
                                        throw e;
305
                                }
306
                } catch (ConcurrentDataModificationException e) {
307
                                retryCount++;
308
                                if (retryCount >= DRAW_MAX_ATTEMPTS) {
309
                                        throw e;
310
                                }
311
                        } finally {
312
                                if (taskStatus != null) {
313
                                        taskStatus.terminate();
314
                                        taskStatus.remove();
315
                                        taskStatus = null;
316
                                }
317
                        }
318
                }
319
    }
320

    
321
        protected void internalDraw(BufferedImage image, Graphics2D g,
322
                        ViewPort viewPort, Cancellable cancel, double scale,
323
                        Map queryParameters, ICoordTrans coordTrans,
324
                        FeatureStore featureStore, FeatureQuery featureQuery, double dpi,
325
                        SimpleTaskStatus taskStatus) throws LegendDrawingException {
326

    
327
                if (!getDefaultSymbol().isShapeVisible()) {
328
                        return;
329
                }
330

    
331
                if (cancel.isCanceled()) {
332
                        return;
333
                }
334

    
335
                IProjection dataProjection;
336
                Envelope reprojectedDataEnvelop;
337

    
338
                try {
339
                        if (coordTrans == null) {
340
                                dataProjection = featureStore.getDefaultFeatureType()
341
                                                .getDefaultSRS();
342

    
343
                                // If the data does not provide a projection, use the
344
                                // current view one
345
                                if (dataProjection == null) {
346
                                        dataProjection = viewPort.getProjection();
347
                                }
348

    
349
                                reprojectedDataEnvelop = featureStore.getEnvelope();
350
                        } else {
351
                                dataProjection = coordTrans.getPOrig();
352

    
353
                                Envelope env = featureStore.getEnvelope();
354
                                if (!env.isEmpty()) {
355
                                        reprojectedDataEnvelop = env.convert(coordTrans);
356
                                } else {
357
                                        reprojectedDataEnvelop = env;
358
                                }
359
                        }
360
                } catch (DataException e) {
361
                        throw new LegendDrawingException(e);
362
                }
363

    
364
                // Gets the view envelope
365
                Envelope viewPortEnvelope = viewPort.getAdjustedEnvelope();
366

    
367
                // Gets the data envelope with the viewport SRS
368
                Envelope myEnvelope = reprojectedDataEnvelop;
369

    
370
                // TODO: in some cases, the legend may need a different check to
371
                // decide if the data must be drawn or not
372
                // Checks if the viewport envelope intersects with the data envelope
373
                if (!viewPortEnvelope.intersects(myEnvelope)) {
374
                        // The data is not visible in the current viewport, do nothing.
375
                        return;
376
                }
377

    
378
                // Check if all the data is contained into the viewport envelope
379
                boolean containsAll = viewPortEnvelope.contains(myEnvelope);
380

    
381
                // Create the drawing notification to be reused on each iteration
382
                DefaultFeatureDrawnNotification drawnNotification = new DefaultFeatureDrawnNotification();
383

    
384
                if (cancel.isCanceled()) {
385
                        return;
386
                }
387

    
388
                FeatureSet featureSet = null;
389
                try {
390
                        taskStatus.message("Retrieve selection");
391
                        FeatureSelection selection = featureStore.getFeatureSelection();
392

    
393
                        if (featureQuery == null) {
394
                                featureQuery = featureStore.createFeatureQuery();
395
                        }
396

    
397
                        completeQuery(featureStore, featureQuery, scale, queryParameters,
398
                                        coordTrans, dataProjection, viewPortEnvelope, containsAll);
399

    
400
                        taskStatus.message("Retrieve data");
401
                        featureSet = featureStore.getFeatureSet(featureQuery);
402

    
403
                        if (cancel.isCanceled()) {
404
                                return;
405
                        }
406

    
407
                        taskStatus.message("Drawing");
408
                        drawFeatures(image, g, viewPort, cancel, coordTrans, dpi,
409
                                        drawnNotification, featureSet, selection);
410

    
411
                } catch (BaseException e) {
412
                        throw new LegendDrawingException(e);
413
                } finally {
414
                        if (featureSet != null) {
415
                                featureSet.dispose();
416
                        }
417
                }
418
        }
419

    
420
    /**
421
     * Complete a {@link FeatureQuery} to load the {@link Feature}s to draw.
422
     */
423
    @SuppressWarnings("unchecked")
424
    private FeatureQuery completeQuery(FeatureStore featureStore, FeatureQuery featureQuery, double scale,
425
        Map queryParameters, ICoordTrans coordTrans,
426
        IProjection dataProjection, Envelope viewPortEnvelope,
427
        boolean containsAll) throws DataException {
428

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

    
447
            if (dataProjection == null) {
448
                throw new IllegalArgumentException(
449
                "Error, the projection parameter value is null");
450
            }
451

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

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

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

    
501
        try {
502
            featureSet.accept(new Visitor() {
503
                public void visit(Object obj) throws VisitCanceledException,
504
                BaseException {
505
                    Feature feat = (Feature) obj;
506
                    drawFeatureSingleLayer(image, g, viewPort, cancel,
507
                        coordTrans, dpi, drawnNotification, feat, selection);
508
                }
509
            });
510

    
511
        } catch (ConcurrentDataModificationException e) {
512
            cancel.setCanceled(true);
513
            return;
514
        }
515
    }
516

    
517
    /**
518
     * Draws a Feature with the symbols of the legend, using a single drawing
519
     * layer.
520
     */
521
    private void drawFeatureSingleLayer(BufferedImage image, Graphics2D g,
522
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
523
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
524
        Feature feat, FeatureSelection selection)
525
    throws MapContextException, CreateGeometryException {
526

    
527
        Geometry geom = feat.getDefaultGeometry();
528
        if (geom == null) {
529
            return;
530
        }
531

    
532
        if (geom.getType() == Geometry.TYPES.NULL) {
533
            return;
534
        }
535

    
536
        ISymbol sym = getSymbol(feat, selection);
537
        if (sym == null) {
538
            return;
539
        }
540

    
541
        if (coordTrans != null) {
542
            geom = geom.cloneGeometry();
543
            geom.reProject(coordTrans);
544
        }
545

    
546
        if (cancel.isCanceled()) {
547
            return;
548
        }
549

    
550
        drawGeometry(geom, image, feat, sym, viewPort, g, dpi, cancel);
551

    
552
        // Notify the drawing observers
553
        drawnNotification.setFeature(feat);
554
        drawnNotification.setDrawnGeometry(geom);
555
        notifyObservers(drawnNotification);
556
    }
557

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

    
568
        // -- visual FX stuff
569
        long time = System.currentTimeMillis();
570

    
571
        boolean bSymbolLevelError = false;
572
        // render temporary map each screenRefreshRate milliseconds;
573
        int screenRefreshDelay = (int) ((1D / MapContext.getDrawFrameRate()) * 3 * 1000);
574
        BufferedImage[] imageLevels = null;
575
        Graphics2D[] graphics = null;
576

    
577
        imageLevels = new BufferedImage[getZSort().getLevelCount()];
578
        graphics = new Graphics2D[imageLevels.length];
579
        for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
580

    
581
            imageLevels[i] = CompatLocator.getGraphicsUtils()
582
            .createBufferedImage(image.getWidth(), image.getHeight(),
583
                image.getType());
584

    
585
            graphics[i] = imageLevels[i].createGraphics();
586
            graphics[i].setTransform(g.getTransform());
587
            graphics[i].setRenderingHints(g.getRenderingHints());
588
        }
589
        // -- end visual FX stuff
590

    
591
        DisposableIterator it = null;
592
        try {
593
            it = featureSet.fastIterator();
594
            // Iteration over each feature
595
            while (it.hasNext()) {
596
                if (cancel.isCanceled()) {
597
                    return;
598
                }
599
                Feature feat = (Feature) it.next();
600

    
601
                bSymbolLevelError |= drawFeatureMultiLayer(image, g, viewPort,
602
                    cancel, coordTrans, dpi, drawnNotification, selection,
603
                    time, screenRefreshDelay, imageLevels, graphics, feat);
604

    
605
            }
606
        } catch (ConcurrentDataModificationException e) {
607
            cancel.setCanceled(true);
608
            return;
609
        } finally {
610
            if (it != null) {
611
                it.dispose();
612
            }
613
        }
614

    
615
        g.drawImage(image, 0, 0, null);
616

    
617
        Point2D offset = viewPort.getOffset();
618
        CompatLocator.getGraphicsUtils().translate(g, offset.getX(),
619
            offset.getY());
620

    
621
        for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
622
            g.drawImage(imageLevels[i], 0, 0, null);
623
            imageLevels[i] = null;
624
            graphics[i] = null;
625
        }
626

    
627
        CompatLocator.getGraphicsUtils().translate(g, -offset.getX(),
628
            -offset.getY());
629

    
630
        imageLevels = null;
631
        graphics = null;
632

    
633
        if (bSymbolLevelError) {
634
            setZSort(null);
635
        }
636
    }
637

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

    
649
        Geometry geom = feat.getDefaultGeometry();
650
        boolean bSymbolLevelError = false;
651
        long drawingTime = time;
652

    
653
        if (geom.getType() == Geometry.TYPES.NULL) {
654
            return false;
655
        }
656

    
657
        ISymbol sym = getSymbol(feat, selection);
658

    
659
        if (sym == null) {
660
            return false;
661
        }
662

    
663
        if (coordTrans != null) {
664
            geom = geom.cloneGeometry();
665
            geom.reProject(coordTrans);
666
        }
667

    
668
        if (cancel.isCanceled()) {
669
            return false;
670
        }
671

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

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

    
716
                BufferedImage virtualBim = CompatLocator.getGraphicsUtils()
717
                .createBufferedImage(image.getWidth(),
718
                    image.getHeight(), BufferedImage.TYPE_INT_ARGB);
719

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

    
731
        }
732
        // Notify the drawing observers
733
        drawnNotification.setFeature(feat);
734
        drawnNotification.setDrawnGeometry(geom);
735
        notifyObservers(drawnNotification);
736
        return bSymbolLevelError;
737
    }
738

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

    
748
        if (sym != null && selection.isSelected(feat)) {
749
            sym = sym.getSymbolForSelection();
750
        }
751
        return sym;
752
    }
753

    
754
    /**
755
     * Returns if the legend is using a ZSort.
756
     */
757
    private boolean isUseZSort() {
758
        return getZSort() != null && getZSort().isUsingZSort();
759
    }
760

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

    
770
        if (geom instanceof Aggregate) {
771
            drawAggregate((Aggregate) geom, image, feature, symbol, viewPort,
772
                graphics, dpi, cancellable);
773
        } else {
774
            boolean bDrawCartographicSupport = false;
775
            if (symbol instanceof CartographicSupport) {
776
                bDrawCartographicSupport = ((CartographicSupport) symbol)
777
                .getUnit() != -1;
778
            }
779

    
780
            double previousSize = 0.0d;
781

    
782
            if (bDrawCartographicSupport) {
783
                // make the symbol to resize itself with the current rendering
784
                // context
785
                previousSize = ((CartographicSupport) symbol)
786
                .toCartographicSize(viewPort, dpi, geom);
787
            }
788

    
789
            try {
790
                // draw it as normally
791
                double[] dot = new double[2];
792
                boolean onePoint = symbol.isOneDotOrPixel(geom, dot, viewPort,
793
                    (int) dpi);
794

    
795
                if (onePoint) {
796
                    if (dot[0] < 0 || dot[1] < 0 || dot[0] >= image.getWidth()
797
                        || dot[1] >= image.getHeight()) {
798
                        return;
799
                    }
800
                    // FIXME: setRgb should change to draw
801
                    image.setRGB((int) dot[0], (int) dot[1],
802
                        symbol.getOnePointRgb());
803
                } else {
804
                    symbol.draw(graphics, viewPort.getAffineTransform(),
805
                        geom, feature, cancellable);
806
                }
807

    
808
            } finally {
809
                if (bDrawCartographicSupport) {
810
                    // restore previous size
811
                    ((CartographicSupport) symbol).setCartographicSize(
812
                        previousSize, geom);
813
                }
814
            }
815
        }
816
    }
817

    
818
    /**
819
     * Draws an {@link Aggregate} using the given {@link ISymbol}, over the
820
     * {@link Graphics2D} object.
821
     */
822
    private void drawAggregate(Aggregate aggregate, BufferedImage image,
823
        Feature feature, ISymbol symbol, ViewPort viewPort,
824
        Graphics2D graphics, double dpi, Cancellable cancellable)
825
    throws CreateGeometryException {
826
        for (int i = 0; i < aggregate.getPrimitivesNumber(); i++) {
827
            Geometry prim = aggregate.getPrimitiveAt(i);
828
            drawGeometry(prim, image, feature, symbol, viewPort, graphics, dpi,
829
                cancellable);
830
        }
831
    }
832

    
833
    public Object clone() throws CloneNotSupportedException {
834
        AbstractVectorialLegend clone = (AbstractVectorialLegend) super.clone();
835

    
836
        // Clone zSort
837
        ZSort zSort = getZSort();
838
        if (zSort != null) {
839
            clone.setZSort(new ZSort(clone));
840
        }
841
        return clone;
842
    }
843

    
844
    public void loadFromState(PersistentState state)
845
    throws PersistenceException {
846
        // Set parent properties
847
        super.loadFromState(state);
848
        // Set own properties
849

    
850
        setShapeType(state.getInt(FIELD_SHAPETYPE));
851
        if (state.getBoolean(FIELD_HAS_ZSORT)) {
852
            setZSort(new ZSort(this));
853
        }
854
        setDefaultSymbol((ISymbol) state.get(FIELD_DEFAULT_SYMBOL));
855
    }
856

    
857
    public void saveToState(PersistentState state) throws PersistenceException {
858
        // Save parent properties
859
        super.saveToState(state);
860
        // Save own properties
861
        state.set(FIELD_SHAPETYPE, getShapeType());
862
        state.set(FIELD_HAS_ZSORT, this.zSort != null);
863
        state.set(FIELD_DEFAULT_SYMBOL, getDefaultSymbol());
864
    }
865

    
866
    public static class RegisterPersistence implements Callable {
867

    
868
        public Object call() throws Exception {
869
            PersistenceManager manager = ToolsLocator.getPersistenceManager();
870
            if (manager
871
                .getDefinition(VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME) == null) {
872
                DynStruct definition = manager.addDefinition(
873
                    AbstractVectorialLegend.class,
874
                    VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME,
875
                    VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME
876
                    + " persistence definition", null, null);
877
                // Extend the Legend base definition
878
                definition.extend(manager
879
                    .getDefinition(LEGEND_PERSISTENCE_DEFINITION_NAME));
880

    
881
                // Shapetype
882
                definition.addDynFieldInt(FIELD_SHAPETYPE).setMandatory(true);
883
                // ZSort
884
                definition.addDynFieldBoolean(FIELD_HAS_ZSORT).setMandatory(
885
                    true);
886
                // Default symbol
887
                definition.addDynFieldObject(FIELD_DEFAULT_SYMBOL)
888
                .setClassOfValue(ISymbol.class).setMandatory(true);
889
            }
890
            return Boolean.TRUE;
891
        }
892

    
893
    }
894

    
895
    /**
896
     * Returns the names of the {@link Feature} attributes required for the
897
     * {@link ILegend} to operate.
898
     * 
899
     * @param featureStore
900
     *            the store where the {@link Feature}s belong to
901
     * @return the names of required {@link Feature} attribute names
902
     * @throws DataException
903
     *             if there is an error getting the attribute names
904
     */
905
    protected abstract String[] getRequiredFeatureAttributeNames(
906
        FeatureStore featureStore) throws DataException;
907
}