Source Code Cross Referenced for LabelCacheDefault.java in  » GIS » GeoTools-2.4.1 » org » geotools » renderer » lite » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » GIS » GeoTools 2.4.1 » org.geotools.renderer.lite 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


0001:        /*
0002:         *    GeoTools - OpenSource mapping toolkit
0003:         *    http://geotools.org
0004:         *    (C) 2005-2006, Geotools Project Managment Committee (PMC)
0005:         *    (C) 2004, Refractions Research Inc.
0006:         *    (c) others
0007:         *
0008:         *    This library is free software; you can redistribute it and/or
0009:         *    modify it under the terms of the GNU Lesser General Public
0010:         *    License as published by the Free Software Foundation;
0011:         *    version 2.1 of the License.
0012:         *
0013:         *    This library is distributed in the hope that it will be useful,
0014:         *    but WITHOUT ANY WARRANTY; without even the implied warranty of
0015:         *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0016:         *    Lesser General Public License for more details.
0017:         */
0018:        package org.geotools.renderer.lite;
0019:
0020:        import java.awt.AlphaComposite;
0021:        import java.awt.BasicStroke;
0022:        import java.awt.Color;
0023:        import java.awt.Composite;
0024:        import java.awt.Graphics2D;
0025:        import java.awt.Paint;
0026:        import java.awt.Rectangle;
0027:        import java.awt.font.FontRenderContext;
0028:        import java.awt.font.GlyphVector;
0029:        import java.awt.geom.AffineTransform;
0030:        import java.awt.geom.Rectangle2D;
0031:        import java.util.ArrayList;
0032:        import java.util.Arrays;
0033:        import java.util.Collection;
0034:        import java.util.Collections;
0035:        import java.util.HashMap;
0036:        import java.util.HashSet;
0037:        import java.util.Hashtable;
0038:        import java.util.Iterator;
0039:        import java.util.List;
0040:        import java.util.Map;
0041:        import java.util.Set;
0042:
0043:        import javax.media.jai.util.Range;
0044:
0045:        import org.geotools.feature.Feature;
0046:        import org.geotools.geometry.jts.Decimator;
0047:        import org.geotools.geometry.jts.LiteShape2;
0048:        import org.geotools.renderer.style.SLDStyleFactory;
0049:        import org.geotools.renderer.style.TextStyle2D;
0050:        import org.geotools.styling.TextSymbolizer;
0051:        import org.opengis.filter.expression.Literal;
0052:
0053:        import com.vividsolutions.jts.geom.Coordinate;
0054:        import com.vividsolutions.jts.geom.CoordinateSequence;
0055:        import com.vividsolutions.jts.geom.Envelope;
0056:        import com.vividsolutions.jts.geom.Geometry;
0057:        import com.vividsolutions.jts.geom.GeometryCollection;
0058:        import com.vividsolutions.jts.geom.GeometryFactory;
0059:        import com.vividsolutions.jts.geom.LineString;
0060:        import com.vividsolutions.jts.geom.LinearRing;
0061:        import com.vividsolutions.jts.geom.MultiLineString;
0062:        import com.vividsolutions.jts.geom.MultiPoint;
0063:        import com.vividsolutions.jts.geom.MultiPolygon;
0064:        import com.vividsolutions.jts.geom.Point;
0065:        import com.vividsolutions.jts.geom.Polygon;
0066:        import com.vividsolutions.jts.operation.linemerge.LineMerger;
0067:        import com.vividsolutions.jts.precision.EnhancedPrecisionOp;
0068:
0069:        /**
0070:         * Default LabelCache Implementation
0071:         *
0072:         * DJB (major changes on May 11th, 2005): 1.The old version of the labeler, if
0073:         * given a *set* of points, lines, or polygons justed labels the first item in
0074:         * the set. The sets are formed when you want to only put a single "Main St" on
0075:         * the map even if you have a bunch of small "Main St" segments.
0076:         *
0077:         * I changed this to be much much wiser.
0078:         *
0079:         * Basically, the new way looks at the set of geometries that its going to put a
0080:         * label on and find the "best" one that represents it. That geometry is then
0081:         * labeled (see below for details on where that label is placed).
0082:         *
0083:         * 2. I changed the actual drawing routines;
0084:         *
0085:         * 1. get the "representative geometry" 2. for points, label as before 3. for
0086:         * lines, find the middle point on the line (old version just averaged start and
0087:         * end points) and centre label on that point (rotated) 4. for polygon, put the
0088:         * label in the middle
0089:         *
0090:         * 3.
0091:         *
0092:         * ie. for lines, try the label at the 1/3, 1/2, and 2/3 location. Metric is how
0093:         * close the label bounding box is to the line.
0094:         *
0095:         * ie. for polygons, bisect the polygon (about the centroid) in to North, South,
0096:         * East and West polygons. Use the location that has the label best inside the
0097:         * polygon.
0098:         *
0099:         * After this is done, you can start doing constraint relaxation...
0100:         *
0101:         * 4. TODO: deal with labels going off the edge of the screen (much reduced
0102:         * now). 5. TODO: add a "minimum quality" parameter (ie. if you're labeling a
0103:         * tiny polygon with a tiny label, dont bother). Metrics are descibed in #3. 6.
0104:         * TODO: add ability for SLD to tweak parameters (ie. "always label").
0105:         *
0106:         *
0107:         * ------------------------------------------------------------------------------------------
0108:         * I've added extra functionality; a) priority -- if you set the <Priority> in a
0109:         * TextSymbolizer, then you can control the order of labelling ** see mailing
0110:         * list for more details b) <VendorOption name="group">no</VendorOption> ---
0111:         * turns off grouping for this symbolizer c) <VendorOption name="spaceAround">5</VendorOption> --
0112:         * do not put labels within 5 pixels of this label.
0113:         *
0114:         * @author jeichar
0115:         * @author dblasby
0116:         * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/render/src/main/java/org/geotools/renderer/lite/LabelCacheDefault.java $
0117:         */
0118:        public final class LabelCacheDefault implements  LabelCache {
0119:
0120:            /**
0121:             * labels that arent this good will not be shown
0122:             */
0123:            public double MIN_GOODNESS_FIT = 0.7;
0124:
0125:            public double DEFAULT_PRIORITY = 1000.0;
0126:
0127:            /** Map<label, LabelCacheItem> the label cache */
0128:            protected Map labelCache = new HashMap();
0129:
0130:            /** non-grouped labels get thrown in here* */
0131:            protected ArrayList labelCacheNonGrouped = new ArrayList();
0132:
0133:            public boolean DEFAULT_GROUP = false; // what to do if there's no grouping option
0134:
0135:            public int DEFAULT_SPACEAROUND = 0;
0136:
0137:            /**
0138:             * When true, the text is rendered as its GlyphVector outline (as a geometry) instead of using
0139:             * drawGlypVector. Pro: labels and halos are perfectly centered, some people prefer the 
0140:             * extra antialiasing obtained. Cons: possibly slower, some people do not like the 
0141:             * extra antialiasing :) 
0142:             */
0143:            protected boolean outlineRenderingEnabled = false;
0144:
0145:            protected SLDStyleFactory styleFactory = new SLDStyleFactory();
0146:            boolean stop = false;
0147:            Set enabledLayers = new HashSet();
0148:            Set activeLayers = new HashSet();
0149:
0150:            LineLengthComparator lineLengthComparator = new LineLengthComparator();
0151:
0152:            private boolean needsOrdering = false;
0153:
0154:            public void stop() {
0155:                stop = true;
0156:                activeLayers.clear();
0157:            }
0158:
0159:            /**
0160:             * @see org.geotools.renderer.lite.LabelCache#start()
0161:             */
0162:            public void start() {
0163:                stop = false;
0164:            }
0165:
0166:            public void clear() {
0167:                if (!activeLayers.isEmpty()) {
0168:                    throw new IllegalStateException(
0169:                            activeLayers
0170:                                    + " are layers that started rendering but have not completed,"
0171:                                    + " stop() or endLayer() must be called before clear is called");
0172:                }
0173:                needsOrdering = true;
0174:                labelCache.clear();
0175:                labelCacheNonGrouped.clear();
0176:                enabledLayers.clear();
0177:            }
0178:
0179:            public void clear(String layerId) {
0180:                if (activeLayers.contains(layerId)) {
0181:                    throw new IllegalStateException(
0182:                            layerId
0183:                                    + " is still rendering, end the layer before calling clear.");
0184:                }
0185:                needsOrdering = true;
0186:
0187:                for (Iterator iter = labelCache.values().iterator(); iter
0188:                        .hasNext();) {
0189:                    LabelCacheItem item = (LabelCacheItem) iter.next();
0190:                    if (item.getLayerIds().contains(layerId))
0191:                        iter.remove();
0192:                }
0193:                for (Iterator iter = labelCacheNonGrouped.iterator(); iter
0194:                        .hasNext();) {
0195:                    LabelCacheItem item = (LabelCacheItem) iter.next();
0196:                    if (item.getLayerIds().contains(layerId))
0197:                        iter.remove();
0198:                }
0199:
0200:                enabledLayers.remove(layerId);
0201:
0202:            }
0203:
0204:            public void disableLayer(String layerId) {
0205:                needsOrdering = true;
0206:                enabledLayers.remove(layerId);
0207:            }
0208:
0209:            /**
0210:             * @see org.geotools.renderer.lite.LabelCache#startLayer()
0211:             */
0212:            public void startLayer(String layerId) {
0213:                enabledLayers.add(layerId);
0214:                activeLayers.add(layerId);
0215:            }
0216:
0217:            /**
0218:             * get the priority from the symbolizer its an expression, so it will try to
0219:             * evaluate it: 1. if its missing --> DEFAULT_PRIORITY 2. if its a number,
0220:             * return that number 3. if its not a number, convert to string and try to
0221:             * parse the number; return the number 4. otherwise, return DEFAULT_PRIORITY
0222:             *
0223:             * @param symbolizer
0224:             * @param feature
0225:             */
0226:            public double getPriority(TextSymbolizer symbolizer, Feature feature) {
0227:                if (symbolizer.getPriority() == null)
0228:                    return DEFAULT_PRIORITY;
0229:
0230:                // evaluate
0231:                try {
0232:                    Double number = (Double) symbolizer.getPriority().evaluate(
0233:                            feature, Double.class);
0234:                    return number.doubleValue();
0235:                } catch (Exception e) {
0236:                    return DEFAULT_PRIORITY;
0237:                }
0238:            }
0239:
0240:            /**
0241:             * @see org.geotools.renderer.lite.LabelCache#put(org.geotools.renderer.style.TextStyle2D,
0242:             *      org.geotools.renderer.lite.LiteShape)
0243:             */
0244:            public void put(String layerId, TextSymbolizer symbolizer,
0245:                    Feature feature, LiteShape2 shape, Range scaleRange) {
0246:                needsOrdering = true;
0247:                try {
0248:                    //get label and geometry				
0249:                    String label = (String) symbolizer.getLabel().evaluate(
0250:                            feature, String.class);
0251:
0252:                    if (label == null)
0253:                        return;
0254:
0255:                    label = label.trim();
0256:                    if (label.length() == 0) {
0257:                        return; // dont label something with nothing!
0258:                    }
0259:                    double priorityValue = getPriority(symbolizer, feature);
0260:                    boolean group = isGrouping(symbolizer);
0261:                    if (!(group)) {
0262:                        TextStyle2D textStyle = (TextStyle2D) styleFactory
0263:                                .createStyle(feature, symbolizer, scaleRange);
0264:
0265:                        LabelCacheItem item = new LabelCacheItem(layerId,
0266:                                textStyle, shape, label);
0267:                        item.setPriority(priorityValue);
0268:                        item.setSpaceAround(getSpaceAround(symbolizer));
0269:                        labelCacheNonGrouped.add(item);
0270:                    } else { // / --------- grouping case ----------------
0271:
0272:                        // equals and hashcode of LabelCacheItem is the hashcode of
0273:                        // label and the
0274:                        // equals of the 2 labels so label can be used to find the
0275:                        // entry.
0276:
0277:                        // DJB: this is where the "grouping" of 'same label' features
0278:                        // occurs
0279:                        LabelCacheItem lci = (LabelCacheItem) labelCache
0280:                                .get(label);
0281:                        if (lci == null) // nothing in there yet!
0282:                        {
0283:                            TextStyle2D textStyle = (TextStyle2D) styleFactory
0284:                                    .createStyle(feature, symbolizer,
0285:                                            scaleRange);
0286:                            LabelCacheItem item = new LabelCacheItem(layerId,
0287:                                    textStyle, shape, label);
0288:                            item.setPriority(priorityValue);
0289:                            item.setSpaceAround(getSpaceAround(symbolizer));
0290:                            labelCache.put(label, item);
0291:                        } else {
0292:                            // add only in the non-default case or non-literal. Ie.
0293:                            // area()
0294:                            if ((symbolizer.getPriority() != null)
0295:                                    && (!(symbolizer.getPriority() instanceof  Literal)))
0296:                                lci.setPriority(lci.getPriority()
0297:                                        + priorityValue); // djb--
0298:                            // changed
0299:                            // because
0300:                            // you
0301:                            // do
0302:                            // not
0303:                            // always
0304:                            // want
0305:                            // to
0306:                            // add!
0307:
0308:                            lci.getGeoms().add(shape.getGeometry());
0309:                        }
0310:                    }
0311:                } catch (Exception e) // DJB: protection if there's a problem with the
0312:                // decimation (getGeometry() can be null)
0313:                {
0314:                    // do nothing
0315:                }
0316:            }
0317:
0318:            /**
0319:             * pull space around from the sybolizer options - defaults to
0320:             * DEFAULT_SPACEAROUND.
0321:             *
0322:             * <0 means "I can overlap other labels" be careful with this.
0323:             *
0324:             * @param symbolizer
0325:             */
0326:            private int getSpaceAround(TextSymbolizer symbolizer) {
0327:                String value = symbolizer.getOption("spaceAround");
0328:                if (value == null)
0329:                    return DEFAULT_SPACEAROUND;
0330:                try {
0331:                    return Integer.parseInt(value);
0332:                } catch (Exception e) {
0333:                    return DEFAULT_SPACEAROUND;
0334:                }
0335:            }
0336:
0337:            /**
0338:             * look at the options in the symbolizer for "group". return its value if
0339:             * not present, return "DEFAULT_GROUP"
0340:             *
0341:             * @param symbolizer
0342:             */
0343:            private boolean isGrouping(TextSymbolizer symbolizer) {
0344:                String value = symbolizer.getOption("group");
0345:                if (value == null)
0346:                    return DEFAULT_GROUP;
0347:                return value.equalsIgnoreCase("yes")
0348:                        || value.equalsIgnoreCase("true")
0349:                        || value.equalsIgnoreCase("1");
0350:            }
0351:
0352:            /**
0353:             * @see org.geotools.renderer.lite.LabelCache#endLayer(java.awt.Graphics2D,
0354:             *      java.awt.Rectangle)
0355:             */
0356:            public void endLayer(String layerId, Graphics2D graphics,
0357:                    Rectangle displayArea) {
0358:                activeLayers.remove(layerId);
0359:            }
0360:
0361:            /**
0362:             * return a list with all the values in priority order. Both grouped and
0363:             * non-grouped
0364:             *
0365:             *
0366:             */
0367:            public List orderedLabels() {
0368:                ArrayList al = getActiveLabels();
0369:
0370:                Collections.sort(al);
0371:                Collections.reverse(al);
0372:                return al;
0373:            }
0374:
0375:            private ArrayList getActiveLabels() {
0376:                Collection c = labelCache.values();
0377:                ArrayList al = new ArrayList(); // modifiable (ie. sortable)
0378:                for (Iterator iter = c.iterator(); iter.hasNext();) {
0379:                    LabelCacheItem item = (LabelCacheItem) iter.next();
0380:                    if (isActive(item.getLayerIds()))
0381:                        al.add(item);
0382:                }
0383:
0384:                for (Iterator iter = labelCacheNonGrouped.iterator(); iter
0385:                        .hasNext();) {
0386:                    LabelCacheItem item = (LabelCacheItem) iter.next();
0387:                    if (isActive(item.getLayerIds()))
0388:                        al.add(item);
0389:                }
0390:                return al;
0391:            }
0392:
0393:            private boolean isActive(Set layerIds) {
0394:                for (Iterator iter = layerIds.iterator(); iter.hasNext();) {
0395:                    String string = (String) iter.next();
0396:                    if (enabledLayers.contains(string))
0397:                        return true;
0398:
0399:                }
0400:                return false;
0401:            }
0402:
0403:            /**
0404:             * @see org.geotools.renderer.lite.LabelCache#end(java.awt.Graphics2D,
0405:             *      java.awt.Rectangle)
0406:             */
0407:            public void end(Graphics2D graphics, Rectangle displayArea) {
0408:                if (!activeLayers.isEmpty()) {
0409:                    throw new IllegalStateException(
0410:                            activeLayers
0411:                                    + " are layers that started rendering but have not completed,"
0412:                                    + " stop() or endLayer() must be called before end() is called");
0413:                }
0414:                List glyphs = new ArrayList();
0415:
0416:                // Hack: let's reduce the display area width and height by one pixel.
0417:                // If the rendered image is 256x256, proper rendering of polygons and
0418:                // lines occurr only if the display area is [0,0; 256,256], yet if you
0419:                // try to render anything at [x,256] or [256,y] it won't show.
0420:                // So, to avoid labels that happen to touch the border being cut
0421:                // by one pixel, we reduce the display area. 
0422:                // Feels hackish, don't have a better solution at the moment thought
0423:                displayArea = new Rectangle(displayArea);
0424:                displayArea.width -= 1;
0425:                displayArea.height -= 1;
0426:
0427:                GeometryFactory factory = new GeometryFactory();
0428:                Geometry displayGeom = factory.toGeometry(new Envelope(
0429:                        displayArea.getMinX(), displayArea.getMaxX(),
0430:                        displayArea.getMinY(), displayArea.getMaxY()));
0431:
0432:                List items; // both grouped and non-grouped
0433:                if (needsOrdering) {
0434:                    items = orderedLabels();
0435:                } else {
0436:                    items = getActiveLabels();
0437:                }
0438:                for (Iterator labelIter = items.iterator(); labelIter.hasNext();) {
0439:                    if (stop)
0440:                        return;
0441:                    try {
0442:                        // LabelCacheItem labelItem = (LabelCacheItem)
0443:                        // labelCache.get(labelIter.next());
0444:                        LabelCacheItem labelItem = (LabelCacheItem) labelIter
0445:                                .next();
0446:                        labelItem.getTextStyle().setLabel(labelItem.getLabel());
0447:                        GlyphVector glyphVector = labelItem.getTextStyle()
0448:                                .getTextGlyphVector(graphics);
0449:
0450:                        // DJB: simplified this. Just send off to the point,line,or
0451:                        // polygon routine
0452:                        // NOTE: labelItem.getGeometry() returns the FIRST geometry, so
0453:                        // we're assuming that lines & points arent mixed
0454:                        // If they are, then the FIRST geometry determines how its
0455:                        // rendered (which is probably bad since it should be in
0456:                        // area,line,point order
0457:                        // TOD: as in NOTE above
0458:
0459:                        Geometry geom = labelItem.getGeometry();
0460:
0461:                        AffineTransform oldTransform = graphics.getTransform();
0462:                        /*
0463:                         * Just use identity for tempTransform because display area is 0,0,width,height
0464:                         * and oldTransform may have a different origin. OldTransform will be used later
0465:                         * for drawing.
0466:                         * -rg & je
0467:                         */
0468:                        AffineTransform tempTransform = new AffineTransform();
0469:
0470:                        Geometry representativeGeom = null;
0471:
0472:                        if ((geom instanceof  Point)
0473:                                || (geom instanceof  MultiPoint))
0474:                            representativeGeom = paintPointLabel(glyphVector,
0475:                                    labelItem, tempTransform, displayGeom);
0476:                        else if (((geom instanceof  LineString) && !(geom instanceof  LinearRing))
0477:                                || (geom instanceof  MultiLineString))
0478:                            representativeGeom = paintLineLabel(glyphVector,
0479:                                    labelItem, tempTransform, displayGeom);
0480:                        else if (geom instanceof  Polygon
0481:                                || geom instanceof  MultiPolygon
0482:                                || geom instanceof  LinearRing)
0483:                            representativeGeom = paintPolygonLabel(glyphVector,
0484:                                    labelItem, tempTransform, displayGeom);
0485:
0486:                        // DJB: this is where overlapping labels are forbidden (first
0487:                        // out of the map has priority)
0488:                        Rectangle glyphBounds = glyphVector.getPixelBounds(
0489:                                null, 0, 0);
0490:
0491:                        glyphBounds = tempTransform.createTransformedShape(
0492:                                glyphBounds).getBounds();
0493:
0494:                        // is  this offscreen? We assume offscreen as anything that is outside
0495:                        // or crosses the rendering borders, since in tiled rendering
0496:                        // we have to insulate ourself from other tiles
0497:                        if (!(displayArea.contains(glyphBounds)))
0498:                            continue;
0499:
0500:                        // we wind up using the translated shield location a number of
0501:                        // times, in overlap calculations, offscreen
0502:                        // calculations, etc. Let's just pre-calculate it here, as we do
0503:                        // the offscreen calculation.
0504:                        Rectangle2D shieldBounds = null;
0505:                        if (labelItem.getTextStyle().getGraphic() != null) {
0506:                            Rectangle area = labelItem.getTextStyle()
0507:                                    .getGraphicDimensions();
0508:                            Rectangle untransformedBounds = glyphVector
0509:                                    .getPixelBounds(
0510:                                            new FontRenderContext(
0511:                                                    new AffineTransform(),
0512:                                                    true, false), 0, 0);
0513:                            // center the graphics on the labels back
0514:                            double[] shieldVerts = new double[] {
0515:                                    -area.width / 2 + untransformedBounds.x
0516:                                            - untransformedBounds.width / 2,
0517:                                    -area.height / 2 + untransformedBounds.y
0518:                                            - untransformedBounds.height / 2,
0519:                                    area.width / 2, area.height / 2 };
0520:                            // transform to rendered space
0521:                            tempTransform.transform(shieldVerts, 0,
0522:                                    shieldVerts, 0, 2);
0523:                            shieldBounds = new Rectangle2D.Double(
0524:                                    shieldVerts[0] + glyphBounds.width / 2,
0525:                                    shieldVerts[1] + glyphBounds.height / 2,
0526:                                    shieldVerts[2] - shieldVerts[0],
0527:                                    shieldVerts[3] - shieldVerts[1]);
0528:                            // if glyph is only partially outside of the display area, don't render it
0529:                            // for the same req
0530:                            if (!displayArea.contains(shieldBounds))
0531:                                continue;
0532:                        }
0533:
0534:                        // take into account radius so that halo do not overwrite other labels
0535:                        // that are too close to the current one
0536:                        int space = labelItem.getSpaceAround();
0537:                        int haloRadius = Math.round(labelItem.getTextStyle()
0538:                                .getHaloFill() != null ? labelItem
0539:                                .getTextStyle().getHaloRadius() : 0);
0540:                        if (space >= 0) // if <0 then its okay to have overlapping items
0541:                        {
0542:                            if (overlappingItems(glyphBounds, glyphs, space
0543:                                    + haloRadius))
0544:                                continue;
0545:                            if (shieldBounds != null
0546:                                    && overlappingItems(shieldBounds
0547:                                            .getBounds(), glyphs, space))
0548:                                continue;
0549:                        }
0550:
0551:                        if (goodnessOfFit(glyphVector, tempTransform,
0552:                                representativeGeom) < MIN_GOODNESS_FIT)
0553:                            continue;
0554:
0555:                        try {
0556:                            /*
0557:                             * Merge the tempTransform with the transform provided by graphics. This is the
0558:                             * proper transform that should be used for drawing.
0559:                             * -je & rg
0560:                             */
0561:                            AffineTransform newTransform = new AffineTransform(
0562:                                    oldTransform);
0563:                            newTransform.concatenate(tempTransform);
0564:                            graphics.setTransform(newTransform);
0565:
0566:                            if (labelItem.getTextStyle().getGraphic() != null) {
0567:
0568:                                // draw the label shield first, underneath the halo
0569:                                LiteShape2 tempShape = new LiteShape2(
0570:                                        new GeometryFactory()
0571:                                                .createPoint(new Coordinate(
0572:                                                        glyphBounds.width / 2.0,
0573:                                                        -1.0
0574:                                                                * glyphBounds.height
0575:                                                                / 2.0)), null,
0576:                                        null, false, false);
0577:
0578:                                // labels should always draw, so we'll just force this
0579:                                // one to draw by setting it's min/max scale to 0<10 and
0580:                                // then
0581:                                // drawing at scale 5.0 on the next line
0582:                                labelItem.getTextStyle().getGraphic()
0583:                                        .setMinMaxScale(0.0, 10.0);
0584:                                new StyledShapePainter(this ).paint(graphics,
0585:                                        tempShape, labelItem.getTextStyle()
0586:                                                .getGraphic(), 5.0);
0587:                                graphics.setTransform(tempTransform);
0588:                            }
0589:
0590:                            java.awt.Shape outline = glyphVector.getOutline();
0591:                            if (labelItem.getTextStyle().getHaloFill() != null) {
0592:                                graphics.setPaint(labelItem.getTextStyle()
0593:                                        .getHaloFill());
0594:                                graphics.setComposite(labelItem.getTextStyle()
0595:                                        .getHaloComposite());
0596:
0597:                                graphics.setStroke(new BasicStroke(
0598:                                        2f * haloRadius, BasicStroke.CAP_ROUND,
0599:                                        BasicStroke.JOIN_ROUND));
0600:                                graphics.draw(outline);
0601:                            }
0602:                            // DJB: added this because several people were using
0603:                            // "font-color" instead of fill
0604:                            // It legal to have a label w/o fill (which means dont
0605:                            // render it)
0606:                            // This causes people no end of trouble.
0607:                            // If they dont want to colour it, then they should use a
0608:                            // filter
0609:                            // DEFAULT (no <Fill>) --> BLACK
0610:                            // NOTE: re-reading the spec says this is the correct
0611:                            // assumption.
0612:                            Paint fill = labelItem.getTextStyle().getFill();
0613:                            Composite comp = labelItem.getTextStyle()
0614:                                    .getComposite();
0615:                            if (fill == null) {
0616:                                fill = Color.BLACK;
0617:                                comp = AlphaComposite.getInstance(
0618:                                        AlphaComposite.SRC_OVER, 1.0f); // 100% opaque
0619:                            }
0620:                            if (fill != null) {
0621:                                graphics.setPaint(fill);
0622:                                graphics.setComposite(comp);
0623:                                if (outlineRenderingEnabled)
0624:                                    graphics.fill(outline);
0625:                                else
0626:                                    graphics.drawGlyphVector(glyphVector, 0, 0);
0627:                                Rectangle bounds = glyphVector.getPixelBounds(
0628:                                        new FontRenderContext(tempTransform,
0629:                                                true, false), 0, 0);
0630:                                int extraSpace = labelItem.getSpaceAround();
0631:                                if (extraSpace >= 0) // if <0 then we dont record
0632:                                // (something can overwrite it)
0633:                                {
0634:                                    bounds = new Rectangle(bounds.x
0635:                                            - extraSpace,
0636:                                            bounds.y - extraSpace, bounds.width
0637:                                                    + extraSpace, bounds.height
0638:                                                    + extraSpace);
0639:                                    if ((shieldBounds != null)) {
0640:                                        bounds.add(shieldBounds);
0641:                                    }
0642:                                    bounds.grow(haloRadius, haloRadius);
0643:                                    glyphs.add(bounds);
0644:                                }
0645:                            }
0646:                        } finally {
0647:                            graphics.setTransform(oldTransform);
0648:                        }
0649:                    } catch (Exception e) {
0650:                        // the decimation can cause problems - we
0651:                        // try to minimize it
0652:                        // do nothing
0653:                    }
0654:                }
0655:            }
0656:
0657:            /**
0658:             * how well does the label "fit" with the geometry. 1. points ALWAYS RETURNS
0659:             * 1.0 2. lines ALWAYS RETURNS 1.0 (modify polygon method to handle rotated
0660:             * labels) 3. polygon + assume: polylabels are unrotated + assume: polygon
0661:             * could be invalid + dont worry about holes
0662:             *
0663:             * like to RETURN area of intersection between polygon and label bounds, but
0664:             * thats expensive and likely to give us problems due to invalid polygons
0665:             * SO, use a sample method - make a few points inside the label and see if
0666:             * they're "close to" the polygon The method sucks, but works well...
0667:             *
0668:             * @param glyphVector
0669:             * @param tempTransform
0670:             * @param representativeGeom
0671:             */
0672:            private double goodnessOfFit(GlyphVector glyphVector,
0673:                    AffineTransform tempTransform, Geometry representativeGeom) {
0674:                if (representativeGeom instanceof  Point) {
0675:                    return 1.0;
0676:                }
0677:                if (representativeGeom instanceof  LineString) {
0678:                    return 1.0;
0679:                }
0680:                if (representativeGeom instanceof  Polygon) {
0681:                    Rectangle glyphBounds = glyphVector.getPixelBounds(
0682:                            new FontRenderContext(tempTransform, true, false),
0683:                            0, 0);
0684:                    try {
0685:                        Polygon p = simplifyPoly((Polygon) representativeGeom);
0686:                        int count = 0;
0687:                        int n = 10;
0688:                        double mindistance = (glyphBounds.height);
0689:                        for (int t = 1; t < (n + 1); t++) {
0690:                            Coordinate c = new Coordinate(glyphBounds.x
0691:                                    + ((double) glyphBounds.width)
0692:                                    * (((double) t) / (n + 1)), glyphBounds
0693:                                    .getCenterY());
0694:                            Point pp = new Point(c, representativeGeom
0695:                                    .getPrecisionModel(), representativeGeom
0696:                                    .getSRID());
0697:                            if (p.distance(pp) < mindistance)
0698:
0699:                            {
0700:                                count++;
0701:                            }
0702:                        }
0703:                        return ((double) count) / n;
0704:                    } catch (Exception e) {
0705:                        representativeGeom.geometryChanged(); // djb -- jessie should
0706:                        // do this during
0707:                        // generalization
0708:                        Envelope ePoly = representativeGeom
0709:                                .getEnvelopeInternal();
0710:                        Envelope eglyph = new Envelope(glyphBounds.x,
0711:                                glyphBounds.x + glyphBounds.width,
0712:                                glyphBounds.y, glyphBounds.y
0713:                                        + glyphBounds.height);
0714:                        Envelope inter = intersection(ePoly, eglyph);
0715:                        if (inter != null)
0716:                            return (inter.getWidth() * inter.getHeight())
0717:                                    / (eglyph.getWidth() * eglyph.getHeight());
0718:                        return 0.0;
0719:                    }
0720:                }
0721:                return 0.0;
0722:            }
0723:
0724:            /**
0725:             * Remove holes from a polygon
0726:             *
0727:             * @param polygon
0728:             */
0729:            private Polygon simplifyPoly(Polygon polygon) {
0730:                if (polygon.getNumInteriorRing() == 0)
0731:                    return polygon;
0732:
0733:                LineString outer = polygon.getExteriorRing();
0734:                if (outer.getStartPoint().distance(outer.getEndPoint()) != 0) {
0735:                    List clist = new ArrayList(Arrays.asList(outer
0736:                            .getCoordinates()));
0737:                    clist.add(outer.getStartPoint().getCoordinate());
0738:                    outer = outer.getFactory().createLinearRing(
0739:                            (Coordinate[]) clist.toArray(new Coordinate[clist
0740:                                    .size()]));
0741:                }
0742:                LinearRing r = (LinearRing) outer;
0743:
0744:                return outer.getFactory().createPolygon(r, null);
0745:            }
0746:
0747:            /**
0748:             * Determines whether labelItems overlaps a previously rendered label.
0749:             *
0750:             * @param glyphs
0751:             *            list of bounds of previously rendered glyphs/shields.
0752:             * @param bounds
0753:             *            new rectangle to check
0754:             * @param extraSpace
0755:             *            extra space added to edges of bounds during check
0756:             * @return true if labelItem overlaps a previously rendered glyph.
0757:             */
0758:            private boolean overlappingItems(Rectangle bounds, List glyphs,
0759:                    int extraSpace) {
0760:                bounds = new Rectangle(bounds.x - extraSpace, bounds.y
0761:                        - extraSpace, bounds.width + extraSpace, bounds.height
0762:                        + extraSpace);
0763:                Rectangle oldBounds;
0764:                for (Iterator iter = glyphs.iterator(); iter.hasNext();) {
0765:                    oldBounds = (Rectangle) iter.next();
0766:                    if (oldBounds.intersects(bounds))
0767:                        return true;
0768:                }
0769:                return false;
0770:            }
0771:
0772:            private Geometry paintLineLabel(GlyphVector glyphVector,
0773:                    LabelCacheItem labelItem, AffineTransform tempTransform,
0774:                    Geometry displayGeom) {
0775:                LineString line = (LineString) getLineSetRepresentativeLocation(
0776:                        labelItem.getGeoms(), displayGeom);
0777:
0778:                if (line == null)
0779:                    return null;
0780:
0781:                TextStyle2D textStyle = labelItem.getTextStyle();
0782:
0783:                paintLineStringLabel(glyphVector, line, textStyle,
0784:                        tempTransform);
0785:                return line;
0786:            }
0787:
0788:            /**
0789:             * This handles point and line placement.
0790:             *
0791:             * 1. lineplacement -- calculate a rotation and location (and does the perp
0792:             * offset) 2. pointplacement -- reduce line to a point and ignore the
0793:             * calculated rotation
0794:             *
0795:             * @param glyphVector
0796:             * @param line
0797:             * @param textStyle
0798:             * @param tempTransform
0799:             */
0800:            private void paintLineStringLabel(GlyphVector glyphVector,
0801:                    LineString line, TextStyle2D textStyle,
0802:                    AffineTransform tempTransform) {
0803:                //Point start = line.getStartPoint();
0804:                //Point end = line.getEndPoint();
0805:                //double dx = end.getX() - start.getX();
0806:                //double dy = end.getY() - start.getY();
0807:                //double slope = dy / dx;
0808:                //double theta = Math.atan(slope);
0809:                // double rotation=theta;
0810:
0811:                Rectangle2D textBounds = glyphVector.getVisualBounds();
0812:                Point centroid = middleLine(line, 0.5); // DJB: changed from centroid to
0813:                // "middle point" -- see
0814:                // middleLine() dox
0815:                // DJB: this is also where you could do "voting" and looking at other
0816:                // locations on the line to label (ie. 0.33,0.66)
0817:                tempTransform.translate(centroid.getX(), centroid.getY());
0818:                double displacementX = 0;
0819:                double displacementY = 0;
0820:
0821:                // DJB: this now does "centering"
0822:                // displacementX = (textStyle.getAnchorX() +
0823:                // (-textBounds.getWidth()/2.0))
0824:                // + textStyle.getDisplacementX();
0825:                // displacementY = (textStyle.getAnchorY() +
0826:                // (textBounds.getHeight()/2.0))
0827:                // - textStyle.getDisplacementY();
0828:
0829:                double anchorX = textStyle.getAnchorX();
0830:                double anchorY = textStyle.getAnchorY();
0831:
0832:                // undo the above if its point placement!
0833:                double rotation;
0834:                if (textStyle.isPointPlacement()) {
0835:                    rotation = textStyle.getRotation(); // use the one the user
0836:                    // supplied!
0837:                } else // lineplacement
0838:                {
0839:                    rotation = middleTheta(line, 0.5);
0840:                    displacementY -= textStyle.getPerpendicularOffset(); // move it
0841:                    // off the
0842:                    // line
0843:                    anchorX = 0.5; // centered
0844:                    anchorY = 0.5; // centered, sitting on line
0845:                }
0846:
0847:                displacementX = (anchorX * (-textBounds.getWidth()))
0848:                        + textStyle.getDisplacementX();
0849:                displacementY += (anchorY * (textBounds.getHeight()))
0850:                        - textStyle.getDisplacementY();
0851:
0852:                if (rotation != rotation) // IEEE def'n x=x for all x except when x is
0853:                    // NaN
0854:                    rotation = 0.0;
0855:                if (Double.isInfinite(rotation))
0856:                    rotation = 0; // weird number
0857:                tempTransform.rotate(rotation);
0858:                tempTransform.translate(displacementX, displacementY);
0859:            }
0860:
0861:            /**
0862:             * Simple to paint a point (or set of points) Just choose the first one and
0863:             * paint it!
0864:             *
0865:             */
0866:            private Geometry paintPointLabel(GlyphVector glyphVector,
0867:                    LabelCacheItem labelItem, AffineTransform tempTransform,
0868:                    Geometry displayGeom) {
0869:                // get the point onto the shape has to be painted
0870:                Point point = getPointSetRepresentativeLocation(labelItem
0871:                        .getGeoms(), displayGeom);
0872:                if (point == null)
0873:                    return null;
0874:
0875:                TextStyle2D textStyle = labelItem.getTextStyle();
0876:                Rectangle2D textBounds = glyphVector.getVisualBounds();
0877:                tempTransform.translate(point.getX(), point.getY());
0878:                double displacementX = 0;
0879:                double displacementY = 0;
0880:
0881:                // DJB: this probably isnt doing what you think its doing - see others
0882:                displacementX = (textStyle.getAnchorX() * (-textBounds
0883:                        .getWidth()))
0884:                        + textStyle.getDisplacementX();
0885:                displacementY = (textStyle.getAnchorY() * (textBounds
0886:                        .getHeight()))
0887:                        - textStyle.getDisplacementY();
0888:
0889:                if (!textStyle.isPointPlacement()) {
0890:                    // lineplacement. We're cheating here, since we cannot line label a
0891:                    // point
0892:                    displacementY -= textStyle.getPerpendicularOffset(); // just move
0893:                    // it up
0894:                    // (yes, its
0895:                    // cheating)
0896:                }
0897:
0898:                double rotation = textStyle.getRotation();
0899:                if (rotation != rotation) // IEEE def'n x=x for all x except when x is
0900:                    // NaN
0901:                    rotation = 0.0;
0902:                if (Double.isInfinite(rotation))
0903:                    rotation = 0; // weird number
0904:
0905:                tempTransform.rotate(rotation);
0906:                tempTransform.translate(displacementX, displacementY);
0907:                return point;
0908:            }
0909:
0910:            /**
0911:             * returns the representative geometry (for further processing)
0912:             *
0913:             * TODO: handle lineplacement for a polygon (perhaps we're supposed to grab
0914:             * the outside line and label it, but spec is unclear)
0915:             */
0916:            private Geometry paintPolygonLabel(GlyphVector glyphVector,
0917:                    LabelCacheItem labelItem, AffineTransform tempTransform,
0918:                    Geometry displayGeom) {
0919:                Polygon geom = getPolySetRepresentativeLocation(labelItem
0920:                        .getGeoms(), displayGeom);
0921:                if (geom == null)
0922:                    return null;
0923:
0924:                Point centroid;
0925:
0926:                try {
0927:                    centroid = geom.getCentroid(); // this where you would do the
0928:                    // north/south/west/east stuff
0929:                } catch (Exception e) // generalized polygons causes problems - this
0930:                // tries to hid them.
0931:                {
0932:                    try {
0933:                        centroid = geom.getExteriorRing().getCentroid();
0934:                    } catch (Exception ee) {
0935:                        try {
0936:                            centroid = geom.getFactory().createPoint(
0937:                                    geom.getCoordinate());
0938:                        } catch (Exception eee) {
0939:                            return null; // we're hooped
0940:                        }
0941:                    }
0942:                }
0943:
0944:                TextStyle2D textStyle = labelItem.getTextStyle();
0945:                Rectangle2D textBounds = glyphVector.getVisualBounds();
0946:                tempTransform.translate(centroid.getX(), centroid.getY());
0947:                double displacementX = 0;
0948:                double displacementY = 0;
0949:
0950:                // DJB: this now does "centering"
0951:                displacementX = (textStyle.getAnchorX() * (-textBounds
0952:                        .getWidth()))
0953:                        + textStyle.getDisplacementX();
0954:                displacementY = (textStyle.getAnchorY() * (textBounds
0955:                        .getHeight()))
0956:                        - textStyle.getDisplacementY();
0957:
0958:                if (!textStyle.isPointPlacement()) {
0959:                    // lineplacement. We're cheating here, since we've reduced the
0960:                    // polygon to a point, when we should be trying to do something
0961:                    // a little smarter (like find its median axis!)
0962:                    displacementY -= textStyle.getPerpendicularOffset(); // just move
0963:                    // it up
0964:                    // (yes, its
0965:                    // cheating)
0966:                }
0967:
0968:                double rotation = textStyle.getRotation();
0969:                if (rotation != rotation) // IEEE def'n x=x for all x except when x is
0970:                    // NaN
0971:                    rotation = 0.0;
0972:                if (Double.isInfinite(rotation))
0973:                    rotation = 0; // weird number
0974:
0975:                tempTransform.rotate(rotation);
0976:                tempTransform.translate(displacementX, displacementY);
0977:                return geom;
0978:            }
0979:
0980:            /**
0981:             *
0982:             * 1. get a list of points from the input geometries that are inside the
0983:             * displayGeom NOTE: lines and polygons are reduced to their centroids (you
0984:             * shouldnt really calling this with lines and polys) 2. choose the most
0985:             * "central" of the points METRIC - choose anyone TODO: change metric to be
0986:             * "closest to the centoid of the possible points"
0987:             *
0988:             * @param geoms
0989:             *            list of Point or MultiPoint (any other geometry types are
0990:             *            rejected
0991:             * @param displayGeometry
0992:             * @return a point or null (if there's nothing to draw)
0993:             */
0994:            Point getPointSetRepresentativeLocation(List geoms,
0995:                    Geometry displayGeometry) {
0996:                ArrayList pts = new ArrayList(); // points that are inside the
0997:                // displayGeometry
0998:
0999:                Iterator it = geoms.iterator();
1000:                Geometry g;
1001:                while (it.hasNext()) {
1002:                    g = (Geometry) it.next();
1003:                    if (!((g instanceof  Point) || (g instanceof  MultiPoint))) // handle
1004:                        // lines,polys,
1005:                        // gc,
1006:                        // etc..
1007:                        g = g.getCentroid(); // will be point
1008:                    if (g instanceof  Point) {
1009:                        if (displayGeometry.intersects(g)) // this is robust!
1010:                            pts.add(g); // possible label location
1011:                    } else if (g instanceof  MultiPoint) {
1012:                        for (int t = 0; t < g.getNumGeometries(); t++) {
1013:                            Point gg = (Point) g.getGeometryN(t);
1014:                            if (displayGeometry.intersects(gg))
1015:                                pts.add(gg); // possible label location
1016:                        }
1017:                    }
1018:                }
1019:                if (pts.size() == 0)
1020:                    return null;
1021:
1022:                // do better metric than this:
1023:                return (Point) pts.get(0);
1024:            }
1025:
1026:            /**
1027:             * 1. make a list of all the geoms (not clipped) NOTE: reject points,
1028:             * convert polygons to their exterior ring (you shouldnt be calling this
1029:             * function with points and polys) 2. join the lines together 3. clip
1030:             * resulting lines to display geometry 4. return longest line
1031:             *
1032:             * NOTE: the joining has multiple solution. For example, consider a Y (3
1033:             * lines): * * 1 2 * * * 3 * solutions are: 1->2 and 3 1->3 and 2 2->3 and 1
1034:             *
1035:             * (see mergeLines() below for detail of the algorithm; its basically a
1036:             * greedy algorithm that should form the 'longest' possible route through
1037:             * the linework)
1038:             *
1039:             * NOTE: we clip after joining because there could be connections "going on"
1040:             * outside the display bbox
1041:             *
1042:             *
1043:             * @param geoms
1044:             * @param displayGeometry
1045:             *            must be poly
1046:             */
1047:            LineString getLineSetRepresentativeLocation(List geoms,
1048:                    Geometry displayGeometry) {
1049:                ArrayList lines = new ArrayList(); // points that are inside the
1050:                // displayGeometry
1051:
1052:                Iterator it = geoms.iterator();
1053:                Geometry g;
1054:                // go through each geometry in the set.
1055:                // if its a polygon or multipolygon, get the boundary (reduce to a line)
1056:                // if its a line, add it to "lines"
1057:                // if its a multiline, add each component line to "lines"
1058:                while (it.hasNext()) {
1059:                    g = (Geometry) it.next();
1060:                    if (!((g instanceof  LineString)
1061:                            || (g instanceof  MultiLineString)
1062:                            || (g instanceof  Polygon) || (g instanceof  MultiPolygon)))
1063:                        continue;
1064:
1065:                    if ((g instanceof  Polygon) || (g instanceof  MultiPolygon)) {
1066:                        g = g.getBoundary(); // line or multiline m
1067:                        // TODO: boundary included the inside rings, might want to
1068:                        // replace this with getExteriorRing()
1069:                        if (!((g instanceof  LineString) || (g instanceof  MultiLineString)))
1070:                            continue; // protection
1071:                    } else if (g instanceof  LineString) {
1072:                        if (g.getLength() != 0)
1073:                            lines.add(g);
1074:                    } else // multiline
1075:                    {
1076:                        for (int t = 0; t < g.getNumGeometries(); t++) {
1077:                            LineString gg = (LineString) g.getGeometryN(t);
1078:                            lines.add(gg);
1079:                        }
1080:                    }
1081:                }
1082:                if (lines.size() == 0)
1083:                    return null;
1084:
1085:                // at this point "lines" now is a list of linestring
1086:
1087:                // join
1088:                // this algo doesnt always do what you want it to do, but its pretty
1089:                // good
1090:                Collection merged = this .mergeLines(lines);
1091:
1092:                // clip to bounding box
1093:                ArrayList clippedLines = new ArrayList();
1094:                it = merged.iterator();
1095:                LineString l;
1096:                MultiLineString ll;
1097:                Envelope displayGeomEnv = displayGeometry.getEnvelopeInternal();
1098:                while (it.hasNext()) {
1099:                    l = (LineString) it.next();
1100:                    ll = clipLineString(l, (Polygon) displayGeometry,
1101:                            displayGeomEnv);
1102:                    if ((ll != null) && (!(ll.isEmpty()))) {
1103:                        for (int t = 0; t < ll.getNumGeometries(); t++)
1104:                            clippedLines.add(ll.getGeometryN(t)); // more robust
1105:                        // clipper -- see
1106:                        // its dox
1107:                    }
1108:                }
1109:
1110:                // clippedLines is a list of LineString, all cliped (hopefully) to the
1111:                // display geometry. we choose longest one
1112:                if (clippedLines.size() == 0)
1113:                    return null;
1114:                double maxLen = -1;
1115:                LineString maxLine = null;
1116:                LineString cline;
1117:                for (int t = 0; t < clippedLines.size(); t++) {
1118:                    cline = (LineString) clippedLines.get(t);
1119:                    if (cline.getLength() > maxLen) {
1120:                        maxLine = cline;
1121:                        maxLen = cline.getLength();
1122:                    }
1123:                }
1124:                return maxLine; // longest resulting line
1125:            }
1126:
1127:            /**
1128:             * try to be more robust dont bother returning points
1129:             *
1130:             * This will try to solve robustness problems, but read code as to what it
1131:             * does. It might return the unclipped line if there's a problem!
1132:             *
1133:             * @param line
1134:             * @param bbox
1135:             *            MUST BE A BOUNDING BOX
1136:             */
1137:            public MultiLineString clipLineString(LineString line,
1138:                    Polygon bbox, Envelope displayGeomEnv) {
1139:
1140:                Geometry clip = line;
1141:                line.geometryChanged();// djb -- jessie should do this during
1142:                // generalization
1143:                if (displayGeomEnv.contains(line.getEnvelopeInternal())) {
1144:                    // shortcut -- entirely inside the display rectangle -- no clipping
1145:                    // required!
1146:                    LineString[] lns = new LineString[1];
1147:                    lns[0] = (LineString) clip;
1148:                    return line.getFactory().createMultiLineString(lns);
1149:                }
1150:                try {
1151:                    // the representative geometry does not need to be accurate, let's
1152:                    // simplify it further before doing the overlay to reduce the overlay cost
1153:                    Decimator d = new Decimator(10, 10);
1154:                    d.decimate(line);
1155:                    line.geometryChanged();
1156:                    clip = EnhancedPrecisionOp.intersection(line, bbox);
1157:                } catch (Exception e) {
1158:                    // TODO: should try to expand the bounding box and re-do the
1159:                    // intersection, but line-bounding box
1160:                    // problems are quite rare.
1161:                    clip = line;// just return the unclipped version
1162:                }
1163:                if (clip instanceof  MultiLineString)
1164:                    return (MultiLineString) clip;
1165:                if (clip instanceof  LineString) {
1166:                    LineString[] lns = new LineString[1];
1167:                    lns[0] = (LineString) clip;
1168:                    return line.getFactory().createMultiLineString(lns);
1169:                }
1170:                // otherwise we've got a point or line&point or empty
1171:                if (clip instanceof  Point)
1172:                    return null;
1173:                if (clip instanceof  MultiPoint)
1174:                    return null;
1175:
1176:                // its a GC (Line intersection Poly cannot be a polygon/multipoly)
1177:                GeometryCollection gc = (GeometryCollection) clip;
1178:                ArrayList lns = new ArrayList();
1179:                Geometry g;
1180:                for (int t = 0; t < gc.getNumGeometries(); t++) {
1181:                    g = gc.getGeometryN(t);
1182:                    if (g instanceof  LineString)
1183:                        lns.add(g);
1184:                    // dont think multilinestring is possible, but not sure
1185:                }
1186:
1187:                // convert to multilinestring
1188:                if (lns.size() == 0)
1189:                    return null;
1190:
1191:                return line.getFactory().createMultiLineString(
1192:                        (LineString[]) lns.toArray(new LineString[1]));
1193:
1194:            }
1195:
1196:            /**
1197:             * 1. make a list of all the polygons clipped to the displayGeometry NOTE:
1198:             * reject any points or lines 2. choose the largest of the clipped
1199:             * geometries
1200:             *
1201:             * @param geoms
1202:             * @param displayGeometry
1203:             */
1204:            Polygon getPolySetRepresentativeLocation(List geoms,
1205:                    Geometry displayGeometry) {
1206:                ArrayList polys = new ArrayList(); // points that are inside the
1207:                // displayGeometry
1208:
1209:                Iterator it = geoms.iterator();
1210:                Geometry g;
1211:                // go through each geometry in the input set
1212:                // if its not a polygon or multipolygon ignore it
1213:                // if its a polygon, add it to "polys"
1214:                // if its a multipolgon, add each component to "polys"
1215:                while (it.hasNext()) {
1216:                    g = (Geometry) it.next();
1217:                    if (!((g instanceof  Polygon) || (g instanceof  MultiPolygon)))
1218:                        continue;
1219:
1220:                    if (g instanceof  Polygon) {
1221:                        polys.add(g);
1222:                    } else // multipoly
1223:                    {
1224:                        for (int t = 0; t < g.getNumGeometries(); t++) {
1225:                            Polygon gg = (Polygon) g.getGeometryN(t);
1226:                            polys.add(gg);
1227:                        }
1228:                    }
1229:                }
1230:                if (polys.size() == 0)
1231:                    return null;
1232:
1233:                // at this point "polys" is a list of polygons
1234:
1235:                // clip
1236:                ArrayList clippedPolys = new ArrayList();
1237:                it = polys.iterator();
1238:                Polygon p;
1239:                MultiPolygon pp;
1240:                Envelope displayGeomEnv = displayGeometry.getEnvelopeInternal();
1241:                while (it.hasNext()) {
1242:                    p = (Polygon) it.next();
1243:                    pp = clipPolygon(p, (Polygon) displayGeometry,
1244:                            displayGeomEnv);
1245:                    if ((pp != null) && (!(pp.isEmpty()))) {
1246:                        for (int t = 0; t < pp.getNumGeometries(); t++)
1247:                            clippedPolys.add(pp.getGeometryN(t)); // more robust
1248:                        // version -- see
1249:                        // dox
1250:                    }
1251:                }
1252:                // clippedPolys is a list of Polygon, all cliped (hopefully) to the
1253:                // display geometry. we choose largest one
1254:                if (clippedPolys.size() == 0)
1255:                    return null;
1256:                double maxSize = -1;
1257:                Polygon maxPoly = null;
1258:                Polygon cpoly;
1259:                for (int t = 0; t < clippedPolys.size(); t++) {
1260:                    cpoly = (Polygon) clippedPolys.get(t);
1261:                    if (cpoly.getArea() > maxSize) {
1262:                        maxPoly = cpoly;
1263:                        maxSize = cpoly.getArea();
1264:                    }
1265:                }
1266:                return maxPoly;
1267:            }
1268:
1269:            /**
1270:             * try to do a more robust way of clipping a polygon to a bounding box. This
1271:             * might return the orginal polygon if it cannot clip TODO: this is a bit
1272:             * simplistic, there's lots more to do.
1273:             *
1274:             * @param poly
1275:             * @param bbox
1276:             * @param displayGeomEnv
1277:             *
1278:             * @return a MutliPolygon
1279:             */
1280:            public MultiPolygon clipPolygon(Polygon poly, Polygon bbox,
1281:                    Envelope displayGeomEnv) {
1282:
1283:                Geometry clip = poly;
1284:                poly.geometryChanged();// djb -- jessie should do this during
1285:                // generalization
1286:                if (displayGeomEnv.contains(poly.getEnvelopeInternal())) {
1287:                    // shortcut -- entirely inside the display rectangle -- no clipping
1288:                    // required!
1289:                    Polygon[] polys = new Polygon[1];
1290:                    polys[0] = (Polygon) clip;
1291:                    return poly.getFactory().createMultiPolygon(polys);
1292:                }
1293:
1294:                try {
1295:                    // the representative geometry does not need to be accurate, let's
1296:                    // simplify it further before doing the overlay to reduce the overlay cost
1297:                    Decimator d = new Decimator(10, 10);
1298:                    d.decimate(poly);
1299:                    poly.geometryChanged();
1300:                    clip = EnhancedPrecisionOp.intersection(poly, bbox);
1301:                } catch (Exception e) {
1302:                    // TODO: should try to expand the bounding box and re-do the
1303:                    // intersection.
1304:                    // TODO: also, try removing the interior rings of the polygon
1305:
1306:                    clip = poly;// just return the unclipped version
1307:                }
1308:                if (clip instanceof  MultiPolygon)
1309:                    return (MultiPolygon) clip;
1310:                if (clip instanceof  Polygon) {
1311:                    Polygon[] polys = new Polygon[1];
1312:                    polys[0] = (Polygon) clip;
1313:                    return poly.getFactory().createMultiPolygon(polys);
1314:                }
1315:                // otherwise we've got a point or line&point or empty
1316:                if (clip instanceof  Point)
1317:                    return null;
1318:                if (clip instanceof  MultiPoint)
1319:                    return null;
1320:                if (clip instanceof  LineString)
1321:                    return null;
1322:                if (clip instanceof  MultiLineString)
1323:                    return null;
1324:
1325:                // its a GC
1326:                GeometryCollection gc = (GeometryCollection) clip;
1327:                ArrayList plys = new ArrayList();
1328:                Geometry g;
1329:                for (int t = 0; t < gc.getNumGeometries(); t++) {
1330:                    g = gc.getGeometryN(t);
1331:                    if (g instanceof  Polygon)
1332:                        plys.add(g);
1333:                    // dont think multiPolygon is possible, but not sure
1334:                }
1335:
1336:                // convert to multipoly
1337:                if (plys.size() == 0)
1338:                    return null;
1339:
1340:                return poly.getFactory().createMultiPolygon(
1341:                        (Polygon[]) plys.toArray(new Polygon[1]));
1342:            }
1343:
1344:            /**
1345:             * see middlePoint() find the segment that the point is apart of, and return
1346:             * the slope.
1347:             *
1348:             * @param l
1349:             * @param percent
1350:             */
1351:            double middleTheta(LineString l, double percent) {
1352:                if (percent >= 1.0)
1353:                    percent = 0.99; // for precision
1354:                if (percent <= 0)
1355:                    percent = 0.01; // for precision
1356:
1357:                double len = l.getLength();
1358:                double dist = percent * len;
1359:
1360:                double running_sum_dist = 0;
1361:                CoordinateSequence pts = l.getCoordinateSequence();
1362:                double segmentLen;
1363:                double dx;
1364:                double dy;
1365:                double slope;
1366:                final int length = pts.size();
1367:                Coordinate curr = new Coordinate();
1368:                Coordinate next = new Coordinate();
1369:                for (int i = 0; i < length - 1; i++) {
1370:                    pts.getCoordinate(i, curr);
1371:                    pts.getCoordinate(i + 1, next);
1372:                    segmentLen = curr.distance(next);
1373:
1374:                    if ((running_sum_dist + segmentLen) >= dist) {
1375:                        // it is on this segment pts[i] to pts[i+1]
1376:                        dx = (next.x - curr.x);
1377:                        dy = (next.y - curr.y);
1378:                        slope = dy / dx;
1379:                        return Math.atan(slope);
1380:                    }
1381:                    running_sum_dist += segmentLen;
1382:                }
1383:                return 0;
1384:            }
1385:
1386:            /**
1387:             * calculate the middle of a line. The returning point will be x% (0.5 =
1388:             * 50%) along the line and on the line.
1389:             *
1390:             *
1391:             * @param l
1392:             * @param percent
1393:             *            0=start, 0.5=middle, 1.0=end
1394:             */
1395:            Point middleLine(LineString l, double percent) {
1396:                if (percent >= 1.0)
1397:                    percent = 0.99; // for precision
1398:                if (percent <= 0)
1399:                    percent = 0.01; // for precision
1400:
1401:                double len = l.getLength();
1402:                double dist = percent * len;
1403:
1404:                double running_sum_dist = 0;
1405:                Coordinate[] pts = l.getCoordinates();
1406:                double segmentLen;
1407:                final int length = pts.length;
1408:                double r;
1409:                Coordinate c;
1410:                for (int i = 0; i < length - 1; i++) {
1411:                    segmentLen = pts[i].distance(pts[i + 1]);
1412:
1413:                    if ((running_sum_dist + segmentLen) >= dist) {
1414:                        // it is on this segment
1415:                        r = (dist - running_sum_dist) / segmentLen;
1416:                        c = new Coordinate(pts[i].x + (pts[i + 1].x - pts[i].x)
1417:                                * r, pts[i].y + (pts[i + 1].y - pts[i].y) * r);
1418:                        return l.getFactory().createPoint(c);
1419:                    }
1420:                    running_sum_dist += segmentLen;
1421:                }
1422:
1423:                return l.getEndPoint(); // precision protection
1424:            }
1425:
1426:            Collection mergeLines(Collection lines) {
1427:                LineMerger lm = new LineMerger();
1428:                lm.add(lines);
1429:                Collection merged = lm.getMergedLineStrings(); // merged lines
1430:
1431:                // Collection merged = lines;
1432:
1433:                if (merged.size() == 0) {
1434:                    return null; // shouldnt happen
1435:                }
1436:                if (merged.size() == 1) // simple case - no need to continue merging
1437:                {
1438:                    return merged;
1439:                }
1440:
1441:                Hashtable nodes = new Hashtable(merged.size() * 2); // coordinate ->
1442:                // list of lines
1443:                Iterator it = merged.iterator();
1444:                while (it.hasNext()) {
1445:                    LineString ls = (LineString) it.next();
1446:                    putInNodeHash(ls, nodes);
1447:                }
1448:
1449:                ArrayList result = new ArrayList();
1450:                ArrayList merged_list = new ArrayList(merged);
1451:
1452:                // SORT -- sorting is important because order does matter.
1453:                Collections.sort(merged_list, lineLengthComparator); // sorted
1454:                // long->short
1455:                processNodes(merged_list, nodes, result);
1456:                // this looks for differences between the two methods.
1457:                // Collection a = mergeLines2(lines);
1458:                // if (a.size() != result.size())
1459:                // {
1460:                // System.out.println("bad");
1461:                // boolean bb= false;
1462:                // if (bb)
1463:                // {
1464:                // Collection b = mergeLines(lines);
1465:                // }
1466:                // }
1467:                return result;
1468:            }
1469:
1470:            /**
1471:             * pull a line from the list, and: 1. if nothing connects to it (its
1472:             * issolated), add it to "result" 2. otherwise, merge it at the start/end
1473:             * with the LONGEST line there. 3. remove the original line, and the lines
1474:             * it merged with from the hashtables 4. go again, with the merged line
1475:             *
1476:             * @param edges
1477:             * @param nodes
1478:             * @param result
1479:             *
1480:             */
1481:            public void processNodes(List edges, Hashtable nodes,
1482:                    ArrayList result) {
1483:                int index = 0; // index into edges
1484:                while (index < edges.size()) // still more to do
1485:                {
1486:                    // 1. get a line and remove it from the graph
1487:                    LineString ls = (LineString) edges.get(index);
1488:                    Coordinate key = ls.getCoordinateN(0);
1489:                    ArrayList nodeList = (ArrayList) nodes.get(key);
1490:                    if (nodeList == null) // this was removed in an earlier iteration
1491:                    {
1492:                        index++;
1493:                        continue;
1494:                    }
1495:                    if (!nodeList.contains(ls)) {
1496:                        index++;
1497:                        continue; // already processed
1498:                    }
1499:                    removeFromHash(nodes, ls); // we're removing this from the network
1500:
1501:                    Coordinate key2 = ls.getCoordinateN(ls.getNumPoints() - 1);
1502:                    ArrayList nodeList2 = (ArrayList) nodes.get(key2);
1503:
1504:                    // case 1 -- this line is independent
1505:                    if ((nodeList.size() == 0) && (nodeList2.size() == 0)) {
1506:                        result.add(ls);
1507:                        index++; // move to next line
1508:                        continue;
1509:                    }
1510:
1511:                    if (nodeList.size() > 0) // touches something at the start
1512:                    {
1513:                        LineString ls2 = getLongest(nodeList); // merge with this one
1514:                        ls = merge(ls, ls2);
1515:                        removeFromHash(nodes, ls2);
1516:                    }
1517:                    if (nodeList2.size() > 0) // touches something at the start
1518:                    {
1519:                        LineString ls2 = getLongest(nodeList2); // merge with this one
1520:                        ls = merge(ls, ls2);
1521:                        removeFromHash(nodes, ls2);
1522:                    }
1523:                    // need for further processing
1524:                    edges.set(index, ls); // redo this one.
1525:                    putInNodeHash(ls, nodes); // put in network
1526:                }
1527:            }
1528:
1529:            public void removeFromHash(Hashtable nodes, LineString ls) {
1530:                Coordinate key = ls.getCoordinateN(0);
1531:                ArrayList nodeList = (ArrayList) nodes.get(key);
1532:                if (nodeList != null) {
1533:                    nodeList.remove(ls);
1534:                }
1535:                key = ls.getCoordinateN(ls.getNumPoints() - 1);
1536:                nodeList = (ArrayList) nodes.get(key);
1537:                if (nodeList != null) {
1538:                    nodeList.remove(ls);
1539:                }
1540:            }
1541:
1542:            public LineString getLongest(ArrayList al) {
1543:                if (al.size() == 1)
1544:                    return (LineString) (al.get(0));
1545:                double maxLength = -1;
1546:                LineString result = null;
1547:                final int size = al.size();
1548:                LineString l;
1549:                for (int t = 0; t < size; t++) {
1550:                    l = (LineString) al.get(t);
1551:                    if (l.getLength() > maxLength) {
1552:                        result = l;
1553:                        maxLength = l.getLength();
1554:                    }
1555:                }
1556:                return result;
1557:            }
1558:
1559:            public void putInNodeHash(LineString ls, Hashtable nodes) {
1560:                Coordinate key = ls.getCoordinateN(0);
1561:                ArrayList nodeList = (ArrayList) nodes.get(key);
1562:                if (nodeList == null) {
1563:                    nodeList = new ArrayList();
1564:                    nodeList.add(ls);
1565:                    nodes.put(key, nodeList);
1566:                } else
1567:                    nodeList.add(ls);
1568:                key = ls.getCoordinateN(ls.getNumPoints() - 1);
1569:                nodeList = (ArrayList) nodes.get(key);
1570:                if (nodeList == null) {
1571:                    nodeList = new ArrayList();
1572:                    nodeList.add(ls);
1573:                    nodes.put(key, nodeList);
1574:                } else
1575:                    nodeList.add(ls);
1576:            }
1577:
1578:            /**
1579:             * merges a set of lines together into a (usually) smaller set. This one's
1580:             * pretty dumb, we use the JTS method (which doesnt merge on degree 3 nodes)
1581:             * and try to construct less lines.
1582:             *
1583:             * There's multiple solutions, but we do this the easy way. Usually you will
1584:             * not be given more than 3 lines (especially after jts is finished with).
1585:             *
1586:             * Find a line, find a lines that it "connects" to and add it. Keep going.
1587:             *
1588:             * DONE: be smarter - use length so the algorithm becomes greedy.
1589:             *
1590:             * This isnt 100% correct, but usually it does the right thing.
1591:             *
1592:             * NOTE: this is O(N^2), but N tends to be <10
1593:             *
1594:             * @param lines
1595:             */
1596:            Collection mergeLines2(Collection lines) {
1597:                LineMerger lm = new LineMerger();
1598:                lm.add(lines);
1599:                Collection merged = lm.getMergedLineStrings(); // merged lines
1600:                // Collection merged = lines;
1601:
1602:                if (merged.size() == 0) {
1603:                    return null; // shouldnt happen
1604:                }
1605:                if (merged.size() == 1) // simple case - no need to continue merging
1606:                {
1607:                    return merged;
1608:                }
1609:
1610:                // key to this algorithm is the sorting by line length!
1611:
1612:                // basic method:
1613:                // 1. grab the first line in the list of lines to be merged
1614:                // 2. search through the rest of lines (longer ones = first checked) for
1615:                // a line that can be merged
1616:                // 3. if you find one, great, merge it and do 2 things - a) update the
1617:                // search geometry with the merged geometry and b) delete the other
1618:                // geometry
1619:                // if not, keep looking
1620:                // 4. go back to step #1, but use the next longest line
1621:                // 5. keep going until you've completely gone through the list and no
1622:                // merging's taken place
1623:
1624:                ArrayList mylines = new ArrayList(merged);
1625:
1626:                boolean keep_going = true;
1627:                while (keep_going) {
1628:                    keep_going = false; // no news is bad news
1629:                    Collections.sort(mylines, lineLengthComparator); // sorted
1630:                    final int size = mylines.size(); // long->short
1631:                    LineString major, minor, merge;
1632:                    for (int t = 0; t < size; t++) // for each line
1633:                    {
1634:                        major = (LineString) mylines.get(t); // this is the
1635:                        // search
1636:                        // geometry
1637:                        // (step #1)
1638:                        if (major != null) {
1639:                            for (int i = t + 1; i < mylines.size(); i++) // search
1640:                            // forward
1641:                            // for a
1642:                            // joining
1643:                            // thing
1644:                            {
1645:                                minor = (LineString) mylines.get(i); // forward
1646:                                // scan
1647:                                if (minor != null) // protection because we remove an
1648:                                // already match line!
1649:                                {
1650:                                    merge = merge(major, minor); // step 3
1651:                                    // (null =
1652:                                    // not
1653:                                    // mergeable)
1654:                                    if (merge != null) {
1655:                                        // step 3a
1656:                                        keep_going = true;
1657:                                        mylines.set(i, null);
1658:                                        mylines.set(t, merge);
1659:                                        major = merge;
1660:                                    }
1661:                                }
1662:                            }
1663:                        }
1664:                    }
1665:                    // remove any null items in the list (see step 3a)
1666:
1667:                    mylines = (ArrayList) removeNulls(mylines);
1668:
1669:                }
1670:
1671:                // return result
1672:                return removeNulls(mylines);
1673:
1674:            }
1675:
1676:            /**
1677:             * given a list, return a new list thats the same as the first, but has no
1678:             * null values in it.
1679:             *
1680:             * @param l
1681:             */
1682:            ArrayList removeNulls(List l) {
1683:                ArrayList al = new ArrayList();
1684:                Iterator it = l.iterator();
1685:                Object o;
1686:                while (it.hasNext()) {
1687:                    o = it.next();
1688:                    if (o != null) {
1689:                        al.add(o);
1690:                    }
1691:                }
1692:                return al;
1693:            }
1694:
1695:            /**
1696:             * reverse direction of points in a line
1697:             */
1698:            LineString reverse(LineString l) {
1699:                List clist = Arrays.asList(l.getCoordinates());
1700:                Collections.reverse(clist);
1701:                return l.getFactory().createLineString(
1702:                        (Coordinate[]) clist.toArray(new Coordinate[1]));
1703:            }
1704:
1705:            /**
1706:             * if possible, merge the two lines together (ie. their start/end points are
1707:             * equal) returns null if not possible
1708:             *
1709:             * @param major
1710:             * @param minor
1711:             */
1712:            LineString merge(LineString major, LineString minor) {
1713:                Coordinate major_s = major.getCoordinateN(0);
1714:                Coordinate major_e = major
1715:                        .getCoordinateN(major.getNumPoints() - 1);
1716:                Coordinate minor_s = minor.getCoordinateN(0);
1717:                Coordinate minor_e = minor
1718:                        .getCoordinateN(minor.getNumPoints() - 1);
1719:
1720:                if (major_s.equals2D(minor_s)) {
1721:                    // reverse minor -> major
1722:                    return mergeSimple(reverse(minor), major);
1723:
1724:                } else if (major_s.equals2D(minor_e)) {
1725:                    // minor -> major
1726:                    return mergeSimple(minor, major);
1727:                } else if (major_e.equals2D(minor_s)) {
1728:                    // major -> minor
1729:                    return mergeSimple(major, minor);
1730:                } else if (major_e.equals2D(minor_e)) {
1731:                    // major -> reverse(minor)
1732:                    return mergeSimple(major, reverse(minor));
1733:                }
1734:                return null; // no merge
1735:            }
1736:
1737:            /**
1738:             * simple linestring merge - l1 points then l2 points
1739:             */
1740:            private LineString mergeSimple(LineString l1, LineString l2) {
1741:                ArrayList clist = new ArrayList(Arrays.asList(l1
1742:                        .getCoordinates()));
1743:                clist.addAll(Arrays.asList(l2.getCoordinates()));
1744:
1745:                return l1.getFactory().createLineString(
1746:                        (Coordinate[]) clist.toArray(new Coordinate[1]));
1747:            }
1748:
1749:            /**
1750:             * sorts a list of LineStrings by length (long=1st)
1751:             *
1752:             */
1753:            private final class LineLengthComparator implements 
1754:                    java.util.Comparator {
1755:                public int compare(Object o1, Object o2) // note order - this sort
1756:                // big->small
1757:                {
1758:                    return Double.compare(((LineString) o2).getLength(),
1759:                            ((LineString) o1).getLength());
1760:                }
1761:            }
1762:
1763:            // djb: replaced because old one was from sun's Rectangle class
1764:            private Envelope intersection(Envelope e1, Envelope e2) {
1765:                Envelope r = e1.intersection(e2);
1766:                if (r.getWidth() < 0)
1767:                    return null;
1768:                if (r.getHeight() < 0)
1769:                    return null;
1770:                return r;
1771:            }
1772:
1773:            public void enableLayer(String layerId) {
1774:                needsOrdering = true;
1775:                enabledLayers.add(layerId);
1776:            }
1777:
1778:            public boolean isOutlineRenderingEnabled() {
1779:                return outlineRenderingEnabled;
1780:            }
1781:
1782:            /**
1783:             * Sets the text rendering mode. 
1784:             * When true, the text is rendered as its GlyphVector outline (as a geometry) instead of using
1785:             * drawGlypVector. Pro: labels and halos are perfectly centered, some people prefer the 
1786:             * extra antialiasing obtained. Cons: possibly slower, some people do not like the 
1787:             * extra antialiasing :) 
1788:             */
1789:            public void setOutlineRenderingEnabled(
1790:                    boolean outlineRenderingEnabled) {
1791:                this.outlineRenderingEnabled = outlineRenderingEnabled;
1792:            }
1793:
1794:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.