The Fun Part: Mapping the Data
There are 5 key things we need to do when presenting data on web maps. They are
- Plotting the data
- Maintaining view state - where was the user last - if this is there first visit - initialize the state.
- Figuring out where a user clicked on an image map and converting these coordinates to spatial coordinates relative to view state.
- Taking additional inputs such as request for specific layers or filtering of layers.
- Doing something with these coordinates and inputs - e.g. do we Zoom in, Zoom Out, Pan, filter data
Everthing beyond those 5 basic items is presentational sugar.
Initializing Properties
For this exercise, we will use arrays to keep track of the 4 maps we have. This makes it easy to extend for more maps.
Protected justmaps_string() As String = {"2002", "2003", "2004", "2005"}
Protected justmaps_maps(3) As SharpMap.Map 'its a zeroth based array
The Presentation Layer
The key parts of our aspx is shown below. Note how we have named the image controls to be in line with our array variables. See the Demo in action
<table>
<tr>
<td nowrap colspan="2">
<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:Button ID="cmdReset" Text="Reset" runat="server"/>
</td>
</tr>
<tr>
<td nowrap>Unit Type:
<asp:DropDownList ID="ddlLU" runat="server" AutoPostBack="true">
<asp:ListItem Value="ALL">ALL</asp:ListItem>
<asp:ListItem Value="A">A</asp:ListItem>
<asp:ListItem Value="C">C</asp:ListItem>
<asp:ListItem Value="CM">CM</asp:ListItem>
<asp:ListItem Value="I">I</asp:ListItem>
<asp:ListItem Value="R1">R1</asp:ListItem>
<asp:ListItem Value="R2">R2</asp:ListItem>
<asp:ListItem Value="R3">R3</asp:ListItem>
<asp:ListItem Value="RC">RC</asp:ListItem>
</asp:DropDownList>
</td>
<td>
Abandoned Type:
<asp:DropDownList ID="ddlAbandtype" runat="server" AutoPostBack="true">
<asp:ListItem Value="ALL">ALL</asp:ListItem>
<asp:ListItem Value="A">Abandoned</asp:ListItem>
<asp:ListItem Value="B">Burned</asp:ListItem>
<asp:ListItem Value="D">Boarded</asp:ListItem>
</asp:DropDownList>
</td>
</tr>
<tr>
<td><b>Survey 2002</b><br />
<asp:ImageButton Width="300" Height="300" ID="imgMap2002" runat="server" style="border: 1px solid #000;" />
</td>
<td>
<b>Survey 2003</b><br />
<asp:ImageButton Width="300" Height="300" ID="imgMap2003" runat="server" style="border: 1px solid #000;" />
</td>
</tr>
<tr>
<td>
<b>Survey 2004</b><br />
<asp:ImageButton Width="300" Height="300" ID="imgMap2004" runat="server" style="border: 1px solid #000;" />
</td>
<td>
<b>Survey 2005</b><br />
<asp:ImageButton Width="300" Height="300" ID="imgMap2005" runat="server" style="border: 1px solid #000;" />
</td>
</tr>
</table>
Initializing the Data
In ASP.NET the basic state is initialized in the Page Load event. Below is what our page load looks like
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim i As Integer
Dim img As System.Web.UI.WebControls.Image
For i = 0 To justmaps_string.GetUpperBound(0)
img = Me.FindControl("imgMap" & justmaps_string(i))
justmaps_maps(i) = Me.InitializeMap(New System.Drawing.Size(img.Width.Value, img.Height.Value), justmaps_string(i))
Next
If Page.IsPostBack Then
'Page is post back. Restore center and zoom-values from viewstate
For i = 0 To justmaps_string.GetUpperBound(0)
justmaps_maps(i).Center = ViewState("mapCenter")
justmaps_maps(i).Zoom = ViewState("mapZoom")
Next
Else
'Save the current mapcenter and zoom in the viewstate by just picking the first map
ViewState.Add("mapCenter", justmaps_maps(0).Center)
ViewState.Add("mapZoom", justmaps_maps(0).Zoom)
For i = 0 To justmaps_string.GetUpperBound(0)
GenerateMap(justmaps_maps(i), justmaps_string(i))
Next
End If
End Sub
Observe in the above, we create an instance of a sharpmap object to correspond with each .NET Image control. Below is what our initialize map code looks like.
Public Function InitializeMap(ByVal size As System.Drawing.Size, ByVal maptype As String) As SharpMap.Map
HttpContext.Current.Trace.Write("Initializing map...")
'Initialize a new map of size 'imagesize'
Dim map As SharpMap.Map = New SharpMap.Map(size)
Dim ConnStr As String = ConfigurationManager.AppSettings("DSN")
Dim dtItem As New SharpMap.Data.Providers.PostGIS(ConnStr, "abansurveys", "the_pointft")
Dim dtNeighborhoods As New SharpMap.Data.Providers.PostGIS(ConnStr, "neighborhoods", "the_geom")
Dim strWhere As String = "(yr = " & maptype & " AND abandtype <> 'N') "
Dim lyrNeighborhoods As SharpMap.Layers.VectorLayer = New SharpMap.Layers.VectorLayer("Neighborhoods")
Dim lyrNeighborhoodNames As SharpMap.Layers.LabelLayer = New SharpMap.Layers.LabelLayer("NeighborhoodNames")
Dim lyrItem As SharpMap.Layers.VectorLayer = New SharpMap.Layers.VectorLayer("Item")
If Not IsNumeric(maptype) Then 'map type should be an integer otherwise the request is a potential hack
Return map
End If
With lyrNeighborhoods
.DataSource = dtNeighborhoods
.Style.Fill = Brushes.White
.Style.Outline = System.Drawing.Pens.Black
.Style.EnableOutline = True
End With
map.Layers.Add(lyrNeighborhoods)
If Me.ddlLU.SelectedValue <> "ALL" Then
strWhere &= "AND lu = '" & Me.ddlLU.SelectedValue & "' "
End If
If Me.ddlAbandtype.SelectedValue <> "ALL" Then
strWhere &= "AND abandtype = '" & Me.ddlAbandtype.SelectedValue & "' "
End If
dtItem.DefinitionQuery = strWhere
With lyrItem
.DataSource = dtItem
.Enabled = True
With .Style
.SymbolScale = 0.8F
End With
End With
map.Layers.Add(lyrItem)
With lyrNeighborhoodNames
.DataSource = dtNeighborhoods
.Enabled = True
.LabelColumn = "name"
.Style = New SharpMap.Styles.LabelStyle
With .Style
.ForeColor = Color.Black
.Font = New Font(FontFamily.GenericMonospace, 11, FontStyle.Bold)
.HorizontalAlignment = SharpMap.Styles.LabelStyle.HorizontalAlignmentEnum.Center
.VerticalAlignment = SharpMap.Styles.LabelStyle.VerticalAlignmentEnum.Middle
.Halo = New Pen(Color.Yellow, 2)
.CollisionBuffer = New System.Drawing.SizeF(30, 30)
.CollisionDetection = True
End With
.TextRenderingHint = Text.TextRenderingHint.AntiAlias
End With
map.Layers.Add(lyrNeighborhoodNames)
map.BackColor = Color.White
map.ZoomToExtents()
HttpContext.Current.Trace.Write("Map initialized")
Return map
End Function
In the page load for each map we call a GenerateMap which renders the image of the map to the browser. The code looks like the below
'<summary>
'Grabs the map corresponding to a given image control, inserts it into the cache and sets the Image control Url to the cached image
'</summary>
Private Sub GenerateMap(ByVal aMap As SharpMap.Map, ByVal maptype As String)
Dim img As System.Drawing.Image = aMap.GetMap()
Dim imgID As String = SharpMap.Web.Caching.InsertIntoCache(1, img)
CType(Me.FindControl("imgMap" & maptype), System.Web.UI.WebControls.Image).ImageUrl = "Getmap.aspx?ID=" + HttpUtility.UrlEncode(imgID)
End Sub
Handling user requests
In this particular example, we've got a couple of actions that a user can perform.
- Zoom In
- Zoom Out
- Pan
- Filter by Unit Type
- Filter by Abandoned Type
To handle the first 3 actions, we define a single event handler that covers clicking on any of the maps. That code looks like
Protected Sub imgMap_Click(ByVal sender As Object, ByVal e As System.Web.UI.ImageClickEventArgs) Handles imgMap2002.Click, imgMap2003.Click, imgMap2004.Click, imgMap2005.Click
Dim i As Integer
'-- Set center of the map to where the client clicked
For i = 0 To justmaps_string.GetUpperBound(0)
justmaps_maps(i).Center = justmaps_maps(i).ImageToWorld(New System.Drawing.Point(e.X, e.Y))
'-- Set zoom value if any of the zoom tools were selected
If rblMapTools.SelectedValue = "0" Then '//Zoom in
justmaps_maps(i).Zoom = justmaps_maps(i).Zoom * 0.5
ElseIf rblMapTools.SelectedValue = "1" Then '//Zoom out
justmaps_maps(i).Zoom = justmaps_maps(i).Zoom * 2
End If
'--//Save the new map's zoom and center in the viewstate
ViewState.Add("mapCenter", justmaps_maps(i).Center)
ViewState.Add("mapZoom", justmaps_maps(i).Zoom)
'--//Create the map
GenerateMap(justmaps_maps(i), justmaps_string(i))
Next
End Sub
To handle query filter requests, again we use a single event handler. Note that most of the logic for filtering is in our InitializeMap routine.
Protected Sub ddl_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles ddlLU.SelectedIndexChanged, ddlAbandtype.SelectedIndexChanged
Dim i As Integer
For i = 0 To justmaps_string.GetUpperBound(0)
'-- Initialize the zoom and center to what user had last
justmaps_maps(i).Zoom = ViewState("mapZoom")
justmaps_maps(i).Center = ViewState("mapCenter")
'--//Create the map
GenerateMap(justmaps_maps(i), justmaps_string(i))
Next
End Sub
There are other ways of tying the event handler to a presentation object. We could have just as easily and perhaps more extensibly defined an onclick event for each of our
controls which would have looked something like
<asp:ImageButton Width="300" Height="300" ID="imgMap2002" runat="server" style="border: 1px solid #000;" onclick="imgMap_Click"/>
The benefit of that approach over the one we chose is that it would be easier to implement a dynamic number of maps say if you have a map for each data item in a datalist.
Post Comments About Part 2 - PostGIS and SharpMap in ASP.NET 2.0 using VB.NET: Displaying the Maps