asp:Feature
LANGUAGES: JavaScript
ASP.NET VERSIONS: 2.0
Geo-tag Your Photos Using ASP.NET and Google Maps: Part II
Upload, Tag, Save, and Retrieve Photos
By Wei-Meng Lee
In Part
I, you learned how to embed Google Maps into your ASP.NET Web applications,
how you can interact with the maps by adding markers, and how to search for
locations. In this installment, we ll extend the project created in Part I so
users can upload photos to it and geo-tag each photo using Google Maps.
Uploading Photos
The first step is to create a new folder in your project
so users can upload their photos. In Solution Explorer, right-click on the
project name and select New Folder. Name the new folder Uploads (see Figure 1).
Figure 1: Create the new Uploads
folder.
Next, add a new Web Form to the project and name it
UploadPhotos.aspx. In the Source View of UploadPhotos.aspx, add a FileUpload
control and a Button control, as shown in bold here:
<body>
<form id="form1"
runat="server">
Upload Photos<br />
<asp:FileUpload ID="FileUpload1"
runat="server" />
<br />
<asp:Button ID="btnUpload"
runat="server" Text="Upload"
Width="76px" />
</form>
</body>
Figure 2 shows the page in Design view. As you can see,
the FileUpload control allows users to upload photos to the Web application. You ll
also add a ListBox control to the page, so you can list the filenames of all
the photos available in the Uploads folder. When the user clicks on a filename
in the ListBox control, you ll use an Image control to display the photo (see
Figure 3). Figure 4 shows how UploadPhotos.aspx should look.
Figure 2: Use the FileUpload control
to upload photos to the site.
<form id="form1" runat="server">
Upload Photos<br />
<asp:FileUpload
ID="FileUpload1" runat="server" /><br />
<asp:Button
ID="btnUpload" runat="server" Text="Upload"
Width="76px" />
<table>
<tr>
<td style="width: 100px; height:
305px">
<strong>Photos</strong><br />
<asp:ListBox
ID="lstPhotos" runat="server"
AutoPostBack="True"
Height="306px"
Width="149px">
</asp:ListBox>
</td>
<td style="width: 182px; height:
305px" valign="top">
<asp:Image ID="imgPhoto"
runat="server"
Height="324px"
Width="432px" />
</td>
</tr>
</table>
</form>
Figure 3: Use an
Image control to display the photo.
Figure 4: Populate the
UploadPhotos.aspx page with various controls.
Switching to the code-behind of UploadPhoto.aspx, first
import the following namespace:
Imports System.IO
Define the ListPhotos subroutine so it will list all the
filenames of the photos stored within the Uploads folder (see Figure 5).
Private Sub ListPhotos()
Dim photos As String() =
_
Directory.GetFiles(Request.PhysicalApplicationPath
& _
"Uploads")
lstPhotos.Items.Clear()
For Each photo As String
In photos
Dim item As New
ListItem
With item
.Text =
photo.Substring(photo.LastIndexOf("\") + 1)
.Value = photo.Substring(photo.LastIndexOf("\")
+ 1)
End With
lstPhotos.Items.Add(item)
Next
End Sub
Figure 5: List all
the filenames of photos stored in the Uploads folder.
Define the click event handler for the Upload button so
users can upload photos to the Uploads folder (see Figure 6).
Protected Sub btnUpload_Click( _
ByVal sender As Object, _
ByVal e As
System.EventArgs) _
Handles btnUpload.Click
Dim savepath As String =
Request.PhysicalApplicationPath
savepath +=
"Uploads\"
If FileUpload1.HasFile
Then
savepath +=
FileUpload1.FileName
FileUpload1.SaveAs(savepath)
Response.Write("Uploading done!")
End If
ListPhotos()
End Sub
Figure 6: Upload a
photo to the Uploads folder.
When a user clicks on a filename in the ListBox control, the
image is displayed using the Image control. This is handled by the
lstPhotos_SelectedIndexChanged subroutine:
Protected Sub lstPhotos_SelectedIndexChanged( _
ByVal sender As Object, _
ByVal e As
System.EventArgs) _
Handles lstPhotos.SelectedIndexChanged
imgPhoto.ImageUrl =
"./Uploads/" & lstPhotos.SelectedValue
End Sub
Finally, invoke the ListPhotos subroutine when the page is
loaded:
Protected Sub Page_Load( _
ByVal sender As Object, _
ByVal e As
System.EventArgs) _
Handles Me.Load
If Not IsPostBack Then
ListPhotos()
End If
End Sub
Press F5 in Visual Studio 2005. You can upload a photo and
view photos by clicking on the ListBox control (see Figure 7).
Figure 7: View the uploaded photos.
Geo-tagging Photos
The next step is to allow users to geo-tag photos from the
Uploads folder. In the Source View of Default.aspx, add a table and populate it
with the controls shown in Figure 8; Figure 9 shows the new controls.
<asp:ScriptManager ID="ScriptManager1" runat="server"
EnablePartialRendering="true">
</asp:ScriptManager>
<table>
<tr>
<td rowspan="2"
style="width: 100px">
<div id="map"
style="width: 700px; height: 700px">
</div>
</td>
<td style="width: 75px; height:
538px;" valign="top">
<asp:Panel ID="Panel1"
runat="server"
Height="538px"
Width="205px"
ScrollBars="Vertical">
</asp:Panel>
</td>
</tr>
<tr>
<td style="width: 75px; height:
19px">
<asp:UpdatePanel ID="UpdatePanel2"
runat="server">
<ContentTemplate>
Filename
<asp:TextBox
ID="txtFileName" runat="server"
Width="150px">
</asp:TextBox><br />
Description
<asp:TextBox
ID="txtDescription"
runat="server"
Width="200px"
Height="55px"
TextMode="MultiLine">
</asp:TextBox><br />
<asp:Button
ID="btnAddPhoto" runat="server"
OnClientClick="AddPhoto()"
Text="Add Photo"
Width="104px" />
</ContentTemplate>
</asp:UpdatePanel>
</td>
</tr>
</table>
Latitude
...
Figure 8: Add a
table and populate it with these controls.
Figure 9: The Default.aspx page with
additional controls.
The Panel control is used to display the photos stored in
the Uploads folder. Users can then select the photos they want to add to the
map (by clicking on the photos) and the selected filename will appear in the
TextBox control (under the Filename label). Users can also add a description for
the photo. Once this is done, clicking the Add Photo button will add the
selected photo to Google Maps.
Switch to the code-behind of Default.aspx and add the
following namespace:
Imports System.IO
Next, define the LoadPhotos subroutine so you can display
all the photos stored in the Uploads folder by dynamically creating new
ImageButton controls and adding them to the Panel control (see Figure 10).
Private Sub LoadPhotos()
'---get the list of
photos in the Uploads directory---
Dim photos As String() =
_
Directory.GetFiles(Request.PhysicalApplicationPath & _
"Uploads")
For Each photo As String
In photos
Dim img As New
ImageButton
With img
.ImageUrl =
"./Uploads/" & _
photo.Substring(photo.LastIndexOf("\") + 1)
.Width = 200
.Height = 150
.BorderStyle =
BorderStyle.Double
.BorderWidth = 4
'---use the filename
as the alternate text---
.AlternateText =
photo.Substring( _
photo.LastIndexOf("\") + 1)
.Attributes.Add("onclick", "display_filename('"
& _
photo.Substring(
_
photo.LastIndexOf("\") + 1) & "'); return
false")
End With
'---add the image
control to the Panel control---
Panel1.Controls.Add(img)
Next
End Sub
Figure 10: Display
photos stored in the Uploads folder.
Each ImageButton control added to the Panel control has
the filename (of its photo) stored in the AlternateText property. Also, an
event (onclick) handler is attached to all ImageButton controls. This is to
allow users to select an image so the filename of the selected image can be
displayed in one of the TextBox controls. And because the ImageButton control
is an ASP.NET server control, you must add the ; return false sentence to
prevent a postback from occurring when the user clicks on an image:
.Attributes.Add("onclick",
"display_filename('" & _
photo.Substring( _
photo.LastIndexOf("\") + 1) & "'); return false")
In the Page_Load event of Default.aspx, invoke the
LoadPhotos subroutine so photos can be displayed when the page is loaded:
Protected Sub Page_Load( _
ByVal sender As Object,
_
ByVal e As
System.EventArgs) _
Handles Me.Load
'---load the available
photos---
LoadPhotos()
End Sub
Getting back to the Source View of Default.aspx, let s now
define the necessary JavaScript functions. The display_filename JavaScript
function is defined as follows:
//---display the name of the selected photo---
function display_filename(filename) {
document.forms[0].txtFileName.value=filename;
}
This function is invoked when the user clicks on an
ImageButton control in the Panel control. The next function to define is
AddPhoto, which is invoked when the user clicks the Add Photo button (see
Figure 11).
function AddPhoto()
{
//---HTML for displaying
the photo---
var content = '<img
width="300"
Height="225" src="./Uploads/' +
document.forms[0].txtFileName.value + '" />';
//---create the info
tabs---
var infoTabs = [
new
GInfoWindowTab("Photo", content),
new
GInfoWindowTab("Description",
document.forms[0].txtDescription.value)
];
//---set the location to
add the photo---
var centerPoint = new
GPoint(
document.forms[0].txtLongitude.value,
document.forms[0].txtLatitude.value);
//---create a new marker
object---
var marker = new
GMarker(centerPoint);
//---add the click event
for the marker---
GEvent.addListener(marker, "click",
function() {
//---open the marker
info tab---
marker.openInfoWindowTabsHtml(infoTabs);
});
//---add the marker---
map.addOverlay(marker);
//---open the marker info
tab---
marker.openInfoWindowTabsHtml(infoTabs);
}
Figure 11: Define
the AddPhoto function.
Here, a marker is inserted on the map when the user clicks
the Add Photo button. A balloon containing two tabs will appear the first tab
displays the photo selected; the second tab displays the description given by
the user.
You are now ready to test the application. Press F5 in
Visual Studio 2005. Figure 12 shows the Panel control listing all the photos
stored in the Uploads folder (assuming you uploaded photos to it). You can
select a photo, give it a description, then click the Add Photo button to add
it to the map (see Figure 13).
Figure 12: View all the photos
stored in the Uploads folder.
Figure 13: Geo-tag a photo.
Saving the Tagged Photos
You now know how to geo-tag photos by adding them to
Google Maps. However, all the markers disappear when you reload the page. This
is because Google Maps doesn t persist the markers you must devise your own
mechanism to save the markers.
To save the geo-tagged photos, you must save to a text
file located on the server the positional information (latitude and longitude),
filename, and description. This can be done via the click event handler for the
Add Photo button (remember, it s an ASP.NET Server control, so there s a
server-side event handler). For simplicity, save the information of each photo
into a text file when the user adds a photo to the map (see Figure 14).
Protected Sub btnAddPhoto_Click( _
ByVal sender As Object,
_
ByVal e As
System.EventArgs) _
Handles
btnAddPhoto.Click
'---Save as: Lat, Lng,
FileName, Description---
Dim str As String =
txtLatitude.Text & "," & _
txtLongitude.Text & "," & _
txtFileName.Text & "," & _
txtDescription.Text & vbCrLf
'---save it to file---
My.Computer.FileSystem.WriteAllText("c:\photos.txt",
str, True)
'---clear the FileName
and Description TextBoxes---
txtFileName.Text =
""
txtDescription.Text =
""
End Sub
Figure 14: Save
the information of each photo into a text file.
Each line in the text file contains information for each
geo-tagged photo, in the following format:
latitude, longitude,
filename, description
The typical content of the text file looks like this:
37.649305852772585,-122.43026733398437,
DSC00452.JPG,This is our
first stop.
37.68612977745383,-122.39765167236328,
DSC00455.JPG,This is where
we had our lunch.
Now that we can save the geo-tagged photos, the next thing
to do is load the geo-tagged photos every time the user loads the page. For
this, use a Web service hosted on the server so the client can request the list
of geo-tagged photos and add it asynchronously to the page.
First, add a new Web service file to the current project
(right-click on the project name in Solution Explorer, then select Add New
Item; select the Web Service template). Use the default name of
WebService.asmx. In the code-behind of WebService.asmx, import the following
namespace:
Imports System.IO
Then, populate it with the code shown in bold in Figure
15. Essentially, the GetTaggedPhotos function returns the content of the text
file containing the geo-tagged photos.
<WebService(Namespace:="http://tempuri.org/")> _
<WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)>
_
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()>
_
<System.Web.Script.Services.ScriptService()>
_
Public Class WebService
Inherits
System.Web.Services.WebService
Const FILE_NAME = "c:\photos.txt"
<WebMethod()> _
Public Function GetTaggedPhotos() As String
Dim fileContents As String
If File.Exists(FILE_NAME) Then
fileContents = _
My.Computer.FileSystem.ReadAllText(FILE_NAME)
Return fileContents
Else
Return String.Empty
End If
End Function
End Class
Figure 15: Retrieve
the contents of the text file containing geo-tagged photos.
Back in the Default.aspx page, add the AddTaggedPhoto
JavaScript function so you can call the Web method from the client side:
//---add the tagged photo to the map---
function AddTaggedPhoto()
{
//---get the points from
the Web service---
req =
WebService.GetTaggedPhotos(onComplete);
}
Add the <Services> element to the ScriptManager
control to be able to call a Web service asynchronously from the client using
JavaScript, as shown here:
<asp:ScriptManager ID="ScriptManager1"
runat="server"
EnablePartialRendering="true">
<Services>
<asp:ServiceReference
Path="WebService.asmx" />
</Services>
</asp:ScriptManager>
When the Web service returns the result, the onComplete
JavaScript function will be invoked (as defined in Figure 16).
//---the result returned by the Web service---
function onComplete(result)
{
//---split the results
into individual lines---
var lines =
result.split("\r\n");
for (i=0; i<lines.length-1;
i++) {
//---split the line
into individual fields---
var fields =
lines[i].split(",");
//---lat---
document.forms[0].txtLatitude.value = fields[0];
//---lng---
document.forms[0].txtLongitude.value = fields[1];
//---filename---
document.forms[0].txtFileName.value = fields[2];
//---Description---
document.forms[0].txtDescription.value = fields[3];
//---Add the photo to
the map---
AddPhoto();
}
//---clear the FileName
and Description TextBoxes---
document.forms[0].txtFileName.value = "";
document.forms[0].txtDescription.value = "";
}
Figure 16: Invoke
the onComplete JavaScript function.
Here, you need to break down the content of the text file
into separate lines, then individually by fields. Finally, add each photo to
the map by calling the AddPhoto JavaScript function. Call the AddTaggedPhoto
JavaScript function in the load JavaScript function to display the geo-tagged
photos every time the page is loaded (see Figure 17).
function load() {
if
(GBrowserIsCompatible()) {
map = new
GMap2(document.getElementById("map"));
//---display
navigational controls---
map.addControl(new
GSmallMapControl());
//---display
Map/Satellite/Hybrid---
map.addControl(new
GMapTypeControl());
//---fired when the map
is dragged---
GEvent.addListener(map,
"moveend",
function()
{
var center =
map.getCenter();
//---update the
lat and lng in the TextBox controls---
document.forms[0].txtLatitude.value =
center.lat();
document.forms[0].txtLongitude.value = center.lng();
}
);
//---display a
particular location on the map---
map.setCenter(new
GLatLng(37.65528588731535,
-122.40726470947265),
13);
//---load the previously tagged photos---
AddTaggedPhoto();
}
}
Figure 17: Call
AddTaggedPhoto in the load JavaScript function to display the geo-tagged photos.
That s it! Press F5 in Visual Studio 2005 to test the application
and geo-tag your photos. Once it s done, you can reload the page and verify
that all the previously geo-tagged photos are still visible on the map (see
Figure 18).
Figure 18: Display all the
previously geo-tagged photos when the page is loaded/reloaded.
Conclusion
To wrap up this two-part series on using the Google Maps
for geo-tagging, you learned how to upload files to the server and geo-tag them
with Google Maps. In addition, you ve made use of AJAX
to asynchronously retrieve the previously geo-tagged photos from the server so
these photos are always loaded when the user loads the page.
The source code accompanying
this article is available for download.
Wei-Meng
Lee (http://weimenglee.blogspot.com)
is a Microsoft MVP and founder of Developer Learning Solutions (http://www.learn2develop.net), a
technology company specializing in hands-on training on the latest Microsoft
technologies. He is an established developer and trainer specializing in .NET
and wireless technologies. Wei-Meng speaks regularly at international
conferences and has authored and co-authored numerous books on .NET, XML, and
wireless technologies. He writes extensively on topics ranging from .NET to Mac
OS X. He is also the author of.NET Compact
Framework Pocket Guide, ASP.NET 2.0: A
Developer s Notebook (both from O Reilly Media, Inc), and Programming Sudoku (Apress).