From d05b0c539f8cc546e8afcd482d2f1ede236f3cb5 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 4 May 2011 00:20:17 +0000 Subject: [PATCH] apply major refactor of RasterColorizer by ben moores from https://github.com/BenMoores/mapnik-trunk/wiki/RasterColorizer - closes #523 --- bindings/python/mapnik_raster_colorizer.cpp | 226 +++++++------ include/mapnik/raster_colorizer.hpp | 336 +++++++++---------- plugins/input/gdal/gdal_featureset.cpp | 5 + src/SConscript | 1 + src/agg/process_raster_symbolizer.cpp | 2 +- src/cairo_renderer.cpp | 2 +- src/load_map.cpp | 95 ++++-- src/raster_colorizer.cpp | 256 ++++++++++++++ src/save_map.cpp | 47 ++- tests/data/good_maps/glyph_symbolizer.xml | 10 +- tests/data/good_maps/raster_colorizer.xml | 74 ++++ tests/data/good_maps/raster_symbolizer.xml | 14 +- tests/python_tests/raster_colorizer_test.py | 129 ++++--- tests/python_tests/raster_symbolizer_test.py | 7 +- 14 files changed, 821 insertions(+), 383 deletions(-) create mode 100644 src/raster_colorizer.cpp create mode 100644 tests/data/good_maps/raster_colorizer.xml diff --git a/bindings/python/mapnik_raster_colorizer.cpp b/bindings/python/mapnik_raster_colorizer.cpp index 8f4a9e72b..d9e6dc47d 100644 --- a/bindings/python/mapnik_raster_colorizer.cpp +++ b/bindings/python/mapnik_raster_colorizer.cpp @@ -27,38 +27,40 @@ using mapnik::raster_colorizer; using mapnik::raster_colorizer_ptr; -using mapnik::color_band; -using mapnik::color_bands; +using mapnik::colorizer_stop; +using mapnik::colorizer_stops; +using mapnik::colorizer_mode_enum; using mapnik::color; +using mapnik::COLORIZER_INHERIT; +using mapnik::COLORIZER_LINEAR; +using mapnik::COLORIZER_DISCRETE; +using mapnik::COLORIZER_EXACT; + namespace { -void append_band1(raster_colorizer_ptr & rc, color_band b) +void add_stop(raster_colorizer_ptr & rc, colorizer_stop & stop) { - rc->append_band(b); + rc->add_stop(stop); } -void append_band2(raster_colorizer_ptr & rc, color_band b, unsigned m) -{ - rc->append_band(b, m); +void add_stop2(raster_colorizer_ptr & rc, float v) { + colorizer_stop stop(v, rc->get_default_mode(), rc->get_default_color()); + rc->add_stop(stop); } -void append_band3(raster_colorizer_ptr & rc, float v, color c) -{ - rc->append_band(v, c); +void add_stop3(raster_colorizer_ptr &rc, float v, color c) { + colorizer_stop stop(v, rc->get_default_mode(), c); + rc->add_stop(stop); } -void append_band4(raster_colorizer_ptr & rc, float v, color c, unsigned m) -{ - rc->append_band(v, c, m); +void add_stop4(raster_colorizer_ptr &rc, float v, colorizer_mode_enum m) { + colorizer_stop stop(v, m, rc->get_default_color()); + rc->add_stop(stop); } -void append_band5(raster_colorizer_ptr & rc, float v, float vm, color c, unsigned m) -{ - rc->append_band(v, vm, c, m); +void add_stop5(raster_colorizer_ptr &rc, float v, colorizer_mode_enum m, color c) { + colorizer_stop stop(v, m, c); + rc->add_stop(stop); } -void append_band6(raster_colorizer_ptr & rc, float v, float vm, color c) +colorizer_stops const& get_stops(raster_colorizer_ptr & rc) { - rc->append_band(v, vm, c); -} -color_bands const& get_color_bands(raster_colorizer_ptr & rc) -{ - return rc->get_color_bands(); + return rc->get_stops(); } } @@ -66,121 +68,135 @@ void export_raster_colorizer() { using namespace boost::python; - class_("RasterColorizer", init<>("Default ctor.")) - - .add_property("bands",make_function - (get_color_bands, - return_value_policy())) - .def("append_band", append_band1, - (arg("color_band")), - "Append a color band to the raster colorizer.\n" - "\n" - "Usage:\n" - ">>> colorizer = mapnik.ColorBand()\n" - ">>> color = mapnik.Color(\"#0044cc\")\n" - ">>> color_band = mapnik.ColorBand(3, color)\n" - ">>> colorizer.append_band(color_band)\n" + class_("RasterColorizer", + "A Raster Colorizer object.", + init(args("default_mode","default_color")) ) - .def("append_band", append_band2, - (arg("color_band"), arg("midpoints")), - "Append a color band to the raster colorizer with midpoints " - "lineally interpolated color bands between this one and the " - "previous one.\n" - "\n" - "Usage:\n" - ">>> colorizer = mapnik.ColorBand()\n" - ">>> color = mapnik.Color(\"#0044cc\")\n" - ">>> color_band = mapnik.ColorBand(3, color)\n" - ">>> colorizer.append_band(color_band, 1)\n" - ) - .def("append_band", append_band3, - (arg("value"), arg("color")), - "Create and append a color band to color the range " - "[value, next_val) where next_val is the next band's color or " - "inifinity if there is no next band.\n" - "Usage:\n" - ">>> colorizer = mapnik.RasterColorizer()\n" - ">>> color = mapnik.Color(\"#0044cc\")\n" - ">>> colorizer.append_band(30, color)\n" - ) - .def("append_band", append_band4, - (arg("value"), arg("color"), arg("midpoints")), - "Create and append a color band to the raster colorizer with " - "midpoints lineally interpolated color bands between this one and " - "the previous one.\n" - "color will be applied to all values in the " - "range [value, next_val) where next_val is the next band's color " - "or infinity if there is no next band\n" + .def(init<>()) + .add_property("default_color", + make_function(&raster_colorizer::get_default_color, return_value_policy()), + &raster_colorizer::set_default_color, + "The default color for stops added without a color (mapnik.Color).\n") + .add_property("default_mode", + &raster_colorizer::get_default_mode_enum, + &raster_colorizer::set_default_mode_enum, + "The default mode (mapnik.ColorizerMode).\n" + "\n" + "If a stop is added without a mode, then it will inherit this default mode\n") + .add_property("stops", + make_function(get_stops,return_value_policy()), + "The list of stops this RasterColorizer contains\n") + .add_property("epsilon", + &raster_colorizer::get_epsilon, + &raster_colorizer::set_epsilon, + "Comparison epsilon value for exact mode\n" + "\n" + "When comparing values in exact mode, values need only be within epsilon to match.\n") + + + .def("add_stop", add_stop, + (arg("ColorizerStop")), + "Add a colorizer stop to the raster colorizer.\n" "\n" "Usage:\n" ">>> colorizer = mapnik.RasterColorizer()\n" ">>> color = mapnik.Color(\"#0044cc\")\n" - ">>> colorizer.append_band(30, color, 4)\n" + ">>> stop = mapnik.ColorizerStop(3, mapnik.COLORIZER_INHERIT, color)\n" + ">>> colorizer.add_stop(stop)\n" ) - .def("append_band", append_band5, - (arg("value"), arg("value_max"), arg("color"), arg("midpoints")), - "Create and append a color band to the raster colorizer with " - "midpoints lineally interpolated color bands between this one and " - "the previous one.\n" - "color will be applied to all values in the " - "range [value, next_val) where next_val is the next band's color " - "or value_max if there is no next band\n" - "\n" + .def("add_stop", add_stop2, + (arg("value")), + "Add a colorizer stop to the raster colorizer, using the default mode and color.\n" "\n" "Usage:\n" - ">>> colorizer = mapnik.RasterColorizer()\n" - ">>> color = mapnik.Color(\"#0044cc\")\n" - ">>> colorizer.append_band(30, 40, color, 4)\n" + ">>> default_color = mapnik.Color(\"#0044cc\")\n" + ">>> colorizer = mapnik.RasterColorizer(mapnik2.COLORIZER_LINEAR, default_color)\n" + ">>> colorizer.add_stop(100)\n" ) - .def("append_band", append_band6, - (arg("value"), arg("value_max"), arg("color")), - "Create and append a color band to color the range " - "[value, next_val) where next_val is the next band's color or " - "value_max if there is no next band.\n" + .def("add_stop", add_stop3, + (arg("value")), + "Add a colorizer stop to the raster colorizer, using the default mode.\n" + "\n" "Usage:\n" - ">>> colorizer = mapnik.RasterColorizer()\n" - ">>> color = mapnik.Color(\"#0044cc\")\n" - ">>> colorizer.append_band(30, 40, color)\n" + ">>> default_color = mapnik.Color(\"#0044cc\")\n" + ">>> colorizer = mapnik.RasterColorizer(mapnik2.COLORIZER_LINEAR, default_color)\n" + ">>> colorizer.add_stop(100, mapnik.Color(\"#123456\"))\n" + ) + .def("add_stop", add_stop4, + (arg("value")), + "Add a colorizer stop to the raster colorizer, using the default color.\n" + "\n" + "Usage:\n" + ">>> default_color = mapnik.Color(\"#0044cc\")\n" + ">>> colorizer = mapnik.RasterColorizer(mapnik2.COLORIZER_LINEAR, default_color)\n" + ">>> colorizer.add_stop(100, mapnik2.COLORIZER_EXACT)\n" + ) + .def("add_stop", add_stop5, + (arg("value")), + "Add a colorizer stop to the raster colorizer.\n" + "\n" + "Usage:\n" + ">>> default_color = mapnik.Color(\"#0044cc\")\n" + ">>> colorizer = mapnik.RasterColorizer(mapnik2.COLORIZER_LINEAR, default_color)\n" + ">>> colorizer.add_stop(100, mapnik.COLORIZER_DISCRETE, mapnik.Color(\"#112233\"))\n" ) .def("get_color", &raster_colorizer::get_color, "Get the color assigned to a certain value in raster data.\n" - "By default, returns Color(\"transparent\")\n" "\n" "Usage:\n" ">>> colorizer = mapnik.RasterColorizer()\n" ">>> color = mapnik.Color(\"#0044cc\")\n" - ">>> colorizer.append_band(30, 40, color)\n" - ">>> colorizer.get_color(35)\n" - "Color('#0044cc')\n" + ">>> colorizer.add_stop(0, mapnik2.COLORIZER_DISCRETE, mapnik.Color(\"#000000\"))\n" + ">>> colorizer.add_stop(100, mapnik2.COLORIZER_DISCRETE, mapnik.Color(\"#0E0A06\"))\n" + ">>> colorizer.get_color(50)\n" + "Color('#070503')\n" ) ; - class_("ColorBands", - "A RasterColorizer's collection of ordered color bands.\n" + class_("ColorizerStops", + "A RasterColorizer's collection of ordered color stops.\n" "This class is not meant to be instantiated from python. However, " - "it can be accessed at a RasterColorizer's \"bands\" attribute for " + "it can be accessed at a RasterColorizer's \"stops\" attribute for " "introspection purposes", no_init) - .def(vector_indexing_suite()) + .def(vector_indexing_suite()) + ; + + enum_("ColorizerMode") + .value("COLORIZER_INHERIT", COLORIZER_INHERIT) + .value("COLORIZER_LINEAR", COLORIZER_LINEAR) + .value("COLORIZER_DISCRETE", COLORIZER_DISCRETE) + .value("COLORIZER_EXACT", COLORIZER_EXACT) + .export_values() ; - class_("ColorBand",init( - "A Color Band object.\n" - "Create with a value and color\n" + class_("ColorizerStop",init( + "A Colorizer Stop object.\n" + "Create with a value, ColorizerMode, and Color\n" "\n" "Usage:" ">>> color = mapnik.Color(\"#fff000\")\n" - ">>> color_band = mapnik.ColorBand(4, color)\n" + ">>> stop= mapnik.ColorizerStop(42.42, mapnik.COLORIZER_LINEAR, color)\n" )) - .add_property("color", make_function - (&color_band::get_color, - return_value_policy())) - .add_property("value", &color_band::get_value) - .add_property("max_value", &color_band::get_max_value) + .add_property("color", + make_function(&colorizer_stop::get_color, return_value_policy()), + &colorizer_stop::set_color, + "The stop color (mapnik.Color).\n") + .add_property("value", + &colorizer_stop::get_value, + &colorizer_stop::set_value, + "The stop value.\n") + .add_property("mode", + &colorizer_stop::get_mode_enum, + &colorizer_stop::set_mode_enum, + "The stop mode (mapnik.ColorizerMode).\n" + "\n" + "If this is COLORIZER_INHERIT then it will inherit the default mode\n" + " from the RasterColorizer it is added to.\n") .def(self == self) - .def("__str__",&color_band::to_string) + .def("__str__",&colorizer_stop::to_string) ; } diff --git a/include/mapnik/raster_colorizer.hpp b/include/mapnik/raster_colorizer.hpp index 03efdaf1d..4a8b0d9b6 100644 --- a/include/mapnik/raster_colorizer.hpp +++ b/include/mapnik/raster_colorizer.hpp @@ -22,6 +22,20 @@ *****************************************************************************/ //$Id$ +/** \brief Raster Colouriser + * + * This class allows GDAL raster bands to be colourised. It only works with single + * band GDAL rasters, not greyscale, alpha, or rgb (due to the GDAL featureset loading + * single channel GDAL rasters as FLOAT32, and the others as BYTE, and not having a method + * of figuring out which). + * + * Every input value is translated to an output value. The output value is determined by + * what 'stop' the input value is in. Each stop covers the range of input values from its + * 'value' parameter, up to the 'value' parameter of the next stop. + * + */ + + #ifndef RASTER_COLORIZER_HPP #define RASTER_COLORIZER_HPP @@ -29,193 +43,171 @@ #include #include #include +#include #include namespace mapnik { -struct MAPNIK_DECL color_band + +//! \brief Enumerates the modes of interpolation +enum colorizer_mode_enum { - float value_; - float max_value_; - color color_; - unsigned midpoints_; - bool is_interpolated_; - color_band(float value, color c) - : value_(value), - max_value_(value), - color_(c), - midpoints_(0), - is_interpolated_(false) {} - color_band(float value, float max_value, color c) - : value_(value), - max_value_(max_value), - color_(c), - midpoints_(0), - is_interpolated_(false) {} - bool is_interpolated() const - { - return is_interpolated_; - } - unsigned get_midpoints() const - { - return midpoints_; - } - float get_value() const - { - return value_; - } - float get_max_value() const - { - return max_value_; - } - const color& get_color() const - { - return color_; - } - bool operator==(color_band const& other) const - { - return value_ == other.value_ && color_ == other.color_ && max_value_ == other.max_value_; - } - std::string to_string() const - { - std::stringstream ss; - ss << color_.to_string() << " " << value_ << " " << max_value_; - return ss.str(); - } -}; + COLORIZER_INHERIT = 0, //!< The stop inherits the mode from the colorizer + COLORIZER_LINEAR = 1, //!< Linear interpolation between colors + COLORIZER_DISCRETE = 2, //!< Single color for stop + COLORIZER_EXACT = 3, //!< Only the exact value specified for the stop gets translated, others use the default + colorizer_mode_enum_MAX +}; -typedef std::vector color_bands; - -struct MAPNIK_DECL raster_colorizer -{ - explicit raster_colorizer() - : colors_() {} - - raster_colorizer(const raster_colorizer &ps) - : colors_(ps.colors_) {} - - raster_colorizer(color_bands &colors) - : colors_(colors) {} - - const color_bands& get_color_bands() const - { - return colors_; - } - void append_band (color_band band) - { - if (colors_.size() > 0 && colors_.back().value_ > band.value_) { -#ifdef MAPNIK_DEBUG - std::clog << "prev.v=" << colors_.back().value_ << ". band.v=" << band.value_ << "\n"; -#endif - throw config_error( - "Bands must be appended in ascending value order" - ); - } - colors_.push_back(band); - if (colors_.size() > 0 && colors_.back().value_ == colors_.back().max_value_) - colors_.back().max_value_ = band.value_; - } - void append_band (color_band band, unsigned midpoints) - { - band.midpoints_ = midpoints; - if (colors_.size() > 0 && midpoints > 0) { - color_band lo = colors_.back(); - color_band const &hi = band; - int steps = midpoints+1; - float dv = (hi.value_ - lo.value_)/steps; - float da = (float(hi.color_.alpha()) - lo.color_.alpha())/steps; - float dr = (float(hi.color_.red()) - lo.color_.red())/steps; - float dg = (float(hi.color_.green()) - lo.color_.green())/steps; - float db = (float(hi.color_.blue()) - lo.color_.blue())/steps; - -#ifdef MAPNIK_DEBUG - std::clog << "lo.v=" << lo.value_ << ", hi.v=" << hi.value_ << ", dv="<value) { - hi = pos-1; - } else { - lo = pos+1; - break; - } - } - lo--; - if ((0 <= lo && lo < last) || - (lo==last && (colors_[last].value_==value || value(raster->data_.getBytes()); - unsigned *imageData = raster->data_.getData(); - unsigned i; - for (i=0; idata_.width()*raster->data_.height(); i++) - { - imageData[i] = get_color(rasterData[i]).rgba(); - } - } - + //! \brief Equality operator + //! \return True if equal, false otherwise + bool operator==(colorizer_stop const& other) const; + + //! \brief Print the stop to a string + //! \return A string representing this stop. + std::string to_string() const; + private: - color_bands colors_; + float value_; //!< The stop value + colorizer_mode mode_; //!< The stop mode + color color_; //!< The stop color }; + +typedef std::vector colorizer_stops; + + +//! \brief Class representing the raster colorizer +class raster_colorizer { +public: + //! \brief Constructor + raster_colorizer(colorizer_mode mode = COLORIZER_LINEAR, const color& _color = color(0,0,0,0)); + + //! \brief Destructor + ~raster_colorizer(); + + + //! \brief Set the default mode + //! + //! This can not be set as INHERIT, if you do, LINEAR will be used instead. + //! \param[in] mode The default mode + void set_default_mode(const colorizer_mode mode) { default_mode_ = (mode == COLORIZER_INHERIT) ? COLORIZER_LINEAR:(colorizer_mode_enum)mode; }; + void set_default_mode_enum(const colorizer_mode_enum mode) { set_default_mode(mode); }; + + //! \brief Get the default mode + //! \return The default mode + colorizer_mode get_default_mode(void) const {return default_mode_; }; + colorizer_mode_enum get_default_mode_enum(void) const {return get_default_mode(); }; + + //! \brief Set the default color + //! \param[in] color The default color + void set_default_color(const color& color) { default_color_ = color; }; + + //! \brief Get the default color + //! \return The default color + const color& get_default_color(void) const {return default_color_; }; + + + //! \brief Add a stop + //! + //! \param[in] stop The stop to add + //! \return True if added, false if error + bool add_stop(const colorizer_stop & stop); + + //! \brief Get the list of stops + //! \return The list of stops + const colorizer_stops& get_stops(void) const {return stops_; }; + + + //! \brief Colorize a raster + //! + //! \param[in, out] raster A raster stored in float32 single channel format, which gets colorized in place. + //! \param[in] properties belonging to the feature, used to find 'NODATA' information if available + void colorize(raster_ptr const& raster,const std::map &Props) const; + + + //! \brief Perform the translation of input to output + //! + //! \param[in] value Input value + //! \return color associated with the value + color get_color(float value) const; + + + //! \brief Set the epsilon value for exact mode + //! \param[in] e The epsilon value + inline void set_epsilon(const float e) { if(e > 0) epsilon_ = e; }; + + //! \brief Get the epsilon value for exact mode + //! \return The epsilon value + inline float get_epsilon(void) const { return epsilon_; }; + +private: + colorizer_stops stops_; //!< The vector of stops + + colorizer_mode default_mode_; //!< The default mode inherited by stops + color default_color_; //!< The default color + float epsilon_; //!< The epsilon value for exact mode +}; + + + + + + + typedef boost::shared_ptr raster_colorizer_ptr; + } // mapnik namespace #endif //RASTER_COLORIZER_HPP diff --git a/plugins/input/gdal/gdal_featureset.cpp b/plugins/input/gdal/gdal_featureset.cpp index 1583860e3..872b46ba5 100644 --- a/plugins/input/gdal/gdal_featureset.cpp +++ b/plugins/input/gdal/gdal_featureset.cpp @@ -194,13 +194,18 @@ feature_ptr gdal_featureset::get_feature(mapnik::query const& q) { if (band_ > nbands_) throw datasource_exception((boost::format("GDAL Plugin: '%d' is an invalid band, dataset only has '%d' bands\n") % band_ % nbands_).str()); + float *imageData = (float*)image.getBytes(); GDALRasterBand * band = dataset_.GetRasterBand(band_); + int hasNoData; + double nodata = band->GetNoDataValue(&hasNoData); band->RasterIO(GF_Read, x_off, y_off, width, height, imageData, image.width(), image.height(), GDT_Float32, 0, 0); feature->set_raster(mapnik::raster_ptr(new mapnik::raster(intersect,image))); + if (hasNoData) + feature->props()["NODATA"]=nodata; } else // working with all bands diff --git a/src/SConscript b/src/SConscript index e30bd69a2..1bafee610 100644 --- a/src/SConscript +++ b/src/SConscript @@ -139,6 +139,7 @@ source = Split( glyph_symbolizer.cpp markers_symbolizer.cpp metawriter.cpp + raster_colorizer.cpp text_placements.cpp wkt/wkt_factory.cpp metawriter_inmem.cpp diff --git a/src/agg/process_raster_symbolizer.cpp b/src/agg/process_raster_symbolizer.cpp index 7ce3eefa1..84c6a1964 100644 --- a/src/agg/process_raster_symbolizer.cpp +++ b/src/agg/process_raster_symbolizer.cpp @@ -43,7 +43,7 @@ void agg_renderer::process(raster_symbolizer const& sym, // If there's a colorizer defined, use it to color the raster in-place raster_colorizer_ptr colorizer = sym.get_colorizer(); if (colorizer) - colorizer->colorize(raster); + colorizer->colorize(raster,feature.props()); box2d ext=t_.forward(raster->ext_); diff --git a/src/cairo_renderer.cpp b/src/cairo_renderer.cpp index 37837a142..648f514ae 100644 --- a/src/cairo_renderer.cpp +++ b/src/cairo_renderer.cpp @@ -1384,7 +1384,7 @@ void cairo_renderer_base::process(raster_symbolizer const& sym, // If there's a colorizer defined, use it to color the raster in-place raster_colorizer_ptr colorizer = sym.get_colorizer(); if (colorizer) - colorizer->colorize(raster); + colorizer->colorize(raster,feature.props()); box2d ext = t_.forward(raster->ext_); int start_x = (int)ext.minx(); diff --git a/src/load_map.cpp b/src/load_map.cpp index 6e4652dc5..676bae9c8 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -1979,37 +1979,82 @@ void map_parser::parse_raster_colorizer(raster_colorizer_ptr const& rc, { try { - ptree::const_iterator cbIter = node.begin(); - ptree::const_iterator endCb = node.end(); + // mode + colorizer_mode default_mode = + get_attr(node, "default-mode", COLORIZER_LINEAR); + + if(default_mode == COLORIZER_INHERIT) { + throw config_error("RasterColorizer mode must not be INHERIT. "); + } + rc->set_default_mode( default_mode ); - for(; cbIter != endCb; ++cbIter) + // default colour + optional default_color = get_opt_attr(node, "default-color"); + if (default_color) { - ptree::value_type const& cb_tag = *cbIter; - ptree const & cb = cbIter->second; + rc->set_default_color( *default_color ); + } + - if (cb_tag.first == "ColorBand") - { - ensure_attrs(cb, "ColorBand", "value,color,midpoints,max-value,label"); - std::string value_s = get_attr(cb, "value"); - float value; - std::stringstream(value_s) >> value; - optional c = get_opt_attr(cb, "color"); - if (!c) { - throw config_error("missing color"); - } - unsigned midpoints = get_attr(cb, "midpoints", 0); - optional max_value = get_opt_attr(cb, "max-value"); - if (max_value) { - rc->append_band(value, *max_value, *c, midpoints); - } else { - rc->append_band(value, *c, midpoints); - } + // epsilon + optional eps = get_opt_attr(node, "epsilon"); + if (eps) + { + if(*eps < 0) { + throw config_error("RasterColorizer epsilon must be > 0. "); } - else if (cb_tag.first != "" && - cb_tag.first != "" ) + rc->set_epsilon( *eps ); + } + + + ptree::const_iterator stopIter = node.begin(); + ptree::const_iterator endStop = node.end(); + float maximumValue = -std::numeric_limits::max(); + + for(; stopIter != endStop; ++stopIter) + { + ptree::value_type const& stop_tag = *stopIter; + ptree const & stop = stopIter->second; + + if (stop_tag.first == "stop") + { + // colour is optional. + optional stopcolor = get_opt_attr(stop, "color"); + if (!stopcolor) { + *stopcolor = *default_color; + } + + // mode default to INHERIT + colorizer_mode mode = + get_attr(stop, "mode", COLORIZER_INHERIT); + + // value is required, and it must be bigger than the previous + optional value = + get_opt_attr(stop, "value"); + + if(!value) { + throw config_error("stop tag missing value"); + } + + if(value < maximumValue) { + throw config_error("stop tag values must be in ascending order"); + } + maximumValue = *value; + + + //append the stop + colorizer_stop tmpStop; + tmpStop.set_color(*stopcolor); + tmpStop.set_mode(mode); + tmpStop.set_value(*value); + + rc->add_stop(tmpStop); + } + else if (stop_tag.first != "" && + stop_tag.first != "" ) { throw config_error(std::string("Unknown child node. ") + - "Expected 'ColorBand' but got '" + cb_tag.first + "'"); + "Expected 'stop' but got '" + stop_tag.first + "'"); } } } diff --git a/src/raster_colorizer.cpp b/src/raster_colorizer.cpp new file mode 100644 index 000000000..fee2d8887 --- /dev/null +++ b/src/raster_colorizer.cpp @@ -0,0 +1,256 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2006 Artem Pavlenko + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + *****************************************************************************/ + +//$Id: $ + +#include +#include +#include + +using namespace std; +using namespace mapnik; + + +//! \brief Strings for the colorizer_mode enumeration +static const char *colorizer_mode_strings[] = { + "inherit", + "linear", + "discrete", + "exact", + "" +}; + +IMPLEMENT_ENUM( colorizer_mode, colorizer_mode_strings ); + + +colorizer_stop::colorizer_stop(const float value/* = 0*/, const colorizer_mode mode/* = COLORIZER_INHERIT*/, const color& _color/* = color(0,0,0,0)*/ ) + : value_(value) + , mode_(mode) + , color_(_color) +{ + +} + +colorizer_stop::colorizer_stop(const colorizer_stop& stop) + : value_(stop.value_) + , mode_(stop.mode_) + , color_(stop.color_) +{ +} + + +colorizer_stop::~colorizer_stop() +{ + +} + + +bool colorizer_stop::operator==(colorizer_stop const& other) const +{ + return (value_ == other.value_) && + (color_ == other.color_) && + (mode_ == other.mode_); +} + + +std::string colorizer_stop::to_string() const +{ + std::stringstream ss; + ss << color_.to_string() << " " << value_ << " " << mode_.as_string(); + return ss.str(); +} + + + + + +raster_colorizer::raster_colorizer(colorizer_mode mode/* = COLORIZER_LINEAR*/, const color& _color/* = color(0,0,0,0)*/) + : default_mode_(mode) + , default_color_(_color) + , epsilon_(numeric_limits::epsilon()) +{ + +} + +raster_colorizer::~raster_colorizer() +{ +} + +bool raster_colorizer::add_stop(const colorizer_stop & stop) { + //make sure stops are added in order of value + if(stops_.size()) { + if(stop.get_value() <= stops_.back().get_value()) { + return false; + } + } + + stops_.push_back(stop); + + return true; +} + +void raster_colorizer::colorize(raster_ptr const& raster,const std::map &Props) const +{ + unsigned *imageData = raster->data_.getData(); + + int len = raster->data_.width() * raster->data_.height(); + + bool hasNoData = false; + float noDataValue = 0; + + if (Props.count("NODATA")>0) + { + hasNoData = true; + noDataValue = Props.at("NODATA").to_double(); + } + + for (int i=0; i (&imageData[i]); + if (hasNoData && noDataValue == value) + imageData[i] = color(0,0,0,0).rgba(); + else + imageData[i] = get_color(value).rgba(); + } +} + +inline float interpolate(float start,float end, float fraction) +{ + return fraction * (end - start) + start; +} + +color raster_colorizer::get_color(float value) const { + int stopCount = stops_.size(); + + //use default color if no stops + if(stopCount == 0) { + return default_color_; + } + + //1 - Find the stop that the value is in + int stopIdx = -1; + bool foundStopIdx = false; + + for(int i=0; i= stopCount) { //there is no next stop + nextStopIdx = stopCount - 1; + } + + //3 - Work out the mode + colorizer_mode stopMode; + if( stopIdx == -1 ) { //before the first stop + stopMode = default_mode_; + } + else { + stopMode = stops_[stopIdx].get_mode(); + if(stopMode == COLORIZER_INHERIT) { + stopMode = default_mode_; + } + } + + //4 - Calculate the colour + color stopColor; + color nextStopColor; + float stopValue = 0; + float nextStopValue = 0; + color outputColor = get_default_color(); + if(stopIdx == -1) { + stopColor = default_color_; + nextStopColor = stops_[nextStopIdx].get_color(); + stopValue = value; + nextStopValue = stops_[nextStopIdx].get_value(); + } + else { + stopColor = stops_[stopIdx].get_color(); + nextStopColor = stops_[nextStopIdx].get_color(); + stopValue = stops_[stopIdx].get_value(); + nextStopValue = stops_[nextStopIdx].get_value(); + } + + switch(stopMode) { + case COLORIZER_LINEAR: + { + //deal with this separately so we don't have to worry about div0 + if(nextStopValue == stopValue) { + outputColor = stopColor; + } + else { + float fraction = (value - stopValue) / (nextStopValue - stopValue); + + float r = interpolate(stopColor.red(), nextStopColor.red(),fraction); + float g = interpolate(stopColor.green(), nextStopColor.green(),fraction); + float b = interpolate(stopColor.blue(), nextStopColor.blue(),fraction); + float a = interpolate(stopColor.alpha(), nextStopColor.alpha(),fraction); + + outputColor.set_red(r); + outputColor.set_green(g); + outputColor.set_blue(b); + outputColor.set_alpha(a); + } + + } + break; + case COLORIZER_DISCRETE: + outputColor = stopColor; + break; + case COLORIZER_EXACT: + default: + //approximately equal (within epsilon) + if(fabs(value - stopValue) < epsilon_) { + outputColor = stopColor; + } + else { + outputColor = default_color_; + } + break; + } + + + /* + std::clog << "get_color: " << value << "\n"; + std::clog << "\tstopIdx: " << stopIdx << "\n"; + std::clog << "\tnextStopIdx: " << nextStopIdx << "\n"; + std::clog << "\tstopValue: " << stopValue << "\n"; + std::clog << "\tnextStopValue: " << nextStopValue << "\n"; + std::clog << "\tstopColor: " << stopColor.to_string() << "\n"; + std::clog << "\tnextStopColor: " << nextStopColor.to_string() << "\n"; + std::clog << "\tstopMode: " << stopMode.as_string() << "\n"; + std::clog << "\toutputColor: " << outputColor.to_string() << "\n"; +*/ + + return outputColor; +} + + diff --git a/src/save_map.cpp b/src/save_map.cpp index d90c59982..c3585d997 100644 --- a/src/save_map.cpp +++ b/src/save_map.cpp @@ -43,30 +43,6 @@ namespace mapnik using boost::property_tree::ptree; using boost::optional; -void serialize_raster_colorizer(ptree & sym_node, - raster_colorizer_ptr const& colorizer, - bool explicit_defaults) -{ - ptree & col_node = sym_node.push_back( - ptree::value_type("RasterColorizer", ptree() ))->second; - - unsigned i; - color_bands const &cb = colorizer->get_color_bands(); - for (i=0; isecond; - set_attr(band_node, "value", cb[i].get_value()); - if (cb[i].get_value() != cb[i].get_max_value()) - set_attr(band_node, "max-value", cb[i].get_max_value()); - set_attr(band_node, "midpoints", cb[i].get_midpoints()); - optional c = cb[i].get_color(); - if (c) set_attr(band_node, "color", * c); - } - } - -} class serialize_symbolizer : public boost::static_visitor<> { @@ -409,6 +385,29 @@ public: private: serialize_symbolizer(); + + void serialize_raster_colorizer(ptree & sym_node, + raster_colorizer_ptr const& colorizer, + bool explicit_defaults) + { + ptree & col_node = sym_node.push_back( + ptree::value_type("RasterColorizer", ptree() ))->second; + + set_attr(col_node, "default-mode", colorizer->get_default_mode()); + set_attr(col_node, "default-color", colorizer->get_default_color()); + set_attr(col_node, "epsilon", colorizer->get_epsilon()); + + unsigned i; + colorizer_stops const &stops = colorizer->get_stops(); + for (i=0; isecond; + set_attr(stop_node, "value", stops[i].get_value()); + set_attr(stop_node, "color", stops[i].get_color()); + set_attr(stop_node, "mode", stops[i].get_mode().as_string()); + } + + } + void add_image_attributes(ptree & node, const symbolizer_with_image & sym) { std::string const& filename = path_processor_type::to_string( *sym.get_filename()); diff --git a/tests/data/good_maps/glyph_symbolizer.xml b/tests/data/good_maps/glyph_symbolizer.xml index 3375d6fbe..370f9578e 100644 --- a/tests/data/good_maps/glyph_symbolizer.xml +++ b/tests/data/good_maps/glyph_symbolizer.xml @@ -2,12 +2,12 @@ - \ No newline at end of file + diff --git a/tests/data/good_maps/raster_colorizer.xml b/tests/data/good_maps/raster_colorizer.xml new file mode 100644 index 000000000..3a5a8719f --- /dev/null +++ b/tests/data/good_maps/raster_colorizer.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + DISCRETE RAINBOW + + ../raster/dataraster.tif + gdal + 1 + + + + + + diff --git a/tests/data/good_maps/raster_symbolizer.xml b/tests/data/good_maps/raster_symbolizer.xml index 0b8d7a2f6..301b53a1c 100644 --- a/tests/data/good_maps/raster_symbolizer.xml +++ b/tests/data/good_maps/raster_symbolizer.xml @@ -3,9 +3,9 @@