NCGMP to GeoSciML: MappedFeature

I posted an example of the <typeMappings> section of a GeoServer/app-schema mapping file here: A Sample GeoServer Mapping file for NCGMP09 to GeoSciML. Checkout this other post to see where your mapping file should be, and how your datastore will refer to it: GeoServer Complex-Feature WFS Configuration: Directory Structure.

The <typeMappings> section is where you indicate what fields in your source data (NCGMP09) go into which fields in your output data (GeoSciML). Here I want to describe the different levels of complexity you can undergo when making a mapping file.

The Simplest Case: From one Field to Another

In this case, you simply want to put the content of one field in your source data directly into another field on the output data. <targetAttribute> specifies the name of the field in the output data, while a CQL (Common-Query Language) query specifies which field to grab data from in the source dataset. Here, the contents of the "notes" field in the NCGMP dataset are passed on to the gml:description field in GeoSciML.

This creates an output that looks like this:

Still Pretty Simple: Adding an Attribute to an Output Element

Here, we want to add an attribute to an element in our output data. To do this, we add a ClientProperty element to the mapping, which specifies the attributes name and value. In this case, I'm putting arbitrary text in for the value of the attribute, so the text goes in single quotes. If I wanted the value to be read from a field in the source data, I would just put the name of that field in the value element, with no quotes around it. Also note that the targetAttribute is gml:name[1], the 1 in the square brackets indicating this should be the first gml:name field in the output data.

Here's an example of the output:

Still Simple Enough: ID Values

Here I want to give a unique identifer for each of my MappedFeatures. The targetAttribute is the MappedFeature element itself, and instead of a sourceExpression we have an idExpression wrapped in OCQL tags. In this case I'm using a field from my source data to pass the identifier into the output data, but you could also put "getID()" there to have GeoServer calculate an ID for you.

And the output:

Slightly More Complex: Nested Elements in the Output Data

In this situation, I want to populate an element that will be nested a little deeper in the output data schema. For example, in GeoSciML, a MappedFeature has an ObservationMethod. This ObservationMethod must be of the type CGI_TermValue. A CGI_TermValue has a value, and that's where I end up saying what the ObservationMethod actually is. In my example here, I'm specifying literal values for both the sourceExpression and the ClientProperty/value. That means that every MappedFeature will have these values I specified. Of course, using the methods outlined above, you could read from a source data field for either of these things.

In my output document, all MappedFeatures will have an element that looks like this:

More Complicated: Joins or "Feature-Chaining"

In some cases, we'll be joining one featuretype to another. In the case of gsml:MappedFeature, this happens in the gsml:specification element, which is populated with gsml:GeologicUnits (for polygons), which themselves are complicated assortments of data from a variety of tables in the NCGMP09 source data. Instead of going through some awful nested mappings like I described above, we'll define a gsml:GeologicUnit mapping elsewhere, and just join to it here in this mapping file. gsml:GeologicUnit becomes another FeatureType, and that's why GeoServer has adopted the "Feature-Chaining" terminology -- we're chaining features together.

In this situation you can see that the targetAttribute is still pretty simple, just tells us what output field to put data into. The sourceExpression is different though. First, the <OCQL> element specifies the field in the source data that points at the target feature. In this case, the mapunit field tells us which GeologicUnit this polygon represents. Note this is a CQL expression, and you could put a literal here in single quotes if you wanted all your MappedFeatures to point to the same GeologicUnit. In database terminology, we're specifying the foreign key. The <linkElement> element tells us the name of the feature that we're joining or chaining too. This is the joined table's name in database terms. Finally the <linkField> element tells us what field in the target feature will match what we put in the <OCQL> tag. This is the primary key of the joined table in database-speak.

Depending on the completeness of your GeologicUnit mapping (mine is not yet finished...), your result will look something like:

Make sure to read completely the Feature-Chaining documentation on the GeoServer site. It has lots of information packed in there, and is really helpful as an example of how to do this.

Special Case: CGI_Value Bug

Looking through the mapping you may notice this weird looking one:

Why are there two AttributeMappings? What is this targetAttributeNode?

This is a workaround for a bug that I found in the app-schema extension. Here's a brief description of the nitty-gritty of the problem: The gsml:positionalAccuracy element must be a CGI_Value, which is an abstract class which can be, among other things, a CGI_PrimitiveValue. A CGI_PrimitiveValue can be either a CGI_TermValue or a CGI_NumericValue. There seems to be an issue where the app-schema extension has trouble navigating the substitution group relationships between some of these abstract classes.

So to get around this, we use the targetAttributeNode to force a new type on the node. So here you can see that I forced the node to accept my CGI_TermValue.


for more information on configuring polymorphic elements (substitution groups) see especially: