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.
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.
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
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.
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?
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,
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.
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
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.
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.
Just for the record: I tried the using esriGeometryAny and it didn’t work.
@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.
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.