Statistics
| Revision:

root / 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 @ 38207

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

    
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 (ConcurrentModificationException e) {
297
                                retryCount++;
298
                                if (retryCount >= DRAW_MAX_ATTEMPTS) {
299
                                        throw e;
300
                                }
301
                        } finally {
302
                                if (taskStatus != null) {
303
                                        taskStatus.terminate();
304
                                        taskStatus.remove();
305
                                        taskStatus = null;
306
                                }
307
                        }
308
                }
309
    }
310

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

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

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

    
325
                IProjection dataProjection;
326
                Envelope reprojectedDataEnvelop;
327

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
620
        imageLevels = null;
621
        graphics = null;
622

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
770
            double previousSize = 0.0d;
771

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

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

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

    
798
            } finally {
799
                if (bDrawCartographicSupport) {
800
                    // restore previous size
801
                    ((CartographicSupport) symbol).setCartographicSize(
802
                        previousSize, geom);
803
                }
804
            }
805
        }
806
    }
807

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

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

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

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

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

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

    
856
    public static class RegisterPersistence implements Callable {
857

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

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

    
883
    }
884

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