A standard way to style any OGC layer is using SLD. Styled Layer Descriptors.
The Styled Layer Descriptors are a simple (and standard) way to style your web maps. But when you style you also want to generate a nice legend for it.
I recently found myself with the situation of reworking a website styling code. Instead of writing it from scratch we used existing pieces to cleanly fill our webapp divs with the correct legend.
SLDs can be generated by hand (if you are brave enough), or using any of the software that supports it:
- OpenJUMP
- uDig
- AtlasStyler SLD editor
- Gaia
- QGIS
- …?
Thankfully QGis is one of them 😀 so we are good to go in this case. You can find about the Qgis Styler in Anita’s blog.
Objective
We have an SLD. And we simply want to fill a div with a plain legend representing that SLD.
About SLD
SLD is an OGC XML standard to define how a layer should be presented. From that description you can easily gather that it has two main uses: Style the map, and generate the legend.
To get you started in using SLD, there’s the beautiful OGC Standard, a dense PDF over 100 pages; the OGC schema definition, the XML schema for SLD; or the Geoserver SLD cookbook, which in my humble opinion is the best one.
The SLD is a simple structure. But in true XML tradition it gets big very fast :-D. I’d recommend simply creating the style in QGis and then exporting it.
<StyledLayerDescriptor xmlns="http://www.opengis.net/sld" xmlns:ogc="http://www.opengis.net/ogc" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0.0" xsi:schemaLocation="http://www.opengis.net/sld StyledLayerDescriptor.xsd"> <NamedLayer> <Name>Simple Point</Name> <UserStyle> <Title>SLD Cook Book: Simple Point</Title> <FeatureTypeStyle> <Rule> <PointSymbolizer> <Graphic> <Mark> <WellKnownName>circle</WellKnownName> <Fill> <CssParameter name="fill">#FF0000</CssParameter> </Fill> </Mark> <Size>6</Size> </Graphic> </PointSymbolizer> </Rule> </FeatureTypeStyle> </UserStyle> </NamedLayer> </StyledLayerDescriptor>
GeoEXT
To generate the legend we can go in the two common ways that one would go. Either generate your own Javascript code to parse the XML and generate icons. Or, use somebody else’s code that already does that.
Luckily for everybody this is covered with GeoEXT. Unluckily it forces you to use Ext.js which is not a bad library, just a big scope one.
Ext.js is a library to generate web applications. And it has its own concepts of views, stores etc. For the purpose of this example I’ll assume you know what that means. But if you don’t, don’t worry, it is easy enough.
First of all, add all the libraries and styling css.
<script type="text/javascript" src="http://extjs.cachefly.net/ext-3.4.0/adapter/ext/ext-base.js"></script> <script type="text/javascript" src="http://extjs.cachefly.net/ext-3.4.0/ext-all.js"></script> <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-3.4.0/resources/css/ext-all.css" /> <script src="http://openlayers.org/api/2.11/OpenLayers.js"></script> <link rel="stylesheet" type="text/css" href="./css/geoext-all.css" /> <script type="text/javascript" src="../js/GeoExt.js"></script>
Wow right? Let’s talk about what we are adding.
We want to use GeoExt.js, so we import it of course:
<link rel="stylesheet" type="text/css" href="./css/geoext-all.css" /> <script type="text/javascript" src="./js/GeoExt.js"></script>
But GeoEXT.js depends on Ext.js. We’ll bring that in too:
<script type="text/javascript" src="http://extjs.cachefly.net/ext-3.4.0/adapter/ext/ext-base.js"></script> <script type="text/javascript" src="http://extjs.cachefly.net/ext-3.4.0/ext-all.js"></script> <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-3.4.0/resources/css/ext-all.css" />
We will be working with SLD, so we need a library to parse that. GeoEXT is tightly coupled with OpenLayers. We bring OpenLayers in.
<script src="http://openlayers.org/api/2.11/OpenLayers.js"></script>
Now, we are ready to roll.
Generate a plain legend
Normally the SLD will come from outside your application, let’s say from a GeoServer.
To simplify, I assume that the following function exists:
/*
* Returns an SLD as an XML string
* @param sldName the name of the sld
*/
getSLD(sldname)
In the world of Ext.js. What we do to create something like a table is:
- Define a Store. (A store contains the data)
- Define the colums of the data.
- Create an Instance of a view with the store and the data.
Define the store
The store is made of a reader and the data to read from.
We’ll use the specific GeoEXT StyleReader, and the data parsed with the SLD format parser of OpenLayers.
Step by step:
Get the XML sld:
var xmlData = getSld("mysld");
Read that data with the OpenLayers parser. multipleSymbolizers
is set to force OpenLayers to use a Style2 class instead of Style. Style2 supports multiple symbols per rule.
Once the data is read, we have to point it to one of the possible userStyles.
var format = new OpenLayers.Format.SLD({multipleSymbolizers: true}); var data = format.read(xmlData); var olStyle = data.namedLayers["mysld"].userSyles[0];
Create the store:
var store = {reader: new GeoExt.data.StyleReader(), data: data}
And finally, all together:
var xmlData = getSld("mysld"); var format = new OpenLayers.Format.SLD({multipleSymbolizers: true}); var data = format.read(xmlData); var olStyle = data.namedLayers["mysld"]userStuñes[0]; var store = {reader: new GeoExt.data.StyleReader(), data: data}
Prepare the Columns
The store returns records with different “columns”. And each column is treated with one type.
We have to tell the view how to treat each column. For a legend we have two:
- The icon
- The name
The name is quite simple. It is a text entry. For the icon we’ll use GeoExt. This is as simple as setting the column to use the stored data type gx_symbolizercolumn
.
The columns are defined with an array of Objects. Each object setting the options for that column. There’s a whole lot of options that you can tweak, and the docs are pretty sweet. For this case, we’ll go simple. dataIndex
and xtype
:
var columns = [ {dataIndex: "symbolizers", width: 32, autoSizeColumn: false, xtype: "gx_symbolizercolumn"}, {id: "Label", header: "Label", dataIndex: "name", editor: {xtype: "textfield"}} ];
Create the view
Ah, the view. For this case we’ll directly a GridPanel. The amount of options could be giganormous. Let me just dump the code and explain afterwards a little why a chose some of the options.
Ext.grid.GridPanel({ id: "myStyleName", autoExpandColumn: "Label", bodyBorder: false, border: false, /* * Next options are important to avoid creating div * handlers in the document BODY. * Check Ext.js 3.4 Documentation */ draggable: false, collapsible: false, constrain: true, enableColumnHide: false, enableColumnMove: false, enableColumnResize: false, enableLocking: false, resizable: false, sortableColumns: false, disableSelection: true, trackMouseOver: false, cls: "legend-grid", //base CLASS hideHeaders: true, columns: columns.concat(), store: store, renderTo: "aDivID" });
id
This will be used to set the created divs ID.
renderTo
An existing DIV with this ID. Where Ext.js will render the grid.
autoExpandColumn
Which column to use to fill empty space.
hideHeaders
Do not show column headers.
cls
Extra class to set for styling.
You also see some options that hang under a big group. I set this options
to avoid creating extra handlers for things that we won’t use (editing, dragging…)
With that running code, we can get something like:
For those of you who are wondering how this is happening. I have a simple word Scalable Vector Graphics (well, that’s not a word at all :-D)
The naming of the rules
For a more generic approach. Right now we get the appended to the rule from the “Name” entry of the SLD.
In my case I found that different SLD were storing their names in different entries (which kind of sucks). But we can modify how a column gets the data appending a function.
var columns = [ {dataIndex: "symbolizers", width: 32, autoSizeColumn: false, xtype: "gx_symbolizercolumn"}, {id: "Label", header: "Label", dataIndex: "name", editor: {xtype: "textfield"}, renderer: getRuleNameRenderer } ];
That function will recieve the record, and has to return a String. My first and quick approach is to just check for existence of entries in the record, and return the first one that matches.
/** * getRuleNameRenderer. Renderer for Ext.js. Will return the existing name * for the SLD dataset. Either name, title, Name, Title * (For the parameters check the EXT.js documentation) */ function getRuleNameRenderer(value, meta, record, rowIndex, colIndex, store) { if (record.get("name") != "" && record.get("name") != undefined) return record.get("name"); if (record.get("title") != "" && record.get("title") != undefined) return record.get("title"); if (record.get("Title") != "" && record.get("Title") != undefined) return record.get("Title"); if (record.get("Name") != "" && record.get("Name") != undefined) return record.get("Name"); if (record.get("Label") != "" && record.get("Label") != undefined) return record.get("Label"); if (record.get("label") != "" && record.get("label") != undefined) return record.get("label"); }
Conclusion
That was easier than what I anticipated.
At the end, we can create a simple function fillWithSLD(style, div)
to be used in any case that we want to generate a legend. Don’t forget that this is SVG and has to be generated, it would be nice if this function checks if that specific legend was already generated.
Reblogged this on SutoCom Solutions.