Wednesday, November 14, 2007

Grails + ROME + geoRSS

Grails + ROME + geoRSS

If you are looking to do some RSS/ATOM feeds in Grails you likely are looking at either at the examples that Glen Smith has done (ref: http://blogs.bytecode.com.au/glen/2006/12/22/1166781151213.html ) or to work with the Feeds plugins (ref: http://grails.org/Feeds+Plugin ) by Marc Palmer. Both are excellent places to start.

What I want to talk about there is adding geospatial data to a RSS feed in the Grails framework by building off this work. The above efforts leverage off the ROME package from http://rome.dev.java.net. For geospatial elements there is the GeoRSS Module for ROME by Marc Wick (ref: http://georss.geonames.org ).

Adding this ability to a Grails project is rather easy. Obviously you need to have the Rome and GeoRSS jar files from these referenced sites in your project lib directory. From there you can do something like the following. Please note these examples are based on the work of these previously mentioned individuals and the credit is theirs. I just glued things together.

So likely you will end up with something like this in your controller:

  // some import for this..   (watch me polute my namespace) ;)
import com.sun.syndication.feed.synd.*;
import com.sun.syndication.io.SyndFeedOutput;
import com.sun.syndication.feed.module.georss.*;
import com.sun.syndication.feed.module.georss.geometries.*;


def supportedFormats = ["rss_0.90", "rss_0.91", "rss_0.92", "rss_0.93", "rss_0.94", "rss_1.0", "rss_2.0", "atom_0.3"]

def rss = {
render(text: getFeed("atom_0.3"), contentType: "text/xml", encoding: "UTF-8")
}

def all = {
def format = params.id
if (supportedFormats.contains(format)) {
render(text: getFeed(format), contentType: "text/xml", encoding: "UTF-8")
} else {
response.sendError(response.SC_FORBIDDEN);
}
}

def getFeed(feedType) {

def ageModels = AgeModel.list()
def jdb = Sql.newInstance('[ WE ARE MAKING SQL CALLS SO THE CONNECT STRING GOES HERE. GORM WOULD BE NICER, BUT NOT AN OPTION FOR ME')

def entries = []
ageModels.each {ageModel ->
List functionData = jdb.rows("select latitude_degrees, longitude_degrees from hole where leg like ${ageModel.leg} and site like ${ageModel.site} and hole like '${ageModel.hole}'")
def locationString = functionData[0][1] + ", " + functionData[0][0]

def desc = new SyndContentImpl(type: "text/plain", value: "Entry for leg " + ageModel.leg + " locate at " + locationString + ageModel.rating + " " + ageModel.status);
def entry = new SyndEntryImpl(title: ageModel.leg + "_" +ageModel.site + ageModel.hole + " - " + ageModel.user,
link: 'http://localhost:8080/janusAmp/rest/lsh/' + ageModel.leg + "/" + ageModel.site+ "/"+ageModel.hole,
publishedDate: ageModel.date, description: desc);

// Select the style of encoding you want to do.
// def geoRSSModule = new W3CGeoModuleImpl()
// def geoRSSModule = new GMLModuleImpl()
def geoRSSModule = new SimpleModuleImpl()
geoRSSModule.setPosition(new Position(functionData[0][0], functionData[0][1]));
entry.getModules().add(geoRSSModule);
entries.add(entry);
}

SyndFeed feed = new SyndFeedImpl(feedType: feedType, title: 'Recently added age models',
link: 'http://www.chronos.org', description: 'List of age models from Janus data',
entries: entries);

StringWriter writer = new StringWriter();
SyndFeedOutput output = new SyndFeedOutput();
output.output(feed, writer);
writer.close();

return writer.toString();
}

The only thing different than what Glen, shows in his blog is the addition of the bold lines to add in the geoRss element. The result... geospatially enabled RSS feeds in the Grails framework (and anywhere else for Groovy and Java of course)

A few things to note:
a) While Google Earth and Google Maps should read this output I had some issues with the resulting files. Running things through the validator at: http://cite.opengeospatial.org/test_engine/georss_validator/ one gets some errors related to the namespaces. I noticed that the examples for RSS feeds at http://georss.org/ even had some issues. While the ATOM version validates ok the RSS based feed errors out in the same way that my generated feeds do. Likely this is an issue with ROME and if I track down the issue I will follow up on it. Until the feeds validate these feeds do not appear to work in Google Earth or Maps.
b) However, you can generate the rss version 2 feed with the SimpleModuleImpl() from the georss jar file and that will translate correctly with the rss to kml style sheet found at http://www.kovacevic.nl/hacks/kml/georss2kml.xsl (ref: http://www.kovacevic.nl/blog ).

RSS to KML via XSL in Grails:

So you can drop into your controller something like:
  def kml = {
def xslPath = getAppPathService.serviceMethod().toString() + "georss2kml.xsl"
def kmlxsl = new File(xslPath).getText().toString()
def rssxml = getFeed("rss_2.0")

def mywriter = new StringWriter()

def factory = TransformerFactory.newInstance()
def transformer = factory.newTransformer(new StreamSource(new StringReader(kmlxsl)))
transformer.transform(new StreamSource(new StringReader(rssxml)), new StreamResult(mywriter))

render(text: mywriter.toString(), contentType: "text/xml", encoding: "UTF-8")
}
you make also need the following in your code for XSL support:
//  for the XSLT transform
import javax.xml.transform.TransformerFactory
import javax.xml.transform.stream.StreamResult
import javax.xml.transform.stream.StreamSource

into your controller where AppPathService is (not mine.. but I don't have the reference for this.. bad me)

 import org.codehaus.groovy.grails.commons.*
import org.apache.commons.logging.*

import org.springframework.beans.BeansException
import org.springframework.context.ApplicationContext
import org.springframework.context.ApplicationContextAware

class GetAppPathService implements ApplicationContextAware {

GrailsApplication grailsApplication
ApplicationContext appCtx

boolean transactional = true

def void setApplicationContext(ApplicationContext arg0) throws BeansException {
appCtx = arg0;
}
def serviceMethod() {
String applicationPath = "${appCtx?.getServletContext()?.getRealPath("/")}"
return applicationPath
}
}
What the KML does is load in the XSL file (it's looking at the root of the response which will be your web-apps directory. You can move and alter this as you wish. Of course you could simple place the XSL in the controller too like at http://groovy.codehaus.org/Processing+XML+with+XSLT which is what kml method is based on. I like this this way a bit better. At this point you should be generating KML from this and Google Earth and Google Maps should load and monitor this URL just fine.

I do want the get the GeoRSS feed vaildating though and will work on that effort as noted.

enjoy
Doug

Monday, November 12, 2007

Checking REST and RSS style feeds

Continuing to do work with REST in Grails as well as RSS feeds. One issue is testing some of these URL's. In my last post I spoke about a groovy client using Apache's http client package which is quite nice for doing testing with. I also ran across this Firefox extension (RestTest) which is quite handy.

I also found this to a nice tool for testing RSS feeds with since it's not hard to get into a situation these days where your browser will either try and show you the RSS styled up or even redirect you to Google Reader or some other RSS reader. You can put the RSS/ATOM URL into this extensions interface and simply retrieve the XML of your feed for inspection during coding.

On the topic of RSS/ATOM and Grails check out the Feeds Plugin at the Grails site.