Rotating the map

Topics: SharpMap Project, SharpMap v0.9 / v1.x, WinForms Controls
Jun 17, 2008 at 12:29 PM
I try to rotate a map (either a geotiff or a shape file) with no success.
Is it possible to rotate (I want to let the user set the north direction) map/layer with SharpMap?
Jul 17, 2008 at 8:12 AM
I have the same question:

Can I rotate the map giving an angle in SharpMap 2.0?

Thank you!
Jul 17, 2008 at 1:11 PM
well in the 0.9 I think i found a solution, but i had not implemented it yet.
My idea was to wrap the rendering call in the map class this way:
1. change the bounding box of the map to one that contains the desired area
  -> if you want to view rectangle with top-left point (x,y) and size (w,h) rotated in angle a then the bounding box should be:
       top left point is (x-h*cos(a), y) size is (  h*cos(a)+w*sin(a), h*sin(a)+w*cos(a)  )
2. set the graphics object to rotate-transform to angle a (counter clock wise -I think)
3. get the image of the map with the new bounding box.
4. I'm not sure, but you might need to clip the image at this point 

anyway I didn't implement this yet so I don't know if it works.
I also don't know i this was solved some way in 2.0
Coordinator
Jul 17, 2008 at 2:02 PM
Hi chaps, I haven't used either but I think:
in 0.9 you would use the Map.MapTransform
in 2 you would use IRenderer.RenderTransform

hth jd
Nov 23, 2008 at 4:09 PM
Edited Nov 23, 2008 at 4:13 PM
OK, sorry it took me that long but I tried the method mentioned above.
It work but not realy well,

Using the Map.MapTransform result in 2 blank triangles at the bottom of the map, and if that was not enough, the pannig and zomming stop behaving as they should.

I managed to solve this by adding few things to the Map object and the MapImage object.

Changes in Map.cs:

add the property:

public float RotateAngle { get; set; }

add a Map object:

private Map rotationMap;

change the GetMap method:

if (RotateAngle % 360 == 0)
{
    if (Layers == null || Layers.Count == 0)
            throw new InvalidOperationException("No layers to render");

    System.Drawing.Image img = new System.Drawing.Bitmap(this.Size.Width, this.Size.Height);
        System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(img);
        g.Transform = this.MapTransform;
        g.Clear(this.BackColor);
        g.PageUnit = System.Drawing.GraphicsUnit.Pixel;
        int SRID = (Layers.Count > 0 ? Layers[0].SRID : -1); //Get the SRID of the first layer
        for (int i = 0; i < _Layers.Count; i++)
    {        
            if (_Layers[i].Enabled && _Layers[i].MaxVisible >= this.Zoom && _Layers[i].MinVisible < this.Zoom)
                    _Layers[i].Render(g, this);
        }
        if (MapRendered != null) MapRendered(g); //Fire render event
        g.Dispose();
        return img;
}
else
{
    double rotInRadian = RotateAngle * Math.PI / 180;

    int newWidth, newHeight;
        double newZoom;

    if ((RotateAngle % 360) <= 90)
        {
        newWidth = (int)Math.Round(
            this.Size.Width * Math.Cos(rotInRadian) + this.Size.Height * Math.Sin(rotInRadian), 0);
                newHeight = (int)Math.Round(
            this.Size.Width * Math.Sin(rotInRadian) + this.Size.Height * Math.Cos(rotInRadian), 0);

                newZoom = this.Envelope.Width * Math.Cos(rotInRadian) + this.Envelope.Height * Math.Sin(rotInRadian);
    }
        else
        {
            rotInRadian = (RotateAngle%90) * Math.PI / 180;
                newWidth = (int)Math.Round(
            this.Size.Height * Math.Cos(rotInRadian) + this.Size.Width * Math.Sin(rotInRadian), 0);
                newHeight = (int)Math.Round(
            this.Size.Height * Math.Sin(rotInRadian) + this.Size.Width * Math.Cos(rotInRadian), 0);

                newZoom = this.Envelope.Height * Math.Cos(rotInRadian) + this.Envelope.Width * Math.Sin(rotInRadian);
         }

         rotationMap = new Map(new System.Drawing.Size(newWidth, newHeight));
         rotationMap.Layers = this.Layers;
         rotationMap.MapTransform = this.MapTransform;
         rotationMap.Zoom = newZoom;
         rotationMap.Center = this.Center;

         System.Drawing.Image img = rotationMap.GetMap();
                
         System.Drawing.Image imgrotated = new System.Drawing.Bitmap(img.Width, img.Height);
         System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(imgrotated);
         g.TranslateTransform(img.Width / 2, img.Height / 2);
         g.RotateTransform((float)RotateAngle);
         g.TranslateTransform(-(img.Width / 2), -(img.Height / 2));
         g.DrawImage(img, new System.Drawing.Point(0, 0));

         System.Drawing.Image resultImg = new System.Drawing.Bitmap(this.Size.Width, this.Size.Height);
         System.Drawing.Graphics gResult = System.Drawing.Graphics.FromImage(resultImg);               

         System.Drawing.Point clipStart = new System.Drawing.Point(
        -(newWidth - this.Size.Width)/2,
        -(newHeight - this.Size.Height)/2);
         gResult.DrawImage(imgrotated, clipStart);

     g.Dispose();
     gResult.Dispose();

         return resultImg;
            
}

this will do the trick, but now the pannig will not have the desired behaviour, so we need to fix it.

Changes in MapImage.cs:

in the MapImage_MouseUp method go to the part that deal with the panning (this.ActiveTool == Tools.Pan) (I think line 419)
change the content of this if block to:

if (mousedragging)
{
    System.Drawing.Point correctedPoint = e.Location;
        if (this.Map.RotateAngle % 360 != 0)
        {
            double rads = -1 * this.Map.RotateAngle * Math.PI / 180;
                double x = e.X - mousedrag.X;
                double y = e.Y - mousedrag.Y;

        correctedPoint.X = (int)(mousedrag.X + x * Math.Cos(rads) - y * Math.Sin(rads));
                   correctedPoint.Y = (int)(mousedrag.Y + y * Math.Cos(rads) + x * Math.Sin(rads));
    }

    System.Drawing.Point pnt = new System.Drawing.Point(
            this.Width / 2 + (mousedrag.X - correctedPoint.X),
                   this.Height / 2 + (mousedrag.Y - correctedPoint.Y));
    _Map.Center = this._Map.ImageToWorld(pnt);
    if (MapCenterChanged != null)
        MapCenterChanged(_Map.Center);
}
else
{         
    _Map.Center = this._Map.ImageToWorld(new System.Drawing.Point(e.X, e.Y));
    if (MapCenterChanged != null)
        MapCenterChanged(_Map.Center);
}
Refresh();

A little explenation of what I done:
I got an image for a bigger map (zoomed out), rotated the result and clip the relevant piece from it.
This broke the panning couse the ImageToWorld coordinate are not correct now, so I fixed it (rotate the point to the relevant point)

This is not completed yet.
  • I have a weird behaviour when the map is rotated in some angles ([80,100], [170,190], and [260, 280]), dont know why.
  • The solution of the panning was not really needed, what I need to do is correct the ImageToWorld function to caculate the relevant world location coresponding with the rotation angle (this should solve the panning without need to change the MapImage.cs).


Aug 24, 2009 at 3:08 PM

JohnDiss, the problam with using the  Map.MapTransform is that it does not fill the spaces created by rotating the generated image.