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

History | View | Annotate | Download (35.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.layers.vectorial.SpatialEvaluatorsFactory;
69
import org.gvsig.fmap.mapcontext.rendering.legend.ILegend;
70
import org.gvsig.fmap.mapcontext.rendering.legend.IVectorLegend;
71
import org.gvsig.fmap.mapcontext.rendering.legend.LegendException;
72
import org.gvsig.fmap.mapcontext.rendering.legend.ZSort;
73
import org.gvsig.fmap.mapcontext.rendering.symbols.CartographicSupport;
74
import org.gvsig.fmap.mapcontext.rendering.symbols.IMultiLayerSymbol;
75
import org.gvsig.fmap.mapcontext.rendering.symbols.ISymbol;
76
import org.gvsig.tools.ToolsLocator;
77
import org.gvsig.tools.dispose.DisposableIterator;
78
import org.gvsig.tools.dynobject.DynStruct;
79
import org.gvsig.tools.evaluator.Evaluator;
80
import org.gvsig.tools.exception.BaseException;
81
import org.gvsig.tools.persistence.PersistenceManager;
82
import org.gvsig.tools.persistence.PersistentState;
83
import org.gvsig.tools.persistence.exception.PersistenceException;
84
import org.gvsig.tools.task.Cancellable;
85
import org.gvsig.tools.task.SimpleTaskStatus;
86
import org.gvsig.tools.util.Callable;
87
import org.gvsig.tools.visitor.VisitCanceledException;
88
import org.gvsig.tools.visitor.Visitor;
89

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

    
101
        private static final Logger LOG = LoggerFactory
102
                        .getLogger(AbstractVectorialLegend.class);
103

    
104
    private static final int DRAW_MAX_ATTEMPTS = 5;
105

    
106
        public static final String VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME = "VectorialLegend";
107

    
108
    private static final String FIELD_HAS_ZSORT = "hasZSort";
109
    private static final String FIELD_SHAPETYPE = "shapeType";
110
    private static final String FIELD_DEFAULT_SYMBOL = "defaultSymbol";
111

    
112
    private static final GeometryManager geomManager = GeometryLocator
113
    .getGeometryManager();
114

    
115
    protected ZSort zSort;
116

    
117
    public ZSort getZSort() {
118
        return zSort;
119
    }
120

    
121
    public void setZSort(ZSort zSort) {
122
        if (zSort == null) {
123
            removeLegendListener(this.zSort);
124
        }
125
        this.zSort = zSort;
126
        addLegendListener(zSort);
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)
133
    throws LegendException {
134
        double dpi = viewPort.getDPI();
135
        draw(image, g, viewPort, cancel, scale, queryParameters, coordTrans,
136
            featureStore, null, dpi);
137
    }
138

    
139
    @SuppressWarnings("unchecked")
140
    public void draw(BufferedImage image, Graphics2D g, ViewPort viewPort,
141
        Cancellable cancel, double scale, Map queryParameters,
142
        ICoordTrans coordTrans, FeatureStore featureStore, FeatureQuery featureQuery)
143
    throws LegendException {
144
        double dpi = viewPort.getDPI();
145
        draw(image, g, viewPort, cancel, scale, queryParameters, coordTrans,
146
            featureStore, featureQuery, dpi);
147
    }
148

    
149
    @SuppressWarnings("unchecked")
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
    public void print(Graphics2D g, ViewPort viewPort, Cancellable cancel,
160
        double scale, Map queryParameters, ICoordTrans coordTrans,
161
        FeatureStore featureStore, FeatureQuery fquery, PrintAttributes properties)
162
    throws LegendException {
163
        double dpi = 72;
164

    
165
        int resolution = properties.getPrintQuality();
166

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

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

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

    
181
            int mapLevelCount = (useZSort) ? zSort.getLevelCount() : 1;
182
            for (int mapPass = 0; mapPass < mapLevelCount; mapPass++) {
183

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

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

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

    
227
                // 'feat_query' can still be NULL here, so we only filter
228
                // the featureStore if it's not null
229
                if (feat_query == null) {
230
                    featureSet = featureStore.getFeatureSet();
231
                } else {
232
                    featureSet = featureStore.getFeatureSet(feat_query);
233
                }
234
                it = featureSet.fastIterator();
235
                // Iteration over each feature
236
                while (!cancel.isCanceled() && it.hasNext()) {
237
                    Feature feat = (Feature) it.next();
238
                    Geometry geom = feat.getDefaultGeometry();
239
                    if (geom==null) {
240
                            continue;
241
                    }
242
                    // Reprojection if needed
243
                    if (coordTrans != null) {
244
                        geom = geom.cloneGeometry();
245
                        geom.reProject(coordTrans);
246
                    }
247

    
248
                    // retrieve the symbol associated to such feature
249
                    ISymbol sym = getSymbolByFeature(feat);
250
                    if (sym == null) {
251
                        continue;
252
                    }
253
                    if (useZSort) {
254
                        int[] symLevels = zSort.getLevels(sym);
255
                        if (symLevels != null) {
256

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

    
271
                            } else {
272
                                // else, just draw the symbol in its level
273
                                if (symLevels[0] != mapPass) {
274
                                    continue;
275
                                }
276
                            }
277
                        }
278
                    }
279

    
280
                    // Check if this symbol is sized with CartographicSupport
281
                    CartographicSupport csSym = null;
282
                    int symbolType = sym.getSymbolType();
283

    
284
                    if (symbolType == Geometry.TYPES.POINT
285
                        || symbolType == Geometry.TYPES.CURVE
286
                        || sym instanceof CartographicSupport) {
287

    
288
                        csSym = (CartographicSupport) sym;
289
                    }
290

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

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

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

    
354
        protected void internalDraw(BufferedImage image, Graphics2D g,
355
                        ViewPort viewPort, Cancellable cancel, double scale,
356
                        Map queryParameters, ICoordTrans coordTrans,
357
                        FeatureStore featureStore, FeatureQuery featureQuery, double dpi,
358
                        SimpleTaskStatus taskStatus) throws LegendDrawingException {
359

    
360
                if (!getDefaultSymbol().isShapeVisible()) {
361
                        return;
362
                }
363

    
364
                if (cancel.isCanceled()) {
365
                        return;
366
                }
367

    
368
                IProjection dataProjection;
369
                Envelope dataEnvelope;
370
                Envelope reprojectedDataEnvelope;
371
                // Gets the view envelope
372
                Envelope viewPortEnvelope = viewPort.getAdjustedEnvelope();
373
        Envelope reprojectedViewPortEnvelope;
374
                try {
375
                    dataEnvelope = featureStore.getEnvelope();
376
                        if (coordTrans == null) {
377
                                dataProjection = featureStore.getDefaultFeatureType()
378
                                                .getDefaultSRS();
379

    
380
                                // If the data does not provide a projection, use the
381
                                // current view one
382
                                if (dataProjection == null) {
383
                                        dataProjection = viewPort.getProjection();
384
                                }
385

    
386
                                reprojectedDataEnvelope = dataEnvelope;
387
                reprojectedViewPortEnvelope = viewPortEnvelope;
388
                        } else {
389
                                dataProjection = coordTrans.getPOrig();
390

    
391
                                if ( dataEnvelope!=null && !dataEnvelope.isEmpty()) {
392
                                        reprojectedDataEnvelope = dataEnvelope.convert(coordTrans);
393
                                } else {
394
                                        reprojectedDataEnvelope = dataEnvelope;
395
                                }
396
                if ( viewPortEnvelope!=null && !viewPortEnvelope.isEmpty()) {
397
                    reprojectedViewPortEnvelope = viewPortEnvelope.convert(coordTrans.getInverted());
398
                } else {
399
                    reprojectedViewPortEnvelope = viewPortEnvelope;
400
                }
401
                        }
402
                } catch (DataException e) {
403
                        throw new LegendDrawingException(e);
404
                }
405

    
406

    
407
                // Gets the data envelope with the viewport SRS
408
//                Envelope myEnvelope = reprojectedDataEnvelope;
409

    
410
                // TODO: in some cases, the legend may need a different check to
411
                // decide if the data must be drawn or not
412
                // Checks if the viewport envelope intersects with the data envelope
413
                // This condition may seem redundant, but sometimes the transformations may fail and cause false negatives.
414
                if (!viewPortEnvelope.intersects(reprojectedDataEnvelope) && !(reprojectedViewPortEnvelope!=null && reprojectedViewPortEnvelope.intersects(dataEnvelope))) {
415
                        // The data is not visible in the current viewport, do nothing.
416
                        return;
417
                }
418

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

    
423
                // Create the drawing notification to be reused on each iteration
424
                DefaultFeatureDrawnNotification drawnNotification = new DefaultFeatureDrawnNotification();
425

    
426
                if (cancel.isCanceled()) {
427
                        return;
428
                }
429

    
430
                FeatureSet featureSet = null;
431
                try {
432
                        taskStatus.message("Retrieve selection");
433
                        FeatureSelection selection = featureStore.getFeatureSelection();
434

    
435
                        if (featureQuery == null) {
436
                                featureQuery = featureStore.createFeatureQuery();
437
                        }
438

    
439
                        completeQuery(featureStore, featureQuery, scale, queryParameters,
440
                                        coordTrans, dataProjection, viewPortEnvelope, containsAll);
441

    
442
                        taskStatus.message("Retrieve data");
443
                        featureSet = featureStore.getFeatureSet(featureQuery);
444

    
445
                        if (cancel.isCanceled()) {
446
                                return;
447
                        }
448

    
449
                        taskStatus.message("Drawing");
450
                        drawFeatures(image, g, viewPort, cancel, coordTrans, dpi,
451
                                        drawnNotification, featureSet, selection);
452

    
453
        } catch (RuntimeException e) {
454
            /*
455
             * Probably a reprojection exception (for example,
456
             * trying to reproject Canada to EPSG:23030)
457
             */
458
            throw new LegendDrawingException(e);
459
                } catch (BaseException e) {
460
                        throw new LegendDrawingException(e);
461
                } finally {
462
                        if (featureSet != null) {
463
                                featureSet.dispose();
464
                        }
465
                }
466
        }
467

    
468
    /**
469
     * Complete a {@link FeatureQuery} to load the {@link Feature}s to draw.
470
     */
471
    @SuppressWarnings("unchecked")
472
    private FeatureQuery completeQuery(FeatureStore featureStore, FeatureQuery featureQuery, double scale,
473
        Map queryParameters, ICoordTrans coordTrans,
474
        IProjection dataProjection, Envelope viewPortEnvelope,
475
        boolean containsAll) throws DataException {
476

    
477
        featureQuery.setScale(scale);
478

    
479
        //Adds the attributes
480
        String[] fieldNames = getRequiredFeatureAttributeNames(featureStore);
481
        for (int i=0 ; i<fieldNames.length ;i++){
482
            featureQuery.addAttributeName(fieldNames[i]);
483
        }
484

    
485
        // TODO: Mobile has it's own IntersectsEnvelopeEvaluator
486
        if (!containsAll) {
487
            // Gets the viewport envelope with the data SRS
488
            Envelope viewPortEnvelopeInMyProj = viewPortEnvelope;
489
            // FIXME
490
            if (coordTrans != null) {
491
                viewPortEnvelopeInMyProj = viewPortEnvelope.convert(coordTrans
492
                        .getInverted());
493
            }
494

    
495
            if (dataProjection == null) {
496
                throw new IllegalArgumentException(
497
                "Error, the projection parameter value is null");
498
            }
499

    
500
            Evaluator iee = SpatialEvaluatorsFactory.getInstance().intersects(
501
                    viewPortEnvelopeInMyProj, 
502
                    dataProjection, 
503
                    featureStore
504
            );
505
            featureQuery.addFilter(iee);
506
        }
507
        if (queryParameters != null) {
508
            Iterator iterEntry = queryParameters.entrySet().iterator();
509
            Entry entry;
510
            while (iterEntry.hasNext()) {
511
                entry = (Entry) iterEntry.next();
512
                featureQuery.setQueryParameter((String) entry.getKey(),
513
                    entry.getValue());
514
            }
515
        }
516
        return featureQuery;
517
    }
518

    
519
    /**
520
     * Draws the features from the {@link FeatureSet}, with the symbols of the
521
     * legend.
522
     */
523
    private void drawFeatures(BufferedImage image, Graphics2D g,
524
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
525
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
526
        FeatureSet featureSet, FeatureSelection selection)
527
    throws BaseException {
528
        if (isUseZSort()) {
529
            drawFeaturesMultiLayer(image, g, viewPort, cancel, coordTrans, dpi,
530
                drawnNotification, featureSet, selection);
531
        } else {
532
            drawFeaturesSingleLayer(image, g, viewPort, cancel, coordTrans,
533
                dpi, drawnNotification, featureSet, selection);
534
        }
535
    }
536

    
537
    /**
538
     * Draws the features from the {@link FeatureSet}, with the symbols of the
539
     * legend, using a single drawing layer.
540
     */
541
    private void drawFeaturesSingleLayer(final BufferedImage image,
542
        final Graphics2D g, final ViewPort viewPort,
543
        final Cancellable cancel, final ICoordTrans coordTrans,
544
        final double dpi,
545
        final DefaultFeatureDrawnNotification drawnNotification,
546
        FeatureSet featureSet, final FeatureSelection selection)
547
    throws BaseException {
548

    
549
        try {
550
            featureSet.accept(new Visitor() {
551
                public void visit(Object obj) throws VisitCanceledException,
552
                BaseException {
553
                    Feature feat = (Feature) obj;
554
                    drawFeatureSingleLayer(image, g, viewPort, cancel,
555
                        coordTrans, dpi, drawnNotification, feat, selection);
556
                }
557
            });
558

    
559
        } catch (ConcurrentDataModificationException e) {
560
            cancel.setCanceled(true);
561
            return;
562
        }
563
    }
564

    
565
    /**
566
     * Draws a Feature with the symbols of the legend, using a single drawing
567
     * layer.
568
     */
569
    private void drawFeatureSingleLayer(BufferedImage image, Graphics2D g,
570
        ViewPort viewPort, Cancellable cancel, ICoordTrans coordTrans,
571
        double dpi, DefaultFeatureDrawnNotification drawnNotification,
572
        Feature feat, FeatureSelection selection)
573
    throws MapContextException, CreateGeometryException {
574

    
575
        Geometry geom = feat.getDefaultGeometry();
576
        if (geom == null) {
577
            return;
578
        }
579

    
580
        if (geom.getType() == Geometry.TYPES.NULL) {
581
            return;
582
        }
583

    
584
        ISymbol sym = getSymbol(feat, selection);
585
        if (sym == null) {
586
            return;
587
        }
588

    
589
        if (coordTrans != null) {
590
            geom = geom.cloneGeometry();
591
            try {
592
                geom.reProject(coordTrans);
593
            } catch (ReprojectionRuntimeException re) {
594
                LOG.warn("Can't reproject geometry "+geom.toString(), re);
595
                return;
596

    
597
                /*
598
                 * Library was unable to reproject
599
                 * See reproject method in Point2D (geometry)
600
                 */
601
//                throw new CreateGeometryException(
602
//                    geom.getGeometryType().getType(),
603
//                    geom.getGeometryType().getSubType(),
604
//                    re);
605
            }
606
        }
607

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

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

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

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

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

    
633
        boolean bSymbolLevelError = false;
634
        // render temporary map each screenRefreshRate milliseconds;
635
        int screenRefreshDelay = (int) ((1D / MapContext.getDrawFrameRate()) * 3 * 1000);
636
        BufferedImage[] imageLevels = null;
637
        Graphics2D[] graphics = null;
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
        // -- end visual FX stuff
652

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

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

    
667
            }
668
        } catch (ConcurrentDataModificationException e) {
669
            cancel.setCanceled(true);
670
            return;
671
        } finally {
672
            if (it != null) {
673
                it.dispose();
674
            }
675
        }
676

    
677
        g.drawImage(image, 0, 0, null);
678

    
679
        Point2D offset = viewPort.getOffset();
680
        CompatLocator.getGraphicsUtils().translate(g, offset.getX(),
681
            offset.getY());
682

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

    
689
        CompatLocator.getGraphicsUtils().translate(g, -offset.getX(),
690
            -offset.getY());
691

    
692
        imageLevels = null;
693
        graphics = null;
694

    
695
        if (bSymbolLevelError) {
696
            setZSort(null);
697
        }
698
    }
699

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

    
711
        Geometry geom = feat.getDefaultGeometry();
712
        boolean bSymbolLevelError = false;
713
        long drawingTime = time;
714

    
715
        if (geom==null && geom.getType() == Geometry.TYPES.NULL) {
716
            return false;
717
        }
718

    
719
        ISymbol sym = getSymbol(feat, selection);
720

    
721
        if (sym == null) {
722
            return false;
723
        }
724

    
725
        if (coordTrans != null) {
726
            geom = geom.cloneGeometry();
727
            geom.reProject(coordTrans);
728
        }
729

    
730
        if (cancel.isCanceled()) {
731
            return false;
732
        }
733

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

    
770
        // -- visual FX stuff
771
        // Cuando el offset!=0 se est? dibujando sobre el
772
        // Layout y por tanto no tiene que ejecutar el
773
        // siguiente c?digo.
774
        Point2D offset = viewPort.getOffset();
775
        if (offset.getX() == 0 && offset.getY() == 0) {
776
            if ((System.currentTimeMillis() - drawingTime) > screenRefreshDelay) {
777

    
778
                BufferedImage virtualBim = CompatLocator.getGraphicsUtils()
779
                .createBufferedImage(image.getWidth(),
780
                    image.getHeight(), BufferedImage.TYPE_INT_ARGB);
781

    
782
                Graphics2D virtualGraphics = virtualBim.createGraphics();
783
                virtualGraphics.drawImage(image, 0, 0, null);
784
                for (int i = 0; !cancel.isCanceled() && i < imageLevels.length; i++) {
785
                    virtualGraphics.drawImage(imageLevels[i], 0, 0, null);
786
                }
787
                g.clearRect(0, 0, image.getWidth(), image.getHeight());
788
                g.drawImage(virtualBim, 0, 0, null);
789
                drawingTime = System.currentTimeMillis();
790
            }
791
            // -- end visual FX stuff
792

    
793
        }
794
        // Notify the drawing observers
795
        drawnNotification.setFeature(feat);
796
        drawnNotification.setDrawnGeometry(geom);
797
        notifyObservers(drawnNotification);
798
        return bSymbolLevelError;
799
    }
800

    
801
    /**
802
     * Returns the symbol to use to draw a {@link Feature} taking into account
803
     * if it is selected.
804
     */
805
    private ISymbol getSymbol(Feature feat, FeatureSelection selection)
806
    throws MapContextException {
807
        // retrieve the symbol associated to such feature
808
        ISymbol sym = getSymbolByFeature(feat);
809

    
810
        if (sym != null && selection.isSelected(feat)) {
811
            sym = sym.getSymbolForSelection();
812
        }
813
        return sym;
814
    }
815

    
816
    /**
817
     * Returns if the legend is using a ZSort.
818
     */
819
    private boolean isUseZSort() {
820
        return getZSort() != null && getZSort().isUsingZSort();
821
    }
822

    
823
    /**
824
     * Draws a {@link Geometry} using the given {@link ISymbol}, over the
825
     * {@link Graphics2D} object.
826
     */
827
    private void drawGeometry(Geometry geom, BufferedImage image,
828
        Feature feature, ISymbol symbol, ViewPort viewPort,
829
        Graphics2D graphics, double dpi, Cancellable cancellable)
830
    throws CreateGeometryException {
831

    
832
        if (geom instanceof Aggregate) {
833
            drawAggregate((Aggregate) geom, image, feature, symbol, viewPort,
834
                graphics, dpi, cancellable);
835
        } else {
836
            boolean bDrawCartographicSupport = false;
837
            if (symbol instanceof CartographicSupport) {
838
                bDrawCartographicSupport = ((CartographicSupport) symbol)
839
                .getUnit() != -1;
840
            }
841

    
842
            double previousSize = 0.0d;
843

    
844
            if (bDrawCartographicSupport) {
845
                // make the symbol to resize itself with the current rendering
846
                // context
847
                previousSize = ((CartographicSupport) symbol)
848
                .toCartographicSize(viewPort, dpi, geom);
849
            }
850

    
851
            try {
852
                symbol.draw(graphics, viewPort.getAffineTransform(), geom,
853
                    feature, cancellable);
854
            } finally {
855
                if (bDrawCartographicSupport) {
856
                    // restore previous size
857
                    ((CartographicSupport) symbol).setCartographicSize(
858
                        previousSize, geom);
859
                }
860
            }
861
        }
862
    }
863

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

    
879
    public Object clone() throws CloneNotSupportedException {
880
        AbstractVectorialLegend clone = (AbstractVectorialLegend) super.clone();
881

    
882
        // Clone zSort
883
        ZSort zSort = getZSort();
884
        if (zSort != null) {
885
            clone.setZSort(new ZSort(clone));
886
        }
887
        return clone;
888
    }
889

    
890
    public void loadFromState(PersistentState state)
891
    throws PersistenceException {
892
        // Set parent properties
893
        super.loadFromState(state);
894
        // Set own properties
895

    
896
        setShapeType(state.getInt(FIELD_SHAPETYPE));
897
        if (state.getBoolean(FIELD_HAS_ZSORT)) {
898
            setZSort(new ZSort(this));
899
        }
900
        setDefaultSymbol((ISymbol) state.get(FIELD_DEFAULT_SYMBOL));
901
    }
902

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

    
912
    public static class RegisterPersistence implements Callable {
913

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

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

    
939
    }
940

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