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

History | View | Annotate | Download (36.1 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.FeatureReference;
49
import org.gvsig.fmap.dal.feature.FeatureSelection;
50
import org.gvsig.fmap.dal.feature.FeatureSet;
51
import org.gvsig.fmap.dal.feature.FeatureStore;
52
import org.gvsig.fmap.dal.feature.FeatureType;
53
import org.gvsig.fmap.dal.feature.exception.ConcurrentDataModificationException;
54
import org.gvsig.fmap.geom.Geometry;
55
import org.gvsig.fmap.geom.GeometryLocator;
56
import org.gvsig.fmap.geom.GeometryManager;
57
import org.gvsig.fmap.geom.aggregate.Aggregate;
58
import org.gvsig.fmap.geom.exception.CreateGeometryException;
59
import org.gvsig.fmap.geom.exception.ReprojectionRuntimeException;
60
import org.gvsig.fmap.geom.operation.DrawInts;
61
import org.gvsig.fmap.geom.operation.DrawOperationContext;
62
import org.gvsig.fmap.geom.operation.GeometryOperationException;
63
import org.gvsig.fmap.geom.operation.GeometryOperationNotSupportedException;
64
import org.gvsig.fmap.geom.primitive.Envelope;
65
import org.gvsig.fmap.mapcontext.MapContext;
66
import org.gvsig.fmap.mapcontext.MapContextException;
67
import org.gvsig.fmap.mapcontext.ViewPort;
68
import org.gvsig.fmap.mapcontext.layers.vectorial.IntersectsEnvelopeEvaluator;
69
import org.gvsig.fmap.mapcontext.layers.vectorial.SpatialEvaluatorsFactory;
70
import org.gvsig.fmap.mapcontext.rendering.legend.ILegend;
71
import org.gvsig.fmap.mapcontext.rendering.legend.IVectorLegend;
72
import org.gvsig.fmap.mapcontext.rendering.legend.LegendException;
73
import org.gvsig.fmap.mapcontext.rendering.legend.ZSort;
74
import org.gvsig.fmap.mapcontext.rendering.symbols.CartographicSupport;
75
import org.gvsig.fmap.mapcontext.rendering.symbols.IMultiLayerSymbol;
76
import org.gvsig.fmap.mapcontext.rendering.symbols.ISymbol;
77
import org.gvsig.tools.ToolsLocator;
78
import org.gvsig.tools.dispose.DisposableIterator;
79
import org.gvsig.tools.dynobject.DynStruct;
80
import org.gvsig.tools.evaluator.Evaluator;
81
import org.gvsig.tools.exception.BaseException;
82
import org.gvsig.tools.logger.FilteredLogger;
83
import org.gvsig.tools.persistence.PersistenceManager;
84
import org.gvsig.tools.persistence.PersistentState;
85
import org.gvsig.tools.persistence.exception.PersistenceException;
86
import org.gvsig.tools.task.Cancellable;
87
import org.gvsig.tools.task.SimpleTaskStatus;
88
import org.gvsig.tools.util.Callable;
89
import org.gvsig.tools.visitor.VisitCanceledException;
90
import org.gvsig.tools.visitor.Visitor;
91

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

    
103
        private static final Logger LOG = LoggerFactory
104
                        .getLogger(AbstractVectorialLegend.class);
105

    
106
    private static final int DRAW_MAX_ATTEMPTS = 5;
107

    
108
        public static final String VECTORIAL_LEGEND_PERSISTENCE_DEFINITION_NAME = "VectorialLegend";
109

    
110
    private static final String FIELD_HAS_ZSORT = "hasZSort";
111
    private static final String FIELD_SHAPETYPE = "shapeType";
112
    private static final String FIELD_DEFAULT_SYMBOL = "defaultSymbol";
113

    
114
    private static final GeometryManager geomManager = GeometryLocator
115
    .getGeometryManager();
116

    
117
    protected ZSort zSort;
118

    
119
    public ZSort getZSort() {
120
        return zSort;
121
    }
122

    
123
    public void setZSort(ZSort zSort) {
124
        if (zSort == null) {
125
            removeLegendListener(this.zSort);
126
        }
127
        this.zSort = zSort;
128
        addLegendListener(zSort);
129
    }
130

    
131
    @SuppressWarnings("unchecked")
132
    public void draw(BufferedImage image, Graphics2D g, ViewPort viewPort,
133
        Cancellable cancel, double scale, Map queryParameters,
134
        ICoordTrans coordTrans, FeatureStore featureStore)
135
    throws LegendException {
136
        double dpi = viewPort.getDPI();
137
        draw(image, g, viewPort, cancel, scale, queryParameters, coordTrans,
138
            featureStore, null, dpi);
139
    }
140

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

    
151
    @SuppressWarnings("unchecked")
152
    public void print(Graphics2D g, ViewPort viewPort, Cancellable cancel,
153
        double scale, Map queryParameters, ICoordTrans coordTrans,
154
        FeatureStore featureStore, PrintAttributes properties)
155
    throws LegendException {
156
        print(g, viewPort, cancel, scale, queryParameters, coordTrans,
157
            featureStore, null, properties);
158
    }
159

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

    
167
        int resolution = properties.getPrintQuality();
168

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

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

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

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

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

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

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

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

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

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

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

    
284
                        // Check if this symbol is sized with CartographicSupport
285
                        CartographicSupport csSym = null;
286
                        int symbolType = sym.getSymbolType();
287

    
288
                        if (symbolType == Geometry.TYPES.POINT
289
                            || symbolType == Geometry.TYPES.CURVE
290
                            || sym instanceof CartographicSupport) {
291

    
292
                            csSym = (CartographicSupport) sym;
293
                        }
294

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

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

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

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

    
363
                if (!getDefaultSymbol().isShapeVisible()) {
364
                        return;
365
                }
366

    
367
                if (cancel.isCanceled()) {
368
                        return;
369
                }
370

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

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

    
389
                                reprojectedDataEnvelope = dataEnvelope;
390
                reprojectedViewPortEnvelope = viewPortEnvelope;
391
                        } else {
392
                                dataProjection = coordTrans.getPOrig();
393

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

    
409

    
410
                // Gets the data envelope with the viewport SRS
411
//                Envelope myEnvelope = reprojectedDataEnvelope;
412

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

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

    
426
                // Create the drawing notification to be reused on each iteration
427
                DefaultFeatureDrawnNotification drawnNotification = new DefaultFeatureDrawnNotification();
428

    
429
                if (cancel.isCanceled()) {
430
                        return;
431
                }
432

    
433
                FeatureSet featureSet = null;
434
                try {
435
                        taskStatus.message("Retrieve selection");
436
                        FeatureSelection selection = featureStore.getFeatureSelection();
437

    
438
                        if (featureQuery == null) {
439
                                featureQuery = featureStore.createFeatureQuery();
440
                        }
441

    
442
                        completeQuery(featureStore, featureQuery, scale, queryParameters,
443
                                        coordTrans, dataProjection, viewPortEnvelope, containsAll);
444

    
445
                        taskStatus.message("Retrieve data");
446
                        featureSet = featureStore.getFeatureSet(featureQuery);
447

    
448
                        if (cancel.isCanceled()) {
449
                                return;
450
                        }
451

    
452
                        taskStatus.message("Drawing");
453
                        drawFeatures(image, g, viewPort, cancel, coordTrans, dpi,
454
                                        drawnNotification, featureSet, selection);
455

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

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

    
480
        featureQuery.setScale(scale);
481

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

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

    
498
            if (dataProjection == null) {
499
                throw new IllegalArgumentException(
500
                "Error, the projection parameter value is null");
501
            }
502

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

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

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

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

    
562
        } catch (ConcurrentDataModificationException e) {
563
            cancel.setCanceled(true);
564
            return;
565
        }
566
    }
567

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

    
578
        Geometry geom = feat.getDefaultGeometry();
579
        if (geom == null) {
580
            return;
581
        }
582

    
583
        if (geom.getType() == Geometry.TYPES.NULL) {
584
            return;
585
        }
586

    
587
        ISymbol sym = getSymbol(feat, selection);
588
        if (sym == null) {
589
            return;
590
        }
591

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

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

    
611
        if (cancel.isCanceled()) {
612
            return;
613
        }
614

    
615
        drawGeometry(geom, image, feat, sym, viewPort, g, dpi, cancel);
616

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

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

    
633
        // -- visual FX stuff
634
        long time = System.currentTimeMillis();
635

    
636
        boolean bSymbolLevelError = false;
637
        // render temporary map each screenRefreshRate milliseconds;
638
        int screenRefreshDelay = (int) ((1D / MapContext.getDrawFrameRate()) * 3 * 1000);
639
        BufferedImage[] imageLevels = null;
640
        Graphics2D[] graphics = null;
641

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

    
646
            imageLevels[i] = CompatLocator.getGraphicsUtils()
647
            .createBufferedImage(image.getWidth(), image.getHeight(),
648
                image.getType());
649

    
650
            graphics[i] = imageLevels[i].createGraphics();
651
            graphics[i].setTransform(g.getTransform());
652
            graphics[i].setRenderingHints(g.getRenderingHints());
653
        }
654
        // -- end visual FX stuff
655

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

    
666
                bSymbolLevelError |= drawFeatureMultiLayer(image, g, viewPort,
667
                    cancel, coordTrans, dpi, drawnNotification, selection,
668
                    time, screenRefreshDelay, imageLevels, graphics, feat);
669

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

    
680
        g.drawImage(image, 0, 0, null);
681

    
682
        Point2D offset = viewPort.getOffset();
683
        CompatLocator.getGraphicsUtils().translate(g, offset.getX(),
684
            offset.getY());
685

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

    
692
        CompatLocator.getGraphicsUtils().translate(g, -offset.getX(),
693
            -offset.getY());
694

    
695
        imageLevels = null;
696
        graphics = null;
697

    
698
        if (bSymbolLevelError) {
699
            setZSort(null);
700
        }
701
    }
702

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

    
714
        Geometry geom = feat.getDefaultGeometry();
715
        boolean bSymbolLevelError = false;
716
        long drawingTime = time;
717

    
718
        if (geom==null && geom.getType() == Geometry.TYPES.NULL) {
719
            return false;
720
        }
721

    
722
        ISymbol sym = getSymbol(feat, selection);
723

    
724
        if (sym == null) {
725
            return false;
726
        }
727

    
728
        if (coordTrans != null) {
729
            geom = geom.cloneGeometry();
730
            geom.reProject(coordTrans);
731
        }
732

    
733
        if (cancel.isCanceled()) {
734
            return false;
735
        }
736

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

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

    
781
                BufferedImage virtualBim = CompatLocator.getGraphicsUtils()
782
                .createBufferedImage(image.getWidth(),
783
                    image.getHeight(), BufferedImage.TYPE_INT_ARGB);
784

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

    
796
        }
797
        // Notify the drawing observers
798
        drawnNotification.setFeature(feat);
799
        drawnNotification.setDrawnGeometry(geom);
800
        notifyObservers(drawnNotification);
801
        return bSymbolLevelError;
802
    }
803

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

    
813
        if (sym != null && selection.isSelected(feat)) {
814
            sym = sym.getSymbolForSelection();
815
        }
816
        return sym;
817
    }
818

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

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

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

    
845
            double previousSize = 0.0d;
846

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

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

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

    
882
    public Object clone() throws CloneNotSupportedException {
883
        AbstractVectorialLegend clone = (AbstractVectorialLegend) super.clone();
884

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

    
893
    public void loadFromState(PersistentState state)
894
    throws PersistenceException {
895
        // Set parent properties
896
        super.loadFromState(state);
897
        // Set own properties
898

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

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

    
915
    public static class RegisterPersistence implements Callable {
916

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

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

    
942
    }
943

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