Click here to Skip to main content
Click here to Skip to main content

Writing GIS and Mapping Software for .NET

, , 19 Aug 2008
Rate this:
Please Sign up or sign in to vote.
In part three of the series, the authors of the "GIS.NET" mapping component for .NET explain how to write a geographic mapping engine which can display geographic coordinates. Source code is provided which can pan and zoom a sample geographic object (the state of Nebraska), in C# and VB.NET.

Introduction

In part one of this series, I described how to write an interpreter for raw GPS NMEA data. Part two described how to monitor and enforce GPS precision data to develop commercial-quality software. The articles includes source code in C# and VB.NET which harness the power of GPS satellites to determine the current location, synchronize the computer clock to atomic time, and point to a satellite on a cloudy day. Yet, even with all of this code, most developers still need a way to display GPS information along with other geographic features. With the help of my colleague Phil Smith, a lead developer of our “GIS.NET” mapping component and the “Geodesy.NET” coordinate and projection library, this article will teach you how to generate your own maps.

The Rule of Threes

In order to understand the technology behind mapping, it’s necessary to have a solid understanding of three coordinate systems: geographic, projected, and pixel. Each system serves an important role when displaying a map, and transformations from one system to another are essential. Developers typically start with a geographic coordinate (expressed as latitude and longitude). Then, it is transformed from Earth’s oblate spheroid (roughly spherical) shape to a plane, resulting in a projected coordinate: a truly flat, two-dimensional coordinate. A projected coordinate is an easting/northing pair describing a distance East of a “central meridian” (a line of longitude) and a distance North of a “central parallel” (a line of latitude). The method which transforms a geographic coordinate to and from a projected easting/northing coordinate is called a projection. Finally, projected coordinates are translated and scaled so that they align to a specific place on-screen, resulting in pixel coordinates. Pixel coordinates are the same coordinates that you’ve already used to align controls on a Form.

Figure31.png

Figure 1.1: Geographic coordinates are converted from 3D to 2D using a map projection. Then, a viewport scales and translates a portion of the map to display Florida.

Let’s take a closer look at each of the three coordinate systems and how to convert between them to produce a map.

The Earth is Better Flat

In parts one and two of this article, we discussed how GPS devices report your location as a latitude and longitude. These pairs are often referred to as “geographic coordinates.” The problem with geographic coordinates, however, is that they represent coordinates on the Earth’s surface, which is a spheroid (roughly spherical) shape. Since our computer monitors are flat, we need a way to “unfold” the Earth into a perfectly flat shape before we try to display it. This technique is known as projection, and it is essential for displaying maps. There are over a hundred map projections in use around the world today, and each projection serves a specific purpose. For example, the Mercator projection is widely used by boats and ships because it produces a map in which lines of constant bearing are a straight line, which greatly simplifies navigation. However, a side-effect of this projection is that it distorts the size of everything as you get closer to the North or South Poles, making this projection unsuitable for other purposes.

Figure32.pngfigure33.png

Figure 1.2: Countries of the world are displayed using two projections, Mercator and Polyconic, to demonstrate how projections can produce widely-differing views of the same data.

As you can see, map projections can make the same geographic features appear in completely different sizes and shapes, but each is perfectly valid. In fact, some projections such as the “Orthographic” projection are flat, but produce the illusion of a 3D image on a flat monitor. This projection is a contemporary example which is widely used by many 3D applications, including modern 3D game engines. Regardless of the shape and size, projected coordinates flatten 3D coordinates, and this greatly simplifies the task of mapping.

Here Comes the Science

The mathematics behind map projections can be somewhat intimidating. For example, the “Van der Grinten” projection uses the following formula (where A, G, P, and Q are mapping parameters):

Figure34a.png

Figure34b.png

For this article, we’ll be writing the code for a much simpler projection known as “Equidistant Cylindrical” or “Plate Carée,” which, because of its simplicity and speed, is the default projection used by our GIS.NET 3.0 component, which includes a library of twenty-five other projections:

Figure34c.png

The formulas for map projection are easier to work with when geographic coordinates are expressed as radians. Radians are straightforward to calculate, and can be applied to either a latitude or longitude:

// 90° or ninety degrees
double degrees = 90.0;    
// Convert to radians
double radians = degrees * (Math.Pi / 180.0);
// Convert radians back to degrees
degrees = radians / (Math.PI / 180.0);

All map projection source code is divided into two methods. The first method, referred to as a forward projection, will convert a geographic coordinate into a projected coordinate. The second method is exactly the opposite, converting a projected coordinate back into a geographic coordinate. This is referred to as a reverse projection or de-projection. Here’s how the two methods look for our example Plate Carée projection:

using System.Drawing;

public class PlateCaree 
{
    public PointF Project(PointF geographicCoordinate)
    {
        // First, convert the geographic coordinate to radians
        double radianX = geographicCoordinate.X * (Math.PI / 180);
        double radianY = geographicCoordinate.Y * (Math.PI / 180);

        // Make a new Point object
        PointF result = new PointF();

        // Calculate the projected X coordinate
        result.X = (float)(radianX * Math.Cos(0));

        // Calculate the projected Y coordinate
        result.Y = (float)radianY;

        // Return the result
        return result;
    } 
    
    public PointF Deproject(PointF projectedCoordinate)
    {
        // Make a new point to store the result
        PointF result = new PointF();

        // Calculate the geographic X coordinate (longitude)
        result.X = (float)(projectedCoordinate.X / Math.Cos(0) / 
                          (Math.PI / 180.0));

        // Calculate the geographic Y coordinate (latitude)
        result.Y = (float)(projectedCoordinate.Y / (Math.PI / 180.0));

        return result;
    }
}

With this class, we can now produce projected coordinates for any location on Earth:

// Define a geographic coordinate, in this case a GPS location
PointF myLocation = new PointF();
myLocation.Y = 39.0;       // 39 North
myLocation.X = -105.0;     // 105  West

// Now use our projection class to flatten this coordinate
PlateCaree projection = new PlateCaree();
PointF myProjectedLocation = projection.Project(myLocation);
myLocation = projection.Deproject(myProjectedLocation);

... this process is then repeated for each geographic coordinate, until all data can be represented in projected coordinates. Once this has been done, only one step remains to convert these coordinates into pixel coordinates which can be painted on the screen.

Paint the Planet

If we were creating a map to display on a wall, our task would be easy because we could paint all of the data once and be done with it. However, mapping software should let users pan and zoom a map so that they can explore any part of it in greater detail. To do this, we must imagine a rectangle (which we refer to as a “viewport” in GIS.NET 3.0) which represents the portion of the map we actually want to see. Once this is known, math is applied a third time to convert projected coordinates into pixel coordinates. In other words, we must make the upper-left of our viewport match up to (0,0) in our Form.

figure34.png

Figure 1.4: A viewport is used to see a portion of all the projected coordinates. In this case, a viewport displays the continent of Africa.

.NET developers are already familiar with pixel coordinates. These are the same coordinates which you’ve used to place controls onto a Form, so there’s nothing new to explain here. But, we need a way to convert projected coordinates into pixel coordinates. To do this, projected coordinates must be scaled and translated to make the viewport align with the pixel size of the Form. Translation is performed by applying the negative value of the X coordinate, then the Y coordinates of the upper-left corner of the viewport. Horizontal scale is calculated by dividing the pixel width of the area to paint by the projected width of the viewport, and similarly to calculate vertical scale.

Fortunately, on desktops, we can make use of the Matrix class to do all of the heavy lifting for this task. Matrix objects can rotate, translate, and scale an array of coordinates in the form of a PointF array. The resulting code will look something like this:

// Use a matrix for translation and scaling
Matrix transform = new Matrix();

// First, translate all projected points so that they match up with pixel 0,0
transform.Translate(-viewport.X, viewport.Y, MatrixOrder.Append);

// Next, scale all points so that the viewport fits inside the form.
transform.Scale(this.Width / viewport.Width,
               this.Height / -viewport.Height, MatrixOrder.Append);

You may have noticed how, for the vertical scale, a negative sign is used. This is because projected coordinate systems have a Y-axis which is the opposite of pixel coordinate systems. In other words, greater Y values travel up in projected coordinates, whereas greater Y values in pixel coordinates travel down. A negative sign here prevents the image from being displayed upside-down.

Since we’re using GDI+ for this example, all painting is done using a Graphics class, typically during an OnPaint method. Thankfully, we can apply our transformation and scale easily by assigning the Transform property of the Graphics object to our Matrix. With this in place, we can now call paint methods such as DrawLine using projected coordinates! As a result, painting objects becomes rather trivial:

protected override void OnPaint(PaintEventArgs e)
{
    // Apply this transform to all graphics operations
    e.Graphics.Transform = transform;

    // Now draw nebraska using a green interior and black outline
    e.Graphics.FillPolygon(Brushes.Green, projectedCoordinates);
    e.Graphics.DrawPolygon(Pens.Black, projectedCoordinates);
}

Have a Good Aspect

In this article, we’re dealing with two rectangles: the “viewport,” a projected area to be painted, and the Form itself, where everything will be displayed. If the shape of the viewport differs greatly from the shape of the Form, however, distortion can occur (see the “Before” picture below). To fix this problem, we must make the shape of the viewport match the shape of the Form. This is done by adjusting the “aspect ratio” of the viewport.

Figure35.png

Figure 1.5: The state of Nebraska is drawn with no correction (left), then with aspect ratio correction (right) to preserve its shape.

Aspect ratio is calculated by dividing the width of a rectangle by its height. For example, if the width of a rectangle were ten pixels, and its height were twenty pixels, then the aspect ratio would be 0.5. To adjust the aspect ratio of the viewport, its aspect ratio is compared to the aspect ratio of the rectangular Form itself. If the viewport’s aspect ratio is greater than the Form’s, the viewport’s height is increased. Otherwise, the viewport’s width is increased. The resulting code looks like this:

// Calculate the aspect ratio of the Form
float pixelAspectRatio = (float)this.Width / this.Height;

// Calculate the aspect ratio of the viewport
float projectedAspectRatio = viewport.Width / viewport.Height;

// Make a copy of the viewport that we can change
RectangleF adjustedViewport = viewport;

// Is the Form's aspect ratio larger than the viewport's ratio?
if (pixelAspectRatio > projectedAspectRatio)
{
    // Yes. Increase the width of the viewport
    adjustedViewport.Inflate(
        (pixelAspectRatio * adjustedViewport.Height - adjustedViewport.Width) 
            / 2,
        0);
}
// Is the viewport's aspect ratio larger than the form's ratio?
else if (pixelAspectRatio < projectedAspectRatio)
{
    // Yes. Increase the height of the viewport
    adjustedViewport.Inflate(
        0, 
        (adjustedViewport.Width / pixelAspectRatio - adjustedViewport.Height) 
            / 2);
}

… with the aspect ratio adjusted, all geographic objects painted will now preserve their shape even as the Form’s shape changes.

Navigating a Map

Now that we have the ability to paint a portion of a map, the final step in this example is to implement some form of navigation. Panning a map means shifting the viewport without changing its size. Zooming, however, is somewhat counter-intuitive: to zoom a map in, you must make the projected viewport smaller. A smaller viewport means a greater scale factor is applied.

If we’re using the PointF class to represent projected coordinates, we can use the RectangleF class to represent the projected viewport. Zooming becomes a matter of calling the Inflate method to either shrink or grow the projected viewport to zoom in or out, respectively. Another important thing to mention here is the concept of "zooming by percentage." Zooming should always be done using a percentage of the current viewport. Otherwise, zooming will appear to have an exaggerated effect the more you zoom in, and closer to no effect as you zoom out:

public void ZoomIn()
{
    // First, get the width of the viewport, then calculate 10% of it
    float zoomWidthAmount = -viewport.Width * 0.10f;
    float zoomHeightAmount = -viewport.Height * 0.10f;

    // Next, apply the amounts to shrink (yes, shrink) the viewport to zoom in
    viewport.Inflate(zoomWidthAmount, zoomHeightAmount);

    // And repaint
    Invalidate();
}

public void ZoomOut()
{
    // First, get the width of the viewport, then calculate 10% of it
    float zoomWidthAmount = viewport.Width * 0.10f;
    float zoomHeightAmount = viewport.Height * 0.10f;

    // Next, apply the amounts to shrink (yes, shrink) the viewport to zoom in
    viewport.Inflate(zoomWidthAmount, zoomHeightAmount);

    // And repaint
    Invalidate();
}

Developers may recognize how easily these methods can be plugged into the MouseWheel event of a Form. Panning methods are just as straightforward, but involve use of the Offset method:

public void PanUp()
{
    // First, get the height of the viewport, then calculate 10% of it
    float zoomHeightAmount = -viewport.Height * 0.10f;

    // Shift the viewport by this amount
    viewport.Offset(0.0f, zoomHeightAmount);
}

public void PanDown()
{
    // First, get the height of the viewport, then calculate 10% of it
    float zoomHeightAmount = viewport.Height * 0.10f;
    
    // Shift the viewport by this amount
    viewport.Offset(0.0f, zoomHeightAmount);
}

... again, you may have already recognized how to plug these methods into the KeyDown event. With these methods implemented, you can now explore your map at any zoom level. If you are familiar with parts one and two of this article, you are well on your way to developing a commercial application which can plot your GPS location, along with all kinds of geographic features. Whether your intent is to draw points, lines, or polygons, the approach is the same.

Play it Backwards

At this point, we’ve successfully drawn a map and provided a way to pan and zoom. But, it would be helpful to be able to see where the mouse is pointing, but in terms of geographic or projected coordinates, not pixel coordinates. So, we’ll add some code into the MouseMove event of the Form, which will show the mouse’s location in all three coordinate systems.

Conversion starts with pixel coordinates, which are then converted to projected coordinates using the inverse of the Matrix we set up earlier in this article. Finally, the Deproject method of our projection is used to convert the projected coordinate back into its geographic equivalent. The code will look like this:

protected override void OnMouseMove(MouseEventArgs e)
{
    /* When the mouse is moved over the map, show the coordinate the mouse
     * is hovering over, in all three coordinate systems: pixel, projected, 
     * and geographic.
     */

    /* We use a Matrix to convert from projected coordinates to pixel 
     * coordinates. The *inverse* of this matrix is used to convert pixels
     * back into projected coordinates.
     */

    // First, make a copy of the current transform
    Matrix reverseTransform = transform.Clone();

    // Next, invert the transform
    reverseTransform.Invert();

    // We can now use this to calculate a projected coordinate.
    Point[] projectedCoordinate = new Point[] { e.Location };
    reverseTransform.TransformPoints(projectedCoordinate);

    /* "Points" now contains projected coordinates. Use our projection to
     * convert this into geographic (latitude/longitude) coordinates.
     */
    PointF geographicCoordinate = plateCaree.Deproject(projectedCoordinate[0]);

    // Finally, display all three coordinates: pixel, projected, geographic
    Console.WriteLine("Pixel: " + e.Location.ToString());
    Console.WriteLine("Projected: "
            + projectedCoordinate[0].ToString();
    Console.WriteLine("Geographic: " 
            + geographicCoordinate.ToString();
}

… with this code, you can now freely convert between all three coordinate systems, in both directions.

Conclusion

The task of displaying geographic data on-screen involves conversion of the data to two other coordinate systems. Map projections are used to flatten 3D coordinates into 2D coordinates, and then matrix math is used to actually paint geographic data in a meaningful way. Panning and zooming a map involves changing the location and size of the viewport, and navigation is typically tied into keyboard and mouse events. The aspect ratio of the viewport is adjusted to match the aspect ratio of the Form to prevent distortion. Finally, an inverse of the Matrix is used to convert coordinates from pixel to projected, and the Deproject method of the projection converts the projected coordinate back into its geographic equivalent.

There are many topics which we have yet to cover in order to develop a commercial-quality mapping application. Topics such as geographic data sources, spatial indexing, vector normalization, and paint optimization could easily take up several articles. Many commercial components for .NET exist which address these topics. However, this article can at least help you to gain a solid understanding of how to display geographic data in your own .NET applications.

Please indicate your interest in part four by rating this article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Authors

Jon Person
Software Developer (Senior) McGraw-Hill
United States United States
Hi there! From 2004 to 2009 I ran a company called "GeoFrameworks," publishing two components called GPS.NET and GIS.NET which helped developers quickly write location-based services. Now, I've released the source code for GPS.NET to CodePlex for you to use as you see fit.
 
GPS.NET 2.0 on CodePlex
GPS.NET 3.0 on CodePlex
 
... I've also released the source code of a library called the "GeoFramework," a collection of commonly used classes such as Latitude, Longitude, Distance, Speed, and Position:
 
GeoFramework 1.0 on CodePlex
GeoFramework 2.0 on CodePlex
 
I'm now taking a break from programming, but I really appreciate the positive feedback from readers!

A. Philip Smith
Architect GeoFrameworks
United States United States
Phil has a highly technical mind as well as having adepts in both art and music. His twenty-five years of computer experience brings knowledge to the table for any enterprise endeavor. But his skills in problem solving provide practical solutions to a myriad of problems, both technical and administrative. He also has a propensity for math and science that suit him well for tasks associated with geospacial information systems.
 
As a .Net architect and developer, Phil has six years under his belt in C# development, on top of twelve years of Visual Basic, starting with version four. He has an extensive background in business enterprise development, which includes SQL server database design and development. Now Phil prefers to exercise his talents in fields of science and mathematics.

Comments and Discussions

 
QuestionIs this right? Pinmembermans.098715-Jan-13 2:33 
GeneralMy vote of 5 PinmemberBadis21-Oct-12 7:28 
QuestionString upside-down [modified] Pinmemberpublius11-Oct-12 3:49 
GeneralMy vote of 5 PinmemberMember 36305653-Oct-12 22:46 
SuggestionClarification graphic display [modified] PinmemberMember 86213617-Sep-12 9:10 
I appreciate you posting this article.   It helped me greatly in the understanding of how to use the matrix operation to calculate screen coordinates.   I had been doing this all manually with loops in my code, which I believe is slower than this example.   For clarity, I would like to point out a couple things for future readers.
 
First, the map projection breaks down to simply multiplying the latitude and longitude by 6378137 meters, the equatorial radius of the WGS84 ellipsoid.   The distortion on this projection is very high. But, it is a great example for understanding the general concept of projecting geographic coordinates, which I believe was the intent of this article.   If a future reader is only interested in a displaying the relative locations of various features, I would recommend not using a projection and use the latitude and longitude directly.   If you are developing in a Windows environment and would like to perform high accuracy projections, I would recommend reading the following, http://www.agc.army.mil/corpscon/using_the_corpscon_dll.pdf
 
Second, it is my opinion the “Have a Good Aspect” is misleading and simply corrects an error in logic.   The “Before” picture in figure 1.5 is distorted because of the arbitrary coordinates used when the viewport object was initialized.   Furthermore, the viewport is not used for anything and creates a smoke screen as to what is really going on, i.e. centering the data on the form.   I have included some code below that will hopefully illustrate my point.   Replace the form code with the code in the first section and add the DataObject class (section two) to the project.   You can swap out the geographicCoordinates with the projectedCoordinates.   You won't see a difference in doing this.   This is due to the projection routine that was provide with the original solution.   I haven't fully tested the code for drifting due to round off errors. But, I believe the coordinate calculations for the mouse tracking are close.
 

PS. I believe the original solution had a xScale and yScale.   In general these should never be different, hence the DataScale used in this example.
 

=====Section One========================================
Imports System.Collections.Generic
Imports System.Drawing.Drawing2D
Imports System.Globalization
Imports System.IO
Imports System.Windows.Forms
Class Form1
      ' This array stores the latitude and longitude of Nebraska in the United States
      Private geographicCoordinates() As PointF
      ' This map projection flattens 3D coordinates to 2D coordinates.
      Private plateCaree As New MapProjection()
      ' This array stores the same coordinates but as projected coordinates
      Private projectedCoordinates() As PointF
      ' Use a matrix for translation and scaling
      Private transform As New Matrix()
      ' This brush and pen are used to paint Nebraska
      Private brush As New SolidBrush(Color.Green)
      Private pen As New Pen(Color.Black, 0.001F)
 
      Private ourData As New DataObject()
      Private formAspectRatio As Single
      Private DataScale As Single
      Private DataAspectRatio As Single
      Private PaddingX As Single
      Private PaddingY As Single
      Private DataPxHeight As Single
      Private DataPxWidth As Single
      Private TopLeftX As Single 'In coordinate units
      Private TopLeftY As Single 'In coordinate units
      Private MouseOn As Boolean = False
      Private MouseDn As Boolean = False
      Private MouseLocation As Drawing.Point
      Private DownTopLeftX As Single
      Private DownTopLeftY As Single
      Private MouseDownLocation As Drawing.Point
 
      Sub New()
            ' This call is required by the Windows Form Designer.
            InitializeComponent()
            ' Redraw the control when it's resized
            SetStyle(ControlStyles.ResizeRedraw, True)
            ' This example displays the state of Nebraska on-screen.   Geographic coordinates
            ' which form Nebraska are stored in a text file, Nebraska.txt.
            'LoadData()
            ourData.LoadData("..\..\Nebraska.txt")
            geographicCoordinates = ourData.Coordinates
            InitializeMapData()
            ' For this example, we will "flatten" the state of ______ using the Plate Caree
            ' map projection.   Once this is done, we can draw these coordinates onto the form.
            ' First, create a new map projection
            Dim plateCaree As New MapProjection()
            '' Next, make an array to store our projected coordinates.
            ReDim projectedCoordinates(geographicCoordinates.Length - 1)
            ' For each geographic coordinate, project it
            Dim index As Integer
            For index = 0 To geographicCoordinates.Length - 1
                  ' Convert the geographic coordinate to a projected coordinate
                  projectedCoordinates(index) = plateCaree.Project(geographicCoordinates(index))
            Next
      End Sub
      Private Sub InitializeMapData()
 
            ' In this example, the entire form surface is painted.
            ' You may project your lat/longs or use them as is
            ' The main task here is to scale and translate our data to
            ' fit the(Form)'s own dimensions
            ' Calculate the aspect ratio of the Form
            formAspectRatio = Me.Width / Me.Height
            ' Calculate the aspect ratio of the data
            DataAspectRatio = ourData.AspectRatio
            PaddingX = 20 'Pixels
            PaddingY = 20 'Pixels
            ' Is the Form's aspect ratio larger than the data ratio?
            If formAspectRatio > DataAspectRatio Then
                  ' Yes.   Data height is controlling
                  'Calculate scale based on height
                  DataScale = (Me.Height - PaddingY * 2) / ourData.Height
            ElseIf formAspectRatio < DataAspectRatio Then
                  ' Yes.   Data Width is controlling
                  'Calculate scale based on Width
                  DataScale = (Me.Width - PaddingX * 2) / ourData.Width
            End If
            DataPxHeight = ourData.Height * DataScale
            DataPxWidth = ourData.Width * DataScale
            TopLeftX = ourData.MinXCoor - (Me.Width - DataPxWidth) / (2 * DataScale)
            TopLeftY = ourData.MaxYCoor + (Me.Height - Me.statusStrip1.Height - DataPxHeight) / (2 * DataScale)
      End Sub
      Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
            e.Graphics.Clear(Color.White)
            'Translate by setting the translation to the following:
            '   Set xOrigin equal to the negative ourData.MinXCoor
            '   Set yOrigin equal to the negative ourData.MaxYCoor
            '   Set the yScale to a negative scale
            'This wierd gyration is required because the origin of the graphic window is
            'the upper left hand corner and y increases going down.
            ' Reset the transform
            transform.Reset()
            ' First, translate all projected points so that they match up with pixel 0,0
            transform.Translate(0 - TopLeftX, 0 - TopLeftY, MatrixOrder.Append)
            ' Next, scale all points so that the viewport fits inside the form.
            transform.Scale(DataScale, 0 - DataScale, MatrixOrder.Append)
            ' Apply this transform to all graphics operations
            e.Graphics.Transform = transform
            ' Now draw nebraska using a green interior and black outline
            e.Graphics.FillPolygon(brush, geographicCoordinates)
            e.Graphics.DrawPolygon(pen, geographicCoordinates)
      End Sub
      Public Sub ZoomIn()
            Zoom(1.1)
      End Sub
      Public Sub ZoomOut()
            Zoom(0.9)
      End Sub
      Private Sub Zoom(ByVal ZoomFactor As Single)
            Dim ZoomX As Single
            Dim ZoomY As Single
            If MouseOn Then
                  ZoomX = TopLeftX + MouseLocation.X / DataScale
                  ZoomY = TopLeftY - MouseLocation.Y / DataScale
            Else
                  ZoomX = TopLeftX + (Me.Width / (2 * DataScale))
                  ZoomY = TopLeftY - (Me.Height / (2 * DataScale))
            End If
            DataScale = DataScale * ZoomFactor
            If MouseOn Then
                  TopLeftX = ZoomX - MouseLocation.X / DataScale
                  TopLeftY = ZoomY + MouseLocation.Y / DataScale
            Else
                  TopLeftX = ZoomX - (Me.Width / (2 * DataScale))
                  TopLeftY = ZoomY + (Me.Height / (2 * DataScale))
            End If
            ' And repaint
            Refresh()
      End Sub
      Public Sub PanUp()
            TopLeftY = TopLeftY + (Me.Height * 0.1F / DataScale)
            ' Finally, redraw the form
            Invalidate()
      End Sub
      Public Sub PanDown()
            TopLeftY = TopLeftY - (Me.Height * 0.1F / DataScale)
            ' Finally, redraw the form
            Invalidate()
      End Sub
      Public Sub PanRight()
            TopLeftX = TopLeftX + (Me.Width * 0.1F / DataScale)
            ' Finally, redraw the form
            Invalidate()
      End Sub
      Public Sub PanLeft()
            TopLeftX = TopLeftX - (Me.Width * 0.1F / DataScale)
            ' Finally, redraw the form
            Invalidate()
      End Sub
      Protected Overrides Sub OnKeyDown(ByVal e As KeyEventArgs)
            Select Case e.KeyValue
                  Case 38
                        PanUp()
                  Case 40
                        PanDown()
                  Case 39
                        PanRight()
                  Case 37
                        PanLeft()
            End Select
      End Sub
      Protected Overrides Sub OnMouseWheel(ByVal e As MouseEventArgs)
            If e.Delta < 0 Then
                  ZoomOut()
            Else
                  ZoomIn()
            End If
      End Sub
      Protected Overrides Sub OnMouseMove(ByVal e As MouseEventArgs)
            If MouseDn Then
                  TopLeftX = DownTopLeftX - (e.Location.X - MouseDownLocation.X) / DataScale
                  TopLeftY = DownTopLeftY + (e.Location.Y - MouseDownLocation.Y) / DataScale
                  Refresh()
            End If
            ' As the mouse moves display the screen coordinate
            ' and the mapping coordinates.
            MouseLocation = e.Location
            Dim X As Double = TopLeftX + e.Location.X / DataScale
            Dim Y As Double = TopLeftY - e.Location.Y / DataScale
            ' Finally, display all three coordinate values: pixel, projected, geographic
            pixelCoordinateLabel.Text = "Pixel: " + e.Location.ToString()
            projectedCoordinateLabel.Text = "Map: " + Format(X, "#0.0000") + ", " + Format(Y, "#0.0000")
      End Sub
      Protected Overrides Sub OnClosed(ByVal e As System.EventArgs)
            MyBase.OnClosed(e)
            ' Clean up our unmanaged resources.
            ' These will get disposed of when the app ends, but this would be essential
            ' if this were not the only form.
            transform.Dispose()
            brush.Dispose()
            pen.Dispose()
      End Sub
      Private Sub Form1_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseDown
            MouseDn = True
            DownTopLeftX = TopLeftX
            DownTopLeftY = TopLeftY
            MouseDownLocation = e.Location
      End Sub
      Private Sub Form1_MouseEnter(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.MouseEnter
            MouseOn = True
      End Sub
      Private Sub Form1_MouseLeave(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.MouseLeave
            MouseOn = False
      End Sub
      Private Sub Form1_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseUp
            MouseDn = False
      End Sub
End Class
 
=====Section Two========================================
Imports System.IO
Imports System.Globalization
Public Class DataObject
      Private mCoordinates As New List(Of PointF)
      Private mMinXCoor As Double
      Private mMinYCoor As Double
      Private mMaxXCoor As Double
      Private mMaxYCoor As Double
      Public ReadOnly Property Coordinates() As Array
            Get
                  Coordinates = mCoordinates.ToArray
            End Get
      End Property
      Public ReadOnly Property Height() As Single
            Get
                  Height = CSng(mMaxYCoor - mMinYCoor)
            End Get
      End Property
      Public ReadOnly Property Width() As Single
            Get
                  Width = CSng(mMaxXCoor - mMinXCoor)
            End Get
      End Property
      Public ReadOnly Property AspectRatio() As Single
            Get
                  AspectRatio = Math.Abs(CSng((mMaxXCoor - mMinXCoor) / mMaxYCoor - mMinYCoor))
            End Get
      End Property
      Public ReadOnly Property MinXCoor() As Single
            Get
                  MinXCoor = CSng(mMinXCoor)
            End Get
      End Property
      Public ReadOnly Property MinYCoor() As Single
            Get
                  MinYCoor = CSng(mMinYCoor)
            End Get
      End Property
      Public ReadOnly Property MaxXCoor() As Single
            Get
                  MaxXCoor = CSng(mMaxXCoor)
            End Get
      End Property
      Public ReadOnly Property MaxYCoor() As Single
            Get
                  MaxYCoor = CSng(mMaxYCoor)
            End Get
      End Property
 
      Public Sub LoadData(ByVal FilePath As String)
            ' This example stores latitude and longitude coordinates in the file Nebraska.txt.
            ' Read in each line and turn it into a PointF object.
            Dim reader As StreamReader = File.OpenText(FilePath)
            ' Read everything in
            While Not reader.EndOfStream
                  ' Each line is in the form "latitude, longitude".   Parse each into a float, then
                  ' create a PointF object from it.
                  Dim values() As String = reader.ReadLine().Split(",")
                  Dim i As Long
                  ' Parse both values into a PointF object
                  mCoordinates.Add( _
                        New PointF( _
                              Single.Parse(values(1), CultureInfo.InvariantCulture), _
                              Single.Parse(values(0), CultureInfo.InvariantCulture)))
                  i = mCoordinates.Count - 1
                  If mCoordinates.Count = 1 Then
                        mMinXCoor = mCoordinates(i).X
                        mMinYCoor = mCoordinates(i).Y
                        mMaxXCoor = mCoordinates(i).X
                        mMaxYCoor = mCoordinates(i).Y
                  Else
                        If mCoordinates(i).X < mMinXCoor Then
                              mMinXCoor = mCoordinates(i).X
                        End If
                        If mCoordinates(i).Y < mMinYCoor Then
                              mMinYCoor = mCoordinates(i).Y
                        End If
                        If mCoordinates(i).X > mMaxXCoor Then
                              mMaxXCoor = mCoordinates(i).X
                        End If
                        If mCoordinates(i).Y > mMaxYCoor Then
                              mMaxYCoor = mCoordinates(i).Y
                        End If
                  End If
            End While
            ' Close the reader
            reader.Close()
      End Sub
End Class

-- modified 8-Sep-12 9:01am.
GeneralMy vote of 5 Pinmembermanoj kumar choubey14-Mar-12 4:47 
GeneralUsing image from file PinmemberHenrique Serra28-May-11 6:25 
GeneralMy vote of 5 PinmemberRavi Sant25-May-11 22:17 
GeneralUsing GDI, not GDI+ Pinmemberxumepoc22-Apr-11 5:56 
GeneralMy vote of 5 Pinmembermy_webmail15-Feb-11 3:37 
QuestionWhat a party Pinmembertrhalvorson2-Jan-11 4:39 
GeneralFurther Information PinmemberDisIsHoody6-Oct-10 18:24 
GeneralMy vote of 5 PinmemberThomas Kunjummen31-Aug-10 22:10 
GeneralDrawing text in same graphics context PinmemberFrank Aurich8-Jul-10 1:47 
GeneralReason for using negative sign with X coordinate in the Translation Part Pinmemberbilal hashmi7-Jul-10 20:02 
GeneralRe: Reason for using negative sign with X coordinate in the Translation Part Pinmembercolinmeier21-Jan-11 11:17 
QuestionViewport size Pinmemberhammad@my.web.pk13-Mar-10 0:57 
GeneralMy vote of 1 PinmemberBob100029-Jun-09 23:12 
GeneralRe: My vote of 1 Pinmembertrhalvorson2-Jan-11 4:44 
GeneralGreat Stuff PinmemberAlan Streisel2-Jun-09 9:06 
GeneralExcellent Work Pinmemberismail raja22-Dec-08 16:54 
GeneralGreat Job PinmemberNewworld14-Dec-08 11:47 
GeneralRe: Great Job PinmemberJon Person20-Mar-09 11:35 
QuestionVery Large Bitmap Support PinmemberMehdi Khezrian25-Nov-08 20:26 
AnswerRe: Very Large Bitmap Support PinmemberJon Person26-Nov-08 6:58 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140721.1 | Last Updated 19 Aug 2008
Article Copyright 2008 by Jon Person, A. Philip Smith
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid