Statistics
| Revision:

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

History | View | Annotate | Download (16.5 KB)

1 20768 jdominguez
/* 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 20905 jdominguez
public class GeneralLabelingStrategy implements ILabelingStrategy, Cloneable {
158 20768 jdominguez
        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 20905 jdominguez
                        boolean bLabelsReallocatable = !isAllowingOverlap();
233 20768 jdominguez
                        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 20905 jdominguez
                if (total>0)
312
                        Logger.getLogger(getClass()).info("Labeled layer '"+layer.getName()+"' "+(System.currentTimeMillis()-t1)/1000D+" seconds. "+placedCount+"/"+total+" labels placed ("+NumberFormat.getInstance().format(100*placedCount/(double) total)+"%)");
313 20768 jdominguez
314
        }
315
316
317
        private int getDuplicateLabelsMode() {
318
                if (getPlacementConstraints() == null) {
319
                        return IPlacementConstraints.DefaultDuplicateLabelsMode;
320
                }
321
                return getPlacementConstraints().getDuplicateLabelsMode();
322
        }
323
324
        private boolean lookupAndPlaceLabel(BufferedImage[] bis, Graphics2D g, ArrayList<LabelLocationMetrics> llm, ILabelPlacement placement, LabelClass lc, IGeometry geom, ViewPort viewPort, Cancellable cancel, boolean bLabelsReallocatable) {
325
                int i;
326
                for (i = 0; !cancel.isCanceled() && i < llm.size(); i++) {
327
                        LabelLocationMetrics labelMetrics = llm.get(i);
328
329
                        if (bLabelsReallocatable) {
330
                                for (int j = 0; j < bis.length; j++) {
331
                                        BufferedImage bi = bis[j];
332
                                        if (!isOverlapping(bi, lc.getShape(labelMetrics))) {
333
                                                lc.draw(g, labelMetrics, null);
334
//                                                SimpleMarkerSymbol sms = new SimpleMarkerSymbol();
335
//                                                sms.setSize(5);
336
//                                                sms.setColor(Color.YELLOW);
337
//                                                sms.draw(g, null, new FPoint2D(labelMetrics.getAnchor()), null);
338
                                                return true;
339
                                        }
340
                                }
341
                        } else {
342
                                lc.draw(g, labelMetrics, null);
343
                                return true;
344
                        }
345
                }
346
                return false;
347
        }
348
349
        @SuppressWarnings("unchecked")
350
        private boolean setupLabel(IFeature feat, LabelClass lc,
351
        Cancellable cancel, String[] usedFields, ViewPort viewPort,
352
        double dpi, int duplicateMode, TreeSet<?> placedLabels) {
353
354
                Value[] vv = feat.getAttributes();
355
                String expr = lc.getLabelExpression();
356
                LabelExpressionParser parser = new LabelExpressionParser(
357
                                new CharArrayReader(expr.toCharArray()));
358
                for (int i = 0; !cancel.isCanceled() && i < usedFields.length; i++) {
359
                        parser.putSymbol(
360
                                        Symbol.createVariableSymbolFromValue(usedFields[i], vv[i]));
361
                }
362
                String[] texts;
363
                try {
364
                        texts = parser.LabelExpression();
365
366
                        if (duplicateMode == IPlacementConstraints.REMOVE_DUPLICATE_LABELS) {
367
                                // check if this text (so label) is already present in the map
368
                                if (placedLabels.contains(texts))
369
                                        // the label has already placed before
370
                                        return false;
371
372
                                /*
373
                                 *  the text is not present, it will be registered for next to
374
                                 *  be avoided
375
                                 */
376
                                ((TreeSet<String[]>) placedLabels).add(texts);
377
                        }
378
                        lc.setTexts(texts);
379
                } catch (ParseException e) {
380
                        e.printStackTrace();
381
                        return false;
382
                }
383
384
                lc.toCartographicSize(viewPort, dpi, null);
385
                return true;
386
        }
387
388
        private boolean isOverlapping(BufferedImage bi, FShape labelShape) {
389
390
                Rectangle2D rPixels = labelShape.getBounds2D();
391
            for (int i= (int) rPixels.getX(); i<rPixels.getMaxX(); i++){
392
                for (int j= (int) rPixels.getY(); j<rPixels.getMaxY(); j++){
393
                        if (!labelShape.contains(i, j)) {
394
                                continue;
395
                        }
396
397
                        if (i<0 || j<0) {
398
                                continue;
399
                        }
400
401
                        if (bi.getWidth()<i+1 || bi.getHeight()<j+1) {
402
                                continue;
403
                        }
404
405
                    if (bi.getRGB(i,j)!=0){
406
                                return true;
407
                    }
408
                }
409
            }
410
            return false;
411
        }
412
413
        private boolean isOnePoint(ViewPort viewPort, IGeometry geom) {
414
                boolean onePoint = false;
415
                int shapeType = geom.getGeometryType();
416
                if (shapeType!=FShape.POINT && shapeType!=FShape.MULTIPOINT) {
417
418
                        Rectangle2D geomBounds = geom.getBounds2D();
419
                        ICoordTrans ct = layer.getCoordTrans();
420
421
                        if (ct!=null) {
422
                                geomBounds = ct.convert(geomBounds);
423
                        }
424
425
                        double dist1Pixel = viewPort.getDist1pixel();
426
                        onePoint = (geomBounds.getWidth() <= dist1Pixel
427
                                        && geomBounds.getHeight() <= dist1Pixel);
428
                }
429
                return onePoint;
430
        }
431
432
        public String getClassName() {
433
                return getClass().getName();
434
        }
435
436
        public XMLEntity getXMLEntity() {
437
                XMLEntity xml = new XMLEntity();
438
                xml.putProperty("className", getClassName());
439
                xml.putProperty("allowOverlapping", allowOverlapping);
440
                xml.putProperty("minScaleView", minScaleView);
441
                xml.putProperty("maxScaleView", maxScaleView);
442
443
                if (method!=null) {
444
                        XMLEntity methodEntity = method.getXMLEntity();
445
                        methodEntity.putProperty("id", "LabelingMethod");
446
                        xml.addChild(methodEntity);
447
                }
448
449
                if (placementConstraints != null) {
450
                        XMLEntity pcEntity = placementConstraints.getXMLEntity();
451
                        pcEntity.putProperty("id", "PlacementConstraints");
452
                        xml.addChild(pcEntity);
453
                }
454
455
                if (zoomConstraints != null) {
456
                        XMLEntity zcEntity = zoomConstraints.getXMLEntity();
457
                        zcEntity.putProperty("id", "ZoomConstraints");
458
                        xml.addChild(zcEntity);
459
                }
460
                return xml;
461
        }
462
463
        public void setXMLEntity(XMLEntity xml) {
464
                XMLEntity aux = xml.firstChild("id", "LabelingMethod");
465
466
                // overlapping mode
467
                if (xml.contains("allowsOverlapping")) {
468
                        allowOverlapping = xml.getBooleanProperty("allowsOverlapping");
469
                }
470
471
                // scale visualization
472
                if (xml.contains("minScaleView")) {
473
                        minScaleView = xml.getLongProperty("minScaleView");
474
                }
475
476
                if (xml.contains("maxScaleView")) {
477
                        maxScaleView = xml.getLongProperty("maxScaleView");
478
                }
479
480
481
                if (aux != null) {
482
                        method = LabelingFactory.createMethodFromXML(aux);
483
                }
484
485
                aux = xml.firstChild("id", "PlacementConstraints");
486
                if (aux != null) {
487
                        placementConstraints = LabelingFactory.createPlacementConstraintsFromXML(aux);
488
                }
489
490
                aux = xml.firstChild("id", "ZoomConstraints");
491
                if (aux != null) {
492
                        zoomConstraints = LabelingFactory.createZoomConstraintsFromXML(aux);
493
                }
494
        }
495
496 20905 jdominguez
        public boolean isAllowingOverlap() {
497 20768 jdominguez
                return allowOverlapping;
498
        }
499
500
        public void setAllowOverlapping(boolean allowOverlapping) {
501
                this.allowOverlapping = allowOverlapping;
502
        }
503
504
        public IPlacementConstraints getPlacementConstraints() {
505
                return placementConstraints;
506
        }
507
508
        public void setPlacementConstraints(IPlacementConstraints constraints) {
509
                this.placementConstraints = constraints;
510
        }
511
512
        public IZoomConstraints getZoomConstraints() {
513
                return zoomConstraints;
514
        }
515
516
        public void setZoomConstraints(IZoomConstraints constraints) {
517
                this.zoomConstraints = constraints;
518
        }
519
520
        public void print(Graphics2D g, ViewPort viewPort, Cancellable cancel, PrintRequestAttributeSet properties) throws ReadDriverException {
521
522
        }
523
524
        public String[] getUsedFields() {
525
                LabelClass[] lcs = method.getLabelClasses();
526
                ArrayList<String> fieldNames = new ArrayList<String>();
527
                for (int i = 0; i < lcs.length; i++) {
528
                        String expr = lcs[i].getLabelExpression();
529
                        int start;
530
                        while ((start = expr.indexOf("[")) != -1) {
531
                                int end = expr.indexOf("]");
532
                                String field = expr.substring(start+1, end).trim();
533
                                if (!fieldNames.contains(field))
534
                                        fieldNames.add(field);
535
                                expr = expr.substring(end+1, expr.length());
536
                        }
537
538
                }
539
                return fieldNames.toArray(new String[fieldNames.size()]);
540
        }
541
542
        public boolean shouldDrawLabels(double scale) {
543
                if (minScaleView == -1 && maxScaleView == -1) {
544
                        // parameters not set, so the layer decides.
545
                        return layer.isWithinScale(scale);
546
                }
547
548
                if (minScaleView <= scale) {
549
                        return (maxScaleView != -1) ? maxScaleView >= scale : true;
550
                }
551
552
                return false;
553
        }
554
555
        public long getMaxScaleView() {
556
                return maxScaleView;
557
        }
558
559
        public long getMinScaleView() {
560
                return minScaleView;
561
        }
562
563
        public void setMaxScaleView(long maxScaleView) {
564
                this.maxScaleView = maxScaleView;
565
        }
566
567
        public void setMinScaleView(long minScaleView) {
568
                this.minScaleView = minScaleView;
569
        }
570
}