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: }
|