Consuming GeoRSS in ArcMap With InMemoryWorkspaceFactory

This will be my last post for a couple of weeks. I’m heading out to Florida tomorrow to spend time with my family and the Mouse. But before I head out, I thought I’d share a little something I’ve been working on.

I’ve been playing the last few days with the InMemoryWorkspaceFactory class in ArcObjects. I am looking at using it for a project I will be working on when I get back so I thought I’d do a little prototyping beforehand.

The fact that it works in memory is very attractive, especially for using volatile data. GeoRSS seemed like a natural source to use for prototyping.

I wrote this in C# as a set of tools for ArcMap 9.2. The first thing I did was create a static reference to a single InMemoryWorkspaceFactory. It’s a singleton anyway but this approach forces me to use a single entry point.

[sourcecode language=”csharp”]
public static void initInMemoryWorkspaceFactory()
{
// Create an InMemory workspace factory.
InMemoryWorkspaceFactory workspaceFactory = new InMemoryWorkspaceFactoryClass();

// Create an InMemory geodatabase.
IWorkspaceName workspaceName = workspaceFactory.Create("", "GeoRssWorkspace", null, 0);

// Cast for IName.
IName name = (IName)workspaceName;

//Open a reference to the in-memory workspace through the name object.
IWorkspace inmemWor = (IWorkspace)name.Open();
m_memFeatWorkspace = (IFeatureWorkspace)inmemWor;
}
[/sourcecode]

Next, I created a custom FeatureLayer class called GeoRssFeatureLayer. It inherits from the ArcObjects FeatureLayer class and implements a custom interface called IGeoRssFeatureLayer. Using inheritance makes it easy to pass an instance around within ArcObjects and have it behave properly. Here’s a snippet with the interface and class declaration:
[sourcecode language=”csharp”]
public interface IGeoRssFeatureLayer
{
string Uri { get; set;}
void Refresh();
}

[Guid("a675765b-30d0-4294-ad98-28579c9f8994")]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("ztGeoRss.GeoRssFeatureLayer")]
public class GeoRssFeatureLayer : ESRI.ArcGIS.Carto.FeatureLayerClass, IGeoRssFeatureLayer
private string m_uri = string.Empty;
private IFeatureClass m_fc = null;
public GeoRssFeatureLayer(string uri)
{
m_uri = uri;
this.Refresh();
}
[/sourcecode]
I also added a constructor so I could pass in the URI directly. The IGeoRssFeatureLayer.Refresh method does most of the heavy lifting with some help from the loadFeatures method and a few statics in the Ambient class (it’s a pretty name that gets the point across without conflicting with reserved words like “Environment”):
[sourcecode language=”csharp”]
public void Refresh()
{
if (m_fc != null)
{
Marshal.ReleaseComObject(m_fc);
}
m_fc = null;
ISpatialReferenceFactory srf = new SpatialReferenceEnvironmentClass();
ISpatialReference sr = srf.CreateGeographicCoordinateSystem((int)esriSRGeoCSType.esriSRGeoCS_WGS1984);
sr.SetDomain(-180, 180, -90, 90);
IGeometryDefEdit gde = (IGeometryDefEdit)new GeometryDefClass();
gde.SpatialReference_2 = sr;
gde.GeometryType_2 = esriGeometryType.esriGeometryPoint;
gde.GridCount_2 = 1;
gde.set_GridSize(0, 1000);
IFieldsEdit fe = (IFieldsEdit)new FieldsClass();
fe.AddField(Ambient.makeField("OBJECTID", esriFieldType.esriFieldTypeOID, 0, null));
fe.AddField(Ambient.makeField("SHAPE", esriFieldType.esriFieldTypeGeometry, 0, (IGeometryDef)gde));
fe.AddField(Ambient.makeField("Title", esriFieldType.esriFieldTypeString, 100, null));
fe.AddField(Ambient.makeField("Description", esriFieldType.esriFieldTypeString, 300, null));
fe.AddField(Ambient.makeField("Link", esriFieldType.esriFieldTypeString, 100, null));
fe.AddField(Ambient.makeField("Author", esriFieldType.esriFieldTypeString, 100, null));
fe.AddField(Ambient.makeField("Comments", esriFieldType.esriFieldTypeString, 300, null));
fe.AddField(Ambient.makeField("PubDate", esriFieldType.esriFieldTypeString, 50, null));
fe.AddField(Ambient.makeField("Guid", esriFieldType.esriFieldTypeString, 50, null));
IFeatureWorkspace fws = Ambient.GeoRssWorkspace;
m_fc = fws.CreateFeatureClass(System.Guid.NewGuid().ToString(), (IFields)fe, null, null, esriFeatureType.esriFTSimple, "Shape", "");
loadFeatures();
base.FeatureClass = m_fc;
}

private void loadFeatures()
{
RssFeed f = RssReader.GetFeed(m_uri);
base.Name = f.Title;
if (f.Items.Count > 0)
{
//Cast for an IWorkspaceEdit
IWorkspaceEdit workspaceEdit = (IWorkspaceEdit)Ambient.GeoRssWorkspace;
//Start an edit session and operation
workspaceEdit.StartEditing(true);
workspaceEdit.StartEditOperation();
//Create the Feature Buffer
IFeatureBuffer featureBuffer = m_fc.CreateFeatureBuffer();
//Create insert Feature Cursor using buffering = true.
IFeatureCursor featureCursor = m_fc.Insert(true);
IPoint pt = new PointClass();
int i;
for (i = 0; (i <= (f.Items.Count – 1)); i++)
{
RssItem itm = f.Items[i];
pt.X = Convert.ToDouble(itm.Longitude);
pt.Y = Convert.ToDouble(itm.Latitude);
featureBuffer.Shape = pt;
featureBuffer.set_Value(2, itm.Title);
featureBuffer.set_Value(3, itm.Description);
featureBuffer.set_Value(4, itm.Link);
featureBuffer.set_Value(5, itm.Author);
featureBuffer.set_Value(6, itm.Comments);
featureBuffer.set_Value(7, itm.Pubdate);
featureBuffer.set_Value(8, itm.Guid);
object oid = featureCursor.InsertFeature(featureBuffer);
}
//Flush the feature cursor to the database
//Calling flush allows you to handle any errors at a known time rather then on the cursor destruction.
featureCursor.Flush();
//Stop editing
workspaceEdit.StopEditOperation();
workspaceEdit.StopEditing(true);
//Release the Cursor
System.Runtime.InteropServices.Marshal.ReleaseComObject(featureCursor);
}
}
}
[/sourcecode]

That pretty much does most of the real work. I have an ArcObjects command that actually instantiates the layer and adds it to ArcMap and there are a few helper functions as well. I snagged a good bit of other people’s code to be able to turn around a prototype quickly so credit where credit is due:

  • RssReader on CodeProject – This handles the interaction with the feeds. I extended it to support geo tags
  • InputBox on CodeProject – I used this to provide a simple means to allow the user to enter a URI.
  • I also C#-ified some of Kirk’s code I found on the ESRI forums
  • I also grabbed a few lines from the ESRI help files

I’ll probably post an update when I get back but here’s a screen capture. It depicts USGS M2.5+ Earthquakes feed and the path of Hurricane Ivan in 2004. As of this writing, it only handles points in the “simple” formats.

GeoRSS in ArcMap

Basically, I like the in-memory implementation because it allows for a pretty fast refresh plus it allows selections (note the selection of the last few positions of Ivan), identify (with automatic hyperlinking from the identify window) and other operations, make it a big plus over an XY event layer. It remains to be seen if there are any memory leaks but that’ll come with further testing.

UPDATE: Basically, the biggest problem with this approach right now is that a feature class can’t contain multiple geometry types, regardless of the type of workspace. This is a big pain and makes management of a full GeoRSS feed (with points, lines and polygons) difficult. I’ll post more when I get farther.

hit counter