Hi,
the last FOSS4G benchmarks showed that MapServer raster
performance improved quite a bit and it's now much
better than the GeoServer 2.0.0 one.
Following a suggestion by Gabriel I've already suggested
on GT2 a small patch that would give GeoServer a 10% speedup.
That is good, but not enough.
After applying the patch I noticed that GS scalability
was still lacking, and found that the threads would still
block each other while blitting the raster data over to
the final BufferedImage.
However, if you think about it, when serving just raster
data, we don't need to copy a raster image to another
with composition, that is an un-necessary step (well,
not always, more on that later).
So I've followed that lead and hacked a very crude
proof of concept patch that would skip the rendering
architecture fully and just encode out the result
of the raster symbolizer, appropriately rescaled.
Using it works fine as long as the coverage is not
rotated and the image requested is fully within
the coverage definition area (both conditions are
satisfied in the OSGEO benchmarks).
I've then turn the benchmarks on the OSGEO server.
The speedup is impressive.
For the ECW case, here are the results in responses/s:
Threads 1 10 20 40
GS 2.0 5.0 11.1 11.2 10.5
MS FCGI 7.4 20.0 19.8 19.1
GS 2.0.1+ 5.6 12.3 12.3 11.4
GS 2.0.1csp 7.8 18.0 17.8 16.9
where:
GS 2.0.1+ is GS 2.0.1 with the patch I proposed on GT2 devel list
GS 2.0.1cps is 2.0.1+, with the attached patch applied.
As you can see we're still not at the MapServer level,
but much closer.
The other benchmarks show similar results, GS becomes around
10% slower than MapServer instead of 40-50% slower.
I find this very encouraging.
Now, how do we leverage this and what is missing?
The main idea is to have a separate rendering path that
triggers when the map context contains only one layer,
with only one active rule, with a single raster symbolizer
in it. In that case we move to the "fast path".
Instead of the current hack we'd need to open a path in
the GridCoverageRenderer that would give us the final
RenderedImage for direct encoding.
As said above the fast path coded as it is now is incomplete.
It does not work properly when:
- a solid background is requested and the raster is translucent
(this should be doable using JAI anyways no?)
- the resulting coverage is smaller than the requested image,
we'd need to add a collar around it (again, this seems doable
with JAI too, no?)
- possibly, but I'm not sure, when the coverage needs to be
rotated
- when an image layout is used, that is, when we need to overlay
a watermark, a scalebar, and so on.
The limitations do not seem too much of a hurdle to me, the
fast path would keep on covering well the common case of a
WMS raster background layer, which is usually drawn by itself.
So, what do you think?
Cheers
Andrea
--
Andrea Aime
OpenGeo - http://opengeo.org
Expert service straight from the developers.
Index: src/main/java/org/vfny/geoserver/wms/responses/DefaultRasterMapProducer.java
===================================================================
--- src/main/java/org/vfny/geoserver/wms/responses/DefaultRasterMapProducer.java (revisione 13825)
+++ src/main/java/org/vfny/geoserver/wms/responses/DefaultRasterMapProducer.java (copia locale)
@@ -5,15 +5,32 @@
package org.vfny.geoserver.wms.responses;
import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
import java.awt.Graphics2D;
+import java.awt.GraphicsConfiguration;
+import java.awt.Image;
+import java.awt.Paint;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.RenderingHints.Key;
+import java.awt.font.FontRenderContext;
+import java.awt.font.GlyphVector;
+import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
+import java.awt.image.BufferedImageOp;
+import java.awt.image.ImageObserver;
import java.awt.image.IndexColorModel;
import java.awt.image.RenderedImage;
+import java.awt.image.renderable.RenderableImage;
import java.io.File;
import java.io.OutputStream;
+import java.text.AttributedCharacterIterator;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
@@ -28,6 +45,7 @@
import javax.media.jai.InterpolationNearest;
import javax.media.jai.JAI;
import javax.media.jai.LookupTableJAI;
+import javax.media.jai.operator.AffineDescriptor;
import javax.media.jai.operator.LookupDescriptor;
import org.geoserver.platform.ServiceException;
@@ -40,13 +58,25 @@
import org.geoserver.wms.responses.MapDecorationLayout;
import org.geoserver.wms.responses.MetatiledMapDecorationLayout;
import org.geoserver.wms.responses.decoration.WatermarkDecoration;
+import org.geotools.coverage.grid.GridCoverage2D;
+import org.geotools.coverage.grid.GridEnvelope2D;
+import org.geotools.coverage.grid.GridGeometry2D;
+import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader;
+import org.geotools.coverage.grid.io.AbstractGridFormat;
import org.geotools.geometry.jts.ReferencedEnvelope;
+import org.geotools.map.DefaultMapContext;
import org.geotools.map.MapLayer;
+import org.geotools.parameter.Parameter;
+import org.geotools.renderer.lite.RendererUtilities;
import org.geotools.renderer.lite.StreamingRenderer;
+import org.geotools.renderer.lite.gridcoverage2d.GridCoverageRenderer;
import org.geotools.renderer.shape.ShapefileRenderer;
import org.geotools.styling.PointSymbolizer;
import org.geotools.styling.Style;
import org.geotools.styling.visitor.DuplicatingStyleVisitor;
+import org.opengis.feature.Feature;
+import org.opengis.parameter.GeneralParameterValue;
+import org.opengis.style.RasterSymbolizer;
import org.vfny.geoserver.global.GeoserverDataDirectory;
import org.vfny.geoserver.wms.RasterMapProducer;
import org.vfny.geoserver.wms.WMSMapContext;
@@ -192,6 +222,11 @@
} catch (Exception e) {
throw new WmsException(e);
}
+
+ if(true) {
+ directRasterRender(mapContext);
+ return;
+ }
Rectangle paintArea = new Rectangle(
0, 0,
@@ -601,5 +636,523 @@
RenderingHints hints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT, layout);
return LookupDescriptor.create(source, IDENTITY_TABLE, hints);
}
+
+ private void directRasterRender(WMSMapContext mapContext) {
+ try {
+ Feature feature = mapContext.getLayer(0).getFeatureSource().getFeatures().features().next();
+ AbstractGridCoverage2DReader reader = (AbstractGridCoverage2DReader) feature.getProperty("grid").getValue();
+ org.geotools.styling.RasterSymbolizer symbolizer = (org.geotools.styling.RasterSymbolizer)
+ mapContext.getLayer(0).getStyle().featureTypeStyles().get(0).rules().get(0).symbolizers().get(0);
+ GridCoverage2D coverage;
+
+ Rectangle paintArea = new Rectangle(0, 0, mapContext.getMapWidth(), mapContext.getMapHeight());
+ AffineTransform worldToScreen = RendererUtilities.worldToScreenTransform(mapContext.getAreaOfInterest(), paintArea);
+ final GridCoverageRenderer gcr = new GridCoverageRenderer(mapContext.getCoordinateReferenceSystem() , mapContext.getAreaOfInterest(),
+ paintArea, worldToScreen, null);
+
+ // //
+ // It is an AbstractGridCoverage2DReader, let's use parameters
+ // if we have any supplied by a user.
+ // //
+ // first I created the correct ReadGeometry
+ final Parameter<GridGeometry2D> readGG = new Parameter<GridGeometry2D>(AbstractGridFormat.READ_GRIDGEOMETRY2D);
+ readGG.setValue(new GridGeometry2D(new GridEnvelope2D(paintArea),
+ mapContext.getAreaOfInterest()));
+ // then I try to get read parameters associated with this
+ // coverage if there are any.
+ final Object params = feature.getProperty("params").getValue();
+ if (params != null) {
+ // //
+ //
+ // Getting parameters to control how to read this coverage.
+ // Remember to check to actually have them before forwarding
+ // them to the reader.
+ //
+ // //
+ GeneralParameterValue[] readParams = (GeneralParameterValue[]) params;
+ final int length = readParams.length;
+ if (length > 0) {
+ // we have a valid number of parameters, let's check if
+ // also have a READ_GRIDGEOMETRY2D. In such case we just
+ // override it with the one we just build for this
+ // request.
+ final String name = AbstractGridFormat.READ_GRIDGEOMETRY2D
+ .getName().toString();
+ int i = 0;
+ for (; i < length; i++)
+ if (readParams[i].getDescriptor().getName()
+ .toString().equalsIgnoreCase(name))
+ break;
+ // did we find anything?
+ if (i < length) {
+ //we found another READ_GRIDGEOMETRY2D, let's override it.
+ ((Parameter) readParams[i]).setValue(readGG);
+ coverage = (GridCoverage2D) reader.read(readParams);
+ } else {
+ // add the correct read geometry to the supplied
+ // params since we did not find anything
+ GeneralParameterValue[] readParams2 = new GeneralParameterValue[length + 1];
+ System.arraycopy(readParams, 0, readParams2, 0,length);
+ readParams2[length] = readGG;
+ coverage = (GridCoverage2D) reader.read(readParams2);
+ }
+ } else
+ // we have no parameters hence we just use the read grid
+ // geometry to get a coverage
+ coverage = (GridCoverage2D) reader.read(new GeneralParameterValue[] { readGG });
+ } else {
+ coverage = (GridCoverage2D) reader.read(new GeneralParameterValue[] { readGG });
+ }
+ try{
+ if(coverage!=null) {
+ FakeGraphics2D g2d = new FakeGraphics2D();
+ gcr.paint(g2d, coverage, symbolizer);
+ this.image = g2d.img;
+ }
+ } finally {
+ //we need to try and dispose this coverage since it was created on purpose for rendering
+ if(coverage!=null)
+ coverage.dispose(true);
+ }
+
+ } catch(Exception e) {
+ throw new WmsException(e);
+ }
+
+
+ }
+
+
+ class FakeGraphics2D extends Graphics2D {
+
+ private RenderedImage img;
+
+ @Override
+ public void addRenderingHints(Map<?, ?> hints) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void clip(Shape s) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void draw(Shape s) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void drawGlyphVector(GlyphVector g, float x, float y) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void drawRenderableImage(RenderableImage img, AffineTransform xform) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void drawRenderedImage(RenderedImage img, AffineTransform xform) {
+ image = AffineDescriptor.create(img, xform, new InterpolationNearest(), null, null);
+
+ this.img = img;
+ }
+
+ @Override
+ public void drawString(String str, int x, int y) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void drawString(String s, float x, float y) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void drawString(AttributedCharacterIterator iterator, int x, int y) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void drawString(AttributedCharacterIterator iterator, float x, float y) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void fill(Shape s) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public Color getBackground() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Composite getComposite() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public GraphicsConfiguration getDeviceConfiguration() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public FontRenderContext getFontRenderContext() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Paint getPaint() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Object getRenderingHint(Key hintKey) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public RenderingHints getRenderingHints() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Stroke getStroke() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public AffineTransform getTransform() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public boolean hit(Rectangle rect, Shape s, boolean onStroke) {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public void rotate(double theta) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void rotate(double theta, double x, double y) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void scale(double sx, double sy) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setBackground(Color color) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setComposite(Composite comp) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setPaint(Paint paint) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setRenderingHint(Key hintKey, Object hintValue) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setRenderingHints(Map<?, ?> hints) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setStroke(Stroke s) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setTransform(AffineTransform Tx) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void shear(double shx, double shy) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void transform(AffineTransform Tx) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void translate(int x, int y) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void translate(double tx, double ty) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void clearRect(int x, int y, int width, int height) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void clipRect(int x, int y, int width, int height) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void copyArea(int x, int y, int width, int height, int dx, int dy) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public Graphics create() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void dispose() {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public boolean drawImage(Image img, int x, int y, ImageObserver observer) {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer) {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public boolean drawImage(Image img, int x, int y, int width, int height,
+ ImageObserver observer) {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor,
+ ImageObserver observer) {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1,
+ int sx2, int sy2, ImageObserver observer) {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1,
+ int sx2, int sy2, Color bgcolor, ImageObserver observer) {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public void drawLine(int x1, int y1, int x2, int y2) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void drawOval(int x, int y, int width, int height) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void fillOval(int x, int y, int width, int height) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void fillRect(int x, int y, int width, int height) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public Shape getClip() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Rectangle getClipBounds() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Color getColor() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Font getFont() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public FontMetrics getFontMetrics(Font f) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void setClip(Shape clip) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setClip(int x, int y, int width, int height) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setColor(Color c) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setFont(Font font) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setPaintMode() {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setXORMode(Color c1) {
+ // TODO Auto-generated method stub
+
+ }
+
+ }
+
}
------------------------------------------------------------------------------
This SF.Net email is sponsored by the Verizon Developer Community
Take advantage of Verizon's best-in-class app development support
A streamlined, 14 day to market process makes app distribution fast and easy
Join now and get one step closer to millions of Verizon customers
http://p.sf.net/sfu/verizon-dev2dev
_______________________________________________
Geoserver-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/geoserver-devel