Hi Martin, hi all,
I checked deeply the part of the GeoServer and GeoTools which is responsible for rendering features and I think I might have found myself the answer to my doubts. As I wrote in one previous email, I think that the worldToScreenTransform of the org.geotools.renderer.lite.RendererUtilities class makes an erroneus assumption about the CRS and the same thing happens for the createMapEnvelope method. I was wondering how things could work smoothly when rendering features in StreamingRenderer therefore I tracked down a complete WMS request from the GetMapRequest to the pain method in the StreamingRenderer using the states shapefile as target. I rely on the EPSG-HSQL crs datastore.
When requesting the states layer from Geoserver with crs set to EPSG:4326 in parseMandatoryParameters of the GetMapKvpReader we parse the WMS BBOX parameters (which is by default lon,lat) and wew set up a lon,lat JTS Envelope (which has no information about the crs) but in the subsequent parseOptionalParameters method of the same class we do as follows:
CoordinateReferenceSystem mapcrs = CRS.decode(epsgCode);
request.setCrs(mapcrs);
The resulting mapcrs WKT is
GEOGCS["WGS 84",
DATUM["World Geodetic System 1984",
SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]],
AUTHORITY["EPSG","6326"]],
PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]],
UNIT["degree", 0.017453292519943295],
AXIS["Geodetic latitude", NORTH],
AXIS["Geodetic longitude", EAST],
AUTHORITY["EPSG","4326"]]
As you can see the axes are swapped with respect to the envelope we set up before.
In the execute method of the GetMapResponse we set up the StreamingRenderer for producing the map. We get the JTS Envelope and the CRS we set up before when parsing the GetMap request, we load all the layers with the respective styles, we get a map producer, depending on the output format (png, jpeg, etc..) and we produce the map.
Now we are in the DefaultRastermapProducer. In the produceMap method we do, more or less, as follows,
final int width = map.getMapWidth();
final int height = map.getMapHeight();
BufferedImage curImage = new BufferedImage(width, height,BufferedImage.TYPE_4BYTE_ABGR);
Rectangle paintArea = new Rectangle(width, height);
Envelope dataArea = map.getAreaOfInterest();
AffineTransform at = RendererUtilities.worldToScreenTransform(dataArea, paintArea);
renderer.paint(graphic, paintArea, at);
In my opinion the affine tranform we build in worldToScreenTransform is wrong since the associated CRS is lat,lon but the Envelope is lon,lat. Despite to what I just said features are rendered smoothly and I also would like to say that performances are dramatically improved! In order to clarify things better, I dag deeper into the StreamingRenderer for features.
The interesting method is the paintprocessSymbolizers method of the StreamingrRenderer class. Here I have the features to render along with the styels and the worldToScreenTransform. In my case I was rendering two multypoligons.
When inspecting the geometry associated I noticed that they were MultyPoligon as expected but I aso noticed the the coordinates of the objects where in lon,lat while again the associated crs was the canonical EPSG:4326 with lat,lon.
Someone, plsease, correct me if I am wrong here, but I think that everything works smoothly because basically when features are involved we ALWAYS make the assumption that we have lon,lat regardless of the CRS. Is this right or there exists another explanation?
I would like one of the lead developer of the GeoServer to spend 10 minutes on this issue because it might be important especially if someone (like me ) tries to extend geoserver to serve raster which makes NO assumptions on the order of the axes for the CRS. I think those 10 minutes would be well spent (I have to admit that, especially after Martin's email, I am quite surprised that no GeoServer developer dropped even 2 lines to answer).
Thanks everybody, and sorry for stealing your time wth such a long email.
Simone.
PS
Martin I will respond to your email in a successive email.
----- Original Message ----- From: "Martin Desruisseaux" <martin.desruisseaux@anonymised.com>
To: "Simone Giannecchini" <simboss1@anonymised.com>
Cc: "Jody Garnett" <jgarnett@anonymised.com>; <geotools-devel@lists.sourceforge.net>; <geoserver-devel@lists.sourceforge.net>
Sent: Sunday, February 19, 2006 9:07 AM
Subject: Re: [Geotools-devel] Fwd: Geoserver and crs
Simone Giannecchini a écrit :
Coudl you spend 5 minutes and take a look? I am trying to improve the
rendering of raster in our GeoServer and all this confusion about axis
order is a real pain for me. I fixed the problem writing a new
worldToScreenTransform method which takes into account the CRS without
making any assumptions.
I looked at RenderUtilities.worldToScreenTransform and you are right,
this method do not take axis order in account. This method expect a JTS
envelope, which do not carry CRS information, so this method had to
assumes a (longitude,latitude) order (unless we had an explicit CRS
argument to this method).
Proposed solutions lower in this email.
I think the source of all my problems is the usage of the JTS envelope
plus epsg_wkt. The JTS envelope does not carry along a crs while the
espg_wkt uses lon,lat instead of lat,lon. In the version of the WCS we
are building right now we have removed dependecies from JTS.Envelope
everywhere but a lot of them remain in the WMS for features. The point
of all this discussion is see if we can reamove this depency and adopt
the GeneralEnvelope as we do in the WCS.
I believe too that JTS + epsg-wkt is contributing to the axis order
mismatch. Using the OpenGIS envelope instead of JTS would probably be
safer. If a user need interoperability between JTS Envelope and GeoAPI
Envelope, he may consider ReferencedEnvelope, which is a JTS envelope
implementing the GeoAPI interface:
http://javadoc.geotools.fr/2.2/org/geotools/geometry/jts/ReferencedEnvelope.html
So if we don't want to break our JTS dependency right now, we could
replace JTS envelope by ReferencedEnvelope as a migration path.
Note that we should encourage usage of GeoAPI's Envelope interface when
possible instead of some specific implementation like GeneralEnvelope.
If the GeoAPI interface need to be expanded with some more API in order
to make it more usable, the timing is good for proposing it. We can
expand section 3.3 in the document attached there:
http://jira.codehaus.org/browse/GEO-76
All the test I have done were performed using epsg-hsql. I can
confidently state that if the crs is EPSG:4326 and comes from
ESPG-HSQL the actual worldToScreen Transform failes. Let's see how
this happens:
public static AffineTransform worldToScreenTransform(Envelope mapExtent, Rectangle paintArea) {
[...snip...]
This is the incriminated method. As you can easily see from the
computation of scaleX, the assumption that the CRS for the supplied
mapExtent (a JTS Envelope) is LON,LAT hence this method fails if the
CRS is LAT,LON.
Right. Possible fixes are:
1) Add a CoordinateReferenceSystem argument (or replace the Envelope
argument by ReferencedEnvelope). Inspect the coordinate system axis.
If an axis with AxisOrientation.SOUTH | NORTH appears before an axis
with AxisOrientation.EAST | WEST, replace the following line:
new AffineTransform(scaleX, 0.0d, 0.0d, -scaleY, tx, ty);
by
new AffineTransform(0.0d, -scaleY, scaleX, 0.0d, tx, ty);
2) Add a CoordinateReferenceSystem argument for *both* the Envelope and
the Rectangle arguments. This is closer to the GO-1 specification.
In GO-1 vocabulary, the Envelope CRS is the "objective CRS" while
the Rectangle CRS is the "display CRS". Axis swapping can be
determined by the following code:
CoordinateSytsem objectiveCS = objectiveCRS.getCoordinateSystem();
CoordinateSystem displayCS = displayCRS.getCoordinateSystem();
Matrix swap = AbstractCS.swapAndScaleAxis(objectiveCS, displayCS);
This is more generic than #1 above; it work for an arbitrary number
of dimensions, can handle non-geographic cases like (latitude,time)
axis (we sometime use that in oceanography), and can performs unit
conversions as well as axis swaping. In the specific case of 2D CRS,
the matrix need to be converted into an AffineTransform (Matrix3 in
the org.geotools.referencing.operation.matrix may be of some help)
and concatenated with the AffineTransform computed by
worldToScreenTransform. Note that the later should be renamed
objectiveToDisplayTransform for GO-1 compliance.
The drawnback is that this approach requires that the renderer carry
around the concept of display CRS as well as objective CRS. I'm not
sure that the StreamingRenderer do that.
3) An other possible approach is to help me with the implementation of
GO-1 renderer. The GO-1 renderer (in ext/go module) is a refactoring
of the old J2D-renderer, which was fast (except for startup time) and
have some features not found in StreamingRenderer (like displaying a
map scale), but had a number of inconvenient like complexity, lack of
SLD support, memory usage, etc. This renderer was designed with great
attention to CRS (understandably, since it was wrote by the same
author than the referencing module) and was handling axis swapping
right. Javadoc for the GO-1 port:
http://javadoc.geotools.fr/2.2/org/geotools/display/canvas/package-summary.html
Note that the base classes are designed for supporting 3D rendering
engines as well as 2D :). The inconvenient of working on GO-1
renderer is that it may take a while before it get the same set of
functionalities than StreamingRenderer for non-rasters graphics.
I plan to "steal" code for StreamingRenderer, but help from someone
who know the StreamingRenderer would certainly make the work faster.
Martin.