As I said before, one of the advantages of using vectors is that they are very versatile, and one of the main reasons for this is the ability to apply complicated styling rules. There's an overview of the main logic for this on the OL documentation site, but here's some examples of how I use styling in OL.
Style hashes
If you look at the end of Feature/Vector.js, you will see OpenLayers.Feature.Vector.style, which defines OL's default styles. These values are used by the renderer (SVG being the standard) when drawing the vectors on the screen.
On the previous page, I passed a style hash as an option to the layer constructor:
style: {
strokeColor: "blue",
strokeWidth: 3,
cursor: "pointer"
}
This simply amends the values in the first of the default styles, 'default', to these new values, for example, changing the scarcely visible strokeWidth 1 to the more visible 3. These style hashes are also known as 'symbolizers', though IMO this is a misleading term, as there's not necessarily any symbol involved.
StyleMaps
You will notice there are 4 different key:value pairs in OpenLayers.Feature.Vector.style. These are the so-called render intents, which enables you to style selected or deleted features differently from the default. These different styles are stored in a StyleMap, which basically just matches the render intents to the appropriate style hashes.
Here's an example StyleMap. In this case, we're adding our style 'defStyle' to the default vector style; we then map that to the 'default' renderintent. We then say that a selected line, i.e. the 'select' renderintent, should have red colouring:
var defStyle = {strokeColor: "blue", strokeOpacity: "0.7", strokeWidth: 3, cursor: "pointer"};
var sty = OpenLayers.Util.applyDefaults(defStyle, OpenLayers.Feature.Vector.style["default"]);
var sm = new OpenLayers.StyleMap({
'default': sty,
'select': {strokeColor: "red", fillColor: "red"}
});
We then give this StyleMap as an option to the layer instead of a style:
styleMap: sm,
This is the same display as before, but when you select a line it should change to red.
Style objects, rules and filters
If you look in initialize() in StyleMap.js, you will see that when you pass a style hash as a parameter, as we do above, it converts it into an OpenLayers.Style. Amongst other things, this Style object (as opposed to a style hash - hope you're not too confused) enables you to associate different filter rules to a style; in other words, if a feature corresponds to certain conditions, it is displayed in a different way from the default.
As an example, let's change our lines, so they are displayed in a different colour if they have an attribute 'type'. To do this, we add the rules to the 'default' renderintent of the styleMap. There is a similar example in the OL examples: style-rules.html/js.
sm.styles['default'].addRules([
new OpenLayers.Rule({
filter: new OpenLayers.Filter.Comparison({
type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO, property: "type", value: null
}),
symbolizer: {strokeColor: "cyan"}
}),
new OpenLayers.Rule({
elseFilter: true
})
]);
This is rather long-winded, but this rule associates a comparison filter ('type' != null) with a symbolizer, in this case, change colour to cyan. The elseFilter says what to do if the other rules don't apply - in this case, no symbolizer is defined, so use the default. If there is no rule with "elseFilter: true", then any feature to which the rules do not apply will not be displayed.
Now you should see some cyan-coloured lines. Look at the attributes for those lines, and you will see they have a 'type' attribute whereas the others don't.
Now let's add some point features, which also have a 'type' attribute, which determines which image/icon is displayed. In addition, at lower zoom levels, the icons are displayed in a smaller size. First, we split the features into categories, and set up a separate layer for each. (On my live system, the categories are defined on the server based on entries in the database, but for this demo I simply list them in an array.)
var categories = ['Accommodation', 'Food/drink', 'Notable buildings', 'Transport'];
for (var i = 0; i < categories.length; i++) {
var cat = categories[i];
map.addLayer(new OpenLayers.Layer.Vector(cat, {
styleMap: sm,
visibility: false,
projection: wgs84,
strategies: [new OpenLayers.Strategy.Fixed()],
protocol: new OpenLayers.Protocol.HTTP({
url: cat+'.json',
format: new OpenLayers.Format.GeoJSON({
ignoreExtraDims: true
})
})
}));
}
As on the previous page, we give the layer option "visibility: false" so the layers are not visible when the map is loaded, but only when the user clicks on them in the layerswitcher.
Here too we define a styleMap, which looks like this:
var defStyle = {externalGraphic: "http://google-maps-icons.googlecode.com/files/${type}.png", graphicWidth: 32, graphicHeight: 37, graphicYOffset: -37, graphicOpacity: 1, cursor: "pointer"};
var sty = OpenLayers.Util.applyDefaults(defStyle, OpenLayers.Feature.Vector.style["default"]);
var sm = new OpenLayers.StyleMap({
'default': sty
});
sm.styles['default'].addRules([
new OpenLayers.Rule({
minScaleDenominator: 100000,
symbolizer: {graphicWidth: 24, graphicHeight: 27.75, graphicYOffset: -27.75}
}),
new OpenLayers.Rule({
elseFilter: true
})
]);
So here the default style is an externalGraphic. Often this is a simple url for a specific graphic. Here we use the $('...') syntax, which means use the property '...'. This property is in a context. By default, this is feature.attributes, but by passing context as an option, you can set it to anything you like. You can even define functions instead of properties; see below for an example of this. In the example above, we define 'type' and are not passing context, so are using the default feature.attributes, i.e. feature.attributes.type. For example, if this is set to 'airport', we fetch the file called 'airport.png'. We define the size of the graphic (they are all the same), and set the Y offset so the bottom of the graphic 'points' to the location.
We also have a rule on this styleMap: if the scale is above 100,000, reduce the size (and of course offset) of the graphic; thus we get smaller icons at the lower zoomlevels.
Recap
- a StyleMap is a hashtable of Style objects, keyed on render intent, of which there are by default 4
- a Style object associates a hash style ('symbolizer') with one or more Rules objects, which may in turn have one or more Filter objects
- a hash style defines the renderer/SVG properties, such as strokeColor or opacity
As a final point in this discussion, the alert may have noticed that Feature/Vector also has a (hash) style property, so each individual feature can be styled differently. If a feature style is defined, it overrides that in the layer.
View/download page with this logic
Changing routeline colour
On my live maps, I also have a button that enables people to change the colour of routelines, in case the default colour is not very visible against a particular baselayer. I shan't give a full example, but the changeColour() function looks like this:
var style = routeLayer.styleMap.styles["default"].defaultStyle;
for (j=0; j < document.paletteForm.colour.length; j++) {
if (document.paletteForm.colour[j].checked) {
style.strokeColor = document.paletteForm.colour[j].value;
}
}
routeLayer.redraw();
So, assuming the routelines are in a layer called 'routeLayer', the strokeColor of the default style in the styleMap is changed to that checked in paletteForm, and the layer redrawn.
Replacing markers with vectors
Of course, you can also use externalGraphics as a means of simulating markers as vectors. Simply define your style, as above, using the externalGraphic you want as the 'icon' (in this case, it's a blue triangle 'pointing' to the location). In this example I have an addMarker(long, lat) function, which 'marks' a particular long/lat value with one of these images at the appropriate point - though it first checks the coordinates entered are in the map's maxExtent. It also sets the mouseover title and sets the 'clickable' attribute to off so it cannot be selected by the selectFeature control we created on the previous page. Of course, you could also provide some descriptive text for a popup, or better from the latency point of view, as I explained before, a url which would then be fetched if the user clicked on the feature. You could also have the graphic/icon as a parameter to addMarker() and create markerStyle based on this.
var markerStyle = {externalGraphic: "bluearrow.png", graphicWidth: 16, graphicHeight: 16, graphicYOffset: -16, graphicOpacity: 0.7};
var mapBounds = map.getMaxExtent();
addMarker = function(long, lat) {
var point = new OpenLayers.Geometry.Point(long, lat);
if (map.projection != "EPSG:4326")
point.transform(wgs84, map.baseLayer.projection);
if (mapBounds.contains(point.x, point.y)) {
var imgTitle = 'Lat '+lat+', long '+long;
vectorLayer.addFeatures([new OpenLayers.Feature.Vector(point, {title: imgTitle, clickable: 'off'}, markerStyle)]);
}
else alert('Coordinates not on map!');
}
Using style context
I mentioned context above, and I use this for my map of medieval itinerary corridors. On this map, the colour of the route lines depends on whether they appear in accounts, inventories, or both. The width of the line depends on the number of itineraries a particular corridor appears in. My style here is:
{strokeColor: "${getColor}", strokeOpacity: "0.7", strokeWidth: "${getWidth}", cursor: "pointer"}
This refers to getWidth and getColor, which are not attributes. They are defined in a context object as follows:
var context = {
getColor: function(feature) {
if (feature.attributes.accounts != '' && feature.attributes.inventories != '')
return "red";
if (feature.attributes.accounts != '')
return "blue";
if (feature.attributes.inventories != '')
return "cyan";
return "yellow";
},
getWidth: function(feature) {
var weight = Math.floor(feature.attributes.total/2);
return weight += 2;
}
};
and this context object is passed as an option when instantiating the Style object. There's a similar example in styles-context.html in the OL examples.
Styled Layer Descriptor (SLD)
In Style.js you will see various references to SLDs; this is misleading but is because Style.js is based on the OGC's SLD, originally intended for use with styling features on WMS/WFS servers. An SLD is an xml file that stores style definitions so the same style can be used with different maps/programs. It defines one or more layers, and then one or more styles, together with rule filters associated with those styles. Because OL's SLD format uses Style.js for the style definition, it can be used for storing styles for use with any type of vectors on OL maps. sld.html in the OL examples illustrates using an SLD to style layers read in from a GML file.

