Statistics
| Revision:

root / tags / v2_0_0_Build_2051 / 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 @ 38760

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.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
import org.slf4j.Logger;
83
import org.slf4j.LoggerFactory;
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_NORMAL) {
163
            dpi = 300;
164
        } else if (resolution == PrintAttributes.PRINT_QUALITY_HIGH) {
165
            dpi = 600;
166
        } else if (resolution == PrintAttributes.PRINT_QUALITY_DRAFT) {
167
            dpi = 72;
168
        }
169

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

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

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

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

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

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

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

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

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

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

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

    
244
                        csSym = (CartographicSupport) sym;
245
                    }
246

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

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

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

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

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

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

    
324
                IProjection dataProjection;
325
                Envelope reprojectedDataEnvelop;
326

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
535
        if (cancel.isCanceled()) {
536
            return;
537
        }
538

    
539
        drawGeometry(geom, image, feat, sym, viewPort, g, dpi, cancel);
540

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

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

    
557
        // -- visual FX stuff
558
        long time = System.currentTimeMillis();
559

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

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

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

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

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

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

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

    
604
        g.drawImage(image, 0, 0, null);
605

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

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

    
616
        CompatLocator.getGraphicsUtils().translate(g, -offset.getX(),
617
            -offset.getY());
618

    
619
        imageLevels = null;
620
        graphics = null;
621

    
622
        if (bSymbolLevelError) {
623
            setZSort(null);
624
        }
625
    }
626

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

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

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

    
646
        ISymbol sym = getSymbol(feat, selection);
647

    
648
        if (sym == null) {
649
            return false;
650
        }
651

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

    
657
        if (cancel.isCanceled()) {
658
            return false;
659
        }
660

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

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

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

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

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

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

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

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

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

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

    
769
            double previousSize = 0.0d;
770

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

    
778
            try {
779
                // draw it as normally
780
                double[] dot = new double[2];
781
                boolean onePoint = symbol.isOneDotOrPixel(geom, dot, viewPort,
782
                    (int) dpi);
783

    
784
                if (onePoint) {
785
                    if (dot[0] < 0 || dot[1] < 0 || dot[0] >= image.getWidth()
786
                        || dot[1] >= image.getHeight()) {
787
                        return;
788
                    }
789
                    // FIXME: setRgb should change to draw
790
                    image.setRGB((int) dot[0], (int) dot[1],
791
                        symbol.getOnePointRgb());
792
                } else {
793
                    symbol.draw(graphics, viewPort.getAffineTransform(),
794
                        geom, feature, cancellable);
795
                }
796

    
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
}