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.

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;
}

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:

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(); 
    }

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”):

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);
    }
}
}

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

About these ads

About Bill Dollins

I've been designing and implementing geospatial systems since the early '90s, using both commercial and open-source technologies. I am available for consulting and can be contacted via the e-mail address on the 'About' page.
This entry was posted in arcgis desktop, arcobjects, c#, esri, georss, gis. Bookmark the permalink.

12 Responses to Consuming GeoRSS in ArcMap With InMemoryWorkspaceFactory

  1. Alex says:

    Great Post,
    I am working on implementing this but I need to handle multiple geometry types in the same feed and gml shapes in the feed. This is a great starting point thought. Oh and it has to be dynamic.

    I love the concept inheriting the featureLayerClass. The inMemoryWorkspace is nice but I am not sure if I can implement persistence, if the geoRSS layer is saved in a map, I have to somehow rebuild the workspace…

    If geoRSS 2.0 standards can get finalized this should be native in ArcMap.

  2. Bill Dollins says:

    @Alex

    Thanks. I’m glad you like it. I plan to get to all of those features as well. I literally had less than a day to do this.

    Persistence shouldn’t be too hard. You should be able to override the IPersistStream implementation on the FeatureLayer to handle what you need.

    As far as making it dynamic, I have typically handled that with a timer that does a refresh on some user-specified interval. I plan to add that to my custom interface.

    Handling GML shouldn’t be hard. You should be able to add that to the RssItem class. As far as handling mutlple geometry types, you can define the shape column as esriGeometryAny but I haven’t tried and/or tested that. If you do, please let me know how well it works.

    I plan to clean up what I have and post the full Visual Studio solution soon.

  3. Bill Dollins says:

    Just for the record: I tried the using esriGeometryAny and it didn’t work.

  4. Alex says:

    Hi Bill,

    Yes the timer thing is the way to go. I was initially planning to create my own scratch workspace on disk. The advantage of that is I could spawn another thread to refresh the feature class and then raise an event so the main thread refreshed itself. I wouldn’t want to do that with an in memory workspace. I might have to go that way if the performance starts becoming an issue.

    I am extending the RSSReader to read GeoRSS. As far as handling multiple geometries, the only way I see is to create 3 featureclasses. If I add the geometry type and georss node to the feed reader. I can discriminate the features in the feed and add them to the correct feature class. The disavantage is I have to parse the feed 3 times.

  5. Bill Dollins says:

    I was thinking of going the same route with three feature classes per feed but my thought was to create a factory class to manage them. Perhaps attaching the timer to that class in order to retrieve the feed. Then I could parse the feed in a single pass and assign each item to the appropriate feature class.

    BTW, don’t forget to set your layer’s ‘Cached’ property to TRUE in order to take advantage of PartialRefresh.

  6. Alex says:

    Hi,

    I just got back to this. I am having a hard time implementing persitence. I can override the call to IPersistStream. The problem is when I re-open the map, the layers are loaded as regular featurelayers not georsslayers. So the IPersistStream override on the map never happens… I wonder if you have looked at this again.

    Alex

  7. Bill Dollins says:

    I have not been able to get back to it. I’ve had two very pressing deadlines at work so I’ve had to set it aside for a bit.

  8. Saqib says:

    Greate Article, Have you posted any sample application for your article and also can I create a FeatureLayer from a file which have coordinates available.

    Thanks,

  9. Bill Dollins says:

    I have not uploaded the code but will work on that. WordPress.com doesn;t let me upload zip files (or the like). There are numerous ways you should able to accomplish what you are trying to do, though. Would an XY event layer be sufficient?

  10. Pingback: A Year of GeoMusings « GeoMusings

  11. Dev says:

    Hi Bill,

    Your post is really interesting. I was trying to find some solution to display RSS feed layer in ArcMap, and this article seems to be way to go. Thanks anyways for the direction.
    I am just doubtfull about the overall respose time if I want to display polygons or polylines shapes.

  12. Bill Dollins says:

    Dev,

    I’m glad it can help you. I agree that, if your have a lot of data or complex geometry, you may see some performance degradation. Keep me posted!

    Bill

Comments are closed.