Map printing

Topics: Algorithms, SharpMap Project
Feb 26, 2012 at 4:47 PM

Hi again,

I just written a method for printing a map. I have intention to submit my code for a possible integration in sharpmap, but first I explain my method:


The issue when printing is that you can't print the map as it is, but you have to consider that the scale in pixel is different from the scale in dpi, but it isn't enough. You can also try to double or triple the size of the map to enhance the quality but the image in RAM can result too big (consider printing on a plotter)

My function takes the size of the paper on which you want to print, than divides the map in "i" columns and "j" rows (10 cols and 10 rows for instance), then for each piece I call the ZoomToBox method to gain the maximum details of the entire map for each piece (each piece has fixed dimensions I compute in function of the papersize), and at last I print into the printer's e.Graphics object a piece at time for don't run low of RAM.

The code is very easy, I see there isn't a defined method for print a map (maybe I wrong), so this is my idea...

What do you think of it?

Feb 27, 2012 at 6:57 AM

Sounds Good!

When printing vector-data it would be great to be able to print vectors and let the printer render instead of rendering at application-level.

There is a method GetMapAsEMF that renders the map as an EMF image, if you paste that image into a Word Document you get really high-quality printing. But to be able to use that, i think we would need to spool data as postscript to the printer somehow.. 

This is not my area of knowledge though...

Feb 27, 2012 at 9:05 AM

I've been thinking about this for a while and would propose to seperate existing Map class into

  • Map
    holds all layer(collections) and global things like extent, spatial reference system, title

  • MapView
    which has all the things related to rendering in it. Thus we can create a MapPrintDocument : PrintDocument class that renders directly using the printers graphics object. The only issue I see is that you may need to split GdalRasterLayers into tiles since some printer drivers may not cope with huge images.

Hth FObermaier

Feb 27, 2012 at 11:12 AM
Edited Feb 27, 2012 at 11:13 AM

Yes you have to split GdalRasterLayers into tiles, but it can be not enough, since the image can result too grainy. I don't know how DrawImage works, but it seems doing a scale conversion. If you want to see my code is this:

        SharpMap.Map M; //my map
        void printDoc_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
            //e.Graphics quality stuff
            e.Graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
            e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
            e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;

            //I assumed to print the entire map, but this can be a parameter of the function
            SharpMap.Geometries.BoundingBox Bbox = M.GetExtents(); 

            //Tot pieces in which I want to divide the map
            int TotalRows = 10;
            int TotalCols = 10;

            //Indicates how much X and Y coordinates have to move to print the entire map
            float StepX = (float)((Bbox.Max.X - Bbox.Min.X) / TotalRows);
            float StepY = (float)((Bbox.Max.Y - Bbox.Min.Y) / TotalCols);

            //Size of the paper on which I want to print
            float PrinterWidth = e.PageSettings.Bounds.Width - e.MarginBounds.Left - (e.PageSettings.Bounds.Width - e.MarginBounds.Right);
            float PrinterHeight = e.PageSettings.Bounds.Height - e.MarginBounds.Top - (e.PageSettings.Bounds.Height - e.MarginBounds.Bottom);

            //A bitmap that stores each piece of the map
            System.Drawing.Bitmap B = new System.Drawing.Bitmap((int)StepX, (int)StepY);

            //New map
            SharpMap.Map M1 = new SharpMap.Map(B.Size);
            foreach (SharpMap.Layers.ILayer L in M.Layers) //copy all the layers in a new Map
                if (L is SharpMap.Layers.LabelLayer)
                    ((SharpMap.Layers.LabelLayer)L).MaxVisible = 0; //if you want to print all labels in the LabelLayer, this can be a parameter of the function

            //Calculates the size ratio between the map and the paper size. This ratio have to be the maximum possible, and for better quality have to be <= 1
            float ratio = Math.Max((StepX * TotalRows) / PrinterWidth, (StepY * TotalCols) / PrinterHeight);
            for (float i = 0; i < TotalRows; i++)
                for (float j = 0; j < TotalCols; j++)
                    //Takes a "picture" of a piece of the map, in original dimensions
                    SharpMap.Geometries.BoundingBox BX = new SharpMap.Geometries.BoundingBox(Bbox.Min.X + StepX * i, Bbox.Min.Y + StepY * j, Bbox.Min.X + StepX * i + StepX, Bbox.Min.Y + StepY * j + StepY);

                    //new Bitmap that stores the piece, in original dimensions
                    B = new System.Drawing.Bitmap((int)StepX, (int)StepY);

                    //some Graphics quality stuff
                    System.Drawing.Graphics G = System.Drawing.Graphics.FromImage(B);
                    G.PageUnit = System.Drawing.GraphicsUnit.Display; //may be not necessary
                    G.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
                    G.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
                    G.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                    //renders the map as it is into my Bitmap B
                    M1.RenderMap(G, true);
                    G = null;
                    //renders my piece in a computed point on the paper and in a computed size
                    System.Drawing.PointF pnt= new System.Drawing.PointF(e.PageSettings.Margins.Left + e.PageSettings.Margins.Right + i * (B.Width / ratio), PrinterWidth - e.PageSettings.Margins.Bottom - e.PageSettings.Margins.Top - j * (B.Height / ratio));
                    System.Drawing.SizeF sz=new System.Drawing.SizeF(StepX / ratio, StepY / ratio);
                    e.Graphics.DrawImage(B, new System.Drawing.RectangleF(pnt,sz));
                    B = null;
            e.HasMorePages = false;