.. _rfc6:

===============================================================
  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.

2) 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


