Statistics
| Revision:

root / trunk / extensions / extSymbology / src / org / gvsig / symbology / fmap / labeling / GeneralLabelingStrategy.java @ 20768

History | View | Annotate | Download (16.5 KB)

1
/* gvSIG. Sistema de Informaci?n Geogr?fica de la Generalitat Valenciana
2
 *
3
 * Copyright (C) 2005 IVER T.I. and Generalitat Valenciana.
4
 *
5
 * This program is free software; you can redistribute it and/or
6
 * modify it under the terms of the GNU General Public License
7
 * as published by the Free Software Foundation; either version 2
8
 * of the License, or (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program; if not, write to the Free Software
17
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,USA.
18
 *
19
 * For more information, contact:
20
 *
21
 *  Generalitat Valenciana
22
 *   Conselleria d'Infraestructures i Transport
23
 *   Av. Blasco Ib??ez, 50
24
 *   46010 VALENCIA
25
 *   SPAIN
26
 *
27
 *      +34 963862235
28
 *   gvsig@gva.es
29
 *      www.gvsig.gva.es
30
 *
31
 *    or
32
 *
33
 *   IVER T.I. S.A
34
 *   Salamanca 50
35
 *   46005 Valencia
36
 *   Spain
37
 *
38
 *   +34 963163400
39
 *   dac@iver.es
40
 */
41

    
42
/* CVS MESSAGES:
43
*
44
* $Id: GeneralLabelingStrategy.java 13749 2007-09-17 14:16:11Z jaume $
45
* $Log$
46
* Revision 1.2  2007-09-17 14:16:11  jaume
47
* multilayer symbols sizing bug fixed
48
*
49
* Revision 1.1  2007/05/22 12:17:41  jaume
50
* *** empty log message ***
51
*
52
* Revision 1.1  2007/05/22 10:05:31  jaume
53
* *** empty log message ***
54
*
55
* Revision 1.10  2007/05/17 09:32:06  jaume
56
* *** empty log message ***
57
*
58
* Revision 1.9  2007/05/09 11:04:58  jaume
59
* refactored legend hierarchy
60
*
61
* Revision 1.8  2007/04/13 11:59:30  jaume
62
* *** empty log message ***
63
*
64
* Revision 1.7  2007/04/12 14:28:43  jaume
65
* basic labeling support for lines
66
*
67
* Revision 1.6  2007/04/11 16:01:08  jaume
68
* maybe a label placer refactor
69
*
70
* Revision 1.5  2007/04/10 16:34:01  jaume
71
* towards a styled labeling
72
*
73
* Revision 1.4  2007/04/02 16:34:56  jaume
74
* Styled labeling (start commiting)
75
*
76
* Revision 1.3  2007/03/28 16:48:01  jaume
77
* *** empty log message ***
78
*
79
* Revision 1.2  2007/03/26 14:40:38  jaume
80
* added print method (BUT UNIMPLEMENTED)
81
*
82
* Revision 1.1  2007/03/20 16:16:20  jaume
83
* refactored to use ISymbol instead of FSymbol
84
*
85
* Revision 1.2  2007/03/09 11:20:57  jaume
86
* Advanced symbology (start committing)
87
*
88
* Revision 1.1  2007/03/09 08:33:43  jaume
89
* *** empty log message ***
90
*
91
* Revision 1.1.2.5  2007/02/21 07:34:08  jaume
92
* labeling starts working
93
*
94
* Revision 1.1.2.4  2007/02/15 16:23:44  jaume
95
* *** empty log message ***
96
*
97
* Revision 1.1.2.3  2007/02/09 07:47:05  jaume
98
* Isymbol moved
99
*
100
* Revision 1.1.2.2  2007/02/02 16:21:24  jaume
101
* start commiting labeling stuff
102
*
103
* Revision 1.1.2.1  2007/02/01 17:46:49  jaume
104
* *** empty log message ***
105
*
106
*
107
*/
108
package org.gvsig.symbology.fmap.labeling;
109

    
110
import java.awt.Graphics2D;
111
import java.awt.RenderingHints;
112
import java.awt.geom.Rectangle2D;
113
import java.awt.image.BufferedImage;
114
import java.io.CharArrayReader;
115
import java.text.NumberFormat;
116
import java.util.ArrayList;
117
import java.util.TreeSet;
118

    
119
import javax.print.attribute.PrintRequestAttributeSet;
120

    
121
import org.apache.log4j.Logger;
122
import org.cresques.cts.ICoordTrans;
123
import org.gvsig.symbology.fmap.labeling.lang.Symbol;
124
import org.gvsig.symbology.fmap.labeling.parse.LabelExpressionParser;
125
import org.gvsig.symbology.fmap.labeling.parse.ParseException;
126
import org.gvsig.symbology.fmap.labeling.placements.ILabelPlacement;
127
import org.gvsig.symbology.fmap.labeling.placements.RemoveDuplicatesComparator;
128

    
129
import com.hardcode.gdbms.driver.exceptions.ReadDriverException;
130
import com.hardcode.gdbms.engine.values.Value;
131
import com.iver.cit.gvsig.fmap.ViewPort;
132
import com.iver.cit.gvsig.fmap.core.FShape;
133
import com.iver.cit.gvsig.fmap.core.IFeature;
134
import com.iver.cit.gvsig.fmap.core.IGeometry;
135
import com.iver.cit.gvsig.fmap.core.v02.FConverter;
136
import com.iver.cit.gvsig.fmap.drivers.IFeatureIterator;
137
import com.iver.cit.gvsig.fmap.layers.FLayer;
138
import com.iver.cit.gvsig.fmap.layers.FLyrVect;
139
import com.iver.cit.gvsig.fmap.rendering.styling.labeling.ILabelingMethod;
140
import com.iver.cit.gvsig.fmap.rendering.styling.labeling.ILabelingStrategy;
141
import com.iver.cit.gvsig.fmap.rendering.styling.labeling.IPlacementConstraints;
142
import com.iver.cit.gvsig.fmap.rendering.styling.labeling.IZoomConstraints;
143
import com.iver.cit.gvsig.fmap.rendering.styling.labeling.LabelClass;
144
import com.iver.cit.gvsig.fmap.rendering.styling.labeling.LabelLocationMetrics;
145
import com.iver.cit.gvsig.fmap.rendering.styling.labeling.LabelingFactory;
146
import com.iver.utiles.XMLEntity;
147
import com.iver.utiles.swing.threads.Cancellable;
148

    
149
/**
150
 * 
151
 * GeneralLabelingStrategy.java
152
 *
153
 * 
154
 * @author jaume dominguez faus - jaume.dominguez@iver.es Jan 4, 2008
155
 *
156
 */
157
public class GeneralLabelingStrategy implements ILabelingStrategy {
158
        private ILabelingMethod method;
159
        private IPlacementConstraints placementConstraints;
160
        protected FLyrVect layer;
161
        private IZoomConstraints zoomConstraints;
162
        private boolean allowOverlapping;
163
        private long minScaleView = -1;
164
        private long maxScaleView = -1;
165
        
166
        public void setLayer(FLayer layer) throws ReadDriverException {
167
                FLyrVect l = (FLyrVect) layer;
168
                this.layer = l;
169
        }
170

    
171
        public ILabelingMethod getLabelingMethod() {
172
                return method;
173
        }
174

    
175
        public void setLabelingMethod(ILabelingMethod method) {
176
                this.method = method;
177
        }
178

    
179
        public void draw(BufferedImage mapImage, Graphics2D mapGraphics, ViewPort viewPort,
180
                        Cancellable cancel, double dpi)
181
        throws ReadDriverException {
182
                mapGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
183
                TreeSet<?> placedLabels = null;
184

    
185
                long t1 = System.currentTimeMillis();
186
                String[] usedFields = getUsedFields();
187
                
188
                int notPlacedCount = 0;
189
                int placedCount = 0;
190
                ILabelPlacement placement = PlacementManager.getPlacement(getPlacementConstraints(), layer.getShapeType());
191

    
192
                /* handle the duplicates mode */
193
                int duplicateMode = getDuplicateLabelsMode();
194

    
195
                /* 
196
                 * get an ordered set of the LabelClasses up on the 
197
                 * label priority
198
                 */
199
                LabelClass[] lcs = method.getLabelClasses();
200
                TreeSet<LabelClass> ts = new TreeSet<LabelClass>(new LabelClassComparatorByPriority());
201

    
202
                for (int i = 0; i < lcs.length; i++) ts.add(lcs[i]);
203

    
204
                /* 
205
                 * now we have an ordered set, it is only need to give a pass
206
                 * for each label class to render by priorities.
207
                 * 
208
                 * If no priorities were defined, the following loop only executes
209
                 * once
210
                 */
211
                for (LabelClass lc : ts) {
212
                        IFeatureIterator it =  method.getFeatureIteratorByLabelClass(layer, lc, viewPort, usedFields);
213
                        
214
                        // duplicates treatment stuff 
215
                        if (duplicateMode == IPlacementConstraints.REMOVE_DUPLICATE_LABELS) {
216
                                // we need to register the labels already placed
217
                                if (placedLabels == null) 
218
                                        placedLabels = new TreeSet<String[]>(
219
                                                        new RemoveDuplicatesComparator());
220
                                else placedLabels.clear();
221
                        }
222

    
223
//                        TODO        Pending of the refactoring on geometries model
224
//                        
225
//                        else if (duplicateMode == IPlacementConstraints.ONE_LABEL_PER_FEATURE) {
226
//                                        // we need to register the labels already placed
227
//                                        if (placedLabels == null) placedLabels = new TreeSet();
228
//                                        else placedLabels.clear();
229
//                        }
230
                        //duplicates stuff
231
                        
232
                        boolean bLabelsReallocatable = !isAllowOverlapping();
233
                        BufferedImage levelImg = null, 
234
                        bi = new BufferedImage(
235
                                        mapImage.getWidth(),
236
                                        mapImage.getHeight(),
237
                                        BufferedImage.TYPE_INT_ARGB
238
                                );
239
                        Graphics2D levelGraphics = null,
240
                        gBi = bi.createGraphics();
241
                        gBi.setRenderingHints(mapGraphics.getRenderingHints());
242
                        
243
                        if (bLabelsReallocatable) {
244
                                levelImg = new BufferedImage(mapImage.getWidth(),mapImage.getHeight(),BufferedImage.TYPE_INT_ARGB);
245
                                levelGraphics = bi.createGraphics();
246
                                levelGraphics.setRenderingHints(mapGraphics.getRenderingHints());
247
                        }
248

    
249
                        while ( !cancel.isCanceled() && it.hasNext()) {
250

    
251
                                IFeature feat = it.next();
252
                                IGeometry geom = feat.getGeometry();
253

    
254
                                if (!setupLabel(feat, lc, cancel, usedFields, viewPort, dpi, duplicateMode, placedLabels))
255
                                        continue;
256

    
257
                                // Check if size is a pixel
258
                                if (isOnePoint(viewPort, geom)) {
259
                                        continue;
260
                                }
261

    
262
                                BufferedImage[] targetBis;
263
                                Graphics2D targetG;
264

    
265
                                if (method.definesPriorities()) {
266
                                        if (bLabelsReallocatable) {
267
                                                targetBis = new BufferedImage[] { bi, levelImg } ;
268
                                                targetG = levelGraphics;
269
                                        } else {
270
                                                targetBis = new BufferedImage[] { bi } ;
271
                                                targetG = gBi;
272
                                        }
273
                                } else {
274
                                        if (bLabelsReallocatable) {
275
                                                targetBis = new BufferedImage[] { bi };
276
                                                targetG = gBi;
277
                                        } else {
278
                                                targetBis = new BufferedImage[] { mapImage };
279
                                                targetG = mapGraphics;
280
                                        }
281
                                }
282
                                
283
                                // Calculate the label possible places
284
                                ArrayList<LabelLocationMetrics> llm = placement.guess(
285
                                                lc, 
286
                                                FConverter.transformToInts(geom, viewPort.getAffineTransform()),
287
                                                getPlacementConstraints(),
288
                                                0,
289
                                                cancel);
290

    
291
                                /* 
292
                                 * search if there is room left by the previous and
293
                                 * with more priority labels, then check the current
294
                                 * level
295
                                 */
296
                                if (lookupAndPlaceLabel(targetBis, targetG,
297
                                                llm, placement, lc, geom, viewPort, cancel,
298
                                                bLabelsReallocatable)) {
299
                                        placedCount++;
300
                                } else {
301
                                        notPlacedCount++;
302
                                }
303

    
304
                                if (bLabelsReallocatable) {
305
                                        mapGraphics.drawImage(bi, null, null);
306
                                }
307
                        }
308
                }
309
                int total = placedCount+notPlacedCount;
310

    
311
                Logger.getLogger(getClass()).info("Labeled layer '"+layer.getName()+"' "+(System.currentTimeMillis()-t1)/1000D+" seconds. "+placedCount+"/"+total+" labels placed ("+NumberFormat.getInstance().format(100*placedCount/(double) total)+"%)");
312

    
313
        }
314

    
315

    
316
        private int getDuplicateLabelsMode() {
317
                if (getPlacementConstraints() == null) {
318
                        return IPlacementConstraints.DefaultDuplicateLabelsMode;
319
                }
320
                return getPlacementConstraints().getDuplicateLabelsMode();
321
        }
322

    
323
        private boolean lookupAndPlaceLabel(BufferedImage[] bis, Graphics2D g, ArrayList<LabelLocationMetrics> llm, ILabelPlacement placement, LabelClass lc, IGeometry geom, ViewPort viewPort, Cancellable cancel, boolean bLabelsReallocatable) {
324
                int i;
325
                for (i = 0; !cancel.isCanceled() && i < llm.size(); i++) {
326
                        LabelLocationMetrics labelMetrics = llm.get(i);
327

    
328
                        if (bLabelsReallocatable) {
329
                                for (int j = 0; j < bis.length; j++) {
330
                                        BufferedImage bi = bis[j];
331
                                        if (!isOverlapping(bi, lc.getShape(labelMetrics))) {
332
                                                lc.draw(g, labelMetrics, null);
333
//                                                SimpleMarkerSymbol sms = new SimpleMarkerSymbol();
334
//                                                sms.setSize(5);
335
//                                                sms.setColor(Color.YELLOW);
336
//                                                sms.draw(g, null, new FPoint2D(labelMetrics.getAnchor()), null);
337
                                                return true;
338
                                        }        
339
                                }
340
                        } else {
341
                                lc.draw(g, labelMetrics, null);        
342
                                return true;
343
                        }
344
                } 
345
                return false;
346
        }
347
        
348
        @SuppressWarnings("unchecked")
349
        private boolean setupLabel(IFeature feat, LabelClass lc, 
350
        Cancellable cancel, String[] usedFields, ViewPort viewPort,
351
        double dpi, int duplicateMode, TreeSet<?> placedLabels) {
352

    
353
                Value[] vv = feat.getAttributes();
354
                String expr = lc.getLabelExpression();
355
                LabelExpressionParser parser = new LabelExpressionParser(
356
                                new CharArrayReader(expr.toCharArray()));
357
                for (int i = 0; !cancel.isCanceled() && i < usedFields.length; i++) {
358
                        parser.putSymbol(
359
                                        Symbol.createVariableSymbolFromValue(usedFields[i], vv[i]));
360
                }
361
                String[] texts;
362
                try {
363
                        texts = parser.LabelExpression();
364
                        
365
                        if (duplicateMode == IPlacementConstraints.REMOVE_DUPLICATE_LABELS) {
366
                                // check if this text (so label) is already present in the map
367
                                if (placedLabels.contains(texts)) 
368
                                        // the label has already placed before
369
                                        return false;
370
                                
371
                                /*
372
                                 *  the text is not present, it will be registered for next to
373
                                 *  be avoided
374
                                 */
375
                                ((TreeSet<String[]>) placedLabels).add(texts);
376
                        }
377
                        lc.setTexts(texts);
378
                } catch (ParseException e) {
379
                        e.printStackTrace();
380
                        return false;
381
                } 
382

    
383
                lc.toCartographicSize(viewPort, dpi, null);
384
                return true;
385
        }
386
        
387
        private boolean isOverlapping(BufferedImage bi, FShape labelShape) {
388
                
389
                Rectangle2D rPixels = labelShape.getBounds2D();
390
            for (int i= (int) rPixels.getX(); i<rPixels.getMaxX(); i++){
391
                for (int j= (int) rPixels.getY(); j<rPixels.getMaxY(); j++){
392
                        if (!labelShape.contains(i, j)) {
393
                                continue;
394
                        }
395
                        
396
                        if (i<0 || j<0) {
397
                                continue;
398
                        }
399
                        
400
                        if (bi.getWidth()<i+1 || bi.getHeight()<j+1) {
401
                                continue;
402
                        }
403
                        
404
                    if (bi.getRGB(i,j)!=0){
405
                                return true;
406
                    }
407
                }
408
            }
409
            return false;
410
        }
411
        
412
        private boolean isOnePoint(ViewPort viewPort, IGeometry geom) {
413
                boolean onePoint = false;
414
                int shapeType = geom.getGeometryType();
415
                if (shapeType!=FShape.POINT && shapeType!=FShape.MULTIPOINT) {
416

    
417
                        Rectangle2D geomBounds = geom.getBounds2D();
418
                        ICoordTrans ct = layer.getCoordTrans();
419

    
420
                        if (ct!=null) {
421
                                geomBounds = ct.convert(geomBounds);
422
                        }
423

    
424
                        double dist1Pixel = viewPort.getDist1pixel();
425
                        onePoint = (geomBounds.getWidth() <= dist1Pixel
426
                                        && geomBounds.getHeight() <= dist1Pixel);
427
                }
428
                return onePoint;
429
        }
430

    
431
        public String getClassName() {
432
                return getClass().getName();
433
        }
434

    
435
        public XMLEntity getXMLEntity() {
436
                XMLEntity xml = new XMLEntity();
437
                xml.putProperty("className", getClassName());
438
                xml.putProperty("allowOverlapping", allowOverlapping);
439
                xml.putProperty("minScaleView", minScaleView);
440
                xml.putProperty("maxScaleView", maxScaleView);
441
                
442
                if (method!=null) {
443
                        XMLEntity methodEntity = method.getXMLEntity();
444
                        methodEntity.putProperty("id", "LabelingMethod");
445
                        xml.addChild(methodEntity);
446
                }
447
                
448
                if (placementConstraints != null) {
449
                        XMLEntity pcEntity = placementConstraints.getXMLEntity();
450
                        pcEntity.putProperty("id", "PlacementConstraints");
451
                        xml.addChild(pcEntity);
452
                }
453
                
454
                if (zoomConstraints != null) {
455
                        XMLEntity zcEntity = zoomConstraints.getXMLEntity();
456
                        zcEntity.putProperty("id", "ZoomConstraints");
457
                        xml.addChild(zcEntity);
458
                }
459
                return xml;
460
        }
461

    
462
        public void setXMLEntity(XMLEntity xml) {
463
                XMLEntity aux = xml.firstChild("id", "LabelingMethod");
464

    
465
                // overlapping mode
466
                if (xml.contains("allowsOverlapping")) {
467
                        allowOverlapping = xml.getBooleanProperty("allowsOverlapping");
468
                }
469
                
470
                // scale visualization
471
                if (xml.contains("minScaleView")) {
472
                        minScaleView = xml.getLongProperty("minScaleView");
473
                }
474
                
475
                if (xml.contains("maxScaleView")) {
476
                        maxScaleView = xml.getLongProperty("maxScaleView");
477
                }
478
                
479
                
480
                if (aux != null) {
481
                        method = LabelingFactory.createMethodFromXML(aux);
482
                }
483
                
484
                aux = xml.firstChild("id", "PlacementConstraints");
485
                if (aux != null) {
486
                        placementConstraints = LabelingFactory.createPlacementConstraintsFromXML(aux);
487
                }
488
                
489
                aux = xml.firstChild("id", "ZoomConstraints");
490
                if (aux != null) {
491
                        zoomConstraints = LabelingFactory.createZoomConstraintsFromXML(aux);
492
                }
493
        }
494

    
495
        public boolean isAllowOverlapping() {
496
                return allowOverlapping;
497
        }
498
        
499
        public void setAllowOverlapping(boolean allowOverlapping) {
500
                this.allowOverlapping = allowOverlapping;
501
        }
502
        
503
        public IPlacementConstraints getPlacementConstraints() {
504
                return placementConstraints;
505
        }
506

    
507
        public void setPlacementConstraints(IPlacementConstraints constraints) {
508
                this.placementConstraints = constraints;
509
        }
510

    
511
        public IZoomConstraints getZoomConstraints() {
512
                return zoomConstraints;
513
        }
514

    
515
        public void setZoomConstraints(IZoomConstraints constraints) {
516
                this.zoomConstraints = constraints;
517
        }
518

    
519
        public void print(Graphics2D g, ViewPort viewPort, Cancellable cancel, PrintRequestAttributeSet properties) throws ReadDriverException {
520

    
521
        }
522
 
523
        public String[] getUsedFields() {
524
                LabelClass[] lcs = method.getLabelClasses();
525
                ArrayList<String> fieldNames = new ArrayList<String>(); 
526
                for (int i = 0; i < lcs.length; i++) {
527
                        String expr = lcs[i].getLabelExpression();
528
                        int start;
529
                        while ((start = expr.indexOf("[")) != -1) {
530
                                int end = expr.indexOf("]"); 
531
                                String field = expr.substring(start+1, end).trim();
532
                                if (!fieldNames.contains(field))
533
                                        fieldNames.add(field);
534
                                expr = expr.substring(end+1, expr.length());
535
                        }
536

    
537
                }
538
                return fieldNames.toArray(new String[fieldNames.size()]);
539
        }
540
        
541
        public boolean shouldDrawLabels(double scale) {
542
                if (minScaleView == -1 && maxScaleView == -1) {
543
                        // parameters not set, so the layer decides.
544
                        return layer.isWithinScale(scale);
545
                }
546
                
547
                if (minScaleView <= scale) {
548
                        return (maxScaleView != -1) ? maxScaleView >= scale : true;
549
                }
550
                
551
                return false;
552
        }
553

    
554
        public long getMaxScaleView() {
555
                return maxScaleView; 
556
        }
557

    
558
        public long getMinScaleView() {
559
                return minScaleView; 
560
        }
561

    
562
        public void setMaxScaleView(long maxScaleView) {
563
                this.maxScaleView = maxScaleView;
564
        }
565
        
566
        public void setMinScaleView(long minScaleView) {
567
                this.minScaleView = minScaleView;
568
        }
569
}