MS RFC 6: Color Range Mapping of Continuous Feature Values

Date:2005/09/27
Author:Bill Binko
Contact:bill at binko.net
Status:Proposed

Description: This proposal addresses the need to be able to easily map continuous feature values to a continuous range of colors. This RFC is the result of (and my interpretation of) the discussion that surrounded Bug #1305.

A preliminary patch has already been applied to MapServer 4.6+ (before the RFC process was in place), however, there is little consensus on the format being used and there is no support for proper display of legends for classes using ColorRanges.

Background

This work started as a patch that I created to be able to quickly visualize data with a large range of values. In particular, I was wishing to map property values, and various ratios that could take on a large range of values. To me, the natural way to do this was to set a max value, a min value and what colors those mapped to. The patch I wrote had MapServer do a linear interpolation of the value for each feature on to that color range.

The initial syntax for this feature simply added 5 new keywords to the STYLE block and looked as follows:

STYLE
  COLOR 60 60 60
  MINCOLOR 0 0 0
  MAXCOLOR 255 255 0
  MINVALUE 0.0
  MAXVALUE 300000.0
  GRADIENTITEM "sale_price"
END

After some discussion, the term Gradient was shown to be problematic. Also, the number of new keywords seemed high. After some discussion, the syntax was changed to this format:

STYLE
  COLORRANGE 0 0 0  255 255 0 # black to yellow
  DATARANGE 0.0 100.0
  RANGEITEM "foobar"
END

While this is still just a set of keywords under a Style, it seemed simpler and is now working in the MapServer 4.6 branch.

Current Syntax Problems

Several people pointed out that the current syntax could be improved by:

  1. Moving the new keywords into a block
  2. Adding a METHOD keyword with the type of interpolation used (‘linear’ being the first defined type, ‘logarithmic’ being a potential second type, etc.
  3. Adding an INTERVALS keyword that would limit the number of colors actually used by rounding values before interopolation.
  4. Moving all of the keywords out of the Style block.
  5. Allowing the ColorRange to be defined separately so that it can be reused

Proposed New Syntax

To meet the above needs, I propose the following new Block Syntax:

COLORRANGE
  RANGEITEM 'itemname' #required
  MINCOLOR 0 0 0 #optional - default = Black
  MAXCOLOR 255 255 0 #optional -default = White
  MINVALUE 0.0 #optional - default = 0
  MAXVALUE 100.0 #optional - default = 1
  INTERVALS 10 #optional - default = 0 (unlimited)
  METHOD LINEAR #optional - default = 'LINEAR'
END

I propose that this block lives at the CLASS level. My reasons for putting it at the CLASS, rather than the LAYER (or above) level are as follows:

  1. CLASS is the lowest level that can define a Legend entry (by using a named class)
  2. Allows multiple COLORANGES to be applied to a single layer (i.e. Red->Yellow and Yellow->Green to make a contiguous Reg->Yellow->Green).
  3. Allows “out of bounds” values to be highlighted separately (with a different CLASS).

(If we wish to provide this capability on the OUTLINECOLOR, then I would suggest we create a OUTLINECOLORRANGE block with identical format.)

Note: I am (and have always been) flexible on all of the keyword names and formats here. However, given the discussion that’s gone on around (and around) this, I thought I’d put a straw man up and start here.

Proposed Legend Format

I have posted a mockup of how I believe legends should look at

https://github.com/mapserver/mapserver/issues/324

The format only changes the height of the legend (which is already dynamic) so it should not have major layout implications. (Nit-picking about how tall the colorbar should be can be worked out during implementation.)

I have not started the legend support, and would like some help in this area. However, I do believe it is straightforward, and I will do it on my own if there are no volunteers.

MapScript Issues

As Sean Gillies mentioned in the discussion, this should be encapsulated in MapScript as a class. While I disagree with his putting it on the LAYER (see above), the rest of his suggestions all seem right in line.

I Propose a class named ColorRamp with the following read/write attributes:

Color minColor
Color maxColor
double minValue
double maxValue
String rangeItem
String method
int intervals

and two methods

Color findColor(double value)
double findValue(Color color)

ColorRamps can be obtained through an appropriate constructor or through a new (read/write) attribute on the ClassObj object:

ColorRange colorRange

Note: I will need help in adding these to MapScript as I do not have the SWIG experience to do it well.

Files affected

I will update this list as the RFC evolves, but right now, these are what I see:

  • mapfile.h => Change Keywords
  • maplexer.l => Change Keywords
  • mapfile.c => process new Keywords
  • mapscript/swiginc/*.i => various interface modifications to fit the above requirements
  • maplegend.c => Add new Legend support
  • mapdraw.c => update existing ColorRange code to use new keywords & add intervals and LOGARITHMIC method

Backwards compatibility issues

Right now, certain code _requires_ that there is a COLOR attribute set on any layer that is going to be displayed. Either this will need to be changed, or we will have to decide what that means if both a COLOR and a COLORRANGE are defined. One option is to use the COLOR for any values that are outside of the range.

Multiple Mapping Methods

The system will allow new color mapping methods to be added with as little effort as possible. If a new color mapping method uses only the keywords defined by this RFC, it should be a simple as:

  1. Implement a function with the signature
int mappingFn(colorRangeObj\* range, shapeObj\* shape, colorObj \*color)

This function should use the shape and range parameters to determine the
shapes color, and modify the color parameter accordingly.
  1. Choose a unique method name (‘linear’, ‘logarithmic’, ‘discrete’) modify the method msMapColorRamp() to call its method when its method name is found on a ColorRange definition.

Note

Whether the msMapColorRamp() method uses if/then logic or dispatches by function pointer can be determined later. For now, I believe the simplest approach would be to move all of the mapping logic + all current methods into a mapColorRange.c file.

Bug ID

Currently this is being tracked by https://github.com/mapserver/mapserver/issues/1305

Voting history

None