While working on the WPF Controls, I repeatedly hit a cognitive wall when trying to understand the design and relationship of renderers,
layers, styles and themes. So, after taking the time to solve the puzzle myself – I’m here to ask for your help… I was thinking of it quite bit, so sorry for the lengthy post.
Here is my understanding of the roles/ responsibilities of the various classes, and afterward some questions:
Renderers: Relatively low level mechanism. According to my understanding, they are not intended to be and an extensibility point except for platform support. i.e. – once a GDI/WPF/whatever renderer exists, it’ll rarely extended or even directly used by the
API end user.
Several other aspects of renderers:
In general, there should exist one renderer per layer type (the registration of a renderer with layer name is not publicly exposed by SharpMap).
Being low level mechanism, the renderer should not be aware of presentation details such as currently visible WorldWidth.
Now, taking the above, I’m having trouble understanding why the ITheme it a member of FeatureRenderer, (and not ILayer, for example). First, being a member of FeatureRenderer means, that there is one theme for all layers (of the same type), while it seems reasonable
to have a theme per layer. Second, there is an issue of using Style.MinVisible / MaxVisible. These properties can have an effect on the display only if WorldWidth is known in the given context. The renderer should not deal with WorldWidth, so the decision
whether to display a feature or not can be done in one of the following:
- The theme itself – This will make coupling between the Theme and the View, instead of leaving the theme to be just a function of FeatureDataRow
- In the presenter (which is reasonable) – but, the MapPresenter2D is not theme aware, and the decision whether to use default style or theme
is already implemented in the BasicGeometryRenderer, so overriding RenderFeatureLayer to make theme based rendering decisions doesn’t seem appropriate
At this point a get confused and helpless …
There’s another issue, rendered objects caching. Seems to me reasonable to have some caching logic in the renderer, but – the layer to which the objects belong seems to be crucial information for the caching policy, and yet – the renderer has no idea which
layer it is rendering… (this is actually not such a big problem, since there can be some kind of caching context which encapsulates the relevant info.
So, please if can clarify the intent behind the design, and the correct use of it, my nights will be much more peaceful :-)
May 6, 2008 at 11:13 PM
Hi blackrussian -
Thanks for the ongoing work on the WPF renderer.
Let's see if I can help shed some light.
Renderers: Yes, these are graphics technology dependant classes whose responsibility is to take a geometry path and turn it into a graphical element. You are correct - once a renderer exists for a particular technology, it is consumed and hidden by upper
layers (such as a presenter), and isn't really an extensibility point for SharpMap users.
Theme support is not really baked in v2.0 - and in fact is not really carefully thought out. I, for one, don't really need it at the moment, so I've done what any responsible programmer should do - deferred it.
There are some problems in the factoring, as you are uncovering. You're right that the idea is that renders should be fed paths and style and generate (or update) the relevant graphical elements. The idea is that IStyle and ITheme are interfaces which a renderer
should be able to consume and generate the correct graphical primitives to render the map visually.
There is a problem with this, however - we don't really know what the renderer's coordinate system is. The world geometry has a coordinate system (even if it isn't explicit). The view has a coordinate system. Which one does the renderer use? Obviously, it can't
use the view's, since it's whole purpose is to transform world coordinates in an ad-hoc manner. Therefore, I picked the other naive alternative: world coordinates. However, in practice, this doesn't work out very well (I had a bad feeling about it, but I was
in a rush). Precision issues and messy geometry paths show up quickly. My current thinking is that the renderer needs a canonical coordinate system which it would still have to generate via a transform of world coordinates. The view would then transform renderer
coordinates. This means the renderer would still need to know the world width in order to generate the linear transform. This is typically not a problem since it is very easy and inexpensive (relative to most other operations) to find out.
Looking back, it might make more sense that IStyle or ITheme be a property of the GraphicsPath class (and after thinking more, GraphicsPath be renamed to GeometryPath). This way the IStyle property doesn't need to be set every call and the renderer can be more
stateless. A renderer would then have a much better opportunity to cache or databind on path data, since it could lookup if the path was already generated and cached, and if a style update is all that is needed.
I agree with you about the rendering context. I had played with that concept for a while and abandoned it since I couldn't spend much time on it. It was leading to creating a spatially indexed visual tree for the GDI renderer (which is probably ultimately the
right thing to do, to simplify development for all other graphical systems), and I just couldn't commit to that.
Let me know if this is helpful. I didn't go back and dig around the code and the comments to fully refresh my understanding on all the shortcuts, but it might get you started. Don't hesitate to call out for me to be more clear - I'll try to keep you and your
efforts near the top of my priority queue. (But don't ask John Diss how much attention I will actually give your code until I'm done with NTS. ;)
Many thanks for your detailed reply.
Your post certainly helps grokking most of the issues I’ve pointed out.
The most important guidance I needed was a confirmation of my intuitive “classification” of ShapMaps abstractions into one of the following (sure, life is a bit more complex than this simple dichotomy, but it’s still useful):
- The abstraction is a product of deliberate design decision.
- The abstraction is a “functional placeholder”, implemented as a result of reality constraints (e.g. the total night-time programming resources a day-time developer can spend on his pet open source project…).
The implications are obvious – in first case, I would try hard not to reinvent the wheel and respect SharpMap’s conceptual model. In the second, I will allow myself a bit more creativity, and perhaps re-implement certain aspects of the code, so they'll fit
my design approach better.
Again, your answer makes many of such decisions easier.
On the practical path, I’ll feel more comfortable to tweak the Thematics architecture, while trying not to mess up the renderers interfaces. Luckily, WPFs mechanisms such as Attached Properties help tremendously to achieve this.
I will sure have more questions as coding goes on, so we will certainly be in touch.