Quantcast
Viewing latest article 1
Browse Latest Browse All 20

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.

Image may be NSFW.
Clik here to view.
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.

Image may be NSFW.
Clik here to view.
hit counter


Viewing latest article 1
Browse Latest Browse All 20

Trending Articles