Monday, November 3, 2008

Installing FalconView Client Editors

As discussed in the FalconView SDK, in order to install FalconView client editors, it is necessary to add an entry to the client editors registry key (under "HKLM\Software\PFPS\FalconView\Client Editors").  The trick with installing a client editor key is that the installer must determine which client editor number to use for the key.  I recently wrote a new FalconView overlay in C# .NET and thought I'd share some steps that other developers may find useful for installing FalconView overlays.  (I'm working in Visual Studio 2005, so the steps below are for that version.  These steps are somewhat broad as I assume that the developer is experienced enough to fill in some of the details.  I also assume that the overlay is being written in .NET as this example uses all .NET classes and assemblies.)

1.  Create a new Setup Project Using Visual Studio and Add Your Binaries to the Setup

I'll leave most of this work to the reader as this post is not meant to address this part of the setup.  Suffice it to say that you add your project outputs to a new setup project in a conventional fashion so that they get installed to the desired location on the user's PC.

2.  Add an Installer Class to Your Output Assembly

An installer class derives from System.Configuration.Install.Installer.  Installer classes have a number of methods that you can override in order to execute steps at certain points during an install.

3.  Add Code to Your Installer Class to Register Your Assembly for COM

FalconView uses COM to communicate with all its client editors.  This function is a short sample that shows how you can add a method to your installer class that will register your assembly for COM so that FalconView can find your overlay class.

private void RegisterAssembly(string assemblyFile)
{
  Assembly assembly = Assembly.LoadFrom(assemblyFile);
  new RegistrationServices().RegisterAssembly(assembly,
     AssemblyRegistrationFlags.SetCodeBase);
}

4. Add Code to Your Installer Class to Set the Client Editor Key

Here's the meat of this post.  The following method sets the client editor key so that your overlay becomes visible to FalconView.

private void InstallClientEditorKey(
   string classIdString, int interfaceVersion, bool isStaticOverlay)
{
   // get the FalconView client editors key

   RegistryKey clientEditorsKey = 
      Registry.LocalMachine.OpenSubKey(
      @"SOFTWARE\PFPS\FalconView\Client Editors", true);

   // create a list of taken client editor numbers

   List listTakenNumbers = new List();
   string[] subKeys = clientEditorsKey.GetSubKeyNames();

   foreach (string subKeyName in subKeys)
   {
      try
      {
         listTakenNumbers.Add(Int32.Parse(subKeyName));
      }
      catch (Exception) { } // probably just a key that isn't an integer
   }

   // find the first available client editor number

   int iFirstAvailable = 0;
   while (listTakenNumbers.Contains(iFirstAvailable))
      iFirstAvailable++;

   // create the client editor key

   RegistryKey overlayKey = 
      clientEditorsKey.CreateSubKey(iFirstAvailable.ToString());

   // create the values

   overlayKey.SetValue("classIdString", classIdString,
      RegistryValueKind.String);
   overlayKey.SetValue("interfaceVersion", interfaceVersion,
      RegistryValueKind.DWord);
   overlayKey.SetValue("isStaticOverlay",
      isStaticOverlay ? 1 : 0, RegistryValueKind.DWord);

   // clean up

   overlayKey.Close();
   clientEditorsKey.Close();
}

5. Call Your Methods From an Overridden Method in Your Installer Class

I choose to override the Install method on the installer class.  (For more documentiontation on Installer classes, consult MSDN.)

public override void Install(IDictionary stateSaver)
{
   try
   {
      base.Install(stateSaver);

      // register the assembly for COM
      RegisterAssembly(Context.Parameters["assemblypath"]);

      // install the FalconView client editor key
      InstallClientEditorKey("Server.Overlay", 3, true);
   }
   catch (Exception ex)
   {
      MessageBox.Show(ex.Message,
         "Install Failed", MessageBoxButtons.OK,
         MessageBoxIcon.Error);
   }
}

Note the use of Context.Parameters above.  This dictionary is how all of the install parameters are passed into an Installer class by the installer.

6. Add the Custom Action to Your Setup Project

When you add the custom action, you will need to specify the entry point for the action.


This screen capture shows where to set the EntryPoint property on the custom action.  When your install reaches the custom action, it will call into your Installer class and execute the actions there.

You could add additional logic to your install, such as checking to make sure the overlay isn't already installed and adding uninstall steps, but I'll leave that as an exercise for the reader.

Tuesday, October 28, 2008

Compiling libkml on Windows

Just a quick hint here for anyone searching for information on compiling libkml under Visual Studio. Version 0.4, which I just downloaded, won't compile the libkmlbase project unless you make a small modification to time_util.cc. Just change the order of the includes so that the winsock2.h include comes before the windows.h include. If you don't, you will get compiler errors such as, "error C2011: 'fd_set' : 'struct' type redefinition."

[[ UPDATE: Apparently there are other problems with using libkml in Windows. I just came across a linker issue where the symbol IsIconParent was undefined. To solve this problem, I had to add get_link_parents.cc and get_link_parents.h to the libkmlengine project and recompile. ]]

[[ UPDATE 5 Nov. 08: Both of these issues have been corrected as of version 0.5. ]]

Thursday, October 16, 2008

FalconView Open Source

Beginning with FalconView 4.3, FalconView will become an open source product.  Georgia Tech will distribute the code to FalconView to the general public, and installable binaries will be available.  The open source effort will include the main FalconView application and most of the overlays that come packaged with FalconView.

In order to accomplish this effort, we are reworking major parts of the FalconView architucture.  Historically, FalconView has included a standard set of overlays that built-in to the executible software and a set of "plug-in" overlays that may or may not be installed with FalconView.  Because some of the overlays are export-sensitive, all of the overlays for FalconView 4.3 will be pulled out and made into plug-in overlays.  This architecture will allow us to distribute a version of FalconView for the general public and a version of FalconView for the government.  The versions will be identical, except that the government version will include the threat overlay, the tactical graphics overlay, and some other components not available to the general public.

The FalconView team is excited about this effort.  We feel a great sense of ownership in our work and will be very glad to make the product available to a wider user base.  I'd expect that public betas of FalconView 4.3 will start hitting the internet sometime in the first half of 2009.  Stay tuned as for more news as events unfold.  Feel free to contact me for more details.

Wednesday, August 20, 2008

Geoprocessing in FalconView Using ArcGIS

I recently presented this paper at the 2008 ESRI User's Conference in San Diego. It's about much of what I've discussed on this blog recently, with some added technical details. Here are the slides from the presentation, in case you missed it.

[[ Note that the slides were originally generated in PowerPoint. I've uploaded them to Google for publication on the web. Some of the formating is a bit off, but they should get the point across. ]]

Friday, July 25, 2008

FalconView / ArcGIS 9.3 Compatibility Testing

I've just finished testing the FalconView GIS Overlay with ArcGIS 9.3 and I'm pleased to say that the testing went off without so much as a bump.

When we moved from ArcGIS 9.1 to 9.2, I found several incompatibilities in the FalconView 4.1 GIS Overlay that prevented FalconView 4.1 from working with ArcGIS 9.2. ESRI had changed the behavior of some of their objects so that we had to rewrite parts of our software to make it work with their new version. In my opinion, this loss of backwards compatibility moving between minor versions was a failure on the part of ESRI software engineering. This time, though, they seem to have gotten everything exactly right. Way to go, ESRI.

I'm very pleased that Georgia Tech will be releasing a version of FalconView 4.1 that is compatible with ArcGIS 9.1, 9.2 and 9.3. FalconView 4.2 will include compatibility with ArcGIS 9.2 and 9.3. For more information on these FalconView enhancements, e-mail me directly.

Wednesday, June 25, 2008

Using FalconView Geoprocessing to Find Communications Sites

This upcoming weekend is a special weekend for the amateur radio community. Field day (which would better be called Field Days) is a time when ham radio operators go outdoors to set up temporary communications stations to practice emergency communications preparedness and to have a fun educational and relational experience doing it.

I'm going to dust off my radio license (W4LL) this field day and hit the air with a friend in Colorado. We talked about several possible sites around the Boulder area where we could set up. One near the top of our list was Rocky Mountain National Park. Since there is a plethora of GIS data available for national parks, I decided to load some into FalconView and see if I could use it to help pick a site for us.

Here are some of the things we wanted in a site:
  1. Within the park.
  2. At a moderate altitude (not too high, not in a valley).
  3. Near a road so we wouldn't have to hike too far with our gear.
  4. On an area with level slope.
  5. An area with a good view of the sky (for radio propagation).
  6. Near a river or stream, if possible.
I loaded Colorado terrain data, RMNP road, stream, trail and border data and went to work. The simple way to do this kind of analysis is to make a group of raster data sets and score them based on a weighting of the criteria above. Adding all the scores together gives you an overall raster that scores the various terrains.

I won't go into too much detail here, but the ArcGIS analysis tools integrated into FalconView performed well for the task. The reclassify tool was used to score the terrain raster for altitude, the slope tool was used to get slopes from the terrain raster, the Euclidean distance tool was used for road and stream proximity data. Once some camp sites were picked out, the view shed tool was used to determine the sky visibility from the raster.

The first picture below shows some of the outputs from the aforementioned tools drawn in FalconView. The colors are so ugly because they represent a mosaic of different outputs which a human must interpret through careful visual analysis. (Honestly, without the table of contents, which shows up on the GIS Editor, the colors aren't very meaningful.)

The next picture shows just a camp site of interest, view shed, and terrain data, all drawn in the FalconView ArcGIS Editor.

Friday, May 23, 2008

Ensuring Release of COM Objects in a .NET FalconView Plugin

As any experienced FalconView developer knows, FalconView relies on Microsoft COM technology for our plugin APIs. To write a FalconView plugin, you implement a member of the ILayerEditor interface family and update the registry to tell FalconView the class to instantiate for the plugin. It's not hard to do once you've seen it done once. The FalconView wiki has a wealth of information on writing plugins.

FalconView 4.1, which is just now hitting the streets, includes the first .NET components ever to ship along with FalconView, including the 4.1 ArcGIS overlay. When I wrote the GIS overlay for FalconView 4.1, I discovered that the stack would become corrupted whenever .NET did its garbage collection. This would lead to instability which eventually made really bad things happen, such as FalconView disappearing from under a user's nose (one exception you may see is ReportAvOnComRelease). After some research, we discovered that this was because .NET was trying to garbage collect COM objects that had already fallen out of scope in FalconView. The trick to fixing the problem - and if you're writing a .NET plugin for FalconView 4.1 this is very important to pay attention to - is to release explicitly the FalconView COM objects once you're finished with them. The following code block, by making use of finally clauses, provides an example of how to ensure the release of your COM objects, even if your routine exits early via a premature return statement or an exception. (See the Hello World example on the wiki for the full project containing this example.)

public void OnDraw(int layer_handle, object pActiveMapProj, int bDrawBeforeLayerObjects)
{
try
{
if (bDrawBeforeLayerObjects == 1)
{
IActiveMapProj pActiveMap = (IActiveMapProj)pActiveMapProj;

try
{
ISettableMapProj pSettableMapProj;
pActiveMap.GetSettableMapProj(out pSettableMapProj);

if (pSettableMapProj != null)
{
try
{
IGraphicsContext pGraphicsContext;
pActiveMap.GetGraphicsContext(out pGraphicsContext);

if (pGraphicsContext != null)
{
try
{
// ... Draw Here ...
}
finally
{
Marshal.ReleaseComObject(pGraphicsContext);
}
}
}
finally
{
Marshal.ReleaseComObject(pSettableMapProj);
}
}
}
finally
{
Marshal.ReleaseComObject(pActiveMap);
}
}
}
finally
{
Marshal.ReleaseComObject(pActiveMapProj);
}
}


Note the use of the nested try ... catch ... finally blocks. This ensures that the COM objects will be released before execution is returned to FalconView.

(In this example, I show each try block explicitly, which has the benefit of constraining the scope of objects. One could make this code more readable by avoiding the nested blocks of code and containing each Marshal.ReleaseComObject call in one big finally statement. As this big finally block could be entered from any point in the try block, this would simply require null reference testing on each COM reference before attempting to release it.)

For developers writing .NET plugins to FalconView 4.2, which is nearly ready for beta release, there is same good news here. The lead FalconView technical guru just informed me this week that FalconView will manage COM objects on the heap, paying full attention to reference counting to prevent premature disposal of the objects. Though this means that explicitly releasing COM objects in you .NET code is no longer required, it is still a nice idea to do so as it allows FalconView to dispose the object without having to wait on .NET garbage collection.

Tuesday, May 6, 2008

ArcGIS Programming Trick: Adding a Legend to a Raster Renderer

In the previous entry on creating a DTED mosaic in the FalconView ArcGIS editor, I touched on creating a RasterStretchColorRampRenderer to symbolize the output of the DTED mosaic. This post is a short extension of that entry. One detail that we discovered in implementing the renderer is that the color ramp did not draw on the table of contents legend. It turns out that there is a trick to getting the color ramp to draw. As far as I can tell, this trick is undocumented on EDN, but we learned it through the help of an ArcGIS guru at ESRI.

There are two things that need to happen for the legend to be created, neither of which we were doing. After instantiating the RasterStretchColorRampRenderer, it is important to associate the raster we are rendering with the renderer and to call Update on the renderer immediately thereafter. The code in the previous blog entry must be changed to look like this (the highlighted code is new):

// Create a renderer to be used with the raster layer

IRasterStretchColorRampRenderer pRasterStretchColorRampRenderer = new RasterStretchColorRampRendererClass();
IRasterRenderer pRasterRenderer = (IRasterRenderer)pRasterStretchColorRampRenderer;

pRasterRenderer.Raster = pRasterLayer.Raster;
pRasterRenderer.Update(); // the call of Update right after setting Raster is important

pRasterStretchColorRampRenderer.BandIndex = 0;
//...


In this case pRasterLayer is created in the same way as before, except we moved the block of code that creates the layer earlier in the raster creation procedure.

Monday, April 21, 2008

Quantum GIS: First Impressions

As I mentioned in the welcome to this blog, part of why I want to write here is to grow professionally by trying technologies outside the four walls of my FalconView / ArcGIS office.  Quantum GIS is a piece of GIS software not much different from FalconView in its mission.  It displays vector layers, raster maps and imagery; it also includes some task-oriented geoprocessing and editing capabilities.  Being a typical engineering type, I only spent a few minutes reading the instructions, too eager to dive right in and start trying the thing.  Here are my first impressions of Quantum GIS.  Please keep in mind that these are impressions coming from an experienced GIS user with no previous exposure to QGIS, so some of my facts about QGIS may be incorrect, but I probably represent a typical GIS user who would be considering this type of solution.  Here are my impressions from my short test drive.



Right off, I discovered support for drawing GeoTIFFs and Shapefiles.  The picture above and left is an image of the Georgia Tech campus drawn in QGIS.  (I included the same image in FalconView on the right just for kicks.)  The image below shows the Shapefile.  Right off I was encouraged: QGIS is easy and intuitive to use.  Note that I opened the GeoTIFF in an Ubuntu QGIS install and the Shapefile in a Windows install.  This is a nice thing about most open source software: the authors generally do a good job of achieving platform independence.

Notice that I don't have the Shapefile and the GeoTIFF drawing together in the same window.  After a little poking around, I wasn't able to get this to work properly.  I assume my difficulty was due to differing coordinate systems between the two files and my lack of experience setting up coordinate systems in QGIS, but I'm still not sure.  Since I was going for first impressions, I gave up trying the get them to draw together after a short time.  (ArcMap and FalconView generally transform different coordinate systems to draw together without much difficulty, though there is some danger to doing this.)  I did get WMS support working, but I could only get their sample WMS servers to draw.  I suspect that with some more experimentation (or actually reading the instructions), I could get support for different coordinate systems and WMS working better.

There are a few other features I quickly discovered that are worth mentioning.  As you would expect, QGIS includes good support for custom symbolization of vector data and exporting a map to an image file.  There is also a neat feature where you can reclaim real estate on the map by pressing CTRL + T / T to hide and show the toolbars.  Finally - and I've saved the best for last - if you're in the market for something to create and edit your own Shapefile, QGIS does this nicely.  Unlike ArcMap, where feature editing, though powerful, is about as natural as breathing underwater, QGIS makes it intuitive.  (Shameless plug: You can create and edit Shapefiles in FalconView via the GIS Editor, which is essentially the same as doing it in ArcMap.)

I very much want to revisit, in a future posting here, the advanced programmatic features of Quantum GIS.  There is support for an interactive Python console (I'm a Python novice, so maybe this will be the push I need to learn it better), and there is support for plugins to QGIS.  I spent about 30 minutes poking around on the QGIS web site, wiki, and Internet in general to learn more about plugin capabilities and how to write one, but I couldn't find anything useful.  Some quick e-mail correspondence with a QGIS developer pointed me here.  (And there we find yet another advantage to using this active open source project: it's not too hard to find help from the project volunteers.)

I'll sum up my initial impressions with Quantum GIS by saying that it's a maturing product worthy of applause.  It does most of what it's supposed to do with ease, and I suspect that with a little more experience, I could overcome the hurdles I experienced.  I enjoyed playing with the tool, which says a lot since I have a pretty short attention span.

If you want a quick demo of the basic capabilities, check out this video.

Thursday, April 10, 2008

Creating a Mosaic of FalconView DTED in the GIS Editor

In my previous post on the subject of DTED in the FalconView ArcGIS Editor, I mentioned that bringing DTED into the GIS Editor is a two step process.  The FalconView user first has the Editor create a feature class that shows all the DTED tiles in the FalconView map data manager; the second step is to select the area of interest and create a DTED mosaic from that area.  The previous post examined the creation of a feature class to show DTED coverage.  This post will examine the technical details of how the GIS Editor uses the geoprocessing capabilities of ArcGIS to add a nicely rendered DTED mosaic to the map.

(Please note that the code below is still somewhat experimental.  Comments and suggestions are welcome.  As FalconView 4.2 is still under development, there are some discussions within Georgia Tech and our user community as to how the final user experience for this will happen.)

1. Get a list of DTED tiles to add to the mosaic

The code block below shows how to enumerate all features the layer of FalconView DTED coverage, collecting a list of DTED tiles which will later be passed to the Mosaic geoprocessing tool.  Note that the only end result from this code block is sMosaicInput, the colon separated list of tiles to add to the mosaic (e.g. "C:\dted\w086\n35.dt1;C:\dted\w086\n34.dt1").  I have highlighted the two most interesting parts of the code: the part that builds the lookup table of selected features and the part that builds sMosaicInput from the features in the lookup table.
string sMosaicInput = null;

// ... some redacted code ...

// The block below populates sMosaicInput

try
{
// Create a lookup table of selected features

IFeatureSelection pFeatureSelection = (IFeatureSelection)pLayer;
Dictionary<int, object> dictSelectedIDs = new Dictionary<int, object>();

IEnumIDs pEnumIDs = pFeatureSelection.SelectionSet.IDs;
for (int id = pEnumIDs.Next(); id != -1; id = pEnumIDs.Next())
dictSelectedIDs[id] = null;

// ... some redacted code ...

// Enumerate all features, adding selected paths to the list

IFeatureLayer pFeatureLayer = (IFeatureLayer)pLayer;
IFeatureCursor pFeatureCursor = pFeatureLayer.FeatureClass.Search(null, true);
int iPathField = pFeatureCursor.FindField("Path");

for (IFeature pFeature = pFeatureCursor.NextFeature(); pFeature != null; pFeature = pFeatureCursor.NextFeature())
{
if (dictSelectedIDs.ContainsKey(pFeature.OID))
{
string sPath = (string)pFeature.get_Value(iPathField);
if (sMosaicInput == null)
sMosaicInput = sPath;
else
sMosaicInput += ';' + sPath;
}
}
}
catch (Exception ex)
{
// ... some redacted code ...

}
2. Use geoprocessing tools to create the mosaic raster

Once we have compiled the string containing the paths of the DTED tiles to be added to the mosaic, creating the mosaic is a simple task requiring the use of two geoprocessing tools.  In the code below, the first highlighted section executes a CreateRasterDataset geoprocessor, creating a scratch raster dataset in a scratch file geodatabase.  The second highlighted lines execute the Mosaic geoprocessor.
// Create a new raster dataset to hold the mosaic

string sRasterName = "dted_mosaic" + Guid.NewGuid().ToString("N");
const string PIXEL_TYPE = "16_BIT_SIGNED"; // Matches DTED 1 & 2 in ArcMap
const int NUMBER_OF_BANDS = 1; // Matches DTED 1 & 2 in ArcMap

CreateRasterDataset createRasterDataset = new CreateRasterDataset(TempGeodatabaseUtils.TempFileGeodatabase, sRasterName, PIXEL_TYPE, NUMBER_OF_BANDS);

Geoprocessor geoprocessor = new Geoprocessor();
geoprocessor.Execute(createRasterDataset, null);
for (int j = 0; j < geoprocessor.MessageCount; j++)
   LoggingUtils.TraceMessage(geoprocessor.GetMessage(j));

// Create the mosaic

Mosaic mosaic = new Mosaic(sMosaicInput, TempGeodatabaseUtils.TempFileGeodatabase + '\\' + sRasterName);
mosaic.mosaic_type = "BLEND";
mosaic.nodata_value = -32767;  // NoData for DTED 1 & 2 in ArcMap

geoprocessor = new Geoprocessor();
geoprocessor.Execute(mosaic, null);
for (int j = 0; j < geoprocessor.MessageCount; j++)
   LoggingUtils.TraceMessage(geoprocessor.GetMessage(j));
3. Symbolize the mosaic and add it to the map

Once the mosaic is created, it must be symbolized before it is added to the FalconView map.  After some experimentation, we found that a simple color ramp shows the features of the DTED well.  (Note that a possible improvement to this would be to add a hillshade effect.)  The first section of highlighted code below shows how to create raster statistics programmatically.  The raster statistics will be used to determine the parameters of the renderer.  The highlighted section shows how to build a simple stretch color ramp.  The last lines show how to create a raster layer from the raw raster, apply the renderer, and add the raster to the map control.
// Set up a raster data object from the mosaic

IWorkspaceFactory pWorkspaceFactory = new FileGDBWorkspaceFactory();
IWorkspace pWorkspace = pWorkspaceFactory.OpenFromFile(TempGeodatabaseUtils.TempFileGeodatabase, hWnd);
IRasterWorkspaceEx pRasterWorkspace = (IRasterWorkspaceEx)pWorkspace;
IRasterDataset pRasterDataset = pRasterWorkspace.OpenRasterDataset(sRasterName);

// Create raster statistics so we can find the highest and lowest points on the raster for the color ramp

IRasterBandCollection pRasterBandCollection = (IRasterBandCollection)pRasterDataset;
IEnumRasterBand pEnumRasterBand = pRasterBandCollection.Bands;
IRasterBand pRasterBand = pEnumRasterBand.Next();
pRasterBand.ComputeStatsAndHist();
IRasterStatistics pRasterStatistics = pRasterBand.Statistics;

// Create a renderer to be used with the raster layer

IRgbColor pFromColor = new RgbColorClass();
pFromColor.Red = 254;
pFromColor.Green = 254;
pFromColor.Blue = 255;

IRgbColor pToColor = new RgbColorClass();
pToColor.Red = 0;
pToColor.Green = 0;
pToColor.Blue = 255;

IAlgorithmicColorRamp pAlgorithmicColorRamp = new AlgorithmicColorRampClass();
pAlgorithmicColorRamp.FromColor = pFromColor;
pAlgorithmicColorRamp.ToColor = pToColor;
pAlgorithmicColorRamp.Algorithm = esriColorRampAlgorithm.esriCIELabAlgorithm;
pAlgorithmicColorRamp.Size = 50;

bool bOK;
pAlgorithmicColorRamp.CreateRamp(out bOK);
LoggingUtils.Assert(bOK, "Failed to create color ramp");

IRasterStretchColorRampRenderer pRasterStretchColorRampRenderer = new RasterStretchColorRampRendererClass();
pRasterStretchColorRampRenderer.BandIndex = 0;
pRasterStretchColorRampRenderer.LabelHigh = pRasterStatistics.Maximum.ToString();
pRasterStretchColorRampRenderer.LabelLow = pRasterStatistics.Minimum.ToString();
pRasterStretchColorRampRenderer.ColorRamp = pAlgorithmicColorRamp;

// Create the raster layer and add it to the map

IRasterLayer pRasterLayer = new RasterLayerClass();
pRasterLayer.CreateFromDataset(pRasterDataset);
pRasterLayer.Name = "FalconView DTED Mosaic";
pRasterLayer.Renderer = (IRasterRenderer)pRasterStretchColorRampRenderer;

pMapControl2.AddLayer(pRasterLayer, 0);







Wednesday, April 2, 2008

Consuming FalconView DTED in the GIS Editor

The FalconView 4.2 ArcGIS Editor will have the capability of pulling DTED from FalconView into an ESRI ArcMap document so that users can perform geoprocessing tasks with the terrain data. In this blog posting, I will examine this capability that will be in FalconView 4.2 and will give an overview of how this capability was implemented technically. The reader will learn how to retrieve coverage data from the FalconView map data manager and how to create a feature class from this coverage data.

Users add DTED tiles to their FalconView coverage database through the FalconView map data manager. Once the DTED is loaded into FalconView, users can draw elevation data visually, use elevation data in route planning, calculate terrain masking of radar systems, and so on. While FalconView has numerous uses for terrain elevation data, ArcGIS has a generalized geoprocessing framework which allows GIS intelligence officers to perform a wide variety of specialized calculations for their mission planning. The integration of FalconView and ArcGIS will provide a simplified framework for mission planning which requires geoprocessing with terrain data.

The screen capture to the left shows FalconView drawing DTED two ways (the DTED view is zoomed out to 25% to remove detail so that the screen capture is suitable for posting to a public forum.) The black and white background map is the normal DTED base map drawn by FalconView. The blue and white tile in the center is a mosaic of four DTED tiles being drawn by the GIS Editor. The FalconView base map is optimized for a viewing experience that shows the general layout of the terrain. The GIS Editor splotch in the center of the map is optimized for a viewing experience that highlights the high and low areas of the terrain.

For GIS Editor users, creating a mosaic of FalconView DTED is a two step process. First, the user creates a vector overlay of polygons which describe the DTED coverage available from FalconView. Next, the user selects the DTED tiles he wishes to turn into a mosaic and creates the mosaic. The coverage overlay and the mosaic are both automatically created by the GIS Editor and are stored in temporary geodatabases. The screen capture to the right shows the tiles of DTED available on my development system. Notice the little raster blotch in the Eastern US. This is a mosaic of four DTED tiles. (It may be necessary to click on the image to the right to see the detail of the overlay.)

Let's explore the technical steps necessary to bring this DTED from FalconView into ArcGIS. (In this blog post we will not examine the steps used to create the DTED mosaic, though we may explore that in a follow-up post.)

1. Creating an ArcGIS feature class to contain the FalconView DTED

ArcGIS always holds feature classes in some sort of geodatabase. In the case of the FalconView DTED, we load the coverage data into a scratch file geodatabase.
// Create the output workspaces

IWorkspaceFactory pOutWorkspaceFactory = new FileGDBWorkspaceFactory();
IWorkspace pOutWorkspace = pOutWorkspaceFactory.OpenFromFile(TempGeodatabaseUtils.TempFileGeodatabase, hWnd);
IFeatureWorkspace pOutFeatWorkspace = (IFeatureWorkspace)pOutWorkspace;
The temporary file geodatabase referred to above is created during the call to the TempFileGeodatabase property, if it didn't exist before.

After we get the reference to the output feature workspace, we must create the Fields that will be stored with the feature class. The fields we create include a shape field to hold the shape of the polygon, a path field which holds the path to the DTED tile, and a level field which indicates the DTED level (0, 1, 2, 3, etc.).
// Create the fields object which will hold the fields for the feature class. I have put much of the field creation
// logic in its own block to demonstrate that the only object that matters after the fields are built is pFields.

IFields pFields = new FieldsClass();

{
// Set up the polygon shape field for the feature class

IFieldsEdit pFieldsEdit = (IFieldsEdit)pFields;

IField pField = new FieldClass();
IFieldEdit pFieldEdit = (IFieldEdit)pField;
pFieldEdit.Name_2 = "Shape";
pFieldEdit.Type_2 = esriFieldType.esriFieldTypeGeometry;

IGeometryDef pGeometryDef = new GeometryDefClass();
IGeometryDefEdit pGeometryDefEdit = (IGeometryDefEdit)pGeometryDef;
pGeometryDefEdit.GeometryType_2 = esriGeometryType.esriGeometryPolygon;

ISpatialReferenceFactory2 pSpaRefFact2 = new SpatialReferenceEnvironmentClass();
IGeographicCoordinateSystem pGeoCoordSys = pSpaRefFact2.CreateGeographicCoordinateSystem(4326); // 4326 is esriSRGeoCS_WGS1984
ISpatialReference pSpatialReference = (ISpatialReference)pGeoCoordSys;

// Set the domain on the coordinate system
// This prevents the error, "The XY domain on the spatial reference is not set or invalid."

ISpatialReferenceResolution pSpatialReferenceResolution = (ISpatialReferenceResolution)pSpatialReference;
pSpatialReferenceResolution.ConstructFromHorizon();

pGeometryDefEdit.SpatialReference_2 = pSpatialReference;
pFieldEdit.GeometryDef_2 = pGeometryDef;
pFieldsEdit.AddField(pField);

// Add the Path field

pField = new FieldClass();
pFieldEdit = pField as IFieldEdit;
pFieldEdit.Name_2 = "Path";
pFieldEdit.Type_2 = esriFieldType.esriFieldTypeString;
pFieldsEdit.AddField(pField);

// Add the Level field

pField = new FieldClass();
pFieldEdit = pField as IFieldEdit;
pFieldEdit.Name_2 = "Level";
pFieldEdit.Type_2 = esriFieldType.esriFieldTypeInteger;
pFieldsEdit.AddField(pField);

// Add other required fields to the feature class

IObjectClassDescription pObjectClassDescription = new FeatureClassDescriptionClass();

for (int i = 0; i < pObjectClassDescription.RequiredFields.FieldCount; i++)
{
pField = pObjectClassDescription.RequiredFields.get_Field(i);
if (pFieldsEdit.FindField(pField.Name) == -1)
pFieldsEdit.AddField(pField);
}
}
Note the loop an the end of this block of code that ensures all required fields are added to the feature class. When creating a feature class from scratch, it is important to add all of the required fields to the class. In a debug session, I see this block of code skips adding the SHAPE field, which we manually created at the beginning of the block, but adds the required OBJECTID field. The image below is a snapshot of the attributes table of the new feature class as seen in the FalconView GIS Editor. If you click on the image, you will see the fields we added to the feature class, along with some of the data which we will add in the next step.
The next step is to create the feature class, which is relatively simple. The code below also shows how we set up some references we will use to add features to the feature class.
// Create the feature class

string sFeatureClassName = "dted" + Guid.NewGuid().ToString("N");
IFeatureClass pFeatureClass = pOutFeatWorkspace.CreateFeatureClass(
sFeatureClassName, pFields, null, null, esriFeatureType.esriFTSimple, "Shape", null);

// Get an insert cursor and a feature buffer

IFeatureCursor pFeatureCursor = pFeatureClass.Insert(true);
IFeatureBuffer pFeatureBuffer = pFeatureClass.CreateFeatureBuffer();

// Get the indicies for the fields of interest

int iPathFieldIndex = pFeatureBuffer.Fields.FindField("Path");
int iLevelFieldIndex = pFeatureBuffer.Fields.FindField("Level");
Note that creating a feature class and adding fields to the class can also be done using several geoprocessing objects, but, once you understand the steps above, it's simpler to do it as described above.

2. Pulling DTED coverage tiles from the FalconView Map Data Manager

The block of code below shows how to enumerate all FalconView map coverage, creating polygons from DTED tiles and adding them to our feature class. Note that there are three nested loops here. The outer loop enumerates over all of the data sources in the FalconView coverage database. The next inner loop enumerates over all of the DTED levels on the data source under consideration (we're only enumerating levels 1 through 3 for now). The innermost loop enumerates over all of the tiles of the particular DTED level on the particular data source. A polygon feature is created for each tile of DTED enumerated in the innermost loop and is added to the feature class using the insert cursor.
// Initialize the coverage rowset

ICoverageRowset coverageRowset = new CoverageRowsetClass();
coverageRowset.Initialize("Dted");

// Enumerate all data sources, cataloging DTED

const int FLUSH_INTERVAL = 100; // Experiment with this value as needed
int iRowsProcessed = 0;
Dictionary<string, object> dictFoldersAdded = new Dictionary<string, object>();

IDataSourcesRowset dataSourcesRowset = new DataSourcesRowsetClass();
dataSourcesRowset.SelectAll(1 /* Online Only */);
while (dataSourcesRowset.m_Identity > 0)
{
string sLocalFolderName = dataSourcesRowset.m_LocalFolderName;

// Enumerate all DTED on this data source

for (int iDtedLevel = 1; iDtedLevel <= 3; iDtedLevel++)
{
coverageRowset.SelectByGeoRectAndDS(dataSourcesRowset.m_Identity, iDtedLevel, -90, -180, 90, 180);

while (coverageRowset.m_LocationSpec != "")
{
// Get the path to this DTED tile and skip it if it's already added

string sPath = sLocalFolderName + '\' + coverageRowset.m_LocationSpec;

if (dictFoldersAdded.ContainsKey(sPath.ToLower()))
goto next_row; // Sometimes can happen when map data server acts up
dictFoldersAdded[sPath.ToLower()] = null;

// Get the bounds of this DTED tile

double dllLat, dllLon, durLat, durLon;
coverageRowset.GetBounds(out dllLat, out dllLon, out durLat, out durLon);

// Create a polygon from the coverage rectangle

IPolygon pPolygon = new PolygonClass();
IPoint pPoint = new PointClass();
IPointCollection pPointCollection = pPolygon as IPointCollection;
object missing = Type.Missing;

pPoint.X = dllLon;
pPoint.Y = dllLat;
pPolygon.FromPoint = pPoint;
pPoint.X = durLon;
pPointCollection.AddPoint(pPoint, ref missing, ref missing);
pPoint.Y = durLat;
pPointCollection.AddPoint(pPoint, ref missing, ref missing);
pPoint.X = dllLon;
pPointCollection.AddPoint(pPoint, ref missing, ref missing);
pPolygon.Close();

// Add the polygon to the feature class

pFeatureBuffer.Shape = pPolygon;
pFeatureBuffer.set_Value(iPathFieldIndex, sPath);
pFeatureBuffer.set_Value(iLevelFieldIndex, iDtedLevel);
pFeatureCursor.InsertFeature(pFeatureBuffer);

// Flush the data from time to time

if (++iRowsProcessed % FLUSH_INTERVAL == 0) pFeatureCursor.Flush();

// Move to the next DTED coverage rowset

next_row: coverageRowset.MoveNext();
}
}

// Move to the next FalconView data source

dataSourcesRowset.MoveNext();
}
The feature cursor is flushed from time to time during its population and again after all of the DTED tiles have been added to the feature class. It's important after this step to manually release all of your COM objects so that the lock on the feature class in the geodatabase is removed. The last step is to create a feature layer from the feature class. (All of these steps are shown below.) The feature layer may be directly added to the map.
// Flush the feature cursor and release COM objects

pFeatureCursor.Flush();

Marshal.ReleaseComObject(pFeatureBuffer);
Marshal.ReleaseComObject(pFeatureCursor);
Marshal.ReleaseComObject(pFeatureClass);
Marshal.ReleaseComObject(pOutFeatWorkspace);
Marshal.ReleaseComObject(pOutWorkspace);
Marshal.ReleaseComObject(pOutWorkspaceFactory);

// Return a DTED feature layer

IWorkspaceFactory pWorkspaceFactory = new FileGDBWorkspaceFactoryClass();
IWorkspace pWorkspace = pWorkspaceFactory.OpenFromFile(TempGeodatabaseUtils.TempFileGeodatabase, hWnd);
IFeatureWorkspace pFeatureWorkspace = (IFeatureWorkspace)pWorkspace;
pFeatureClass = pFeatureWorkspace.OpenFeatureClass(sFeatureClassName);

IFeatureLayer pFeatureLayer = new FeatureLayerClass();
pFeatureLayer.Name = FV_DTED_LAYER_NAME;
pFeatureLayer.FeatureClass = pFeatureClass;

Friday, March 28, 2008

Integration of FalconView and ArcGIS

Starting with version 4.1, FalconView will be able to read and display map documents generated by ArcMap through a plugin overlay which will be shipped and installed with FalconView. When the user installs ArcGIS, a button that activates the GIS Overlay will appear on the FalconView toolbar. The early FalconView 4.1 version of the GIS Overlay will allow users to view ArcGIS map documents and data in FalconView and do some limited changes to the way those map documents are displayed. This paper describes the 4.1 effort in some detail.

(For users who do not have access to an ArcGIS license, FalconView will continue to function normally without the GIS Overlay. Many FalconView users - perhaps the majority of them - will have access to ArcGIS through the CJMTK program.)

I'm quite excited about the project I'm working on now. FalconView 4.2, which is still in development, will not only have all of the features of FalconView 4.1, but it will contain a GIS Editor which is almost like an "ArcMap lite." It will allow editing of map documents and feature classes, spatial queries, access to the ArcGIS geoprocessing libraries, and lots of extras. The output of the GIS Editor will be drawn as an overlay on the FalconView map. We're planning to allow users to pull data from existing FalconView overlays (local points, threats, tactical graphics, DAFIF, DTED, etc.) into the GIS Editor so that FalconView data can be used in spatial queries and geoprocessing. Map documents generated in the FalconView 4.2 GIS Editor can be saved and opened in ArcMap.

FalconView 4.2 will enter beta testing this summer. The screen capture included with this entry shows where we are at the moment (click on it to see a larger image.) It's a big undertaking, but the early results are promising. Expect some technical discussion of the ArcGIS and FalconView programming involved in this effort in future blog entries here. Stay tuned.

Thursday, March 27, 2008

Welcome to GIS Coder!

Welcome to GIS Coder, a blog about all things GIS. We're going to start this blog with a focus on the technical side of GIS programming, though we may very well branch into other aspects of GIS.

By way of introduction, I'm Joel Odom, a research scientist at Georgia Tech. I'm in the FalconView department, and I specialize on the integration of FalconView and ArcGIS. Because of my background, you'll find me initially doing a lot of talking about these programs. I'm going to dive into technical aspects as much as my professional responsibilities allow. As a government funded academic researcher, I expect to get a lot of leeway in sharing my research.

My aim in this blog is to promote information sharing within the GIS community, to promote the FalconView program - a highly successful program of which I'm proud to be a part - and to promote my own professional development by forcing myself to think outside the walls of my office as I share technology, ideas and news. That's how the blog is going to start - we'll see where it goes.

The layout and format of this blog will change over time. The style of the blog - now informal - and the content of the blog will also surely evolve over time. I'm open to outside writers and welcome all feedback via the comment system. Comment moderation will initially be disabled, though I may choose to moderate comments if that becomes a problem.

(Because FalconView was mostly developed under Department of Defense funding, there are some aspects of the FalconView program which are "sensitive." By and large, FalconView is, in and of itself, unclassified and not sensitive, though it is export-controlled. There is a movement underway to create a version of FalconView which is available to export, maybe even open-source. For more information on the FalconView program itself, you may contact me directly, or you may contact the FalconView program manager at Georgia Tech. Be assured that nothing on this blog will touch on technology that compromises security or intellectual property rights.)