Source Code Cross Referenced for KMLTransformer.java in  » GIS » GeoServer » org » vfny » geoserver » wms » responses » map » kml » 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 » GeoServer » org.vfny.geoserver.wms.responses.map.kml 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


001:        /* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved.
002:         * This code is licensed under the GPL 2.0 license, availible at the root
003:         * application directory.
004:         */
005:        package org.vfny.geoserver.wms.responses.map.kml;
006:
007:        import com.vividsolutions.jts.geom.Envelope;
008:        import org.geotools.data.DataUtilities;
009:        import org.geotools.data.DefaultQuery;
010:        import org.geotools.data.FeatureSource;
011:        import org.geotools.data.Query;
012:        import org.geotools.data.crs.ReprojectFeatureResults;
013:        import org.geotools.factory.CommonFactoryFinder;
014:        import org.geotools.feature.AttributeType;
015:        import org.geotools.feature.FeatureCollection;
016:        import org.geotools.feature.FeatureType;
017:        import org.geotools.feature.GeometryAttributeType;
018:        import org.geotools.filter.BBoxExpression;
019:        import org.geotools.filter.IllegalFilterException;
020:        import org.geotools.geometry.jts.ReferencedEnvelope;
021:        import org.geotools.map.MapLayer;
022:        import org.geotools.referencing.CRS;
023:        import org.geotools.renderer.lite.LiteFeatureTypeStyle;
024:        import org.geotools.renderer.lite.RendererUtilities;
025:        import org.geotools.styling.FeatureTypeStyle;
026:        import org.geotools.styling.Rule;
027:        import org.geotools.styling.Style;
028:        import org.geotools.xml.transform.TransformerBase;
029:        import org.geotools.xml.transform.Translator;
030:        import org.opengis.filter.Filter;
031:        import org.opengis.filter.FilterFactory;
032:        import org.opengis.referencing.crs.CoordinateReferenceSystem;
033:        import org.vfny.geoserver.global.MapLayerInfo;
034:        import org.vfny.geoserver.wms.WMSMapContext;
035:        import org.vfny.geoserver.wms.requests.GetMapRequest;
036:        import org.xml.sax.ContentHandler;
037:        import java.awt.Rectangle;
038:        import java.awt.Transparency;
039:        import java.awt.geom.AffineTransform;
040:        import java.awt.image.BufferedImage;
041:        import java.text.NumberFormat;
042:        import java.util.ArrayList;
043:        import java.util.Iterator;
044:        import java.util.List;
045:        import java.util.logging.Level;
046:        import java.util.logging.Logger;
047:
048:        import javax.xml.transform.Transformer;
049:
050:        public class KMLTransformer extends TransformerBase {
051:            /**
052:             * logger
053:             */
054:            static Logger LOGGER = org.geotools.util.logging.Logging
055:                    .getLogger("org.geoserver.kml");
056:
057:            /**
058:             * Factory used to create filter objects
059:             */
060:            FilterFactory filterFactory = (FilterFactory) CommonFactoryFinder
061:                    .getFilterFactory(null);
062:
063:            private static final CoordinateReferenceSystem WGS84;
064:
065:            static {
066:                try {
067:                    WGS84 = CRS.decode("EPSG:4326");
068:                } catch (Exception e) {
069:                    throw new RuntimeException(
070:                            "Cannot decode EPSG:4326, the CRS subsystem must be badly broken...");
071:                }
072:            }
073:
074:            /**
075:             * Flag controlling wether kmz was requested.
076:             */
077:            boolean kmz = false;
078:
079:            public KMLTransformer() {
080:                setNamespaceDeclarationEnabled(false);
081:            }
082:
083:            public Translator createTranslator(ContentHandler handler) {
084:                return new KMLTranslator(handler);
085:            }
086:
087:            public void setFilterFactory(FilterFactory filterFactory) {
088:                this .filterFactory = filterFactory;
089:            }
090:
091:            public void setKmz(boolean kmz) {
092:                this .kmz = kmz;
093:            }
094:
095:            protected class KMLTranslator extends TranslatorSupport {
096:                /**
097:                 * Tolerance used to compare doubles for equality
098:                 */
099:                static final double TOLERANCE = 1e-6;
100:
101:                static final int RULES = 0;
102:                static final int ELSE_RULES = 1;
103:
104:                private double scaleDenominator;
105:
106:                public KMLTranslator(ContentHandler handler) {
107:                    super (handler, null, null);
108:                }
109:
110:                public void encode(Object o) throws IllegalArgumentException {
111:                    start("kml");
112:
113:                    WMSMapContext mapContext = (WMSMapContext) o;
114:                    GetMapRequest request = mapContext.getRequest();
115:                    MapLayer[] layers = mapContext.getLayers();
116:
117:                    //calculate scale denominator
118:                    scaleDenominator = 1;
119:                    try {
120:                        scaleDenominator = RendererUtilities.calculateScale(
121:                                mapContext.getAreaOfInterest(), mapContext
122:                                        .getMapWidth(), mapContext
123:                                        .getMapHeight(), null);
124:                    } catch (Exception e) {
125:                        LOGGER.log(Level.WARNING,
126:                                "Error calculating scale denominator", e);
127:                    }
128:                    LOGGER.fine("scale denominator = " + scaleDenominator);
129:
130:                    //if we have more than one layer ( or a legend was requested ),
131:                    //use the name "GeoServer" to group them
132:                    boolean group = (layers.length > 1) || request.getLegend();
133:
134:                    if (group) {
135:                        StringBuffer sb = new StringBuffer();
136:                        for (int i = 0; i < layers.length; i++) {
137:                            sb.append(layers[i].getTitle() + ",");
138:                        }
139:                        sb.setLength(sb.length() - 1);
140:
141:                        start("Document");
142:                        element("name", sb.toString());
143:                    }
144:
145:                    //for every layer specified in the request
146:                    for (int i = 0; i < layers.length; i++) {
147:                        //layer and info
148:                        MapLayer layer = layers[i];
149:                        MapLayerInfo layerInfo = mapContext.getRequest()
150:                                .getLayers()[i];
151:
152:                        //was a super overlay requested?
153:                        if (mapContext.getRequest().getSuperOverlay()) {
154:                            //encode as super overlay
155:                            encodeSuperOverlayLayer(mapContext, layer);
156:                        } else {
157:                            //figure out which type of layer this is, raster or vector
158:                            if (layerInfo.getType() == MapLayerInfo.TYPE_VECTOR
159:                                    || layerInfo.getType() == MapLayerInfo.TYPE_REMOTE_VECTOR) {
160:                                //vector 
161:                                encodeVectorLayer(mapContext, layer);
162:                            } else {
163:                                //encode as normal ground overlay
164:                                encodeRasterLayer(mapContext, layer);
165:                            }
166:                        }
167:                    }
168:
169:                    //legend suppoer
170:                    if (request.getLegend()) {
171:                        //for every layer specified in the request
172:                        for (int i = 0; i < layers.length; i++) {
173:                            //layer and info
174:                            MapLayer layer = layers[i];
175:                            encodeLegend(mapContext, layer);
176:                        }
177:                    }
178:
179:                    if (group) {
180:                        end("Document");
181:                    }
182:
183:                    end("kml");
184:                }
185:
186:                /**
187:                 * Encodes a vector layer as kml.
188:                 */
189:                protected void encodeVectorLayer(WMSMapContext mapContext,
190:                        MapLayer layer) {
191:                    //get the data
192:                    FeatureSource featureSource = layer.getFeatureSource();
193:                    FeatureCollection features = null;
194:
195:                    try {
196:                        features = loadFeatureCollection(featureSource, layer,
197:                                mapContext);
198:                        if (features == null)
199:                            return;
200:                    } catch (Exception e) {
201:                        throw new RuntimeException(e);
202:                    }
203:
204:                    //was kmz requested?
205:                    if (kmz) {
206:                        //calculate kmscore to determine if we shoud write as vectors
207:                        // or pre-render
208:                        int kmscore = mapContext.getRequest().getKMScore();
209:                        boolean useVector = useVectorOutput(kmscore, features
210:                                .size());
211:
212:                        if (useVector) {
213:                            //encode
214:                            KMLVectorTransformer tx = createVectorTransformer(
215:                                    mapContext, layer);
216:                            initTransformer(tx);
217:                            tx.setScaleDenominator(scaleDenominator);
218:                            tx.createTranslator(contentHandler)
219:                                    .encode(features);
220:                        } else {
221:                            KMLRasterTransformer tx = createRasterTransfomer(mapContext);
222:                            initTransformer(tx);
223:
224:                            //set inline to true to have the transformer reference images
225:                            // inline in the zip file
226:                            tx.setInline(true);
227:                            tx.createTranslator(contentHandler).encode(layer);
228:                        }
229:                    } else {
230:                        //kmz not selected, just do straight vector
231:                        KMLVectorTransformer tx = createVectorTransformer(
232:                                mapContext, layer);
233:                        initTransformer(tx);
234:                        tx.setScaleDenominator(scaleDenominator);
235:                        tx.createTranslator(contentHandler).encode(features);
236:                    }
237:                }
238:
239:                /**
240:                 * Factory method, allows subclasses to inject their own version of the raster transfomer
241:                 * @param mapContext
242:                 * @return
243:                 */
244:                protected KMLRasterTransformer createRasterTransfomer(
245:                        WMSMapContext mapContext) {
246:                    return new KMLRasterTransformer(mapContext);
247:                }
248:
249:                /**
250:                 * Factory method, allows subclasses to inject their own version of the vector transfomer
251:                 * @param mapContext
252:                 * @return
253:                 */
254:                protected KMLVectorTransformer createVectorTransformer(
255:                        WMSMapContext mapContext, MapLayer layer) {
256:                    return new KMLVectorTransformer(mapContext, layer);
257:                }
258:
259:                /**
260:                 * Encodes a raster layer as kml.
261:                 */
262:                protected void encodeRasterLayer(WMSMapContext mapContext,
263:                        MapLayer layer) {
264:                    KMLRasterTransformer tx = createRasterTransfomer(mapContext);
265:                    initTransformer(tx);
266:
267:                    tx.setInline(kmz);
268:                    tx.createTranslator(contentHandler).encode(layer);
269:                }
270:
271:                /**
272:                 * Encodes a layer as a super overlay.
273:                 */
274:                protected void encodeSuperOverlayLayer(
275:                        WMSMapContext mapContext, MapLayer layer) {
276:                    KMLSuperOverlayTransformer tx = new KMLSuperOverlayTransformer(
277:                            mapContext);
278:                    initTransformer(tx);
279:                    tx.createTranslator(contentHandler).encode(layer);
280:                }
281:
282:                /**
283:                 * Encodes the legend for a maper layer as a scree overlay.
284:                 */
285:                protected void encodeLegend(WMSMapContext mapContext,
286:                        MapLayer layer) {
287:                    KMLLegendTransformer tx = new KMLLegendTransformer(
288:                            mapContext);
289:                    initTransformer(tx);
290:                    tx.createTranslator(contentHandler).encode(layer);
291:                }
292:
293:                protected void initTransformer(KMLTransformerBase delegate) {
294:                    delegate.setIndentation(getIndentation());
295:                    delegate.setStandAlone(false);
296:                }
297:
298:                double computeScaleDenominator(MapLayer layer,
299:                        WMSMapContext mapContext) {
300:                    Rectangle paintArea = new Rectangle(mapContext
301:                            .getMapWidth(), mapContext.getMapHeight());
302:                    AffineTransform worldToScreen = RendererUtilities
303:                            .worldToScreenTransform(mapContext
304:                                    .getAreaOfInterest(), paintArea);
305:
306:                    try {
307:                        //90 = OGC standard DPI (see SLD spec page 37)
308:                        return RendererUtilities.calculateScale(mapContext
309:                                .getAreaOfInterest(), mapContext
310:                                .getCoordinateReferenceSystem(),
311:                                paintArea.width, paintArea.height, 90);
312:                    } catch (Exception e) {
313:                        //probably either (1) no CRS (2) error xforming, revert to
314:                        // old method - the best we can do (DJB)
315:                        return 1 / worldToScreen.getScaleX();
316:                    }
317:                }
318:
319:                /**
320:                 * Determines whether to return a vector (KML) result of the data or to
321:                 * return an image instead.
322:                 * If the kmscore is 100, then the output should always be vector. If
323:                 * the kmscore is 0, it should always be raster. In between, the number of
324:                 * features is weighed against the kmscore value.
325:                 * kmscore determines whether to return the features as vectors, or as one
326:                 * raster image. It is the point, determined by the user, where X number of
327:                 * features is "too many" and the result should be returned as an image instead.
328:                 *
329:                 * kmscore is logarithmic. The higher the value, the more features it takes
330:                 * to make the algorithm return an image. The lower the kmscore, the fewer
331:                 * features it takes to force an image to be returned.
332:                 * (in use, the formula is exponential: as you increase the KMScore value,
333:                 * the number of features required increases exponentially).
334:                 *
335:                 * @param kmscore the score, between 0 and 100, use to determine what output to use
336:                 * @param numFeatures how many features are being rendered
337:                 * @return true: use just kml vectors, false: use raster result
338:                 */
339:                boolean useVectorOutput(int kmscore, int numFeatures) {
340:                    if (kmscore == 100) {
341:                        return true; // vector KML
342:                    }
343:
344:                    if (kmscore == 0) {
345:                        return false; // raster KMZ
346:                    }
347:
348:                    // For numbers in between, determine exponentionally based on kmscore value:
349:                    // 10^(kmscore/15)
350:                    // This results in exponential growth.
351:                    // The lowest bound is 1 feature and the highest bound is 3.98 million features
352:                    // The most useful kmscore values are between 20 and 70 (21 and 46000 features respectively)
353:                    // A good default kmscore value is around 40 (464 features)
354:                    double magic = Math.pow(10, kmscore / 15);
355:
356:                    if (numFeatures > magic) {
357:                        return false; // return raster
358:                    } else {
359:                        return true; // return vector
360:                    }
361:                }
362:
363:                FeatureCollection loadFeatureCollection(
364:                        FeatureSource featureSource, MapLayer layer,
365:                        WMSMapContext mapContext) throws Exception {
366:                    FeatureType schema = featureSource.getSchema();
367:
368:                    Envelope envelope = mapContext.getAreaOfInterest();
369:                    ReferencedEnvelope aoi = new ReferencedEnvelope(envelope,
370:                            mapContext.getCoordinateReferenceSystem());
371:                    CoordinateReferenceSystem sourceCrs = schema
372:                            .getDefaultGeometry().getCoordinateSystem();
373:
374:                    boolean reprojectBBox = (sourceCrs != null)
375:                            && !CRS.equalsIgnoreMetadata(aoi
376:                                    .getCoordinateReferenceSystem(), sourceCrs);
377:                    if (reprojectBBox) {
378:                        aoi = aoi.transform(sourceCrs, true);
379:                    }
380:
381:                    Filter filter = createBBoxFilter(schema, aoi);
382:
383:                    // now build the query using only the attributes and the bounding
384:                    // box needed
385:                    DefaultQuery q = new DefaultQuery(schema.getTypeName());
386:                    q.setFilter(filter);
387:
388:                    // now, if a definition query has been established for this layer, be
389:                    // sure to respect it by combining it with the bounding box one.
390:                    Query definitionQuery = layer.getQuery();
391:
392:                    if (definitionQuery != Query.ALL) {
393:                        if (q == Query.ALL) {
394:                            q = (DefaultQuery) definitionQuery;
395:                        } else {
396:                            q = (DefaultQuery) DataUtilities.mixQueries(
397:                                    definitionQuery, q, "KMLEncoder");
398:                        }
399:                    }
400:
401:                    // make sure we output in 4326 since that's what KML mandates
402:                    if (sourceCrs != null
403:                            && !CRS.equalsIgnoreMetadata(WGS84, sourceCrs)) {
404:                        return new ReprojectFeatureResults(featureSource
405:                                .getFeatures(q), WGS84);
406:                    }
407:
408:                    // extract the actual rules that are going to be applied to this layer, 
409:                    // - if none applies (scale denominator rules) then nothing to render
410:                    // - if there are else rules, we have to load everything
411:                    // - if there are no else rules, we can try to limit the features read by summarizing
412:                    //   the direct filter rules
413:                    List[] rules = getLayerRules(featureSource.getSchema(),
414:                            layer.getStyle());
415:                    if (rules[RULES].size() == 0
416:                            && rules[ELSE_RULES].size() == 0)
417:                        return null;
418:                    if (rules[ELSE_RULES].size() == 0) {
419:                        Filter newFilter = summarizeRuleFilters(rules[RULES], q
420:                                .getFilter());
421:                        q.setFilter(newFilter);
422:                    }
423:
424:                    return featureSource.getFeatures(q);
425:                }
426:
427:                private List[] getLayerRules(FeatureType ftype, Style style) {
428:                    List[] result = new List[] { new ArrayList(),
429:                            new ArrayList() };
430:
431:                    final String typeName = ftype.getTypeName();
432:                    FeatureTypeStyle[] featureStyles = style
433:                            .getFeatureTypeStyles();
434:                    final int length = featureStyles.length;
435:                    for (int i = 0; i < length; i++) {
436:                        // getting feature styles
437:                        FeatureTypeStyle fts = featureStyles[i];
438:
439:                        // check if this FTS is compatible with this FT.
440:                        if ((typeName != null)
441:                                && (ftype.isDescendedFrom(null, fts
442:                                        .getFeatureTypeName()) || typeName
443:                                        .equalsIgnoreCase(fts
444:                                                .getFeatureTypeName()))) {
445:
446:                            // get applicable rules at the current scale
447:                            Rule[] ftsRules = fts.getRules();
448:                            for (int j = 0; j < ftsRules.length; j++) {
449:                                // getting rule
450:                                Rule r = ftsRules[j];
451:
452:                                if (isWithInScale(r)) {
453:                                    if (r.hasElseFilter()) {
454:                                        result[ELSE_RULES].add(r);
455:                                    } else {
456:                                        result[RULES].add(r);
457:                                    }
458:                                }
459:                            }
460:                        }
461:                    }
462:
463:                    return result;
464:                }
465:
466:                /**
467:                 * Tries to build a more restrictive filter by creating a summary of filters that
468:                 * apply to each rule (or returns simply the original filter, if there is at least one
469:                 * rule that applies to all features)
470:                 * @param rules
471:                 * @param originalFiter
472:                 * @return
473:                 */
474:                private Filter summarizeRuleFilters(List rules,
475:                        Filter originalFiter) {
476:                    List filters = new ArrayList();
477:                    for (Iterator it = rules.iterator(); it.hasNext();) {
478:                        Rule rule = (Rule) it.next();
479:                        // if there is a single rule asking for all filters, we have to 
480:                        // return everything that the original filter returned already
481:                        if (rule.getFilter() == null
482:                                || Filter.INCLUDE.equals(rule.getFilter()))
483:                            return originalFiter;
484:                        else
485:                            filters.add(rule.getFilter());
486:                    }
487:
488:                    org.opengis.filter.FilterFactory ff = CommonFactoryFinder
489:                            .getFilterFactory(null);
490:                    Filter summary = ff.or(filters);
491:                    if (originalFiter != null
492:                            && !Filter.INCLUDE.equals(originalFiter))
493:                        return ff.and(originalFiter, summary);
494:                    else
495:                        return summary;
496:                }
497:
498:                /**
499:                 * Checks if a rule can be triggered at the current scale level
500:                 * 
501:                 * @param r
502:                 *            The rule
503:                 * @return true if the scale is compatible with the rule settings
504:                 */
505:                boolean isWithInScale(Rule r) {
506:                    return ((r.getMinScaleDenominator() - TOLERANCE) <= scaleDenominator)
507:                            && ((r.getMaxScaleDenominator() + TOLERANCE) > scaleDenominator);
508:                }
509:
510:                /** Creates the bounding box filters (one for each geometric attribute) needed to query a
511:                 * <code>MapLayer</code>'s feature source to return just the features for the target
512:                 * rendering extent
513:                 *
514:                 * @param schema the layer's feature source schema
515:                 * @param bbox the expression holding the target rendering bounding box
516:                 * @return an or'ed list of bbox filters, one for each geometric attribute in
517:                 *         <code>attributes</code>. If there are just one geometric attribute, just returns
518:                 *         its corresponding <code>GeometryFilter</code>.
519:                 * @throws IllegalFilterException if something goes wrong creating the filter
520:                 */
521:                Filter createBBoxFilter(FeatureType schema, Envelope bbox)
522:                        throws IllegalFilterException {
523:                    List filters = new ArrayList();
524:                    for (int j = 0; j < schema.getAttributeCount(); j++) {
525:                        AttributeType attType = schema.getAttributeType(j);
526:
527:                        if (attType instanceof  GeometryAttributeType) {
528:                            Filter gfilter = filterFactory.bbox(attType
529:                                    .getLocalName(), bbox.getMinX(), bbox
530:                                    .getMinY(), bbox.getMaxX(), bbox.getMaxY(),
531:                                    null);
532:                            filters.add(gfilter);
533:                        }
534:                    }
535:
536:                    if (filters.size() == 0)
537:                        return Filter.INCLUDE;
538:                    else if (filters.size() == 1)
539:                        return (Filter) filters.get(0);
540:                    else
541:                        return filterFactory.or(filters);
542:                }
543:            }
544:        }
w_w___w___.j___a___v_a_2s.___c_om__ | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.