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

History | View | Annotate | Download (35.6 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 = viewPort.getDPI();
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 = viewPort.getDPI();
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
                it = featureSet.fastIterator();
232
                // Iteration over each feature
233
                while (!cancel.isCanceled() && it.hasNext()) {
234
                    Feature feat = (Feature) it.next();
235
                    Geometry geom = feat.getDefaultGeometry();
236
                    if (geom==null) {
237
                            continue;
238
                    }
239
                    // Reprojection if needed
240
                    if (coordTrans != null) {
241
                        geom = geom.cloneGeometry();
242
                        geom.reProject(coordTrans);
243
                    }
244

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

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

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

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

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

    
285
                        csSym = (CartographicSupport) sym;
286
                    }
287

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

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

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

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

    
357
                if (!getDefaultSymbol().isShapeVisible()) {
358
                        return;
359
                }
360

    
361
                if (cancel.isCanceled()) {
362
                        return;
363
                }
364

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

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

    
383
                                reprojectedDataEnvelope = dataEnvelope;
384
                reprojectedViewPortEnvelope = viewPortEnvelope;
385
                        } else {
386
                                dataProjection = coordTrans.getPOrig();
387

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

    
403

    
404
                // Gets the data envelope with the viewport SRS
405
//                Envelope myEnvelope = reprojectedDataEnvelope;
406

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

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

    
420
                // Create the drawing notification to be reused on each iteration
421
                DefaultFeatureDrawnNotification drawnNotification = new DefaultFeatureDrawnNotification();
422

    
423
                if (cancel.isCanceled()) {
424
                        return;
425
                }
426

    
427
                FeatureSet featureSet = null;
428
                try {
429
                        taskStatus.message("Retrieve selection");
430
                        FeatureSelection selection = featureStore.getFeatureSelection();
431

    
432
                        if (featureQuery == null) {
433
                                featureQuery = featureStore.createFeatureQuery();
434
                        }
435

    
436
                        completeQuery(featureStore, featureQuery, scale, queryParameters,
437
                                        coordTrans, dataProjection, viewPortEnvelope, containsAll);
438

    
439
                        taskStatus.message("Retrieve data");
440
                        featureSet = featureStore.getFeatureSet(featureQuery);
441

    
442
                        if (cancel.isCanceled()) {
443
                                return;
444
                        }
445

    
446
                        taskStatus.message("Drawing");
447
                        drawFeatures(image, g, viewPort, cancel, coordTrans, dpi,
448
                                        drawnNotification, featureSet, selection);
449

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

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

    
474
        featureQuery.setScale(scale);
475

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

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

    
492
            if (dataProjection == null) {
493
                throw new IllegalArgumentException(
494
                "Error, the projection parameter value is null");
495
            }
496

    
497
            IntersectsEnvelopeEvaluator iee = new IntersectsEnvelopeEvaluator(
498
                viewPortEnvelopeInMyProj, dataProjection,
499
                featureStore.getDefaultFeatureType(), featureStore
500
                .getDefaultFeatureType()
501
                .getDefaultGeometryAttributeName());
502
            featureQuery.addFilter(iee);
503
        }
504
        if (queryParameters != null) {
505
            Iterator iterEntry = queryParameters.entrySet().iterator();
506
            Entry entry;
507
            while (iterEntry.hasNext()) {
508
                entry = (Entry) iterEntry.next();
509
                featureQuery.setQueryParameter((String) entry.getKey(),
510
                    entry.getValue());
511
            }
512
        }
513
        return featureQuery;
514
    }
515

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

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

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

    
556
        } catch (ConcurrentDataModificationException e) {
557
            cancel.setCanceled(true);
558
            return;
559
        }
560
    }
561

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

    
572
        Geometry geom = feat.getDefaultGeometry();
573
        if (geom == null) {
574
            return;
575
        }
576

    
577
        if (geom.getType() == Geometry.TYPES.NULL) {
578
            return;
579
        }
580

    
581
        ISymbol sym = getSymbol(feat, selection);
582
        if (sym == null) {
583
            return;
584
        }
585

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

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

    
605
        if (cancel.isCanceled()) {
606
            return;
607
        }
608

    
609
        drawGeometry(geom, image, feat, sym, viewPort, g, dpi, cancel);
610

    
611
        // Notify the drawing observers
612
        drawnNotification.setFeature(feat);
613
        drawnNotification.setDrawnGeometry(geom);
614
        notifyObservers(drawnNotification);
615
    }
616

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

    
627
        // -- visual FX stuff
628
        long time = System.currentTimeMillis();
629

    
630
        boolean bSymbolLevelError = false;
631
        // render temporary map each screenRefreshRate milliseconds;
632
        int screenRefreshDelay = (int) ((1D / MapContext.getDrawFrameRate()) * 3 * 1000);
633
        BufferedImage[] imageLevels = null;
634
        Graphics2D[] graphics = null;
635

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

    
640
            imageLevels[i] = CompatLocator.getGraphicsUtils()
641
            .createBufferedImage(image.getWidth(), image.getHeight(),
642
                image.getType());
643

    
644
            graphics[i] = imageLevels[i].createGraphics();
645
            graphics[i].setTransform(g.getTransform());
646
            graphics[i].setRenderingHints(g.getRenderingHints());
647
        }
648
        // -- end visual FX stuff
649

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

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

    
664
            }
665
        } catch (ConcurrentDataModificationException e) {
666
            cancel.setCanceled(true);
667
            return;
668
        } finally {
669
            if (it != null) {
670
                it.dispose();
671
            }
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
        imageLevels = null;
690
        graphics = null;
691

    
692
        if (bSymbolLevelError) {
693
            setZSort(null);
694
        }
695
    }
696

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

    
708
        Geometry geom = feat.getDefaultGeometry();
709
        boolean bSymbolLevelError = false;
710
        long drawingTime = time;
711

    
712
        if (geom==null && geom.getType() == Geometry.TYPES.NULL) {
713
            return false;
714
        }
715

    
716
        ISymbol sym = getSymbol(feat, selection);
717

    
718
        if (sym == null) {
719
            return false;
720
        }
721

    
722
        if (coordTrans != null) {
723
            geom = geom.cloneGeometry();
724
            geom.reProject(coordTrans);
725
        }
726

    
727
        if (cancel.isCanceled()) {
728
            return false;
729
        }
730

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

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

    
775
                BufferedImage virtualBim = CompatLocator.getGraphicsUtils()
776
                .createBufferedImage(image.getWidth(),
777
                    image.getHeight(), BufferedImage.TYPE_INT_ARGB);
778

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

    
790
        }
791
        // Notify the drawing observers
792
        drawnNotification.setFeature(feat);
793
        drawnNotification.setDrawnGeometry(geom);
794
        notifyObservers(drawnNotification);
795
        return bSymbolLevelError;
796
    }
797

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

    
807
        if (sym != null && selection.isSelected(feat)) {
808
            sym = sym.getSymbolForSelection();
809
        }
810
        return sym;
811
    }
812

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

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

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

    
839
            double previousSize = 0.0d;
840

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

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

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

    
876
    public Object clone() throws CloneNotSupportedException {
877
        AbstractVectorialLegend clone = (AbstractVectorialLegend) super.clone();
878

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

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

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

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

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

    
936
    }
937

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