Export to Image

Topics: SharpMap v0.9 / v1.x, WinForms Controls
May 13, 2010 at 1:23 PM
Edited May 13, 2010 at 1:23 PM
Hi, I am trying to provide "Save As" and Print features in my application. Till now the only way I figured to get a Bitmap of the map is using the GetMap() function. However, I must say that the Images are not of very good quality. To my surprise they don't look as good as what is visible on screen. What would be the Graphics properties I need to set before calling GetMap() to ensure that I get the best quality image. Secondly, are there any extensions available which would allow exporting features to vector image formats such as SVG or EPS. Thanks, Raghu
Developer
May 14, 2010 at 7:17 PM
hmm, I would expect the same result. Do you request it on the current map object that is used in the MapControl, with the same Width and Height? In what image format do you save to disk? There is no support for SVG type rendering in V1. It is planned for V2, and the V1 Silverlight branch will use XAML for that. Paul
Coordinator
May 15, 2010 at 8:32 AM

Hi Raghu, GetMap by default will return an image at the screen resolution which is usually 96dpi. For print you may want up to ~300dpi. You can try just scaling the map width and height up before calling GetMap() and printing that before resetting for normal scrren interaction.. hth jd

May 15, 2010 at 3:33 PM
Edited May 15, 2010 at 3:39 PM

Hi John,

Scaling does not seem to be working because the Images have gotten worse. The Page size to which the map is being printed is smaller than what is visible on screen, especially the width. I tried adding another overloaded GetMap method which would simply accept a Graphics instance and draw onto it. This way, I could have better control over things like Resolution, Smoothing, Compositing Mode and Compositing quality.

But this requires the Layer's Render method to be agnostic to the Maps size. I am creating a Bitmap in memory and setting it's resolution to 300 DPI. Then I am creating a Graphics instance off this Bitmap and passing it to the newly overloaded GetMap method. But, still the image seems to be drawn using the Size of the Map object.

Looks like I have hit a dead end unless I make some changes to VectorRenderer, Map, VectorLayer, LabelLayer etc in SharpMap.

May 15, 2010 at 3:38 PM
pauldendulk wrote:
hmm, I would expect the same result. Do you request it on the current map object that is used in the MapControl, with the same Width and Height? In what image format do you save to disk? There is no support for SVG type rendering in V1. It is planned for V2, and the V1 Silverlight branch will use XAML for that. Paul

Hi Paul,

No, I create a new Map object and copy the layers from the source map. This is because, I need the map to fit in to whatever size the user chooses in the Print Settings. The Images are saved in TIFF format.

When you say, v1 Silverlight branch will use XAML, how does one view it unless it is compiled into an XAP?

Cheers,
Raghu

Jun 5, 2010 at 6:23 AM
Edited Jun 5, 2010 at 6:25 AM

I have found another workaround using PDFSharp library to draw the map onto a PDF Page directly.

The good thing about PDFSharp is that, it has a XGraphics class which you can create for a page as in XGraphics.CreateFromPdfPage(page), which is very similar to Graphics.CreateFromImage(img). This XGraphics class has all the drawing methods that the Graphics class has. There are minor differences but one can work around them. The result is a PDF document with vector shapes!

My primary goal of printing was actually to produce PDFs, not printing onto a paper! Now the problem seems to be solved.

However, this effort has resulted in me having to fork the current SharpMap v0.9 trunk and create overloaded methods of Map.Render to accept XGraphics as well. Moreover, since I am currently only interested in VectorLayer and LabelLayer, I have implemented them for these layers only!

If anyone is interested, I can post the patch here.

Cheers,
Raghu

Sep 28, 2010 at 10:02 AM

Hi Raghu,

Can you post your code here? I'm interested to see how you implemented printing with PDFSharp.

Thanks in advance!

-ghelo

Oct 3, 2010 at 2:13 PM
Edited Oct 3, 2010 at 2:28 PM

Hello ghelobytes,

As indicated already in my previous post, the changes I have done are in SharpMap v0.9 source itself. Anyway, here it is, for the benefit of the people who may want to use it:

https://docs.google.com/leaf?id=0BzwZB6Myjo23YjNhMDVkZWYtOGNjZC00OTgzLWJlZjktMzZjYTBhZmJkYzU0&hl=en

That link contains the source of SharpMap v0.9 with the PDF Rendering enhancements.

The major classes of interest for you to examine PDF Printing would be XGraphicsUtils.cs and XGraphicsVectorRenderer.cs. Apart from these, I have added overloads to the Render method with the signature Render(XGraphics g) to VectorLayer.cs and LabelLayer.cs. If you want this functionality for other layers such as WmsLayer, TiledWmsLayer etc then you will have to implement it yourself!

Below is the helper class which is used to take a Map instance and create a vector PDF Document:

    internal static class PDFPrinter
    {
        private const int Margin = 20;

        internal static void GeneratePDF(Map mapCtrl, string pdfFileName, PageSize pageSize)
        {
            if (pageSize == PageSize.Undefined)
                throw new ArgumentException("PageSize cannot be Undefined.", "pageSize");

            GeneratePDF(mapCtrl, pdfFileName, pageSize, 0, 0, XGraphicsUnit.Inch);
        }

        internal static void GeneratePDF(Map mapCtrl, string pdfFileName, PageSize pageSize, 
            double width, double height, XGraphicsUnit pageUnits)
        {
            PdfDocument doc = new PdfDocument();
            doc.Options.CompressContentStreams = true;
            doc.Options.NoCompression = false;

            PdfPage page = doc.AddPage();
            page.Size = pageSize;

            if (pageSize == PageSize.Undefined)
            {
                page.Width = new XUnit(width, pageUnits);
                page.Height = new XUnit(height, pageUnits);
            }

            DrawMapOnPage(mapCtrl, page);

            doc.Save(pdfFileName);
        }

        private static void DrawMapOnPage(Map mapCtrl, PdfPage page)
        {
             Size size = PageSizeConverter.ToSize(page.Size).ToSizeF().ToSize(); size = new Size(size.Width - totMargin, size.Height - totMargin); XRect titleRect, mapRect, legendRect = XRect.Empty; //1.8% of page height for Title titleRect = new XRect(Margin, Margin, size.Width, (double)size.Height * 0.018d);

            //80% of page height for Map mapRect = new XRect(Margin, Margin + titleRect.Height, size.Width, (double)size.Height * 0.8d); //18.2% of page height for Legend legendRect = new XRect(Margin, Margin + titleRect.Height + mapRect.Height, size.Width, (double)size.Height * 0.182d);

            using (XGraphics g = XGraphics.FromPdfPage(page)) { XFont xFont = mapCtrl.Font; using (XForm xForm = new XForm(g, titleRect.Size)) { using (XGraphics xg = XGraphics.FromForm(xForm)) { xg.DrawString(mapCtrl.MapTitle, xFont, XBrushes.Black, 0d, 10d); } g.DrawImage(xForm, titleRect); } //Calculate the size allowed for the map size = mapRect.Size.ToSizeF().ToSize(); using (Map tempMap = mapCtrl.CloneToSize(size)) { ILayer[] layers = new ILayer[tempMap.Layers.Count]; for (int i = 0; i < tempMap.Layers.Count; i++) layers[i] = tempMap.Layers[i];

                    foreach (ILayer layer in layers)
                    { using (XForm xForm = new XForm(g, mapRect.Size)) { using (XGraphics xg = XGraphics.FromForm(xForm)) { layer.Render(xg, tempMap); } g.DrawImage(xForm, mapRect); } }); }

               
using (XForm xForm = new XForm(page.Reference.Document, legendRect.Size)) { using (XGraphics xg = XGraphics.FromForm(xForm)) {
                      // Draw the legend here
                   }
                    g.DrawImage(xForm, legendRect);
                }
            }
       }
 }

In my case I do not use the MapBox control directly. There is yet another Inherited control from this which has more app specific GUI features such as Title of the Map, a legend at the bottom and a toolbar to do stuff like Fit to Window, Zoom In, Zoom Out, Set Opacity etc. Therefore the above helper class actually receives an instance of this inherited control, which already has the Title, Legends and of course the Map instance.

So the helper class above might not even compile, because I have made changes to it after I pasted it into the post. But hopefully it will get you started on how to render maps into PDFs using SharpMap and PdfSharp!

HTH,

Cheers
Raghu

Oct 4, 2010 at 3:00 AM

Hi Raghu,

I have several questions if you don't mind.

  1. 1. Is CloneToSize your own implementation?
  2. In your sample code above, where does totMargin come from?
  3. Also in the sample code above, is mapCtrl an instance of Sharpmap.Map class? If so, then mapCtrl.Font and mapCtrl.MapTitle properties must be your own addition to the class right?

Thank you for providing the code. I'm trying to run a sample based on it. I hitting some snag though. =)

Thanks in advance!

-ghelo

Coordinator
Oct 4, 2010 at 8:37 AM

hello ghelo,

the SharpMap Map object offers a Render class, which takes a graphics object as argument.

If you derive a class from System.Drawing.Printing.PrintDocument you can use that easyly to print your map:

  • Store original Map.Size in overloaded OnBeginPrint event
  • Scale your Map.Size in the OnQueryPageSettings event,
  • Call Map.Render(e.Graphics) in overloaded OnPrintPage event
  • Reset Map.Size in overloaded OnEndPrint event.

You don't have a map title and a legend then. Also you may need to change the Map.Render method so it takes a System.Drawing.Matrix to take into account possible margin changes.

@Raghu: thanks for sharing your code.

 

Hth FObermaier

Oct 4, 2010 at 10:08 AM

Hi ghelobytes,

Here are the answers to your questions:

1. Is CloneToSize your own implementation?

Yes. All the method does is, instantiate a new Map object with the desired size. It then adds all the layers in the Layers collection of the source Map to the newly created instance. The reason I have implemented this way is because, while printing, if I modify the size of the source map itself, the map being rendered on the screen also changes its size once the printing is done! So, I always create a new Map instance with the desired size and use it to do the rendering onto PDF.

2. In your sample code above, where does totMargin come from?

As I mentioned in my post, the PDFRenderer I have working does not work with the Map object directly. The input parameter is actually a MapControl instance which has customized GUI for Title, Legend, Toolbars etc. In fact I have another control (MapEditControl), which further inherits from MapControl and provides some additional functionality to edit polygons! Therefore, all these missing properties or info you find in PDFRenderer, I actually get from MapControl. Sorry, if it has caused any confusions.

I would have been willing to give the source code for these controls as well. But unfortunately these controls currently are tightly coupled with the applications business logic. May be I will create these controls as standalones someday and push them out as a library here on SharpMap project for anyone who wants to use it.

3. Also in the sample code above, is mapCtrl an instance of Sharpmap.Map class? If so, then mapCtrl.Font and mapCtrl.MapTitle properties must be your own addition to the class right?

Yes. I modified it to make the input parameter an instance of SharpMap.Map class. But anyway, as you have already pointed out, the code has residues of my custom MapControl instance as well!

Dear FObermaier,

Is the Render class you mentioned part of Sharpap v0.9? If yes, then there is some serious reflection to be done on my implementation!

Secondly, the implementation you are suggesting will only create a bitmap (raster) image of the map, whereas, my implementation actually produces a vector PDF.

Cheers,

Raghu

Coordinator
Oct 4, 2010 at 3:07 PM
Edited Oct 4, 2010 at 3:12 PM
kraghavk wrote:

Is the Render class you mentioned part of Sharpap v0.9? If yes, then there is some serious reflection to be done on my implementation!

Render is not a class, its a method from the SharpMap.Map class. It is internally called by the SharpMap.GetMap() function. Depending on the revision you started from, it may not have been there, but it definately is there now.

Secondly, the implementation you are suggesting will only create a bitmap (raster) image of the map, whereas, my implementation actually produces a vector PDF.

If you follow my suggestion, you won't get an image, you will get whatever your Printer needs to print the Map. If you use the Map.GetMap() function, a graphics context is created from the image to render upon, it is passed to the Map.Render(...) function. In the printing case, a graphic context representing the printer is passed to the function (I hope that is clear). I just found out that you can set Map.MapTransform property to setup left and top offset according to your page settings.

I'll try to set up a universal MapPrintDocument class, after I've finished to study your code. Would you mind me adding your code to the repository?

Hth FObermaier

Oct 5, 2010 at 11:45 AM

Thank you kraghavk & FObermaier for your thoughts on this. These has been a really fruitful discussion.

I just have one more question: How to I calculate the map scale on the printed map? Is this possible at all?

Coordinator
Oct 5, 2010 at 1:58 PM

Hello ghelo,

[Query|Default]PageSettings have the properties PaperSize, Margins and PrinterResolution. PageSize and Margins are given in 100/Inches, the Resolution is given in DotsPerInch. With these values you should be able to calculate the scale or set the map size to print at a desired scale.

Hth FObermaier

Oct 7, 2010 at 12:01 PM

Thank you FObermaier... and to those interested on map scales, this link seems to be a good reference.

Coordinator
Oct 8, 2010 at 6:28 AM

Hello ghelo, since you seem to have some working code, how about sharing it? Please submit a patch.

thanks FObermaier

Oct 8, 2010 at 7:29 AM
FObermaier wrote:
kraghavk wrote:

Is the Render class you mentioned part of Sharpap v0.9? If yes, then there is some serious reflection to be done on my implementation!

Render is not a class, its a method from the SharpMap.Map class. It is internally called by the SharpMap.GetMap() function. Depending on the revision you started from, it may not have been there, but it definately is there now.

Secondly, the implementation you are suggesting will only create a bitmap (raster) image of the map, whereas, my implementation actually produces a vector PDF.

If you follow my suggestion, you won't get an image, you will get whatever your Printer needs to print the Map. If you use the Map.GetMap() function, a graphics context is created from the image to render upon, it is passed to the Map.Render(...) function. In the printing case, a graphic context representing the printer is passed to the function (I hope that is clear). I just found out that you can set Map.MapTransform property to setup left and top offset according to your page settings.

I'll try to set up a universal MapPrintDocument class, after I've finished to study your code. Would you mind me adding your code to the repository?

Hth FObermaier

Hi FObermaier,

Because you used the phrase "Render Class" in the previous post, I went around looking for such a class in the current trunk of SharpMap v0.9 :).

I absolutely wouldn't mind you adding my code to repository. Thanks.

Cheers,
Raghu

Oct 12, 2010 at 10:21 AM

FObermaier,

Sorry, all I have for now is theory. As soon as I have a working code I will surely post it here.

Good luck to me!

- ghelo

Oct 26, 2010 at 7:49 AM

hi,

i have a problem in export shapefile to pdf using pdfsharp.

i have custom mapimage control, have 3 layer( 1 vector and 2 label), when export to pdf labellayer seem rotate 180 degree ( vector layer can not).

thanks,

Oct 27, 2010 at 4:00 AM

hi,

as previous post, labellayer have RotationColumn not equal zero will error, others labellayer can not error

thanks,