From fc3a1beeefd49ad70eb9b633914489a1b74f396f Mon Sep 17 00:00:00 2001 From: Alberto Valverde Date: Thu, 18 Mar 2010 20:04:35 +0000 Subject: [PATCH] initial skeleton of glyph_symbolizer (coarsely ported from arrow_symbolizer). Mapnik compiles but symbolizer does nothing useful yet and is going to be heavily modified to make use of expressions to be general enough to be usable for purposes other than rendering arrows representing vector direction/intensity --- bindings/python/mapnik_glyph_symbolizer.cpp | 75 ++++++++ bindings/python/mapnik_python.cpp | 2 + bindings/python/mapnik_rule.cpp | 2 + bindings/python/mapnik_symbolizer.cpp | 6 + include/mapnik/agg_renderer.hpp | 3 + include/mapnik/cairo_renderer.hpp | 3 + include/mapnik/glyph_symbolizer.hpp | 203 ++++++++++++++++++++ include/mapnik/rule.hpp | 10 +- include/mapnik/text_path.hpp | 3 + include/mapnik/value.hpp | 37 ++++ src/SConscript | 1 + src/agg_renderer.cpp | 8 + src/cairo_renderer.cpp | 6 + src/glyph_symbolizer.cpp | 69 +++++++ src/save_map.cpp | 13 +- tests/data/shp/arrows.dbf | Bin 0 -> 293 bytes tests/data/shp/arrows.shp | Bin 0 -> 212 bytes tests/data/shp/arrows.shx | Bin 0 -> 132 bytes tests/python_tests/glyph_symbolizer_test.py | 57 ++++++ 19 files changed, 493 insertions(+), 5 deletions(-) create mode 100644 bindings/python/mapnik_glyph_symbolizer.cpp create mode 100644 include/mapnik/glyph_symbolizer.hpp create mode 100644 src/glyph_symbolizer.cpp create mode 100644 tests/data/shp/arrows.dbf create mode 100644 tests/data/shp/arrows.shp create mode 100644 tests/data/shp/arrows.shx create mode 100644 tests/python_tests/glyph_symbolizer_test.py diff --git a/bindings/python/mapnik_glyph_symbolizer.cpp b/bindings/python/mapnik_glyph_symbolizer.cpp new file mode 100644 index 000000000..2419f5ca6 --- /dev/null +++ b/bindings/python/mapnik_glyph_symbolizer.cpp @@ -0,0 +1,75 @@ +#include +#include +#include + +using mapnik::glyph_symbolizer; +using mapnik::position; +using namespace boost::python; + +list get_displacement_list(const glyph_symbolizer& t) +{ + position pos = t.get_displacement(); + double dx = boost::get<0>(pos); + double dy = boost::get<1>(pos); + boost::python::list disp; + disp.append(dx); + disp.append(dy); + return disp; +} + +void export_glyph_symbolizer() +{ + class_("GlyphSymbolizer", + init()) + .add_property("face_name", + make_function(&glyph_symbolizer::get_face_name,return_value_policy()), + &glyph_symbolizer::set_face_name) + .add_property("char", + make_function(&glyph_symbolizer::get_char,return_value_policy()), + &glyph_symbolizer::set_char) + .add_property("angle_offset", + &glyph_symbolizer::get_angle_offset, + &glyph_symbolizer::set_angle_offset) + .add_property("allow_overlap", + &glyph_symbolizer::get_allow_overlap, + &glyph_symbolizer::set_allow_overlap) + .add_property("avoid_edges", + &glyph_symbolizer::get_avoid_edges, + &glyph_symbolizer::set_avoid_edges) + .def("get_displacement", get_displacement_list) + .def("set_displacement", &glyph_symbolizer::set_displacement) + .add_property("halo_fill", + make_function(&glyph_symbolizer::get_halo_fill, + return_value_policy()), + &glyph_symbolizer::set_halo_fill) + .add_property("halo_radius", + &glyph_symbolizer::get_halo_radius, + &glyph_symbolizer::set_halo_radius) + .add_property("value_attr", make_function( + &glyph_symbolizer::get_value_attr, + return_value_policy()), + &glyph_symbolizer::set_value_attr) + .add_property("azimuth_attr", make_function( + &glyph_symbolizer::get_azimuth_attr, + return_value_policy()), + &glyph_symbolizer::set_azimuth_attr) + .add_property("colorizer", + &glyph_symbolizer::get_colorizer, + &glyph_symbolizer::set_colorizer, + "Get/Set the RasterColorizer used to color the arrows.\n" + "\n" + "Usage:\n" + "\n" + ">>> from mapnik import GlyphSymbolizer, RasterColorizer\n" + ">>> sym = GlyphSymbolizer()\n" + ">>> sym.colorizer = RasterColorizer()\n" + ">>> for value, color in [\n" + "... (0, \"#000000\"),\n" + "... (10, \"#ff0000\"),\n" + "... (40, \"#00ff00\"),\n" + "... ]:\n" + "... sym.colorizer.append_band(value, color)\n" + ) + ; + ; +} diff --git a/bindings/python/mapnik_python.cpp b/bindings/python/mapnik_python.cpp index a2013e2c4..f94b908f8 100644 --- a/bindings/python/mapnik_python.cpp +++ b/bindings/python/mapnik_python.cpp @@ -60,6 +60,7 @@ void export_projection(); void export_proj_transform(); void export_view_transform(); void export_raster_colorizer(); +void export_glyph_symbolizer(); #include #include @@ -333,6 +334,7 @@ BOOST_PYTHON_MODULE(_mapnik2) export_coord(); export_map(); export_raster_colorizer(); + export_glyph_symbolizer(); def("render_to_file",&render_to_file1, "\n" diff --git a/bindings/python/mapnik_rule.cpp b/bindings/python/mapnik_rule.cpp index d0c446745..3726256e6 100644 --- a/bindings/python/mapnik_rule.cpp +++ b/bindings/python/mapnik_rule.cpp @@ -44,6 +44,7 @@ using mapnik::shield_symbolizer; using mapnik::text_symbolizer; using mapnik::building_symbolizer; using mapnik::markers_symbolizer; +using mapnik::glyph_symbolizer; using mapnik::symbolizer; using mapnik::to_expression_string; @@ -161,6 +162,7 @@ void export_rule() implicitly_convertible(); implicitly_convertible(); implicitly_convertible(); + implicitly_convertible(); class_("Symbolizers",init<>("TODO")) .def(vector_indexing_suite()) diff --git a/bindings/python/mapnik_symbolizer.cpp b/bindings/python/mapnik_symbolizer.cpp index 84a8fd1a0..e216f3544 100644 --- a/bindings/python/mapnik_symbolizer.cpp +++ b/bindings/python/mapnik_symbolizer.cpp @@ -39,6 +39,7 @@ using mapnik::shield_symbolizer; using mapnik::text_symbolizer; using mapnik::building_symbolizer; using mapnik::markers_symbolizer; +using mapnik::glyph_symbolizer; struct get_symbolizer_type : public boost::static_visitor { @@ -95,6 +96,11 @@ struct get_symbolizer_type : public boost::static_visitor return "markers"; } + std::string operator () ( const glyph_symbolizer & sym ) + { + return "glyph"; + } + }; std::string get_symbol_type(const symbolizer& symbol) diff --git a/include/mapnik/agg_renderer.hpp b/include/mapnik/agg_renderer.hpp index 296ba716b..f12f24079 100644 --- a/include/mapnik/agg_renderer.hpp +++ b/include/mapnik/agg_renderer.hpp @@ -83,6 +83,9 @@ namespace mapnik { void process(markers_symbolizer const& sym, Feature const& feature, proj_transform const& prj_trans); + void process(glyph_symbolizer const& sym, + Feature const& feature, + proj_transform const& prj_trans); private: T & pixmap_; unsigned width_; diff --git a/include/mapnik/cairo_renderer.hpp b/include/mapnik/cairo_renderer.hpp index 7fbb18644..8bb8a9059 100644 --- a/include/mapnik/cairo_renderer.hpp +++ b/include/mapnik/cairo_renderer.hpp @@ -104,6 +104,9 @@ namespace mapnik { void process(markers_symbolizer const& sym, Feature const& feature, proj_transform const& prj_trans); + void process(glyph_symbolizer const& sym, + Feature const& feature, + proj_transform const& prj_trans); protected: Map const& m_; Cairo::RefPtr context_; diff --git a/include/mapnik/glyph_symbolizer.hpp b/include/mapnik/glyph_symbolizer.hpp new file mode 100644 index 000000000..1aaa1db9c --- /dev/null +++ b/include/mapnik/glyph_symbolizer.hpp @@ -0,0 +1,203 @@ +#ifndef ARROW_SYMBOLIZER_HPP +#define ARROW_SYMBOLIZER_HPP + +#include +#include +#include +#include +#include +#include + +namespace mapnik +{ + struct MAPNIK_DECL glyph_symbolizer + { + glyph_symbolizer(std::string const& face_name, std::string const& c) + : face_name_(face_name), + char_(c), + angle_offset_(0.0), + min_size_(10), + max_size_(100), + min_value_(0), + max_value_(1), + colorizer_(), + allow_overlap_(false), + avoid_edges_(false), + displacement_(0.0, 0.0), + halo_fill_(color(255,255,255)), + halo_radius_(0), + azimuth_attr_("azimuth"), + value_attr_("value") + { + if (get_min_size() > get_max_size()) + throw config_error("Invalid size range"); + } + + std::string const& get_face_name() const + { + return face_name_; + } + + void set_face_name(std::string face_name) + { + face_name_ = face_name; + } + + std::string const& get_char() const + { + return char_; + } + + void set_char(std::string c) + { + char_ = c; + } + + double get_angle_offset() const + { + return angle_offset_; + } + void set_angle_offset(double angle) + { + angle_offset_ = angle; + } + + float get_min_value() const + { + return min_value_; + } + void set_min_value(float min_value) + { + min_value_ = min_value; + } + + float get_max_value() const + { + return max_value_; + } + void set_max_value(float max_value) + { + max_value_ = max_value; + } + + unsigned get_min_size() const + { + return min_size_; + } + void set_min_size(unsigned min_size) + { + min_size_ = min_size; + } + + unsigned get_max_size() const + { + return max_size_; + } + void set_max_size(unsigned max_size) + { + max_size_ = max_size; + } + + bool get_allow_overlap() const + { + return allow_overlap_; + } + void set_allow_overlap(bool allow_overlap) + { + allow_overlap_ = allow_overlap; + } + bool get_avoid_edges() const + { + return avoid_edges_; + } + void set_avoid_edges(bool avoid_edges) + { + avoid_edges_ = avoid_edges; + } + void set_displacement(double x, double y) + { + displacement_ = boost::make_tuple(x,y); + } + position const& get_displacement() const + { + return displacement_; + } + void set_halo_fill(color const& fill) + { + halo_fill_ = fill; + } + color const& get_halo_fill() const + { + return halo_fill_; + } + void set_halo_radius(unsigned radius) + { + halo_radius_ = radius; + } + + unsigned get_halo_radius() const + { + return halo_radius_; + } + + std::string const& get_azimuth_attr() const + { + return azimuth_attr_; + } + + void set_azimuth_attr(std::string azimuth_attr) + { + azimuth_attr_ = azimuth_attr; + } + + std::string const& get_value_attr() const + { + return value_attr_; + } + + raster_colorizer_ptr get_colorizer() const + { + return colorizer_; + } + void set_colorizer(raster_colorizer_ptr const& colorizer) + { + colorizer_ = colorizer; + } + + void set_value_attr(std::string value_attr) + { + value_attr_ = value_attr; + } + + /* + * helpers + */ + + text_path_ptr get_text_path(face_set_ptr const& faces, + Feature const& feature) const; + unsigned int get_size(double value) const; + double get_angle(Feature const& feature) const; + double get_value(Feature const& feature) const; + + + + private: + std::string face_name_; + std::string char_; + double angle_offset_; + unsigned min_size_; + unsigned max_size_; + float min_value_; + float max_value_; + raster_colorizer_ptr colorizer_; + bool allow_overlap_; + bool avoid_edges_; + position displacement_; + color halo_fill_; + unsigned halo_radius_; + std::string azimuth_attr_; + std::string value_attr_; + }; +}; + +#endif diff --git a/include/mapnik/rule.hpp b/include/mapnik/rule.hpp index cdee9c8a3..2eddc7ad4 100644 --- a/include/mapnik/rule.hpp +++ b/include/mapnik/rule.hpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -101,6 +102,12 @@ namespace mapnik { return (&lhs == &rhs); } + + inline bool operator==(glyph_symbolizer const& lhs, + glyph_symbolizer const& rhs) + { + return (&lhs == &rhs); + } typedef boost::variant symbolizer; + markers_symbolizer, + glyph_symbolizer> symbolizer; diff --git a/include/mapnik/text_path.hpp b/include/mapnik/text_path.hpp index 94b25639d..78e40e690 100644 --- a/include/mapnik/text_path.hpp +++ b/include/mapnik/text_path.hpp @@ -27,6 +27,7 @@ #define __TEXT_PATH_H__ #include +#include #include namespace mapnik @@ -164,6 +165,8 @@ namespace mapnik nodes_.clear(); } }; + + typedef boost::shared_ptr text_path_ptr; } #endif diff --git a/include/mapnik/value.hpp b/include/mapnik/value.hpp index 5019399de..12eb1cae9 100644 --- a/include/mapnik/value.hpp +++ b/include/mapnik/value.hpp @@ -624,6 +624,37 @@ struct to_expression_string : public boost::static_visitor return ss.str(); } }; + +struct to_double : public boost::static_visitor +{ + double operator() (double val) const + { + return val; + } + + double operator() (std::string const& val) const + { + std::istringstream stream(val); + double t; + stream >> t; + return t; + } + double operator() (UnicodeString const& val) const + { + std::string utf8; + to_utf8(val,utf8); + std::istringstream stream(utf8); + double t; + stream >> t; + return t; + } + + double operator() (value_null const& val) const + { + boost::ignore_unused_variable_warning(val); + return 0.0; + } +}; } class value @@ -696,6 +727,12 @@ public: { return boost::apply_visitor(impl::to_unicode(),base_); } + + double to_double() const + { + return boost::apply_visitor(impl::to_double(),base_); + } + }; inline const value operator+(value const& p1,value const& p2) diff --git a/src/SConscript b/src/SConscript index 1187bf018..95cf3c0f6 100644 --- a/src/SConscript +++ b/src/SConscript @@ -109,6 +109,7 @@ source = Split( symbolizer.cpp arrow.cpp unicode.cpp + glyph_symbolizer.cpp """ ) diff --git a/src/agg_renderer.cpp b/src/agg_renderer.cpp index 5958a7bd0..94aab0103 100644 --- a/src/agg_renderer.cpp +++ b/src/agg_renderer.cpp @@ -945,5 +945,13 @@ void agg_renderer::process(text_symbolizer const& sym, } } } + +template +void agg_renderer::process(glyph_symbolizer const& sym, + Feature const& feature, + proj_transform const& prj_trans) +{ +} + template class agg_renderer; } diff --git a/src/cairo_renderer.cpp b/src/cairo_renderer.cpp index 8bf246b7b..af3f8b63e 100644 --- a/src/cairo_renderer.cpp +++ b/src/cairo_renderer.cpp @@ -1041,6 +1041,12 @@ void cairo_renderer_base::process(markers_symbolizer const& sym, { } +void cairo_renderer_base::process(glyph_symbolizer const& sym, + Feature const& feature, + proj_transform const& prj_trans) +{ +} + void cairo_renderer_base::process(text_symbolizer const& sym, Feature const& feature, proj_transform const& prj_trans) diff --git a/src/glyph_symbolizer.cpp b/src/glyph_symbolizer.cpp new file mode 100644 index 000000000..a4217c2e8 --- /dev/null +++ b/src/glyph_symbolizer.cpp @@ -0,0 +1,69 @@ +#include +#include + +#include + + +namespace mapnik +{ + text_path_ptr glyph_symbolizer::get_text_path(face_set_ptr const& faces, + Feature const& feature) const + { + // Calculate face rotation angle and box offset adjustments + typedef std::pair dimension_t; + + // Get string_info with arrow glyph + string_info info(UnicodeString(get_char().c_str())); + faces->get_string_info(info); + if (info.num_characters() != 1) + { + throw config_error("'char' length must be exactly 1"); + } + + character_info ci = info.at(0); + dimension_t cdim = faces->character_dimensions(ci.character); + double cwidth = (static_cast(cdim.first))/2.0; + double cheight = (static_cast(cdim.second))/2.0; + double angle = get_angle(feature); + double xoff = cwidth*cos(angle) - cheight*sin(angle); + double yoff = cwidth*sin(angle) + cheight*cos(angle); + + text_path_ptr path_ptr = text_path_ptr(new text_path()); + path_ptr->add_node(ci.character, -xoff, -yoff, angle); + return path_ptr; + } + + unsigned int glyph_symbolizer::get_size(double value) const + { + double scale = (get_max_size()-get_min_size()) / + (get_max_value()-get_min_value()); + double size = get_min_size() + (value - get_min_value())*scale; + if (size > get_max_size()) + { + // size too large, clip it + size = get_max_size(); + } + return static_cast(floor(size)); + } + + double glyph_symbolizer::get_angle(Feature const& feature) const + { + // Returns the angle of rotation of the glyph in radians + std::string key = get_azimuth_attr(); + double azimuth = feature[key].to_double(); + azimuth = std::fmod(azimuth, 360.0); + if (azimuth<0) azimuth += 360.0; + azimuth *= (M_PI/180.0); + double angle = atan2(cos(azimuth), sin(azimuth)); + angle += get_angle_offset() * (M_PI/180.0); + angle = std::fmod(angle, 2*M_PI); + if (angle<0) angle += 2*M_PI; + return angle; + } + + double glyph_symbolizer::get_value(Feature const& feature) const + { + std::string key = get_value_attr(); + return feature[key].to_double(); + } +} diff --git a/src/save_map.cpp b/src/save_map.cpp index 526aa7671..f90561f88 100644 --- a/src/save_map.cpp +++ b/src/save_map.cpp @@ -252,10 +252,15 @@ namespace mapnik } } - void operator () ( markers_symbolizer const& ) - { - // FIXME!!!!! - } + void operator () ( markers_symbolizer const& ) + { + // FIXME!!!!! + } + + void operator () ( glyph_symbolizer const& ) + { + // FIXME!!!!! + } private: serialize_symbolizer(); void add_image_attributes(ptree & node, const symbolizer_with_image & sym) diff --git a/tests/data/shp/arrows.dbf b/tests/data/shp/arrows.dbf new file mode 100644 index 0000000000000000000000000000000000000000..64b2c241e6fef0aec77f729f41179c02f359bf8f GIT binary patch literal 293 zcmZRMXP07OU|>jOFa(llAe@1rEHS4v6(Z~hq9piH)g)GB=9ZRZfCRD1^D01qp@E(O iDgcWcT3{DfP%y$SVPuY7TtUGYmjo_x6C4tj7~%jLx+yFG literal 0 HcmV?d00001 diff --git a/tests/data/shp/arrows.shp b/tests/data/shp/arrows.shp new file mode 100644 index 0000000000000000000000000000000000000000..27247e1d4032bfa48cd27671852f63f5362df529 GIT binary patch literal 212 zcmZQzQ0HR64zgY_GcYhB?XaE5!1JnQj literal 0 HcmV?d00001 diff --git a/tests/python_tests/glyph_symbolizer_test.py b/tests/python_tests/glyph_symbolizer_test.py new file mode 100644 index 000000000..eea844faa --- /dev/null +++ b/tests/python_tests/glyph_symbolizer_test.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python + +from nose.tools import * +from utilities import execution_path, save_data + +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_arrows_symbolizer(): + srs = '+init=epsg:32630' + lyr = mapnik2.Layer('arrows') + lyr.datasource = mapnik2.Shapefile( + file = '../data/shp/arrows.shp', + ) + lyr.srs = srs + _map = mapnik2.Map(256,256, srs) + style = mapnik2.Style() + rule = mapnik2.Rule() + rule.filter = mapnik2.Expression("[value] > 0 and [azimuth]>-1") #XXX Need to mention an attribute in the expression + sym = mapnik2.GlyphSymbolizer("DejaVu Sans Condensed", "A") + sym.angle_offset = -90 + sym.min_value = 1 + sym.max_value = 100 + sym.min_size = 1 + sym.max_size = 100 + sym.allow_overlap = True + # 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"), + ]: + 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(mapnik2.Box2d(0,0,8,8)) + + im = mapnik2.Image(_map.width,_map.height) + mapnik2.render(_map, im) + save_data('test_arrows_symbolizer.png', im.tostring('png')) + imdata = im.tostring() + assert len(imdata) > 0 + # we have features with 20 as a value so check that they're colored + assert '\xff\xff\xff\x00' in imdata +