Source Code Cross Referenced for RendererUtilities.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) 


001:        /*
002:         *    GeoTools - OpenSource mapping toolkit
003:         *    http://geotools.org
004:         *    (C) 2004-2006, Geotools Project Managment Committee (PMC)
005:         *
006:         *    This library is free software; you can redistribute it and/or
007:         *    modify it under the terms of the GNU Lesser General Public
008:         *    License as published by the Free Software Foundation; either
009:         *    version 2.1 of the License, or (at your option) any later version.
010:         *
011:         *    This library is distributed in the hope that it will be useful,
012:         *    but WITHOUT ANY WARRANTY; without even the implied warranty of
013:         *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014:         *    Lesser General Public License for more details.
015:         */package org.geotools.renderer.lite;
016:
017:        import java.awt.Rectangle;
018:        import java.awt.geom.AffineTransform;
019:        import java.awt.geom.NoninvertibleTransformException;
020:        import java.awt.geom.Point2D;
021:        import java.util.Map;
022:        import java.util.logging.Level;
023:        import java.util.logging.Logger;
024:
025:        import org.geotools.coverage.grid.GeneralGridRange;
026:        import org.geotools.geometry.Envelope2D;
027:        import org.geotools.geometry.GeneralEnvelope;
028:        import org.geotools.geometry.jts.JTS;
029:        import org.geotools.geometry.jts.ReferencedEnvelope;
030:        import org.geotools.referencing.CRS;
031:        import org.geotools.referencing.GeodeticCalculator;
032:        import org.geotools.referencing.crs.DefaultGeographicCRS;
033:        import org.geotools.referencing.operation.builder.GridToEnvelopeMapper;
034:        import org.geotools.resources.CRSUtilities;
035:        import org.opengis.referencing.FactoryException;
036:        import org.opengis.referencing.crs.CoordinateReferenceSystem;
037:        import org.opengis.referencing.crs.EngineeringCRS;
038:        import org.opengis.referencing.crs.GeodeticCRS;
039:        import org.opengis.referencing.crs.GeographicCRS;
040:        import org.opengis.referencing.cs.AxisDirection;
041:        import org.opengis.referencing.datum.PixelInCell;
042:        import org.opengis.referencing.operation.MathTransform;
043:        import org.opengis.referencing.operation.TransformException;
044:        import org.opengis.geometry.DirectPosition;
045:        import org.opengis.geometry.MismatchedDimensionException;
046:
047:        import com.vividsolutions.jts.geom.Coordinate;
048:        import com.vividsolutions.jts.geom.Envelope;
049:
050:        /**
051:         * Class for holding utility functions that are common tasks for people using
052:         * the "StreamingRenderer/Renderer".
053:         * 
054:         * 
055:         * @author dblasby
056:         * @author Simone Giannecchini
057:         * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/render/src/main/java/org/geotools/renderer/lite/RendererUtilities.java $
058:         */
059:        public final class RendererUtilities {
060:
061:            private final static Logger LOGGER = org.geotools.util.logging.Logging
062:                    .getLogger(RendererUtilities.class.getName());
063:
064:            private final static DefaultGeographicCRS GeogCRS = DefaultGeographicCRS.WGS84;;
065:
066:            /**
067:             * Helber class for building affine transforms. We use one instance per thread,
068:             * in order to avoid the need for {@code synchronized} statements.
069:             */
070:            private static final ThreadLocal/*<GridToEnvelopeMapper>*/gridToEnvelopeMappers = new ThreadLocal/*<GridToEnvelopeMapper>*/() {
071:                //@Override
072:                protected Object/*<GridToEnvelopeMapper>*/initialValue() {
073:                    final GridToEnvelopeMapper mapper = new GridToEnvelopeMapper();
074:                    mapper.setGridType(PixelInCell.CELL_CORNER);
075:                    return mapper;
076:                }
077:            };
078:
079:            /**
080:             * Utilities classes should not be instantiated.
081:             * 
082:             */
083:            private RendererUtilities() {
084:            };
085:
086:            /**
087:             * Sets up the affine transform <p/> ((Taken from the old LiteRenderer))
088:             * 
089:             * @param mapExtent
090:             *            the map extent
091:             * @param paintArea
092:             *            the size of the rendering output area
093:             * @return a transform that maps from real world coordinates to the screen
094:             * @deprecated Uses the alternative based on <code>ReferencedEnvelope</code>
095:             *             that doe not assume anything on axes order.
096:             * 
097:             */
098:            public static AffineTransform worldToScreenTransform(
099:                    Envelope mapExtent, Rectangle paintArea) {
100:                double scaleX = paintArea.getWidth() / mapExtent.getWidth();
101:                double scaleY = paintArea.getHeight() / mapExtent.getHeight();
102:
103:                double tx = -mapExtent.getMinX() * scaleX;
104:                double ty = (mapExtent.getMinY() * scaleY)
105:                        + paintArea.getHeight();
106:
107:                AffineTransform at = new AffineTransform(scaleX, 0.0d, 0.0d,
108:                        -scaleY, tx, ty);
109:                AffineTransform originTranslation = AffineTransform
110:                        .getTranslateInstance(paintArea.x, paintArea.y);
111:                originTranslation.concatenate(at);
112:
113:                return originTranslation != null ? originTranslation : at;
114:            }
115:
116:            /**
117:             * Sets up the affine transform <p/>
118:             * 
119:             * NOTE It is worth to note that here we do not take into account the half a
120:             * pixel translation stated by ogc for coverages bounds. One reason is that
121:             * WMS 1.1.1 does not follow it!!!
122:             * 
123:             * @param mapExtent
124:             *            the map extent
125:             * @param paintArea
126:             *            the size of the rendering output area
127:             * @return a transform that maps from real world coordinates to the screen
128:             */
129:            public static AffineTransform worldToScreenTransform(
130:                    ReferencedEnvelope mapExtent, Rectangle paintArea) {
131:
132:                // //
133:                //
134:                // Convert the JTS envelope and get the transform
135:                //
136:                // //
137:                final Envelope2D genvelope = new Envelope2D(mapExtent);
138:
139:                // //
140:                //
141:                // Get the transform
142:                //
143:                // //
144:                final GridToEnvelopeMapper m = (GridToEnvelopeMapper) gridToEnvelopeMappers
145:                        .get();
146:                try {
147:                    m.setGridRange(new GeneralGridRange(paintArea));
148:                    m.setEnvelope(genvelope);
149:                    return m.createAffineTransform().createInverse();
150:                } catch (MismatchedDimensionException e) {
151:                    LOGGER.log(Level.WARNING, e.getLocalizedMessage(), e);
152:                    return null;
153:                } catch (NoninvertibleTransformException e) {
154:                    LOGGER.log(Level.WARNING, e.getLocalizedMessage(), e);
155:                    return null;
156:                }
157:
158:            }
159:
160:            /**
161:             * Creates the map's bounding box in real world coordinates <p/> ((Taken
162:             * from the old LiteRenderer))
163:             * 
164:             * @param worldToScreen
165:             *            a transform which converts World coordinates to screen pixel
166:             *            coordinates.
167:             * @param paintArea
168:             *            the size of the rendering output area
169:             * @deprecated Uses the alternative using a
170:             *             <code>CoordinateReferenceSystem</code> that doe not assume
171:             *             anything on axes order.
172:             */
173:            public static Envelope createMapEnvelope(Rectangle paintArea,
174:                    AffineTransform worldToScreen)
175:                    throws NoninvertibleTransformException {
176:                AffineTransform pixelToWorld = null;
177:
178:                // Might throw NoninvertibleTransformException
179:                pixelToWorld = worldToScreen.createInverse();
180:
181:                Point2D p1 = new Point2D.Double();
182:                Point2D p2 = new Point2D.Double();
183:                pixelToWorld.transform(new Point2D.Double(paintArea.getMinX(),
184:                        paintArea.getMinY()), p1);
185:                pixelToWorld.transform(new Point2D.Double(paintArea.getMaxX(),
186:                        paintArea.getMaxY()), p2);
187:
188:                double x1 = p1.getX();
189:                double y1 = p1.getY();
190:                double x2 = p2.getX();
191:                double y2 = p2.getY();
192:                return new Envelope(Math.min(x1, x2), Math.max(x1, x2), Math
193:                        .min(y1, y2), Math.max(y1, y2));
194:            }
195:
196:            /**
197:             * Creates the map's bounding box in real world coordinates <p/>
198:             * 
199:             * NOTE It is worth to note that here we do not take into account the half a
200:             * pixel translation stated by ogc for coverages bounds. One reason is that
201:             * WMS 1.1.1 does not follow it!!!
202:             * 
203:             * @param worldToScreen
204:             *            a transform which converts World coordinates to screen pixel
205:             *            coordinates.
206:             * @param paintArea
207:             *            the size of the rendering output area
208:             */
209:            public static ReferencedEnvelope createMapEnvelope(
210:                    Rectangle paintArea, AffineTransform worldToScreen,
211:                    final CoordinateReferenceSystem crs)
212:                    throws NoninvertibleTransformException {
213:
214:                // //
215:                //
216:                // Make sure the CRS is 2d
217:                //
218:                // //
219:                final CoordinateReferenceSystem crs2d;
220:                try {
221:                    crs2d = CRSUtilities.getCRS2D(crs);
222:                } catch (TransformException e) {
223:                    LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
224:                    return null;
225:                }
226:
227:                // //
228:                //
229:                // build the transform
230:                //
231:                // //
232:                AffineTransform pixelToWorld = worldToScreen.createInverse();
233:
234:                // //
235:                //
236:                // tranform corners
237:                //
238:                // //
239:                Point2D p1 = new Point2D.Double();
240:                Point2D p2 = new Point2D.Double();
241:                pixelToWorld.transform(new Point2D.Double(paintArea.getMinX(),
242:                        paintArea.getMinY()), p1);
243:                pixelToWorld.transform(new Point2D.Double(paintArea.getMaxX(),
244:                        paintArea.getMaxY()), p2);
245:
246:                // //
247:                //
248:                // build the final envelope
249:                //
250:                // //
251:                double x1 = p1.getX();
252:                double y1 = p1.getY();
253:                double x2 = p2.getX();
254:                double y2 = p2.getY();
255:                return new ReferencedEnvelope(Math.min(x1, x2), Math
256:                        .max(x1, x2), Math.min(y1, y2), Math.max(y1, y2), crs2d);
257:            }
258:
259:            /**
260:             * Find the scale denominator of the map. Method: 1. find the diagonal
261:             * distance (meters) 2. find the diagonal distance (pixels) 3. find the
262:             * diagonal distance (meters) -- use DPI 4. calculate scale (#1/#2)
263:             * 
264:             * NOTE: return the scale denominator not the actual scale (1/scale =
265:             * denominator)
266:             * 
267:             * TODO: (SLD spec page 28): Since it is common to integrate the output of
268:             * multiple servers into a single displayed result in the web-mapping
269:             * environment, it is important that different map servers have consistent
270:             * behaviour with respect to processing scales, so that all of the
271:             * independent servers will select or deselect rules at the same scales. To
272:             * insure consistent behaviour, scales relative to coordinate spaces must be
273:             * handled consistently between map servers. For geographic coordinate
274:             * systems, which use angular units, the angular coverage of a map should be
275:             * converted to linear units for computation of scale by using the
276:             * circumference of the Earth at the equator and by assuming perfectly
277:             * square linear units. For linear coordinate systems, the size of the
278:             * coordinate space should be used directly without compensating for
279:             * distortions in it with respect to the shape of the real Earth.
280:             * 
281:             * NOTE: we are actually doing a a much more exact calculation, and
282:             * accounting for non-square pixels (which are allowed in WMS) ADDITIONAL
283:             * NOTE from simboss: I added soe minor fixes. See below.
284:             * 
285:             * @param envelope
286:             * @param coordinateReferenceSystem
287:             * @param imageWidth
288:             * @param imageHeight
289:             * @param DPI
290:             *            screen dots per inch (OGC standard is 90)
291:             * @throws TransformException
292:             * @throws FactoryException
293:             * 
294:             * @deprecated
295:             */
296:            public static double calculateScale(Envelope envelope,
297:                    CoordinateReferenceSystem coordinateReferenceSystem,
298:                    int imageWidth, int imageHeight, double DPI)
299:                    throws TransformException, FactoryException {
300:
301:                // simboss: TODO TO BE REMOVED
302:                // we need to take into account axes swapping. We can do that by
303:                // preconcatenating an axes swapping to the transform we perform below
304:                final CoordinateReferenceSystem tempCRS = CRSUtilities
305:                        .getCRS2D(coordinateReferenceSystem);
306:                // final CoordinateSystem tempCS = tempCRS.getCoordinateSystem();
307:                // final MathTransform preTransform;
308:                // if (tempCS.getAxis(0).getDirection().absolute().equals(
309:                // AxisDirection.NORTH)) {
310:                // preTransform = ProjectiveTransform.create(new AffineTransform(0, 1,
311:                // 1, 0, 0, 0));
312:                //
313:                // } else
314:                // preTransform = ProjectiveTransform.create(AffineTransform
315:                // .getTranslateInstance(0, 0));
316:
317:                // DJB: be much wiser if the requested image is larger than the world
318:                // (this happens VERY OFTEN)
319:                // we first convert to WSG84 and check to see if we're outside the
320:                // 'world'
321:                // bbox
322:                final double[] cs = new double[4];
323:                final double[] csLatLong = new double[4];
324:                final Coordinate p1 = new Coordinate(envelope.getMinX(),
325:                        envelope.getMinY());
326:                final Coordinate p2 = new Coordinate(envelope.getMaxX(),
327:                        envelope.getMaxY());
328:                cs[0] = p1.x;
329:                cs[1] = p1.y;
330:                cs[2] = p2.x;
331:                cs[3] = p2.y;
332:
333:                // transform the provided crs to WGS84 lon,lat
334:                MathTransform transform = CRS.findMathTransform(tempCRS,
335:                        DefaultGeographicCRS.WGS84, true);
336:                // transform = ConcatenatedTransform.create(preTransform, transform);//
337:                // TODO
338:                // TO
339:                // BE
340:                // REMOVED
341:                transform.transform(cs, 0, csLatLong, 0, 2);
342:
343:                // in long/lat format
344:                if ((csLatLong[0] < -180) || (csLatLong[0] > 180)
345:                        || (csLatLong[2] < -180) || (csLatLong[2] > 180)
346:                        || (csLatLong[1] < -90) || (csLatLong[1] > 90)
347:                        || (csLatLong[3] < -90) || (csLatLong[3] > 90)) {
348:                    // we have a problem -- the bbox is outside the 'world' so distance
349:                    // will fail
350:                    // we handle this by making a new measurement for a smaller portion
351:                    // of the image - the portion thats inside the world.
352:                    // if the request is outside the world then we need to throw an
353:                    // error
354:
355:                    if ((csLatLong[0] > csLatLong[2])
356:                            || (csLatLong[1] > csLatLong[3]))
357:                        throw new IllegalArgumentException("BBox is backwards");
358:                    if (((csLatLong[0] < -180) || (csLatLong[0] > 180))
359:                            && ((csLatLong[2] < -180) || (csLatLong[2] > 180))
360:                            && ((csLatLong[1] < -90) || (csLatLong[1] > 90))
361:                            && ((csLatLong[3] < -90) || (csLatLong[3] > 90)))
362:                        throw new IllegalArgumentException(
363:                                "World isn't in the requested bbox");
364:
365:                    // okay, all good. We need to find the world bbox intersect the
366:                    // requested bbox then we're going to convert that back to the
367:                    // original coordinate reference system and from there we can find
368:                    // the (x1,y2) and (x2,y2) of this new bbox.
369:                    // At that point we can do simple math to find the distance.
370:
371:                    final double[] newCsLatLong = new double[4]; // intersected with
372:                    // the world bbox
373:
374:                    newCsLatLong[0] = Math.min(Math.max(csLatLong[0], -180),
375:                            180);
376:                    newCsLatLong[1] = Math.min(Math.max(csLatLong[1], -90), 90);
377:                    newCsLatLong[2] = Math.min(Math.max(csLatLong[2], -180),
378:                            180);
379:                    newCsLatLong[3] = Math.min(Math.max(csLatLong[3], -90), 90);
380:
381:                    double[] origProject = new double[4];
382:                    transform.transform(newCsLatLong, 0, origProject, 0, 2);
383:
384:                    // have the truncated bbox in the original projection, so we can
385:                    // find the image (x,y) for the two points.
386:                    double image_min_x = (origProject[0] - envelope.getMinX())
387:                            / envelope.getWidth() * imageWidth;
388:                    double image_max_x = (origProject[2] - envelope.getMinX())
389:                            / envelope.getWidth() * imageWidth;
390:
391:                    double image_min_y = (origProject[1] - envelope.getMinY())
392:                            / envelope.getHeight() * imageHeight;
393:                    double image_max_y = (origProject[3] - envelope.getMinY())
394:                            / envelope.getHeight() * imageHeight;
395:
396:                    double distance_ground = JTS.orthodromicDistance(
397:                            new Coordinate(newCsLatLong[0], newCsLatLong[1]),
398:                            new Coordinate(newCsLatLong[2], newCsLatLong[3]),
399:                            DefaultGeographicCRS.WGS84);
400:                    double pixel_distance = Math
401:                            .sqrt((image_max_x - image_min_x)
402:                                    * (image_max_x - image_min_x)
403:                                    + (image_max_y - image_min_y)
404:                                    * (image_max_y - image_min_y));
405:                    double pixel_distance_m = pixel_distance / DPI * 2.54
406:                            / 100.0;
407:                    return distance_ground / pixel_distance_m;
408:                    // remember, this is the denominator, not the actual scale;
409:                }
410:
411:                // simboss:
412:                // this way we never ran into problems with lat,lon lon,lat
413:                double diagonalGroundDistance = JTS.orthodromicDistance(
414:                        new Coordinate(csLatLong[0], csLatLong[1]),
415:                        new Coordinate(csLatLong[2], csLatLong[3]),
416:                        DefaultGeographicCRS.WGS84);
417:                // pythagorus theorm
418:                double diagonalPixelDistancePixels = Math.sqrt(imageWidth
419:                        * imageWidth + imageHeight * imageHeight);
420:                double diagonalPixelDistanceMeters = diagonalPixelDistancePixels
421:                        / DPI * 2.54 / 100; // 2.54 = cm/inch, 100= cm/m
422:                return diagonalGroundDistance / diagonalPixelDistanceMeters;
423:                // remember, this is the denominator, not the actual scale;
424:            }
425:
426:            final static double OGC_DEGREE_TO_METERS = 6378137.0 * 2.0 * Math.PI / 360;
427:
428:            /**
429:             * This method performs the computation using the methods suggested by the OGC SLD specification, page 26.
430:             * @param envelope
431:             * @param imageWidth
432:             * @return
433:             */
434:            public static double calculateOGCScale(ReferencedEnvelope envelope,
435:                    int imageWidth, Map hints) {
436:                // if it's geodetic, we're dealing with lat/lon unit measures
437:                if (envelope.getCoordinateReferenceSystem() instanceof  GeographicCRS) {
438:                    return (envelope.getWidth() * OGC_DEGREE_TO_METERS)
439:                            / (imageWidth / getDpi(hints) * 0.0254);
440:                } else {
441:                    return envelope.getWidth()
442:                            / (imageWidth / getDpi(hints) * 0.0254);
443:                }
444:            }
445:
446:            /**
447:             * First searches the hints for the scale denominator hint otherwise calls 
448:             * {@link #calculateScale(Envelope, CoordinateReferenceSystem, int, int, double)}.  If
449:             * the hints contains a DPI then that DPI is used otherwise 90 is used (the OGS default).
450:             */
451:            public static double calculateScale(ReferencedEnvelope envelope,
452:                    int imageWidth, int imageHeight, Map hints)
453:                    throws TransformException, FactoryException {
454:
455:                if (hints != null
456:                        && hints.containsKey("declaredScaleDenominator")) {
457:                    Double scale = (Double) hints
458:                            .get("declaredScaleDenominator");
459:                    if (scale.doubleValue() <= 0)
460:                        throw new IllegalArgumentException(
461:                                "the declaredScaleDenominator must be greater than 0, was: "
462:                                        + scale.doubleValue());
463:                    return scale.doubleValue();
464:                }
465:
466:                return calculateScale(envelope, imageWidth, imageHeight,
467:                        getDpi(hints));
468:            }
469:
470:            /**
471:             * Either gets a DPI from the hints, or return the OGC standard, stating that a pixel is 0.28 mm
472:             * (the result is a non integer DPI...)
473:             * @param hints 
474:             * @return DPI as doubles, to avoid issues with integer trunking in scale computation expression
475:             */
476:            private static double getDpi(Map hints) {
477:                if (hints != null && hints.containsKey("dpi")) {
478:                    return ((Integer) hints.get("dpi")).intValue();
479:                } else {
480:                    return 25.4 / 0.28; // 90 = OGC standard
481:                }
482:            }
483:
484:            /**
485:             * Find the scale denominator of the map. Method: 1. find the diagonal
486:             * distance (meters) 2. find the diagonal distance (pixels) 3. find the
487:             * diagonal distance (meters) -- use DPI 4. calculate scale (#1/#2)
488:             * 
489:             * NOTE: return the scale denominator not the actual scale (1/scale =
490:             * denominator)
491:             * 
492:             * TODO: (SLD spec page 28): Since it is common to integrate the output of
493:             * multiple servers into a single displayed result in the web-mapping
494:             * environment, it is important that different map servers have consistent
495:             * behaviour with respect to processing scales, so that all of the
496:             * independent servers will select or deselect rules at the same scales. To
497:             * insure consistent behaviour, scales relative to coordinate spaces must be
498:             * handled consistently between map servers. For geographic coordinate
499:             * systems, which use angular units, the angular coverage of a map should be
500:             * converted to linear units for computation of scale by using the
501:             * circumference of the Earth at the equator and by assuming perfectly
502:             * square linear units. For linear coordinate systems, the size of the
503:             * coordinate space should be used directly without compensating for
504:             * distortions in it with respect to the shape of the real Earth.
505:             * 
506:             * NOTE: we are actually doing a a much more exact calculation, and
507:             * accounting for non-square pixels (which are allowed in WMS) ADDITIONAL
508:             * NOTE from simboss: I added soe minor fixes. See below.
509:             * 
510:             * @param envelope
511:             * @param imageWidth
512:             * @param imageHeight
513:             * @param DPI
514:             *            screen dots per inch (OGC standard is 90)
515:             * 
516:             * 
517:             * TODO should I take into account also the destination CRS? Otherwise I am
518:             * just assuming that the final crs is lon,lat that is it maps lon to x (n
519:             * raster space) and lat to y (in raster space).
520:             * @throws TransformException
521:             * @throws FactoryException
522:             * 
523:             */
524:            public static double calculateScale(ReferencedEnvelope envelope,
525:                    int imageWidth, int imageHeight, double DPI)
526:                    throws TransformException, FactoryException {
527:
528:                final double diagonalGroundDistance;
529:                if (!(envelope.getCoordinateReferenceSystem() instanceof  EngineeringCRS)) { // geographic or cad?
530:                    // //
531:                    //
532:                    // get CRS2D for this referenced envelope, check that its 2d
533:                    //
534:                    // //
535:                    final CoordinateReferenceSystem tempCRS = CRSUtilities
536:                            .getCRS2D(envelope.getCoordinateReferenceSystem());
537:                    // make sure the crs is 2d
538:                    envelope = new ReferencedEnvelope((Envelope) envelope,
539:                            tempCRS);
540:                    MathTransform toWGS84 = StreamingRenderer.getMathTransform(
541:                            tempCRS, GeogCRS);
542:
543:                    // //
544:                    // Try to compute the source crs envelope, either by asking CRS or
545:                    // by trying to project the WGS84 envelope (world) to the specified
546:                    // CRS
547:                    // //
548:                    GeneralEnvelope sourceCRSEnvelope = (GeneralEnvelope) CRS
549:                            .getEnvelope(tempCRS);
550:                    if (sourceCRSEnvelope == null) {
551:                        try {
552:                            // try to compute the envelope by reprojecting the WGS84
553:                            // envelope
554:                            sourceCRSEnvelope = CRS
555:                                    .transform(toWGS84.inverse(), CRS
556:                                            .getEnvelope(GeogCRS));
557:                        } catch (TransformException e) {
558:                            // for some transformatio this is normal, it's not possible
559:                            // to project the whole WGS84 envelope in many transforms,
560:                            // such
561:                            // as Mercator or Gauss (the transform does diverge)
562:                        } catch (AssertionError ae) {
563:                            // same reason as above basically.  For some 
564:                            // projections the assertion will complain.  Nothing can be done about this
565:                        }
566:                    }
567:
568:                    // //
569:                    //
570:                    // Make sure it intersect the world in the source projection by
571:                    // intersecting the provided envelope with the envelope of its crs.
572:                    //
573:                    // This will also prevent us from having problems with requests for
574:                    // images bigger than the world (thanks Dave!!!)
575:                    //
576:                    // It is important to note that I also have to update the image
577:                    // width in
578:                    // case the provided envelope is bigger than the envelope of the
579:                    // source
580:                    // crs.
581:                    // //
582:                    final GeneralEnvelope intersectedEnvelope = new GeneralEnvelope(
583:                            envelope);
584:                    if (sourceCRSEnvelope != null) {
585:                        intersectedEnvelope.intersect(sourceCRSEnvelope);
586:                        if (intersectedEnvelope.isEmpty())
587:                            throw new IllegalArgumentException(
588:                                    "The provided envelope is outside the source CRS definition area");
589:                        if (!intersectedEnvelope.equals(envelope)) {
590:                            final double scale0 = intersectedEnvelope
591:                                    .getLength(0)
592:                                    / envelope.getLength(0);
593:                            final double scale1 = intersectedEnvelope
594:                                    .getLength(1)
595:                                    / envelope.getLength(1);
596:                            imageWidth *= scale0;
597:                            imageHeight *= scale1;
598:                        }
599:                    }
600:
601:                    // //
602:                    //
603:                    // Go to WGS84 in order to see how things are there
604:                    //
605:                    // //
606:                    final GeneralEnvelope geographicEnvelope = CRS.transform(
607:                            toWGS84, intersectedEnvelope);
608:                    geographicEnvelope.setCoordinateReferenceSystem(GeogCRS);
609:
610:                    // //
611:                    //
612:                    // Instantiate a geodetic calculator for GCS WGS84 and get the
613:                    // orthodromic distance between LLC and UC of the geographic
614:                    // envelope.
615:                    //
616:                    // //
617:                    final GeodeticCalculator calculator = new GeodeticCalculator(
618:                            GeogCRS);
619:                    final DirectPosition lowerLeftCorner = geographicEnvelope
620:                            .getLowerCorner();
621:                    final DirectPosition upperRightCorner = geographicEnvelope
622:                            .getUpperCorner();
623:                    calculator.setStartingGeographicPoint(lowerLeftCorner
624:                            .getOrdinate(0), lowerLeftCorner.getOrdinate(1));
625:                    calculator.setDestinationGeographicPoint(upperRightCorner
626:                            .getOrdinate(0), upperRightCorner.getOrdinate(1));
627:                    diagonalGroundDistance = calculator
628:                            .getOrthodromicDistance();
629:                } else {
630:                    // if it's an engineering crs, compute only the graphical scale, assuming a CAD space
631:                    diagonalGroundDistance = Math.sqrt(envelope.getWidth()
632:                            * envelope.getWidth() + envelope.getHeight()
633:                            * envelope.getHeight());
634:                }
635:
636:                // //
637:                //
638:                // Compute the distances on the requested image using the provided DPI.
639:                //
640:                // //
641:                // pythagorus theorm
642:                double diagonalPixelDistancePixels = Math.sqrt(imageWidth
643:                        * imageWidth + imageHeight * imageHeight);
644:                double diagonalPixelDistanceMeters = diagonalPixelDistancePixels
645:                        / DPI * 2.54 / 100; // 2.54 = cm/inch, 100= cm/m
646:                return diagonalGroundDistance / diagonalPixelDistanceMeters;
647:                // remember, this is the denominator, not the actual scale;
648:            }
649:
650:            /**
651:             * This worldToScreenTransform method makes the assumption that the crs is
652:             * in Lon,Lat or Lat,Lon. If the provided envelope does not carry along a
653:             * crs the assumption that the map extent is in the classic Lon,Lat form. In
654:             * case the provided envelope is of type.
655:             * 
656:             * Note that this method takes into account also the OGC standard with
657:             * respect to the relation between pixels and sample.
658:             * 
659:             * @param mapExtent
660:             *            The envelope of the map in lon,lat
661:             * @param paintArea
662:             *            The area to paint as a rectangle
663:             * @param destinationCrs
664:             * @todo add georeferenced envelope check when merge with trunk will
665:             *         be performed
666:             */
667:            public static AffineTransform worldToScreenTransform(
668:                    Envelope mapExtent, Rectangle paintArea,
669:                    CoordinateReferenceSystem destinationCrs) {
670:                try {
671:
672:                    // is the crs also lon,lat?
673:                    final boolean lonFirst = CRSUtilities.getCRS2D(
674:                            destinationCrs).getCoordinateSystem().getAxis(0)
675:                            .getDirection().absolute().equals(
676:                                    AxisDirection.EAST);
677:                    final GeneralEnvelope newEnvelope = lonFirst ? new GeneralEnvelope(
678:                            new double[] { mapExtent.getMinX(),
679:                                    mapExtent.getMinY() }, new double[] {
680:                                    mapExtent.getMaxX(), mapExtent.getMaxY() })
681:                            : new GeneralEnvelope(new double[] {
682:                                    mapExtent.getMinY(), mapExtent.getMinX() },
683:                                    new double[] { mapExtent.getMaxY(),
684:                                            mapExtent.getMaxX() });
685:                    newEnvelope.setCoordinateReferenceSystem(destinationCrs);
686:
687:                    //			
688:                    // with this method I can build a world to grid transform
689:                    // without adding half of a pixel translations. The cost
690:                    // is a hashtable lookup. The benefit is reusing the last
691:                    // transform (instead of creating a new one) if the grid
692:                    // and envelope are the same one than during last invocation.
693:                    final GridToEnvelopeMapper m = (GridToEnvelopeMapper) gridToEnvelopeMappers
694:                            .get();
695:                    m.setGridRange(new GeneralGridRange(paintArea));
696:                    m.setEnvelope(newEnvelope);
697:                    return (AffineTransform) (m.createTransform().inverse());
698:
699:                } catch (IndexOutOfBoundsException e) {
700:                    return null;
701:                } catch (TransformException e) {
702:                    return null;
703:                }
704:
705:            }
706:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.