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

History | View | Annotate | Download (35.5 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.feature.Feature;
46
import org.gvsig.fmap.dal.feature.FeatureQuery;
47
import org.gvsig.fmap.dal.feature.FeatureReference;
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.exception.ConcurrentDataModificationException;
52
import org.gvsig.fmap.geom.Geometry;
53
import org.gvsig.fmap.geom.GeometryLocator;
54
import org.gvsig.fmap.geom.GeometryManager;
55
import org.gvsig.fmap.geom.aggregate.Aggregate;
56
import org.gvsig.fmap.geom.exception.CreateGeometryException;
57
import org.gvsig.fmap.geom.exception.ReprojectionRuntimeException;
58
import org.gvsig.fmap.geom.operation.DrawInts;
59
import org.gvsig.fmap.geom.operation.DrawOperationContext;
60
import org.gvsig.fmap.geom.primitive.Envelope;
61
import org.gvsig.fmap.mapcontext.MapContext;
62
import org.gvsig.fmap.mapcontext.MapContextException;
63
import org.gvsig.fmap.mapcontext.ViewPort;
64
import org.gvsig.fmap.mapcontext.layers.vectorial.SpatialEvaluatorsFactory;
65
import org.gvsig.fmap.mapcontext.rendering.legend.ILegend;
66
import org.gvsig.fmap.mapcontext.rendering.legend.IVectorLegend;
67
import org.gvsig.fmap.mapcontext.rendering.legend.LegendException;
68
import org.gvsig.fmap.mapcontext.rendering.legend.ZSort;
69
import org.gvsig.fmap.mapcontext.rendering.symbols.CartographicSupport;
70
import org.gvsig.fmap.mapcontext.rendering.symbols.IMultiLayerSymbol;
71
import org.gvsig.fmap.mapcontext.rendering.symbols.ISymbol;
72
import org.gvsig.tools.ToolsLocator;
73
import org.gvsig.tools.dispose.DisposableIterator;
74
import org.gvsig.tools.dispose.DisposeUtils;
75
import org.gvsig.tools.dynobject.DynStruct;
76
import org.gvsig.tools.evaluator.Evaluator;
77
import org.gvsig.tools.exception.BaseException;
78
import org.gvsig.tools.logger.FilteredLogger;
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
 */
95
public abstract class AbstractVectorialLegend extends AbstractLegend implements
96
IVectorLegend {
97

    
98
        private static final Logger LOG = LoggerFactory
99
                        .getLogger(AbstractVectorialLegend.class);
100

    
101
    private static final int DRAW_MAX_ATTEMPTS = 5;
102

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

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

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

    
112
    protected ZSort zSort;
113

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

    
118
    public void setZSort(ZSort zSort) {
119
        if (zSort == null) {
120
            removeLegendListener(this.zSort);
121
        }
122
        this.zSort = zSort;
123
        addLegendListener(zSort);
124
    }
125

    
126
    @SuppressWarnings("unchecked")
127
    @Override
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 = viewPort.getDPI();
133
        draw(image, g, viewPort, cancel, scale, queryParameters, coordTrans,
134
            featureStore, null, dpi);
135
    }
136

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

    
158
    @SuppressWarnings("unchecked")
159
    @Override
160
    public void print(Graphics2D g, ViewPort viewPort, Cancellable cancel,
161
        double scale, Map queryParameters, ICoordTrans coordTrans,
162
        FeatureStore featureStore, FeatureQuery fquery, PrintAttributes properties)
163
    throws LegendException {
164
        double dpi = 72;
165

    
166
        int resolution = properties.getPrintQuality();
167

    
168
        if (resolution == PrintAttributes.PRINT_QUALITY_DRAFT
169
            || resolution == PrintAttributes.PRINT_QUALITY_NORMAL
170
            || resolution == PrintAttributes.PRINT_QUALITY_HIGH) {
171
            dpi = PrintAttributes.PRINT_QUALITY_DPI[resolution];
172
        }
173

    
174
        FeatureSet featureSet = null;
175
        DisposableIterator it = null;
176
        try {
177
            ZSort zSort = getZSort();
178
            boolean useZSort = false;
179
            int mapLevelCount = 1;
180
            
181
            if( zSort != null ) {
182
                useZSort = zSort.isUsingZSort();
183
                if( useZSort ) {
184
                    mapLevelCount = zSort.getLevelCount();
185
                }
186
            }
187
            for (int mapPass = 0; mapPass < mapLevelCount; mapPass++) {
188

    
189
                Envelope vp_env_in_store_crs;
190
                IProjection store_crs;
191
                if (coordTrans != null) {
192
                    // 'coordTrans' is from store crs to vp crs
193
                    ICoordTrans inv = coordTrans.getInverted();
194
                    Envelope aux = viewPort.getAdjustedEnvelope();
195
                    vp_env_in_store_crs = aux.convert(inv);
196
                    store_crs = coordTrans.getPOrig();
197
                } else {
198
                    vp_env_in_store_crs = viewPort.getAdjustedEnvelope();
199
                    store_crs = viewPort.getProjection();
200
                }
201

    
202
                FeatureQuery feat_query = fquery;
203
                Envelope store_env = featureStore.getEnvelope();
204
                boolean use_intersection_cond;
205
                if (store_env == null) {
206
                    // Store does not know its envelope, so we must:
207
                    use_intersection_cond = true;
208
                } else {
209
                    if (vp_env_in_store_crs.contains(store_env)) {
210
                        use_intersection_cond = false;
211
                    } else {
212
                        use_intersection_cond = true;
213
                    }
214
                }
215

    
216
                if (use_intersection_cond) {
217
                    Evaluator iee = SpatialEvaluatorsFactory.getInstance().intersects(
218
                            vp_env_in_store_crs,
219
                            store_crs,
220
                            featureStore
221
                    );
222
                    if (feat_query == null) {
223
                        feat_query = featureStore.createFeatureQuery();
224
                    }
225
                    String[] fns = getRequiredFeatureAttributeNames(featureStore);
226
                    for (int i = 0; i < fns.length; i++) {
227
                        feat_query.addAttributeName(fns[i]);
228
                    }
229
                    feat_query.addFilter(iee);
230
                }
231

    
232
                // 'feat_query' can still be NULL here, so we only filter
233
                // the featureStore if it's not null
234
                if (feat_query == null) {
235
                    featureSet = featureStore.getFeatureSet();
236
                } else {
237
                    featureSet = featureStore.getFeatureSet(feat_query);
238
                }
239
                it = featureSet.fastIterator();
240
                // Iteration over each feature
241
                FilteredLogger logger = new FilteredLogger(LOG,"Drawing "+featureStore.getName(), 10);
242
                while (!cancel.isCanceled() && it.hasNext()) {
243
                    Feature feat = (Feature) it.next();
244
                    try {
245
                        Geometry geom = feat.getDefaultGeometry();
246
                        if (geom==null) {
247
                            continue;
248
                        }
249
                        // Reprojection if needed
250
                        if (coordTrans != null) {
251
                            geom = geom.cloneGeometry();
252
                            geom.reProject(coordTrans);
253
                        }
254

    
255
                        // retrieve the symbol associated to such feature
256
                        ISymbol sym = getSymbolByFeature(feat);
257
                        if (sym == null) {
258
                            continue;
259
                        }
260
                        if (useZSort) {
261
                            int[] symLevels = zSort.getLevels(sym);
262
                            if (symLevels != null) {
263

    
264
                                // Check if this symbol is a multilayer
265
                                if (sym instanceof IMultiLayerSymbol) {
266
                                    // if so, get the layer corresponding to the
267
                                    // current level. If none, continue to next
268
                                    // iteration
269
                                    IMultiLayerSymbol mlSym = (IMultiLayerSymbol) sym;
270
                                    for (int i = 0; i < mlSym.getLayerCount(); i++) {
271
                                        ISymbol mySym = mlSym.getLayer(i);
272
                                        if (symLevels[i] == mapPass) {
273
                                            sym = mySym;
274
                                            break;
275
                                        }
276
                                    }
277

    
278
                                } else {
279
                                    // else, just draw the symbol in its level
280
                                    if (symLevels[0] != mapPass) {
281
                                        continue;
282
                                    }
283
                                }
284
                            }
285
                        }
286

    
287
                        // Check if this symbol is sized with CartographicSupport
288
                        CartographicSupport csSym = null;
289
                        int symbolType = sym.getSymbolType();
290

    
291
                        if (symbolType == Geometry.TYPES.POINT
292
                            || symbolType == Geometry.TYPES.CURVE
293
                            || sym instanceof CartographicSupport) {
294

    
295
                            csSym = (CartographicSupport) sym;
296
                        }
297

    
298
                        DrawOperationContext doc = new DrawOperationContext();
299
                        doc.setGraphics(g);
300
                        doc.setViewPort(viewPort);
301
                        if (csSym == null) {
302
                            doc.setSymbol(sym);
303
                        } else {
304
                            doc.setDPI(dpi);
305
                            doc.setCancellable(cancel);
306
                            doc.setSymbol((ISymbol) csSym);
307
                        }
308
                        geom.invokeOperation(DrawInts.CODE, doc);
309
                    } catch(Exception ex) {
310
                        FeatureReference ref = null;
311
                        if( feat!=null ) {
312
                            ref = feat.getReference();
313
                        }
314
                        logger.warn("Can't draw feature ("+ref+").", ex);
315
                    }
316
                }
317
            }
318
        } catch (DataException e) {
319
            throw new LegendDrawingException(e);
320
        } finally {
321
            if (it != null) {
322
                it.dispose();
323
            }
324
            if (featureSet != null) {
325
                featureSet.dispose();
326
            }
327
        }
328
    }
329

    
330
    /**
331
     * Draws the features from the {@link FeatureStore}, filtered with the scale
332
     * and the query parameters, with the symbols of the legend.
333
     * @param image
334
     * @param g
335
     * @param viewPort
336
     * @param cancel
337
     * @param scale
338
     * @param queryParameters
339
     * @param coordTrans
340
     * @param featureStore
341
     * @param featureQuery
342
     * @param dpi
343
     * @throws org.gvsig.fmap.mapcontext.rendering.legend.LegendException
344
     */
345
    @SuppressWarnings("unchecked")
346
        protected void draw(BufferedImage image, Graphics2D g, ViewPort viewPort,
347
                        Cancellable cancel, double scale, Map queryParameters,
348
                        ICoordTrans coordTrans, FeatureStore featureStore,
349
                        FeatureQuery featureQuery, double dpi) throws LegendException {
350

    
351
                SimpleTaskStatus taskStatus = ToolsLocator.getTaskStatusManager()
352
                                .createDefaultSimpleTaskStatus(featureStore.getName());
353
                taskStatus.add();
354
                try {
355
                        // Avoid ConcurrentModificationException errors if
356
                        // while drawing another thread edits the store data.
357
                        synchronized (featureStore) {
358
                                internalDraw(image, g, viewPort, cancel, scale,
359
                                                queryParameters, coordTrans, featureStore,
360
                                                featureQuery, dpi, taskStatus);
361
                        }
362
                } finally {
363
            taskStatus.terminate();
364
            taskStatus.remove();
365
                }
366
        }
367

    
368
        protected void internalDraw(BufferedImage image, Graphics2D g,
369
                        ViewPort viewPort, Cancellable cancel, double scale,
370
                        Map queryParameters, ICoordTrans coordTrans,
371
                        FeatureStore featureStore, FeatureQuery featureQuery, double dpi,
372
                        SimpleTaskStatus taskStatus) throws LegendDrawingException {
373

    
374
                if (!getDefaultSymbol().isShapeVisible()) {
375
                        return;
376
                }
377

    
378
                if (cancel.isCanceled()) {
379
                        return;
380
                }
381

    
382
                IProjection dataProjection;
383
                Envelope dataEnvelope;
384
                Envelope reprojectedDataEnvelope;
385
                // Gets the view envelope
386
                Envelope viewPortEnvelope = viewPort.getAdjustedEnvelope();
387
        Envelope reprojectedViewPortEnvelope;
388
                try {
389
                    dataEnvelope = featureStore.getEnvelope();
390
                        if (coordTrans == null) {
391
                                dataProjection = featureStore.getDefaultFeatureType()
392
                                                .getDefaultSRS();
393

    
394
                                // If the data does not provide a projection, use the
395
                                // current view one
396
                                if (dataProjection == null) {
397
                                        dataProjection = viewPort.getProjection();
398
                                }
399

    
400
                                reprojectedDataEnvelope = dataEnvelope;
401
                reprojectedViewPortEnvelope = viewPortEnvelope;
402
                        } else {
403
                                dataProjection = coordTrans.getPOrig();
404

    
405
                                if ( dataEnvelope!=null && !dataEnvelope.isEmpty()) {
406
                                        reprojectedDataEnvelope = dataEnvelope.convert(coordTrans);
407
                                } else {
408
                                        reprojectedDataEnvelope = dataEnvelope;
409
                                }
410
                if ( viewPortEnvelope!=null && !viewPortEnvelope.isEmpty()) {
411
                    reprojectedViewPortEnvelope = viewPortEnvelope.convert(coordTrans.getInverted());
412
                } else {
413
                    reprojectedViewPortEnvelope = viewPortEnvelope;
414
                }
415
                        }
416
                } catch (DataException e) {
417
                        throw new LegendDrawingException(e);
418
                }
419

    
420

    
421
                // Gets the data envelope with the viewport SRS
422
//                Envelope myEnvelope = reprojectedDataEnvelope;
423

    
424
                // TODO: in some cases, the legend may need a different check to
425
                // decide if the data must be drawn or not
426
                // Checks if the viewport envelope intersects with the data envelope
427
                // This condition may seem redundant, but sometimes the transformations may fail and cause false negatives.
428
                if ((viewPortEnvelope==null || !viewPortEnvelope.intersects(reprojectedDataEnvelope)) && !(reprojectedViewPortEnvelope!=null && reprojectedViewPortEnvelope.intersects(dataEnvelope))) {
429
                        // The data is not visible in the current viewport, do nothing.
430
                        return;
431
                }
432

    
433
                // Check if all the data is contained into the viewport envelope
434
        // This condition may seem redundant, but sometimes the transformations may fail and cause false negatives.
435
                boolean containsAll = viewPortEnvelope.contains(reprojectedDataEnvelope) || (reprojectedViewPortEnvelope!=null && reprojectedViewPortEnvelope.contains(dataEnvelope));
436

    
437
                // Create the drawing notification to be reused on each iteration
438
                DefaultFeatureDrawnNotification drawnNotification = new DefaultFeatureDrawnNotification();
439

    
440
                if (cancel.isCanceled()) {
441
                        return;
442
                }
443

    
444
                FeatureSet featureSet = null;
445
                try {
446
                        taskStatus.message("Retrieve selection");
447
                        FeatureSelection selection = featureStore.getFeatureSelection();
448

    
449
                        if (featureQuery == null) {
450
                                featureQuery = featureStore.createFeatureQuery();
451
                        }
452

    
453
                        completeQuery(featureStore, featureQuery, scale, queryParameters,
454
                                        coordTrans, dataProjection, viewPortEnvelope, containsAll);
455

    
456
                        taskStatus.message("Retrieve data");
457
                        featureSet = featureStore.getFeatureSet(featureQuery);
458

    
459
                        if (cancel.isCanceled()) {
460
                                return;
461
                        }
462

    
463
                        taskStatus.message("Drawing");
464
                        drawFeatures(image, g, viewPort, cancel, coordTrans, dpi,
465
                                        drawnNotification, featureSet, selection);
466

    
467
        } catch (Throwable e) {
468
            /*
469
             * Probably a reprojection exception (for example,
470
             * trying to reproject Canada to EPSG:23030)
471
             */
472
            throw new LegendDrawingException(e);
473
                } finally {
474
                        if (featureSet != null) {
475
                                featureSet.dispose();
476
                        }
477
                }
478
        }
479

    
480
    /**
481
     * Complete a {@link FeatureQuery} to load the {@link Feature}s to draw.
482
     */
483
    @SuppressWarnings("unchecked")
484
    private FeatureQuery completeQuery(FeatureStore featureStore, FeatureQuery featureQuery, double scale,
485
        Map queryParameters, ICoordTrans coordTrans,
486
        IProjection dataProjection, Envelope viewPortEnvelope,
487
        boolean containsAll) throws DataException {
488

    
489
        featureQuery.setScale(scale);
490

    
491
        //Adds the attributes
492
        String[] fieldNames = getRequiredFeatureAttributeNames(featureStore);
493
        for (int i=0 ; i<fieldNames.length ;i++){
494
            featureQuery.addAttributeName(fieldNames[i]);
495
        }
496

    
497
        if (!containsAll) {
498
            // Gets the viewport envelope with the data SRS
499
            Envelope viewPortEnvelopeInMyProj = viewPortEnvelope;
500
            if (coordTrans != null) {
501
                viewPortEnvelopeInMyProj = viewPortEnvelope.convert(coordTrans
502
                        .getInverted());
503
            }
504

    
505
            if (dataProjection == null) {
506
                throw new IllegalArgumentException(
507
                "Error, the projection parameter value is null");
508
            }
509

    
510
            Evaluator iee = SpatialEvaluatorsFactory.getInstance().intersects(
511
                    viewPortEnvelopeInMyProj,
512
                    dataProjection,
513
                    featureStore
514
            );
515
            featureQuery.addFilter(iee);
516
        }
517
        if (queryParameters != null) {
518
            Iterator iterEntry = queryParameters.entrySet().iterator();
519
            Entry entry;
520
            while (iterEntry.hasNext()) {
521
                entry = (Entry) iterEntry.next();
522
                featureQuery.setQueryParameter((String) entry.getKey(),
523
                    entry.getValue());
524
            }
525
        }
526
        return featureQuery;
527
    }
528

    
529
    /**
530
     * Draws the features from the {@link FeatureSet}, with the symbols of the
531
     * legend.
532
     */
533
    private void drawFeatures(BufferedImage image, Graphics2D g,
534
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
535
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
536
        FeatureSet featureSet, FeatureSelection selection)
537
    throws BaseException {
538
        if (isUseZSort()) {
539
            drawFeaturesMultiLayer(image, g, viewPort, cancel, coordTrans, dpi,
540
                drawnNotification, featureSet, selection);
541
        } else {
542
            drawFeaturesSingleLayer(image, g, viewPort, cancel, coordTrans,
543
                dpi, drawnNotification, featureSet, selection);
544
        }
545
    }
546

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

    
559
        try {
560
            featureSet.accept(new Visitor() {
561
                @Override
562
                public void visit(Object obj) throws VisitCanceledException,
563
                BaseException {
564
                    Feature feat = (Feature) obj;
565
                    drawFeatureSingleLayer(image, g, viewPort, cancel,
566
                        coordTrans, dpi, drawnNotification, feat, selection);
567
                }
568
            });
569

    
570
        } catch (ConcurrentDataModificationException e) {
571
            cancel.setCanceled(true);
572
        }
573
    }
574

    
575
    /**
576
     * Draws a Feature with the symbols of the legend, using a single drawing
577
     * layer.
578
     */
579
    private void drawFeatureSingleLayer(BufferedImage image, Graphics2D g,
580
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
581
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
582
        Feature feat, FeatureSelection selection)
583
    throws MapContextException, CreateGeometryException {
584

    
585
        Geometry geom = feat.getDefaultGeometry();
586
        if (geom == null) {
587
            return;
588
        }
589

    
590
        if (geom.getType() == Geometry.TYPES.NULL) {
591
            return;
592
        }
593

    
594
        ISymbol sym = getSymbol(feat, selection);
595
        if (sym == null) {
596
            return;
597
        }
598

    
599
        if (coordTrans != null) {
600
            geom = geom.cloneGeometry();
601
            try {
602
                geom.reProject(coordTrans);
603
            } catch (ReprojectionRuntimeException re) {
604
                LOG.warn("Can't reproject geometry "+geom.toString(), re);
605
                return;
606
            }
607
        }
608

    
609
        if (cancel.isCanceled()) {
610
            return;
611
        }
612

    
613
        drawGeometry(geom, image, feat, sym, viewPort, g, dpi, cancel);
614

    
615
        // Notify the drawing observers
616
        drawnNotification.setFeature(feat);
617
        drawnNotification.setDrawnGeometry(geom);
618
        notifyObservers(drawnNotification);
619
    }
620

    
621
    /**
622
     * Draws the features from the {@link FeatureSet}, with the symbols of the
623
     * legend, using a multiple drawing layer.
624
     */
625
    private void drawFeaturesMultiLayer(BufferedImage image, Graphics2D g,
626
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
627
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
628
        FeatureSet featureSet, FeatureSelection selection)
629
    throws MapContextException, CreateGeometryException, DataException {
630

    
631
        // -- visual FX stuff
632
        long time = System.currentTimeMillis();
633

    
634
        boolean bSymbolLevelError = false;
635
        int screenRefreshDelay = (int) ((1D / MapContext.getDrawFrameRate()) * 3 * 1000);
636
        BufferedImage[] imageLevels;
637
        Graphics2D[] graphics;
638

    
639
        imageLevels = new BufferedImage[getZSort().getLevelCount()];
640
        graphics = new Graphics2D[imageLevels.length];
641
        for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
642

    
643
            imageLevels[i] = CompatLocator.getGraphicsUtils()
644
            .createBufferedImage(image.getWidth(), image.getHeight(),
645
                image.getType());
646

    
647
            graphics[i] = imageLevels[i].createGraphics();
648
            graphics[i].setTransform(g.getTransform());
649
            graphics[i].setRenderingHints(g.getRenderingHints());
650
        }
651

    
652
        DisposableIterator it = null;
653
        try {
654
            it = featureSet.fastIterator();
655
            // Iteration over each feature
656
            while (it.hasNext()) {
657
                if (cancel.isCanceled()) {
658
                    return;
659
                }
660
                Feature feat = (Feature) it.next();
661

    
662
                bSymbolLevelError |= drawFeatureMultiLayer(image, g, viewPort,
663
                    cancel, coordTrans, dpi, drawnNotification, selection,
664
                    time, screenRefreshDelay, imageLevels, graphics, feat);
665

    
666
            }
667
        } catch (ConcurrentDataModificationException e) {
668
            cancel.setCanceled(true);
669
            return;
670
        } finally {
671
            DisposeUtils.dispose(it);
672
        }
673

    
674
        g.drawImage(image, 0, 0, null);
675

    
676
        Point2D offset = viewPort.getOffset();
677
        CompatLocator.getGraphicsUtils().translate(g, offset.getX(),
678
            offset.getY());
679

    
680
        for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
681
            g.drawImage(imageLevels[i], 0, 0, null);
682
            imageLevels[i] = null;
683
            graphics[i] = null;
684
        }
685

    
686
        CompatLocator.getGraphicsUtils().translate(g, -offset.getX(),
687
            -offset.getY());
688

    
689
        if (bSymbolLevelError) {
690
            setZSort(null);
691
        }
692
    }
693

    
694
    /**
695
     * Draws a Feature with the symbols of the legend, using a multiple drawing
696
     * layer.
697
     */
698
    private boolean drawFeatureMultiLayer(BufferedImage image, Graphics2D g,
699
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
700
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
701
        FeatureSelection selection, long time, int screenRefreshDelay,
702
        BufferedImage[] imageLevels, Graphics2D[] graphics, Feature feat)
703
    throws MapContextException, CreateGeometryException {
704

    
705
        Geometry geom = feat.getDefaultGeometry();
706
        boolean bSymbolLevelError = false;
707
        long drawingTime = time;
708

    
709
        if (geom==null || geom.getType() == Geometry.TYPES.NULL) {
710
            return false;
711
        }
712

    
713
        ISymbol sym = getSymbol(feat, selection);
714

    
715
        if (sym == null) {
716
            return false;
717
        }
718

    
719
        if (coordTrans != null) {
720
            geom = geom.cloneGeometry();
721
            geom.reProject(coordTrans);
722
        }
723

    
724
        if (cancel.isCanceled()) {
725
            return false;
726
        }
727

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

    
764
        // -- visual FX stuff
765
        // Cuando el offset!=0 se est? dibujando sobre el
766
        // Layout y por tanto no tiene que ejecutar el
767
        // siguiente c?digo.
768
        Point2D offset = viewPort.getOffset();
769
        if (offset.getX() == 0 && offset.getY() == 0) {
770
            if ((System.currentTimeMillis() - drawingTime) > screenRefreshDelay) {
771

    
772
                BufferedImage virtualBim = CompatLocator.getGraphicsUtils()
773
                .createBufferedImage(image.getWidth(),
774
                    image.getHeight(), BufferedImage.TYPE_INT_ARGB);
775

    
776
                Graphics2D virtualGraphics = virtualBim.createGraphics();
777
                virtualGraphics.drawImage(image, 0, 0, null);
778
                for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
779
                    virtualGraphics.drawImage(imageLevels[i], 0, 0, null);
780
                }
781
                g.clearRect(0, 0, image.getWidth(), image.getHeight());
782
                g.drawImage(virtualBim, 0, 0, null);
783
                drawingTime = System.currentTimeMillis();
784
            }
785
            // -- end visual FX stuff
786

    
787
        }
788
        // Notify the drawing observers
789
        drawnNotification.setFeature(feat);
790
        drawnNotification.setDrawnGeometry(geom);
791
        notifyObservers(drawnNotification);
792
        return bSymbolLevelError;
793
    }
794

    
795
    /**
796
     * Returns the symbol to use to draw a {@link Feature} taking into account
797
     * if it is selected.
798
     */
799
    private ISymbol getSymbol(Feature feat, FeatureSelection selection)
800
    throws MapContextException {
801
        // retrieve the symbol associated to such feature
802
        ISymbol sym = getSymbolByFeature(feat);
803

    
804
        if (sym != null && selection.isSelected(feat)) {
805
            sym = sym.getSymbolForSelection();
806
        }
807
        return sym;
808
    }
809

    
810
    /**
811
     * Returns if the legend is using a ZSort.
812
     */
813
    private boolean isUseZSort() {
814
        return getZSort() != null && getZSort().isUsingZSort();
815
    }
816

    
817
    /**
818
     * Draws a {@link Geometry} using the given {@link ISymbol}, over the
819
     * {@link Graphics2D} object.
820
     */
821
    private void drawGeometry(Geometry geom, BufferedImage image,
822
        Feature feature, ISymbol symbol, ViewPort viewPort,
823
        Graphics2D graphics, double dpi, Cancellable cancellable)
824
    throws CreateGeometryException {
825

    
826
        if (geom instanceof Aggregate) {
827
            drawAggregate((Aggregate) geom, image, feature, symbol, viewPort,
828
                graphics, dpi, cancellable);
829
        } else {
830
            boolean bDrawCartographicSupport = false;
831
            if (symbol instanceof CartographicSupport) {
832
                bDrawCartographicSupport = ((CartographicSupport) symbol)
833
                .getUnit() != -1;
834
            }
835

    
836
            double previousSize = 0.0d;
837

    
838
            if (bDrawCartographicSupport) {
839
                // make the symbol to resize itself with the current rendering
840
                // context
841
                previousSize = ((CartographicSupport) symbol)
842
                .toCartographicSize(viewPort, dpi, geom);
843
            }
844

    
845
            try {
846
                symbol.draw(graphics, viewPort.getAffineTransform(), geom,
847
                    feature, cancellable);
848
            } finally {
849
                if (bDrawCartographicSupport) {
850
                    // restore previous size
851
                    ((CartographicSupport) symbol).setCartographicSize(
852
                        previousSize, geom);
853
                }
854
            }
855
        }
856
    }
857

    
858
    /**
859
     * Draws an {@link Aggregate} using the given {@link ISymbol}, over the
860
     * {@link Graphics2D} object.
861
     */
862
    private void drawAggregate(Aggregate aggregate, BufferedImage image,
863
        Feature feature, ISymbol symbol, ViewPort viewPort,
864
        Graphics2D graphics, double dpi, Cancellable cancellable)
865
    throws CreateGeometryException {
866
        for (int i = 0; i < aggregate.getPrimitivesNumber(); i++) {
867
            Geometry prim = aggregate.getPrimitiveAt(i);
868
            drawGeometry(prim, image, feature, symbol, viewPort, graphics, dpi,
869
                cancellable);
870
        }
871
    }
872

    
873
    @Override
874
    public Object clone() throws CloneNotSupportedException {
875
        AbstractVectorialLegend clone = (AbstractVectorialLegend) super.clone();
876

    
877
        // Clone zSort
878
        ZSort zSort = getZSort();
879
        if (zSort != null) {
880
            clone.setZSort(new ZSort(clone));
881
        }
882
        return clone;
883
    }
884

    
885
    @Override
886
    public void loadFromState(PersistentState state)
887
    throws PersistenceException {
888
        // Set parent properties
889
        super.loadFromState(state);
890
        // Set own properties
891

    
892
        setShapeType(state.getInt(FIELD_SHAPETYPE));
893
        if (state.getBoolean(FIELD_HAS_ZSORT)) {
894
            setZSort(new ZSort(this));
895
        }
896
        setDefaultSymbol((ISymbol) state.get(FIELD_DEFAULT_SYMBOL));
897
    }
898

    
899
    @Override
900
    public void saveToState(PersistentState state) throws PersistenceException {
901
        // Save parent properties
902
        super.saveToState(state);
903
        // Save own properties
904
        state.set(FIELD_SHAPETYPE, getShapeType());
905
        state.set(FIELD_HAS_ZSORT, this.zSort != null);
906
        state.set(FIELD_DEFAULT_SYMBOL, getDefaultSymbol());
907
    }
908

    
909
    public static class RegisterPersistence implements Callable {
910

    
911
        @Override
912
        public Object call() throws Exception {
913
            PersistenceManager manager = ToolsLocator.getPersistenceManager();
914
            if (manager
915
                .getDefinition(VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME) == null) {
916
                DynStruct definition = manager.addDefinition(
917
                    AbstractVectorialLegend.class,
918
                    VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME,
919
                    VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME
920
                    + " persistence definition", null, null);
921
                // Extend the Legend base definition
922
                definition.extend(manager.getDefinition(LEGEND_PERSISTENCE_DEFINITION_NAME));
923

    
924
                // Shapetype
925
                definition.addDynFieldInt(FIELD_SHAPETYPE).setMandatory(true);
926
                // ZSort
927
                definition.addDynFieldBoolean(FIELD_HAS_ZSORT).setMandatory(true);
928
                // Default symbol
929
                definition.addDynFieldObject(FIELD_DEFAULT_SYMBOL)
930
                    .setClassOfValue(ISymbol.class).setMandatory(true);
931
            }
932
            return Boolean.TRUE;
933
        }
934

    
935
    }
936

    
937
    /**
938
     * Returns the names of the {@link Feature} attributes required for the
939
     * {@link ILegend} to operate.
940
     *
941
     * @param featureStore
942
     *            the store where the {@link Feature}s belong to
943
     * @return the names of required {@link Feature} attribute names
944
     * @throws DataException
945
     *             if there is an error getting the attribute names
946
     */
947
    protected abstract String[] getRequiredFeatureAttributeNames(
948
        FeatureStore featureStore) throws DataException;
949
}