The code always knows
Posted by jive on February 20, 2007 at 11:45 PM EST
The problem? We have some plugin implementations that do not follow a coding contact. Normally this is not such a big deal (you throw those plugins out of the build until they shape up). Problem is this time it is *every* plugin.
Unimplemented functionality - how did this Happen?
When this happens it usually means that we (the GeoTools community) have let a user request change our api (probably because some functionality is commonly requested), but we did not take the trouble to get buy in (and time) from the plugin implementors.Without client code around at the same time as implementations are being built this kind of thing falls through the cracks (release early release often is the mantra, feedback early when developers care is the reality).
The DataStore API has suffered this same fate before; currently uDig only supports three DataStores. As a client application needs events to be fired when editing; but since uDig arrived on the scene a bit late later then the implementations it was not around to ensure this functionality worked as advertised.
Unimplemented functionlaity - why Fix it?
A really easy alternative is to just remove the functionality (it is not being implemented that should tell us something is in a WONT FIX state).Here is why I am not going to do that ... hacking around this problem is producing the same boiler plate code in a number of spots:
- In our Renderer .. I saw this back in November when we hooked up Expression (a query langague) so that POJOs (ie normal objects) could be drawn onto a map
- In other applications .. GeoServer noticed this problem and talked about it in last weeks IRC meeting
- In other applications .. uDig developers noticed and hacked around the problem so quickly they forgot to report it
Broken - using Query to Reproject
This is what a simple data access query looks like:
FeatureSource source = dataStore.getFeatureSource( "road" ); |
And here is one that uses a Query:
FeatureSource source = dataStore.getFeatureSource( "road" ); |
And finally here is the problem - using Query to ask for something in another projection:
CoordinateReferenceSystem world = CRS.decode("EPSG:4326"); // world lon/lat
|
You can see why this is a popular request - it actuall does something a bit more then just data access.
- setCoordinateSystem() - changes the "Metadata", the information will be returned as is (same values) but the meaning will be changed - the FeatureType will report back that the information is in the provided CRS
- setCoordianteSystemReproject() - changes the actual "Data", in addition to changing the FeatureType the actual data values will be reprojected and the result returned
Solution? The code always knows...
The needed code is around, it has just not been hooked up behind that getFeatures( query ) method.Here is the first HACK needed to set the FeatureType up correctly (from DefaultView.java)::
FeatureType origionalType = source.getSchema();
|
Here is the second HACK, the utility classes to do the hard work are right there, they just have not been connected.
if (forcedCS != null)
|
So the solution is to "wrap" the origional feature collection in a helper class that does the work. You can see these same utility classes used in the Renderer, and in GeoTools and in ... your application. (that is the nature of a workaround).
First of All the Test Case
In order to debug a problem you need to be able to reproduce it. Here in java land that tends to mean a test case, for bonus points it means the failure will have a hard time coming back from the dead.I am going to pick on a *really simple* data store that uses Java property files, simply because I am the implementor responsible for it and I feel guilty :-)
Here is the test case:
| public void
testQueryReproject() throws Exception { CoordinateReferenceSystem world = CRS.decode("EPSG:4326"); // world lon/lat CoordinateReferenceSystem local = CRS.decode("EPSG:3005"); // british columbia FeatureSource road = store.getFeatureSource( "road" ); FeatureType origionalType = road.getSchema(); DefaultQuery query = new DefaultQuery( "road", Filter.INCLUDE, new String[]{ "name" } ); query.setCoordinateSystem( local ); // FROM query.setCoordinateSystemReproject( world ); // TO FeatureCollection features = road.getFeatures( query ); FeatureType resultType = features.getFeatureType(); assertNotNull( resultType ); assertNotSame( resultType, origionalType ); GeometryAttributeType resultGeometryType = resultType.getDefaultGeometry(); assertEquals( world, resultGeometryType.getCoordinateSystem() ); } |
Using a Debugger with Binary Search
This technique works like peanut butter and chocolate.The first trick is to set a debugger break point somewhere in your code at the "half way point" and make sure you see what you expected to see.
After a bit of a wild ride (through some AbstractDataStore code) turns out that that the DefaultFeatureResults class is going to be doing most of the work.
DefaultFeatureResults Constructor
So let's make sure that we got the Query where we need it - Here is what the code looks like:
public DefaultFeatureResults(FeatureSource source, Query query) {
|
This looks "odd" if they query type name (say "roads" equals the feature souce typeName then we use the query as is. Okay fine). The odd part is the "else" statement it should really throw an error (we are being asked to look for some content, say "rivers", that we do not have!). The code goes on to copy the CRS information we are interseted in, but does not pay attention to things like requested attributes ... oh wait it does (they Query was passed in as a constructor argument my bad).
If we want to know who did this work we can use "svn blame":
cholmesny public DefaultFeatureResults(FeatureSource source, Query query) {
|
Looks like our friend Andrea was working on this .. lets check to see if it was needed
public DefaultQuery(Query query) {
|
Looks like the query made it this far okay.
DefaultFeatureResults getSchema()
The first hack is about making sure the correct FeatureType is produced ... lets set a break point and see if DefaultFeatureResults.getSchema() is up to the task.Here is what the code looks like - does not look like CRS is mentioned at all!
public FeatureType getSchema() {
|
Well we can cut and paste the working code in from DefaultQuery (remember the Hack example?). However since this work is going to be *the same* every time we run it - we may as well place the work into the consructor and have it called once.
Munching up the various examples we end up with:
public DefaultFeatureResults(FeatureSource source, Query query) { |
Not something to be proud of:
- That SchemaException is being gobbled up; the origional code just "faked it" by using the origionalSchema; this would result in data being returned, with no indication that it was not as asked for!
Related Topics >>
Blog Links >>
- Login or register to post comments
- Printer-friendly version
- jive's blog
- 783 reads





