pre-eliminary raster coloring support
This commit is contained in:
parent
6a9030f7cc
commit
2f1d60b666
11 changed files with 454 additions and 1 deletions
|
@ -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.
|
||||
#
|
||||
|
|
|
@ -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"
|
||||
|
|
88
bindings/python/mapnik_raster_colorizer.cpp
Normal file
88
bindings/python/mapnik_raster_colorizer.cpp
Normal 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)
|
||||
;
|
||||
}
|
|
@ -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"
|
||||
)
|
||||
;
|
||||
}
|
||||
|
|
191
include/mapnik/raster_colorizer.hpp
Normal file
191
include/mapnik/raster_colorizer.hpp
Normal 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
|
|
@ -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_;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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_);
|
||||
|
||||
|
|
|
@ -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()));
|
||||
|
|
BIN
tests/data/raster/dataraster.tif
Normal file
BIN
tests/data/raster/dataraster.tif
Normal file
Binary file not shown.
32
tests/python_tests/raster_colorizer_test.py
Normal file
32
tests/python_tests/raster_colorizer_test.py
Normal 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"))
|
90
tests/python_tests/raster_symbolizer_test.py
Normal file
90
tests/python_tests/raster_symbolizer_test.py
Normal 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
|
Loading…
Add table
Reference in a new issue