dinsdag 6 juli 2010

Adding Grid Strategy to the mix

Last week I started on adding the Grid Strategy developed by Eric Lemoine in an OpenLayers sandbox to the mix of the GEOZET application. Eric developed the Strategy 2 years ago, when there was no Cluster Strategy as yet, so my challenge was to get the Grid Strategy to work together nicely with the Cluster Strategy. Getting it to work with the Protocol.WFS was a very simple change, however getting it to work together with the Cluster Strategy was a bigger challenge.

The reason we want to use the Grid Strategy is that for performance reasons later on we might want to add caching (e.g. through Squid), and I found it too risky to incorporate the grid strategy at the end of the project.

I started off adding an HTTP GET interface for Protocol.WFS, since POST isn't cacheable through Squid. It can be found here:

http://trac.openlayers.org/ticket/2718

Then I started to use Eric's code.

http://trac.openlayers.org/browser/sandbox/vector-behavior/lib/OpenLayers/Grid.js
http://trac.openlayers.org/browser/sandbox/vector-behavior/lib/OpenLayers/Strategy/Grid.js

The biggest issue I encountered was that the grid strategy just adds the features to the layer as they come in. This did not work well for the cluster strategy, so I needed to find out when the grid strategy was done, and then only add the features to the layer for them to play well together. I ended up making a few changes to accomplish this:

For the Grid class:
  • add a counter property and set it to 0 in the initGrid function
  • set the counter to 0 in the moveGrid function
  • increase the counter in a few places in the code, i.e. just before the createTile call in initGrid, in shiftRow just before the moveTo call, in shiftColumn just before the moveTo call.
For the Stratregy.Grid class:
  • add a count property and initialize it to 0
  • change the redraw variable's default to false if not provided in moveTo
  • add featureMap function here, Eric added this to Layer.Vector instead but I was lazy to adapt yet another file
  • in the draw function, increase this.strategy.count, and check if this.strategy.count equals this.strategy.grid.counter, if so we are done and we add the features to the layer.
  • where the BBOX filter is created, we give this.bounds for the value, and not the Geometry Rectangle, so it can play well with the Protocol.WFS
  • in the refresh function, set force to false so the grid can decide based on the bounds
That is how I basically got things to work together. However, it might be a bit too specific for general use.

woensdag 23 juni 2010

Anchor Renderer

So as I wrote in an earlier post, for the GEOZET viewer the features in the map need to be accessible by keyboard, that's why we ended up using anchors. The anchors are styled using a css class coming from a feature attribute.

So the HTML I had to create using the renderer is for the normal features an anchor with a css class to map to the background-image, the anchor is absolutely positioned. The innerHTML of the anchor has the internal id used also in the print list, it won't be visible in the map only when printing. The identifiers (id attributes) of the anchors are the feature ids generated by OpenLayers. Since background-image print can be troublesome, we decided to print white blocks with the number in it, corresponding to a number in a list underneath the map. Using only border color to differentiate the feature classes.

For clusters we needed a different HTML structure, since the number of features present in the cluster needed to be displayed. It is an anchor with a span, the span contains the internal ID. The anchor also has a child of type "strong" with the number of features in the cluster in it. This is used as the label.

The first issue I ran into was that all renderers coupled to an OpenLayers.Layer.Vector needed to be in the OpenLayers namespace, which is the subject of:

http://trac.openlayers.org/ticket/2669

I had some trouble deciding whether or not my custom renderer needed to extend OpenLayers.Renderer.Elements or OpenLayers.Renderer. In the end I decided on the latter, mostly since the only overlap was the getFeatureIdFromEvent function, and also since I needed to put an _style object on all the nodes to overcome destroy issues in IE.

The drawText function is used to add labels for the cluster features, and it looks something like:


Since I incrementally implemented my Renderer, I had a few situations in which strange things were happening, for instance using the cluster strategy, I was getting more and more features on my map, until I realized that I had not yet implemented eraseGeometry in my Renderer. One of the problems I had when implementing the renderer is the fact that eraseGeometry does not get the featureId, and I needed to get easy access to the DOM structure associated with the feature, using the featureId and not the geometry id. This is subject of:

http://trac.openlayers.org/ticket/2693

Chris Schmidt already answered on the e-mail list that he has no objection to this change. This would also be a benefit for the existing Canvas Renderer of OpenLayers, since it does not need to keep a node hashmap mapping featureIds to geometryIds anymore.

My implementation of eraseGeometry ended up to be very simple:


I did not need to implement removeText, since in my Renderer the labels are part of the DOM structure of the feature, so it will automatically be cleared when the geometry is erased.

The drawGeometry function ended up to be the most difficult. It took some time for me to realize that if the style object passed has a display property of "none", that the Renderer should not draw the feature, or even remove it from the DOM if it exists. The current code is something like (this still needs to be cleaned up so bear with me):


Goes to say lastly that this Renderer only supports point geometries. This could be a start to replace the Marker layer maybe in the future, I am not sure.

The vector layer definition is now:


Here is a screenshot of the print function (the administrative list with the numbers corresponding to the features in the map follows on page 2, not included in this screendump), we still need to find a way to get a good internal number for the features, maybe by having the Web Feature Service sort them by the distance from the center:


vrijdag 18 juni 2010

Custom Renderer in OpenLayers

For a new project for the Dutch government (the project is run by Geonovum) we are creating a viewer based on OpenLayers. The project is called GEOZET and it is an abbreviation in Dutch. In English it means something like "geographical view and search engine". We in the above sentence is a team of developers in Java, Javascript and HTML/CSS. I am focussing on the javascript part together with someone else.

The requirements are quite tough, and they deal a lot with accessibility.

So using the keyboard people must be able to step through the features in the map, which kind of rules out the standard renderers available in OpenLayers. That's why I ended up creating my own renderer which creates anchors, with a lot of help from Maarten van Oudenniel (the HTML/CSS guru in the project team) who came up with the actual HTML structure which I have to render and the corresponding CSS. The structure differs for normal features and for clustered features since for clusters there is a label with the amout of subfeatures in the cluster.

Maarten also came up with a good way to print such a map using normal browser print, the features in the map will become white squares with a number, and below the map a list will be shown with the actual information from the features. Something I had never thought about doing. Downside is that next to the feature structure in the map, we also have to keep an unordered list synchronized which will only be shown when printing. But this is because no separate print button is allowed in the application.

In a future post I'll tell you more about the internals of the renderer.