Friday, March 4, 2011

Sharp Map and PostGIS using ASP.Net

Sharp Map:

Sharp Map is an open source freely available map engine. It support different datasources such as POSTGIS, Shape files, SQL Spatial and Oracle Spatial.
SharpMap is an easy-to-use mapping library for use in web and desktop applications. It provides access to many types of GIS data, enables spatial querying of that data, and renders beautiful maps. The engine is written in C# and based on the .Net 2.0 framework. 

Goals:

In this article we will use sharp map and combine it with PostGIS database. It will enable us to populate our data from shape files or PostGIS database to map.

Tools Setup:

PostGIS Installation

PostGIS is an extension of PostgreSQL for spatial data. You can download PostgreSQL from their site. After installation on PostgreSQL, we can install extension of PostGIS on it so that it can support our spatial data (used for maps). Complete guide of installation is here.

Compiling the SharpMap resource

SharpMap.Net source, you can download it from here. Create a folder of “SharpMap” in C drive and copy these downloaded files in this folder.
Copy the PostGIS.cs file from the extracted folder SharpMap/SharpMap.Extensions/Data/Providers to folder SharpMap/SharpMap/Data/Providers.
Download the latest PostgreSQL.Net driver (npgsql) binary for MS 2.0 from here.
  1. Create a bin folder in your extracted SharpMap/SharpMap folder and copy the Npgsql.* files and Mono.Security.dll from npgsql zip files into this new folder
  2. Creat a batch file in the bin folder with the following lines in it
    set croot=C:\SharpMap\SharpMap\ 
    "%SystemRoot%\microsoft.NET\Framework\v2.0.50727\csc.exe" /t:library /debug:full /out:%croot%bin\SharpMap.dll /recurse:%croot%*.cs /r:System.web.dll /r:System.data.dll /r:System.Xml.dll /r:system.dll /r:%croot%bin\Npgsql.dll 
    pause
    Then run the batch script. Running the batch should create 2 files a .dll and a .pdb file. The pdb is used for debugging so it can highlight lines that break in the internal library.
  3. Now create an application folder on your webserver complete with a bin folder. Drop the SharpMap.dll, SharpMap.dbg, npgsql.dll, Mono.Security.dll files into the bin folder.

Loading the Test Data

Below is the batch script to load all the data. For details on how to create your own load files, read here or OGR2OGR Cheatsheet
Batch Script
c:\pgutils\psql -d gisdb -h localhost -U postgres -f neighborhoods.sql 
c:\pgutils\psql -d gisdb -h localhost -U postgres -f abansurveys.sql 
pause 
For complete detail you can read this article.

Code:

Presentation Layer

The code of the aspx file will be like this
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
   <title>Creating an interactive map in ASP.NET 2.0 using SharpMap</title>
</head>
<body>
<form runat="server">
    <div>  
       <asp:RadioButtonList ID="rblMapTools" runat="server" RepeatDirection="Horizontal">
            <asp:ListItem Value="0">Zoom in</asp:ListItem>
            <asp:ListItem Value="1">Zoom out</asp:ListItem>
            <asp:ListItem Value="2" Selected="True">Pan</asp:ListItem>
        </asp:RadioButtonList>
        <asp:ImageButton Width="1000" Height="600" ID="imgMap" runat="server" OnClick="imgMap_Click" style="border: 1px solid #000;" />
    </div>
</form>
Powered by ASP.NET 2.0 & <a href="http://sharpmap.iter.dk">SharpMap</a>

</body>
</html>

Here in the div, we will first show three radio buttons for zoom in, zoom out and pan. Image Button will display the map on the screen.

Initializing Map

 private SharpMap.Map myMap;

       protected void Page_Load(object sender, EventArgs e)
       {
              //Set up the map
              myMap = InitializeMap(new System.Drawing.Size((int)imgMap.Width.Value,(int)imgMap.Height.Value));
        SharpMap.Geometries.Point pt = new SharpMap.Geometries.Point(-7.48625, 38.6555194091797);
      
              if (Page.IsPostBack)
              {
                     //Page is post back. Restore center and zoom-values from viewstate
                     myMap.Center = (SharpMap.Geometries.Point)ViewState["mapCenter"];
                     myMap.Zoom = (double)ViewState["mapZoom"];
              }
              else
              {
                     //This is the initial view of the map. Zoom to the extents of the map:
                     //myMap.ZoomToExtents();
                     //Save the current mapcenter and zoom in the viewstate
            myMap.Zoom = 11.25;
            myMap.Center = pt;
            myMap.BackColor = Color.Blue;
                     ViewState.Add("mapCenter", myMap.Center);
                     ViewState.Add("mapZoom", myMap.Zoom);

                     //Create the map
                     CreateMap();
              }
       }

Initialize map detail will be like this

/// <summary>
       /// Sets up the map, add layers and sets styles
       /// </summary>
       /// <param name="outputsize">Initiatial size of output image</param>
       /// <returns>Map object</returns>
       private SharpMap.Map InitializeMap(System.Drawing.Size outputsize)
       {
              //Initialize a new map of size 'imagesize'
              SharpMap.Map map = new SharpMap.Map(outputsize);
       
              //Add the layers to the map object.
              //The order we add them in are the order they are drawn, so we add the rivers last to put them on top
        addcountries(map);
        addcities(map);
        addnature(map);
        addplaces(map);
 addRiverLabel(map);

              return map;
       }

Detail of CreateMap method will be like this

/// <summary>
       /// Creates the map, inserts it into the cache and sets the ImageButton Url
       /// </summary>
       private void CreateMap()
       {
              System.Drawing.Image img = myMap.GetMap();
              string imgID = SharpMap.Web.Caching.InsertIntoCache(1, img);
              imgMap.ImageUrl = "getmap.aspx?ID=" + HttpUtility.UrlEncode(imgID);
       }


We also need to catch the click event of the map.
 
       protected void imgMap_Click(object sender, ImageClickEventArgs e)
       {
              //Set center of the map to where the client clicked
              myMap.Center = SharpMap.Utilities.Transform.MapToWorld(new System.Drawing.Point(e.X, e.Y), myMap);
              //Set zoom value if any of the zoom tools were selected
              if (rblMapTools.SelectedValue == "0") //Zoom in
                     myMap.Zoom = myMap.Zoom * 0.5;
              else if (rblMapTools.SelectedValue == "1") //Zoom out
                     myMap.Zoom = myMap.Zoom * 2;
              //Save the new map's zoom and center in the viewstate
              ViewState.Add("mapCenter", myMap.Center);
              ViewState.Add("mapZoom", myMap.Zoom);
        myMap.BackColor = Color.Blue;
              //Create the map
              CreateMap();
       }

Now we have to implement the methods addcountries(map), addcities(map), addnature(map), addplaces(map) and addRiverLabel(map). Then you can add more methods to add layers on the map.

addcountries()
In this method we used countries shape file to show data on the map. This method will add two layers on the map. On layer will differentiate countries color depending on their population density. Second layer will display name of the countries.

private void addcountries(SharpMap.Map map)
    {
        //Add World Map
        SharpMap.Layers.VectorLayer laycountries = new SharpMap.Layers.VectorLayer("Countries");
        SharpMap.Layers.LabelLayer labcountries = new SharpMap.Layers.LabelLayer("labCountries");
        laycountries.DataSource = new SharpMap.Data.Providers.ShapeFile(Server.MapPath(@"~\App_data\countries.shp"), true);
        labcountries.DataSource = new SharpMap.Data.Providers.ShapeFile(Server.MapPath(@"~\App_data\countries.shp"), true);
        new SharpMap.Data.Providers.ShapeFile(Server.MapPath(@"~\App_data\countries.shp"), true);


        SharpMap.Styles.VectorStyle min = new SharpMap.Styles.VectorStyle();
        SharpMap.Styles.VectorStyle max = new SharpMap.Styles.VectorStyle();
        //Create a fill that starts from white to red
        //and an outline that starts from thin black to wide yellow
        min.Fill = new SolidBrush(Color.White);
        max.Fill = new SolidBrush(Color.Red);
        min.Outline = new Pen(Color.Black, 1);
        max.Outline = new Pen(Color.Yellow, 5);
        min.EnableOutline = true;
        max.EnableOutline = true;

        //Create theme using a population density from 0 (min) to 400 (max)
        laycountries.Theme = new SharpMap.Rendering.Thematics.GradientTheme("PopDens", 0, 400, min, max);
        map.Layers.Add(laycountries);

        labcountries.Enabled = true;
        labcountries.Style.ForeColor = Color.Black;
        labcountries.LabelColumn = "name";
        labcountries.Style.Font = new Font(FontFamily.GenericMonospace, 11, FontStyle.Bold);
        labcountries.Style.HorizontalAlignment = SharpMap.Styles.LabelStyle.HorizontalAlignmentEnum.Center;
        labcountries.Style.VerticalAlignment = SharpMap.Styles.LabelStyle.VerticalAlignmentEnum.Middle;
        labcountries.Style.Halo = new Pen(Color.Yellow, 2);
        labcountries.Style.CollisionBuffer = new System.Drawing.SizeF(30, 30);
        labcountries.Style.CollisionDetection = true;
        labcountries.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
        map.Layers.Add(labcountries);
    }

addcities()
In this method we will collect the data from cities shape file. We will add label layer to show the cities name on the map.
    private void addcities(SharpMap.Map map)
    {
        //points
        SharpMap.Data.Providers.ShapeFile dtCities = new SharpMap.Data.Providers.ShapeFile(Server.MapPath(@"~\App_data\cities.shp"), true);

        SharpMap.Layers.LabelLayer lebCities = new SharpMap.Layers.LabelLayer("cities");
        lebCities.DataSource = dtCities;
        lebCities.Enabled = true;
        lebCities.Style.ForeColor = Color.Black;
        lebCities.LabelColumn = "CITY_NAME";
        lebCities.Style.Font = new Font(FontFamily.GenericMonospace, 11, FontStyle.Bold);
        lebCities.Style.HorizontalAlignment = SharpMap.Styles.LabelStyle.HorizontalAlignmentEnum.Center;
        lebCities.Style.VerticalAlignment = SharpMap.Styles.LabelStyle.VerticalAlignmentEnum.Middle;
        lebCities.Style.Halo = new Pen(Color.Yellow, 2);
        lebCities.Style.CollisionBuffer = new System.Drawing.SizeF(30, 30);
        lebCities.Style.CollisionDetection = true;
        lebCities.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
        map.Layers.Add(lebCities);
    }

addnature()
In this method we will get data from shape file of natural (Portugal). And using vector layer to show those places in the map.

    private void addnature(SharpMap.Map map)
    {
        //Natural
        SharpMap.Layers.VectorLayer layNatural = new SharpMap.Layers.VectorLayer("Natural");
        layNatural.DataSource = new SharpMap.Data.Providers.ShapeFile(Server.MapPath(@"~\App_data\natural.shp"), true);
        layNatural.Style.Fill = new SolidBrush(Color.Green);
        layNatural.Style.Line = new Pen(Color.Green, 2);
        //Set the polygons to have a black outline
        layNatural.Style.Outline = System.Drawing.Pens.Black;
        map.Layers.Add(layNatural);
    }

addplaces()
In this method we include shape file of places. Using label layer and vector layer we will display the places name and its boundaries. 
private void addplaces(SharpMap.Map map)
    {
        //Set up a river layer
        SharpMap.Layers.LabelLayer labPlaces = new SharpMap.Layers.LabelLayer("Places");
        SharpMap.Layers.VectorLayer leyPlaces = new SharpMap.Layers.VectorLayer("vPlaces");

        labPlaces.DataSource = new SharpMap.Data.Providers.ShapeFile(Server.MapPath(@"~\App_data\places.shp"), true);
        leyPlaces.DataSource = new SharpMap.Data.Providers.ShapeFile(Server.MapPath(@"~\App_data\places.shp"), true);

        SharpMap.Styles.VectorStyle min = new SharpMap.Styles.VectorStyle();
        SharpMap.Styles.VectorStyle max = new SharpMap.Styles.VectorStyle();
        //Create a fill that starts from white to red
        //and an outline that starts from thin black to wide yellow
        min.Fill = new SolidBrush(Color.White);
        max.Fill = new SolidBrush(Color.Red);
        min.Outline = new Pen(Color.Black, 1);
        max.Outline = new Pen(Color.Yellow, 5);
        min.EnableOutline = true;
        max.EnableOutline = true;
        leyPlaces.Theme = new SharpMap.Rendering.Thematics.GradientTheme("PopDens", 0, 400, min, max);
        //map.Layers.Add(leyPlaces);

        labPlaces.Enabled = true;
        labPlaces.LabelColumn = "name";
        labPlaces.Style.ForeColor = Color.Black;
        labPlaces.Style.Font = new Font(FontFamily.GenericMonospace, 11, FontStyle.Bold);
        labPlaces.Style.HorizontalAlignment = SharpMap.Styles.LabelStyle.HorizontalAlignmentEnum.Center;
        labPlaces.Style.VerticalAlignment = SharpMap.Styles.LabelStyle.VerticalAlignmentEnum.Middle;
        labPlaces.Style.Halo = new Pen(Color.Yellow, 2);
        labPlaces.Style.CollisionBuffer = new System.Drawing.SizeF(30, 30);
        labPlaces.Style.CollisionDetection = true;
        labPlaces.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
        map.Layers.Add(labPlaces);
    }

addRiverLavel()
In this method instead of using shape file we include data from database river table. Using river table we will display rivers name on our map.

private void addRiverLabel(SharpMap.Map map)
    {
        //Set up a river layer
        string ConnStr = "Server=localhost;Port=5432;User Id=postgres;Password=mobilink.396;Database=postgis;";
        SharpMap.Data.Providers.PostGIS dtRivers = new SharpMap.Data.Providers.PostGIS(ConnStr, "rivers", "the_geom");
        SharpMap.Layers.LabelLayer lebRivers = new SharpMap.Layers.LabelLayer("RiversName");
        lebRivers.DataSource = dtRivers;
        lebRivers.Enabled = true;
        lebRivers.LabelColumn = "name";
        lebRivers.Style.ForeColor = Color.Black;
        lebRivers.Style.Font = new Font(FontFamily.GenericMonospace, 11, FontStyle.Bold);
        lebRivers.Style.HorizontalAlignment = SharpMap.Styles.LabelStyle.HorizontalAlignmentEnum.Center;
        lebRivers.Style.VerticalAlignment = SharpMap.Styles.LabelStyle.VerticalAlignmentEnum.Middle;
        lebRivers.Style.Halo = new Pen(Color.Yellow, 2);
        lebRivers.Style.CollisionBuffer = new System.Drawing.SizeF(30, 30);
        lebRivers.Style.CollisionDetection = true;
        lebRivers.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
        map.Layers.Add(lebRivers);
    }

 Output:
Output of our code will be like this. In this map, color of the countries is depending on the density of the population.


And output of Portugal region will be like this (most of the data files I had was of portugal)


We can add more shape files and data from the tables (depending on our requirement or data we have). In this map we have used Label Layer and Vector Layer. Label layer is to show data from the table to the location depending on its latitude and longitude value. On the other hand vector layer is to write show graphical data on the map like area of river, roads, boundaries etc.