31 March, 2008

Highlighting features with ArcGis Server 9.2

I have the need to highlight items when the user selects to zoom to a particular item from a custom search. If I was using the ResultsTask control, then the user could simply check the checkbox next to the feature, and it would highlight. However, since the client doesn't like the treeview style of the ResultsTask, I have put the search results into an HTML table. Now, when the user selects to zoom to a feature, it will automatically highlight the feature.

I found this code in the ESRI forums, and it works well.

// This gives us a nice light blue color. You can change the color to whatever you want.
ESRI.ArcGIS.ADF.ArcGISServer.RgbColor irgbc = new ESRI.ArcGIS.ADF.ArcGISServer.RgbColor();
irgbc.Red = 50;
irgbc.Green = 255;
irgbc.Blue = 255;
irgbc.AlphaValue = 255;

ESRI.ArcGIS.ADF.ArcGISServer.FIDSet fids = new ESRI.ArcGIS.ADF.ArcGISServer.FIDSet();
int[] ids = new int[1];
ids[0] = Convert.ToInt32(key); //<-- OBJECTID of the feature to highlight
fids.FIDArray = ids;

ESRI.ArcGIS.ADF.ArcGISServer.MapDescription mapdesc = ((MapFunctionality)mf).MapDescription;
ESRI.ArcGIS.ADF.ArcGISServer.LayerDescription[] layerdescs = mapdesc.LayerDescriptions;
ESRI.ArcGIS.ADF.ArcGISServer.LayerDescription layerdesc = layerdescs[layerid];
layerdesc.Visible = true;
layerdesc.SelectionColor = irgbc;
layerdesc.SelectionFeatures = fids.FIDArray;

The problem with the above code is that it highlights the entire area. Not bad for point features, but for polygons, it covers everything underneath it completely. I tried adjusting the RgbColor.AlphaValue, but that didn't change anything at all. I'm thinking that is a red herring, and doesn't really do anything at all. So what I decided to do was instead of filling the entire polygon, I just use ShowSelectBuffer as seen below:

ESRI.ArcGIS.ADF.ArcGISServer.RgbColor irgbc = new ESRI.ArcGIS.ADF.ArcGISServer.RgbColor();
irgbc.Red = 50;
irgbc.Green = 255;
irgbc.Blue = 255;
irgbc.AlphaValue = 255;

ESRI.ArcGIS.ADF.ArcGISServer.FIDSet fids = new ESRI.ArcGIS.ADF.ArcGISServer.FIDSet();
int[] ids = new int[1];
ids[0] = Convert.ToInt32(key);
fids.FIDArray = ids;

ESRI.ArcGIS.ADF.ArcGISServer.MapDescription mapdesc = ((MapFunctionality)mf).MapDescription;
ESRI.ArcGIS.ADF.ArcGISServer.LayerDescription[] layerdescs = mapdesc.LayerDescriptions;
ESRI.ArcGIS.ADF.ArcGISServer.LayerDescription layerdesc = layerdescs[layerid];
layerdesc.Visible = true;
layerdesc.ShowSelectionBuffer = true;
layerdesc.SelectionFeatures = fids.FIDArray;



By using LayerDescription.ShowSelectBuffer, the feature is outlined with the highligh color, instead of filled in. This gives us the ability to see what is within the feature, while still knowing where the feature was located.



References:



http://forums.esri.com/Thread.asp?c=158&f=2276&t=211190&mc=12#msgid719661



http://forums.esri.com/Thread.asp?c=158&f=2276&t=223224&mc=1




28 March, 2008

ESRI breaks my custom zoom to method

I have an ArcGIS Server application. The client didn't like the standard format ESRI uses for displaying search results. So I made my own version. Basically, I return an HTML table through Callback scripting and populate a DIV with it. That table contains a column which is a hyperlink which calls functionality to zoom to that particular feature. Here's my code for that:

 System.Data.DataTable dataTable = null;
ESRI.ArcGIS.ADF.Web.DataSources.IMapFunctionality mf = (ESRI.ArcGIS.ADF.Web.DataSources.IMapFunctionality )Map1.GetFunctionality(0);
if (mf != null)
{
ESRI.ArcGIS.ADF.Web.DataSources.IGISResource gisresource = mf.Resource;
bool supported = gisresource.SupportsFunctionality(typeof(ESRI.ArcGIS.ADF.Web.DataSources.IQueryFunctionality));
int shapeInd = -1;
if (supported)
{
ESRI.ArcGIS.ADF.Web.DataSources.IQueryFunctionality qfunc;
qfunc = (ESRI.ArcGIS.ADF.Web.DataSources.IQueryFunctionality)gisresource.CreateFunctionality(typeof(ESRI.ArcGIS.ADF.Web.DataSources.IQueryFunctionality), null);
string[] lids;
string[] lnames;
qfunc.GetQueryableLayers(null, out lids, out lnames);



All this works splendidly...or at least it did. That was until I went an used ESRI's MapIdentify tool. This tool by default puts things in the task results panel. When I used the zoom to functionality in the task results panel, I noticed that my custom zoom to method suddenly quick functioning. This was distressing to say the least. It seems that every time I actually start to feel like I somewhat understand ArcGIS, I get blind-sided by something stupid like this.



After some careful debugging, I discovered what was happening was that ESRI stacks new MapResourceItems on top of the actual map when you interact with the task results. So originally my code was working fine when I was getting MapResourceItems[0] since that was the only MapResourceItem. But after running the task results, both the layer names and layer ids returned from the GetQueryalbleLayers() method were coming back as GUIDs.



My solution was to loop through all MapResourceItems until I found a MapResourceItem with a name that matches the map name.





  System.Data.DataTable dataTable = null;
ESRI.ArcGIS.ADF.Web.DataSources.IMapFunctionality mf = null;
for (int i = 0; i < this.MapResourceManager1.ResourceItems.Count; i++)
{
if (string.Compare(this.MapResourceManager1.ResourceItems[i].Name, this.m_mapName, true) == 0)
{
mf = (ESRI.ArcGIS.ADF.Web.DataSources.IMapFunctionality)Map1.GetFunctionality(i);
}
}
if (mf != null)
{
ESRI.ArcGIS.ADF.Web.DataSources.IGISResource gisresource = mf.Resource;
bool supported = gisresource.SupportsFunctionality(typeof(ESRI.ArcGIS.ADF.Web.DataSources.IQueryFunctionality));
int shapeInd = -1;
if (supported)
{
ESRI.ArcGIS.ADF.Web.DataSources.IQueryFunctionality qfunc;
qfunc = (ESRI.ArcGIS.ADF.Web.DataSources.IQueryFunctionality)gisresource.CreateFunctionality(typeof(ESRI.ArcGIS.ADF.Web.DataSources.IQueryFunctionality), null);
string[] lids;
string[] lnames;
qfunc.GetQueryableLayers(null, out lids, out lnames);

19 March, 2008

BlogMyCode VS2005 plugin

I have been using Windows Live Writer for quite a while now. I like it. It contains all the info for my different blogs. It is a desktop app. Makes it easy to start a post, save it as a draft, and continue it on later and when you actually get around to posting, it uses current date/time stamp. I know blogger for instance, will post a draft with the date/time stamp of when you started it, now when you finish it.

What I have not been doing is using WLW to its full extent, or even, it seems, a portion of that extent. As I was finally getting it setup on my work computer (I had to bypass the lockout for Windows Update to do it - don't tell the IS folks, ok?). As I was searching for plugins for WLW, I can across a plugin for Visual Studio 2005 which, I have to tell you, Is freaking awesome!

BlogMyCode is Microsoft Visual Studio 2005 plugin, that helps you to blog formatted source code, by using Windows Live Writer.

BlogMyCode adds a "Blog This" element to your context menu in VS2005. Highlight some code, right-click, select "Blog This" and in just a few steps, you get this:

public ParcelIdentify(Map map, string filePath)


{


    m_map = map;


    m_filePath = filePath;


    SetupIdentify();


 


}



The drawback I have found so far (and it is probably a setting somewhere in WLW) is that when I do the "Blog This", it opens a new instance of WLW and starts a new post.



You can get the BlogMyCode VS2005 plugin here.

10 March, 2008

Using SQL Database images in the ASP.NET ReportViewer


One of the criteria for a project was for the user to input data into the database for viewing in a report. The user entered data, and uploaded an image for the report. Now, I've added images to an SQL database before. But years and years ago, way back when there wasn't an 'image' type for a field in SQL. Instead, I believe the type for the field was called 'binary' or something along those lines. The process for inserting an image into an MSSQL image field is a little different than it was before.

I start off with the ASP.NET System.Web.UI.WebControls.FileUpload control for uploading a file through the web page. Back in the ASP days, there wasn't a process for this built into ASP, so we had to 1) buy a DLL for doing this or 2) make a DLL for doing this. Either way, it wasn't integrated, and required either and ActiveX control or a Java applet to get the job done.

The data the user entered was to be available in a report created in ReportViewer. The Reporting Services report has an image object, which has the option for defining it as a database image, and define the field in the database that has the image. Now, the implication is that one would have to define the field and the report would get the data, and display the image. Easy as pie. But the reality is something wholly different. After days on end of searching and searching and reading forum posts and tutorials ad-infinitum, I finally found (although I have to admit, I don't know where now) that it isn't as simple and easy as they let on. You can't just put:

=Fields!ImageField.Value


and have it display an image. No. The report must start with a string for the image, and then the report converts that string to an image. So the question becomes 'how do I get the the data into string format?' Oddly, you have to convert the data to base 64 string, then convert it FROM base 64 string. I don't know why, but I do know this works.

=System.Convert.FromBase64String(System.Convert.ToBase64string(Fields!ImageField.Value))


I don't pretend to know why this is the way it is, but I know that it works, and the image appears. And frankly, since I know how to make it work, I don't really care why I have to do it. Oh, crap, I almost forgot. To use the source Database for a report, you've got to set the MIMEType to either image/jpg, image/png or image/bmp. Again, I don't know why this is, I just know that it is, and that is enough for me.

I spent oodles of time trying to get the image to display, but it just seemed like it would never ever work properly. Finally, I decided to create a quick and dirty (and extremely ugly) desktop application where I would pull the image from the database and assign it to a PictureBox control. What I found was as I would pull the image out of the database, and using a MemoryStream to convert it to an actual image.



System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand();
cmd.CommandText = "SELECT * FROM [REPORT_TABLE]";
System.Data.SqlClient.SqlConnection conn = new System.Data.SqlClient.SqlConnection();
conn.ConnectionString = _connectionString;
cmd.Connection = conn;
System.Data.SqlClient.SqlDataAdapter da = new System.Data.SqlClient.SqlDataAdapter(cmd.CommandText, conn);
DataSet ds = new DataSet();
da.Fill(ds);
DataRow dr = ds.Tables[0].Rows[0];
Byte[] b = (Byte[])dr["IMAGEFIELD"];
MemoryStream ms = new MemoryStream(b);
this.pictureBox1.Image = Image.FromStream(ms);



This code, while it appears on the surface that it should work, didn't. It would kick out the ambiguous "Parameter is not valid" error message at the "Image.FromStream(ms)" line. After several days of googleing and reading what was said by others regarding this error, I discovered that my code was syntactically correct and there was no real reason why it wasn't working. That is when it hit me. I wonder if I'm putting the data into the database correctly?

For inserting the data, at first I was using a stored procedure, through an SqlDataSource and it appeared to be working. That is to say, it didn't kick out any errors. Then I figured I would just go through the process of creating the SqlDataSource and, for lack of a better description, manually insert the data. Again, this seemed to work fine. No errors. But still the image was not visible in the report, and in my desktop application, it would error when trying to create the image "FromStream(Stream stream)". This got me to thinking, so I took a step back and instead of using the SqlDataSource, I decided to try using System.Data.SqlClient to insert the data and image. And lo' and behold this method worked. I don't know what is funky with SqlDataSource method, but what I know is that it didn't work - but that using SqlClient does work, and that is all that really matters to me.


string uploadFileName = string.Empty;
byte[] imageBytes = null;
if(imageUpload != null&& imageUpload.HasFile)
{
uploadFileName = imagUpload.FileName;
imageBytes = imageUpload.FileBytes;
}
System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand();
System.Data.SqlClient.SqlConnection conn = new System.Data.SqlClient.SqlConnection();
conn.ConnectionString = _connectionString;
cmd.CommandText = "INSERT INTO REPORT_TABLE (IMAGEFIELD,IMAGENAME) VALUES (@image,@filename)";
cmd.Parameters.Add("@image", SqlDbType.Image, imageBytes.Length).Value = imageBytes;
cmd.Parameters.Add("@filename", SqlDbType.VarChar, 50).Value = uploadFileName;
cmd.Connection = conn;
conn.Open();
cmd.ExecuteNonQuery();
conn.Close();



Now, just inserting the imageBytes object into the command text did not work. I had to add it as a Parameter to the SqlCommand object. Why? Hell, I don't know, but I know it works, and that is what counts for me at this point. So I put added 23 parameters to the Command object (one for each column of course) and set the values accordingly

cmd.Parameters.Add("@image", SqlDbType.Image, _imageBytes.Length).Value = _imageBytes;


This put the image into the database properly. I'm guessing the parameter is required because it actually defines exactly what DBType we are dealing with, where an normal insert statement wouldn't. At any rate it WORKS! Now the image goes into the database, and get extracted by the ReportViewer and displays properly in the report.

w00t!



Technorati Tags: , , , ,