pre-eliminary raster coloring support

This commit is contained in:
Alberto Valverde 2010-03-12 13:34:13 +00:00
parent 6a9030f7cc
commit 2f1d60b666
11 changed files with 454 additions and 1 deletions

View file

@ -263,6 +263,11 @@ class _Symbolizer(Symbolizer,_injector):
def symbol(self):
return getattr(self,self.type())()
class _Color(Color,_injector):
def __repr__(self):
return "Color(%r)" % self.to_hex_string()
#class _Filter(Filter,_injector):
# """Mapnik Filter expression.
#

View file

@ -59,6 +59,7 @@ void export_font_engine();
void export_projection();
void export_proj_transform();
void export_view_transform();
void export_raster_colorizer();
#include <mapnik/version.hpp>
#include <mapnik/map.hpp>
@ -331,6 +332,7 @@ BOOST_PYTHON_MODULE(_mapnik2)
export_view_transform();
export_coord();
export_map();
export_raster_colorizer();
def("render_to_file",&render_to_file1,
"\n"

View file

@ -0,0 +1,88 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2010 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 <boost/python.hpp>
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
#include <mapnik/raster_colorizer.hpp>
using mapnik::raster_colorizer;
using mapnik::raster_colorizer_ptr;
using mapnik::color_band;
using mapnik::color_bands;
namespace {
void append_band1(raster_colorizer_ptr & rc, color_band b)
{
rc->append_band(b);
}
void append_band2(raster_colorizer_ptr & rc, color_band b, unsigned m)
{
rc->append_band(b, m);
}
void append_band3(raster_colorizer_ptr & rc, float v, color c)
{
rc->append_band(v, c);
}
void append_band4(raster_colorizer_ptr & rc, float v, color c, unsigned m)
{
rc->append_band(v, c, m);
}
color_bands const& get_color_bands(raster_colorizer_ptr & rc)
{
return rc->get_color_bands();
}
}
void export_raster_colorizer()
{
using namespace boost::python;
class_<raster_colorizer,raster_colorizer_ptr>("RasterColorizer", init<>("Deafult ctor."))
.add_property("bands",make_function
(get_color_bands,
return_value_policy<reference_existing_object>()))
.def("append_band", append_band1, "TODO: Write docs")
.def("append_band", append_band2, "TODO: Write docs")
.def("append_band", append_band3, "TODO: Write docs")
.def("append_band", append_band4, "TODO: Write docs")
.def("get_color", &raster_colorizer::get_color, "TODO: Write docs")
;
class_<color_bands>("ColorBands",init<>("Default ctor."))
.def(vector_indexing_suite<color_bands>())
;
class_<color_band>("ColorBand",
init<float,color const&>("Deafult ctor."))
.add_property("color", make_function
(&color_band::get_color,
return_value_policy<reference_existing_object>()))
.add_property("value", &color_band::get_value)
.def(self == self)
.def("__str__",&color_band::to_string)
;
}

View file

@ -111,5 +111,22 @@ void export_raster_symbolizer()
">>> r = RasterSymbolizer()\n"
">>> r.opacity = .5\n"
)
.add_property("colorizer",
&raster_symbolizer::get_colorizer,
&raster_symbolizer::set_colorizer,
"Get/Set the RasterColorizer used to color data rasters.\n"
"\n"
"Usage:\n"
"\n"
">>> from mapnik import RasterSymbolizer, RasterColorizer\n"
">>> r = RasterSymbolizer()\n"
">>> r.colorizer = RasterColorizer()\n"
">>> for value, color in [\n"
"... (0, \"#000000\"),\n"
"... (10, \"#ff0000\"),\n"
"... (40, \"#00ff00\"),\n"
"... ]:\n"
"... r.colorizer.append_band(value, color)\n"
)
;
}

View file

@ -0,0 +1,191 @@
/*****************************************************************************
*
* 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$
#ifndef RASTER_COLORIZER_HPP
#define RASTER_COLORIZER_HPP
#include <mapnik/config.hpp>
#include <mapnik/config_error.hpp>
#include <mapnik/color.hpp>
#include <mapnik/feature.hpp>
#include <vector>
using mapnik::color;
using std::vector;
namespace mapnik
{
struct MAPNIK_DECL color_band
{
float value_;
color color_;
unsigned midpoints_;
bool is_interpolated_;
color_band(float value, color c)
: value_(value),
color_(c),
midpoints_(0),
is_interpolated_(false) {}
const bool is_interpolated() const
{
return is_interpolated_;
}
const unsigned get_midpoints() const
{
return midpoints_;
}
const float get_value() const
{
return value_;
}
const color& get_color() const
{
return color_;
}
bool operator==(color_band const& other) const
{
return value_ == other.value_ && color_ == other.color_;
}
std::string to_string() const
{
std::stringstream ss;
ss << color_.to_string() << " " << value_;
return ss.str();
}
};
typedef vector<color_band> 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);
}
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="<<dv<<"\n";
#endif
// interpolate intermediate values and colors
int j;
for (j=1; j<steps; j++) {
color_band b(
lo.get_value() + dv*j,
color(int(float(lo.color_.red()) + dr*j),
int(float(lo.color_.green()) + dg*j),
int(float(lo.color_.blue()) + db*j),
int(float(lo.color_.alpha()) + da*j)
)
);
b.is_interpolated_ = true;
append_band(b);
}
}
append_band(band);
}
void append_band (float value, color c)
{
append_band(color_band(value, c));
}
void append_band (float value, color c, unsigned midpoints)
{
append_band(color_band(value, c), midpoints);
}
/* rgba =
* if cs[pos].value <= value < cs[pos+1].value: cs[pos].color
* otherwise: transparent
* where 0 <= pos < length(bands)-1
*/
color get_color(float value) const {
int pos=-1, lo=0, hi=colors_.size()-1;
while (lo<=hi) {
pos = (lo+hi)/2;
if (colors_[pos].value_<value) {
lo = pos+1;
} else if (colors_[pos].value_>value) {
hi = pos-1;
} else {
lo = pos+1;
break;
}
}
lo--;
return (0 <= lo && lo < (int)colors_.size()-1)?
colors_[lo].color_:
color(0,0,0,0);
}
void colorize(mapnik::raster_ptr const& raster) const {
float *rasterData = (float*)raster->data_.getBytes();
unsigned *imageData = raster->data_.getData();
unsigned i;
for (i=0; i<raster->data_.width()*raster->data_.height(); i++)
imageData[i] = get_color(rasterData[i]).rgba();
}
private:
color_bands colors_;
};
typedef boost::shared_ptr<mapnik::raster_colorizer> raster_colorizer_ptr;
}
#endif //RASTER_COLORIZER_HPP

View file

@ -25,6 +25,9 @@
#define RASTER_SYMBOLIZER_HPP
#include <mapnik/config.hpp>
#include <mapnik/raster_colorizer.hpp>
#include <boost/shared_ptr.hpp>
namespace mapnik
{
@ -32,7 +35,14 @@ namespace mapnik
explicit raster_symbolizer()
: mode_("normal"),
scaling_("fast"),
opacity_(1.0) {}
opacity_(1.0),
colorizer_() {}
raster_symbolizer(const raster_symbolizer &rs)
: mode_(rs.get_mode()),
scaling_(rs.get_scaling()),
opacity_(rs.get_opacity()),
colorizer_(rs.colorizer_) {}
std::string const& get_mode() const
{
@ -58,10 +68,19 @@ namespace mapnik
{
return opacity_;
}
raster_colorizer_ptr get_colorizer() const
{
return colorizer_;
}
void set_colorizer(raster_colorizer_ptr const& colorizer)
{
colorizer_ = colorizer;
}
private:
std::string mode_;
std::string scaling_;
float opacity_;
raster_colorizer_ptr colorizer_;
};
}

View file

@ -748,6 +748,10 @@ void agg_renderer<T>::process(raster_symbolizer const& sym,
raster_ptr const& raster=feature.get_raster();
if (raster)
{
// 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);
box2d<double> ext=t_.forward(raster->ext_);

View file

@ -1000,6 +1000,11 @@ void cairo_renderer_base::process(raster_symbolizer const& sym,
raster_ptr const& raster = feature.get_raster();
if (raster)
{
// 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);
box2d<double> ext = t_.forward(raster->ext_);
int start_x = int(round(ext.minx()));
int start_y = int(round(ext.miny()));

Binary file not shown.

View file

@ -0,0 +1,32 @@
import mapnik2
from nose.tools import *
class test_raster_colorizer():
colorizer = mapnik2.RasterColorizer()
# Setup the color bands. band[N].color will apply to all
# values 'v' if band[N].value <= v < band[N+1].color
# If no color is found then "transparent" will be assigned
bands = [(value, mapnik2.Color(color)) for value, color in [
( 0, "#0044cc"),
( 10, "#00cc00"),
( 20, "#ffff00"),
( 30, "#ff7f00"),
( 40, "#ff0000"),
( 50, "#ff007f"),
( 60, "#ff00ff"),
( 70, "#cc00cc"),
( 80, "#990099"),
( 90, "#660066"),
( 200, "#00000"), # last band denotes upper limit, values above it will
# not return the color
]]
for value, color in bands:
colorizer.append_band(value, color)
eq_(colorizer.get_color(-1), mapnik2.Color("transparent"))
eq_(colorizer.get_color(0), bands[0][1])
eq_(colorizer.get_color(5), bands[0][1])
eq_(colorizer.get_color(10), bands[1][1])
eq_(colorizer.get_color(200), mapnik2.Color("transparent"))
eq_(colorizer.get_color(201), mapnik2.Color("transparent"))

View file

@ -0,0 +1,90 @@
#!/usr/bin/env python
from nose.tools import *
from utilities import execution_path
import os, mapnik2
def setup():
# All of the paths used are relative, if we run the tests
# from another directory we need to chdir()
os.chdir(execution_path('.'))
def test_dataraster_coloring():
srs = '+init=epsg:32630'
lyr = mapnik2.Layer('dataraster')
lyr.datasource = mapnik2.Gdal(
file = '../data/raster/dataraster.tif',
band = 1,
)
lyr.srs = srs
_map = mapnik2.Map(256,256, srs)
style = mapnik2.Style()
rule = mapnik2.Rule()
sym = mapnik2.RasterSymbolizer()
# Assigning a colorizer to the RasterSymbolizer tells the later
# that it should use it to colorize the raw data raster
sym.colorizer = mapnik2.RasterColorizer()
for value, color in [
( 0, "#0044cc"),
( 10, "#00cc00"),
( 20, "#ffff00"),
( 30, "#ff7f00"),
( 40, "#ff0000"),
( 50, "#ff007f"),
( 60, "#ff00ff"),
( 70, "#cc00cc"),
( 80, "#990099"),
( 90, "#660066"),
( 200, "transparent"),
]:
sym.colorizer.append_band(value, mapnik2.Color(color))
rule.symbols.append(sym)
style.rules.append(rule)
_map.append_style('foo', style)
lyr.styles.append('foo')
_map.layers.append(lyr)
_map.zoom_to_box(lyr.envelope())
im = mapnik2.Image(_map.width,_map.height)
mapnik2.render(_map, im)
imdata = im.tostring()
assert len(imdata) > 0
# we have some values in the [20,30) interval so check that they're colored
assert '\xff\xff\xff\x00' in imdata
# save a png somewhere so we can see it
if 'MAPNIK_TEST_IMAGE_PATH' in os.environ:
f = open(os.environ['MAPNIK_TEST_IMAGE_PATH'],'wb')
f.write(im.tostring('png'))
f.close()
def test_dataraster_query_point():
srs = '+init=epsg:32630'
lyr = mapnik2.Layer('dataraster')
lyr.datasource = mapnik2.Gdal(
file = '../data/raster/dataraster.tif',
band = 1,
)
lyr.srs = srs
_map = mapnik2.Map(256,256, srs)
_map.layers.append(lyr)
# point inside raster extent with valid data
x, y = 427417, 4477517
features = _map.query_point(0,x,y).features
assert len(features) == 1
feat = features[0]
center = feat.envelope().center()
assert center.x==x and center.y==y, center
value = feat['value']
assert value == 21.0, value
# point outside raster extent
features = _map.query_point(0,-427417,4477517).features
assert len(features) == 0
# point inside raster extent with nodata
features = _map.query_point(0,126850,4596050).features
assert len(features) == 0