Statistics
| Revision:

svn-gvsig-desktop / trunk / org.gvsig.desktop / org.gvsig.desktop.library / 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 @ 40560

History | View | Annotate | Download (34.7 KB)

1
/**
2
 * gvSIG. Desktop Geographic Information System.
3
 *
4
 * Copyright (C) 2007-2013 gvSIG Association.
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 3
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
 * For any additional information, do not hesitate to contact us
22
 * at info AT gvsig.com, or visit our website www.gvsig.com.
23
 */
24
/*
25
 * AUTHORS (In addition to CIT):
26
 * 2009 {DiSiD Technologies}  {{Task}}
27
 */
28
package org.gvsig.symbology.fmap.mapcontext.rendering.legend.impl;
29

    
30
import java.awt.Graphics2D;
31
import java.awt.geom.Point2D;
32
import java.awt.image.BufferedImage;
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.slf4j.Logger;
40
import org.slf4j.LoggerFactory;
41

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

    
88
/**
89
 * Base implementation for Vectorial data Legends.
90
 * 
91
 * Provides a draw method implementation which loads the {@link Feature}s and
92
 * uses the {@link ISymbol} objects to draw the {@link Geometry} objects.
93
 * 
94
 * @author 2009- <a href="cordinyana@gvsig.org">C?sar Ordi?ana</a> - gvSIG team
95
 */
96
public abstract class AbstractVectorialLegend extends AbstractLegend implements
97
IVectorLegend {
98
        
99
        private static final Logger LOG = LoggerFactory
100
                        .getLogger(AbstractVectorialLegend.class);
101

    
102
    private static final int DRAW_MAX_ATTEMPTS = 5;
103

    
104
        public static final String VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME = "VectorialLegend";
105

    
106
    private static final String FIELD_HAS_ZSORT = "hasZSort";
107
    private static final String FIELD_SHAPETYPE = "shapeType";
108
    private static final String FIELD_DEFAULT_SYMBOL = "defaultSymbol";
109

    
110
    private static final GeometryManager geomManager = GeometryLocator
111
    .getGeometryManager();
112

    
113
    protected ZSort zSort;
114

    
115
    public ZSort getZSort() {
116
        return zSort;
117
    }
118

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

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

    
163
        int resolution = properties.getPrintQuality();
164

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

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

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

    
179
            int mapLevelCount = (useZSort) ? zSort.getLevelCount() : 1;
180
            for (int mapPass = 0; mapPass < mapLevelCount; mapPass++) {
181
                
182
                Envelope vp_env_in_store_crs = null;
183
                IProjection store_crs = null;
184
                if (coordTrans != null) {
185
                    // 'coordTrans' is from store crs to vp crs
186
                    ICoordTrans inv = coordTrans.getInverted();
187
                    Envelope aux = viewPort.getAdjustedEnvelope();
188
                    vp_env_in_store_crs = aux.convert(inv);
189
                    store_crs = coordTrans.getPOrig();
190
                } else {
191
                    vp_env_in_store_crs = viewPort.getAdjustedEnvelope();
192
                    store_crs = viewPort.getProjection();
193
                }
194
                
195
                FeatureQuery feat_query = fquery;
196
                Envelope store_env = featureStore.getEnvelope();
197
                boolean use_intersection_cond = false;
198
                if (store_env == null) {
199
                    // Store does not know its envelope, so we must:
200
                    use_intersection_cond = true;
201
                } else {
202
                    if (vp_env_in_store_crs.contains(store_env)) {
203
                        use_intersection_cond = false;
204
                    } else {
205
                        use_intersection_cond = true;
206
                    }
207
                }
208
                
209
                if (use_intersection_cond) {
210
                    FeatureType ft = featureStore.getDefaultFeatureType(); 
211
                    IntersectsEnvelopeEvaluator iee =
212
                        new IntersectsEnvelopeEvaluator(
213
                            vp_env_in_store_crs,
214
                            store_crs,
215
                            ft, ft.getDefaultGeometryAttributeName());
216
                    if (feat_query == null) {
217
                        feat_query = featureStore.createFeatureQuery();
218
                        String[] fns = getRequiredFeatureAttributeNames(featureStore);
219
                        feat_query.setAttributeNames(fns);
220
                    }
221
                    feat_query.addFilter(iee);
222
                }
223
                
224
                // 'feat_query' can still be NULL here, so we only filter
225
                // the featureStore if it's not null
226
                if (feat_query == null) {
227
                    featureSet = featureStore.getFeatureSet();
228
                } else {
229
                    featureSet = featureStore.getFeatureSet(feat_query);
230
                }
231
                
232
                it = featureSet.fastIterator();
233
                // Iteration over each feature
234
                while (!cancel.isCanceled() && it.hasNext()) {
235
                    Feature feat = (Feature) it.next();
236
                    Geometry geom = feat.getDefaultGeometry();
237
                    // Reprojection if needed
238
                    if (coordTrans != null) {
239
                        geom = geom.cloneGeometry();
240
                        geom.reProject(coordTrans);
241
                    }
242

    
243
                    // retrieve the symbol associated to such feature
244
                    ISymbol sym = getSymbolByFeature(feat);
245
                    if (sym == null) {
246
                        continue;
247
                    }
248
                    if (useZSort) {
249
                        int[] symLevels = zSort.getLevels(sym);
250
                        if (symLevels != null) {
251

    
252
                            // Check if this symbol is a multilayer
253
                            if (sym instanceof IMultiLayerSymbol) {
254
                                // if so, get the layer corresponding to the
255
                                // current level. If none, continue to next
256
                                // iteration
257
                                IMultiLayerSymbol mlSym = (IMultiLayerSymbol) sym;
258
                                for (int i = 0; i < mlSym.getLayerCount(); i++) {
259
                                    ISymbol mySym = mlSym.getLayer(i);
260
                                    if (symLevels[i] == mapPass) {
261
                                        sym = mySym;
262
                                        break;
263
                                    }
264
                                }
265

    
266
                            } else {
267
                                // else, just draw the symbol in its level
268
                                if (symLevels[0] != mapPass) {
269
                                    continue;
270
                                }
271
                            }
272
                        }
273
                    }
274

    
275
                    // Check if this symbol is sized with CartographicSupport
276
                    CartographicSupport csSym = null;
277
                    int symbolType = sym.getSymbolType();
278

    
279
                    if (symbolType == Geometry.TYPES.POINT
280
                        || symbolType == Geometry.TYPES.CURVE
281
                        || sym instanceof CartographicSupport) {
282

    
283
                        csSym = (CartographicSupport) sym;
284
                    }
285

    
286
                    DrawOperationContext doc = new DrawOperationContext();
287
                    doc.setGraphics(g);
288
                    doc.setViewPort(viewPort);
289
                    if (csSym == null) {
290
                        doc.setSymbol(sym);
291
                    } else {
292
                        doc.setDPI(dpi);
293
                        doc.setCancellable(cancel);
294
                        doc.setSymbol((ISymbol) csSym);
295
                    }
296
                    geom.invokeOperation(DrawInts.CODE, doc);
297
                }
298
            }
299
        } catch (ReadException e) {
300
            throw new LegendDrawingException(e);
301
        } catch (GeometryOperationNotSupportedException e) {
302
            throw new LegendDrawingException(e);
303
        } catch (GeometryOperationException e) {
304
            throw new LegendDrawingException(e);
305
        } catch (DataException e) {
306
            throw new LegendDrawingException(e);
307
        } catch (MapContextException e) {
308
            throw new LegendDrawingException(e);
309
        } finally {
310
            if (it != null) {
311
                it.dispose();
312
            }
313
            if (featureSet != null) {
314
                featureSet.dispose();
315
            }
316
        }
317
    }
318

    
319
    /**
320
     * Draws the features from the {@link FeatureStore}, filtered with the scale
321
     * and the query parameters, with the symbols of the legend.
322
     */
323
    @SuppressWarnings("unchecked")
324
        protected void draw(BufferedImage image, Graphics2D g, ViewPort viewPort,
325
                        Cancellable cancel, double scale, Map queryParameters,
326
                        ICoordTrans coordTrans, FeatureStore featureStore,
327
                        FeatureQuery featureQuery, double dpi) throws LegendException {
328

    
329
                SimpleTaskStatus taskStatus = ToolsLocator.getTaskStatusManager()
330
                                .createDefaultSimpleTaskStatus(featureStore.getName());
331
                taskStatus.add();
332
                try {
333
                        // Avoid ConcurrentModificationException errors if
334
                        // while drawing another thread edits the store data.
335
                        synchronized (featureStore) {
336
                                internalDraw(image, g, viewPort, cancel, scale,
337
                                                queryParameters, coordTrans, featureStore,
338
                                                featureQuery, dpi, taskStatus);
339
                        }
340
                } finally {
341
                        if (taskStatus != null) {
342
                                taskStatus.terminate();
343
                                taskStatus.remove();
344
                                taskStatus = null;
345
                        }
346
                }
347
        }
348

    
349
        protected void internalDraw(BufferedImage image, Graphics2D g,
350
                        ViewPort viewPort, Cancellable cancel, double scale,
351
                        Map queryParameters, ICoordTrans coordTrans,
352
                        FeatureStore featureStore, FeatureQuery featureQuery, double dpi,
353
                        SimpleTaskStatus taskStatus) throws LegendDrawingException {
354

    
355
                if (!getDefaultSymbol().isShapeVisible()) {
356
                        return;
357
                }
358

    
359
                if (cancel.isCanceled()) {
360
                        return;
361
                }
362

    
363
                IProjection dataProjection;
364
                Envelope reprojectedDataEnvelop;
365

    
366
                try {
367
                        if (coordTrans == null) {
368
                                dataProjection = featureStore.getDefaultFeatureType()
369
                                                .getDefaultSRS();
370

    
371
                                // If the data does not provide a projection, use the
372
                                // current view one
373
                                if (dataProjection == null) {
374
                                        dataProjection = viewPort.getProjection();
375
                                }
376

    
377
                                reprojectedDataEnvelop = featureStore.getEnvelope();
378
                        } else {
379
                                dataProjection = coordTrans.getPOrig();
380

    
381
                                Envelope env = featureStore.getEnvelope();
382
                                if (!env.isEmpty()) {
383
                                        reprojectedDataEnvelop = env.convert(coordTrans);
384
                                } else {
385
                                        reprojectedDataEnvelop = env;
386
                                }
387
                        }
388
                } catch (DataException e) {
389
                        throw new LegendDrawingException(e);
390
                }
391

    
392
                // Gets the view envelope
393
                Envelope viewPortEnvelope = viewPort.getAdjustedEnvelope();
394

    
395
                // Gets the data envelope with the viewport SRS
396
                Envelope myEnvelope = reprojectedDataEnvelop;
397

    
398
                // TODO: in some cases, the legend may need a different check to
399
                // decide if the data must be drawn or not
400
                // Checks if the viewport envelope intersects with the data envelope
401
                if (!viewPortEnvelope.intersects(myEnvelope)) {
402
                        // The data is not visible in the current viewport, do nothing.
403
                        return;
404
                }
405

    
406
                // Check if all the data is contained into the viewport envelope
407
                boolean containsAll = viewPortEnvelope.contains(myEnvelope);
408

    
409
                // Create the drawing notification to be reused on each iteration
410
                DefaultFeatureDrawnNotification drawnNotification = new DefaultFeatureDrawnNotification();
411

    
412
                if (cancel.isCanceled()) {
413
                        return;
414
                }
415

    
416
                FeatureSet featureSet = null;
417
                try {
418
                        taskStatus.message("Retrieve selection");
419
                        FeatureSelection selection = featureStore.getFeatureSelection();
420

    
421
                        if (featureQuery == null) {
422
                                featureQuery = featureStore.createFeatureQuery();
423
                        }
424

    
425
                        completeQuery(featureStore, featureQuery, scale, queryParameters,
426
                                        coordTrans, dataProjection, viewPortEnvelope, containsAll);
427

    
428
                        taskStatus.message("Retrieve data");
429
                        featureSet = featureStore.getFeatureSet(featureQuery);
430

    
431
                        if (cancel.isCanceled()) {
432
                                return;
433
                        }
434

    
435
                        taskStatus.message("Drawing");
436
                        drawFeatures(image, g, viewPort, cancel, coordTrans, dpi,
437
                                        drawnNotification, featureSet, selection);
438

    
439
        } catch (RuntimeException e) {
440
            /*
441
             * Probably a reprojection exception (for example,
442
             * trying to reproject Canada to EPSG:23030)
443
             */
444
            throw new LegendDrawingException(e);
445
                } catch (BaseException e) {
446
                        throw new LegendDrawingException(e);
447
                } finally {
448
                        if (featureSet != null) {
449
                                featureSet.dispose();
450
                        }
451
                }
452
        }
453

    
454
    /**
455
     * Complete a {@link FeatureQuery} to load the {@link Feature}s to draw.
456
     */
457
    @SuppressWarnings("unchecked")
458
    private FeatureQuery completeQuery(FeatureStore featureStore, FeatureQuery featureQuery, double scale,
459
        Map queryParameters, ICoordTrans coordTrans,
460
        IProjection dataProjection, Envelope viewPortEnvelope,
461
        boolean containsAll) throws DataException {
462

    
463
        featureQuery.setScale(scale);
464
        
465
        //Adds the attributes
466
        String[] fieldNames = getRequiredFeatureAttributeNames(featureStore);
467
        for (int i=0 ; i<fieldNames.length ;i++){
468
            featureQuery.addAttributeName(fieldNames[i]);
469
        }       
470
        
471
        // TODO: Mobile has it's own IntersectsEnvelopeEvaluator
472
        if (!containsAll) {
473
            // Gets the viewport envelope with the data SRS
474
            Envelope viewPortEnvelopeInMyProj = viewPortEnvelope;
475
            // FIXME
476
            if (coordTrans != null) {
477
                viewPortEnvelopeInMyProj = viewPortEnvelope.convert(coordTrans
478
                        .getInverted());
479
            }
480

    
481
            if (dataProjection == null) {
482
                throw new IllegalArgumentException(
483
                "Error, the projection parameter value is null");
484
            }
485

    
486
            IntersectsEnvelopeEvaluator iee = new IntersectsEnvelopeEvaluator(
487
                viewPortEnvelopeInMyProj, dataProjection,
488
                featureStore.getDefaultFeatureType(), featureStore
489
                .getDefaultFeatureType()
490
                .getDefaultGeometryAttributeName());
491
            featureQuery.addFilter(iee);                        
492
        }
493
        if (queryParameters != null) {
494
            Iterator iterEntry = queryParameters.entrySet().iterator();
495
            Entry entry;
496
            while (iterEntry.hasNext()) {
497
                entry = (Entry) iterEntry.next();
498
                featureQuery.setQueryParameter((String) entry.getKey(),
499
                    entry.getValue());
500
            }
501
        }        
502
        return featureQuery;
503
    }
504

    
505
    /**
506
     * Draws the features from the {@link FeatureSet}, with the symbols of the
507
     * legend.
508
     */
509
    private void drawFeatures(BufferedImage image, Graphics2D g,
510
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
511
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
512
        FeatureSet featureSet, FeatureSelection selection)
513
    throws BaseException {
514
        if (isUseZSort()) {
515
            drawFeaturesMultiLayer(image, g, viewPort, cancel, coordTrans, dpi,
516
                drawnNotification, featureSet, selection);
517
        } else {
518
            drawFeaturesSingleLayer(image, g, viewPort, cancel, coordTrans,
519
                dpi, drawnNotification, featureSet, selection);
520
        }
521
    }
522

    
523
    /**
524
     * Draws the features from the {@link FeatureSet}, with the symbols of the
525
     * legend, using a single drawing layer.
526
     */
527
    private void drawFeaturesSingleLayer(final BufferedImage image,
528
        final Graphics2D g, final ViewPort viewPort,
529
        final Cancellable cancel, final ICoordTrans coordTrans,
530
        final double dpi,
531
        final DefaultFeatureDrawnNotification drawnNotification,
532
        FeatureSet featureSet, final FeatureSelection selection)
533
    throws BaseException {
534

    
535
        try {
536
            featureSet.accept(new Visitor() {
537
                public void visit(Object obj) throws VisitCanceledException,
538
                BaseException {
539
                    Feature feat = (Feature) obj;
540
                    drawFeatureSingleLayer(image, g, viewPort, cancel,
541
                        coordTrans, dpi, drawnNotification, feat, selection);
542
                }
543
            });
544

    
545
        } catch (ConcurrentDataModificationException e) {
546
            cancel.setCanceled(true);
547
            return;
548
        }
549
    }
550

    
551
    /**
552
     * Draws a Feature with the symbols of the legend, using a single drawing
553
     * layer.
554
     */
555
    private void drawFeatureSingleLayer(BufferedImage image, Graphics2D g,
556
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
557
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
558
        Feature feat, FeatureSelection selection)
559
    throws MapContextException, CreateGeometryException {
560

    
561
        Geometry geom = feat.getDefaultGeometry();
562
        if (geom == null) {
563
            return;
564
        }
565

    
566
        if (geom.getType() == Geometry.TYPES.NULL) {
567
            return;
568
        }
569

    
570
        ISymbol sym = getSymbol(feat, selection);
571
        if (sym == null) {
572
            return;
573
        }
574

    
575
        if (coordTrans != null) {
576
            geom = geom.cloneGeometry();
577
            try {
578
                geom.reProject(coordTrans);
579
            } catch (ReprojectionRuntimeException re) {
580
                /*
581
                 * Library was unable to reproject
582
                 * See reproject method in Point2D (geometry)
583
                 */
584
                throw new CreateGeometryException(
585
                    geom.getGeometryType().getType(),
586
                    geom.getGeometryType().getSubType(),
587
                    re);
588
            }
589
        }
590

    
591
        if (cancel.isCanceled()) {
592
            return;
593
        }
594

    
595
        drawGeometry(geom, image, feat, sym, viewPort, g, dpi, cancel);
596

    
597
        // Notify the drawing observers
598
        drawnNotification.setFeature(feat);
599
        drawnNotification.setDrawnGeometry(geom);
600
        notifyObservers(drawnNotification);
601
    }
602

    
603
    /**
604
     * Draws the features from the {@link FeatureSet}, with the symbols of the
605
     * legend, using a multiple drawing layer.
606
     */
607
    private void drawFeaturesMultiLayer(BufferedImage image, Graphics2D g,
608
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
609
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
610
        FeatureSet featureSet, FeatureSelection selection)
611
    throws MapContextException, CreateGeometryException, DataException {
612

    
613
        // -- visual FX stuff
614
        long time = System.currentTimeMillis();
615

    
616
        boolean bSymbolLevelError = false;
617
        // render temporary map each screenRefreshRate milliseconds;
618
        int screenRefreshDelay = (int) ((1D / MapContext.getDrawFrameRate()) * 3 * 1000);
619
        BufferedImage[] imageLevels = null;
620
        Graphics2D[] graphics = null;
621

    
622
        imageLevels = new BufferedImage[getZSort().getLevelCount()];
623
        graphics = new Graphics2D[imageLevels.length];
624
        for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
625

    
626
            imageLevels[i] = CompatLocator.getGraphicsUtils()
627
            .createBufferedImage(image.getWidth(), image.getHeight(),
628
                image.getType());
629

    
630
            graphics[i] = imageLevels[i].createGraphics();
631
            graphics[i].setTransform(g.getTransform());
632
            graphics[i].setRenderingHints(g.getRenderingHints());
633
        }
634
        // -- end visual FX stuff
635

    
636
        DisposableIterator it = null;
637
        try {
638
            it = featureSet.fastIterator();
639
            // Iteration over each feature
640
            while (it.hasNext()) {
641
                if (cancel.isCanceled()) {
642
                    return;
643
                }
644
                Feature feat = (Feature) it.next();
645

    
646
                bSymbolLevelError |= drawFeatureMultiLayer(image, g, viewPort,
647
                    cancel, coordTrans, dpi, drawnNotification, selection,
648
                    time, screenRefreshDelay, imageLevels, graphics, feat);
649

    
650
            }
651
        } catch (ConcurrentDataModificationException e) {
652
            cancel.setCanceled(true);
653
            return;
654
        } finally {
655
            if (it != null) {
656
                it.dispose();
657
            }
658
        }
659

    
660
        g.drawImage(image, 0, 0, null);
661

    
662
        Point2D offset = viewPort.getOffset();
663
        CompatLocator.getGraphicsUtils().translate(g, offset.getX(),
664
            offset.getY());
665

    
666
        for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
667
            g.drawImage(imageLevels[i], 0, 0, null);
668
            imageLevels[i] = null;
669
            graphics[i] = null;
670
        }
671

    
672
        CompatLocator.getGraphicsUtils().translate(g, -offset.getX(),
673
            -offset.getY());
674

    
675
        imageLevels = null;
676
        graphics = null;
677

    
678
        if (bSymbolLevelError) {
679
            setZSort(null);
680
        }
681
    }
682

    
683
    /**
684
     * Draws a Feature with the symbols of the legend, using a multiple drawing
685
     * layer.
686
     */
687
    private boolean drawFeatureMultiLayer(BufferedImage image, Graphics2D g,
688
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
689
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
690
        FeatureSelection selection, long time, int screenRefreshDelay,
691
        BufferedImage[] imageLevels, Graphics2D[] graphics, Feature feat)
692
    throws MapContextException, CreateGeometryException {
693

    
694
        Geometry geom = feat.getDefaultGeometry();
695
        boolean bSymbolLevelError = false;
696
        long drawingTime = time;
697

    
698
        if (geom.getType() == Geometry.TYPES.NULL) {
699
            return false;
700
        }
701

    
702
        ISymbol sym = getSymbol(feat, selection);
703

    
704
        if (sym == null) {
705
            return false;
706
        }
707

    
708
        if (coordTrans != null) {
709
            geom = geom.cloneGeometry();
710
            geom.reProject(coordTrans);
711
        }
712

    
713
        if (cancel.isCanceled()) {
714
            return false;
715
        }
716

    
717
        // Check if this symbol is a multilayer
718
        int[] symLevels = getZSort().getLevels(sym);
719
        if (sym instanceof IMultiLayerSymbol) {
720
            // if so, treat each of its layers as a single
721
            // symbol
722
            // in its corresponding map level
723
            IMultiLayerSymbol mlSym = (IMultiLayerSymbol) sym;
724
            for (int i = 0; !cancel.isCanceled() && i < mlSym.getLayerCount(); i++) {
725
                ISymbol mySym = mlSym.getLayer(i);
726
                int symbolLevel = 0;
727
                if (symLevels != null) {
728
                    symbolLevel = symLevels[i];
729
                } else {
730
                    /*
731
                     * an error occured when managing symbol levels some of the
732
                     * legend changed events regarding the symbols did not
733
                     * finish satisfactory and the legend is now inconsistent.
734
                     * For this drawing, it will finish as it was at the bottom
735
                     * (level 0) but, when done, the ZSort will be reset to
736
                     * avoid app crashes. This is a bug that has to be fixed.
737
                     */
738
                    bSymbolLevelError = true;
739
                }
740
                drawGeometry(geom, imageLevels[symbolLevel], feat, mySym,
741
                    viewPort, graphics[symbolLevel], dpi, cancel);
742
            }
743
        } else {
744
            // else, just draw the symbol in its level
745
            int symbolLevel = 0;
746
            if (symLevels != null) {
747
                symbolLevel = symLevels[0];
748
            }
749
            drawGeometry(geom, imageLevels[symbolLevel], feat, sym, viewPort,
750
                graphics[symbolLevel], dpi, cancel);
751
        }
752

    
753
        // -- visual FX stuff
754
        // Cuando el offset!=0 se est? dibujando sobre el
755
        // Layout y por tanto no tiene que ejecutar el
756
        // siguiente c?digo.
757
        Point2D offset = viewPort.getOffset();
758
        if (offset.getX() == 0 && offset.getY() == 0) {
759
            if ((System.currentTimeMillis() - drawingTime) > screenRefreshDelay) {
760

    
761
                BufferedImage virtualBim = CompatLocator.getGraphicsUtils()
762
                .createBufferedImage(image.getWidth(),
763
                    image.getHeight(), BufferedImage.TYPE_INT_ARGB);
764

    
765
                Graphics2D virtualGraphics = virtualBim.createGraphics();
766
                virtualGraphics.drawImage(image, 0, 0, null);
767
                for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
768
                    virtualGraphics.drawImage(imageLevels[i], 0, 0, null);
769
                }
770
                g.clearRect(0, 0, image.getWidth(), image.getHeight());
771
                g.drawImage(virtualBim, 0, 0, null);
772
                drawingTime = System.currentTimeMillis();
773
            }
774
            // -- end visual FX stuff
775

    
776
        }
777
        // Notify the drawing observers
778
        drawnNotification.setFeature(feat);
779
        drawnNotification.setDrawnGeometry(geom);
780
        notifyObservers(drawnNotification);
781
        return bSymbolLevelError;
782
    }
783

    
784
    /**
785
     * Returns the symbol to use to draw a {@link Feature} taking into account
786
     * if it is selected.
787
     */
788
    private ISymbol getSymbol(Feature feat, FeatureSelection selection)
789
    throws MapContextException {
790
        // retrieve the symbol associated to such feature
791
        ISymbol sym = getSymbolByFeature(feat);
792

    
793
        if (sym != null && selection.isSelected(feat)) {
794
            sym = sym.getSymbolForSelection();
795
        }
796
        return sym;
797
    }
798

    
799
    /**
800
     * Returns if the legend is using a ZSort.
801
     */
802
    private boolean isUseZSort() {
803
        return getZSort() != null && getZSort().isUsingZSort();
804
    }
805

    
806
    /**
807
     * Draws a {@link Geometry} using the given {@link ISymbol}, over the
808
     * {@link Graphics2D} object.
809
     */
810
    private void drawGeometry(Geometry geom, BufferedImage image,
811
        Feature feature, ISymbol symbol, ViewPort viewPort,
812
        Graphics2D graphics, double dpi, Cancellable cancellable)
813
    throws CreateGeometryException {
814

    
815
        if (geom instanceof Aggregate) {
816
            drawAggregate((Aggregate) geom, image, feature, symbol, viewPort,
817
                graphics, dpi, cancellable);
818
        } else {
819
            boolean bDrawCartographicSupport = false;
820
            if (symbol instanceof CartographicSupport) {
821
                bDrawCartographicSupport = ((CartographicSupport) symbol)
822
                .getUnit() != -1;
823
            }
824

    
825
            double previousSize = 0.0d;
826

    
827
            if (bDrawCartographicSupport) {
828
                // make the symbol to resize itself with the current rendering
829
                // context
830
                previousSize = ((CartographicSupport) symbol)
831
                .toCartographicSize(viewPort, dpi, geom);
832
            }
833

    
834
            try {
835
                symbol.draw(graphics, viewPort.getAffineTransform(), geom,
836
                    feature, cancellable);
837
            } finally {
838
                if (bDrawCartographicSupport) {
839
                    // restore previous size
840
                    ((CartographicSupport) symbol).setCartographicSize(
841
                        previousSize, geom);
842
                }
843
            }
844
        }
845
    }
846

    
847
    /**
848
     * Draws an {@link Aggregate} using the given {@link ISymbol}, over the
849
     * {@link Graphics2D} object.
850
     */
851
    private void drawAggregate(Aggregate aggregate, BufferedImage image,
852
        Feature feature, ISymbol symbol, ViewPort viewPort,
853
        Graphics2D graphics, double dpi, Cancellable cancellable)
854
    throws CreateGeometryException {
855
        for (int i = 0; i < aggregate.getPrimitivesNumber(); i++) {
856
            Geometry prim = aggregate.getPrimitiveAt(i);
857
            drawGeometry(prim, image, feature, symbol, viewPort, graphics, dpi,
858
                cancellable);
859
        }
860
    }
861

    
862
    public Object clone() throws CloneNotSupportedException {
863
        AbstractVectorialLegend clone = (AbstractVectorialLegend) super.clone();
864

    
865
        // Clone zSort
866
        ZSort zSort = getZSort();
867
        if (zSort != null) {
868
            clone.setZSort(new ZSort(clone));
869
        }
870
        return clone;
871
    }
872

    
873
    public void loadFromState(PersistentState state)
874
    throws PersistenceException {
875
        // Set parent properties
876
        super.loadFromState(state);
877
        // Set own properties
878

    
879
        setShapeType(state.getInt(FIELD_SHAPETYPE));
880
        if (state.getBoolean(FIELD_HAS_ZSORT)) {
881
            setZSort(new ZSort(this));
882
        }
883
        setDefaultSymbol((ISymbol) state.get(FIELD_DEFAULT_SYMBOL));
884
    }
885

    
886
    public void saveToState(PersistentState state) throws PersistenceException {
887
        // Save parent properties
888
        super.saveToState(state);
889
        // Save own properties
890
        state.set(FIELD_SHAPETYPE, getShapeType());
891
        state.set(FIELD_HAS_ZSORT, this.zSort != null);
892
        state.set(FIELD_DEFAULT_SYMBOL, getDefaultSymbol());
893
    }
894

    
895
    public static class RegisterPersistence implements Callable {
896

    
897
        public Object call() throws Exception {
898
            PersistenceManager manager = ToolsLocator.getPersistenceManager();
899
            if (manager
900
                .getDefinition(VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME) == null) {
901
                DynStruct definition = manager.addDefinition(
902
                    AbstractVectorialLegend.class,
903
                    VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME,
904
                    VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME
905
                    + " persistence definition", null, null);
906
                // Extend the Legend base definition
907
                definition.extend(manager
908
                    .getDefinition(LEGEND_PERSISTENCE_DEFINITION_NAME));
909

    
910
                // Shapetype
911
                definition.addDynFieldInt(FIELD_SHAPETYPE).setMandatory(true);
912
                // ZSort
913
                definition.addDynFieldBoolean(FIELD_HAS_ZSORT).setMandatory(
914
                    true);
915
                // Default symbol
916
                definition.addDynFieldObject(FIELD_DEFAULT_SYMBOL)
917
                .setClassOfValue(ISymbol.class).setMandatory(true);
918
            }
919
            return Boolean.TRUE;
920
        }
921

    
922
    }
923

    
924
    /**
925
     * Returns the names of the {@link Feature} attributes required for the
926
     * {@link ILegend} to operate.
927
     * 
928
     * @param featureStore
929
     *            the store where the {@link Feature}s belong to
930
     * @return the names of required {@link Feature} attribute names
931
     * @throws DataException
932
     *             if there is an error getting the attribute names
933
     */
934
    protected abstract String[] getRequiredFeatureAttributeNames(
935
        FeatureStore featureStore) throws DataException;
936
}