From 2bf9d9dbdff476ca8a1bae77614440e9997febd5 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sat, 3 Mar 2012 01:12:23 +0100 Subject: [PATCH 001/238] Fix handling of empty XML nodes. Closes #1106. --- include/mapnik/ptree_helpers.hpp | 6 ++++++ src/load_map.cpp | 8 ++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/include/mapnik/ptree_helpers.hpp b/include/mapnik/ptree_helpers.hpp index 51708d0f7..3156369f2 100644 --- a/include/mapnik/ptree_helpers.hpp +++ b/include/mapnik/ptree_helpers.hpp @@ -437,6 +437,12 @@ inline boost::optional get_optional(boost::property_tree::ptree const& no return result; } +static inline bool has_child(boost::property_tree::ptree const& node, std::string const& name) +{ + boost::optional str = node.get_optional(name); + return str; +} + } // end of namespace mapnik #endif // MAPNIK_PTREE_HELPERS_HPP diff --git a/src/load_map.cpp b/src/load_map.cpp index a222e12de..ddb076e80 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -841,16 +841,12 @@ void map_parser::parse_rule( feature_type_style & style, ptree const & r ) rule.set_filter(parse_expr(*filter_expr)); } - optional else_filter = - get_opt_child(r, "ElseFilter"); - if (else_filter) + if (has_child(r, "ElseFilter")) { rule.set_else(true); } - optional also_filter = - get_opt_child(r, "AlsoFilter"); - if (also_filter) + if (has_child(r, "AlsoFilter")) { rule.set_also(true); } From 7163806cda23ea73af85fcf9b429e02317780c4f Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Fri, 2 Mar 2012 18:51:19 -0800 Subject: [PATCH 002/238] c++ style --- src/save_map.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/save_map.cpp b/src/save_map.cpp index 9ba3570fe..5a5fd50d4 100644 --- a/src/save_map.cpp +++ b/src/save_map.cpp @@ -54,7 +54,7 @@ public: rule_(r), explicit_defaults_(explicit_defaults) {} - void operator () ( const point_symbolizer & sym ) + void operator () ( point_symbolizer const& sym ) { ptree & sym_node = rule_.push_back( ptree::value_type("PointSymbolizer", ptree()))->second; @@ -77,7 +77,7 @@ public: add_metawriter_attributes(sym_node, sym); } - void operator () ( const line_symbolizer & sym ) + void operator () ( line_symbolizer const& sym ) { ptree & sym_node = rule_.push_back( ptree::value_type("LineSymbolizer", ptree()))->second; @@ -93,7 +93,7 @@ public: } } - void operator () ( const line_pattern_symbolizer & sym ) + void operator () ( line_pattern_symbolizer const& sym ) { ptree & sym_node = rule_.push_back( ptree::value_type("LinePatternSymbolizer", @@ -103,7 +103,7 @@ public: add_metawriter_attributes(sym_node, sym); } - void operator () ( const polygon_symbolizer & sym ) + void operator () ( polygon_symbolizer const& sym ) { ptree & sym_node = rule_.push_back( ptree::value_type("PolygonSymbolizer", ptree()))->second; @@ -128,7 +128,7 @@ public: add_metawriter_attributes(sym_node, sym); } - void operator () ( const polygon_pattern_symbolizer & sym ) + void operator () ( polygon_pattern_symbolizer const& sym ) { ptree & sym_node = rule_.push_back( ptree::value_type("PolygonPatternSymbolizer", @@ -151,7 +151,7 @@ public: add_metawriter_attributes(sym_node, sym); } - void operator () ( const raster_symbolizer & sym ) + void operator () ( raster_symbolizer const& sym ) { ptree & sym_node = rule_.push_back( ptree::value_type("RasterSymbolizer", ptree()))->second; @@ -184,7 +184,7 @@ public: //Note: raster_symbolizer doesn't support metawriters } - void operator () ( const shield_symbolizer & sym ) + void operator () ( shield_symbolizer const& sym ) { ptree & sym_node = rule_.push_back( ptree::value_type("ShieldSymbolizer", @@ -222,7 +222,7 @@ public: } - void operator () ( const text_symbolizer & sym ) + void operator () ( text_symbolizer const& sym ) { ptree & sym_node = rule_.push_back( ptree::value_type("TextSymbolizer", @@ -232,7 +232,7 @@ public: add_metawriter_attributes(sym_node, sym); } - void operator () ( const building_symbolizer & sym ) + void operator () ( building_symbolizer const& sym ) { ptree & sym_node = rule_.push_back( ptree::value_type("BuildingSymbolizer", ptree()))->second; @@ -503,8 +503,8 @@ void serialize_rule( ptree & style_node, const rule & r, bool explicit_defaults) void serialize_style( ptree & map_node, Map::const_style_iterator style_it, bool explicit_defaults ) { - const feature_type_style & style = style_it->second; - const std::string & name = style_it->first; + feature_type_style const& style = style_it->second; + std::string const& name = style_it->first; filter_mode_e filter_mode = style.get_filter_mode(); ptree & style_node = map_node.push_back( @@ -529,8 +529,8 @@ void serialize_style( ptree & map_node, Map::const_style_iterator style_it, bool void serialize_fontset( ptree & map_node, Map::const_fontset_iterator fontset_it ) { - const font_set & fontset = fontset_it->second; - const std::string & name = fontset_it->first; + font_set const& fontset = fontset_it->second; + std::string const& name = fontset_it->first; ptree & fontset_node = map_node.push_back( ptree::value_type("FontSet", ptree()))->second; From c47eb3642ab5765858ffef51e52aea40ca3e1588 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sat, 3 Mar 2012 13:49:28 +0100 Subject: [PATCH 003/238] Reformat test and extend to verify text is centered correctly. --- tests/visual_tests/lines-1.xml | 24 ++-- tests/visual_tests/lines.osm | 198 ++++++++++++++++++--------------- 2 files changed, 119 insertions(+), 103 deletions(-) diff --git a/tests/visual_tests/lines-1.xml b/tests/visual_tests/lines-1.xml index e90947932..7df2294c7 100644 --- a/tests/visual_tests/lines-1.xml +++ b/tests/visual_tests/lines-1.xml @@ -2,19 +2,19 @@ - - My Style - - osm - lines.osm - - + + My Style + + osm + lines.osm + + + + + [name] + + diff --git a/tests/visual_tests/lines.osm b/tests/visual_tests/lines.osm index 0bd8f1bb0..59f01b538 100644 --- a/tests/visual_tests/lines.osm +++ b/tests/visual_tests/lines.osm @@ -1,101 +1,117 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + - - - - - - - - - - - - - - - + + + - + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From f8c784b0a6eb2311ea26f90cdbada4976b3d72ec Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sat, 3 Mar 2012 13:50:26 +0100 Subject: [PATCH 004/238] Correctly center text. --- src/placement_finder.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/placement_finder.cpp b/src/placement_finder.cpp index 2bb6cc00e..d7075d6c1 100644 --- a/src/placement_finder.cpp +++ b/src/placement_finder.cpp @@ -817,8 +817,8 @@ std::auto_ptr placement_finder::get_placement_offset(const if (orientation < 0) { // rotate in place - render_x += cwidth*cosa - (char_height-2)*sina; - render_y -= cwidth*sina + (char_height-2)*cosa; + render_x += cwidth*cosa - char_height*sina; + render_y -= cwidth*sina + char_height*cosa; render_angle += M_PI; } current_placement->add_node(c,render_x - current_placement->center.x, @@ -877,9 +877,8 @@ bool placement_finder::test_placement(const std::auto_ptr if (orientation < 0) { // rotate in place - /* TODO: What's the meaning of -2? */ - x += cwidth*cosa - (string_height_-2)*sina; - y -= cwidth*sina + (string_height_-2)*cosa; + x += cwidth*cosa - string_height_*sina; + y -= cwidth*sina + string_height_*cosa; angle += M_PI; //sin(x+PI) = -sin(x) sina = -sina; From 0ef573c197e7ae1cc9696e89eb268a62fad31559 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sat, 3 Mar 2012 16:30:20 +0100 Subject: [PATCH 005/238] Avoid accidential uploading of test file to OSM server. --- tests/visual_tests/lines.osm | 222 +++++++++++++++++------------------ 1 file changed, 111 insertions(+), 111 deletions(-) diff --git a/tests/visual_tests/lines.osm b/tests/visual_tests/lines.osm index 59f01b538..e04b6fb14 100644 --- a/tests/visual_tests/lines.osm +++ b/tests/visual_tests/lines.osm @@ -1,117 +1,117 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 586cdff6e83f108d8a0a6ef83064173fa64557c5 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sat, 3 Mar 2012 16:32:31 +0100 Subject: [PATCH 006/238] Add new test. --- tests/visual_tests/lines-2.xml | 20 ++++++++++++++++++++ tests/visual_tests/test.py | 1 + 2 files changed, 21 insertions(+) create mode 100644 tests/visual_tests/lines-2.xml diff --git a/tests/visual_tests/lines-2.xml b/tests/visual_tests/lines-2.xml new file mode 100644 index 000000000..1c77a3115 --- /dev/null +++ b/tests/visual_tests/lines-2.xml @@ -0,0 +1,20 @@ + + + + + + My Style + + osm + lines.osm + + + + + + diff --git a/tests/visual_tests/test.py b/tests/visual_tests/test.py index dd068783f..3f38709dc 100755 --- a/tests/visual_tests/test.py +++ b/tests/visual_tests/test.py @@ -12,6 +12,7 @@ files = [ ("list", 800, 600, 400, 300, 250, 200, 150, 100), ("simple", 800, 600, 400, 300, 250, 200, 150, 100), ("lines-1", (800, 800), (600, 600), (400, 400), (200, 200)), + ("lines-2", (800, 800), (600, 600), (400, 400), (200, 200)), ("simple-E", 500), ("simple-NE", 500), ("simple-NW", 500), From 36918e4a8b4b4dbf562554e95bea9906a6b5596f Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sat, 3 Mar 2012 17:41:43 +0100 Subject: [PATCH 007/238] Remove warning. Empty expressions might exist in real applications without being an error. --- src/formatting/text.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/formatting/text.cpp b/src/formatting/text.cpp index fc4e85e83..bfe3e80b2 100644 --- a/src/formatting/text.cpp +++ b/src/formatting/text.cpp @@ -70,10 +70,6 @@ void text_node::apply(char_properties const& p, Feature const& feature, processe } if (text_str.length() > 0) { output.push_back(p, text_str); - } else { -#ifdef MAPNIK_DEBUG - std::cerr << "Warning: Empty expression.\n"; -#endif } } From b4eddaab24b8abcf5db686b3c8cd81bdc69d94d9 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sat, 3 Mar 2012 20:15:24 +0100 Subject: [PATCH 008/238] Move variables from text_placements_info to placement_finder. Reuse placement_finder objects. Pass feature to placement_finder. Refs #1048. --- bindings/python/mapnik_text_placement.cpp | 3 - include/mapnik/placement_finder.hpp | 31 ++++++-- include/mapnik/symbolizer_helpers.hpp | 37 +++++++--- include/mapnik/text_placements/base.hpp | 20 ++---- src/agg/process_shield_symbolizer.cpp | 16 +++-- src/agg/process_text_symbolizer.cpp | 10 +-- src/cairo_renderer.cpp | 19 ++--- src/grid/process_shield_symbolizer.cpp | 12 ++-- src/grid/process_text_symbolizer.cpp | 10 +-- src/metawriter.cpp | 2 + src/metawriter_inmem.cpp | 8 ++- src/placement_finder.cpp | 83 +++++++++++---------- src/symbolizer_helpers.cpp | 87 +++++++++++++---------- 13 files changed, 188 insertions(+), 150 deletions(-) diff --git a/bindings/python/mapnik_text_placement.cpp b/bindings/python/mapnik_text_placement.cpp index 2a566d588..4cba5a31d 100644 --- a/bindings/python/mapnik_text_placement.cpp +++ b/bindings/python/mapnik_text_placement.cpp @@ -431,10 +431,7 @@ void export_text_placement() .def_readwrite("has_dimensions", &text_placement_info::has_dimensions) .def_readwrite("dimensions", &text_placement_info::dimensions) .def_readwrite("collect_extents", &text_placement_info::collect_extents) - .def_readwrite("extents", &text_placement_info::extents) .def_readwrite("additional_boxes", &text_placement_info::additional_boxes) - .def_readwrite("envelopes", &text_placement_info::envelopes) -// .def_readwrite("placements", &text_placement_info::placements) ; register_ptr_to_python >(); diff --git a/include/mapnik/placement_finder.hpp b/include/mapnik/placement_finder.hpp index aa5c555a4..970d2573b 100644 --- a/include/mapnik/placement_finder.hpp +++ b/include/mapnik/placement_finder.hpp @@ -23,8 +23,14 @@ #ifndef MAPNIK_PLACEMENT_FINDER_HPP #define MAPNIK_PLACEMENT_FINDER_HPP +//mapnik #include #include +#include +#include + +//stl +#include namespace mapnik { @@ -33,12 +39,16 @@ class text_placement_info; class string_info; class text_path; + template class placement_finder : boost::noncopyable { public: - placement_finder(text_placement_info &p, string_info &info, DetectorT & detector); - placement_finder(text_placement_info &p, string_info &info, DetectorT & detector, box2d const& extent); + placement_finder(Feature const& feature, + text_placement_info const& placement_info, + string_info const& info, + DetectorT & detector, + box2d const& extent); /** Try place a single label at the given point. */ void find_point_placement(double pos_x, double pos_y, double angle=0.0); @@ -51,9 +61,13 @@ public: template void find_line_placements(T & path); + /** Add placements to detector. */ void update_detector(); - void clear(); + /** Remove old placements. */ + void clear_placements(); + + inline placements_type &get_results() { return placements_; } private: ///Helpers for find_line_placement @@ -94,9 +108,9 @@ private: ///General Internals DetectorT & detector_; box2d const& dimensions_; - string_info &info_; - text_symbolizer_properties &p; - text_placement_info π + string_info const& info_; + text_symbolizer_properties const& p; + text_placement_info const& pi; /** Length of the longest line after linebreaks. * Before find_line_breaks() this is the total length of the string. */ @@ -113,6 +127,11 @@ private: horizontal_alignment_e halign_; std::vector line_breaks_; std::vector > line_sizes_; + std::queue< box2d > envelopes_; + /** Used to return all placements found. */ + placements_type placements_; + /** Bounding box of all texts placed. */ + box2d extents_; }; } diff --git a/include/mapnik/symbolizer_helpers.hpp b/include/mapnik/symbolizer_helpers.hpp index 74c33871e..cbfc234e3 100644 --- a/include/mapnik/symbolizer_helpers.hpp +++ b/include/mapnik/symbolizer_helpers.hpp @@ -35,6 +35,9 @@ namespace mapnik { +typedef boost::ptr_vector placements_type; +template class placement_finder; + /** Helper object that does all the TextSymbolizer placment finding * work except actually rendering the object. */ template @@ -60,11 +63,12 @@ public: dims_(0, 0, width, height), text_(font_manager, scale_factor), angle_(0.0), - placement_valid_(true), - points_on_line_(false) + placement_valid_(false), + points_on_line_(false), + finder_() { initialize_geometries(); - if (!geometries_to_process_.size()) return; //TODO: Test this + if (!geometries_to_process_.size()) return; placement_ = sym_.get_placement_options()->get_placement_info( scale_factor, std::make_pair(width, height), false); //TODO: has_dimensions? Why? When? @@ -76,10 +80,13 @@ public: /** Return next placement. * If no more placements are found returns null pointer. */ - text_placement_info_ptr get_placement(); - text_placement_info_ptr get_point_placement(); - text_placement_info_ptr get_line_placement(); + bool next(); + + /** Get current placement. next() has to be called before! */ + placements_type &placements() const; protected: + bool next_point_placement(); + bool next_line_placement(); bool next_placement(); void initialize_geometries(); void initialize_points(); @@ -97,18 +104,27 @@ protected: //Processing processed_text text_; /* Using list instead of vector, because we delete random elements and need iterators to stay valid. */ + /** Remaining geometries to be processed. */ std::list geometries_to_process_; + /** Geometry currently being processed. */ std::list::iterator geo_itr_; + /** Remaining points to be processed. */ std::list points_; + /** Point currently being processed. */ std::list::iterator point_itr_; + /** Text rotation. */ double angle_; + /** Text + formatting. */ string_info *info_; + /** Did last call to next_placement return true? */ bool placement_valid_; + /** Use point placement. Otherwise line placement is used. */ bool point_placement_; + /** Place text at points on a line instead of following the line (used for ShieldSymbolizer) .*/ bool points_on_line_; - //Output text_placement_info_ptr placement_; + boost::shared_ptr > finder_; }; template @@ -131,13 +147,13 @@ public: init_marker(); } - text_placement_info_ptr get_placement(); + bool next(); pixel_position get_marker_position(text_path const& p); marker &get_marker() const; agg::trans_affine const& get_transform() const; protected: - text_placement_info_ptr get_point_placement(); - text_placement_info_ptr get_line_placement(); + bool next_point_placement(); + bool next_line_placement(); void init_marker(); shield_symbolizer const& sym_; box2d marker_ext_; @@ -166,6 +182,7 @@ protected: using text_symbolizer_helper::placement_valid_; using text_symbolizer_helper::point_placement_; using text_symbolizer_helper::angle_; + using text_symbolizer_helper::finder_; }; } //namespace #endif // SYMBOLIZER_HELPERS_HPP diff --git a/include/mapnik/text_placements/base.hpp b/include/mapnik/text_placements/base.hpp index cbfb02ab6..23f4de606 100644 --- a/include/mapnik/text_placements/base.hpp +++ b/include/mapnik/text_placements/base.hpp @@ -29,9 +29,6 @@ #include #include //TODO: Remove this again after text_placement_info::placements is moved to a better place. -// stl -#include - namespace mapnik { typedef std::pair dimension_type; @@ -70,29 +67,22 @@ public: /** Set scale factor. */ void set_scale_factor(double factor) { scale_factor = factor; } /** Get scale factor. */ - double get_scale_factor() { return scale_factor; } + double get_scale_factor() const { return scale_factor; } /** Get label spacing taking the scale factor into account. */ - double get_actual_label_spacing() { return scale_factor * properties.label_spacing; } + double get_actual_label_spacing() const { return scale_factor * properties.label_spacing; } /** Get minimum distance taking the scale factor into account. */ - double get_actual_minimum_distance() { return scale_factor * properties.minimum_distance; } + double get_actual_minimum_distance() const { return scale_factor * properties.minimum_distance; } /** Get minimum padding taking the scale factor into account. */ - double get_actual_minimum_padding() { return scale_factor * properties.minimum_padding; } + double get_actual_minimum_padding() const { return scale_factor * properties.minimum_padding; } /** Collect a bounding box of all texts placed. */ bool collect_extents; - //Output by placement finder - /** Bounding box of all texts placed. */ - box2d extents; + /** Additional boxes to take into account when finding placement. * Used for finding line placements where multiple placements are returned. * Boxes are relative to starting point of current placement. */ std::vector > additional_boxes; - - /* TODO */ - std::queue< box2d > envelopes; - /** Used to return all placements found. */ - boost::ptr_vector placements; }; typedef boost::shared_ptr text_placement_info_ptr; diff --git a/src/agg/process_shield_symbolizer.cpp b/src/agg/process_shield_symbolizer.cpp index 04e8d6017..31c0c6507 100644 --- a/src/agg/process_shield_symbolizer.cpp +++ b/src/agg/process_shield_symbolizer.cpp @@ -50,15 +50,17 @@ void agg_renderer::process(shield_symbolizer const& sym, text_renderer ren(pixmap_, font_manager_, *(font_manager_.get_stroker())); - text_placement_info_ptr placement; - while ((placement = helper.get_placement())) { - for (unsigned int ii = 0; ii < placement->placements.size(); ++ii) + while (helper.next()) { + placements_type &placements = helper.placements(); + for (unsigned int ii = 0; ii < placements.size(); ++ii) { - pixel_position marker_pos = helper.get_marker_position(placement->placements[ii]); - render_marker(marker_pos, helper.get_marker(), helper.get_transform(), sym.get_opacity()); + render_marker(helper.get_marker_position(placements[ii]), + helper.get_marker(), + helper.get_transform(), + sym.get_opacity()); - ren.prepare_glyphs(&(placement->placements[ii])); - ren.render(placement->placements[ii].center); + ren.prepare_glyphs(&(placements[ii])); + ren.render(placements[ii].center); } } } diff --git a/src/agg/process_text_symbolizer.cpp b/src/agg/process_text_symbolizer.cpp index f88a48c74..e548346ef 100644 --- a/src/agg/process_text_symbolizer.cpp +++ b/src/agg/process_text_symbolizer.cpp @@ -42,12 +42,12 @@ void agg_renderer::process(text_symbolizer const& sym, text_renderer ren(pixmap_, font_manager_, *(font_manager_.get_stroker())); - text_placement_info_ptr placement; - while ((placement = helper.get_placement())) { - for (unsigned int ii = 0; ii < placement->placements.size(); ++ii) + while (helper.next()) { + placements_type &placements = helper.placements(); + for (unsigned int ii = 0; ii < placements.size(); ++ii) { - ren.prepare_glyphs(&(placement->placements[ii])); - ren.render(placement->placements[ii].center); + ren.prepare_glyphs(&(placements[ii])); + ren.render(placements[ii].center); } } } diff --git a/src/cairo_renderer.cpp b/src/cairo_renderer.cpp index 44dd0736a..a89822f78 100644 --- a/src/cairo_renderer.cpp +++ b/src/cairo_renderer.cpp @@ -1058,15 +1058,15 @@ void cairo_renderer_base::start_map_processing(Map const& map) cairo_context context(context_); - text_placement_info_ptr placement; - while ((placement = helper.get_placement())) { - for (unsigned int ii = 0; ii < placement->placements.size(); ++ii) + while (helper.next()) { + placements_type &placements = helper.placements(); + for (unsigned int ii = 0; ii < placements.size(); ++ii) { - pixel_position marker_pos = helper.get_marker_position(placement->placements[ii]); + pixel_position marker_pos = helper.get_marker_position(placements[ii]); render_marker(marker_pos, helper.get_marker(), helper.get_transform(), sym.get_opacity()); - context.add_text(placement->placements[ii], face_manager_, font_manager_); + context.add_text(placements[ii], face_manager_, font_manager_); } } } @@ -1255,11 +1255,12 @@ void cairo_renderer_base::start_map_processing(Map const& map) text_symbolizer_helper, label_collision_detector4> helper(sym, *feature, prj_trans, detector_.extent().width(), detector_.extent().height(), 1.0 /*scale_factor*/, t_, font_manager_, detector_); cairo_context context(context_); - text_placement_info_ptr placement; - while ((placement = helper.get_placement())) { - for (unsigned int ii = 0; ii < placement->placements.size(); ++ii) + + while (helper.next()) { + placements_type &placements = helper.placements(); + for (unsigned int ii = 0; ii < placements.size(); ++ii) { - context.add_text(placement->placements[ii], face_manager_, font_manager_); + context.add_text(placements[ii], face_manager_, font_manager_); } } } diff --git a/src/grid/process_shield_symbolizer.cpp b/src/grid/process_shield_symbolizer.cpp index 005f5ab65..9c85b1344 100644 --- a/src/grid/process_shield_symbolizer.cpp +++ b/src/grid/process_shield_symbolizer.cpp @@ -56,18 +56,18 @@ void grid_renderer::process(shield_symbolizer const& sym, text_renderer ren(pixmap_, font_manager_, *(font_manager_.get_stroker())); text_placement_info_ptr placement; - while ((placement = helper.get_placement())) { + while (helper.next()) { placement_found = true; - for (unsigned int ii = 0; ii < placement->placements.size(); ++ii) + placements_type &placements = helper.placements(); + for (unsigned int ii = 0; ii < placements.size(); ++ii) { - pixel_position marker_pos = helper.get_marker_position(placement->placements[ii]); render_marker(feature, pixmap_.get_resolution(), - marker_pos, + helper.get_marker_position(placements[ii]), helper.get_marker(), helper.get_transform(), sym.get_opacity()); - ren.prepare_glyphs(&(placement->placements[ii])); - ren.render_id(feature->id(),placement->placements[ii].center, 2); + ren.prepare_glyphs(&(placements[ii])); + ren.render_id(feature->id(), placements[ii].center, 2); } } if (placement_found) diff --git a/src/grid/process_text_symbolizer.cpp b/src/grid/process_text_symbolizer.cpp index ac09c935b..0f49a6862 100644 --- a/src/grid/process_text_symbolizer.cpp +++ b/src/grid/process_text_symbolizer.cpp @@ -42,13 +42,13 @@ void grid_renderer::process(text_symbolizer const& sym, text_renderer ren(pixmap_, font_manager_, *(font_manager_.get_stroker())); - text_placement_info_ptr placement; - while ((placement = helper.get_placement())) { + while (helper.next()) { placement_found = true; - for (unsigned int ii = 0; ii < placement->placements.size(); ++ii) + placements_type &placements = helper.placements(); + for (unsigned int ii = 0; ii < placements.size(); ++ii) { - ren.prepare_glyphs(&(placement->placements[ii])); - ren.render_id(feature->id(), placement->placements[ii].center,2); + ren.prepare_glyphs(&(placements[ii])); + ren.render_id(feature->id(), placements[ii].center, 2); } } if (placement_found) pixmap_.add_feature(feature); diff --git a/src/metawriter.cpp b/src/metawriter.cpp index c107021c4..46eb27e50 100644 --- a/src/metawriter.cpp +++ b/src/metawriter.cpp @@ -180,6 +180,7 @@ void metawriter_json_stream::add_text(text_placement_info const& p, CoordTransform const& t, metawriter_properties const& properties) { +#if 0 /* Note: Map coordinate system (and starting_{x,y}) starts in upper left corner and grows towards lower right corner. @@ -266,6 +267,7 @@ void metawriter_json_stream::add_text(text_placement_info const& p, *f_ << "]"; write_properties(feature, properties); } +#endif } void metawriter_json_stream::add_polygon(path_type & path, diff --git a/src/metawriter_inmem.cpp b/src/metawriter_inmem.cpp index a94c5b432..22ed373b8 100644 --- a/src/metawriter_inmem.cpp +++ b/src/metawriter_inmem.cpp @@ -74,13 +74,17 @@ metawriter_inmem::add_text(text_placement_info const& p, face_manager_freetype & /*face*/, Feature const& feature, CoordTransform const& /*t*/, - metawriter_properties const& properties) { - if (p.extents.valid()) { + metawriter_properties const& properties) +{ +#if 0 + if (p.extents.valid()) + { meta_instance inst; inst.properties = intersect_properties(feature, properties); inst.box = p.extents; instances_.push_back(inst); } +#endif } void diff --git a/src/placement_finder.cpp b/src/placement_finder.cpp index d7075d6c1..3b46104dd 100644 --- a/src/placement_finder.cpp +++ b/src/placement_finder.cpp @@ -100,21 +100,26 @@ double get_total_distance(T & shape_path) } template -placement_finder::placement_finder(text_placement_info &placement_info, string_info &info, DetectorT & detector) - : detector_(detector), - dimensions_(detector_.extent()), - info_(info), p(placement_info.properties), pi(placement_info), string_width_(0), string_height_(0), first_line_space_(0), valign_(V_AUTO), halign_(H_AUTO), line_breaks_(), line_sizes_() -{ - placement_info.placements.clear(); //Remove left overs -} - -template -placement_finder::placement_finder(text_placement_info &placement_info, string_info &info, DetectorT & detector, box2d const& extent) +placement_finder::placement_finder(Feature const& feature, + text_placement_info const& placement_info, + string_info const& info, + DetectorT & detector, + box2d const& extent) : detector_(detector), dimensions_(extent), - info_(info), p(placement_info.properties), pi(placement_info), string_width_(0), string_height_(0), first_line_space_(0), valign_(V_AUTO), halign_(H_AUTO), line_breaks_(), line_sizes_() + info_(info), + p(placement_info.properties), + pi(placement_info), + string_width_(0), + string_height_(0), + first_line_space_(0), + valign_(V_AUTO), + halign_(H_AUTO), + line_breaks_(), + line_sizes_() { - placement_info.placements.clear(); //Remove left overs + init_string_size(); + init_alignment(); } template @@ -187,9 +192,6 @@ template void placement_finder::init_string_size() { // Get total string size - string_width_ = 0; - string_height_ = 0; - first_line_space_ = 0; if (!info_.num_characters()) return; //At least one character is required for (unsigned i = 0; i < info_.num_characters(); i++) { @@ -209,9 +211,8 @@ void placement_finder::init_string_size() template void placement_finder::find_line_breaks() { + if (!line_sizes_.empty()) return; bool first_line = true; - line_breaks_.clear(); - line_sizes_.clear(); // check if we need to wrap the string double wrap_at = string_width_ + 1.0; if (p.wrap_width && string_width_ > p.wrap_width) @@ -351,9 +352,7 @@ void placement_finder::adjust_position(text_path *current_placement, template void placement_finder::find_point_placement(double label_x, double label_y, double angle) { - init_string_size(); find_line_breaks(); - init_alignment(); double rad = M_PI * angle/180.0; double cosa = std::cos(rad); @@ -491,13 +490,13 @@ void placement_finder::find_point_placement(double label_x, double la } // since there was no early exit, add the character envelopes to the placements' envelopes - while( !c_envelopes.empty() ) + while (!c_envelopes.empty()) { - pi.envelopes.push( c_envelopes.front() ); + envelopes_.push(c_envelopes.front()); c_envelopes.pop(); } - pi.placements.push_back(current_placement.release()); + placements_.push_back(current_placement.release()); } @@ -505,7 +504,13 @@ template template void placement_finder::find_line_placements(PathT & shape_path) { - init_string_size(); +#ifdef MAPNIK_DEBUG + if (!line_sizes_.empty()) + { + std::cerr << "WARNING: Internal error. Text contains line breaks, but line placement is used. Please file a bug report!\n"; + } +#endif + unsigned cmd; double new_x = 0.0; double new_y = 0.0; @@ -640,7 +645,7 @@ void placement_finder::find_line_placements(PathT & shape_path) if (status) //We have successfully placed one { - pi.placements.push_back(current_placement.release()); + placements_.push_back(current_placement.release()); update_detector(); //Totally break out of the loops @@ -650,8 +655,8 @@ void placement_finder::find_line_placements(PathT & shape_path) else { //If we've failed to place, remove all the envelopes we've added up - while (!pi.envelopes.empty()) - pi.envelopes.pop(); + while (!envelopes_.empty()) + envelopes_.pop(); } //Don't need to loop twice when diff = 0 @@ -929,7 +934,7 @@ bool placement_finder::test_placement(const std::auto_ptr break; } } - pi.envelopes.push(e); + envelopes_.push(e); } current_placement->rewind(); @@ -987,34 +992,26 @@ void placement_finder::find_line_circle_intersection( template void placement_finder::update_detector() { - bool first = true; - // add the bboxes to the detector and remove from the placement - while (!pi.envelopes.empty()) + while (!envelopes_.empty()) { - box2d e = pi.envelopes.front(); + box2d e = envelopes_.front(); detector_.insert(e, info_.get_string()); - pi.envelopes.pop(); + envelopes_.pop(); + extents_.init(0,0,0,0); if (pi.collect_extents) { - if(first) - { - first = false; - pi.extents = e; - } - else - { - pi.extents.expand_to_include(e); - } + extents_.expand_to_include(e); } } } template -void placement_finder::clear() +void placement_finder::clear_placements() { - detector_.clear(); + placements_.clear(); + while (!envelopes_.empty()) envelopes_.pop(); } typedef coord_transform2 PathType; diff --git a/src/symbolizer_helpers.cpp b/src/symbolizer_helpers.cpp index 4be456195..b43be696a 100644 --- a/src/symbolizer_helpers.cpp +++ b/src/symbolizer_helpers.cpp @@ -28,85 +28,84 @@ namespace mapnik { template -text_placement_info_ptr text_symbolizer_helper::get_placement() +bool text_symbolizer_helper::next() { - if (!placement_valid_) return text_placement_info_ptr(); + if (!placement_valid_) return false; if (point_placement_) - return get_point_placement(); + return next_point_placement(); else - return get_line_placement(); + return next_line_placement(); } template -text_placement_info_ptr text_symbolizer_helper::get_line_placement() +bool text_symbolizer_helper::next_line_placement() { while (!geometries_to_process_.empty()) { if (geo_itr_ == geometries_to_process_.end()) { //Just processed the last geometry. Try next placement. - if (!next_placement()) return text_placement_info_ptr(); //No more placements + if (!next_placement()) return false; //No more placements //Start again from begin of list geo_itr_ = geometries_to_process_.begin(); continue; //Reexecute size check } - //TODO: Avoid calling constructor repeatedly - placement_finder finder(*placement_, *info_, detector_, dims_); - typedef coord_transform2 path_type; + typedef coord_transform2 path_type; path_type path(t_, **geo_itr_, prj_trans_); + finder_->clear_placements(); if (points_on_line_) { - finder.find_point_placements(path); + finder_->find_point_placements(path); } else { - finder.find_line_placements(path); + finder_->find_line_placements(path); } - if (!placement_->placements.empty()) + if (!finder_->get_results().empty()) { //Found a placement if (points_on_line_) { - finder.update_detector(); + finder_->update_detector(); } geo_itr_ = geometries_to_process_.erase(geo_itr_); if (writer_.first) writer_.first->add_text( *placement_, font_manager_, feature_, t_, writer_.second); - return placement_; + return true; } //No placement for this geometry. Keep it in geometries_to_process_ for next try. geo_itr_++; } - return text_placement_info_ptr(); + return false; } template -text_placement_info_ptr text_symbolizer_helper::get_point_placement() +bool text_symbolizer_helper::next_point_placement() { while (!points_.empty()) { if (point_itr_ == points_.end()) { //Just processed the last point. Try next placement. - if (!next_placement()) return text_placement_info_ptr(); //No more placements + if (!next_placement()) return false; //No more placements //Start again from begin of list point_itr_ = points_.begin(); continue; //Reexecute size check } - placement_finder finder(*placement_, *info_, detector_, dims_); - finder.find_point_placement(point_itr_->first, point_itr_->second, angle_); - if (!placement_->placements.empty()) + finder_->clear_placements(); + finder_->find_point_placement(point_itr_->first, point_itr_->second, angle_); + if (!finder_->get_results().empty()) { //Found a placement point_itr_ = points_.erase(point_itr_); if (writer_.first) writer_.first->add_text( *placement_, font_manager_, feature_, t_, writer_.second); - finder.update_detector(); - return placement_; + finder_->update_detector(); + return true; } //No placement for this point. Keep it in points_ for next try. point_itr_++; } - return text_placement_info_ptr(); + return false; } struct largest_bbox_first @@ -227,25 +226,34 @@ bool text_symbolizer_helper::next_placement() } else { angle_ = 0.0; } + finder_ = boost::shared_ptr >(new placement_finder(feature_, *placement_, *info_, detector_, dims_)); +// boost::make_shared >(feature_, *placement_, *info_, detector_, dims_); + placement_valid_ = true; return true; } +template +placements_type &text_symbolizer_helper::placements() const +{ + return finder_->get_results(); +} + /*****************************************************************************/ template -text_placement_info_ptr shield_symbolizer_helper::get_placement() +bool shield_symbolizer_helper::next() { - if (!placement_valid_ || !marker_) return text_placement_info_ptr(); + if (!placement_valid_ || !marker_) return false; if (point_placement_) - return get_point_placement(); + return next_point_placement(); else - return get_line_placement(); + return next_line_placement(); } template -text_placement_info_ptr shield_symbolizer_helper::get_point_placement() +bool shield_symbolizer_helper::next_point_placement() { position const& shield_pos = sym_.get_shield_displacement(); while (!points_.empty()) @@ -253,7 +261,7 @@ text_placement_info_ptr shield_symbolizer_helper::get_p if (point_itr_ == points_.end()) { //Just processed the last point. Try next placement. - if (!next_placement()) return text_placement_info_ptr(); //No more placements + if (!next_placement()) return false; //No more placements //Start again from begin of list point_itr_ = points_.begin(); continue; //Reexecute size check @@ -262,9 +270,9 @@ text_placement_info_ptr shield_symbolizer_helper::get_p double label_x = point_itr_->first + shield_pos.first; double label_y = point_itr_->second + shield_pos.second; - placement_finder finder(*placement_, *info_, detector_, dims_); - finder.find_point_placement(label_x, label_y, angle_); - if (placement_->placements.empty()) + finder_->clear_placements(); + finder_->find_point_placement(label_x, label_y, angle_); + if (finder_->get_results().empty()) { //No placement for this point. Keep it in points_ for next try. point_itr_++; @@ -276,8 +284,9 @@ text_placement_info_ptr shield_symbolizer_helper::get_p { // center image at text center position // remove displacement from image label - double lx = placement_->placements[0].center.x - text_disp.first; - double ly = placement_->placements[0].center.y - text_disp.second; + placements_type const& p = finder_->get_results(); + double lx = p[0].center.x - text_disp.first; + double ly = p[0].center.y - text_disp.second; marker_x_ = lx - 0.5 * marker_w_; marker_y_ = ly - 0.5 * marker_h_; marker_ext_.re_center(lx, ly); @@ -292,23 +301,23 @@ text_placement_info_ptr shield_symbolizer_helper::get_p if (placement_->properties.allow_overlap || detector_.has_placement(marker_ext_)) { detector_.insert(marker_ext_); - finder.update_detector(); + finder_->update_detector(); if (writer_.first) { writer_.first->add_box(marker_ext_, feature_, t_, writer_.second); writer_.first->add_text(*placement_, font_manager_, feature_, t_, writer_.second); } point_itr_ = points_.erase(point_itr_); - return placement_; + return true; } //No placement found. Try again point_itr_++; } - return text_placement_info_ptr(); + return false; } template -text_placement_info_ptr shield_symbolizer_helper::get_line_placement() +bool shield_symbolizer_helper::next_line_placement() { position const& pos = placement_->properties.displacement; //Markers are automatically centered @@ -317,7 +326,7 @@ text_placement_info_ptr shield_symbolizer_helper::get_l -0.5 * marker_ext_.height() - pos.second, 0.5 * marker_ext_.width() - pos.first, 0.5 * marker_ext_.height() - pos.second)); - return text_symbolizer_helper::get_line_placement(); + return text_symbolizer_helper::next_line_placement(); } From 0eece15f622b7e4ed1be938ae6ccaa1f8ecc187c Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 4 Mar 2012 02:04:43 +0100 Subject: [PATCH 009/238] New tests. --- tests/visual_tests/lines-3.xml | 20 ++++++++++++++++++++ tests/visual_tests/lines-shield.xml | 20 ++++++++++++++++++++ tests/visual_tests/test.py | 2 ++ 3 files changed, 42 insertions(+) create mode 100644 tests/visual_tests/lines-3.xml create mode 100644 tests/visual_tests/lines-shield.xml diff --git a/tests/visual_tests/lines-3.xml b/tests/visual_tests/lines-3.xml new file mode 100644 index 000000000..ef37dc51c --- /dev/null +++ b/tests/visual_tests/lines-3.xml @@ -0,0 +1,20 @@ + + + + + + My Style + + osm + lines.osm + + + + + + diff --git a/tests/visual_tests/lines-shield.xml b/tests/visual_tests/lines-shield.xml new file mode 100644 index 000000000..05a0f4fd2 --- /dev/null +++ b/tests/visual_tests/lines-shield.xml @@ -0,0 +1,20 @@ + + + + + + My Style + + osm + lines.osm + + + + + + diff --git a/tests/visual_tests/test.py b/tests/visual_tests/test.py index 3f38709dc..b7509fd73 100755 --- a/tests/visual_tests/test.py +++ b/tests/visual_tests/test.py @@ -13,6 +13,8 @@ files = [ ("simple", 800, 600, 400, 300, 250, 200, 150, 100), ("lines-1", (800, 800), (600, 600), (400, 400), (200, 200)), ("lines-2", (800, 800), (600, 600), (400, 400), (200, 200)), + ("lines-3", (800, 800), (600, 600), (400, 400), (200, 200)), + ("lines-shield", (800, 800), (600, 600), (400, 400), (200, 200)), ("simple-E", 500), ("simple-NE", 500), ("simple-NW", 500), From b5e1ebfac89d127f725d66894083031b897b8d15 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 4 Mar 2012 02:06:03 +0100 Subject: [PATCH 010/238] New reference images. --- tests/visual_tests/lines-1-200-reference.png | Bin 0 -> 3563 bytes tests/visual_tests/lines-1-400-reference.png | Bin 0 -> 11772 bytes tests/visual_tests/lines-1-600-reference.png | Bin 0 -> 24196 bytes tests/visual_tests/lines-1-800-reference.png | Bin 0 -> 28276 bytes tests/visual_tests/lines-2-200-reference.png | Bin 0 -> 4058 bytes tests/visual_tests/lines-2-400-reference.png | Bin 0 -> 9600 bytes tests/visual_tests/lines-2-600-reference.png | Bin 0 -> 17692 bytes tests/visual_tests/lines-2-800-reference.png | Bin 0 -> 35810 bytes tests/visual_tests/lines-3-200-reference.png | Bin 0 -> 3563 bytes tests/visual_tests/lines-3-400-reference.png | Bin 0 -> 11792 bytes tests/visual_tests/lines-3-600-reference.png | Bin 0 -> 24512 bytes tests/visual_tests/lines-3-800-reference.png | Bin 0 -> 32246 bytes .../visual_tests/lines-shield-200-reference.png | Bin 0 -> 4337 bytes .../visual_tests/lines-shield-400-reference.png | Bin 0 -> 12719 bytes .../visual_tests/lines-shield-600-reference.png | Bin 0 -> 15244 bytes .../visual_tests/lines-shield-800-reference.png | Bin 0 -> 27052 bytes 16 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/visual_tests/lines-1-200-reference.png create mode 100644 tests/visual_tests/lines-1-400-reference.png create mode 100644 tests/visual_tests/lines-1-600-reference.png create mode 100644 tests/visual_tests/lines-1-800-reference.png create mode 100644 tests/visual_tests/lines-2-200-reference.png create mode 100644 tests/visual_tests/lines-2-400-reference.png create mode 100644 tests/visual_tests/lines-2-600-reference.png create mode 100644 tests/visual_tests/lines-2-800-reference.png create mode 100644 tests/visual_tests/lines-3-200-reference.png create mode 100644 tests/visual_tests/lines-3-400-reference.png create mode 100644 tests/visual_tests/lines-3-600-reference.png create mode 100644 tests/visual_tests/lines-3-800-reference.png create mode 100644 tests/visual_tests/lines-shield-200-reference.png create mode 100644 tests/visual_tests/lines-shield-400-reference.png create mode 100644 tests/visual_tests/lines-shield-600-reference.png create mode 100644 tests/visual_tests/lines-shield-800-reference.png diff --git a/tests/visual_tests/lines-1-200-reference.png b/tests/visual_tests/lines-1-200-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..ce5e0ab6a9705e1908839513d4e65bba3bddea99 GIT binary patch literal 3563 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 P;>4Wn-QeV3uK)R8EVP-q literal 0 HcmV?d00001 diff --git a/tests/visual_tests/lines-1-400-reference.png b/tests/visual_tests/lines-1-400-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..2bf5672937049984d11df89531bddc4212e139a4 GIT binary patch literal 11772 zcmeHtXH=8lvo0D%rHBpbP3cH)0#XzRAV_agLKEqr^Z)@P0@6c`bTM><&_hR&UJWI5 zqL9#wh8_r<{LVce?)max=ff>)-FrS{t-bQj?0IJPe)r6N=8e|Z)u5$frlO#rpw-e; zGo+xn1pDWAos#^|yAGKl3JT#LT53;?1GBetZZuAIE%ZdGKTTCvxqrL*^XK;^AHRHw z_~KVVj1IOgX-RLMd|uxmGHF`wmmkx(^xn<7rOfsyKQ&pOxK@%jNPREZFmpt>Qsx@twrm`4)&YAqTe&Y6ng>h0JXFXH4~7 zYl%QhmPzT^>5>#Q1p*neZZYi}5$D*5KEA_$_e0E=nOd@fiM!m%cNP8n6|%E~B}r&{ zAR->fXzeyJ>>MNNAx=|}gTBh6FLIq;o$kZPGucZb{;5mGMbDZl2P+xIAO~bPF z35!6Ev~TC?JR&q#(rVjH7q^969_1N;!ZeHON`a)ucn8P#coF}8MG_DXu&@JQGK94_^<9I*anzLV;%9&fb47B1^PVCJPq z;M|6Pmf0UcO`Q$S(+);Mw)*`1kGT7vK25aoRD~Jbpawjxf9j3BPzEvx?#5X%>&iXM zThV?MWV9O)j6_NvjgOWo*e4zvSP(E=GH};4eQ7JSgnkDQIG_OSEjUfbHAYL}A0naMXUi^23#0}B1MZft2ANB*-zwVAb;mWaW4zoHI% z-wp#kymW5j$ylUIl{z24*Y#cH)TyvHDLLmR39W{M4RwQ>tSPl!?7ZwuV4j>AQkty% z^GeM_Y1i^4@mopr=_%i@b<_{)hy``6@muCMWg5=f%*}E)Oxx{fn3r(avRei>LW0{Sfh;E7 z55!5OCPEOi7XRj`ml}e@AYSCJW?tV7U~x^5!%wXF^OYrj23mbjKS(CJz`pnNr)stIfnY=SS=TF0LueX0 z^G*rp%sr_%g}8IW%WoaWt$|^L7aYZ7S?rXC85^I)VEk0A;V zR-Gy^8@79thBS7Z>-=A!Ph2L(osAF}U@zOW4RjwemA+z+b8P^F{31O9U4%i)Guk z&dNmgo#iaso0baJYs{09nd^g_o>@~~@gRNB^2GO@`BjB+|P9a|xm@UG)j^f~Y# ze&u*9UVOH1?ZQ3#3uH?Ml5V$9>oNZjvUey`<|g%1R!`>zb7{SQ(7Q6vdAx0Ur0UR~ zc)8hwbQ}4TsoO{A%&+(4=XQ`>{$W}Vv&(-|w3+>AE zbZpCL|KOzULvFQN#xpbJEL$Erwsg>;#Gg1!^m_i_d%2W&$E?yLWI7%3NMW{TM#NsO z**(~5PHeCw0j&z1UPR3uI42y>@$XLGd&pea3bahfnF?9&oS3th43tw}RPV7H=W;bS6tY|g7zDK~JPn?^Fxs=J87!z|PNC|pV)pXA76X8^@#i~PeAkws(AKc3YU@--5}WbC_x7Pm?UCc=#jL?2bs9&t^GgLpr~XI-?vd!mqUPP*PR_V;;AEk^ zG`S_XIq5Z*pLXRl;_(2#P8%)xok=|LMO78ewDxLDUnG5i@{_7b+appP{Y=JTHas&* zFET5EPSY2w)2-)hnza^Sr-yGDHBnN7T7W!xp)3~%*x+&LrRdVF3`bJ&2>~v2TTZ`t z?(9DQ?jsimJlp3|$M^I~Sx}r{hYkVxaD*hSSE$^QdiRo?^AD*IJJkAJL_RRg6r}ig zu`>91dTVHvOXba>j;RrNNkQH!_%{SBk(AeJY&oveFbd?{GgylM88I36Wdn+ieOee*ICQ&YR{8`J^!vTd&Ex!la#f$v|3U{e$uesrKx(6Ne;$(;q?0H&^OcFbj(fa2G=1n_=J$9U5zRN^XdqkXo!1N|7*LIDZ z1j`%R!8X(2FV`-whtr?Sa zX}8~CnHTIG;(w^r#lI|a>>AU-o}Zg#lJKK*(v`k={v5P-#|1lnAvnwuOu9*H{F{b* z$3wHU!@50I&8q)1+=| z>G%m-+wSqaIB6xI6vuasFsB9X5mT-@b2(Jec_Av^F-ULjlchxOl}ex%Y)oi8nzF66 z(rD?x9sPZ6F|r{#c-w5+?}-z^Z70@*$P+o|o$KtN3!k#cdae0+h@*Osfw)2^eSxCd z@@kSM=%kajna!F#tQ^M%Keox2HPh|fl+?37Zc~+uT=-`P6pFu=>vz*nXf%4xG(q1k zFR%$$xVZ`W8&!(>-pqdZ>0$)O6~HEg>dmnt>C_y|+VB8G*-@M#dy0l%HDTN?5ib8H*6oR5j2o05cInu|Bw1>W(enz{z4mQp6$o8y>vv#0??RV(_j>y{a4LtH6Lf!cd5Y`{v4M z92OX@)^pxm)S{06 z_uA)a&E>@=A1BCKepDo=$BXk9C8d3mWNgsE#(E?z%Z6~nbZ3jZ`+`LxlO*r@Pfpt1 zj*HNE(qx&5kYdpOT7hc#gWA>H;EfkDGitJK7#+PlCB=PjM$L0uPNnomFTY>|muVGU zC-@cZIs@zJGLlG?nGmUTRipmEo%iqwr2X>ZBG`Yc~>myQD@UZXy}@%LXU_ zmnbOs#BPzvm&^YbhD)-rG@<&?zv{+N3vt)fc8^~wq`y^mF2&jgb`Dk&_G%{0O>|1; zReqetZ{k@P+C*jx5DcEshdFpDa{Qcaw@bTkK5RS zv?NT*5i;$@iGwE^2mzo?cU?zPxEO4}h!Ftpbq>zbHJ)1Y#pl=!?n-W*DZ~d6$Lh^M zKbA+`z8bKc2D2e@;?G@*SDm7{ri8y5KYY^VOlL#0?S6LNswBCws{L>S(MriXPUIQA z8#0b%@pv`y%){9p5}%-XcRB7G(U6%*{Ll*jAoEq7s%=<~Yh7plSF0nlRKG1Z2c5#1 ziM16e@WShfA8=0%#8sdkv&T->MGNDhtoJQSL-2qj=a@>p)JA1%ZM!2-HGYGIw$8;c z-hoUn^gYP(w?r&J!2LC2zs~2KE79IrQ?CV;07;RrK9WcPgb1*ONH{@{%=J6JC+PfP zQ}Eh-wAnFKQtw3sBY*>L$GoQmPuqQ@lz3(Rx*_niaB|>!b(8?S8(0z7EU+7A6}RJw z&sdnbJt&w*qIb-yGzN9R7_%pG+_q{)DGA9$KO~{@osAg?Ywd-w-fB~ct%O_ ztRJApx!z2DFAk560DE|H;R$Rngk<@c0cT$Q{jquQ8)4BeahK5_3AH zb+Jyx%2Wlx{Udp)b|ld`ta8;j0~1?WtK<^PR;#$o@IHq(bd zdwf-U$f23i9Cvbgddg++Rpg_Qn!VoBm40D0@rpshJht!YKU)kAW%Zhn89poC@DpTD_0ei{dUiE5D6{><}XWZnAmvkz5;$|-m{`^M(% zcwTV4^8K76%dc;0K+A2mWjCPCydXSQaGF@ABuUru4~$d~;zara*FcN?!f;6j!Sfn% zFNhlkxAN$tx2+mgt8AylHT}oF69Js08B9s-+0+mAS7fI`<7f)Ba`1)B)N(%CxxC0I z;}FU_to^AmJ~27x%6Mu1sMkNkYP)`pruR+_Y|_EXZ#QkgEMJzVPW{RG#IsU@sCrLc zIEI;a5tlKNo(c9o)xQIp_8T(Dec88NV3`(h=K+M-`|6m2253rPH{I^dg2L&cs?sTo zI{BLT&n_z#*rl-H_>||Wh70vJ2Qa-NmmpTn&*?{1aHa|KKj;!ls1O}ONbzL9h8o8Q zq|0L;6%nnqzfWtsT2<&5RYq#WDUYTQG7BHBZw@+zMOa7Scu?7p2bloW11IzGG&335(BzexSitn$SEXhtHwLF zOZh=LGKa4NlFqZTyS4vQrTr~)-?Be3L#5e95P3Jq@xuYWwxhFBb;G(vrD@O>Ain%R zSu3Pb1D0mz0PSyj%S?Bz0r3l@n}LhE7d_{qq`MOC<~o8m0sT&4h>v8V=H$?_;t0#p zUE+7TU3}!W_yke3(^L$aFHSt)Hnm(oq-9>?C1 zW4dAOyX_|9%fW~9woON5f+tfsc(*Mq6NPc#tKf~a9`^4$tXl|lSZHxEf{+^KHl%sm z-SK|wGIPdwzByK_UfS0u?&7iB3D}I9Cnf5!c>|B0Z#505*L4k@<}BF64<8LVNVyhdVUWlGuQ9d6Sw{wlUvC1j^KiR%<=|6k__Zu=n#$8H z|LuLhc1Y6bIqp8%e%HWs@$>RZn~nn{{8%Y!8eu9l(LI~2+z3&50i)7vq75`2QOq7Qhm)~mkt@Y zuToxSdf66K5306TAG1@@oVL+-|6i1aAA|z5h$->cxju{u?W`!Bvub~a-FkL?(s+It zaweCq!o~IBR#|Cd*6Fd&Bj~ed*SU<1-pU^RT{XWP@!{69lA{$*X}<)mL&G)c#Kc{uhzSUA7J&M^jL}Tj%T_FaE1; z{b#@RPhZvoHyJ=biLxDNVibqWnMc?4iq&Cl-b#sUfJ^UY`-uk@dbwW@)EQhBie4O0 zU*!mGlpp@0ZsFBjiaFSHQ?z}D887C$?1z*$%0%G~zoI8s5ZA5(h=wEME#+ywaU2PQ)NI zg3Tmg_i4CqU@FFmlV#Cy z@4v@(T5Bb>}}g>dw#h za7&{1I6|tvH?-T=)p2(z!F-iV&S<#don0;SYbl_cUHk)u8 z5$x>;N`RS}60?sUx=eivKx>cXg>P%_h)HPOD3f1xGxoq=5F5rvN5T_)8{pjZcAClSO)Swyer}G~2mUnlqnd8(Vhc2ealIdH2?_-I`J~ zROWEK;nAy^YewsWXq9sU9G8eJxpOyZ4o!cV^3GKz>%CYT9J^EQ2l)UDaWS|a)FiQP zNEJ1z(k`=sC_~nf^lYrJJ1mlymhBLut z+=b7X0e`ib{nzipEDcKMEHZ$0w8oPVdN+5(>8jMai2mZO-|paU5AxjBZg7{v*3e|% z^%+*m2SHsjm80s;F!gwo6(N|Z96_)F`Z1F+C&>+x_KK76Jo)iP#}k+kqH!@) z;m9_{WxP%af3IS5GS+E9q7`+S48O`2ied3cZRKjz1ld`~9Mj;kXsT^oSa5%Dv*5zh zrmih<+;1hf17Z>elBRO}bL=9|dmK46guEsum)tkY?*Wp&b6PhyP6dDq```AuD7vN| zm8Fz~5KCo~1+i!wOAmZoO-Xt<^uyEK`=Z7GQ7@VW|GBkY!j1B~dOFSfW6P4Tg*c*+ zS3z=IX!6M3V5+QZSrox@Pt65}ty(Xz`7Mnwi$APBc`=X#e?r60`=%NF19&lF*zlIA zj|Oio-T!8#I8I57*$uADmsV*=gIg~~OucQYpGW3ljXx*FJ?B)w78tK>6AX5&`(slPH1lKcIZ`}NpR^+4rUag zNglInHkprErYg#f9#&^&U5vWV8i$RSj_a-OkST_Mq+-Ih?{l+y=ls)tf8vTFwlZh& zd%+7lD&^$DQBG(m3GS>}ZW<)&ur`-bKEftha9eSm%{Kwbhzw- zv6nluYP1>A)!*Z%3RZ~B{|)ygY3){bK15~sV-Is_@M$`;u z(eI$RXYz|n&V7YOmWzG|9T$!rb!pgKi0P){QQW8+dr(OrK8 zJ5!ow-HrAd)*5W4Y9twin}MQ1D7@c;?m#{=MPjkrU|Bbf`~8a}>`{pOjrY)3s-W`R zQ5NMwEUxci+aRH2)TwV~+70Of`SbmRrSHOE`E(9C?;)O!rd5+?U>?aCQSozZ1~lZA(eW3!&nSc=&;AhSmX)DYkpALa?^>>x`t>;1e16*{>A|R_X zbL)1v{w2NMXBnKmaf7PO%RM|SsO%5@{pP8k@UM_o%IE1-d4WQlJ}+q2YXFSJO0uZ1_1uX~( zq~!lKSc2yt;R)S8VYZwCD?`!e1*kadqmEnRHv;AF572`>f+kWp!1is%Z%$m7BuBZ~ z{JxR}Lr>?{(tfWYQW9pv$<|rHt2t}E^H&+jkEKO8+MjQnq+NzWuTwKLLlRYQzBBw4 z&G)4j6q}b2A@NlH2O{(dxmP@PJWw?Me9kUvt#^SffSV0Wp7_1*dksk1Gl4c{HTiCQ zJ;q=O*-eff)7$NOG|TUcbJ_Khwvn@vx3MRaHJ2VSYQ~TbV@;WG5s_F3+;?I!+uvfJ zD#!PN_8VWG8WJTj&7*}IC^bW_y}cmq_V-v*h{5jBJ-OBPY(Cg)%)rbCpN3xU*eEMN zQ2q?ULJ*UdC!muW$NuiqjdFCNG?S9ebG~eGD6{}ZB|tCvyl`!tZ@okjS2OLX(-v}a zi_>MNT_I>8ZB&`B8+r|x81VBmZb_Jv@gv8Hv(ZG1xEE;5z)kU$=6)l7ZnIFTh+U8Y zT&Oj{Ah^>JCQ+@M%Stj&nOmse5BFsJn75jj2$D-~M+!VNY?vu(9r;D^p4HE|jwlok zN)7~cz3o+3#<>2zyCxuZOIbv(cR;_L8#}z$_Yf`QdwSS?@`ql<92oZzl<3u3-6%ei zNl&tK&=MV-G!%nzlErm1Q+2-DTPbYcQ(XKWo)XQ#dAHouWuX;Z7c}1%{MNzu*I%TWPlRavVtijDW$2wEtYXGgN!SQN@27@dRt~cvR zuGC}i)643`g##)`u8AZTFFM0NU9H?G3Ma1>Nf3dr;3x!_7v?Gsc26n9#8wQZqt>Q4 zQIdqghS_9KD;?i$6b(moM>O5wV#WD)c}d=u+Wh z-;FgUvJ@XMIR)Ct#RXd*Ve@=%+^41CW--W1IM9|~c!dM9BE`sOu2n$J5%D(M9M>+U z()4Fo_OCVVmC1$*44eEtUN5w%C3v*X_&TGej8$#|?eoR(hjg6k5N5yrJG&kokkb{8 zA6?NA0vzw2y`8p;aKllTT6$_V<6@{NlWEDGN@{^zIQ0r&2Ulrgj^=$VIDF!qp1l2P zCV%(R04*Hp&MWL96eA5QQv0Uj{hPevBK-iM1kj2orRE(W_U7|ZYT*24auPoUK62m0 z$|oKpY{~0IW5@F&REUO#n}NK1k>vh^g%`^DHv#K66~h>Nv;Gr)uub>i*`Z|@7gLWY zM3$bCGMRd+*g^ZLH_K<^q$EMYio-qKJV^RJyY_u>@ILusL9Tz#-s0gQA9ULv8eh`O zDFy&;s4D68^74S+%ZyJS8QQM>#0{2Y@2?k=bMBZ*EAlZ*$qJkt>7l zWg5TWI>i7Ln0@&@b?a%4mMs6wL{a?F2M>X8sM=l1WF~N7OKYsi2B5*N4xesu@tdwY z*drt(Kl$e8M@b_ROmC~nwXN)<(EgM?(y1G1)=dkp?w9R-H1=XgY;pTWXbTlmMwoqwZ6Cc}pY~cB_f75K>D$Nyuz&#sC;Q=1*w(#}k zF(IuS9!`fLqBTx_SzHl=J%(MQh@y=>=PC=CXsVUf1&uTh-2O*NN*f-8eF2|>zY?5U z@E<2)EZahLKhjX%;gfiV%W$e&bV%%!dLWU#*Sc31db&*rYX<ej$`0(` zLGNX1rPL`Mn(tbh+p$nHIV~ddj;eC3BBy|2w`i^mu+tQn_rWnE@4+cw<2JE#tH(^gT{!O$9unqump)f#JzDJWGWMUjLcbXGO();y-DS;Ra9 zNtLh-HH%7+O=^e;4MGU=rhEVI=ea+JFbzN(n*LkjMML#q* zI(ba&*ntBFPCmGAXm#Mg@Akj`jvnFt2lpgx^1uNXlLv;kY(lb@r})w)JGQ%+xulKh z+y1}Z7R-J91R!gOW4DS~geePHCK7PN__WOYYM`I81 z9$maHq`>>am;ZV2KPLGfXZU|U4CFQ2#5DRye3DW}^WMxhH?P*;sc5aIQZ~H&T?5|e zMGojOiknyDT5}HFI@LsdBa)Fbo0RG1!{>M6)+xnPTxiwn7=Gt-lIzV{N-F&)BiMf) zAyUmm4m$5*4wGJAe0M>Jj-hOv?z(|||3J$A(2x-GQ?yW-I!O2LS#|E}A4>J+vE#9a z=mG|D!H%CmM_(>py7ltzDML}FtcW$1UC|lReF#z>lj_{E;2!>lsor0vb0Pd!pn<2@614bZh2A#q>zA@u;!R1(*<+~rSV*b*p{7$nrvr!AK`(h=hP2( z_z1#e_^}~&Ighpdg^*XG8u=)XoJXtn+e8tld((!|3sp)x>_>@>?66WLi)Oa%Y#8=( z(cwv{f6Cl0hUgk1Z8W_>`bwu`;z4? ztnT-#W=T@niE?zw5@rO$Jy3Y+LTpqkqpS5Bw>2udf>_}HDrdP!rSNcy%B_v1e@5Lm zXe&N-nLJ<^{pZJbW2OsdK5OwKSW(WQGCSmH zY-wQ!#8+54!FUsTnEuJe;guDI{GBjd^2lSiae;#uQ5#bHYu&o4NOF~R*1rDGP}x7D zvg4Pw#j55Xn$b5XAGUs7l)0GVw%g#x@lCXxpV;T1Y_664(>}D<|F%eGQ{#8T1}T3P zOMDFyl2ou-o+P%JFS)hcu`5T!oYMW{!6|7z{;sE$7}>7s9%*uv_5^Ch8jkKJ9|pO#(5nAnR)^P>L^f~pTO zZ=BDs%z}ksVw5q%?CCN5)s@fLnz=)Xku{BJU4#OqilC~N=p9`>%Y22_HV_7CKSW^4 zEM3(vz1_M_G{V@Y4RXUUG5Kam%Vv#&TLUpS0t&N>U7(a2u z!l`+abm&&nc?;#{@+q%=ZN{**nWtCAV<8<@hcNkWQQFrFssp;4y<>J9pO&r^ht3M^ zy$yH>J_3Y`To*F=4ttW8+glk7bnNz1=~aNwKai~(!LAXXQNngL@g)u^v;08Re$6+c zWnt$7;w4c8eSI$jLV7iyL+&4Iz+_KpzQi`krnXcgPBD@kJ&FA z5qk0eTiBt;qB35)lZ4+-_D&2zrh=9^VJi-ydSF6*jN%jjDd@=i$OiWxCDg`t!G=u5 z+CN*pVid(RPUf*@TlF>t+?l( zEjB`@eI+n$_x%X>#{*7^j2Con54{NpfPeBi#rR`@Gq)dde6#?RHx1K+G+zn5AA5dc zbffbI4D4{fk|ak`ZwnnDq$)ndfIn1F4VBEz5W{pJ_upx!F9agbg16>zIuIz5%D=ed+mzilX zZ7|}DlSo68);EHul75K_DoeS2k6W{sKn~5 z=FZPhl%{)%DfZmGHc?bT?>JyPSi~#Mo7^piCxtoB}Id`T{BEsJ?09LZh8W7Z5zPXKi`dyA=CICHT` z8Ezz@?l3*4=s%;wgo7ff?mi;I93N!597NMV62HbuA5^Xh+TB&6rBZigjPGGf+0wCxH*3IZ8$v_9zyyJNsXc{`~N0_zcD2Gv?YJ!w;z@U z@%>7{szc@m)9G{$>4op?ET4gCcek^Q^Gn+@0m?RwrIyWi`CB^*iD;KIftD3t|LF44Hae{S3T18*EciyXc z<7m(Ax2{S7VWZ8w5tb2kh7S2qNf6@-=AN^PSucPTV7lGFx?;%*%qxv*k@@8KfLqXmut)VS7f zd_2dR9C>42L68b;Q-H1OpPdj`#oE^B3b<4+>6-e=b_DxQ$&4{TgEqgWWQB2D56R|o z>PACk&vddD)ACB{NG2x!K>}6b2auJA$*e_I~Q@QH(Mc`&9MfjJ>*O zelCOvYI0mIX8>y;8Z4bDv1i}H4@ytbH)Z1+DJg4*B@<;9pl{>-) zMwHOiE${mh1#-%o(@2qLyRy`Kux*0etLo15wVvNnf?HX}1NlT@kf$h0Z1sg-tw#<* zP+RbrG!gsptgEOMnRaYYvk<|M7!1)lBKgt`F6h&Ja%h6Kbt{2Gs`VC<=&*&<%ee$ z<4~E4#_s@PbjzO`>7gHZsA6ip)}%}U(=rl5w(xKghV2j;Vj#uc8h<+2lklK(aP%3v zR~{CG2rhD=A#^7VfyTolA?4zXG_UD9{jX+CTyu7iaV*czm~z`vF25)WO?Myb2rR9e zE*xiFhG8~N=lWsf5F@sDpS=*4{x_4P(^B?^@#nt!di%6=(onZ z_GJPhY&_rCkU3Mg3h5L3bOI7awJr&l^Wqax6!2kt8?6NEBigq8 z@2=+Wu&-|GUsUP$7M7lmGp)Scf9HGvw{Y7O+S@`3O;?Bv9AViCFz;O1k}3p3TFo%@ zyGuEBQ%JYGaV@E+lh-X@Cc6ONofY`CVcgMi0)23}dS%1q9PG%LfXQC`rpl1@9g^tu z54`JRqap!3t!saaNwp0f)rmjFpK@Dg>w($E>yH;ADP%+B^&86sxse9kNt@~P=to=Z zM>UUQQ5EYhIL5~5?^qo2j*NZ%-4x)o&(&KGg)|$Y{o0n`DFVeynx`+fl?MNE3kQbQ z7r_zemfDcK0r5thS>^44JLmD6D3DFTG;Ifx;TW~K!Ak3BxEna>_`fV~PJd~B778;% zvg$Wkfa_i1e<+@ARcZsUY#W%gI{a|o-8cVE2G%DmH|9%(O$OM~e=3g) z$gRFQD7fqMy*NK%p6BBIq%%$1Nn^1ikNe0@x^XU3&pl`iHLn?1IC>@R0^ld7b`fBDh zf)5-qW5`CGA6A(*;*+pxlvwW%TSPouZkT-!@8QA4EAfERdE8u=M?&1kPZaL7K6b>s zo;z$3tnf-La!W_&9^N7I*A7h1_SJ_phmE7hOQV)H_%vImQb5G%Rz|(V95F+gm&I~c zGc%@>P;S2+ep)+p4^DLOs-t!#C^yUf^WCg|RQDfDX!DVP-)j{0uT5oTKm}+Iq#r7z{hE(Uk=&5Jz zCmEj;&B2ri4#SyprR}!GotJM?SU(aiNc(#nvaOv1L8*DU8s4keP%EgGs=!1BdRtJbd85sdM~i z4;;AZCdH%X4!rpA|H}tA1vNr9ymj0=-tjPQUJLBJIygx}JMi(HqTdd%O~(pQ8aHG^nxgcQG3w;w+j6FyryZ zizB>*E2>Me;f5aJ8n2-z;=qGVE=Q?D$&6R|q z;XcW8$-!dE&ELs{_YqkvXJ?1p^3-rA#pPK+rRfb6w>>iQnn+X=J2$g8^P(f*e&x|T zy*#^@FicFeS<(ULzh|F`AonQDVCZ1xIAKx-@Fkl*JFc|T$kwtXN<=t{H?o_RO(^Np z9N(>A=gf9bpd_Z#FxFBk+MLiL_8R^RB0b>)_D_+~i4Xn0*&vwRE(Kr1(|$ zKS*b-e}o;on)5jC^G~vk!+=aDqHo+|X0&Bx-u>-?UuM(z*KCe2SvlP0meFStr@b^R zzkb5Q4me+=aGkVbT zxlo&*7>SgCX38V)-ciZx*)Z6kw2q}BN+m1$S0mv8uVX~b#+_Vf01ZGJxL_{XOU%P8 z$$3wE%x`PQ#{g7NuFG#1oWh&+r~l464_P7IMn#^ajy{tXUos6(iMU<37P^;9~%1jZ{P;JWcds0O`}eeFLZ!;$_fL$+uhlnVak>W}{&be5av8-b_! zX3ztp6u|v1{!UP|>e_`$*nzRJ=x!t;)Uz#HW~mG6-MN)GM0x$7(uQxUpH5xy6mu1k z80A{#3pB6;xM^bgc**^W5*IItjmS)IO2ogJq$gr7yK^qW;c8MN&F9;?S>vHHonP*) z8VY*J8UqpqB#e10;+1JcmjH5mu8+NhZioiE#3~)}Z~NHJ!^+L5KF1*9}AvEP}Op`>*|!A${yPZrPZA zPjxdm;Di6T@yK8ZiSOhy=E5=Rwa)FirZR4SbIAZ+eF)GH))_3xq9D2mA0hw-Phx}f zP@Vg@ta9JAAOnMi`KPbSStSK|`;Sl;IX(!jFX8eZBBoVka!IHSh2!owtVQAdc#db+oc{blO<20Z`u6NJIj zgCnr0h@xMd(yY5OZ;#ZHQ`x_Brf0H;(8E(7R#8s50{Gx6Pr03i^Q#3NAq^Z_ACDjz zrYV^}tBS_7lSDmn=|dWKcD-bQqIe?Zy)shVr#0eo7c>!Nu{fI%J0SQI<&)KAMX{Vr z5sR#pjIeb79;YM&Q^7>nHwQT{|AFBlb)y|gOn!o93NA{vn-7gDE+Zkng zPmKVI_l9vY#u+1YLswx2{$sm9U{#H>eA$%b8f=)qvuU53Y|h@FGcXw9hTU3Pj}q>s zu8(>-D%UB=sf2fqFR(|o1b0?TPjVULx!Ty!;;BMSOZQB_S6Uh@$~b`S`)1oG{i^)8 zYyLfogNy2yAyEZ2a|q|%yw^Lm9xn@OjC`L1K1l_?CLw5_3M76Uli|o27GG>TCZ3S) z-K;gp&BOs7(ry}j5sV~q(F3w-LuYdRRSSbKG2AlC`DelsCpIQLwU_>k*QL_H=B};- z<*W@4VGyF*`pLUPbTwg+p7rcc_kNm;f$O*-`PrHph?Yi(Oi6ZAe4J+}g;2fm9corN zY7Y2-5 zE_3^h48X)1K~GzJ>T4;I`q(@|Fm}++f!|mcXCt)KsHeLyZXM7 z?n(^qd~(sNNrEU9TX9RfX}aD1R^|wVo?o~KXyxVB}3n@=?o7;V)q|+^zQ=!ACMH_)|y^$PNW$E{Dy8NPpAWnQ1?m zl3q7Jgl8lx*v72L*zY)TBz*^H?X$>E7j>=n;5OnFLPJRsNnon-Z*Np`uQaT=$Mo|pQJf=)_X(D z(#HeTYv>TUwqO78sP7GIDyDZ8@NV&db~J6-Bu{O>WcL{Q#~txWl{ce=pf649rn;obW8 z9mwCj@lT~#jN4~{?cp)e_62VEuJP{RnGU%-1v;iG1l@VxfouYFl_n%E`IL$gi)jZG>}r^ zfK0BwZ1@s}>c-uMR!QzBaXGr&E%E(T|2^GB4b)~f-Z*@#a5OOH3eL7Mm%G~%yHjFO zz8OGzn>0$3LX&W}s1THO$$*2&JqI6igtgy`dze3%ouS#$6^6wJJ;OVp55dm9P!dym zz&Ocf@i`}q8ys#j*WaOq1?QLjwkO;UO?Lsxe@vyKeo{dAejgVnBjh^qiu|ZPnWvBr z7y>PzV@BPCr!oNR_rU<;%NoU&PwZYM7wO4QUG5t>0;p8-11Ois?~8{xJie>D;#o2S z&PNFK8nQC=)Iri=q>?4M%!(E71q34AXFi(Ur>=iIUhtWwOlNe1xJ2y27K=eoHJNnv z{w(R0nl*w>)}353z^kk8D-^DOz8JqRI@iD06?etuN%B&Or_PNt)IifPJL|}o>goQy z14~cyA6G+LTs6clQ0lQ{8;eTloGpELV!x)XP_3<}Mi*MA07KlUT4&naEzI9{u1J#~J}?s^?&h6|&fzGu+nWdlZ0n#`lKaOFnNm20(LzifW8lM@jCt+@C17b$lO-U*jmmdpfj z-|_SuVNSpZWxOByfEy`sA4a!nK?K9U|w7L~PTwo9LK zisC+Y4@cn?K)JSE5Q!Ug7l0;}8x7_eP+OnZv<$a7haBTgm?wC|W2nVcftV{9NyD&MKmyJ5aZOIV?d$uMr=vKV-0$4ILdk{E4y{s*5La=Jf(5r^aq*i7 zYuTRN9Z?NMq&tzjV1LG&4k z(T@!qvmA=pwCt6g9VYamDa9J-eb8~5%RTODXXl!kY`So(^{*RV#>t5byd}r-+6@$w z(Zavdg=e|8_Pa??+cuenkF1ga!rF!S8 zopB|c*(+Vv$L?<7dzAKt6(C|f_*{usY&(4>{+pS3I7I4}*Y8VL&$~vSo9im8Uz1ytI^?lu93I#i zVtkV*c>S9$<(pH2ac)gpqw=kAZJq7UY5i|&??el3U}IZ7;#7I`GlFz+x=k{bIiY+| zF$PS1B3-~t&d?MZ(F0I9OPURC;52v#K+TmLsl2zJ=NM4gjRTzO;xAl-3mfg`$nW7% z(fx1NorlVjb+0l9cXrJBnd>QKgD-pwi+jV@1Ys()8KoV8Xs`Fotp6*l!+ z8InCw@W#KkD`#iZ!Zkm@<3pDnb#mFNdzOXk)lIi`hBU!R4*o9-oWhZ6oxLMRuvT?8 zVEakU%cIrBH!2E0=+ol`{Q_KL?pm#v4vyRh9HbQ9p1_{@oXz^6O&5wZ@)}OA(7Kiz zrZ1W3unPxhqc4;Je@;)SIeQ{{h4p3yx^{v^>sC_ zuE1EC7a-yirCGU_MyroqyLI_fDzd;jli#6Jf}M;cSYB?~Edb4mZ^_>bw==J3%}J32 zC-Lw{1+;k?6EO)?)h|nW-I3kr&*MyF<4InH{sXR$02Y4^0~=@GZK-1qm&!lv9)G=f zNec9VSayDR4>RlZDn@qRno%K~#V+~Uvc8#y;cnWdSoa$J?`>`jeqbp^HI=4&1v`#e z1~lSlfN<$6*rdopNm>8f&(O3B1H3dL9Ugw=bjM>E|Be1X{~m&$MGzv5Y1Fta~9O^HQ+UZ`(otno2> zt+_^_`m#RRMx17911w9r?`;%n=-z_oAVw6Auwd4=^PKpEIjgjkR~N2_OsD2%FMAf! z1wiY`u)9*C%;cK#gYRa=xJ^479ppHHCjd9@h#|e*Dk5Y}`s3c-b&1zG8!Tiy6z6hf zr7Km*8V3Ab65sHE?zxs?u8ZqzlD^XUxw9%EL4)?qnZ&+5bi=#K}Lr;XD3Z)e(@^ zBu%WkRqKeAnU`^Vr13?bk@d9j4EcCkxcF!PZNkuaL!H1;z^4L9Xz}QTcDybybC@+s z#w?g7=lk9UzPQkx25;rJ3y)cv5}Tua!c}0}14w@EVoj z1V#i^nqa;>5`2sxo~pFL_?kuf~yS|Hh}z^cOhSn7aVpWiurV?(a?p z4NSDX;v)x8JI6r8%17;ho*{f(2G6+jXd{w5rp3J|8}1m!o{1u>cyvBC=e(D%sJs&; zplIXgeV4pS_Q&t};IK}1(tH;}K4p8RH~{v!-!v)T7f_MY5xlp~bV>fwD72n^;!$R) zj0B$0R5K7IDCx)LP`IRVm#}Z~7xb_d0Cf^i0rEm2=0!O?`iNBJ(Uk(qeqZa1LT*mP z*V{Em)$_hP!7r2cCH}m&YRJU5yt%DTaewGjK2-SHgF#hP=+eJJ(zU|0JP&Q?co8as zRK;ld`StiFPiR_!BbfVZ1QFNe@4!!6m)maNp}93}44aU(=hNCRJp1yS1pk=&xU)xs z?%Mt4!Q00tnig`Jsh>!tIL?Yr*w$1qH|Z+vQ$xFe&Ub#5{Dc3jm^l!((zI})x$8pM_quTx zaz#A}Tp!8Z-`t^N8Yq!m<(eZ3kb$HDl5>q#CKSZbyZEk!ApX(dQecI^#bDpsql2NeoTWNK~TiSzNSUFUw1@5 z==-Bh<}1N^Nw0ELx2jQ2ScOC+lb}vdEx)7Mxy$fZ?;5#L-81GjAQ92Hm*5mN4cN5T zQS!j&sT!doBj&g8uy(w8om1$pV`NvZPZy`bam1{jz9r)Pu*#2yNWdr7aYt+A&<7pg zS~PlU|HMS1?Ta3h6N(3?r>?0DM7Yw2jAT)hmMbPax!H3fe1Vu~On<04nnPpBiGnS@ zq3RFDZFwX!E?qDp-{Bp)VZwUk?u=M=P;D}TzvcH}G|Y7^u9QAC3J7=d%!U{zeQr6_ zn>OrmL`3Ef;U;?`;h^;BT!3rTf{9;Z=n}IvCJc0bDGwjh3UdB2yMETbll7YTYUFr? zR~BlE&*{@X`qXM#zwgpW?7#?l`jFCmU_gt1?~g_d)}BmwU9ze$GrKA03En*nTArOv zJ`cO!Nz?9h($CMKKN+>APzDIV64i$CLAXxyVxr}^;_+SdAiSw0=7IBMng_NUF;($( zA%-)+@eKF6*~#7m^?ej8MD`vl(O9CpTZrU?74l1*%jagBp7RK=RY+u`4LBTKW`aG( zn7wI@kTC8f>{F=nwRB^pvoPjc$<^<2y^9xx(H_T}?6s-u*+|ia&u<~k8T|x1p17CR zM{p6P;5#F^HX?~T2-5_$X@Z>6JiN*}ew)(JvwK6j$2h1bGqC%lU*1VyGhtstZa_dm z%=X^j&(G$@vaffAbidkFQ!2bZ1b>E(xuk>(U}B>MY-+HL2DVSG2rEeJj;jpe zmZ!5u;tWu21HFHZdL1nF zI@W8$Su_FdeobYwo!EhM{^ilc0+#bG!H|Ejp(J2q$_Ml`OYFkyaq$ zyvHliHVzByRsC7A0z^FYuYnkx>rS-SKX6H@sm*M&U&j2pdCxgl`4+gUCg%*jdDII4 z8NXNjijglvYKwG!L~`W}tZq_DlD_}QE`(nsJ@iw_)=30Y6de=F2OaiDl`1elw;@U? zYD4{>>^|Do?sc-a$6E9t*p=A#DarA{W(#9)bvj$$i{k>`Sn?zkjJ;6>j~Fv8`eM{tgrvZJ)I?j#G#`F25GNt zfI1O$chU9rLy&{Q(w>}xUsEGvAE4ud*jM%-MQRRdKm+4;^cTByWZZa|e@}j|l3OD;FELjagH zAMJi`z^eY$+>y=3=g}YGCr9)>0)?eR#z>cO;9&5JO^E%!G})TC9^_68a1d$LP6?{2 z<(hh(B011amqfYS_qh`vl+H5cKlNwS<+oh2O%ZrMvaR}r$u;QS)vfB*F#}0E4q8}H zPYf`pF0=kRmVIq0mNnOL-M5lUe?VMrhExuu0L=T!5BI3u4&Sn*-Vr(}8CMx2p%_2k*rhSH4a6-W!CgRK_FH z+bkl3g{OXh9~HT3Fw$lq-!>c^|C41d^KsA2>*bjS>w>ZtK-$29dyW`uuOqd_=!V*{XH^UHj6p2QZ(n;2(xvOUqrzb&H&Fs$B)Vw6iDw_M(_rl2Rr|ICuz zlI^~ZW>M$RgV@dEOFOy9mU;`2v}^Ge52eQTMLop(zZqSJf_V&-c}rV%^%^Dx zv@S%^K4EiQAHkwE^zD7_woGH*^{~fT^5Nmhx48RXF`r#`9wnqN{+*$lb~e3GNtino z=~aumhyDeW3LfHL>i&JFz9FEA{_RL*8B##ABv;X#CTb-&{t@9qL@W@ z=TynLc>{y~@%6FaeQkV6^o$)L1yI3AeNke9W~r&B#YkAd!v z@-HuTT+eO+H=A!}4hMgXuB@*y+vV0_-dUJ;ay%20e|9t+Q^#Ziiz%WJS-!lpZ>vl> z-|HRFm3$+hD5m6J(!SRp>gx@tp{=@y;q$=K!@hAp6qDIj%aPU;$>pdrNvrTmlD@im z_}eq92P#;R_$0B_10yUq$Hy6&g`M5&Q!Ts@V6-HPbF*bgKVf%`fX2|w2mH2yQMqN; zwt&~v{5c>R*WCYauZ(`R`W8>y_vO5E01nAQ<+{C>bBdsN{1wwiSe|1hCRJ=d*)<~a zo8>(KH1l3}TR`-qrKC)K`^T{JDVMQx>0$dHm*du?LDuKMU!;ZJY^$N8e+7=#jMU+@B|eI*5bzc0;X@?(!plomBt25Oe`%l-GT4hHPxlS zOd8fhS@j!KcJ?1Gr~F}HJ$TkXoAp`Q3(}Q3`N&%qZkw#SUXR)dS(G!{c~H}MaAoXp z&#-Ocx-hd`GAG$>Os>r8WwMvUB7E9L)Hkq*zroxFvR;OL^8x|Yha z?fUwwH}z_0P0IE|_d;?smQ=?h18;I$TaXK2X+cLx5sNr{x_=ZjRxOz>W~6J*GknjN zkNTrB&2w4sNNd-ZEFEFJ2Srk!hJDeO`BR9O^uFX7KAAO49}`lji`mQ%c!@zo3-e}P zZAD7|s^#YAeM)+`lUxA%Q@pCd{iJj+Jn95`z%erTC!-9|nx*1We8>dI+ovK}9hI(} zZHowHeVWzn%?Du8_|CSK!@|zMbWd7+N_K?{?bw{3C~7>T;qlc++26-y2QEWu*_85Lg5*e-*hd!T(!>;=HTq}o@9w?d||eErPvUkNrOh}R`1`gus#!2hvDCx$S;L7rKJLHJ1DkU zIcM0v{bwmx9*8P6jl3g_mZxNdvlOl71<&-9*HSR22RJ?}HU;`LsUJZGyNky>mtH@i zjb5Khv2m~HxV~~HO@Anc^QdH!ZaNyQ=3*|H-Y*|aaxLPxUYkY65@*;NZUwAHyO(GD zn-LsS4fTJ~;dWe39TVrDSCjao|N06W#IUyB@_aO7`uTaV4~xX@c+L@RnLg zrGL@>nL_mIo1AIbu~AL`^vH6{C&UOm5%2-_yVW&*%gn2^Z=v|KitDsPkoM7^f_p93 zhDelgWx8GQW(_vK(&U~fDSm*(GTXurhpHAuS9~lHFz&Ov4noQGsret1_QVDz2Rr%x zuefg=?ktl(44_6UU&GGQ-Oj$dM>bql8D?}LklWw|Cx}8!3qyo*%yg4E)IYN&gYDDHyXlaemvhJ@SN4RU zMO-Q`+An@O2ww9HH@+xxeTEnf_N|al?>3)eI`{^r)0jWW;G`n*SHRKmbmoK?4n&pB zsjkb#$|_z_3K|dMy&I zI+k)UC@Fn<6(YH9*%>mhgCOfsYrqfQxxDx!Z%T_Y^1G1@{4h)zIu2)@{#-xwO|CDJ zNAm{r4=dkaS#^JuIK?&YHRaH@gxjZodV01#uN`hVsgaGGbKK01`d9eby@#pI^*K2B z!acR4(t59_;NbHenZ>|T_Lu&w^D7xoS~0S(gir00ug$sgwzqRm=I`qux!bD#nq z3AHsP1K>%vA;0!1Wm;XZt3MB;Y%XtzKnEp1N z=RxTYQ8@t-lM{Pv@omrISMLOPLhn_qZS4z8QO*II$T|drNw6vJ-p&MQyYQA~>4HAMP!)W;?dq+V@*nILgXl+rYvTGUwdb(iw- zob29PPOmUZn&V24wOsH@D#B%v@P(YuwiyjXi#TfZ8!uAk!?rRxnF`aKwn;Q~$G9R{7kl&WbK*GbD8^mC?Q&*}>vzIbi|_Su=gl10IJ&I7{5x#s z4dWG_1DdJ6w^I2^Z?yr>u=n4Xpo;sYg*kLky|Z=Qm;^>X9pmrs67(%zVw~gTC;Bw| z{@W$;huEh2mZ!sQ~1c#01HRBLm1o2#8!d+?VBb>dn(GgRlPqd za>u&s#s*hwb7u^*?9;0_UbXLu-0H6zuXh?Sz=NK2it5aVQJWU(f#q0mDnrd--p^0; zLT!hT#QJ7RnNtD7z#;0fa}5dHv?)w$49kWs!;dQrR+;%sIYTxl7#~cOeJK;}F_WAN zioH`)02`lOrr#dw@mGF^Ieypk+Zv7(5ylZ2ujeRWizQ}KeCiMv8kwCED9Fy(yd{TU zvFt}}4Q~Cl6W6waxbWkdFCI*Zn^YaGc8-`&*O}i+j%BF&lZ$$@VAb6KA|nzzXZgAB zM}#Z+OPwE=7}<7Mr#_CrBkndcee2^He_J=~c_Cft`1$QC4e?&ciWzVM1&gplI2!aj zK03^qir0A(kC`D!mY91??E@fL%Jt+5j|?$oz5Y|0ddNc9MeSYth7*DTy~**vPtQqFmfmz&FJ$pnk?QF=NUCVXo zljg5!dhPx5?>0{$U+Zc9vsll;Pq*M6!SgyyUfrr|Q_W{}qJ6s(qyIZ=ORlWk9Tl-d zt1kO~wqJ{SBCJoP>wEJGws$(PR}oJ3ZD44<%YhLcPG-vR+l3)`XYrS(!&ZI0vSGw{ z;mk{S-X?(xQVJ7s=z3Dm1s(&L^&^*8QFD|>Q{pOlR4J-;skCfaB6#wV!OmiSb=j!h z=##PvY-zSfyf&c15ncK9*d~uf_FlYU6es3cl>Q{+tp(pB-HpLJaKag2t)=VILf-8Wh%l44S-KwOePt0E(@qVr1QQo=-hiFc8ka(3E z@bz!a5fM``V!sZiPQ=BV(LhPDHo9@*SDiv`tfW7Kn&8w-_9L2WX6y81^e>KdkubQW z^ZrbNjzK2k3B&?WvK~~c_p3S=3mLo!NdU-UFDILp4l}p#MpR0e|KCHto_*L{GO?nmuuj-?5atp2 zv#pNrUyUrV6+dvya$!wvpcYG@n9Lq(0#^aRraB<9Vo#fnGO`bFbU);Ow?% zYF&|H<@2dv9>jf)Sg0FU;;kjs!^Q?k{lx}+UCNvHPh zHhWU5dwX1BPKj#|lgcpher+WK))UdzLup7jWB?$aAy|7tNC8?9#u+e>l=#AONzoPG z`jKcPzIj;0M$-S|gFO8c{y2wC=XwKa-50^bRM7I@UmMfL30S3HcL#`!NOgvdB?Ltt z6g;=u#H%-+((aU)r3b}uIl``#<{tgUvG?r)@?nZgQ_$bHzZ@Lm&)a$U8@Goa}kpfoZ=e&jQ%9hZ#U8bccu|Vb(y|rTmPz9mbiJIw{0-Y zG-1fge9qSV&|OCKh4vBNGas?GvzKQJn>vbnb}B2KRG6W&5SpR%`bbf z-e@mpOZ;7FKlL0&r{SnPk0)B<|u)O3|KCBFMJcYhV1R-7LSxD|sDYR$Y#?;F*M`&DPU zb<+UjVNGpa=?c3F4NG)OzQgG_@_)7S?Eg&Y@2^8jov0KimvlK&3F$&%ri3IBWiHK> zT!)>CO=HvHln^SHB{kPvVs4qm8prAALN0Te%i4C_XUxLpvNb+$=bX>q@O^z>-|z3w z^L{_~_w#<<@8=0NgWWH`Fv)hWXn5Xu=>l%%ZGOMwOpitilv$i%op6Zu!^VsL{?XDb zie6dg(iX}tqqY;w-GsN4Y`iO`wKhXR$L+Hd@<@0n$aCZkRyveZR5GN_BV1uzfWqaAFw0j4ez}HyN zQu{~PAJw^uNWU~JQCrA3?0a%wQ;Q3??wYyl-UqJF2<$U4y-_&$mEy4?JeiQ<)M#If zg*;f`u6oeMGKRRVC&fLW>O=SdELOe%8K{ik8bIl6w=uN9Prj=5q+N5kSlMG@AO9(I zMfgrJQSysJQ2hegS##{8wB~}lP&z~%FGhmuPKCh%2{ppz?ZK$)cIi4IC@iwJ&tZSqGeQ!K~l<$;I)c;GWcl=PV^f+H~)Rn%18@z4)~) zYeh)#7euePU|Na!>lB&k7EfQ+*=F%3VLJOvB-ih%WDp&C0emYN1p6-k6|b-Touc|TTA z13OXbX(q*r+B9}XQE)x{g zR!f|OPN2PHw+h|n>`ONMmsyQfD#vdX&l6|Jm)`9$ywJ#khwTb!sG~TVGdzTo-O|mD z7N=JYLvG#=MH^3Tx@PFtQV#hcLMHy8YgAgUF2g)Go-wbns-GZ@e{C@xfA*mkt%sXA zjnc*)jEK6$3R$SlPpS2vsm+4=Nd*z^OiWkBLCd3~BS*H`_8Up?t?Itbvz%TyE6x#0@zLznM}fb=9WdJR_?N~+ z=3=SYOl6-YF2EMwtAy2xEYkw{&k&1dLi#CfY=M<1gs1&pa~rACE67VMdx>t4EM(QU z#Xs2B#8>?KYtG*IM1t|epDJY8U$Ha2!r6~xM6~RKjvH=^wcg9?KLltEn)J*|Fx(+{_r~hEk7+ z<>c<2pqu9MR?f*@%z2T~JI!X?qAXgcU6`96dsApzVaqw%3Tzw-C2WM5Y6R6Ra;Xc3 z(N(o*?k|cNrYt$j=Y+gM-pc4>tCsTcTPfzwQ5Go{_0t}U0YAG5%FF;YmI6ok z3Lb77>sg)eAVY^YqKaPDN)-vM9tO({xTv7Gfpl=0#e+ z2SmKV5$3L!*OftKVJO&g!So=ut7EA65*V^V|JmNr_y^12MEwMzt*E^O^GXkGJppfY zNqn%UDF`lJ8I28N4JC8 z%dh)R0knb!8*3p&PjnD%RyWr&JYTHm4Z)d+b6-ys`HG~`f|*Ch6FL^PuxW^?-SP6J zTD_?eydbCJq^&>N;ol@!1Qv6BRD= zxgo%E(-HF$p8wT^_6m-Lat^jjqd#%xxX5)L!^?h7^lEge*+A9zsF){b}cs$4iPH&5JC3R0sBbhW#ZVGT>)sdJuF>we2G z@Q&9>5bhWKF;2P&pG&NbpZRIeMzD6LMl@I(FPAWLuxGWZw6EU-+&Ud>QSPyzve=gH9%A59(3#hpY*KbJCJeo7@VL~dO)~`Nag<-g` za5xRNO-wHzH}7asFbUPSW}OknuIs!5UA{Rhi}AXCG%8}?Bsv+#*<84&oX0v}>48A6 ze2zOo4}2R?UB~qB?Nhhh-#dS7SU@$TG28NywFd_+By8`md>0!jFOAAR4^JMVI)C>t33x)aV& z!~L!szHx@DTN9{`j(C`x$`fzOVIW}EGb3=VK~x#L-~L>U+LH5x6y31YaC=n-G&NuB z7|K|1_elaeTmba$ge^M_%+S=(S#U4#M+c&QpSTT9Z8d0K%!JO`i4N><3bOt_Y85Uv zKUomB9wNFX$t31U?{mH#yCFU^W+!q6cCarz)Uf`{ms36+GQj&wa=Qt#Y9SNk;UAOr zY2y&}S0v&#^c#l(O6a>-(AaAL75}sF)vRFW;MmSA7WlCQq;p5aXvV68DD*dX4C1ya zyc={mWCllROLGARwJ@s7$a*MWdXjJZ~a4p|(TGEDeEzB!x}BYF^w7 zMI)ReXK5CO<@Lv)u=omoa zkI;B8k<2@RwDL1EHwBApf!NK40t zHya#mNa@+^7WqDv=WsTrw`F(qyCsF&{nvf_>Q6J>iy~Oy3>ly(bOwmuJz9En94zRD z+m0vQO`yJqtz5>mtvQHmA1F>tIJr9vEnAQfDf`Ru21F;sqHZ-7OjJYM zhQuOf@de3RbfAC(z%;^bce_VfO-?%A3$C)BH693|uBqJ}H{bd&{6@plaP!3Ki#sjg zL;<6Y_#9m6(u3;xc0kuHktj!UaYvwX(rUYzZ=a^+8__s%8@Suf-tF7jcsc)H6r~JA zZU2Y7_OG~@0^Qd|BQV%92n^zeM{jk*Dh^5vjy4;8{BLl4f%Tf*GG3Sk*o)v>H9J~B zKINMj{wtXIS~~RmR|UYV1?a0*tS7?htMW3Vg$GkCm&dcidgwz z{kjtEvozq%ke!wWT|IN!rzE^I!+}@{0?w6O9N@FAsjh`3Z;^LX1 z^l&_$+?5Mo$vFhj7`yy;aErnHW7Ee1G)4P$- zJP`+IaB6k;KWQ8v@6%;RX8RIb3~{d6ZnCaFwO0c|vMt(5x(^kdppwDGq}C?+kwh~b zo8K)I2U;1hu@7Z2l(G@H*GRHF|#X?=?L@~Yh zpj;QkgdAF!bf#$o8G#qispL83&S>NA|M_jmml|4O4N}d@l&n6Jp50|)W#3EYXDimtTCq5I>(Z|h$b>55;6A6ON2 z^K^=>xhIfl;LH$pYpE&XuJXXk+-eh_tjNVVY zHjp}dR!O#;$fL(%sT5u4b+RtmY9%6!;ubN)o4BK(Y!KOg`7yYD2W(}VMvXp^5S)vy zTESau*~a|-@^jTx^idq2U%3HGf_8<&i9N~paFZc5KB zHA*-K%BY)&c&9_^{60#%DbwqYLl1ZJ&u-8)Mil4?Hu&5OWJBasEC((wg1JX>@nO-t zy=&J%l}QEyRE|u~XW)M+*@-**?aT3e)^#2ThW_%3t{r)L4iY?E){f$YTk6tLruEDjLkbT1@T;}qOt)|wAL{ztIN@L1NZ!p_6WFtE`?WfY zYF=`BAHrWSoMZ3Cv^K-Cph z=NPGYFqa4%ZNAz^MEp1h6UxE@i>!6LaC&ed7-(fb=*iy=ip9;m3H1ez8SW5CX}exU zut10CCT(0zN4(*HVT3^bfHF}2wcr=8tVn(P@~~a+HwHxld(xv@ru_fRo&WRGhW@AK zEd900BKv^3Pmp_OR+y>%?mN~U2b0jBfhUdMFM?+nKQImIsLBz@y-Y9z>I--h@jd`X eLj5lSQSCn`?_dAqYAj$8VrPBnT=`k=nEwF2MP^_C literal 0 HcmV?d00001 diff --git a/tests/visual_tests/lines-1-800-reference.png b/tests/visual_tests/lines-1-800-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..235afaa46a48daf6251bbd41ebb4be62466987be GIT binary patch literal 28276 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

C49?X3r&E943btCR%l5N5`7VbkT!-4 zpv4*iRUe&ChT~BKe}H_N3=l8z+s-hMPh%Wmtw$rJ*7LjcI#%`1E{-j!Cp1fD*%<}N z4HM6)9^J8|qgw$OTM9=GT)^prp|TJCoBua{nC4v125|~;BGD^FBacyXZfaFwDr$4G zgsUfEwF?TZPrznBE?Zo>p4Ey`l0JYO3%3>zE&GRy*`NsE4qoLmLq?7a!{qys-}htI zJVVQRV!JFJ^z`{g?HT=HtZPQycjfIH9#f}t)h7V-!dC*xP{||8r%SMHajk3kOg5OC z;j#H(bIbhMwvEOnTaWc#yz-d5IlY?bBmT}S`I$){zbop47CT0wBoCDT*#WAR$r4H# zK%kUclR0{$o#p_ak^nw6XGJkXVtER-@W9Ja=j6uO>6>9kN0Zz?vaX}WUTq)54hP=Z z8dE}Iq?hd>MVA;N_qe`B0MRc{(&dK_cZ`!LmlFr7cE*p7KFw3oMx+h-KV1~ybwRUT za6#GuYLlIDciNuln`t$P*qurmz-!1L%$VnEpkBix{w*pz0*ho%jet?c?`~-=2(IEh zdS=oa;1k19otym>QFWU)P97H3-H7|XpAeqHN>Tc>QbzO&-dUvRh4g`>{)szG8t@X-)_UXqC?QIiAWrPY45ViPpz#-g_!o&3Sctx~{Gn{m7_jCXe`hIP~cv zFS`Totj01nPS5~iE!FzQzDX1cUZ6o5PrKP7&^WIi`ohMD!1Y-nC%ew#>v}yq;PZ`I z+~BGq&&1wuL#k;Wmh`=MwXB|dhb2jXeqyr_P$jCV_r5Ckapx}d~2W!D@(8^ zyAB_GasyJy>b!UZ1bh%eDTr`yJE@-~-bGyIL9rOKdH5dRJG(|pGIYp3gawCE$$ zP%9nbG?4Gb%M37LK>tW=vFMvl$g*iN(ZItT)b(cv_e z3kF*7{pX3o7WW4sqg0uQLiJc}O8~u#EBwxmmXH_WT}fQD&FB)-OU9mG&mDZfaNIFk z84M$X;_04Z--q8nr&F@(O4XIC+=*#0TBpq6ajc$$9AjQU!TCD;A1 z?m{f=(B1ymhiw-79uTNSy0M?%(NNHAA6T=J=A;cEkCf$sBEGN0xtG6*sDV^(4Q#LC zlhOvmI+6mh>@HKENH`{VRiUM3Y#na+JRaXrY1$jLi#77hSK7Os(5wq8Vkc&FiALo~ z6T&w4q$6X=hh06+4mu%xKP-MuI&`%u1|D3|BbkvaObsYVIWkqqVK|>mK^j^AewPH$ zsa5U#5%3Dpn3fmeW@b>F)}n3OT)ShlD2Pu9lS!a!H7}x`eg-E@QW#{)AdohGsNJqv zCRsRc$2p;-CZw5F9?v8HWbdn)emjvT z3hNjU*KLgF^}1YO)s~iKS7{xfha_ui%)N27_l~$Q6TJ58?szvcEfSyW{^rqbW75vK zg=)v6OXJd`qrq}Rn&V*-pvV4Fwc}8NA@wh zxOJUI*OYBv>tdR7>qqr*5F-O+Mg)Z%aGtx{R6O4}7+}5iP{)An+_cl`cdLmN?-8?#t=KyLpJ(0!Q1(Zsl`J#*>QE zQZM@53j?Nbe__QGp488Mfd*g(Fpv0~$PcR5q{5U5Ot}QXlx?I~7S=n}K&;DB_2j#79*s4VCA-_!y4LZBsYi~MYWNT6duBe=obfvgWwXUL zIdqfUYQGaJyQ^hv|5*yRYP$WQ9mL$5wWr(cRV<2{8SAwj@Cgw4gS&NuR3ItNjOP5? z4s9Fsk%FsNp6u7L7WE`$xTfBj`%Lw)Klgt&u5v1HghvOqEzU*R==3V}*I`9h(*{1s zH~j@U)MUC^IewSw83JUV4#w@J%STNM9tM2`VBa|NUQ?{MR7<>ka=Op|N}tj``jH9~I6MLjV8( literal 0 HcmV?d00001 diff --git a/tests/visual_tests/lines-2-200-reference.png b/tests/visual_tests/lines-2-200-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..46cda9ba3476c8b5cf7a29e7751e7a81004aa09f GIT binary patch literal 4058 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 QpPkLvz(T*__Je2t1B7rq!2kdN literal 0 HcmV?d00001 diff --git a/tests/visual_tests/lines-2-400-reference.png b/tests/visual_tests/lines-2-400-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..6e34796da7cd2224c27bacdfd4eb058c64fa3806 GIT binary patch literal 9600 zcmeI2cTiJZx9|}kL_r@=5Jajdf&xJlK{_f(6R?LCqS8e|2O$JRK&1wdA~n*LDxd^H zQ;`}VKqz7oke1L9LI~+!eBS%b{qxS9@142x&D_kK{BibLd#|<5+I#)7a^h}Tn;sE3 zDZsou|*pQ$1BZDl8i|e1#UkeLfJHm)7Xv-@q0Hw(9F(WYpE`4qd z=ENy;w|YishtrJs)8LCV*R*_}vzOjn*~jzcOQ}8&j}ffMh==E_=s}(@F}(YD-rkbq z;Smw|i|66BC?1}hO2RzP5ApHxTqyisq5shk+$%RaYb_eOetA@Yyc%<8kiD^SyLe#` z=VJp)j7d3`t*Kgf9~LCw(x<@6llD}geI-3Z7#OUF>(EpdFMTIfwl1S?VDAU0IKvA1 zzFa(20wJ>*an&oJ#{vDOQJ->{#D-)Wal~Di%dy?@AWGjcfccEIW z*U!L|5tv+qd@qQ!-!5nLxvPn{A1}iO?o4%&T3S8nj67n1?R4uWy&ct0+Qqohy!(&! zbl_O!6Br2t{h}E!VnPkoD|L&h?t{0aw{DON1(BnU8tsJevPBS(6mU&r2Jg)rA`zCT zKvZFUTg&yoLyDRH&48w-LRu5zcP8Uy%dvzHNdm7^+^$EZ*e`mCx5LIU?s(8lIICY^!%F)*r_zu_EbQpOdQW=D%o8}YWwketHuO3^e>UsX;76esk0UE0Z|)u< zlIvFIk!8f)#jtp(6*e)!zjO?daHjBJR?VF0d=aI$KF zvl>RnJyUs0yc1&fdv|hrMo5w*r8Qb{=rnD;Jx8vpQ@|=)q4OE(b z$*(X&6ReXoOHYSRYkRS500$WA`ZzJfBuw$V=d@&!GRJ}p_*;AvOpz&>lp)V{^s8o+*zF09y#Nny$8TYLxF&26huo6#I=?9 z0P;!57|2GAf2@NVM+A}&IPk7Exu3KOc-|ua1Jx^QS{M3sMMfZk{Y~}!emps;Z0ei- zurG+!L3BVq{2eg6Z+}TLciX5K1LG}8EXO_pM*k8iM4*hJfnv5EBafZ|$-!?J8iFt6 zPv1Dkmj=zP^i5DUG;J1i1$JDnYiKcf+0qb=w)S=Bb5Nj168H-pgfPuf^~Ei!Zy|XM zSh1%}faC#J-)LO*qxLSMUxB0;exh$<;GyN2$dd%%MTS9QrWQUvLYY+d5LLL*KZfx6 zCzl_LT(>S4YjzX@j%Tcdg=XEa{}zEeKI7vH38W<`5KBk$X$;_%U5joqe2K(lX@~uF zADneFYRNj3FzNUBfQ;p}7u+53V1!De3{N^cHeCXN>)=~<*jQyu?si$VOKkN>rz7Ro zj{wJK#-n^A7+iWba05jsT3x#0bWWW+Z?W<-{Y2ZD`qAo?SB_00jzX(eMMfGuH^`&c zpUZBA4#jqr`f!pkUz~6Lw7m$Hi#`xn)q2sBkLKUJ=(6`s5ZxazbS_{}{ncM2@4ZO= z;AMFvbsgvNbB2Zntc(o}P1aoO z0y{l%$}x}7yDn~THj(F5^RPW~eX3!q4yaR*%j7^q)=nh4?lUZ#XR+E)KsCFr8PXkc zSRJ_Iq0{Eh=FHNzVaO)HVNlnn7!9DzJ9gVtA`4AjDNDmwU#c{{3vXPj|6tf%lC+4m z5pfs?mKzN}u`1BBa~sUb_`xi@nVt5d)&9xK*oXTaNn9Jzi4vD8B6N*Z|GmKRW<~UM zgfn3vJ4X0gedb>sw?FCd)sqUABmMMF#iW!qv+3U-Pylt>i^WC<#y(`jgES?^84n zo-(-@5nfcuSP*m>J!1&Wno9hgJwtkRII;BJ-(*P<#KuT6?Q=Fd zLb*wgQ_oai=1>D3MKMn*P+OS1A6(W-Gc6y;)LuarqF0MOTESV_lhlzw@bR1EI*g>j zrJUv@iIMs0`L{VCG>u7fZvBX3>BHv2(61m{FC*XNxz96jie{zQH*VLWKB+7U7;~P% zDa$9U%9rQKX44wY$A8U)69;lPesK$y1r)Os60jGzt=;3!C*H23c6rF(=h1zg1A|K9 z?cQ0rZU%@-3;Zr?B#_hBvKF&ce>*fQ2@ZgM?mfXzA~*rOPINm#nq3yRDt0s>TXrQ6 zo2xd-5vie|Us&y&vS>^*K&8BzT+{MaFhQQ9382`?`Q(JcZkTqobb70~quz)xpeSyI zSBJcgd{kf2`fI|%rJy;Ku-Hg(_@v91l@tX1>)hp+B^!4u|K=O5PgfQum=-C+8$eBn z1rUM~j@k#HPZrat0HZb1+ns(JeKp?wfX6zF|Q|lX8jQgE*pU25Z%R;76M)6{qp z+r!vds^_SVH-#_ zr6C>KXX(uu)Bwmkz2ykt4*e2Hm$P>`t8l}j`MdOlFDQTV-lVotf*|4cS6A`o(~IYj zP8VwR@wQU~`b(Q=^Q)m!q#;j@nQ!W!cG^XA+4{(IsU<)b41bv&JD!YvUns|CNuk&!i%(gYqoIas3pq8< z&fY*GjHB*dJOGPDxu?=rLXjrxG=OSQs2PGsY^yuwd<|`phX|GVG6*j6>(%87q-}j z2n9!O8UZIZTNI)>tChslc#{Z;wW;6aHps0DRuE3K6VfqC)Cr6{s9{PEU9F_>PNDcAG~ZIPiJs`ZcP1BEj!59*&q^tnB6UX(TWdO-i3i}3?-Wv?85~9NcA>}; zo0*HwGAk7ha&}>@=bMj!%23X1{0q;Gpgz&wk|jkVp{fn#c9z69I28^%43F}`s22aHLL@vlS(gMHAx|J$_Nh+pU^|d@rw}i-= zDL4-^>hqK%mLRjInq#N>-AyLrwn{|$Vh9A z(pxwFKRk&+eg3eh{kfNd)}J0+glcu**MWI(R1%XFsyEX4+$(<~5X^-P`%K5Ewbg)I zR{_EF{?qt#&831fl@nBWkZE;$2kWi|+yZD828fdRTD(BQSmezI!L#gmXi;PkiE8fJodWeL4ViYO$G2Jr(vG-xhdXExrU82L%Rk@#b1MB) zv35Kf3jlid7C?53v9lF7*6a-;74Ow+AMau(j}M zpUN1IEr@~(I3E*Df<}Wx19Q8+@0#H8K|dJd02Ys2si@yzlF>|Q9W%IfzKa=LX1j~N zsx{#3(h=c5{5auqHdP%)@BLt@GO|=Z$^UnHp+2<#T@A?fEr)f+5Mjd9qi{;QL;EhG zd#BmKxeV>06Vr}RZ;3U;l`t^TYsM*ac~qgvM&!U@CGD4St5qT@Sw}5Z#}_nrw$;d) z=5^{%YV0A}Q+}x?E_V%Cg>7M7GSKG#C}VpbZ4?(qtav6wTGs+Ilpnp?O7yv#MN%G_ zAHlaaZabqqyN*kOb_a624otH2HrefqvYQ~*d^LHpr==sZH7L97y*pIa_R25OQPIvHWyHBS9 zv)Z84%)U2@<@$P}zqWYi3XLk364csLqW1G!lw0S&0M_N}hGbriKG4^)*`B*QeCsc` zxUdl4ozrxI;E5_bPoe$C=eBtJi~k?`b7|pGC9I4jF5bo~E|0^u0|vfalaCP|)iAH! zc!O|0Aa?8zI-7?DA(&E^KBjP)SHo(QOOd3vQL)AcPnCG4IS~{Mdk(9d;c~Loo1GN{ znzq}+Q%A2I^vo#%Qu{FK`vv2KubE-bw4rPwk|k`-xJ%`n{ckMwopg}LL*&0|Msod- z^5x;$s&e+x{VSK%_q#hB`}f)KZ*X@mpmk1w8zZ}$-_q2Cd5m(6{yRO^>Y3V(c;+ft zp0RfNVyOT848iYJuOg!4QjLyj6pu%K1jA|}18AIVVDB8~DtJPyK9pKzRRwqEL5~a@7Gq<}8Q-jf}?l#bF39HBs)~-B8KWln#^&lVb zJ2vZgbngrYP(S-pK49QB{q!L2vq-kaxp3u5uSsG#?&1Daewl+vms_HIX)o;>jAbGn z7Z%>+8fa4v>AG5+@8n(SPn3`knNh7r1LOmSBsQVy0F;@_*!%u6kn3@7>r8-J{O+hg zN#+h~T*O*5=LmmgZbJ&ECPN1ovn##q;F})!(rCJkDa&_)6tRu}!M}R*Lf_?Ql zt-X(4#RaaqaUPduJb1zku{FO=T!tbAql1E#t5(;sV|LV6#! zHi_A$3qPDq3^;miTR^m=PYGl2DJ4@9V#YXXIms^@!%)t2(qZ)!L8-p7f~D3`_hQ?T zI>A-FVh%A5qNN3i`eCmJ^3H8{iOc8f97o(xlhwJmV8}6N6ezsnEi^h`sQY~qq{UhU ztB?m3{h3>{3Ews2dClW9zeEeguYCqSvwfJSICotr?y#&=gbqQw32QB{)|S58=B+Jo z#_8viD5;2NbacC;PKcQH-osStj1AOtJkJu!6J#|5ABUajkrIKJ+ajLhAj%gvTeoT& zmdoVfdUrZZU*D438Wj^pdZH5|EMoFV2p*n}#|9fTKjkoMK4US>EqS_qhZ=nll zrIcr$UVKH$vrkmflnFtMX6=OT!OEJP-O);ktX-a!Zrx3n0CaJ)%15InleGJyX04w} zc}J=}V5ia-wy~-^moTEV-<{n^%D^RgoOc#;ojSJykliJ!G#CT9DC26QcTcT_p~Pb}}qahUE**J2-4pVqqGx9akS?6(6~c7*5d z+IUYTGkat#-BcuqqcTQB7{hxnRo=Yjuo&2J!6s6GTDh=PS-M)|#XkMMdU@1jfrP+1 zL=28JtWYw}eo&#}-|U`#0HbSS?Y+7plUIAq;^Y+F)#GUWwIL|L|!%<#GL4Xvq1uY4QyHfR#`Cu?zv#*IHK7WwLF#BnM31obf5{jJq38Lt=Wzh z9uk=|tfgFf|Acn+B?@fZLDw8Qft)J)M|V_;pg2-)!&IbPp1kLClm5v2mDDlz?00FP zq>ipwkOwJ$-msjq|FD;p;HjY#pM5_)vp*+LqK%r7+YmwdfN_PNIX!o&R%qkyPL<=GN!bKhl3+kz)sh zPJ*x!l^ddPM&H!_10|OxPQEjWAKA1^X2eY7uYYC&$mNJ+!){IgV3n2E5jeL1aLI%7 zG%z2?E9@Brztvc#7LC3hB|2J+n6Oi1ix>-0t0`BqY~`m<;CiR(-kI-43XXoTm@j$}Vw@5El8pHBn}wbf?%9Ohb|M*hmbh1r>Ko+KLz zs~^deP6#WN&6S~c!3*jNj%WH;YI^x|db*5Z@Woj3v#um|8XK8&gmF(PZ4u;CDUHgti5&cRZKO3?%;CH2=I`0%BxJl& zN;tjL$`5e@5uUdD-UQI5!EU0oOg04G)c*-!nZ0R}ec!ZPwwy@G0M2o$mvMyZ9YF$D ziAoUX^rh}Ha>9)J=Mp9-m9M-TPS)*)vRAIEd4}_T|Mhi?Ghsg~v7{|(W`Lfm3sVB; zAO-_Ek8B}^+n|%ggxcp81V2+IX)Ba89-O$@CeCqj&UdPvw74x7tK#0%&h*!D9-p)K zEZv)zzI;A^i+3>F0OV2_JEso^IyE;se+rMwh17j|ENXU&JH4rN#*nS5Pjk@vc`l^r z%=1}{Y+ivg!Y71`s`&`79?|BM3GSou|C&2^vtVed&QB5KdC@%wEwnTm+eq8!5RKDS$%_F=}EEys?qtcIUUN_9%j{Xr?*Nv zSU7YxHWPk5Bz(26*;3V2GNIr@L9AMP#YOJf;x{9+^3Pf9b*WKn`eU0#WmlQO@UhKy zFSQir(@&|r(L&u0ajO%}1$Rql!hVe6CX4%j@^BS18=P&f6l%(5Hj)aqdb)vAMNYN` z?iI~d?Wo>rWc)y$9kX#K_oz>cDn9EN6&jR-u17a!zX?2-Lgd;zzN83Q>5CoT!%M<0 zm+GFxA>mqQ37-&G)M;Bh;}2<>gZ|5tYT>uv&m4TOOx>C{dHMH|j>QYhxRaO8t}EDH z5;Z&Lhe;lUZv z1=!IWx8zE>tE(nP{GD9zT$K8BOg7@6;d)5t5eI{pBt-4d_Pz>NoOdNMt=0{IQd(C4 z-sP^ubu~93q9m#y!+zzvd|O+~rr1;{>EpAsCJ-U};Fu&D`Ls+L1RPs2%1L}@)L)k; z?EnaSkt%b#hM}Q>iuL{RXx47dd?r&Sj(1Fw0Q4hf%M(zg;KLa=gc#n=N-6%_6nv0# zc~aCaY-wN_W=R)f5~-?yI+e-y5+p zIGe%!CjuMseY6xIqtB%{0p$`-NQNxym<~&(P}6rJR9)0e!QMaksr|?$k6hhdeZd=W z>qv?5y9Vql>!vD_JHmMA#!li!B@=N4=d<6Phi~*)*Z?(FJYJ>I z7NsoC`eh13TNp-^I^B{JHTxV+$>{A7Mka-=Mu_8&D5|Kh>RoohIQ;S7&^+8&Aq zO3@=WYFgsC%=Jtq-0N!8ExFHSj7x#;&m?K@DfaJ-D%KaXOoFErm?~8kK(VkqTDRmY zMclI6GcRMV?0cxFS@n`uwe+)x%bO?c{7g~-XtZd?_Wex!!~^)H$y)Y!r>eQDXC+3&<>ddq_45Dsi$Ao; YWx4*U&#;|~Jlv1DiM4UfwTCbM5A-BkUjP6A literal 0 HcmV?d00001 diff --git a/tests/visual_tests/lines-2-600-reference.png b/tests/visual_tests/lines-2-600-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..48fec03f7a79135c8dcf2ebd16ae66a9f064499c GIT binary patch literal 17692 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 ZWJ}8#G2sBI0#}m5Q2*f_#4V@i{~Nz^pfUge literal 0 HcmV?d00001 diff --git a/tests/visual_tests/lines-2-800-reference.png b/tests/visual_tests/lines-2-800-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..423da68a82098f883327808788d631208417f1e8 GIT binary patch literal 35810 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≈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 P;>4Wn-QeV3uK)R8EVP-q literal 0 HcmV?d00001 diff --git a/tests/visual_tests/lines-3-400-reference.png b/tests/visual_tests/lines-3-400-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..280df3f1221a1d499dd4c0b201cabfa1b5ea02bc GIT binary patch literal 11792 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 V@h@DcfB*trv30yqW&PK~{{tBV)2{#k literal 0 HcmV?d00001 diff --git a/tests/visual_tests/lines-shield-200-reference.png b/tests/visual_tests/lines-shield-200-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..e604fb290b06d9ff32e59e3699df80bc9156d4ea GIT binary patch literal 4337 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 JF4ua5{2%Hi84>^h literal 0 HcmV?d00001 diff --git a/tests/visual_tests/lines-shield-400-reference.png b/tests/visual_tests/lines-shield-400-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..36561ba14ff84f585275727cefe114f5bfe50dd8 GIT binary patch literal 12719 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 literal 0 HcmV?d00001 diff --git a/tests/visual_tests/lines-shield-600-reference.png b/tests/visual_tests/lines-shield-600-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..2b2c0f439576674a54853b9e12632cfcae39b828 GIT binary patch literal 15244 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 MK>I%au4VB50FfT@mjD0& literal 0 HcmV?d00001 diff --git a/tests/visual_tests/lines-shield-800-reference.png b/tests/visual_tests/lines-shield-800-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..321a44fa03baa655db20b5dfe6c0f424c3844fd6 GIT binary patch literal 27052 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 L?8ypqufP8XLR@3- literal 0 HcmV?d00001 From 4638b28c369908cf1297f2f4ecca21b67fec66f5 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 4 Mar 2012 03:53:39 +0100 Subject: [PATCH 011/238] Store pointer to char_info in text_path instead of pointer to char_properties. --- include/mapnik/text_path.hpp | 20 ++++++++++---------- src/cairo_renderer.cpp | 17 ++++++++--------- src/font_engine_freetype.cpp | 13 ++++++------- src/placement_finder.cpp | 15 +++++++-------- 4 files changed, 31 insertions(+), 34 deletions(-) diff --git a/include/mapnik/text_path.hpp b/include/mapnik/text_path.hpp index 7905f25ce..f29cd903d 100644 --- a/include/mapnik/text_path.hpp +++ b/include/mapnik/text_path.hpp @@ -118,32 +118,32 @@ public: } }; +typedef char_info const * char_info_ptr; + /** List of all characters and their positions and formats for a placement. */ class text_path : boost::noncopyable { struct character_node { - int c; + char_info_ptr c; pixel_position pos; double angle; - char_properties *format; - character_node(int c_, double x_, double y_, double angle_, char_properties *format_) - : c(c_), pos(x_, y_), angle(angle_), format(format_) + character_node(char_info_ptr c_, double x_, double y_, double angle_) + : c(c_), pos(x_, y_), angle(angle_) { } ~character_node() {} - void vertex(int *c_, double *x_, double *y_, double *angle_, char_properties **format_) + void vertex(char_info_ptr *c_, double *x_, double *y_, double *angle_) { *c_ = c; *x_ = pos.x; *y_ = pos.y; *angle_ = angle; - *format_ = format; } }; @@ -163,15 +163,15 @@ public: ~text_path() {} /** Adds a new char to the list. */ - void add_node(int c, double x, double y, double angle, char_properties *format) + void add_node(char_info_ptr c, double x, double y, double angle) { - nodes_.push_back(character_node(c, x, y, angle, format)); + nodes_.push_back(character_node(c, x, y, angle)); } /** Return node. Always returns a new node. Has no way to report that there are no more nodes. */ - void vertex(int *c, double *x, double *y, double *angle, char_properties **format) + void vertex(char_info_ptr *c, double *x, double *y, double *angle) { - nodes_[itr_++].vertex(c, x, y, angle, format); + nodes_[itr_++].vertex(c, x, y, angle); } /** Start again at first node. */ diff --git a/src/cairo_renderer.cpp b/src/cairo_renderer.cpp index a89822f78..815bcf497 100644 --- a/src/cairo_renderer.cpp +++ b/src/cairo_renderer.cpp @@ -581,17 +581,16 @@ public: for (int iii = 0; iii < path.num_nodes(); iii++) { - int c; + char_info_ptr c; double x, y, angle; - char_properties *format; - path.vertex(&c, &x, &y, &angle, &format); + path.vertex(&c, &x, &y, &angle); - face_set_ptr faces = font_manager.get_face_set(format->face_name, format->fontset); - float text_size = format->text_size; + face_set_ptr faces = font_manager.get_face_set(c->format->face_name, c->format->fontset); + float text_size = c->format->text_size; faces->set_character_sizes(text_size); - glyph_ptr glyph = faces->get_glyph(c); + glyph_ptr glyph = faces->get_glyph(c->c); if (glyph) { @@ -609,11 +608,11 @@ public: set_font_face(manager, glyph->get_face()); glyph_path(glyph->get_index(), sx + x, sy - y); - set_line_width(format->halo_radius); + set_line_width(c->format->halo_radius); set_line_join(ROUND_JOIN); - set_color(format->halo_fill); + set_color(c->format->halo_fill); stroke(); - set_color(format->fill); + set_color(c->format->fill); show_glyph(glyph->get_index(), sx + x, sy - y); } } diff --git a/src/font_engine_freetype.cpp b/src/font_engine_freetype.cpp index 617a85f50..6b115275e 100644 --- a/src/font_engine_freetype.cpp +++ b/src/font_engine_freetype.cpp @@ -313,11 +313,10 @@ box2d text_renderer::prepare_glyphs(text_path *path) for (int i = 0; i < path->num_nodes(); i++) { - int c; + char_info_ptr c; double x, y, angle; - char_properties *properties; - path->vertex(&c, &x, &y, &angle, &properties); + path->vertex(&c, &x, &y, &angle); #ifdef MAPNIK_DEBUG // TODO Enable when we have support for setting verbosity @@ -331,10 +330,10 @@ box2d text_renderer::prepare_glyphs(text_path *path) pen.x = int(x * 64); pen.y = int(y * 64); - face_set_ptr faces = font_manager_.get_face_set(properties->face_name, properties->fontset); - faces->set_character_sizes(properties->text_size); + face_set_ptr faces = font_manager_.get_face_set(c->format->face_name, c->format->fontset); + faces->set_character_sizes(c->format->text_size); - glyph_ptr glyph = faces->get_glyph(unsigned(c)); + glyph_ptr glyph = faces->get_glyph(unsigned(c->c)); FT_Face face = glyph->get_face()->get_face(); matrix.xx = (FT_Fixed)( cos( angle ) * 0x10000L ); @@ -372,7 +371,7 @@ box2d text_renderer::prepare_glyphs(text_path *path) } // take ownership of the glyph - glyphs_.push_back(new glyph_t(image, properties)); + glyphs_.push_back(new glyph_t(image, c->format)); } return box2d(bbox.xMin, bbox.yMin, bbox.xMax, bbox.yMax); diff --git a/src/placement_finder.cpp b/src/placement_finder.cpp index 3b46104dd..d4f0f3977 100644 --- a/src/placement_finder.cpp +++ b/src/placement_finder.cpp @@ -396,7 +396,6 @@ void placement_finder::find_point_placement(double label_x, double la double cwidth = ci.width + ci.format->character_spacing; - unsigned c = ci.c; if (i == index_to_wrap_at) { index_to_wrap_at = line_breaks_[++line_number]; @@ -420,7 +419,7 @@ void placement_finder::find_point_placement(double label_x, double la double dx = x * cosa - y*sina; double dy = x * sina + y*cosa; - current_placement->add_node(c, dx, dy, rad, ci.format); + current_placement->add_node(&ci, dx, dy, rad); // compute the Bounding Box for each character and test for: // overlap, minimum distance or edge avoidance - exit if condition occurs @@ -732,7 +731,6 @@ std::auto_ptr placement_finder::get_placement_offset(const // grab the next character according to the orientation char_info const &ci = orientation > 0 ? info_.at(i) : info_.at(info_.num_characters() - i - 1); double cwidth = ci.width + ci.format->character_spacing; - unsigned c = ci.c; double last_character_angle = angle; @@ -826,9 +824,10 @@ std::auto_ptr placement_finder::get_placement_offset(const render_y -= cwidth*sina + char_height*cosa; render_angle += M_PI; } - current_placement->add_node(c,render_x - current_placement->center.x, + current_placement->add_node(&ci, + render_x - current_placement->center.x, -render_y + current_placement->center.y, - render_angle, ci.format); + render_angle); //Normalise to 0 <= angle < 2PI while (render_angle >= 2*M_PI) @@ -867,13 +866,13 @@ bool placement_finder::test_placement(const std::auto_ptr bool status = true; for (unsigned i = 0; i < info_.num_characters(); ++i) { + //TODO: I think this can be simplified by taking the char_info from vertex() but this needs to be carefully tested! // grab the next character according to the orientation char_info const& ci = orientation > 0 ? info_.at(i) : info_.at(info_.num_characters() - i - 1); double cwidth = ci.width + ci.format->character_spacing; - int c; + char_info_ptr c; double x, y, angle; - char_properties *properties; - current_placement->vertex(&c, &x, &y, &angle, &properties); + current_placement->vertex(&c, &x, &y, &angle); x = current_placement->center.x + x; y = current_placement->center.y - y; From 46272d836bf6a2c96b158e7e471e04abb0693f0a Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 4 Mar 2012 13:32:10 +0100 Subject: [PATCH 012/238] Move more variables from text_placements_info to placement_finder. Refs #1048. --- bindings/python/mapnik_text_placement.cpp | 2 -- include/mapnik/placement_finder.hpp | 11 +++++++++++ include/mapnik/symbolizer_helpers.hpp | 1 - include/mapnik/text_placements/base.hpp | 9 --------- src/placement_finder.cpp | 15 ++++++++------- src/symbolizer_helpers.cpp | 8 +++++++- src/text_placements/base.cpp | 3 +-- 7 files changed, 27 insertions(+), 22 deletions(-) diff --git a/bindings/python/mapnik_text_placement.cpp b/bindings/python/mapnik_text_placement.cpp index 4cba5a31d..9e5764f0e 100644 --- a/bindings/python/mapnik_text_placement.cpp +++ b/bindings/python/mapnik_text_placement.cpp @@ -430,8 +430,6 @@ void export_text_placement() .def_readwrite("scale_factor", &text_placement_info::scale_factor) .def_readwrite("has_dimensions", &text_placement_info::has_dimensions) .def_readwrite("dimensions", &text_placement_info::dimensions) - .def_readwrite("collect_extents", &text_placement_info::collect_extents) - .def_readwrite("additional_boxes", &text_placement_info::additional_boxes) ; register_ptr_to_python >(); diff --git a/include/mapnik/placement_finder.hpp b/include/mapnik/placement_finder.hpp index 970d2573b..ff809e56f 100644 --- a/include/mapnik/placement_finder.hpp +++ b/include/mapnik/placement_finder.hpp @@ -69,6 +69,15 @@ public: inline placements_type &get_results() { return placements_; } + /** Additional boxes to take into account when finding placement. + * Used for finding line placements where multiple placements are returned. + * Boxes are relative to starting point of current placement. + */ + std::vector > additional_boxes; + + void set_collect_extents(bool collect) { collect_extents_ = collect; } + bool get_collect_extents() const { return collect_extents_; } + private: ///Helpers for find_line_placement @@ -132,6 +141,8 @@ private: placements_type placements_; /** Bounding box of all texts placed. */ box2d extents_; + /** Collect a bounding box of all texts placed. */ + bool collect_extents_; }; } diff --git a/include/mapnik/symbolizer_helpers.hpp b/include/mapnik/symbolizer_helpers.hpp index cbfc234e3..1a5e6016e 100644 --- a/include/mapnik/symbolizer_helpers.hpp +++ b/include/mapnik/symbolizer_helpers.hpp @@ -72,7 +72,6 @@ public: placement_ = sym_.get_placement_options()->get_placement_info( scale_factor, std::make_pair(width, height), false); //TODO: has_dimensions? Why? When? - if (writer_.first) placement_->collect_extents = true; next_placement(); initialize_points(); } diff --git a/include/mapnik/text_placements/base.hpp b/include/mapnik/text_placements/base.hpp index 23f4de606..ee3f39db7 100644 --- a/include/mapnik/text_placements/base.hpp +++ b/include/mapnik/text_placements/base.hpp @@ -74,15 +74,6 @@ public: double get_actual_minimum_distance() const { return scale_factor * properties.minimum_distance; } /** Get minimum padding taking the scale factor into account. */ double get_actual_minimum_padding() const { return scale_factor * properties.minimum_padding; } - - /** Collect a bounding box of all texts placed. */ - bool collect_extents; - - /** Additional boxes to take into account when finding placement. - * Used for finding line placements where multiple placements are returned. - * Boxes are relative to starting point of current placement. - */ - std::vector > additional_boxes; }; typedef boost::shared_ptr text_placement_info_ptr; diff --git a/src/placement_finder.cpp b/src/placement_finder.cpp index d4f0f3977..38d1eccc6 100644 --- a/src/placement_finder.cpp +++ b/src/placement_finder.cpp @@ -116,7 +116,8 @@ placement_finder::placement_finder(Feature const& feature, valign_(V_AUTO), halign_(H_AUTO), line_breaks_(), - line_sizes_() + line_sizes_(), + collect_extents_(false) { init_string_size(); init_alignment(); @@ -472,9 +473,9 @@ void placement_finder::find_point_placement(double label_x, double la } // check the placement of any additional envelopes - if (!p.allow_overlap && !pi.additional_boxes.empty()) + if (!p.allow_overlap && !additional_boxes.empty()) { - BOOST_FOREACH(box2d box, pi.additional_boxes) + BOOST_FOREACH(box2d box, additional_boxes) { box2d pt(box.minx() + current_placement->center.x, box.miny() + current_placement->center.y, @@ -881,8 +882,8 @@ bool placement_finder::test_placement(const std::auto_ptr if (orientation < 0) { // rotate in place - x += cwidth*cosa - string_height_*sina; - y -= cwidth*sina + string_height_*cosa; + x += cwidth * cosa - string_height_ * sina; + y -= cwidth * sina + string_height_ * cosa; angle += M_PI; //sin(x+PI) = -sin(x) sina = -sina; @@ -991,15 +992,15 @@ void placement_finder::find_line_circle_intersection( template void placement_finder::update_detector() { + if (collect_extents_) extents_.init(0,0,0,0); // add the bboxes to the detector and remove from the placement while (!envelopes_.empty()) { box2d e = envelopes_.front(); detector_.insert(e, info_.get_string()); envelopes_.pop(); - extents_.init(0,0,0,0); - if (pi.collect_extents) + if (collect_extents_) { extents_.expand_to_include(e); } diff --git a/src/symbolizer_helpers.cpp b/src/symbolizer_helpers.cpp index b43be696a..1722d9638 100644 --- a/src/symbolizer_helpers.cpp +++ b/src/symbolizer_helpers.cpp @@ -226,8 +226,13 @@ bool text_symbolizer_helper::next_placement() } else { angle_ = 0.0; } + + finder_ = boost::shared_ptr >(new placement_finder(feature_, *placement_, *info_, detector_, dims_)); // boost::make_shared >(feature_, *placement_, *info_, detector_, dims_); + + if (writer_.first) finder_->set_collect_extents(true); + placement_valid_ = true; return true; } @@ -320,8 +325,9 @@ template bool shield_symbolizer_helper::next_line_placement() { position const& pos = placement_->properties.displacement; + finder_->additional_boxes.clear(); //Markers are automatically centered - placement_->additional_boxes.push_back( + finder_->additional_boxes.push_back( box2d(-0.5 * marker_ext_.width() - pos.first, -0.5 * marker_ext_.height() - pos.second, 0.5 * marker_ext_.width() - pos.first, diff --git a/src/text_placements/base.cpp b/src/text_placements/base.cpp index f11a222cc..72b1db405 100644 --- a/src/text_placements/base.cpp +++ b/src/text_placements/base.cpp @@ -39,8 +39,7 @@ text_placement_info::text_placement_info(text_placements const* parent, : properties(parent->defaults), scale_factor(scale_factor_), has_dimensions(has_dimensions_), - dimensions(dim), - collect_extents(false) + dimensions(dim) { } From 5578bdb6f9c0a4a09ad62c41da904687a360e6b4 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 4 Mar 2012 16:38:40 +0100 Subject: [PATCH 013/238] Remove has_dimensions and dimensions from text_placement_info. This functionality is better provided by placement_finder.additional_boxes. --- bindings/python/mapnik_text_placement.cpp | 11 +++--- include/mapnik/placement_finder.hpp | 1 + include/mapnik/symbolizer_helpers.hpp | 4 +-- include/mapnik/text_placements/base.hpp | 11 ++---- include/mapnik/text_placements/dummy.hpp | 8 ++--- include/mapnik/text_placements/list.hpp | 8 ++--- include/mapnik/text_placements/simple.hpp | 7 ++-- src/placement_finder.cpp | 41 ++++++----------------- src/text_placements/base.cpp | 6 ++-- src/text_placements/dummy.cpp | 4 +-- src/text_placements/list.cpp | 7 ++-- src/text_placements/simple.cpp | 6 ++-- 12 files changed, 37 insertions(+), 77 deletions(-) diff --git a/bindings/python/mapnik_text_placement.cpp b/bindings/python/mapnik_text_placement.cpp index 9e5764f0e..12c8dfa55 100644 --- a/bindings/python/mapnik_text_placement.cpp +++ b/bindings/python/mapnik_text_placement.cpp @@ -251,8 +251,7 @@ struct ListNodeWrap: formatting::list_node, wrapper struct TextPlacementsWrap: text_placements, wrapper { - text_placement_info_ptr get_placement_info(double scale_factor_, dimension_type dim, - bool has_dimensions_) const + text_placement_info_ptr get_placement_info(double scale_factor_) const { python_block_auto_unblock b; return this->get_override("get_placement_info")(); @@ -262,8 +261,8 @@ struct TextPlacementsWrap: text_placements, wrapper struct TextPlacementInfoWrap: text_placement_info, wrapper { TextPlacementInfoWrap(text_placements const* parent, - double scale_factor_, dimension_type dim, bool has_dimensions_) - : text_placement_info(parent, scale_factor_, dim, has_dimensions_) + double scale_factor_) + : text_placement_info(parent, scale_factor_) { } @@ -421,15 +420,13 @@ void export_text_placement() boost::shared_ptr, boost::noncopyable> ("TextPlacementInfo", - init()) + init()) .def("next", pure_virtual(&text_placement_info::next)) .def("get_actual_label_spacing", &text_placement_info::get_actual_label_spacing) .def("get_actual_minimum_distance", &text_placement_info::get_actual_minimum_distance) .def("get_actual_minimum_padding", &text_placement_info::get_actual_minimum_padding) .def_readwrite("properties", &text_placement_info::properties) .def_readwrite("scale_factor", &text_placement_info::scale_factor) - .def_readwrite("has_dimensions", &text_placement_info::has_dimensions) - .def_readwrite("dimensions", &text_placement_info::dimensions) ; register_ptr_to_python >(); diff --git a/include/mapnik/placement_finder.hpp b/include/mapnik/placement_finder.hpp index ff809e56f..4e3a74eff 100644 --- a/include/mapnik/placement_finder.hpp +++ b/include/mapnik/placement_finder.hpp @@ -72,6 +72,7 @@ public: /** Additional boxes to take into account when finding placement. * Used for finding line placements where multiple placements are returned. * Boxes are relative to starting point of current placement. + * Only used for point placements! */ std::vector > additional_boxes; diff --git a/include/mapnik/symbolizer_helpers.hpp b/include/mapnik/symbolizer_helpers.hpp index 1a5e6016e..0b2af948c 100644 --- a/include/mapnik/symbolizer_helpers.hpp +++ b/include/mapnik/symbolizer_helpers.hpp @@ -69,9 +69,7 @@ public: { initialize_geometries(); if (!geometries_to_process_.size()) return; - placement_ = sym_.get_placement_options()->get_placement_info( - scale_factor, std::make_pair(width, height), false); - //TODO: has_dimensions? Why? When? + placement_ = sym_.get_placement_options()->get_placement_info(scale_factor); next_placement(); initialize_points(); } diff --git a/include/mapnik/text_placements/base.hpp b/include/mapnik/text_placements/base.hpp index ee3f39db7..477bea26d 100644 --- a/include/mapnik/text_placements/base.hpp +++ b/include/mapnik/text_placements/base.hpp @@ -27,7 +27,6 @@ #include #include #include -#include //TODO: Remove this again after text_placement_info::placements is moved to a better place. namespace mapnik { @@ -43,8 +42,7 @@ class text_placement_info : boost::noncopyable public: /** Constructor. Takes the parent text_placements object as a parameter * to read defaults from it. */ - text_placement_info(text_placements const* parent, - double scale_factor_, dimension_type dim, bool has_dimensions_); + text_placement_info(text_placements const* parent, double scale_factor_); /** Get next placement. * This function is also called before the first placement is tried. * Each class has to return at least one position! @@ -60,10 +58,6 @@ public: /** Scale factor used by the renderer. */ double scale_factor; - /* TODO: Don't know what this is used for. */ - bool has_dimensions; - /* TODO: Don't know what this is used for. */ - dimension_type dimensions; /** Set scale factor. */ void set_scale_factor(double factor) { scale_factor = factor; } /** Get scale factor. */ @@ -100,8 +94,7 @@ public: * } */ virtual text_placement_info_ptr get_placement_info( - double scale_factor_, dimension_type dim, - bool has_dimensions_) const =0; + double scale_factor_) const =0; /** Get a list of all expressions used in any placement. * This function is used to collect attributes. */ diff --git a/include/mapnik/text_placements/dummy.hpp b/include/mapnik/text_placements/dummy.hpp index ff47c75c2..0a67744b8 100644 --- a/include/mapnik/text_placements/dummy.hpp +++ b/include/mapnik/text_placements/dummy.hpp @@ -34,8 +34,7 @@ class text_placements_info_dummy; class MAPNIK_DECL text_placements_dummy: public text_placements { public: - text_placement_info_ptr get_placement_info( - double scale_factor, dimension_type dim, bool has_dimensions) const; + text_placement_info_ptr get_placement_info(double scale_factor) const; friend class text_placement_info_dummy; }; @@ -43,9 +42,8 @@ public: class MAPNIK_DECL text_placement_info_dummy : public text_placement_info { public: - text_placement_info_dummy(text_placements_dummy const* parent, - double scale_factor, dimension_type dim, bool has_dimensions) - : text_placement_info(parent, scale_factor, dim, has_dimensions), + text_placement_info_dummy(text_placements_dummy const* parent, double scale_factor) + : text_placement_info(parent, scale_factor), state(0), parent_(parent) {} bool next(); private: diff --git a/include/mapnik/text_placements/list.hpp b/include/mapnik/text_placements/list.hpp index 951f00b79..e6e0adaea 100644 --- a/include/mapnik/text_placements/list.hpp +++ b/include/mapnik/text_placements/list.hpp @@ -33,8 +33,7 @@ class text_placements_list: public text_placements { public: text_placements_list(); - text_placement_info_ptr get_placement_info( - double scale_factor, dimension_type dim, bool has_dimensions) const; + text_placement_info_ptr get_placement_info(double scale_factor) const; virtual void add_expressions(expression_set &output); text_symbolizer_properties & add(); text_symbolizer_properties & get(unsigned i); @@ -50,9 +49,8 @@ private: class text_placement_info_list : public text_placement_info { public: - text_placement_info_list(text_placements_list const* parent, - double scale_factor, dimension_type dim, bool has_dimensions) : - text_placement_info(parent, scale_factor, dim, has_dimensions), + text_placement_info_list(text_placements_list const* parent, double scale_factor) : + text_placement_info(parent, scale_factor), state(0), parent_(parent) {} bool next(); private: diff --git a/include/mapnik/text_placements/simple.hpp b/include/mapnik/text_placements/simple.hpp index a7461ffad..4173ab87a 100644 --- a/include/mapnik/text_placements/simple.hpp +++ b/include/mapnik/text_placements/simple.hpp @@ -49,8 +49,7 @@ class text_placements_simple: public text_placements public: text_placements_simple(); text_placements_simple(std::string positions); - text_placement_info_ptr get_placement_info( - double scale_factor, dimension_type dim, bool has_dimensions) const; + text_placement_info_ptr get_placement_info(double scale_factor) const; void set_positions(std::string positions); std::string get_positions(); static text_placements_ptr from_xml(boost::property_tree::ptree const &xml, fontset_map const & fontsets); @@ -67,8 +66,8 @@ class text_placement_info_simple : public text_placement_info { public: text_placement_info_simple(text_placements_simple const* parent, - double scale_factor, dimension_type dim, bool has_dimensions) - : text_placement_info(parent, scale_factor, dim, has_dimensions), + double scale_factor) + : text_placement_info(parent, scale_factor), state(0), position_state(0), parent_(parent) { } diff --git a/src/placement_finder.cpp b/src/placement_finder.cpp index 38d1eccc6..0d91f81c2 100644 --- a/src/placement_finder.cpp +++ b/src/placement_finder.cpp @@ -426,22 +426,10 @@ void placement_finder::find_point_placement(double label_x, double la // overlap, minimum distance or edge avoidance - exit if condition occurs box2d e; /*x axis: left to right, y axis: top to bottom (negative values higher)*/ - if (pi.has_dimensions) - { - e.init(current_placement->center.x - (pi.dimensions.first/2.0), // Top Left - current_placement->center.y - (pi.dimensions.second/2.0), - - current_placement->center.x + (pi.dimensions.first/2.0), // Bottom Right - current_placement->center.y + (pi.dimensions.second/2.0)); - } - else - { - e.init(current_placement->center.x + dx, // Bottom Left - current_placement->center.y - dy - ci.ymin, /*ymin usually <0 */ - - current_placement->center.x + dx + ci.width, // Top Right - current_placement->center.y - dy - ci.ymax); - } + e.init(current_placement->center.x + dx, // Bottom Left + current_placement->center.y - dy - ci.ymin, // ymin usually <0 + current_placement->center.x + dx + ci.width, // Top Right + current_placement->center.y - dy - ci.ymax); // if there is an overlap with existing envelopes, then exit - no placement if (!detector_.extent().intersects(e) || (!p.allow_overlap && !detector_.has_point_placement(e, pi.get_actual_minimum_distance()))) { @@ -890,21 +878,12 @@ bool placement_finder::test_placement(const std::auto_ptr cosa = -cosa; } - box2d e; - if (pi.has_dimensions) - { - e.init(x, y, x + pi.dimensions.first, y + pi.dimensions.second); - } - else - { - // put four corners of the letter into envelope - e.init(x, y, x + cwidth*cosa, - y - cwidth*sina); - e.expand_to_include(x - ci.height()*sina, - y - ci.height()*cosa); - e.expand_to_include(x + (cwidth*cosa - ci.height()*sina), - y - (cwidth*sina + ci.height()*cosa)); - } + box2d e(x, y, x + cwidth*cosa, y - cwidth*sina); + // put four corners of the letter into envelope + e.expand_to_include(x - ci.height()*sina, + y - ci.height()*cosa); + e.expand_to_include(x + (cwidth*cosa - ci.height()*sina), + y - (cwidth*sina + ci.height()*cosa)); if (!detector_.extent().intersects(e) || !detector_.has_placement(e, info_.get_string(), pi.get_actual_minimum_distance())) diff --git a/src/text_placements/base.cpp b/src/text_placements/base.cpp index 72b1db405..c753d34da 100644 --- a/src/text_placements/base.cpp +++ b/src/text_placements/base.cpp @@ -35,11 +35,9 @@ void text_placements::add_expressions(expression_set &output) /************************************************************************/ text_placement_info::text_placement_info(text_placements const* parent, - double scale_factor_, dimension_type dim, bool has_dimensions_) + double scale_factor_) : properties(parent->defaults), - scale_factor(scale_factor_), - has_dimensions(has_dimensions_), - dimensions(dim) + scale_factor(scale_factor_) { } diff --git a/src/text_placements/dummy.cpp b/src/text_placements/dummy.cpp index 3c66b1f14..36c74597a 100644 --- a/src/text_placements/dummy.cpp +++ b/src/text_placements/dummy.cpp @@ -33,10 +33,10 @@ bool text_placement_info_dummy::next() } text_placement_info_ptr text_placements_dummy::get_placement_info( - double scale_factor, dimension_type dim, bool has_dimensions) const + double scale_factor) const { return text_placement_info_ptr(boost::make_shared( - this, scale_factor, dim, has_dimensions)); + this, scale_factor)); } } //ns mapnik diff --git a/src/text_placements/list.cpp b/src/text_placements/list.cpp index 80762baea..a9bc7aa83 100644 --- a/src/text_placements/list.cpp +++ b/src/text_placements/list.cpp @@ -56,11 +56,10 @@ text_symbolizer_properties & text_placements_list::get(unsigned i) /***************************************************************************/ -text_placement_info_ptr text_placements_list::get_placement_info( - double scale_factor, dimension_type dim, bool has_dimensions) const +text_placement_info_ptr text_placements_list::get_placement_info(double scale_factor) const { - return text_placement_info_ptr(boost::make_shared(this, - scale_factor, dim, has_dimensions)); + return text_placement_info_ptr( + boost::make_shared(this, scale_factor)); } text_placements_list::text_placements_list() : text_placements(), list_(0) diff --git a/src/text_placements/simple.cpp b/src/text_placements/simple.cpp index 49151066f..598082e55 100644 --- a/src/text_placements/simple.cpp +++ b/src/text_placements/simple.cpp @@ -100,10 +100,10 @@ bool text_placement_info_simple::next_position_only() } text_placement_info_ptr text_placements_simple::get_placement_info( - double scale_factor, dimension_type dim, bool has_dimensions) const + double scale_factor) const { - return text_placement_info_ptr(boost::make_shared(this, - scale_factor, dim, has_dimensions)); + return text_placement_info_ptr( + boost::make_shared(this, scale_factor)); } /** Position string: [POS][SIZE] From ad86e9aebca1d56e16969a0ca91b21c934549244 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 4 Mar 2012 23:25:13 +0100 Subject: [PATCH 014/238] Reenable text meta writers. --- include/mapnik/font_engine_freetype.hpp | 8 +++- include/mapnik/metawriter.hpp | 8 ++-- include/mapnik/metawriter_inmem.hpp | 4 +- include/mapnik/metawriter_json.hpp | 4 +- include/mapnik/placement_finder.hpp | 2 + include/mapnik/processed_text.hpp | 1 + include/mapnik/symbolizer_helpers.hpp | 3 ++ src/agg/agg_renderer.cpp | 1 - src/cairo_renderer.cpp | 1 + src/font_engine_freetype.cpp | 1 + src/grid/grid_renderer.cpp | 1 - src/metawriter.cpp | 59 +++++++++++-------------- src/metawriter_inmem.cpp | 17 ++++--- src/symbolizer_helpers.cpp | 7 +-- 14 files changed, 60 insertions(+), 57 deletions(-) diff --git a/include/mapnik/font_engine_freetype.hpp b/include/mapnik/font_engine_freetype.hpp index 7e881e35f..12eb518bf 100644 --- a/include/mapnik/font_engine_freetype.hpp +++ b/include/mapnik/font_engine_freetype.hpp @@ -28,8 +28,9 @@ #include #include #include -#include #include +#include +#include // freetype2 extern "C" @@ -56,9 +57,14 @@ extern "C" #include #include +// uci +#include + namespace mapnik { class font_face; +class text_path; +class string_info; typedef boost::shared_ptr face_ptr; diff --git a/include/mapnik/metawriter.hpp b/include/mapnik/metawriter.hpp index a9f09f3e8..c5644241d 100644 --- a/include/mapnik/metawriter.hpp +++ b/include/mapnik/metawriter.hpp @@ -24,9 +24,8 @@ #define MAPNIK_METAWRITER_HPP // mapnik -#include #include -#include +#include // boost #include @@ -42,6 +41,7 @@ namespace mapnik { class text_placement_info; +class text_path; /** Implementation of std::map that also returns const& for operator[]. */ class metawriter_property_map @@ -103,8 +103,8 @@ public: virtual void add_box(box2d const& box, Feature const& feature, CoordTransform const& t, metawriter_properties const& properties)=0; - virtual void add_text(text_placement_info const& placement, - face_manager_freetype &font_manager, + virtual void add_text(boost::ptr_vector &placements, + box2d const& extents, Feature const& feature, CoordTransform const& t, metawriter_properties const& properties)=0; diff --git a/include/mapnik/metawriter_inmem.hpp b/include/mapnik/metawriter_inmem.hpp index 044ed64b6..ee57f2a6b 100644 --- a/include/mapnik/metawriter_inmem.hpp +++ b/include/mapnik/metawriter_inmem.hpp @@ -65,8 +65,8 @@ public: virtual void add_box(box2d const& box, Feature const& feature, CoordTransform const& t, metawriter_properties const& properties); - virtual void add_text(text_placement_info const& p, - face_manager_freetype &font_manager, + virtual void add_text(boost::ptr_vector &placements, + box2d const& extents, Feature const& feature, CoordTransform const& t, metawriter_properties const& properties); diff --git a/include/mapnik/metawriter_json.hpp b/include/mapnik/metawriter_json.hpp index 029e52973..1a51b7609 100644 --- a/include/mapnik/metawriter_json.hpp +++ b/include/mapnik/metawriter_json.hpp @@ -45,8 +45,8 @@ public: virtual void add_box(box2d const& box, Feature const& feature, CoordTransform const& t, metawriter_properties const& properties); - virtual void add_text(text_placement_info const& p, - face_manager_freetype &font_manager, + virtual void add_text(boost::ptr_vector &placements, + box2d const& extents, Feature const& feature, CoordTransform const& t, metawriter_properties const& properties); diff --git a/include/mapnik/placement_finder.hpp b/include/mapnik/placement_finder.hpp index 4e3a74eff..90561eaf1 100644 --- a/include/mapnik/placement_finder.hpp +++ b/include/mapnik/placement_finder.hpp @@ -79,6 +79,8 @@ public: void set_collect_extents(bool collect) { collect_extents_ = collect; } bool get_collect_extents() const { return collect_extents_; } + box2d const& get_extents() const { return extents_; } + private: ///Helpers for find_line_placement diff --git a/include/mapnik/processed_text.hpp b/include/mapnik/processed_text.hpp index 2c4dcd295..54c2958e5 100644 --- a/include/mapnik/processed_text.hpp +++ b/include/mapnik/processed_text.hpp @@ -25,6 +25,7 @@ // mapnik #include #include +#include namespace mapnik { diff --git a/include/mapnik/symbolizer_helpers.hpp b/include/mapnik/symbolizer_helpers.hpp index 0b2af948c..5e3894067 100644 --- a/include/mapnik/symbolizer_helpers.hpp +++ b/include/mapnik/symbolizer_helpers.hpp @@ -22,6 +22,7 @@ #ifndef SYMBOLIZER_HELPERS_HPP #define SYMBOLIZER_HELPERS_HPP +//mapnik #include #include #include @@ -29,7 +30,9 @@ #include #include #include +#include +//boost #include diff --git a/src/agg/agg_renderer.cpp b/src/agg/agg_renderer.cpp index ead94bdae..ac618ee56 100644 --- a/src/agg/agg_renderer.cpp +++ b/src/agg/agg_renderer.cpp @@ -29,7 +29,6 @@ #include #include #include -#include #include #include #include diff --git a/src/cairo_renderer.cpp b/src/cairo_renderer.cpp index 815bcf497..85cc7592d 100644 --- a/src/cairo_renderer.cpp +++ b/src/cairo_renderer.cpp @@ -39,6 +39,7 @@ #include #include #include +#include // cairo #include diff --git a/src/font_engine_freetype.cpp b/src/font_engine_freetype.cpp index 6b115275e..754a106dd 100644 --- a/src/font_engine_freetype.cpp +++ b/src/font_engine_freetype.cpp @@ -25,6 +25,7 @@ #include #include #include +#include // boost #include diff --git a/src/grid/grid_renderer.cpp b/src/grid/grid_renderer.cpp index de584f103..754356a46 100644 --- a/src/grid/grid_renderer.cpp +++ b/src/grid/grid_renderer.cpp @@ -34,7 +34,6 @@ #include #include #include -#include #include #include #include diff --git a/src/metawriter.cpp b/src/metawriter.cpp index 46eb27e50..e373ea973 100644 --- a/src/metawriter.cpp +++ b/src/metawriter.cpp @@ -24,6 +24,7 @@ #include #include #include +#include // Boost #include @@ -174,13 +175,11 @@ void metawriter_json_stream::add_box(box2d const &box, Feature const& fe } -void metawriter_json_stream::add_text(text_placement_info const& p, - face_manager_freetype &font_manager, - Feature const& feature, - CoordTransform const& t, - metawriter_properties const& properties) +void metawriter_json_stream::add_text( + boost::ptr_vector &placements, box2d const& extents, + Feature const& feature, CoordTransform const& t, + metawriter_properties const& properties) { -#if 0 /* Note: Map coordinate system (and starting_{x,y}) starts in upper left corner and grows towards lower right corner. @@ -193,19 +192,19 @@ void metawriter_json_stream::add_text(text_placement_info const& p, Hightest y = baseline of top line */ - for (unsigned n = 0; n < p.placements.size(); n++) { - text_path & current_placement = const_cast(p.placements[n]); + for (unsigned n = 0; n < placements.size(); n++) + { + text_path ¤t_placement = placements[n]; bool inside = false; /* Part of text is inside rendering region */ bool straight = true; - int c; + char_info_ptr c; double x, y, angle; - char_properties *format; current_placement.rewind(); for (int i = 0; i < current_placement.num_nodes(); ++i) { int cx = current_placement.center.x; int cy = current_placement.center.y; - current_placement.vertex(&c, &x, &y, &angle, &format); + current_placement.vertex(&c, &x, &y, &angle); if (cx+x >= 0 && cx+x < width_ && cy-y >= 0 && cy-y < height_) inside = true; if (angle > 0.001 || angle < -0.001) straight = false; if (inside && !straight) break; @@ -218,13 +217,11 @@ void metawriter_json_stream::add_text(text_placement_info const& p, //Reduce number of polygons double minx = INT_MAX, miny = INT_MAX, maxx = INT_MIN, maxy = INT_MIN; for (int i = 0; i < current_placement.num_nodes(); ++i) { - current_placement.vertex(&c, &x, &y, &angle, &format); - face_set_ptr face = font_manager.get_face_set(format->face_name, format->fontset); - char_info ci = face->character_dimensions(c); + current_placement.vertex(&c, &x, &y, &angle); minx = std::min(minx, x); - maxx = std::max(maxx, x+ci.width); - maxy = std::max(maxy, y+ci.ymax); - miny = std::min(miny, y+ci.ymin); + maxx = std::max(maxx, x+c->width); + maxy = std::max(maxy, y+c->ymax); + miny = std::min(miny, y+c->ymin); } add_box(box2d(current_placement.center.x+minx, current_placement.center.y-miny, @@ -235,27 +232,22 @@ void metawriter_json_stream::add_text(text_placement_info const& p, write_feature_header("MultiPolygon"); *f_ << "["; - c = ' '; for (int i = 0; i < current_placement.num_nodes(); ++i) { - if (c != ' ') { - *f_ << ","; - } - current_placement.vertex(&c, &x, &y, &angle, &format); - if (c == ' ') continue; - face_set_ptr face = font_manager.get_face_set(format->face_name, format->fontset); - char_info ci = face->character_dimensions(c); + current_placement.vertex(&c, &x, &y, &angle); + if (c->c == ' ') continue; + *f_ << ","; double x0, y0, x1, y1, x2, y2, x3, y3; double sina = sin(angle); double cosa = cos(angle); - x0 = current_placement.center.x + x - sina*ci.ymin; - y0 = current_placement.center.y - y - cosa*ci.ymin; - x1 = x0 + ci.width * cosa; - y1 = y0 - ci.width * sina; - x2 = x0 + (ci.width * cosa - ci.height() * sina); - y2 = y0 - (ci.width * sina + ci.height() * cosa); - x3 = x0 - ci.height() * sina; - y3 = y0 - ci.height() * cosa; + x0 = current_placement.center.x + x - sina*c->ymin; + y0 = current_placement.center.y - y - cosa*c->ymin; + x1 = x0 + c->width * cosa; + y1 = y0 - c->width * sina; + x2 = x0 + (c->width * cosa - c->height() * sina); + y2 = y0 - (c->width * sina + c->height() * cosa); + x3 = x0 - c->height() * sina; + y3 = y0 - c->height() * cosa; *f_ << "\n [["; write_point(t, x0, y0); @@ -267,7 +259,6 @@ void metawriter_json_stream::add_text(text_placement_info const& p, *f_ << "]"; write_properties(feature, properties); } -#endif } void metawriter_json_stream::add_polygon(path_type & path, diff --git a/src/metawriter_inmem.cpp b/src/metawriter_inmem.cpp index 22ed373b8..744b910dc 100644 --- a/src/metawriter_inmem.cpp +++ b/src/metawriter_inmem.cpp @@ -70,21 +70,20 @@ metawriter_inmem::add_box(box2d const& box, Feature const& feature, } void -metawriter_inmem::add_text(text_placement_info const& p, - face_manager_freetype & /*face*/, - Feature const& feature, - CoordTransform const& /*t*/, - metawriter_properties const& properties) +metawriter_inmem::add_text( + boost::ptr_vector & /*text*/, + box2d const& extents, + Feature const& feature, + CoordTransform const& /*t*/, + metawriter_properties const& properties) { -#if 0 - if (p.extents.valid()) + if (extents.valid()) { meta_instance inst; inst.properties = intersect_properties(feature, properties); - inst.box = p.extents; + inst.box = extents; instances_.push_back(inst); } -#endif } void diff --git a/src/symbolizer_helpers.cpp b/src/symbolizer_helpers.cpp index 1722d9638..9672e95dc 100644 --- a/src/symbolizer_helpers.cpp +++ b/src/symbolizer_helpers.cpp @@ -67,7 +67,7 @@ bool text_symbolizer_helper::next_line_placement() } geo_itr_ = geometries_to_process_.erase(geo_itr_); if (writer_.first) writer_.first->add_text( - *placement_, font_manager_, + finder_->get_results(), finder_->get_extents(), feature_, t_, writer_.second); return true; } @@ -97,7 +97,7 @@ bool text_symbolizer_helper::next_point_placement() //Found a placement point_itr_ = points_.erase(point_itr_); if (writer_.first) writer_.first->add_text( - *placement_, font_manager_, + finder_->get_results(), finder_->get_extents(), feature_, t_, writer_.second); finder_->update_detector(); return true; @@ -309,7 +309,8 @@ bool shield_symbolizer_helper::next_point_placement() finder_->update_detector(); if (writer_.first) { writer_.first->add_box(marker_ext_, feature_, t_, writer_.second); - writer_.first->add_text(*placement_, font_manager_, feature_, t_, writer_.second); + writer_.first->add_text(finder_->get_results(), finder_->get_extents(), + feature_, t_, writer_.second); } point_itr_ = points_.erase(point_itr_); return true; From ab4c9da7ad5729e55e05f7c16c318d5edbb584c2 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Sun, 4 Mar 2012 22:28:02 -0800 Subject: [PATCH 015/238] fixup ogr plugin error string output --- plugins/input/ogr/ogr_datasource.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/input/ogr/ogr_datasource.cpp b/plugins/input/ogr/ogr_datasource.cpp index 0e6035a3c..84458760d 100644 --- a/plugins/input/ogr/ogr_datasource.cpp +++ b/plugins/input/ogr/ogr_datasource.cpp @@ -214,24 +214,24 @@ void ogr_datasource::bind() const if (! layer_.is_valid()) { - std::string s("OGR Plugin: "); + std::ostringstream s("OGR Plugin: "); if (layer_by_name) { - s += "cannot find layer by name '" + *layer_by_name; + s << "cannot find layer by name '" << *layer_by_name; } else if (layer_by_index) { - s += "cannot find layer by index number '" + *layer_by_index; + s << "cannot find layer by index number '" << *layer_by_index; } else if (layer_by_sql) { - s += "cannot find layer by sql query '" + *layer_by_sql; + s << "cannot find layer by sql query '" << *layer_by_sql; } - s += "' in dataset '" + dataset_name_ + "'"; + s << "' in dataset '" << dataset_name_ << "'"; - throw datasource_exception(s); + throw datasource_exception(s.str()); } // work with real OGR layer From 481271cb76a4a297bebbf6e4ceb81b5236155fb2 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Mon, 5 Mar 2012 16:49:54 +0100 Subject: [PATCH 016/238] Add new XML data structure and modify XML parser to work with this structure. --- include/mapnik/xml_tree.hpp | 78 ++++++++++++++++++++++++++++ src/build.py | 1 + src/libxml2_loader.cpp | 100 ++++++++++++++++-------------------- src/xml_tree.cpp | 87 +++++++++++++++++++++++++++++++ 4 files changed, 209 insertions(+), 57 deletions(-) create mode 100644 include/mapnik/xml_tree.hpp create mode 100644 src/xml_tree.cpp diff --git a/include/mapnik/xml_tree.hpp b/include/mapnik/xml_tree.hpp new file mode 100644 index 000000000..b34bb9376 --- /dev/null +++ b/include/mapnik/xml_tree.hpp @@ -0,0 +1,78 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2012 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 + * + *****************************************************************************/ + +#ifndef MAPNIK_XML_TREE_H +#define MAPNIK_XML_TREE_H + +#include +#include +#include + +namespace mapnik +{ +class xml_tree; + +class xml_attribute +{ +public: + std::string value; + bool processed; +}; + +class xml_node +{ +public: + xml_node(xml_tree &tree, std::string name, unsigned line=0, bool text_node = false); + + std::string name() const; + std::string text() const; + + xml_node &add_child(std::string name, unsigned line=0, bool text_node = false); + void add_attribute(std::string name, std::string value); + void set_processed(bool processed); +private: + xml_tree &tree_; + std::string name_; + std::list children_; + std::map attributes_; + bool text_node_; + unsigned line_; + bool processed_; + +}; + +class xml_tree +{ +public: + xml_tree(); + void set_filename(std::string fn); + std::string filename() const; + xml_node &node(); +private: + xml_node node_; + std::string file_; + //TODO: Grammars +}; + +} //ns mapnik + +#endif // MAPNIK_XML_TREE_H diff --git a/src/build.py b/src/build.py index c25bc2fb5..b19789d3e 100644 --- a/src/build.py +++ b/src/build.py @@ -178,6 +178,7 @@ source = Split( text_placements/list.cpp text_placements/simple.cpp text_properties.cpp + xml_tree.cpp """ ) diff --git a/src/libxml2_loader.cpp b/src/libxml2_loader.cpp index c4111696f..f67f0e0db 100644 --- a/src/libxml2_loader.cpp +++ b/src/libxml2_loader.cpp @@ -22,26 +22,27 @@ #ifdef HAVE_LIBXML2 +// mapnik #include - +#include #include +// boost #include -#include #include #include +// libxml #include #include #include #include +// stl #include -using boost::property_tree::ptree; using namespace std; -//#define DEFAULT_OPTIONS (XML_PARSE_NOENT | XML_PARSE_NOBLANKS | XML_PARSE_DTDLOAD | XML_PARSE_NOCDATA) #define DEFAULT_OPTIONS (XML_PARSE_NOERROR | XML_PARSE_NOENT | XML_PARSE_NOBLANKS | XML_PARSE_DTDLOAD | XML_PARSE_NOCDATA) namespace mapnik @@ -50,14 +51,14 @@ class libxml2_loader : boost::noncopyable { public: libxml2_loader(const char *encoding = NULL, int options = DEFAULT_OPTIONS, const char *url = NULL) : - ctx_( 0 ), - encoding_( encoding ), - options_( options ), - url_( url ) + ctx_(0), + encoding_(encoding), + options_(options), + url_(url) { LIBXML_TEST_VERSION; ctx_ = xmlNewParserCtxt(); - if ( ! ctx_ ) + if (!ctx_) { throw std::runtime_error("Failed to create parser context."); } @@ -71,19 +72,20 @@ public: } } - void load( const std::string & filename, ptree & pt ) + void load(const std::string & filename, xml_node &node) { boost::filesystem::path path(filename); - if ( !boost::filesystem::exists( path ) ) { + if (!boost::filesystem::exists(path)) + { throw config_error(string("Could not load map file '") + filename + "': File does not exist"); } xmlDocPtr doc = xmlCtxtReadFile(ctx_, filename.c_str(), encoding_, options_); - if ( !doc ) + if (!doc) { - xmlError * error = xmlCtxtGetLastError( ctx_ ); + xmlError * error = xmlCtxtGetLastError(ctx_); if (error) { std::ostringstream os; @@ -91,13 +93,13 @@ public: os << ": " << std::endl << error->message; // remove CR std::string msg = os.str().substr(0, os.str().size() - 1); - config_error ex( msg ); + config_error ex(msg); os.str(""); os << "(encountered in file '" << error->file << "' at line " << error->line << ")"; - ex.append_context( os.str() ); + ex.append_context(os.str()); throw ex; } @@ -110,21 +112,21 @@ public: << std::endl; } */ - load(doc, pt); + load(doc, node); } - void load( const int fd, ptree & pt ) + void load(const int fd, xml_node &node) { xmlDocPtr doc = xmlCtxtReadFd(ctx_, fd, url_, encoding_, options_); - load(doc, pt); + load(doc, node); } - void load_string( const std::string & buffer, ptree & pt, std::string const & base_path ) + void load_string(const std::string & buffer, xml_node &node, std::string const & base_path ) { if (!base_path.empty()) { boost::filesystem::path path(base_path); - if ( ! boost::filesystem::exists( path ) ) { + if (!boost::filesystem::exists(path)) { throw config_error(string("Could not locate base_path '") + base_path + "': file or directory does not exist"); } @@ -132,12 +134,12 @@ public: xmlDocPtr doc = xmlCtxtReadMemory(ctx_, buffer.data(), buffer.length(), base_path.c_str(), encoding_, options_); - load(doc, pt); + load(doc, node); } - void load( const xmlDocPtr doc, ptree & pt ) + void load(const xmlDocPtr doc, xml_node &node) { - if ( !doc ) + if (!doc) { xmlError * error = xmlCtxtGetLastError( ctx_ ); std::ostringstream os; @@ -149,7 +151,7 @@ public: throw config_error(os.str()); } - int iXIncludeReturn = xmlXIncludeProcessFlags( doc, options_ ); + int iXIncludeReturn = xmlXIncludeProcessFlags(doc, options_); if (iXIncludeReturn < 0) { @@ -157,63 +159,47 @@ public: throw config_error("XML XInclude error. One or more files failed to load."); } - xmlNode * root = xmlDocGetRootElement( doc ); - if ( ! root ) { + xmlNode * root = xmlDocGetRootElement(doc); + if (!root) { xmlFreeDoc(doc); throw config_error("XML document is empty."); } - populate_tree( root, pt ); + populate_tree(root, node); xmlFreeDoc(doc); } private: - void append_attributes( xmlAttr * attributes, ptree & pt) + void append_attributes(xmlAttr *attributes, xml_node &node) { - if (attributes) + for (; attributes; attributes = attributes->next ) { - ptree::iterator it = pt.push_back( ptree::value_type( "", ptree() )); - ptree & attr_list = it->second; - xmlAttr * cur_attr = attributes; - for (; cur_attr; cur_attr = cur_attr->next ) - { - ptree::iterator it = attr_list.push_back( - ptree::value_type( (char*)cur_attr->name, ptree() )); - it->second.put_value( (char*) cur_attr->children->content ); - } + node.add_attribute((char *)attributes->name, (char *)attributes->children->content); } } - void populate_tree( xmlNode * node, ptree & pt ) + void populate_tree(xmlNode *cur_node, xml_node &node) { - xmlNode * cur_node = node; - for (; cur_node; cur_node = cur_node->next ) { switch (cur_node->type) { case XML_ELEMENT_NODE: { - ptree::iterator it = pt.push_back( ptree::value_type( - (char*)cur_node->name, ptree() )); - append_attributes( cur_node->properties, it->second); - populate_tree( cur_node->children, it->second ); + + xml_node &new_node = node.add_child((char *)cur_node->name, cur_node->line, false); + append_attributes(cur_node->properties, new_node); + populate_tree(cur_node->children, new_node); } break; case XML_TEXT_NODE: { std::string trimmed = boost::algorithm::trim_copy(std::string((char*)cur_node->content)); - if (trimmed.empty()) break; - ptree::iterator it = pt.push_back(ptree::value_type("", ptree())); - it->second.put_value(trimmed); + if (trimmed.empty()) break; //Don't add empty text nodes + node.add_child(trimmed, cur_node->line, true); } break; case XML_COMMENT_NODE: - { - ptree::iterator it = pt.push_back( - ptree::value_type( "", ptree() )); - it->second.put_value( (char*) cur_node->content ); - } break; default: break; @@ -228,15 +214,15 @@ private: const char *url_; }; -void read_xml2( std::string const & filename, boost::property_tree::ptree & pt) +void read_xml2(std::string const & filename, xml_node &node) { libxml2_loader loader; - loader.load( filename, pt ); + loader.load(filename, node); } -void read_xml2_string( std::string const & str, boost::property_tree::ptree & pt, std::string const & base_path) +void read_xml2_string(std::string const & str, xml_node &node, std::string const & base_path) { libxml2_loader loader; - loader.load_string( str, pt, base_path ); + loader.load_string(str, node, base_path); } } // end of namespace mapnik diff --git a/src/xml_tree.cpp b/src/xml_tree.cpp new file mode 100644 index 000000000..3a05a82ac --- /dev/null +++ b/src/xml_tree.cpp @@ -0,0 +1,87 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2012 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 + * + *****************************************************************************/ +#include + +namespace mapnik +{ + +xml_tree::xml_tree() + : node_(*this, "") +{ +} + +void xml_tree::set_filename(std::string fn) +{ + file_ = fn; +} + +std::string xml_tree::filename() const +{ + return file_; +} + +xml_node &xml_tree::node() +{ + return node_; +} + +/****************************************************************************/ + +xml_node::xml_node(xml_tree &tree, std::string name, unsigned line, bool text_node) + : tree_(tree), + name_(name), + text_node_(text_node), + line_(line), + processed_(false) +{ + +} + +std::string xml_node::name() const +{ + if (!text_node_) + return name_; + else + return ""; +} + +std::string xml_node::text() const +{ + if (text_node_) + return name_; + else + return "NOT A TEXT NODE"; +} + +void xml_node::set_processed(bool processed) +{ + processed_ = processed; +} + +xml_node &xml_node::add_child(std::string name, unsigned line, bool text_node) +{ + children_.push_back(xml_node(tree_, name, line, text_node)); + return children_.back(); +} + + +} //ns mapnik From 232256e136056ddac12d8b8d5a41cc20c27cc26f Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Mon, 5 Mar 2012 10:44:20 -0800 Subject: [PATCH 017/238] load_map_string: do not pass optional arg by const& and better error if string is empty --- include/mapnik/load_map.hpp | 2 +- src/load_map.cpp | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/include/mapnik/load_map.hpp b/include/mapnik/load_map.hpp index 3dbba801f..7858c9395 100644 --- a/include/mapnik/load_map.hpp +++ b/include/mapnik/load_map.hpp @@ -32,7 +32,7 @@ namespace mapnik { MAPNIK_DECL void load_map(Map & map, std::string const& filename, bool strict = false); -MAPNIK_DECL void load_map_string(Map & map, std::string const& str, bool strict = false, std::string const& base_path=""); +MAPNIK_DECL void load_map_string(Map & map, std::string const& str, bool strict = false, std::string base_path=""); } #endif // MAPNIK_LOAD_MAP_HPP diff --git a/src/load_map.cpp b/src/load_map.cpp index ddb076e80..8afdd97a4 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -184,8 +184,13 @@ void load_map(Map & map, std::string const& filename, bool strict) parser.parse_map(map, pt); } -void load_map_string(Map & map, std::string const& str, bool strict, std::string const& base_path) +void load_map_string(Map & map, std::string const& str, bool strict, std::string base_path) { + if (str.empty()) + { + throw config_error( "Cannot load map, XML string is empty" ) ; + } + ptree pt; #ifdef HAVE_LIBXML2 if (!base_path.empty()) From 6462af3a02442bbfe2f47957efdc875bcc0033d3 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Mon, 5 Mar 2012 11:09:34 -0800 Subject: [PATCH 018/238] add to c++ style docs note about const& convention --- docs/contributing.markdown | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/contributing.markdown b/docs/contributing.markdown index b3fba320a..d733242d6 100644 --- a/docs/contributing.markdown +++ b/docs/contributing.markdown @@ -95,6 +95,12 @@ If you see bits of code around that do not follow these please don't hesitate to (int)value; // no +#### Use const keyword after the type + + std::string const& variable_name // preferred, for consistency + + std::string const& variable_name // no + #### Shared pointers should be created with [boost::make_shared](http://www.boost.org/doc/libs/1_47_0/libs/smart_ptr/make_shared.html) where possible #### Function definitions should not be separated from their arguments: From f90b410ece17d6238d5e7336c78eab5d5aea4e10 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Mon, 5 Mar 2012 11:10:04 -0800 Subject: [PATCH 019/238] formatting to use const& as per 6462af3 --- bindings/python/mapnik_map.cpp | 4 ++-- include/mapnik/config_error.hpp | 4 ++-- include/mapnik/enumeration.hpp | 6 +++--- include/mapnik/image_util.hpp | 2 +- include/mapnik/value_error.hpp | 4 ++-- plugins/input/osm/demo/MapSource.h | 2 +- plugins/input/postgis/postgis_datasource.cpp | 2 +- plugins/input/postgis/postgis_datasource.hpp | 2 +- src/libxml2_loader.cpp | 4 ++-- src/load_map.cpp | 4 ++-- src/text_properties.cpp | 6 +++--- 11 files changed, 20 insertions(+), 20 deletions(-) diff --git a/bindings/python/mapnik_map.cpp b/bindings/python/mapnik_map.cpp index ab89de292..9e1287420 100644 --- a/bindings/python/mapnik_map.cpp +++ b/bindings/python/mapnik_map.cpp @@ -62,7 +62,7 @@ struct map_pickle_suite : boost::python::pickle_suite Map::const_style_iterator end = m.styles().end(); for (; it != end; ++it) { - const std::string & name = it->first; + std::string const& name = it->first; const mapnik::feature_type_style & style = it->second; boost::python::tuple style_pair = boost::python::make_tuple(name,style); s.append(style_pair); @@ -153,7 +153,7 @@ bool has_metawriter(mapnik::Map const& m) // returns empty shared_ptr when the metawriter isn't found, or is // of the wrong type. empty pointers make it back to Python as a None. -mapnik::metawriter_inmem_ptr find_inmem_metawriter(const mapnik::Map &m, const std::string &name) { +mapnik::metawriter_inmem_ptr find_inmem_metawriter(const mapnik::Map &m, std::string const&name) { mapnik::metawriter_ptr metawriter = m.find_metawriter(name); mapnik::metawriter_inmem_ptr inmem; diff --git a/include/mapnik/config_error.hpp b/include/mapnik/config_error.hpp index b25ae07f1..1e720c5ca 100644 --- a/include/mapnik/config_error.hpp +++ b/include/mapnik/config_error.hpp @@ -33,7 +33,7 @@ class config_error : public std::exception public: config_error() {} - config_error( const std::string & what ) : + config_error( std::string const& what ) : what_( what ) { } @@ -44,7 +44,7 @@ public: return what_.c_str(); } - void append_context(const std::string & ctx) const + void append_context(std::string const& ctx) const { what_ += " " + ctx; } diff --git a/include/mapnik/enumeration.hpp b/include/mapnik/enumeration.hpp index 0e069e4f1..7dc5e2e1d 100644 --- a/include/mapnik/enumeration.hpp +++ b/include/mapnik/enumeration.hpp @@ -39,7 +39,7 @@ class illegal_enum_value : public std::exception public: illegal_enum_value() {} - illegal_enum_value( const std::string & what ) : + illegal_enum_value( std::string const& what ) : what_( what ) { } @@ -171,7 +171,7 @@ public: /** Converts @p str to an enum. * @throw illegal_enum_value @p str is not a legal identifier. * */ - void from_string(const std::string & str) + void from_string(std::string const& str) { for (unsigned i = 0; i < THE_MAX; ++i) { @@ -267,7 +267,7 @@ public: } return true; } - static const std::string & get_full_qualified_name() + static std::string const& get_full_qualified_name() { return our_name_; } diff --git a/include/mapnik/image_util.hpp b/include/mapnik/image_util.hpp index efbed9eea..4ff3a08b2 100644 --- a/include/mapnik/image_util.hpp +++ b/include/mapnik/image_util.hpp @@ -147,7 +147,7 @@ inline boost::optional type_from_filename(std::string const& filena return result_type(); } -inline std::string guess_type( const std::string & filename ) +inline std::string guess_type( std::string const& filename ) { std::string::size_type idx = filename.find_last_of("."); if ( idx != std::string::npos ) { diff --git a/include/mapnik/value_error.hpp b/include/mapnik/value_error.hpp index 7239dd450..5b53664b9 100644 --- a/include/mapnik/value_error.hpp +++ b/include/mapnik/value_error.hpp @@ -33,7 +33,7 @@ class value_error : public std::exception public: value_error() {} - value_error( const std::string & what ) : + value_error( std::string const& what ) : what_( what ) { } @@ -44,7 +44,7 @@ public: return what_.c_str(); } - void append_context(const std::string & ctx) const + void append_context(std::string const& ctx) const { what_ += " " + ctx; } diff --git a/plugins/input/osm/demo/MapSource.h b/plugins/input/osm/demo/MapSource.h index c51970c47..bf7eb3ede 100755 --- a/plugins/input/osm/demo/MapSource.h +++ b/plugins/input/osm/demo/MapSource.h @@ -70,7 +70,7 @@ public: (source=="api" && hasBbox() && zoom_start>=0 && tiled==true)); } - void setSource(const std::string & src) + void setSource(std::string const& src) { if(src=="api" || src=="osm") { diff --git a/plugins/input/postgis/postgis_datasource.cpp b/plugins/input/postgis/postgis_datasource.cpp index 0c5f083e8..8e7556ed7 100644 --- a/plugins/input/postgis/postgis_datasource.cpp +++ b/plugins/input/postgis/postgis_datasource.cpp @@ -423,7 +423,7 @@ std::string postgis_datasource::populate_tokens(const std::string& sql, double s } -boost::shared_ptr postgis_datasource::get_resultset(boost::shared_ptr const &conn, const std::string &sql) const +boost::shared_ptr postgis_datasource::get_resultset(boost::shared_ptr const &conn, std::string const& sql) const { if (cursor_fetch_size_ > 0) { diff --git a/plugins/input/postgis/postgis_datasource.hpp b/plugins/input/postgis/postgis_datasource.hpp index 846664de1..047796e4f 100644 --- a/plugins/input/postgis/postgis_datasource.hpp +++ b/plugins/input/postgis/postgis_datasource.hpp @@ -92,7 +92,7 @@ private: std::string populate_tokens(const std::string& sql, double scale_denom, box2d const& env) const; std::string populate_tokens(const std::string& sql) const; static std::string unquote(const std::string& sql); - boost::shared_ptr get_resultset(boost::shared_ptr const &conn, const std::string &sql) const; + boost::shared_ptr get_resultset(boost::shared_ptr const &conn, std::string const& sql) const; postgis_datasource(const postgis_datasource&); postgis_datasource& operator=(const postgis_datasource&); }; diff --git a/src/libxml2_loader.cpp b/src/libxml2_loader.cpp index c4111696f..af9ff4de9 100644 --- a/src/libxml2_loader.cpp +++ b/src/libxml2_loader.cpp @@ -71,7 +71,7 @@ public: } } - void load( const std::string & filename, ptree & pt ) + void load( std::string const& filename, ptree & pt ) { boost::filesystem::path path(filename); if ( !boost::filesystem::exists( path ) ) { @@ -119,7 +119,7 @@ public: load(doc, pt); } - void load_string( const std::string & buffer, ptree & pt, std::string const & base_path ) + void load_string( std::string const& buffer, ptree & pt, std::string const & base_path ) { if (!base_path.empty()) { diff --git a/src/load_map.cpp b/src/load_map.cpp index 8afdd97a4..eea4353a8 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -122,7 +122,7 @@ private: void parse_stroke(stroke & strk, ptree const & sym); expression_ptr parse_expr(std::string const& expr); - void ensure_font_face( const std::string & face_name ); + void ensure_font_face( std::string const& face_name ); std::string ensure_relative_to_xml( boost::optional opt_path ); void ensure_attrs( ptree const& sym, std::string name, std::string attrs); @@ -1793,7 +1793,7 @@ void map_parser::parse_raster_colorizer(raster_colorizer_ptr const& rc, } } -void map_parser::ensure_font_face( const std::string & face_name ) +void map_parser::ensure_font_face( std::string const& face_name ) { if ( ! font_manager_.get_face( face_name ) ) { diff --git a/src/text_properties.cpp b/src/text_properties.cpp index 864cdd4ec..fc9253156 100644 --- a/src/text_properties.cpp +++ b/src/text_properties.cpp @@ -125,7 +125,7 @@ void text_symbolizer_properties::to_xml(boost::property_tree::ptree &node, bool { if (orientation) { - const std::string & orientationstr = to_expression_string(*orientation); + std::string const& orientationstr = to_expression_string(*orientation); if (!dfl.orientation || orientationstr != to_expression_string(*(dfl.orientation)) || explicit_defaults) { set_attr(node, "orientation", orientationstr); } @@ -280,8 +280,8 @@ void char_properties::from_xml(boost::property_tree::ptree const &sym, fontset_m void char_properties::to_xml(boost::property_tree::ptree &node, bool explicit_defaults, char_properties const &dfl) const { - const std::string & fontset_name = fontset.get_name(); - const std::string & dfl_fontset_name = dfl.fontset.get_name(); + std::string const& fontset_name = fontset.get_name(); + std::string const& dfl_fontset_name = dfl.fontset.get_name(); if (fontset_name != dfl_fontset_name || explicit_defaults) { set_attr(node, "fontset-name", fontset_name); From 50a417fc8392abb9612f63b51065e36d0830384f Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Mon, 5 Mar 2012 11:31:58 -0800 Subject: [PATCH 020/238] formatting --- src/placement_finder.cpp | 43 ++++++++++++++++++++++++++++++---------- src/text_properties.cpp | 7 +++++-- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/src/placement_finder.cpp b/src/placement_finder.cpp index 0d91f81c2..fcd71a12f 100644 --- a/src/placement_finder.cpp +++ b/src/placement_finder.cpp @@ -219,9 +219,13 @@ void placement_finder::find_line_breaks() if (p.wrap_width && string_width_ > p.wrap_width) { if (p.text_ratio) + { for (double i = 1.0; ((wrap_at = string_width_/i)/(string_height_*i)) > p.text_ratio && (string_width_/i) > p.wrap_width; i += 1.0) ; + } else + { wrap_at = p.wrap_width; + } } // work out where our line breaks need to be and the resultant width to the 'wrapped' string @@ -265,8 +269,10 @@ void placement_finder::find_line_breaks() // wrap text at first wrap_char after (default) the wrap width or immediately before the current word if ((c == '\n') || - (line_width > 0 && ((line_width > wrap_at && !ci.format->wrap_before) || - ((line_width + last_wrap_char_width + word_width) > wrap_at && ci.format->wrap_before)) )) + (line_width > 0 && + ((line_width > wrap_at && !ci.format->wrap_before) || + ((line_width + last_wrap_char_width + word_width) > wrap_at && ci.format->wrap_before)) ) + ) { add_line(line_width, line_height, first_line); line_breaks_.push_back(last_wrap_char_pos); @@ -322,7 +328,9 @@ void placement_finder::init_alignment() template -void placement_finder::adjust_position(text_path *current_placement, double label_x, double label_y) +void placement_finder::adjust_position(text_path *current_placement, + double label_x, + double label_y) { // if needed, adjust for desired vertical alignment current_placement->center.y = label_y; // no adjustment, default is MIDDLE @@ -351,7 +359,9 @@ void placement_finder::adjust_position(text_path *current_placement, } template -void placement_finder::find_point_placement(double label_x, double label_y, double angle) +void placement_finder::find_point_placement(double label_x, + double label_y, + double angle) { find_line_breaks(); @@ -432,12 +442,16 @@ void placement_finder::find_point_placement(double label_x, double la current_placement->center.y - dy - ci.ymax); // if there is an overlap with existing envelopes, then exit - no placement - if (!detector_.extent().intersects(e) || (!p.allow_overlap && !detector_.has_point_placement(e, pi.get_actual_minimum_distance()))) { + if (!detector_.extent().intersects(e) || + (!p.allow_overlap && !detector_.has_point_placement(e, pi.get_actual_minimum_distance())) + ) + { return; } // if avoid_edges test dimensions contains e - if (p.avoid_edges && !dimensions_.contains(e)) { + if (p.avoid_edges && !dimensions_.contains(e)) + { return; } @@ -551,7 +565,7 @@ void placement_finder::find_line_placements(PathT & shape_path) //If there is no spacing then just do one label, otherwise calculate how many there should be int num_labels = 1; if (p.label_spacing > 0) - num_labels = static_cast (floor(total_distance / (pi.get_actual_label_spacing() + string_width_))); + num_labels = static_cast(floor(total_distance / (pi.get_actual_label_spacing() + string_width_))); if (p.force_odd_labels && (num_labels % 2 == 0)) num_labels--; @@ -664,7 +678,11 @@ void placement_finder::find_line_placements(PathT & shape_path) } template -std::auto_ptr placement_finder::get_placement_offset(const std::vector &path_positions, const std::vector &path_distances, int &orientation, unsigned index, double distance) +std::auto_ptr placement_finder::get_placement_offset(std::vector const& path_positions, + std::vector const& path_distances, + int &orientation, + unsigned index, + double distance) { //Check that the given distance is on the given index and find the correct index and distance if not while (distance < 0 && index > 1) @@ -835,7 +853,11 @@ std::auto_ptr placement_finder::get_placement_offset(const if (!orientation_forced) { orientation = -orientation; - current_placement = get_placement_offset(path_positions, path_distances, orientation, initial_index, initial_distance); + current_placement = get_placement_offset(path_positions, + path_distances, + orientation, + initial_index, + initial_distance); } else { @@ -849,7 +871,8 @@ std::auto_ptr placement_finder::get_placement_offset(const } template -bool placement_finder::test_placement(const std::auto_ptr & current_placement, const int & orientation) +bool placement_finder::test_placement(std::auto_ptr const& current_placement, + int const& orientation) { //Create and test envelopes bool status = true; diff --git a/src/text_properties.cpp b/src/text_properties.cpp index fc9253156..3fdfc600f 100644 --- a/src/text_properties.cpp +++ b/src/text_properties.cpp @@ -121,7 +121,9 @@ void text_symbolizer_properties::from_xml(boost::property_tree::ptree const &sym if (n) set_format_tree(n); } -void text_symbolizer_properties::to_xml(boost::property_tree::ptree &node, bool explicit_defaults, text_symbolizer_properties const &dfl) const +void text_symbolizer_properties::to_xml(boost::property_tree::ptree &node, + bool explicit_defaults, + text_symbolizer_properties const& dfl) const { if (orientation) { @@ -263,7 +265,8 @@ void char_properties::from_xml(boost::property_tree::ptree const &sym, fontset_m if (itr != fontsets.end()) { fontset = itr->second; - } else + } + else { throw config_error("Unable to find any fontset named '" + *fontset_name_ + "'"); } From 84c9ee653a5c8f7be2f4f97ae57d6229bf569e3e Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Mon, 5 Mar 2012 11:36:48 -0800 Subject: [PATCH 021/238] no need to pass orientation as const& to test_placement, which unlike get_placement_offset to does not change the value --- src/placement_finder.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/placement_finder.cpp b/src/placement_finder.cpp index fcd71a12f..5fee755f5 100644 --- a/src/placement_finder.cpp +++ b/src/placement_finder.cpp @@ -680,7 +680,7 @@ void placement_finder::find_line_placements(PathT & shape_path) template std::auto_ptr placement_finder::get_placement_offset(std::vector const& path_positions, std::vector const& path_distances, - int &orientation, + int & orientation, unsigned index, double distance) { @@ -727,7 +727,7 @@ std::auto_ptr placement_finder::get_placement_offset(std:: current_placement->center.y = old_y + dy*distance/segment_length; double angle = atan2(-dy, dx); - bool orientation_forced = (orientation != 0); //Wether the orientation was set by the caller + bool orientation_forced = (orientation != 0); // Whether the orientation was set by the caller if (!orientation_forced) orientation = (angle > 0.55*M_PI || angle < -0.45*M_PI) ? -1 : 1; @@ -872,7 +872,7 @@ std::auto_ptr placement_finder::get_placement_offset(std:: template bool placement_finder::test_placement(std::auto_ptr const& current_placement, - int const& orientation) + int orientation) { //Create and test envelopes bool status = true; From 7a9c5ac0ed0e6f8515d89f85a037a2c168ac3d1e Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Mon, 5 Mar 2012 11:48:34 -0800 Subject: [PATCH 022/238] add note about generally passing built in types by value - for most compilers this should be faster --- docs/contributing.markdown | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/contributing.markdown b/docs/contributing.markdown index d733242d6..36b6b8d01 100644 --- a/docs/contributing.markdown +++ b/docs/contributing.markdown @@ -101,6 +101,12 @@ If you see bits of code around that do not follow these please don't hesitate to std::string const& variable_name // no +#### Pass built-in types by value, all others by const& + + void my_function(int double val); // if int, char, double, etc pass by value + + void my_function(std::string const& val); // if std::string or user type, pass by const& + #### Shared pointers should be created with [boost::make_shared](http://www.boost.org/doc/libs/1_47_0/libs/smart_ptr/make_shared.html) where possible #### Function definitions should not be separated from their arguments: From 7dfc6d9ccd8c4c8537580709bc6d07a6d2383030 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Mon, 5 Mar 2012 11:50:20 -0800 Subject: [PATCH 023/238] pass doubles by value in find_line_circle_intersection --- include/mapnik/placement_finder.hpp | 14 +++++++------- src/placement_finder.cpp | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/include/mapnik/placement_finder.hpp b/include/mapnik/placement_finder.hpp index 90561eaf1..103b68965 100644 --- a/include/mapnik/placement_finder.hpp +++ b/include/mapnik/placement_finder.hpp @@ -92,14 +92,14 @@ private: // otherwise it will autodetect the orientation. // If >= 50% of the characters end up upside down, it will be retried the other way. // RETURN: 1/-1 depending which way up the string ends up being. - std::auto_ptr get_placement_offset(const std::vector & path_positions, - const std::vector & path_distances, + std::auto_ptr get_placement_offset(std::vector const& path_positions, + std::vector const& path_distances, int & orientation, unsigned index, double distance); - ///Tests wether the given text_path be placed without a collision + ///Tests whether the given text_path be placed without a collision // Returns true if it can // NOTE: This edits p.envelopes so it can be used afterwards (you must clear it otherwise) - bool test_placement(const std::auto_ptr & current_placement, const int & orientation); + bool test_placement(std::auto_ptr const& current_placement, int orientation); ///Does a line-circle intersect calculation // NOTE: Follow the strict pre conditions @@ -107,9 +107,9 @@ private: // This means there is exactly one intersect point // Result is returned in ix, iy void find_line_circle_intersection( - const double &cx, const double &cy, const double &radius, - const double &x1, const double &y1, const double &x2, const double &y2, - double &ix, double &iy); + double cx, double cy, double radius, + double x1, double y1, double x2, double y2, + double & ix, double & iy); void find_line_breaks(); void init_string_size(); diff --git a/src/placement_finder.cpp b/src/placement_finder.cpp index 5fee755f5..6f9d6c8cd 100644 --- a/src/placement_finder.cpp +++ b/src/placement_finder.cpp @@ -946,9 +946,9 @@ bool placement_finder::test_placement(std::auto_ptr const& template void placement_finder::find_line_circle_intersection( - const double &cx, const double &cy, const double &radius, - const double &x1, const double &y1, const double &x2, const double &y2, - double &ix, double &iy) + double cx, double cy, double radius, + double x1, double y1, double x2, double y2, + double & ix, double & iy) { double dx = x2 - x1; double dy = y2 - y1; From f81108d5b35f5060cfbc691ce9c3fc487481aaec Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Mon, 5 Mar 2012 12:09:22 -0800 Subject: [PATCH 024/238] formatting --- include/mapnik/shield_symbolizer.hpp | 2 +- include/mapnik/symbolizer_helpers.hpp | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/include/mapnik/shield_symbolizer.hpp b/include/mapnik/shield_symbolizer.hpp index d31305664..85322f48e 100644 --- a/include/mapnik/shield_symbolizer.hpp +++ b/include/mapnik/shield_symbolizer.hpp @@ -51,7 +51,7 @@ struct MAPNIK_DECL shield_symbolizer : public text_symbolizer, bool get_unlock_image() const; // image is not locked to the text placement void set_unlock_image(bool unlock_image); - void set_shield_displacement(double shield_dx,double shield_dy); + void set_shield_displacement(double shield_dx, double shield_dy); position const& get_shield_displacement() const; private: diff --git a/include/mapnik/symbolizer_helpers.hpp b/include/mapnik/symbolizer_helpers.hpp index 5e3894067..bae7e25d9 100644 --- a/include/mapnik/symbolizer_helpers.hpp +++ b/include/mapnik/symbolizer_helpers.hpp @@ -53,7 +53,7 @@ public: unsigned width, unsigned height, double scale_factor, - CoordTransform const &t, + CoordTransform const& t, FaceManagerT &font_manager, DetectorT &detector) : sym_(sym), @@ -83,7 +83,7 @@ public: bool next(); /** Get current placement. next() has to be called before! */ - placements_type &placements() const; + placements_type & placements() const; protected: bool next_point_placement(); bool next_line_placement(); @@ -96,8 +96,8 @@ protected: Feature const& feature_; proj_transform const& prj_trans_; CoordTransform const& t_; - FaceManagerT &font_manager_; - DetectorT &detector_; + FaceManagerT & font_manager_; + DetectorT & detector_; metawriter_with_properties writer_; box2d dims_; @@ -137,9 +137,9 @@ public: unsigned width, unsigned height, double scale_factor, - CoordTransform const &t, - FaceManagerT &font_manager, - DetectorT &detector) : + CoordTransform const& t, + FaceManagerT & font_manager, + DetectorT & detector) : text_symbolizer_helper(sym, feature, prj_trans, width, height, scale_factor, t, font_manager, detector), sym_(sym) { @@ -149,7 +149,7 @@ public: bool next(); pixel_position get_marker_position(text_path const& p); - marker &get_marker() const; + marker & get_marker() const; agg::trans_affine const& get_transform() const; protected: bool next_point_placement(); From 87da7c2e3e85281c7f50a7719e9971bbcb6ac8b0 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Mon, 5 Mar 2012 12:34:10 -0800 Subject: [PATCH 025/238] ptree_helpers: ensure proper return type upon exception (TODO: needs larger cleanup) --- include/mapnik/ptree_helpers.hpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/include/mapnik/ptree_helpers.hpp b/include/mapnik/ptree_helpers.hpp index 811317312..1edcb9d80 100644 --- a/include/mapnik/ptree_helpers.hpp +++ b/include/mapnik/ptree_helpers.hpp @@ -211,7 +211,15 @@ struct name_trait< mapnik::enumeration > template inline boost::optional fast_cast(std::string const& value) { - return boost::lexical_cast( value ); + try + { + return boost::lexical_cast( value ); + } + catch (boost::bad_lexical_cast const& ex) + { + return boost::optional(); + } + } template <> From 90fb50b158969d66f32700af574967708466ca53 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Mon, 5 Mar 2012 13:29:20 -0800 Subject: [PATCH 026/238] initialize sx,sy in ctrans (refs #1114) and avoid possible divide by zero condition (refs #1110) --- include/mapnik/ctrans.hpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/include/mapnik/ctrans.hpp b/include/mapnik/ctrans.hpp index 6d350c540..43219ad29 100644 --- a/include/mapnik/ctrans.hpp +++ b/include/mapnik/ctrans.hpp @@ -380,20 +380,27 @@ class CoordTransform private: int width_; int height_; - double sx_; - double sy_; box2d extent_; double offset_x_; double offset_y_; + double sx_; + double sy_; public: CoordTransform(int width, int height, const box2d& extent, double offset_x = 0, double offset_y = 0) - : width_(width), height_(height), extent_(extent), - offset_x_(offset_x), offset_y_(offset_y) + : width_(width), + height_(height), + extent_(extent), + offset_x_(offset_x), + offset_y_(offset_y), + sx_(1.0), + sy_(1.0) { - sx_ = static_cast(width_) / extent_.width(); - sy_ = static_cast(height_) / extent_.height(); + if (extent_.width()) + sx_ = static_cast(width_) / extent_.width(); + if (extent_.height()) + sy_ = static_cast(height_) / extent_.height(); } inline int width() const From e9c043a42a7be050f7a5596c1f8b7890407d0fe6 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Mon, 5 Mar 2012 13:32:02 -0800 Subject: [PATCH 027/238] ensure members are initialized (refs #1114) --- include/mapnik/config_error.hpp | 3 ++- include/mapnik/enumeration.hpp | 6 ++++-- include/mapnik/feature.hpp | 6 ++++-- include/mapnik/font_engine_freetype.hpp | 3 ++- include/mapnik/ptree_helpers.hpp | 2 +- 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/include/mapnik/config_error.hpp b/include/mapnik/config_error.hpp index 1e720c5ca..ad06fba58 100644 --- a/include/mapnik/config_error.hpp +++ b/include/mapnik/config_error.hpp @@ -31,7 +31,8 @@ namespace mapnik { class config_error : public std::exception { public: - config_error() {} + config_error(): + what_() {} config_error( std::string const& what ) : what_( what ) diff --git a/include/mapnik/enumeration.hpp b/include/mapnik/enumeration.hpp index 7dc5e2e1d..b3c00dfa0 100644 --- a/include/mapnik/enumeration.hpp +++ b/include/mapnik/enumeration.hpp @@ -37,7 +37,8 @@ namespace mapnik { class illegal_enum_value : public std::exception { public: - illegal_enum_value() {} + illegal_enum_value(): + what_() {} illegal_enum_value( std::string const& what ) : what_( what ) @@ -138,7 +139,8 @@ template class MAPNIK_DECL enumeration { public: typedef ENUM native_type; - enumeration() {}; + enumeration(): + value_() {}; enumeration( ENUM v ) : value_(v) {} enumeration( const enumeration & other ) : value_(other.value_) {} diff --git a/include/mapnik/feature.hpp b/include/mapnik/feature.hpp index 3b627d418..9b636824d 100644 --- a/include/mapnik/feature.hpp +++ b/include/mapnik/feature.hpp @@ -100,7 +100,9 @@ public: feature_impl(context_ptr const& ctx, int id) : id_(id), ctx_(ctx), - data_(ctx_->mapping_.size()) + data_(ctx_->mapping_.size()), + geom_cont_(), + raster_() {} inline int id() const { return id_;} @@ -280,9 +282,9 @@ public: private: int id_; context_ptr ctx_; + cont_type data_; boost::ptr_vector geom_cont_; raster_ptr raster_; - cont_type data_; }; diff --git a/include/mapnik/font_engine_freetype.hpp b/include/mapnik/font_engine_freetype.hpp index 12eb518bf..b8b91ea7b 100644 --- a/include/mapnik/font_engine_freetype.hpp +++ b/include/mapnik/font_engine_freetype.hpp @@ -152,7 +152,8 @@ class MAPNIK_DECL font_face_set : private boost::noncopyable { public: font_face_set(void) - : faces_() {} + : faces_(), + dimension_cache_() {} void add(face_ptr face) { diff --git a/include/mapnik/ptree_helpers.hpp b/include/mapnik/ptree_helpers.hpp index 1edcb9d80..0c3e8bf63 100644 --- a/include/mapnik/ptree_helpers.hpp +++ b/include/mapnik/ptree_helpers.hpp @@ -93,7 +93,7 @@ operator << ( std::basic_ostream & s, mapnik::color const& c ) /** Helper for class bool */ class boolean { public: - boolean() {} + boolean() : b_(false) {} boolean(bool b) : b_(b) {} boolean(boolean const& b) : b_(b.b_) {} From f3c4f9eec7a804cb2caa4abc963640307ae75026 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Mon, 5 Mar 2012 13:48:35 -0800 Subject: [PATCH 028/238] initialize members of text_symbolizer_properties and char_properties - refs #1114 --- include/mapnik/text_properties.hpp | 2 +- src/text_properties.cpp | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/include/mapnik/text_properties.hpp b/include/mapnik/text_properties.hpp index 4362a632f..dbf21f8d7 100644 --- a/include/mapnik/text_properties.hpp +++ b/include/mapnik/text_properties.hpp @@ -56,7 +56,7 @@ struct char_properties /** Construct object from XML. */ void from_xml(boost::property_tree::ptree const &sym, fontset_map const & fontsets); /** Write object to XML ptree. */ - void to_xml(boost::property_tree::ptree &node, bool explicit_defaults, char_properties const &dfl=char_properties()) const; + void to_xml(boost::property_tree::ptree &node, bool explicit_defaults, char_properties const& dfl=char_properties()) const; std::string face_name; font_set fontset; float text_size; diff --git a/src/text_properties.cpp b/src/text_properties.cpp index 3fdfc600f..1ae028021 100644 --- a/src/text_properties.cpp +++ b/src/text_properties.cpp @@ -31,6 +31,7 @@ namespace mapnik using boost::optional; text_symbolizer_properties::text_symbolizer_properties() : + displacement(0,0), label_placement(POINT_PLACEMENT), halign(H_AUTO), jalign(J_MIDDLE), @@ -40,11 +41,13 @@ text_symbolizer_properties::text_symbolizer_properties() : avoid_edges(false), minimum_distance(0.0), minimum_padding(0.0), + minimum_path_length(0.0), max_char_angle_delta(22.5 * M_PI/180.0), force_odd_labels(false), allow_overlap(false), text_ratio(0), wrap_width(0), + format(), tree_() { @@ -218,6 +221,8 @@ void text_symbolizer_properties::set_old_style_expression(expression_ptr expr) } char_properties::char_properties() : + face_name(), + fontset(), text_size(10.0), character_spacing(0), line_spacing(0), From 3dafe6080d62a3124e447d6729641de9c75d9462 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Mon, 5 Mar 2012 14:15:01 -0800 Subject: [PATCH 029/238] rework text_path ctor to ensure members are properly initialized - refs #1114 --- include/mapnik/placement_finder.hpp | 2 +- include/mapnik/text_path.hpp | 13 ++++++++----- src/placement_finder.cpp | 20 +++++++++----------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/include/mapnik/placement_finder.hpp b/include/mapnik/placement_finder.hpp index 103b68965..662078238 100644 --- a/include/mapnik/placement_finder.hpp +++ b/include/mapnik/placement_finder.hpp @@ -114,7 +114,7 @@ private: void find_line_breaks(); void init_string_size(); void init_alignment(); - void adjust_position(text_path *current_placement, double label_x, double label_y); + void adjust_position(text_path *current_placement); void add_line(double width, double height, bool first_line); ///General Internals diff --git a/include/mapnik/text_path.hpp b/include/mapnik/text_path.hpp index f29cd903d..90490d154 100644 --- a/include/mapnik/text_path.hpp +++ b/include/mapnik/text_path.hpp @@ -131,7 +131,9 @@ class text_path : boost::noncopyable double angle; character_node(char_info_ptr c_, double x_, double y_, double angle_) - : c(c_), pos(x_, y_), angle(angle_) + : c(c_), + pos(x_, y_), + angle(angle_) { } @@ -150,12 +152,13 @@ class text_path : boost::noncopyable int itr_; public: typedef std::vector character_nodes_t; + pixel_position center; character_nodes_t nodes_; - pixel_position center; - - text_path() - : itr_(0) + text_path(double x, double y) + : itr_(0), + center(x,y), + nodes_() { } diff --git a/src/placement_finder.cpp b/src/placement_finder.cpp index 6f9d6c8cd..c9c2c6e1d 100644 --- a/src/placement_finder.cpp +++ b/src/placement_finder.cpp @@ -328,12 +328,9 @@ void placement_finder::init_alignment() template -void placement_finder::adjust_position(text_path *current_placement, - double label_x, - double label_y) +void placement_finder::adjust_position(text_path *current_placement) { // if needed, adjust for desired vertical alignment - current_placement->center.y = label_y; // no adjustment, default is MIDDLE if (valign_ == V_TOP) { current_placement->center.y -= 0.5 * string_height_; // move center up by 1/2 the total height @@ -343,7 +340,6 @@ void placement_finder::adjust_position(text_path *current_placement, } // set horizontal position to middle of text - current_placement->center.x = label_x; // no adjustment, default is MIDDLE if (halign_ == H_LEFT) { current_placement->center.x -= 0.5 * string_width_; // move center left by 1/2 the string width @@ -370,9 +366,9 @@ void placement_finder::find_point_placement(double label_x, double sina = std::sin(rad); double x, y; - std::auto_ptr current_placement(new text_path); + std::auto_ptr current_placement(new text_path(label_x, label_y)); - adjust_position(current_placement.get(), label_x, label_y); + adjust_position(current_placement.get()); // presets for first line unsigned int line_number = 0; @@ -706,8 +702,6 @@ std::auto_ptr placement_finder::get_placement_offset(std:: const unsigned initial_index = index; const double initial_distance = distance; - std::auto_ptr current_placement(new text_path); - double old_x = path_positions[index-1].x; double old_y = path_positions[index-1].y; @@ -723,8 +717,12 @@ std::auto_ptr placement_finder::get_placement_offset(std:: return std::auto_ptr(NULL); } - current_placement->center.x = old_x + dx*distance/segment_length; - current_placement->center.y = old_y + dy*distance/segment_length; + std::auto_ptr current_placement( + new text_path((old_x + dx*distance/segment_length), + (old_y + dy*distance/segment_length) + ) + ); + double angle = atan2(-dy, dx); bool orientation_forced = (orientation != 0); // Whether the orientation was set by the caller From 228766856247bb4d0c67ebadc06db796670ba2d3 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Mon, 5 Mar 2012 14:51:14 -0800 Subject: [PATCH 030/238] initialize members of char_info - refs #1114 --- include/mapnik/char_info.hpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/include/mapnik/char_info.hpp b/include/mapnik/char_info.hpp index a1417735f..12da2bf31 100644 --- a/include/mapnik/char_info.hpp +++ b/include/mapnik/char_info.hpp @@ -31,11 +31,24 @@ struct char_properties; class char_info { public: char_info(unsigned c_, double width_, double ymax_, double ymin_, double line_height_) - : c(c_), width(width_), line_height(line_height_), ymin(ymin_), ymax(ymax_) + : c(c_), + width(width_), + line_height(line_height_), + ymin(ymin_), + ymax(ymax_), + avg_height(ymax_-ymin_), + format() { } + char_info() - : c(0), width(0), line_height(0), ymin(0), ymax(0) + : c(0), + width(0), + line_height(0), + ymin(0), + ymax(0), + avg_height(0), + format() { } From 943f92fffc360fffd361e13ac8b0be18b3b7bee8 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Mon, 5 Mar 2012 14:54:30 -0800 Subject: [PATCH 031/238] initialize another member in text_properties --- src/text_properties.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/text_properties.cpp b/src/text_properties.cpp index 1ae028021..6b1e45ce8 100644 --- a/src/text_properties.cpp +++ b/src/text_properties.cpp @@ -31,6 +31,7 @@ namespace mapnik using boost::optional; text_symbolizer_properties::text_symbolizer_properties() : + orientation(), displacement(0,0), label_placement(POINT_PLACEMENT), halign(H_AUTO), From b9f2af6f8790d163c749aef4e7a87da9d33dc2fe Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Mon, 5 Mar 2012 15:48:04 -0800 Subject: [PATCH 032/238] fix typo --- docs/contributing.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contributing.markdown b/docs/contributing.markdown index 36b6b8d01..6bc272f18 100644 --- a/docs/contributing.markdown +++ b/docs/contributing.markdown @@ -99,7 +99,7 @@ If you see bits of code around that do not follow these please don't hesitate to std::string const& variable_name // preferred, for consistency - std::string const& variable_name // no + const std::string & variable_name // no #### Pass built-in types by value, all others by const& From 3c594efec8b0aca80cce356f397ee48b92c5ae2b Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Mon, 5 Mar 2012 17:59:07 -0800 Subject: [PATCH 033/238] fix spelling --- include/mapnik/label_collision_detector.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/mapnik/label_collision_detector.hpp b/include/mapnik/label_collision_detector.hpp index fa23b7ca0..b913ac0dc 100644 --- a/include/mapnik/label_collision_detector.hpp +++ b/include/mapnik/label_collision_detector.hpp @@ -39,7 +39,7 @@ struct label_collision_detector { typedef std::vector > label_placements; - bool has_plasement(box2d const& box) + bool has_placement(box2d const& box) { label_placements::const_iterator itr=labels_.begin(); for( ; itr !=labels_.end();++itr) @@ -134,7 +134,7 @@ public: }; -//quad tree based label collission detector so labels dont appear within a given distance +//quad tree based label collision detector so labels dont appear within a given distance class label_collision_detector4 : boost::noncopyable { public: From df54f710bc0e7406fe722b798cb23791f212e5b2 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Mon, 5 Mar 2012 18:00:15 -0800 Subject: [PATCH 034/238] minor formatting --- src/placement_finder.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/placement_finder.cpp b/src/placement_finder.cpp index c9c2c6e1d..b055a9308 100644 --- a/src/placement_finder.cpp +++ b/src/placement_finder.cpp @@ -439,7 +439,9 @@ void placement_finder::find_point_placement(double label_x, // if there is an overlap with existing envelopes, then exit - no placement if (!detector_.extent().intersects(e) || - (!p.allow_overlap && !detector_.has_point_placement(e, pi.get_actual_minimum_distance())) + (!p.allow_overlap && + !detector_.has_point_placement(e, pi.get_actual_minimum_distance()) + ) ) { return; @@ -907,7 +909,10 @@ bool placement_finder::test_placement(std::auto_ptr const& y - (cwidth*sina + ci.height()*cosa)); if (!detector_.extent().intersects(e) || - !detector_.has_placement(e, info_.get_string(), pi.get_actual_minimum_distance())) + (!p.allow_overlap && + !detector_.has_placement(e, info_.get_string(), pi.get_actual_minimum_distance()) + ) + ) { //std::clog << "No Intersects:" << !dimensions_.intersects(e) << ": " << e << " @ " << dimensions_ << std::endl; //std::clog << "No Placements:" << !detector_.has_placement(e, info.get_string(), p.minimum_distance) << std::endl; From 1c6da3893e570bf6623008eda4505976005c2e38 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Mon, 5 Mar 2012 18:00:45 -0800 Subject: [PATCH 035/238] minor formatting --- src/symbolizer_helpers.cpp | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/symbolizer_helpers.cpp b/src/symbolizer_helpers.cpp index 9672e95dc..4a9a38a12 100644 --- a/src/symbolizer_helpers.cpp +++ b/src/symbolizer_helpers.cpp @@ -136,9 +136,7 @@ void text_symbolizer_helper::initialize_geometries() largest_box_only = true; if (sym_.get_minimum_path_length() > 0) { - // TODO - find less costly method than fetching full envelope box2d gbox = t_.forward(geom.envelope(), prj_trans_); - if (gbox.width() < sym_.get_minimum_path_length()) { continue; @@ -162,10 +160,13 @@ template void text_symbolizer_helper::initialize_points() { label_placement_enum how_placed = placement_->properties.label_placement; - if (how_placed == LINE_PLACEMENT) { + if (how_placed == LINE_PLACEMENT) + { point_placement_ = false; return; - } else { + } + else + { point_placement_ = true; } @@ -188,14 +189,19 @@ void text_symbolizer_helper::initialize_points() t_.forward(&label_x, &label_y); points_.push_back(std::make_pair(label_x, label_y)); } - } else { + } + else + { if (how_placed == POINT_PLACEMENT) { geom.label_position(&label_x, &label_y); - } else if (how_placed == INTERIOR_PLACEMENT) + } + else if (how_placed == INTERIOR_PLACEMENT) { geom.label_interior_position(&label_x, &label_y); - } else { + } + else + { #ifdef MAPNIK_DEBUG std::cerr << "ERROR: Unknown placement type in initialize_points();\n"; #endif From 191d0f907a94c1690e461394990ecd0ef905d3d2 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Tue, 6 Mar 2012 15:18:11 +0100 Subject: [PATCH 036/238] Convert first function to new XML structure. --- include/mapnik/libxml2_loader.hpp | 8 +- include/mapnik/xml_tree.hpp | 30 ++++++- src/load_map.cpp | 85 ++++++++---------- src/xml_tree.cpp | 137 +++++++++++++++++++++++++++++- 4 files changed, 203 insertions(+), 57 deletions(-) diff --git a/include/mapnik/libxml2_loader.hpp b/include/mapnik/libxml2_loader.hpp index 105a5f8b2..5acad070e 100644 --- a/include/mapnik/libxml2_loader.hpp +++ b/include/mapnik/libxml2_loader.hpp @@ -23,16 +23,14 @@ #ifndef MAPNIK_LIBXML2_LOADER_HPP #define MAPNIK_LIBXML2_LOADER_HPP -// boost -#include - // stl #include namespace mapnik { -void read_xml2( std::string const & filename, boost::property_tree::ptree & pt); -void read_xml2_string( std::string const & str, boost::property_tree::ptree & pt, std::string const & base_path=""); +class xml_node; +void read_xml2( std::string const & filename, xml_node &node); +void read_xml2_string( std::string const & str, xml_node &node, std::string const & base_path=""); } #endif // MAPNIK_LIBXML2_LOADER_HPP diff --git a/include/mapnik/xml_tree.hpp b/include/mapnik/xml_tree.hpp index b34bb9376..fc17ae7cb 100644 --- a/include/mapnik/xml_tree.hpp +++ b/include/mapnik/xml_tree.hpp @@ -23,13 +23,19 @@ #ifndef MAPNIK_XML_TREE_H #define MAPNIK_XML_TREE_H +//boost +#include + +//stl #include #include #include +#include namespace mapnik { class xml_tree; +class color; class xml_attribute { @@ -38,6 +44,19 @@ public: bool processed; }; +class node_not_found: public std::exception +{ +public: + node_not_found(std::string node_name) : node_name_(node_name) {} + virtual const char* what() const throw() + { + return ("Node "+node_name_+ "not found").c_str(); + } + ~node_not_found() throw (); +private: + std::string node_name_; +}; + class xml_node { public: @@ -49,6 +68,15 @@ public: xml_node &add_child(std::string name, unsigned line=0, bool text_node = false); void add_attribute(std::string name, std::string value); void set_processed(bool processed); + + xml_node & get_child(std::string name); + xml_node const& get_child(std::string name) const; + + template + boost::optional get_opt_attr(std::string const& name) const; + + template + T get_attr(std::string const& name, T const& default_value) const; private: xml_tree &tree_; std::string name_; @@ -66,7 +94,7 @@ public: xml_tree(); void set_filename(std::string fn); std::string filename() const; - xml_node &node(); + xml_node &root(); private: xml_node node_; std::string file_; diff --git a/src/load_map.cpp b/src/load_map.cpp index ddb076e80..2fc7d6a5b 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -22,6 +22,7 @@ // mapnik #include +#include #include #include #include @@ -94,7 +95,7 @@ public: expr_grammar_(tr_) {} - void parse_map(Map & map, ptree const & sty, std::string const& base_path=""); + void parse_map(Map & map, xml_node const& sty, std::string const& base_path=""); private: void parse_map_include( Map & map, ptree const & include); void parse_style(Map & map, ptree const & sty); @@ -166,9 +167,10 @@ void remove_empty_text_nodes(ptree &pt) //#include void load_map(Map & map, std::string const& filename, bool strict) { - ptree pt; + xml_tree tree; + tree.set_filename(filename); #ifdef HAVE_LIBXML2 - read_xml2(filename, pt); + read_xml2(filename, tree.root()); #else try { @@ -180,18 +182,18 @@ void load_map(Map & map, std::string const& filename, bool strict) throw config_error( ex.what() ); } #endif - map_parser parser( strict, filename); - parser.parse_map(map, pt); + map_parser parser(strict, filename); +// parser.parse_map(map, pt); } void load_map_string(Map & map, std::string const& str, bool strict, std::string const& base_path) { - ptree pt; + xml_tree tree; #ifdef HAVE_LIBXML2 if (!base_path.empty()) - read_xml2_string(str, pt, base_path); // accept base_path passed into function + read_xml2_string(str, tree.root(), base_path); // accept base_path passed into function else - read_xml2_string(str, pt, map.base_path()); // default to map base_path + read_xml2_string(str, tree.root(), map.base_path()); // default to map base_path #else try { @@ -206,8 +208,8 @@ void load_map_string(Map & map, std::string const& str, bool strict, std::string } #endif - map_parser parser( strict, base_path); - parser.parse_map(map, pt, base_path); + map_parser parser(strict, base_path); +// parser.parse_map(map, tree.root(), base_path); } expression_ptr map_parser::parse_expr(std::string const& str) @@ -244,44 +246,31 @@ boost::optional map_parser::get_opt_color_attr(boost::property_tree::ptre return result; } -void map_parser::parse_map( Map & map, ptree const & pt, std::string const& base_path ) +void map_parser::parse_map(Map & map, xml_node const& pt, std::string const& base_path) { try { - ptree const & map_node = pt.get_child("Map"); - - std::ostringstream s(""); - s << "background-color," - << "background-image," - << "srs," - << "buffer-size," - << "paths-from-xml," - << "minimum-version," - << "font-directory," - << "maximum-extent," - << "base"; - ensure_attrs(map_node, "Map", s.str()); - + xml_node const& map_node = pt.get_child("Map"); try { parameters extra_attr; // Check if relative paths should be interpreted as relative to/from XML location // Default is true, and map_parser::ensure_relative_to_xml will be called to modify path - optional paths_from_xml = get_opt_attr(map_node, "paths-from-xml"); + optional paths_from_xml = map_node.get_opt_attr("paths-from-xml"); if (paths_from_xml) { relative_to_xml_ = *paths_from_xml; } - optional base_path_from_xml = get_opt_attr(map_node, "base"); + optional base_path_from_xml = map_node.get_opt_attr("base"); if (!base_path.empty()) { - map.set_base_path( base_path ); + map.set_base_path(base_path); } else if (base_path_from_xml) { - map.set_base_path( *base_path_from_xml ); + map.set_base_path(*base_path_from_xml); } else { @@ -293,30 +282,30 @@ void map_parser::parse_map( Map & map, ptree const & pt, std::string const& base std::string base = xml_path.branch_path().string(); #endif - map.set_base_path( base ); + map.set_base_path(base); } - optional bgcolor = get_opt_color_attr(map_node, "background-color"); + optional bgcolor = map_node.get_opt_attr("background-color"); if (bgcolor) { - map.set_background( * bgcolor ); + map.set_background(*bgcolor); } - optional image_filename = get_opt_attr(map_node, "background-image"); + optional image_filename = map_node.get_opt_attr("background-image"); if (image_filename) { map.set_background_image(ensure_relative_to_xml(image_filename)); } - map.set_srs( get_attr(map_node, "srs", map.srs() )); + map.set_srs(map_node.get_attr("srs", map.srs())); - optional buffer_size = get_opt_attr(map_node,"buffer-size"); + optional buffer_size = map_node.get_opt_attr("buffer-size"); if (buffer_size) { map.set_buffer_size(*buffer_size); } - optional maximum_extent = get_opt_attr(map_node,"maximum-extent"); + optional maximum_extent = map_node.get_opt_attr("maximum-extent"); if (maximum_extent) { box2d box; @@ -327,33 +316,33 @@ void map_parser::parse_map( Map & map, ptree const & pt, std::string const& base else { std::ostringstream s_err; - s << "failed to parse 'maximum-extent'"; - if ( strict_ ) + s_err << "failed to parse 'maximum-extent'"; + if (strict_) throw config_error(s_err.str()); else - std::clog << "### WARNING: " << s.str() << std::endl; + std::clog << "### WARNING: " << s_err.str() << std::endl; } } - optional font_directory = get_opt_attr(map_node,"font-directory"); + optional font_directory = map_node.get_opt_attr("font-directory"); if (font_directory) { extra_attr["font-directory"] = *font_directory; - freetype_engine::register_fonts( ensure_relative_to_xml(font_directory), false); + freetype_engine::register_fonts(ensure_relative_to_xml(font_directory), false); } - optional min_version_string = get_opt_attr(map_node, "minimum-version"); + optional min_version_string = map_node.get_opt_attr("minimum-version"); if (min_version_string) { extra_attr["minimum-version"] = *min_version_string; boost::char_separator sep("."); - boost::tokenizer > tokens(*min_version_string,sep); + boost::tokenizer > tokens(*min_version_string, sep); unsigned i = 0; bool success = false; int n[3]; - for (boost::tokenizer >::iterator beg=tokens.begin(); - beg!=tokens.end();++beg) + for (boost::tokenizer >::iterator beg = tokens.begin(); + beg != tokens.end(); ++beg) { try { @@ -391,15 +380,15 @@ void map_parser::parse_map( Map & map, ptree const & pt, std::string const& base throw; } - parse_map_include( map, map_node ); +// parse_map_include( map, map_node ); } - catch (const boost::property_tree::ptree_bad_path &) + catch (node_not_found const&) { throw config_error("Not a map file. Node 'Map' not found."); } } -void map_parser::parse_map_include( Map & map, ptree const & include ) +void map_parser::parse_map_include(Map & map, xml_node const& include ) { ptree::const_iterator itr = include.begin(); ptree::const_iterator end = include.end(); diff --git a/src/xml_tree.cpp b/src/xml_tree.cpp index 3a05a82ac..a004748a6 100644 --- a/src/xml_tree.cpp +++ b/src/xml_tree.cpp @@ -19,11 +19,112 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ + +//mapnik #include +#include +#include + +//boost +#include namespace mapnik { +template +inline boost::optional fast_cast(xml_tree const& tree, std::string const& value) +{ + return boost::lexical_cast(value); +} + +template <> +inline boost::optional fast_cast(xml_tree const& tree, std::string const& value) +{ + int result; + if (mapnik::conversions::string2int(value, result)) + return boost::optional(result); + return boost::optional(); +} + +template <> +inline boost::optional fast_cast(xml_tree const& tree, std::string const& value) +{ + double result; + if (mapnik::conversions::string2double(value, result)) + return boost::optional(result); + return boost::optional(); +} + +template <> +inline boost::optional fast_cast(xml_tree const& tree, std::string const& value) +{ + float result; + if (mapnik::conversions::string2float(value, result)) + return boost::optional(result); + return boost::optional(); +} + +template <> +inline boost::optional fast_cast(xml_tree const& tree, std::string const& value) +{ + return value; +} + + +/****************************************************************************/ + +class boolean; +template +struct name_trait +{ + static std::string name() + { + return ""; + } + // missing name_trait for type ... + // if you get here you are probably using a new type + // in the XML file. Just add a name trait for the new + // type below. + BOOST_STATIC_ASSERT( sizeof(T) == 0 ); +}; + +#define DEFINE_NAME_TRAIT( type, type_name ) \ + template <> \ + struct name_trait \ + { \ + static std::string name() { return std::string("type ") + type_name; } \ + }; + + +DEFINE_NAME_TRAIT( double, "double") +DEFINE_NAME_TRAIT( float, "float") +DEFINE_NAME_TRAIT( unsigned, "unsigned") +DEFINE_NAME_TRAIT( boolean, "boolean") +DEFINE_NAME_TRAIT( int, "integer" ) +DEFINE_NAME_TRAIT( std::string, "string" ) +DEFINE_NAME_TRAIT( color, "color" ) + +template +struct name_trait< mapnik::enumeration > +{ + typedef enumeration Enum; + + static std::string name() + { + std::string value_list("one of ["); + for (unsigned i = 0; i < Enum::MAX; ++i) + { + value_list += Enum::get_string( i ); + if ( i + 1 < Enum::MAX ) value_list += ", "; + } + value_list += "]"; + + return value_list; + } +}; + +/****************************************************************************/ + xml_tree::xml_tree() : node_(*this, "") { @@ -39,7 +140,7 @@ std::string xml_tree::filename() const return file_; } -xml_node &xml_tree::node() +xml_node &xml_tree::root() { return node_; } @@ -61,7 +162,7 @@ std::string xml_node::name() const if (!text_node_) return name_; else - return ""; + return ""; //TODO: throw } std::string xml_node::text() const @@ -69,7 +170,7 @@ std::string xml_node::text() const if (text_node_) return name_; else - return "NOT A TEXT NODE"; + return "NOT A TEXT NODE"; //TODO: throw } void xml_node::set_processed(bool processed) @@ -83,5 +184,35 @@ xml_node &xml_node::add_child(std::string name, unsigned line, bool text_node) return children_.back(); } +xml_node & xml_node::get_child(std::string name) +{ + std::list::iterator itr = children_.begin(); + std::list::iterator end = children_.end(); + for (; itr != end; itr++) + { + if (!(itr->text_node_) && itr->name_ == name) + { + itr->set_processed(true); + return *itr; + } + } + throw node_not_found(name); +} + +template +boost::optional xml_node::get_opt_attr(std::string const& name) const +{ + std::map::const_iterator itr = attributes_.find(name); + if (itr == attributes_.end()) return boost::optional(); + boost::optional result = fast_cast(itr->second); + if (!result) + { + throw config_error(std::string("Failed to parse attribute '") + + name + "'. Expected " + name_trait::name() + + " but got '" + itr->second + "'"); + } + return result; +} + } //ns mapnik From ac50834d92926ffdcd913a6ce6ddcaa6dadaaf44 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Tue, 6 Mar 2012 15:47:08 +0100 Subject: [PATCH 037/238] Convert parse_map_include to xml_node. --- include/mapnik/xml_tree.hpp | 12 +++++ src/load_map.cpp | 101 +++++++++++++----------------------- src/xml_tree.cpp | 7 +++ 3 files changed, 55 insertions(+), 65 deletions(-) diff --git a/include/mapnik/xml_tree.hpp b/include/mapnik/xml_tree.hpp index fc17ae7cb..bbb12589f 100644 --- a/include/mapnik/xml_tree.hpp +++ b/include/mapnik/xml_tree.hpp @@ -60,15 +60,20 @@ private: class xml_node { public: + typedef std::list::const_iterator const_iterator; xml_node(xml_tree &tree, std::string name, unsigned line=0, bool text_node = false); std::string name() const; std::string text() const; + bool is_text() const; xml_node &add_child(std::string name, unsigned line=0, bool text_node = false); void add_attribute(std::string name, std::string value); void set_processed(bool processed); + const_iterator begin() const; + const_iterator end() const; + xml_node & get_child(std::string name); xml_node const& get_child(std::string name) const; @@ -77,6 +82,13 @@ public: template T get_attr(std::string const& name, T const& default_value) const; + template + T get_attr(std::string const& name) const; + + std::string get_text() const; + + template + T get_value(std::string const& name) const; private: xml_tree &tree_; std::string name_; diff --git a/src/load_map.cpp b/src/load_map.cpp index 2fc7d6a5b..f549468a5 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -97,7 +97,7 @@ public: void parse_map(Map & map, xml_node const& sty, std::string const& base_path=""); private: - void parse_map_include( Map & map, ptree const & include); + void parse_map_include( Map & map, xml_node const& include); void parse_style(Map & map, ptree const & sty); void parse_layer(Map & map, ptree const & lay); void parse_metawriter(Map & map, ptree const & lay); @@ -380,7 +380,7 @@ void map_parser::parse_map(Map & map, xml_node const& pt, std::string const& bas throw; } -// parse_map_include( map, map_node ); + parse_map_include(map, map_node); } catch (node_not_found const&) { @@ -388,124 +388,95 @@ void map_parser::parse_map(Map & map, xml_node const& pt, std::string const& bas } } -void map_parser::parse_map_include(Map & map, xml_node const& include ) +void map_parser::parse_map_include(Map & map, xml_node const& include) { - ptree::const_iterator itr = include.begin(); - ptree::const_iterator end = include.end(); + xml_node::const_iterator itr = include.begin(); + xml_node::const_iterator end = include.end(); for (; itr != end; ++itr) { - ptree::value_type const& v = *itr; - - if (v.first == "Include") + if (itr->is_text()) continue; + if (itr->name() == "Include") { - parse_map_include( map, v.second ); + parse_map_include(map, *itr); } - else if (v.first == "Style") + else if (itr->name() == "Style") { - parse_style( map, v.second ); +// parse_style(map, *itr); } - else if (v.first == "Layer") + else if (itr->name() == "Layer") { - parse_layer(map, v.second ); +// parse_layer(map, *itr); } - else if (v.first == "FontSet") + else if (itr->name() == "FontSet") { - parse_fontset(map, v.second); +// parse_fontset(map, *itr); } - else if (v.first == "MetaWriter") + else if (itr->name() == "MetaWriter") { - parse_metawriter(map, v.second); +// parse_metawriter(map, *itr); } - else if (v.first == "FileSource") + else if (itr->name() == "FileSource") { - std::string name = get_attr( v.second, "name"); - std::string value = get_value( v.second, ""); + std::string name = itr->get_attr("name"); + std::string value = itr->get_text(); file_sources_[name] = value; } - else if (v.first == "Datasource") + else if (itr->name() == "Datasource") { - std::string name = get_attr(v.second, "name", std::string("Unnamed")); + std::string name = itr->get_attr("name", std::string("Unnamed")); parameters params; - ptree::const_iterator paramIter = v.second.begin(); - ptree::const_iterator endParam = v.second.end(); + xml_node::const_iterator paramIter = itr->begin(); + xml_node::const_iterator endParam = itr->end(); for (; paramIter != endParam; ++paramIter) { - ptree const& param = paramIter->second; - - if (paramIter->first == "Parameter") + if (paramIter->name() == "Parameter") { - std::string name = get_attr(param, "name"); - std::string value = get_value( param, - "datasource parameter"); + std::string name = paramIter->get_attr("name"); + std::string value = paramIter->get_text(); params[name] = value; } - else if( paramIter->first != "" && - paramIter->first != "") - { - throw config_error(std::string("Unknown child node in ") + - "'Datasource'. Expected 'Parameter' but got '" + - paramIter->first + "'"); - } } datasource_templates_[name] = params; } - else if (v.first == "Parameters") + else if (itr->name() == "Parameters") { - std::string name = get_attr(v.second, "name", std::string("Unnamed")); + std::string name = itr->get_attr("name", std::string("Unnamed")); parameters & params = map.get_extra_parameters(); - ptree::const_iterator paramIter = v.second.begin(); - ptree::const_iterator endParam = v.second.end(); + xml_node::const_iterator paramIter = itr->begin(); + xml_node::const_iterator endParam = itr->end(); for (; paramIter != endParam; ++paramIter) { - ptree const& param = paramIter->second; - - if (paramIter->first == "Parameter") + if (paramIter->name() == "Parameter") { - std::string name = get_attr(param, "name"); + std::string name = paramIter->get_attr("name"); bool is_string = true; - boost::optional type = get_opt_attr(param, "type"); + boost::optional type = paramIter->get_opt_attr("type"); if (type) { if (*type == "int") { is_string = false; - int value = get_value( param,"parameter"); + int value = paramIter->get_value("parameter"); params[name] = value; } else if (*type == "float") { is_string = false; - double value = get_value( param,"parameter"); + double value = paramIter->get_value("parameter"); params[name] = value; } } if (is_string) { - std::string value = get_value( param, - "parameter"); + std::string value = paramIter->get_text(); params[name] = value; } } - else if( paramIter->first != "" && - paramIter->first != "" ) - { - throw config_error(std::string("Unknown child node in ") + - "'Parameters'. Expected 'Parameter' but got '" + - paramIter->first + "'"); - } } } - else if (v.first != "" && - v.first != "") - { - throw config_error(std::string("Unknown child node in 'Map': '") + - v.first + "'"); - } } - - map.init_metawriters(); } diff --git a/src/xml_tree.cpp b/src/xml_tree.cpp index a004748a6..494fc14d7 100644 --- a/src/xml_tree.cpp +++ b/src/xml_tree.cpp @@ -24,6 +24,7 @@ #include #include #include +#include //boost #include @@ -70,6 +71,12 @@ inline boost::optional fast_cast(xml_tree const& tree, std::string return value; } +template <> +inline boost::optional fast_cast(xml_tree const& tree, std::string const& value) +{ + return mapnik::color_factory::from_string(value); +} + /****************************************************************************/ From 7a052f81f7e7e36614d6c82de9e8a8ef4f3a71c9 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Tue, 6 Mar 2012 17:29:33 +0100 Subject: [PATCH 038/238] Update more functions. --- include/mapnik/xml_tree.hpp | 9 +- src/load_map.cpp | 181 ++++++++++++------------------------ 2 files changed, 64 insertions(+), 126 deletions(-) diff --git a/include/mapnik/xml_tree.hpp b/include/mapnik/xml_tree.hpp index bbb12589f..a9e48a260 100644 --- a/include/mapnik/xml_tree.hpp +++ b/include/mapnik/xml_tree.hpp @@ -41,7 +41,7 @@ class xml_attribute { public: std::string value; - bool processed; + mutable bool processed; }; class node_not_found: public std::exception @@ -66,9 +66,10 @@ public: std::string name() const; std::string text() const; bool is_text() const; + bool is(std::string const& name) const; - xml_node &add_child(std::string name, unsigned line=0, bool text_node = false); - void add_attribute(std::string name, std::string value); + xml_node &add_child(std::string const& name, unsigned line=0, bool text_node = false); + void add_attribute(std::string const& name, std::string const& value); void set_processed(bool processed); const_iterator begin() const; @@ -96,7 +97,7 @@ private: std::map attributes_; bool text_node_; unsigned line_; - bool processed_; + mutable bool processed_; }; diff --git a/src/load_map.cpp b/src/load_map.cpp index f549468a5..db80bd62f 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -98,13 +98,13 @@ public: void parse_map(Map & map, xml_node const& sty, std::string const& base_path=""); private: void parse_map_include( Map & map, xml_node const& include); - void parse_style(Map & map, ptree const & sty); - void parse_layer(Map & map, ptree const & lay); - void parse_metawriter(Map & map, ptree const & lay); + void parse_style(Map & map, xml_node const& sty); + void parse_layer(Map & map, xml_node const& lay); + void parse_metawriter(Map & map, xml_node const& lay); void parse_metawriter_in_symbolizer(symbolizer_base &sym, ptree const &pt); - void parse_fontset(Map & map, ptree const & fset); - void parse_font(font_set & fset, ptree const & f); + void parse_fontset(Map & map, xml_node const & fset); + void parse_font(font_set & fset, xml_node const& f); void parse_rule(feature_type_style & style, ptree const & r); @@ -390,6 +390,8 @@ void map_parser::parse_map(Map & map, xml_node const& pt, std::string const& bas void map_parser::parse_map_include(Map & map, xml_node const& include) { + try + { xml_node::const_iterator itr = include.begin(); xml_node::const_iterator end = include.end(); @@ -402,7 +404,7 @@ void map_parser::parse_map_include(Map & map, xml_node const& include) } else if (itr->name() == "Style") { -// parse_style(map, *itr); + parse_style(map, *itr); } else if (itr->name() == "Layer") { @@ -477,99 +479,73 @@ void map_parser::parse_map_include(Map & map, xml_node const& include) } } } + } catch (const config_error & ex) { + ex.append_context(std::string("in map '") + filename_ + "'"); + throw; + } + map.init_metawriters(); } -void map_parser::parse_style( Map & map, ptree const & sty ) +void map_parser::parse_style(Map & map, xml_node const& sty) { - std::ostringstream s(""); - s << "name," - << "filter-mode"; - ensure_attrs(sty, "Style", s.str()); - std::string name(""); try { - name = get_attr(sty, "name"); + name = sty.get_attr("name"); feature_type_style style; - filter_mode_e filter_mode = get_attr(sty, "filter-mode", FILTER_ALL); + filter_mode_e filter_mode = sty.get_attr("filter-mode", FILTER_ALL); style.set_filter_mode(filter_mode); - ptree::const_iterator ruleIter = sty.begin(); - ptree::const_iterator endRule = sty.end(); + xml_node::const_iterator ruleIter = sty.begin(); + xml_node::const_iterator endRule = sty.end(); for (; ruleIter!=endRule; ++ruleIter) { - ptree::value_type const& rule_tag = *ruleIter; - if (rule_tag.first == "Rule") + if (ruleIter->is("Rule")) { - parse_rule( style, rule_tag.second ); - } - else if (rule_tag.first != "" && - rule_tag.first != "" ) - { - throw config_error(std::string("Unknown child node in 'Style'. ") + - "Expected 'Rule' but got '" + rule_tag.first + "'"); +// parse_rule(style, rule_tag.second); } } map.insert_style(name, style); - } catch (const config_error & ex) { - if ( ! name.empty() ) { - ex.append_context(std::string("in style '") + name + "'"); - } - ex.append_context(std::string("in map '") + filename_ + "'"); + ex.append_context(std::string("in style '") + name + "'"); throw; } } -void map_parser::parse_metawriter(Map & map, ptree const & pt) +void map_parser::parse_metawriter(Map & map, xml_node const& pt) { - ensure_attrs(pt, "MetaWriter", "name,type,file,default-output,output-empty,pixel-coordinates"); std::string name(""); metawriter_ptr writer; try { - name = get_attr(pt, "name"); - writer = metawriter_create(pt); + name = pt.get_attr("name"); +// writer = metawriter_create(pt); map.insert_metawriter(name, writer); - } catch (const config_error & ex) { - if (!name.empty()) { - ex.append_context(std::string("in meta writer '") + name + "'"); - } - ex.append_context(std::string("in map '") + filename_ + "'"); - throw; + ex.append_context(std::string("in meta writer '") + name + "'"); } } -void map_parser::parse_fontset( Map & map, ptree const & fset ) +void map_parser::parse_fontset(Map & map, xml_node const& fset) { - ensure_attrs(fset, "FontSet", "name,Font"); std::string name(""); try { - name = get_attr(fset, "name"); + name = fset.get_attr("name"); font_set fontset(name); - ptree::const_iterator itr = fset.begin(); - ptree::const_iterator end = fset.end(); + xml_node::const_iterator itr = fset.begin(); + xml_node::const_iterator end = fset.end(); for (; itr != end; ++itr) { - ptree::value_type const& font_tag = *itr; - - if (font_tag.first == "Font") + if (itr->is("Font")) { - parse_font(fontset, font_tag.second); - } - else if (font_tag.first != "" && - font_tag.first != "" ) - { - throw config_error(std::string("Unknown child node in 'FontSet'. ") + - "Expected 'Font' but got '" + font_tag.first + "'"); + parse_font(fontset, *itr); } } @@ -579,19 +555,14 @@ void map_parser::parse_fontset( Map & map, ptree const & fset ) // when it's parsed fontsets_.insert(pair(name, fontset)); } catch (const config_error & ex) { - if ( ! name.empty() ) { - ex.append_context(std::string("in FontSet '") + name + "'"); - } - ex.append_context(std::string("in map '") + filename_ + "'"); + ex.append_context(std::string("in FontSet '") + name + "'"); throw; } } -void map_parser::parse_font(font_set & fset, ptree const & f) +void map_parser::parse_font(font_set &fset, xml_node const& f) { - ensure_attrs(f, "Font", "face-name"); - - optional face_name = get_opt_attr(f, "face-name"); + optional face_name = f.get_opt_attr("face-name"); if (face_name) { if ( strict_ ) @@ -606,85 +577,72 @@ void map_parser::parse_font(font_set & fset, ptree const & f) } } -void map_parser::parse_layer( Map & map, ptree const & lay ) +void map_parser::parse_layer(Map & map, xml_node const& lay) { std::string name; - std::ostringstream s(""); - s << "name," - << "srs," - << "status," - << "minzoom," - << "maxzoom," - << "queryable," - << "clear-label-cache," - << "cache-features," - << "group-by"; - ensure_attrs(lay, "Layer", s.str()); try { - name = get_attr(lay, "name", std::string("Unnamed")); + name = lay.get_attr("name", std::string("Unnamed")); // XXX if no projection is given inherit from map? [DS] - std::string srs = get_attr(lay, "srs", map.srs()); + std::string srs = lay.get_attr("srs", map.srs()); layer lyr(name, srs); - optional status = get_opt_attr(lay, "status"); + optional status = lay.get_opt_attr("status"); if (status) { - lyr.setActive( * status ); + lyr.setActive(*status); } - optional minZoom = get_opt_attr(lay, "minzoom"); + optional minZoom = lay.get_opt_attr("minzoom"); if (minZoom) { lyr.setMinZoom( * minZoom ); } - optional maxZoom = get_opt_attr(lay, "maxzoom"); + optional maxZoom = lay.get_opt_attr("maxzoom"); if (maxZoom) { lyr.setMaxZoom( * maxZoom ); } - optional queryable = get_opt_attr(lay, "queryable"); + optional queryable = lay.get_opt_attr("queryable"); if (queryable) { lyr.setQueryable( * queryable ); } optional clear_cache = - get_opt_attr(lay, "clear-label-cache"); + lay.get_opt_attr("clear-label-cache"); if (clear_cache) { lyr.set_clear_label_cache( * clear_cache ); } optional cache_features = - get_opt_attr(lay, "cache-features"); + lay.get_opt_attr("cache-features"); if (cache_features) { lyr.set_cache_features( * cache_features ); } optional group_by = - get_opt_attr(lay, "group-by"); + lay.get_opt_attr("group-by"); if (group_by) { lyr.set_group_by( * group_by ); } - ptree::const_iterator itr2 = lay.begin(); - ptree::const_iterator end2 = lay.end(); + xml_node::const_iterator child = lay.begin(); + xml_node::const_iterator end = lay.end(); - for(; itr2 != end2; ++itr2) + for(; child != end; ++child) { - ptree::value_type const& child = *itr2; - if (child.first == "StyleName") + if (child->is("StyleName")) { - ensure_attrs(child.second, "StyleName", "none"); - std::string style_name = get_value(child.second, "style name"); + std::string style_name = child->get_value("style name"); //TODO: get_text if (style_name.empty()) { std::ostringstream ss; @@ -699,11 +657,10 @@ void map_parser::parse_layer( Map & map, ptree const & lay ) lyr.add_style(style_name); } } - else if (child.first == "Datasource") + else if (child->is("Datasource")) { - ensure_attrs(child.second, "Datasource", "base"); parameters params; - optional base = get_opt_attr( child.second, "base" ); + optional base = child->get_opt_attr("base"); if( base ) { std::map::const_iterator base_itr = datasource_templates_.find(*base); @@ -711,27 +668,16 @@ void map_parser::parse_layer( Map & map, ptree const & lay ) params = base_itr->second; } - ptree::const_iterator paramIter = child.second.begin(); - ptree::const_iterator endParam = child.second.end(); + xml_node::const_iterator paramIter = child->begin(); + xml_node::const_iterator endParam = child->end(); for (; paramIter != endParam; ++paramIter) { - ptree const& param = paramIter->second; - - if (paramIter->first == "Parameter") + if (paramIter->is("Parameter")) { - ensure_attrs(param, "Parameter", "name"); - std::string name = get_attr(param, "name"); - std::string value = get_value( param, - "datasource parameter"); + std::string name = paramIter->get_attr("name"); + std::string value = paramIter->get_text(); params[name] = value; } - else if( paramIter->first != "" && - paramIter->first != "" ) - { - throw config_error(std::string("Unknown child node in ") + - "'Datasource'. Expected 'Parameter' but got '" + - paramIter->first + "'"); - } } boost::optional base_param = params.get("base"); @@ -763,23 +709,14 @@ void map_parser::parse_layer( Map & map, ptree const & lay ) throw config_error("Unknown exception occured attempting to create datasoure for layer '" + lyr.name() + "'"); } } - else if (child.first != "" && - child.first != "") - { - throw config_error(std::string("Unknown child node in 'Layer'. ") + - "Expected 'StyleName' or 'Datasource' but got '" + - child.first + "'"); - } } - map.addLayer(lyr); - } catch (const config_error & ex) { - if ( ! name.empty() ) + if (!name.empty()) { - ex.append_context(std::string("(encountered during parsing of layer '") + name + "' in map '" + filename_ + "')"); + ex.append_context(std::string(" encountered during parsing of layer '") + name + "'"); } throw; } From 7d3fd0755d668c7c6c51534ae97c5007b2e420d6 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Wed, 7 Mar 2012 01:35:37 +0100 Subject: [PATCH 039/238] Complete XML changes in load_map.cpp --- include/mapnik/formatting/base.hpp | 3 +- include/mapnik/formatting/expression.hpp | 2 +- include/mapnik/formatting/format.hpp | 2 +- include/mapnik/formatting/registry.hpp | 4 +- include/mapnik/formatting/text.hpp | 2 +- include/mapnik/text_placements/list.hpp | 2 +- include/mapnik/text_placements/registry.hpp | 2 +- include/mapnik/text_placements/simple.hpp | 2 +- include/mapnik/text_properties.hpp | 4 +- include/mapnik/xml_tree.hpp | 8 +- src/load_map.cpp | 525 +++++++------------- src/xml_tree.cpp | 2 +- tests/visual_tests/test.py | 2 +- 13 files changed, 186 insertions(+), 374 deletions(-) diff --git a/include/mapnik/formatting/base.hpp b/include/mapnik/formatting/base.hpp index 47efe70de..7c4f0a144 100644 --- a/include/mapnik/formatting/base.hpp +++ b/include/mapnik/formatting/base.hpp @@ -36,6 +36,7 @@ namespace mapnik { typedef std::set expression_set; class processed_text; +class xml_node; struct char_properties; namespace formatting { @@ -48,7 +49,7 @@ class node public: virtual ~node() {} virtual void to_xml(boost::property_tree::ptree &xml) const; - static node_ptr from_xml(boost::property_tree::ptree const& xml); + static node_ptr from_xml(xml_node const& xml); virtual void apply(char_properties const& p, Feature const& feature, processed_text &output) const = 0; virtual void add_expressions(expression_set &output) const; }; diff --git a/include/mapnik/formatting/expression.hpp b/include/mapnik/formatting/expression.hpp index e85b7d973..bd903a4d9 100644 --- a/include/mapnik/formatting/expression.hpp +++ b/include/mapnik/formatting/expression.hpp @@ -31,7 +31,7 @@ namespace formatting { class expression_format: public node { public: void to_xml(boost::property_tree::ptree &xml) const; - static node_ptr from_xml(boost::property_tree::ptree const& xml); + static node_ptr from_xml(xml_node const& xml); virtual void apply(char_properties const& p, Feature const& feature, processed_text &output) const; virtual void add_expressions(expression_set &output) const; diff --git a/include/mapnik/formatting/format.hpp b/include/mapnik/formatting/format.hpp index 53e09a47f..22bef2d8e 100644 --- a/include/mapnik/formatting/format.hpp +++ b/include/mapnik/formatting/format.hpp @@ -31,7 +31,7 @@ namespace formatting { class format_node: public node { public: void to_xml(boost::property_tree::ptree &xml) const; - static node_ptr from_xml(boost::property_tree::ptree const& xml); + static node_ptr from_xml(xml_node const& xml); virtual void apply(char_properties const& p, Feature const& feature, processed_text &output) const; virtual void add_expressions(expression_set &output) const; diff --git a/include/mapnik/formatting/registry.hpp b/include/mapnik/formatting/registry.hpp index 9c2c9df22..2362e86ba 100644 --- a/include/mapnik/formatting/registry.hpp +++ b/include/mapnik/formatting/registry.hpp @@ -38,7 +38,7 @@ namespace mapnik namespace formatting { -typedef node_ptr (*from_xml_function_ptr)(boost::property_tree::ptree const& xml); +typedef node_ptr (*from_xml_function_ptr)(xml_node const& xml); class registry : public singleton, private boost::noncopyable @@ -47,7 +47,7 @@ public: registry(); ~registry() {} void register_name(std::string name, from_xml_function_ptr ptr, bool overwrite=false); - node_ptr from_xml(std::string name, boost::property_tree::ptree const& xml); + node_ptr from_xml(std::string name, xml_node const& xml); private: std::map map_; }; diff --git a/include/mapnik/formatting/text.hpp b/include/mapnik/formatting/text.hpp index c216b4fc2..8fd1551d8 100644 --- a/include/mapnik/formatting/text.hpp +++ b/include/mapnik/formatting/text.hpp @@ -31,7 +31,7 @@ public: text_node(expression_ptr text): node(), text_(text) {} text_node(std::string text): node(), text_(parse_expression(text)) {} void to_xml(boost::property_tree::ptree &xml) const; - static node_ptr from_xml(boost::property_tree::ptree const& xml); + static node_ptr from_xml(xml_node const& xml); virtual void apply(char_properties const& p, Feature const& feature, processed_text &output) const; virtual void add_expressions(expression_set &output) const; diff --git a/include/mapnik/text_placements/list.hpp b/include/mapnik/text_placements/list.hpp index e6e0adaea..923df0c9d 100644 --- a/include/mapnik/text_placements/list.hpp +++ b/include/mapnik/text_placements/list.hpp @@ -38,7 +38,7 @@ public: text_symbolizer_properties & add(); text_symbolizer_properties & get(unsigned i); unsigned size() const; - static text_placements_ptr from_xml(boost::property_tree::ptree const &xml, fontset_map const & fontsets); + static text_placements_ptr from_xml(xml_node const &xml, fontset_map const & fontsets); private: std::vector list_; friend class text_placement_info_list; diff --git a/include/mapnik/text_placements/registry.hpp b/include/mapnik/text_placements/registry.hpp index c171a5089..d8fd8b48f 100644 --- a/include/mapnik/text_placements/registry.hpp +++ b/include/mapnik/text_placements/registry.hpp @@ -49,7 +49,7 @@ public: ~registry() {} void register_name(std::string name, from_xml_function_ptr ptr, bool overwrite=false); text_placements_ptr from_xml(std::string name, - boost::property_tree::ptree const& xml, + xml_node const& xml, fontset_map const & fontsets); private: std::map map_; diff --git a/include/mapnik/text_placements/simple.hpp b/include/mapnik/text_placements/simple.hpp index 4173ab87a..59c57d87e 100644 --- a/include/mapnik/text_placements/simple.hpp +++ b/include/mapnik/text_placements/simple.hpp @@ -52,7 +52,7 @@ public: text_placement_info_ptr get_placement_info(double scale_factor) const; void set_positions(std::string positions); std::string get_positions(); - static text_placements_ptr from_xml(boost::property_tree::ptree const &xml, fontset_map const & fontsets); + static text_placements_ptr from_xml(xml_node const &xml, fontset_map const & fontsets); private: std::string positions_; std::vector direction_; diff --git a/include/mapnik/text_properties.hpp b/include/mapnik/text_properties.hpp index 4362a632f..5168ebaa1 100644 --- a/include/mapnik/text_properties.hpp +++ b/include/mapnik/text_properties.hpp @@ -54,7 +54,7 @@ struct char_properties { char_properties(); /** Construct object from XML. */ - void from_xml(boost::property_tree::ptree const &sym, fontset_map const & fontsets); + void from_xml(xml_node const &sym, fontset_map const & fontsets); /** Write object to XML ptree. */ void to_xml(boost::property_tree::ptree &node, bool explicit_defaults, char_properties const &dfl=char_properties()) const; std::string face_name; @@ -124,7 +124,7 @@ struct text_symbolizer_properties { text_symbolizer_properties(); /** Load all values from XML ptree. */ - void from_xml(boost::property_tree::ptree const &sym, fontset_map const & fontsets); + void from_xml(xml_node const &sym, fontset_map const & fontsets); /** Save all values to XML ptree (but does not create a new parent node!). */ void to_xml(boost::property_tree::ptree &node, bool explicit_defaults, text_symbolizer_properties const &dfl=text_symbolizer_properties()) const; diff --git a/include/mapnik/xml_tree.hpp b/include/mapnik/xml_tree.hpp index a9e48a260..137fa83ab 100644 --- a/include/mapnik/xml_tree.hpp +++ b/include/mapnik/xml_tree.hpp @@ -75,8 +75,10 @@ public: const_iterator begin() const; const_iterator end() const; - xml_node & get_child(std::string name); - xml_node const& get_child(std::string name) const; + xml_node & get_child(std::string const& name); + xml_node const& get_child(std::string const& name) const; + xml_node *get_opt_child(std::string const& name) const; + bool has_child(std::string const& name) const; template boost::optional get_opt_attr(std::string const& name) const; @@ -89,7 +91,7 @@ public: std::string get_text() const; template - T get_value(std::string const& name) const; + T get_value() const; private: xml_tree &tree_; std::string name_; diff --git a/src/load_map.cpp b/src/load_map.cpp index db80bd62f..1fbeff3d5 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -73,7 +73,6 @@ using boost::lexical_cast; using boost::bad_lexical_cast; using boost::tokenizer; -using boost::property_tree::ptree; using std::cerr; using std::endl; @@ -101,32 +100,31 @@ private: void parse_style(Map & map, xml_node const& sty); void parse_layer(Map & map, xml_node const& lay); void parse_metawriter(Map & map, xml_node const& lay); - void parse_metawriter_in_symbolizer(symbolizer_base &sym, ptree const &pt); + void parse_metawriter_in_symbolizer(symbolizer_base &sym, xml_node const& pt); void parse_fontset(Map & map, xml_node const & fset); void parse_font(font_set & fset, xml_node const& f); - void parse_rule(feature_type_style & style, ptree const & r); + void parse_rule(feature_type_style & style, xml_node const & r); - void parse_point_symbolizer(rule & rule, ptree const & sym); - void parse_line_pattern_symbolizer(rule & rule, ptree const & sym); - void parse_polygon_pattern_symbolizer(rule & rule, ptree const & sym); - void parse_text_symbolizer(rule & rule, ptree const & sym); - void parse_shield_symbolizer(rule & rule, ptree const & sym); - void parse_line_symbolizer(rule & rule, ptree const & sym); - void parse_polygon_symbolizer(rule & rule, ptree const & sym); - void parse_building_symbolizer(rule & rule, ptree const & sym ); - void parse_raster_symbolizer(rule & rule, ptree const & sym ); - void parse_markers_symbolizer(rule & rule, ptree const & sym ); + void parse_point_symbolizer(rule & rule, xml_node const& sym); + void parse_line_pattern_symbolizer(rule & rule, xml_node const& sym); + void parse_polygon_pattern_symbolizer(rule & rule, xml_node const& sym); + void parse_text_symbolizer(rule & rule, xml_node const& sym); + void parse_shield_symbolizer(rule & rule, xml_node const& sym); + void parse_line_symbolizer(rule & rule, xml_node const& sym); + void parse_polygon_symbolizer(rule & rule, xml_node const& sym); + void parse_building_symbolizer(rule & rule, xml_node const& sym ); + void parse_raster_symbolizer(rule & rule, xml_node const& sym ); + void parse_markers_symbolizer(rule & rule, xml_node const& sym ); - void parse_raster_colorizer(raster_colorizer_ptr const& rc, ptree const& node ); - void parse_stroke(stroke & strk, ptree const & sym); + void parse_raster_colorizer(raster_colorizer_ptr const& rc, xml_node const& node ); + void parse_stroke(stroke & strk, xml_node const & sym); expression_ptr parse_expr(std::string const& expr); void ensure_font_face( const std::string & face_name ); std::string ensure_relative_to_xml( boost::optional opt_path ); - void ensure_attrs( ptree const& sym, std::string name, std::string attrs); boost::optional get_opt_color_attr(boost::property_tree::ptree const& node, std::string const& name); @@ -144,27 +142,7 @@ private: }; -void remove_empty_text_nodes(ptree &pt) -{ - ptree::iterator itr = pt.begin(); - ptree::iterator end = pt.end(); - while (itr!=end) - { - if (itr->first == "") { - std::string trimmed = boost::algorithm::trim_copy(itr->second.data()); - if (trimmed.empty()) { - itr = pt.erase(itr); - } else { - itr++; - } - } else { - remove_empty_text_nodes(itr->second); - itr++; - } - } -} -//#include void load_map(Map & map, std::string const& filename, bool strict) { xml_tree tree; @@ -183,7 +161,7 @@ void load_map(Map & map, std::string const& filename, bool strict) } #endif map_parser parser(strict, filename); -// parser.parse_map(map, pt); + parser.parse_map(map, tree.root()); } void load_map_string(Map & map, std::string const& str, bool strict, std::string const& base_path) @@ -209,7 +187,7 @@ void load_map_string(Map & map, std::string const& str, bool strict, std::string #endif map_parser parser(strict, base_path); -// parser.parse_map(map, tree.root(), base_path); + parser.parse_map(map, tree.root(), base_path); } expression_ptr map_parser::parse_expr(std::string const& str) @@ -408,15 +386,15 @@ void map_parser::parse_map_include(Map & map, xml_node const& include) } else if (itr->name() == "Layer") { -// parse_layer(map, *itr); + parse_layer(map, *itr); } else if (itr->name() == "FontSet") { -// parse_fontset(map, *itr); + parse_fontset(map, *itr); } else if (itr->name() == "MetaWriter") { -// parse_metawriter(map, *itr); + parse_metawriter(map, *itr); } else if (itr->name() == "FileSource") { @@ -459,13 +437,13 @@ void map_parser::parse_map_include(Map & map, xml_node const& include) if (*type == "int") { is_string = false; - int value = paramIter->get_value("parameter"); + int value = paramIter->get_value(); params[name] = value; } else if (*type == "float") { is_string = false; - double value = paramIter->get_value("parameter"); + double value = paramIter->get_value(); params[name] = value; } } @@ -505,7 +483,7 @@ void map_parser::parse_style(Map & map, xml_node const& sty) { if (ruleIter->is("Rule")) { -// parse_rule(style, rule_tag.second); + parse_rule(style, *ruleIter); } } @@ -523,7 +501,7 @@ void map_parser::parse_metawriter(Map & map, xml_node const& pt) try { name = pt.get_attr("name"); -// writer = metawriter_create(pt); + //TODO: writer = metawriter_create(pt); map.insert_metawriter(name, writer); } catch (const config_error & ex) { ex.append_context(std::string("in meta writer '") + name + "'"); @@ -642,7 +620,7 @@ void map_parser::parse_layer(Map & map, xml_node const& lay) if (child->is("StyleName")) { - std::string style_name = child->get_value("style name"); //TODO: get_text + std::string style_name = child->get_value(); //TODO: get_text if (style_name.empty()) { std::ostringstream ss; @@ -722,149 +700,140 @@ void map_parser::parse_layer(Map & map, xml_node const& lay) } } -void map_parser::parse_rule( feature_type_style & style, ptree const & r ) +void map_parser::parse_rule(feature_type_style & style, xml_node const& r) { - ensure_attrs(r, "Rule", "name"); std::string name; try { - name = get_attr( r, "name", std::string()); + name = r.get_attr("name", std::string()); rule rule(name); - optional filter_expr = - get_opt_child( r, "Filter"); - if (filter_expr) + xml_node *child = r.get_opt_child("Filter"); + if (child) { - rule.set_filter(parse_expr(*filter_expr)); + rule.set_filter(child->get_value()); } - if (has_child(r, "ElseFilter")) + if (r.has_child("ElseFilter")) { rule.set_else(true); } - if (has_child(r, "AlsoFilter")) + if (r.has_child("AlsoFilter")) { rule.set_also(true); } - optional min_scale = - get_opt_child(r, "MinScaleDenominator"); - if (min_scale) + child = r.get_opt_child("MinScaleDenominator"); + if (child) { - rule.set_min_scale(*min_scale); + rule.set_min_scale(child->get_value()); } - optional max_scale = - get_opt_child(r, "MaxScaleDenominator"); - if (max_scale) + child = r.get_opt_child("MaxScaleDenominator"); + if (child) { - rule.set_max_scale(*max_scale); + rule.set_max_scale(child->get_value()); } - ptree::const_iterator symIter = r.begin(); - ptree::const_iterator endSym = r.end(); + xml_node::const_iterator symIter = r.begin(); + xml_node::const_iterator endSym = r.end(); for( ;symIter != endSym; ++symIter) { - ptree::value_type const& sym = *symIter; - if ( sym.first == "PointSymbolizer") + if ( symIter->is("PointSymbolizer")) { - parse_point_symbolizer( rule, sym.second ); + parse_point_symbolizer(rule, *symIter); } - else if ( sym.first == "LinePatternSymbolizer") + else if (symIter->is("LinePatternSymbolizer")) { - parse_line_pattern_symbolizer( rule, sym.second ); + parse_line_pattern_symbolizer(rule, *symIter); } - else if ( sym.first == "PolygonPatternSymbolizer") + else if (symIter->is("PolygonPatternSymbolizer")) { - parse_polygon_pattern_symbolizer( rule, sym.second ); + parse_polygon_pattern_symbolizer(rule, *symIter); } - else if ( sym.first == "TextSymbolizer") + else if (symIter->is("TextSymbolizer")) { - parse_text_symbolizer( rule, sym.second ); + parse_text_symbolizer(rule, *symIter); } - else if ( sym.first == "ShieldSymbolizer") + else if (symIter->is("ShieldSymbolizer")) { - parse_shield_symbolizer( rule, sym.second ); + parse_shield_symbolizer(rule, *symIter); } - else if ( sym.first == "LineSymbolizer") + else if (symIter->is("LineSymbolizer")) { - parse_line_symbolizer( rule, sym.second ); + parse_line_symbolizer(rule, *symIter); } - else if ( sym.first == "PolygonSymbolizer") + else if (symIter->is("PolygonSymbolizer")) { - parse_polygon_symbolizer( rule, sym.second ); + parse_polygon_symbolizer(rule, *symIter); } - else if ( sym.first == "BuildingSymbolizer") + else if (symIter->is("BuildingSymbolizer")) { - parse_building_symbolizer( rule, sym.second ); + parse_building_symbolizer(rule, *symIter); } - else if ( sym.first == "RasterSymbolizer") + else if (symIter->is("RasterSymbolizer")) { - parse_raster_symbolizer( rule, sym.second ); + parse_raster_symbolizer( rule, *symIter); } - else if ( sym.first == "MarkersSymbolizer") + else if (symIter->is("MarkersSymbolizer")) { - parse_markers_symbolizer(rule, sym.second); - } - - else if ( sym.first != "MinScaleDenominator" && - sym.first != "MaxScaleDenominator" && - sym.first != "Filter" && - sym.first != "ElseFilter" && - sym.first != "AlsoFilter" && - sym.first != "" && - sym.first != "" ) - { - throw config_error(std::string("Unknown symbolizer '") + - sym.first + "'"); + parse_markers_symbolizer(rule, *symIter); } } - style.add_rule(rule); } catch (const config_error & ex) { - if ( ! name.empty() ) + if (!name.empty() ) { - ex.append_context(std::string("in rule '") + name + "' in map '" + filename_ + "')"); + ex.append_context(std::string("in rule '") + name + "'"); } throw; } } -void map_parser::parse_metawriter_in_symbolizer(symbolizer_base &sym, ptree const &pt) +void map_parser::parse_metawriter_in_symbolizer(symbolizer_base &sym, xml_node const &pt) { - optional writer = get_opt_attr(pt, "meta-writer"); + optional writer = pt.get_opt_attr("meta-writer"); if (!writer) return; - optional output = get_opt_attr(pt, "meta-output"); + optional output = pt.get_opt_attr("meta-output"); sym.add_metawriter(*writer, output); } -void map_parser::parse_point_symbolizer( rule & rule, ptree const & sym ) +void map_parser::parse_point_symbolizer(rule & rule, xml_node const & sym) { try { - std::stringstream s; - s << "file,base,allow-overlap,ignore-placement,opacity,placement,transform,meta-writer,meta-output"; + optional file = sym.get_opt_attr("file"); + optional base = sym.get_opt_attr("base"); + optional allow_overlap = sym.get_opt_attr("allow-overlap"); + optional ignore_placement = sym.get_opt_attr("ignore-placement"); + optional opacity = sym.get_opt_attr("opacity"); + optional transform_wkt = sym.get_opt_attr("transform"); - optional file = get_opt_attr(sym, "file"); - optional base = get_opt_attr(sym, "base"); - optional allow_overlap = - get_opt_attr(sym, "allow-overlap"); - optional ignore_placement = - get_opt_attr(sym, "ignore-placement"); - optional opacity = - get_opt_attr(sym, "opacity"); - - optional transform_wkt = get_opt_attr(sym, "transform"); + point_symbolizer symbol; + if (allow_overlap) + { + symbol.set_allow_overlap( * allow_overlap ); + } + if (opacity) + { + symbol.set_opacity( * opacity ); + } + if (ignore_placement) + { + symbol.set_ignore_placement( * ignore_placement ); + } + point_placement_e placement = + sym.get_attr("placement", CENTROID_POINT_PLACEMENT); + symbol.set_point_placement( placement ); if (file) { - ensure_attrs(sym, "PointSymbolizer", s.str()); try { if( base ) @@ -878,23 +847,7 @@ void map_parser::parse_point_symbolizer( rule & rule, ptree const & sym ) *file = ensure_relative_to_xml(file); - point_symbolizer symbol(parse_path(*file)); - - if (allow_overlap) - { - symbol.set_allow_overlap( * allow_overlap ); - } - if (opacity) - { - symbol.set_opacity( * opacity ); - } - if (ignore_placement) - { - symbol.set_ignore_placement( * ignore_placement ); - } - point_placement_e placement = - get_attr(sym, "placement", CENTROID_POINT_PLACEMENT); - symbol.set_point_placement( placement ); + symbol.set_filename(parse_path(*file)); if (transform_wkt) { @@ -930,32 +883,9 @@ void map_parser::parse_point_symbolizer( rule & rule, ptree const & sym ) std::clog << "### WARNING: " << msg << endl; } } - - } - else - { - ensure_attrs(sym, "PointSymbolizer", s.str()); - point_symbolizer symbol; - - if (allow_overlap) - { - symbol.set_allow_overlap( * allow_overlap ); - } - if (opacity) - { - symbol.set_opacity( * opacity ); - } - if (ignore_placement) - { - symbol.set_ignore_placement( * ignore_placement ); - } - point_placement_e placement = - get_attr(sym, "placement", CENTROID_POINT_PLACEMENT); - symbol.set_point_placement( placement ); - - parse_metawriter_in_symbolizer(symbol, sym); - rule.append(symbol); } + parse_metawriter_in_symbolizer(symbol, sym); + rule.append(symbol); } catch (const config_error & ex) { @@ -965,30 +895,17 @@ void map_parser::parse_point_symbolizer( rule & rule, ptree const & sym ) } -void map_parser::parse_markers_symbolizer( rule & rule, ptree const & sym ) +void map_parser::parse_markers_symbolizer(rule & rule, xml_node const& sym) { try { std::string filename(""); - optional file = get_opt_attr(sym, "file"); - optional base = get_opt_attr(sym, "base"); - optional transform_wkt = get_opt_attr(sym, "transform"); - - std::stringstream s; - //s << "file,opacity,spacing,max-error,allow-overlap,placement,"; - s << "file,base,transform,fill,opacity," - << "spacing,max-error,allow-overlap," - << "width,height,placement,marker-type," - << "stroke,stroke-width,stroke-opacity,stroke-linejoin," - << "stroke-linecap,stroke-dashoffset,stroke-dasharray," - // note: stroke-gamma intentionally left off here as markers do not support them - << "meta-writer,meta-output"; - ensure_attrs(sym, "MarkersSymbolizer", s.str()); + optional file = sym.get_opt_attr("file"); + optional base = sym.get_opt_attr("base"); + optional transform_wkt = sym.get_opt_attr("transform"); if (file) { - //s << "base,transform"; - //ensure_attrs(sym, "MarkersSymbolizer", s.str()); try { if (base) @@ -1015,14 +932,9 @@ void map_parser::parse_markers_symbolizer( rule & rule, ptree const & sym ) } } } - /*else - { - //s << "fill,marker-type,width,height"; - //ensure_attrs(sym, "MarkersSymbolizer", s.str()); - }*/ markers_symbolizer symbol(parse_path(filename)); - optional opacity = get_opt_attr(sym, "opacity"); + optional opacity = sym.get_opt_attr("opacity"); if (opacity) symbol.set_opacity( *opacity ); if (transform_wkt) @@ -1043,17 +955,17 @@ void map_parser::parse_markers_symbolizer( rule & rule, ptree const & sym ) symbol.set_transform(matrix); } - optional c = get_opt_color_attr(sym, "fill"); + optional c = sym.get_opt_attr("fill"); if (c) symbol.set_fill(*c); - optional spacing = get_opt_attr(sym, "spacing"); + optional spacing = sym.get_opt_attr("spacing"); if (spacing) symbol.set_spacing(*spacing); - optional max_error = get_opt_attr(sym, "max-error"); + optional max_error = sym.get_opt_attr("max-error"); if (max_error) symbol.set_max_error(*max_error); - optional allow_overlap = get_opt_attr(sym, "allow-overlap"); + optional allow_overlap = sym.get_opt_attr("allow-overlap"); if (allow_overlap) symbol.set_allow_overlap(*allow_overlap); - optional w = get_opt_attr(sym, "width"); - optional h = get_opt_attr(sym, "height"); + optional w = sym.get_opt_attr("width"); + optional h = sym.get_opt_attr("height"); if (w && h) { symbol.set_width(*w); @@ -1075,7 +987,7 @@ void map_parser::parse_markers_symbolizer( rule & rule, ptree const & sym ) parse_stroke(strk,sym); symbol.set_stroke(strk); - marker_placement_e placement = get_attr(sym, "placement", MARKER_LINE_PLACEMENT); + marker_placement_e placement = sym.get_attr("placement", MARKER_LINE_PLACEMENT); symbol.set_marker_placement( placement ); marker_type_e dfl_marker_type = ARROW; @@ -1083,7 +995,7 @@ void map_parser::parse_markers_symbolizer( rule & rule, ptree const & sym ) if (placement == MARKER_POINT_PLACEMENT) dfl_marker_type = ELLIPSE; - marker_type_e marker_type = get_attr(sym, "marker-type", dfl_marker_type); + marker_type_e marker_type = sym.get_attr("marker-type", dfl_marker_type); symbol.set_marker_type( marker_type ); parse_metawriter_in_symbolizer(symbol, sym); @@ -1096,13 +1008,12 @@ void map_parser::parse_markers_symbolizer( rule & rule, ptree const & sym ) } } -void map_parser::parse_line_pattern_symbolizer( rule & rule, ptree const & sym ) +void map_parser::parse_line_pattern_symbolizer( rule & rule, xml_node const & sym ) { - ensure_attrs(sym, "LinePatternSymbolizer", "file,base,meta-writer,meta-output"); try { - std::string file = get_attr(sym, "file"); - optional base = get_opt_attr(sym, "base"); + std::string file = sym.get_attr("file"); + optional base = sym.get_opt_attr("base"); try { @@ -1144,13 +1055,12 @@ void map_parser::parse_line_pattern_symbolizer( rule & rule, ptree const & sym ) } void map_parser::parse_polygon_pattern_symbolizer( rule & rule, - ptree const & sym ) + xml_node const & sym ) { - ensure_attrs(sym, "PolygonPatternSymbolizer", "file,base,alignment,gamma,meta-writer,meta-output"); try { - std::string file = get_attr(sym, "file"); - optional base = get_opt_attr(sym, "base"); + std::string file = sym.get_attr("file"); + optional base = sym.get_opt_attr("base"); try { @@ -1168,15 +1078,15 @@ void map_parser::parse_polygon_pattern_symbolizer( rule & rule, polygon_pattern_symbolizer symbol(parse_path(file)); // pattern alignment - pattern_alignment_e p_alignment = get_attr(sym, "alignment",LOCAL_ALIGNMENT); + pattern_alignment_e p_alignment = sym.get_attr("alignment",LOCAL_ALIGNMENT); symbol.set_alignment(p_alignment); // gamma - optional gamma = get_opt_attr(sym, "gamma"); + optional gamma = sym.get_opt_attr("gamma"); if (gamma) symbol.set_gamma(*gamma); // gamma method - optional gamma_method = get_opt_attr(sym, "gamma-method"); + optional gamma_method = sym.get_opt_attr("gamma-method"); if (gamma_method) symbol.set_gamma_method(*gamma_method); parse_metawriter_in_symbolizer(symbol, sym); @@ -1203,27 +1113,12 @@ void map_parser::parse_polygon_pattern_symbolizer( rule & rule, } } -void map_parser::parse_text_symbolizer( rule & rule, ptree const & sym ) +void map_parser::parse_text_symbolizer( rule & rule, xml_node const& sym ) { - std::stringstream s_common; - s_common << "name,face-name,fontset-name,size,fill,orientation," - << "dx,dy,placement,vertical-alignment,halo-fill," - << "halo-radius,text-ratio,wrap-width,wrap-before," - << "wrap-character,text-transform,line-spacing," - << "label-position-tolerance,character-spacing," - << "spacing,minimum-distance,minimum-padding,minimum-path-length," - << "avoid-edges,allow-overlap,opacity,max-char-angle-delta," - << "horizontal-alignment,justify-alignment"; - - std::stringstream s_symbolizer; - s_symbolizer << s_common.str() << ",placements,placement-type," - << "meta-writer,meta-output"; - - ensure_attrs(sym, "TextSymbolizer", s_symbolizer.str()); try { text_placements_ptr placement_finder; - optional placement_type = get_opt_attr(sym, "placement-type"); + optional placement_type = sym.get_opt_attr("placement-type"); if (placement_type) { placement_finder = placements::registry::instance()->from_xml(*placement_type, sym, fontsets_); } else { @@ -1245,28 +1140,12 @@ void map_parser::parse_text_symbolizer( rule & rule, ptree const & sym ) } } -void map_parser::parse_shield_symbolizer( rule & rule, ptree const & sym ) +void map_parser::parse_shield_symbolizer(rule & rule, xml_node const& sym ) { - std::string s_common( - "name,face-name,fontset-name,size,fill,orientation," - "dx,dy,placement,vertical-alignment,halo-fill," - "halo-radius,text-ratio,wrap-width,wrap-before," - "wrap-character,text-transform,line-spacing," - "label-position-tolerance,character-spacing," - "spacing,minimum-distance,minimum-padding,minimum-path-length," - "avoid-edges,allow-overlap,opacity,max-char-angle-delta," - "horizontal-alignment,justify-alignment"); - - std::string s_symbolizer(s_common + ",file,base," - "transform,shield-dx,shield-dy,text-opacity," - "unlock-image" - "placements,placement-type,meta-writer,meta-output"); - - ensure_attrs(sym, "ShieldSymbolizer", s_symbolizer); try { text_placements_ptr placement_finder; - optional placement_type = get_opt_attr(sym, "placement-type"); + optional placement_type = sym.get_opt_attr("placement-type"); if (placement_type) { placement_finder = placements::registry::instance()->from_xml(*placement_type, sym, fontsets_); } else { @@ -1279,7 +1158,7 @@ void map_parser::parse_shield_symbolizer( rule & rule, ptree const & sym ) shield_symbolizer shield_symbol = shield_symbolizer(placement_finder); /* Symbolizer specific attributes. */ - optional transform_wkt = get_opt_attr(sym, "transform"); + optional transform_wkt = sym.get_opt_attr("transform"); if (transform_wkt) { agg::trans_affine tr; @@ -1297,12 +1176,12 @@ void map_parser::parse_shield_symbolizer( rule & rule, ptree const & sym ) shield_symbol.set_transform(matrix); } // shield displacement - double shield_dx = get_attr(sym, "shield-dx", 0.0); - double shield_dy = get_attr(sym, "shield-dy", 0.0); + double shield_dx = sym.get_attr("shield-dx", 0.0); + double shield_dy = sym.get_attr("shield-dy", 0.0); shield_symbol.set_shield_displacement(shield_dx,shield_dy); // opacity - optional opacity = get_opt_attr(sym, "opacity"); + optional opacity = sym.get_opt_attr("opacity"); if (opacity) { shield_symbol.set_opacity(*opacity); @@ -1311,7 +1190,7 @@ void map_parser::parse_shield_symbolizer( rule & rule, ptree const & sym ) // text-opacity // TODO: Could be problematic because it is named opacity in TextSymbolizer but opacity has a diffrent meaning here. optional text_opacity = - get_opt_attr(sym, "text-opacity"); + sym.get_opt_attr("text-opacity"); if (text_opacity) { shield_symbol.set_text_opacity( * text_opacity ); @@ -1319,7 +1198,7 @@ void map_parser::parse_shield_symbolizer( rule & rule, ptree const & sym ) // unlock_image optional unlock_image = - get_opt_attr(sym, "unlock-image"); + sym.get_opt_attr("unlock-image"); if (unlock_image) { shield_symbol.set_unlock_image( * unlock_image ); @@ -1327,8 +1206,8 @@ void map_parser::parse_shield_symbolizer( rule & rule, ptree const & sym ) parse_metawriter_in_symbolizer(shield_symbol, sym); - std::string image_file = get_attr(sym, "file"); - optional base = get_opt_attr(sym, "base"); + std::string image_file = sym.get_attr("file"); + optional base = sym.get_opt_attr("base"); try { @@ -1366,42 +1245,42 @@ void map_parser::parse_shield_symbolizer( rule & rule, ptree const & sym ) } } -void map_parser::parse_stroke(stroke & strk, ptree const & sym) +void map_parser::parse_stroke(stroke & strk, xml_node const & sym) { // stroke color - optional c = get_opt_color_attr(sym, "stroke"); + optional c = sym.get_opt_attr("stroke"); if (c) strk.set_color(*c); // stroke-width - optional width = get_opt_attr(sym, "stroke-width"); + optional width = sym.get_opt_attr("stroke-width"); if (width) strk.set_width(*width); // stroke-opacity - optional opacity = get_opt_attr(sym, "stroke-opacity"); + optional opacity = sym.get_opt_attr("stroke-opacity"); if (opacity) strk.set_opacity(*opacity); // stroke-linejoin - optional line_join = get_opt_attr(sym, "stroke-linejoin"); + optional line_join = sym.get_opt_attr("stroke-linejoin"); if (line_join) strk.set_line_join(*line_join); // stroke-linecap - optional line_cap = get_opt_attr(sym, "stroke-linecap"); + optional line_cap = sym.get_opt_attr("stroke-linecap"); if (line_cap) strk.set_line_cap(*line_cap); // stroke-gamma - optional gamma = get_opt_attr(sym, "stroke-gamma"); + optional gamma = sym.get_opt_attr("stroke-gamma"); if (gamma) strk.set_gamma(*gamma); // stroke-gamma-method - optional gamma_method = get_opt_attr(sym, "stroke-gamma-method"); + optional gamma_method = sym.get_opt_attr("stroke-gamma-method"); if (gamma_method) strk.set_gamma_method(*gamma_method); // stroke-dashoffset - optional dash_offset = get_opt_attr(sym, "stroke-dashoffset"); + optional dash_offset = sym.get_opt_attr("stroke-dashoffset"); if (dash_offset) strk.set_dash_offset(*dash_offset); // stroke-dasharray - optional str = get_opt_attr(sym,"stroke-dasharray"); + optional str = sym.get_opt_attr("stroke-dasharray"); if (str) { tokenizer<> tok (*str); @@ -1441,15 +1320,8 @@ void map_parser::parse_stroke(stroke & strk, ptree const & sym) } } -void map_parser::parse_line_symbolizer( rule & rule, ptree const & sym ) +void map_parser::parse_line_symbolizer( rule & rule, xml_node const & sym ) { - std::stringstream s; - s << "stroke,stroke-width,stroke-opacity,stroke-linejoin," - << "stroke-linecap,stroke-gamma,stroke-dash-offset,stroke-dasharray," - << "rasterizer," - << "meta-writer,meta-output"; - - ensure_attrs(sym, "LineSymbolizer", s.str()); try { stroke strk; @@ -1457,8 +1329,8 @@ void map_parser::parse_line_symbolizer( rule & rule, ptree const & sym ) line_symbolizer symbol = line_symbolizer(strk); // rasterizer method - line_rasterizer_e rasterizer = get_attr(sym, "rasterizer", RASTERIZER_FULL); - //optional rasterizer_method = get_opt_attr(sym, "full"); + line_rasterizer_e rasterizer = sym.get_attr("rasterizer", RASTERIZER_FULL); + //optional rasterizer_method = sym.get_opt_attr("full"); symbol.set_rasterizer(rasterizer); parse_metawriter_in_symbolizer(symbol, sym); @@ -1472,23 +1344,22 @@ void map_parser::parse_line_symbolizer( rule & rule, ptree const & sym ) } -void map_parser::parse_polygon_symbolizer( rule & rule, ptree const & sym ) +void map_parser::parse_polygon_symbolizer( rule & rule, xml_node const & sym ) { - ensure_attrs(sym, "PolygonSymbolizer", "fill,fill-opacity,gamma,gamma-method,meta-writer,meta-output"); try { polygon_symbolizer poly_sym; // fill - optional fill = get_opt_color_attr(sym, "fill"); + optional fill = sym.get_opt_attr("fill"); if (fill) poly_sym.set_fill(*fill); // fill-opacity - optional opacity = get_opt_attr(sym, "fill-opacity"); + optional opacity = sym.get_opt_attr("fill-opacity"); if (opacity) poly_sym.set_opacity(*opacity); // gamma - optional gamma = get_opt_attr(sym, "gamma"); + optional gamma = sym.get_opt_attr("gamma"); if (gamma) poly_sym.set_gamma(*gamma); // gamma method - optional gamma_method = get_opt_attr(sym, "gamma-method"); + optional gamma_method = sym.get_opt_attr("gamma-method"); if (gamma_method) poly_sym.set_gamma_method(*gamma_method); parse_metawriter_in_symbolizer(poly_sym, sym); @@ -1502,21 +1373,20 @@ void map_parser::parse_polygon_symbolizer( rule & rule, ptree const & sym ) } -void map_parser::parse_building_symbolizer( rule & rule, ptree const & sym ) +void map_parser::parse_building_symbolizer( rule & rule, xml_node const & sym ) { - ensure_attrs(sym, "PolygonSymbolizer", "fill,fill-opacity,height,meta-writer,meta-output"); try { building_symbolizer building_sym; // fill - optional fill = get_opt_color_attr(sym, "fill"); + optional fill = sym.get_opt_attr("fill"); if (fill) building_sym.set_fill(*fill); // fill-opacity - optional opacity = get_opt_attr(sym, "fill-opacity"); + optional opacity = sym.get_opt_attr("fill-opacity"); if (opacity) building_sym.set_opacity(*opacity); // height - optional height = get_opt_attr(sym, "height"); + optional height = sym.get_opt_attr("height"); if (height) building_sym.set_height(parse_expr(*height)); parse_metawriter_in_symbolizer(building_sym, sym); @@ -1529,53 +1399,43 @@ void map_parser::parse_building_symbolizer( rule & rule, ptree const & sym ) } } -void map_parser::parse_raster_symbolizer( rule & rule, ptree const & sym ) +void map_parser::parse_raster_symbolizer( rule & rule, xml_node const & sym ) { - // no support for meta-writer,meta-output - ensure_attrs(sym, "RasterSymbolizer", "mode,scaling,opacity,filter-factor,mesh-size"); try { raster_symbolizer raster_sym; // mode - optional mode = get_opt_attr(sym, "mode"); + optional mode = sym.get_opt_attr("mode"); if (mode) raster_sym.set_mode(*mode); // scaling - optional scaling = get_opt_attr(sym, "scaling"); + optional scaling = sym.get_opt_attr("scaling"); if (scaling) raster_sym.set_scaling(*scaling); // opacity - optional opacity = get_opt_attr(sym, "opacity"); + optional opacity = sym.get_opt_attr("opacity"); if (opacity) raster_sym.set_opacity(*opacity); // filter factor - optional filter_factor = get_opt_attr(sym, "filter-factor"); + optional filter_factor = sym.get_opt_attr("filter-factor"); if (filter_factor) raster_sym.set_filter_factor(*filter_factor); // mesh-size - optional mesh_size = get_opt_attr(sym, "mesh-size"); + optional mesh_size = sym.get_opt_attr("mesh-size"); if (mesh_size) raster_sym.set_mesh_size(*mesh_size); - ptree::const_iterator cssIter = sym.begin(); - ptree::const_iterator endCss = sym.end(); + xml_node::const_iterator cssIter = sym.begin(); + xml_node::const_iterator endCss = sym.end(); for(; cssIter != endCss; ++cssIter) { - ptree::value_type const& css_tag = *cssIter; - - if (css_tag.first == "RasterColorizer") + if (cssIter->is("RasterColorizer")) { raster_colorizer_ptr colorizer(new raster_colorizer()); raster_sym.set_colorizer(colorizer); - parse_raster_colorizer(colorizer, css_tag.second); - } - else if (css_tag.first != "" && - css_tag.first != "" ) - { - throw config_error(std::string("Unknown child node. ") + - "Expected 'RasterColorizer' but got '" + css_tag.first + "'"); + parse_raster_colorizer(colorizer, *cssIter); } } //Note: raster_symbolizer doesn't support metawriters @@ -1589,14 +1449,13 @@ void map_parser::parse_raster_symbolizer( rule & rule, ptree const & sym ) } void map_parser::parse_raster_colorizer(raster_colorizer_ptr const& rc, - ptree const& node ) + xml_node const& node ) { try { - ensure_attrs(node, "RasterColorizer", "default-mode,default-color,epsilon"); // mode colorizer_mode default_mode = - get_attr(node, "default-mode", COLORIZER_LINEAR); + node.get_attr("default-mode", COLORIZER_LINEAR); if(default_mode == COLORIZER_INHERIT) { throw config_error("RasterColorizer mode must not be INHERIT. "); @@ -1604,7 +1463,7 @@ void map_parser::parse_raster_colorizer(raster_colorizer_ptr const& rc, rc->set_default_mode( default_mode ); // default colour - optional default_color = get_opt_color_attr(node, "default-color"); + optional default_color = node.get_opt_attr("default-color"); if (default_color) { rc->set_default_color( *default_color ); @@ -1612,7 +1471,7 @@ void map_parser::parse_raster_colorizer(raster_colorizer_ptr const& rc, // epsilon - optional eps = get_opt_attr(node, "epsilon"); + optional eps = node.get_opt_attr("epsilon"); if (eps) { if(*eps < 0) { @@ -1622,31 +1481,27 @@ void map_parser::parse_raster_colorizer(raster_colorizer_ptr const& rc, } - ptree::const_iterator stopIter = node.begin(); - ptree::const_iterator endStop = node.end(); + xml_node::const_iterator stopIter = node.begin(); + xml_node::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") + if (stopIter->is("stop")) { - ensure_attrs(stop_tag.second, "stop", "color,mode,value,label"); // colour is optional. - optional stopcolor = get_opt_color_attr(stop, "color"); + optional stopcolor = stopIter->get_opt_attr("color"); if (!stopcolor) { *stopcolor = *default_color; } // mode default to INHERIT colorizer_mode mode = - get_attr(stop, "mode", COLORIZER_INHERIT); + stopIter->get_attr("mode", COLORIZER_INHERIT); // value is required, and it must be bigger than the previous optional value = - get_opt_attr(stop, "value"); + stopIter->get_opt_attr("value"); if(!value) { throw config_error("stop tag missing value"); @@ -1658,7 +1513,7 @@ void map_parser::parse_raster_colorizer(raster_colorizer_ptr const& rc, maximumValue = *value; optional label = - get_opt_attr(stop, "label"); + stopIter->get_opt_attr("label"); //append the stop colorizer_stop tmpStop; @@ -1670,12 +1525,6 @@ void map_parser::parse_raster_colorizer(raster_colorizer_ptr const& rc, rc->add_stop(tmpStop); } - else if (stop_tag.first != "" && - stop_tag.first != "" ) - { - throw config_error(std::string("Unknown child node. ") + - "Expected 'stop' but got '" + stop_tag.first + "'"); - } } } catch (const config_error & ex) @@ -1720,44 +1569,4 @@ std::string map_parser::ensure_relative_to_xml( boost::optional opt return *opt_path; } -void map_parser::ensure_attrs(ptree const& sym, std::string name, std::string attrs) -{ - typedef ptree::key_type::value_type Ch; - optional attribs = sym.get_child_optional( boost::property_tree::xml_parser::xmlattr() ); - if (attribs) - { - std::set attr_set; - boost::split(attr_set, attrs, boost::is_any_of(",")); - std::ostringstream s(""); - s << "### " << name << " properties warning: "; - int missing = 0; - for (ptree::const_iterator it = attribs.get().begin(); it != attribs.get().end(); ++it) - { - std::string name = it->first; - bool found = (attr_set.find(name) != attr_set.end()); - if (!found) - { - if (missing) - { - s << ","; - } - s << "'" << name << "'"; - ++missing; - } - } - if (missing) { - if (missing > 1) - { - s << " are"; - } - else - { - s << " is"; - } - s << " invalid, acceptable values are:\n'" << attrs << "'\n"; - std::clog << s.str(); - } - } -} - } // end of namespace mapnik diff --git a/src/xml_tree.cpp b/src/xml_tree.cpp index 494fc14d7..2b98e2dc0 100644 --- a/src/xml_tree.cpp +++ b/src/xml_tree.cpp @@ -185,7 +185,7 @@ void xml_node::set_processed(bool processed) processed_ = processed; } -xml_node &xml_node::add_child(std::string name, unsigned line, bool text_node) +xml_node &xml_node::add_child(std::string const& name, unsigned line, bool text_node) { children_.push_back(xml_node(tree_, name, line, text_node)); return children_.back(); diff --git a/tests/visual_tests/test.py b/tests/visual_tests/test.py index b7509fd73..a7ded9ce5 100755 --- a/tests/visual_tests/test.py +++ b/tests/visual_tests/test.py @@ -51,7 +51,7 @@ def render(filename, width, height=100): return m if len(sys.argv) == 2: - files = [(sys.argv[1], 500)] + files = [(sys.argv[1], (500, 500))] elif len(sys.argv) > 2: files = [sys.argv[1:]] From f214675c69cedf59438729478b496d66dbc49ab8 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Wed, 7 Mar 2012 02:23:16 +0100 Subject: [PATCH 040/238] Modify all other files for new XML structure. --- include/mapnik/formatting/expression.hpp | 2 +- include/mapnik/formatting/registry.hpp | 2 +- include/mapnik/text_placements/registry.hpp | 2 +- src/formatting/base.cpp | 11 ++-- src/formatting/expression.cpp | 7 ++- src/formatting/format.cpp | 25 ++++---- src/formatting/registry.cpp | 7 ++- src/formatting/text.cpp | 9 +-- src/text_placements/list.cpp | 16 +++-- src/text_placements/registry.cpp | 2 +- src/text_placements/simple.cpp | 5 +- src/text_properties.cpp | 65 +++++++++++---------- src/xml_tree.cpp | 2 +- 13 files changed, 81 insertions(+), 74 deletions(-) diff --git a/include/mapnik/formatting/expression.hpp b/include/mapnik/formatting/expression.hpp index bd903a4d9..d3db09691 100644 --- a/include/mapnik/formatting/expression.hpp +++ b/include/mapnik/formatting/expression.hpp @@ -51,7 +51,7 @@ public: private: node_ptr child_; - static expression_ptr get_expression(boost::property_tree::ptree const& xml, std::string name); + static expression_ptr get_expression(xml_node const& xml, std::string name); }; } //ns formatting } //ns mapnik diff --git a/include/mapnik/formatting/registry.hpp b/include/mapnik/formatting/registry.hpp index 2362e86ba..7c4faa4d7 100644 --- a/include/mapnik/formatting/registry.hpp +++ b/include/mapnik/formatting/registry.hpp @@ -47,7 +47,7 @@ public: registry(); ~registry() {} void register_name(std::string name, from_xml_function_ptr ptr, bool overwrite=false); - node_ptr from_xml(std::string name, xml_node const& xml); + node_ptr from_xml(xml_node const& xml); private: std::map map_; }; diff --git a/include/mapnik/text_placements/registry.hpp b/include/mapnik/text_placements/registry.hpp index d8fd8b48f..35fd59f46 100644 --- a/include/mapnik/text_placements/registry.hpp +++ b/include/mapnik/text_placements/registry.hpp @@ -39,7 +39,7 @@ namespace placements { typedef text_placements_ptr (*from_xml_function_ptr)( - boost::property_tree::ptree const& xml, fontset_map const & fontsets); + xml_node const& xml, fontset_map const & fontsets); class registry : public singleton, private boost::noncopyable diff --git a/src/formatting/base.cpp b/src/formatting/base.cpp index e9c1ff3e7..307935809 100644 --- a/src/formatting/base.cpp +++ b/src/formatting/base.cpp @@ -23,6 +23,7 @@ #include #include #include +#include // boost #include @@ -38,18 +39,18 @@ void node::to_xml(boost::property_tree::ptree &xml) const #endif } -node_ptr node::from_xml(boost::property_tree::ptree const& xml) +node_ptr node::from_xml(xml_node const& xml) { list_node *list = new list_node(); node_ptr list_ptr(list); - boost::property_tree::ptree::const_iterator itr = xml.begin(); - boost::property_tree::ptree::const_iterator end = xml.end(); + xml_node::const_iterator itr = xml.begin(); + xml_node::const_iterator end = xml.end(); for (; itr != end; ++itr) { - if (itr->first == "" || itr->first == "" || itr->first == "Placement") + if (itr->name() == "Placement") { continue; } - node_ptr n = registry::instance()->from_xml(itr->first, itr->second); + node_ptr n = registry::instance()->from_xml(*itr); if (n) list->push_back(n); } if (list->get_children().size() == 1) { diff --git a/src/formatting/expression.cpp b/src/formatting/expression.cpp index 0ee7598bd..40bc1f466 100644 --- a/src/formatting/expression.cpp +++ b/src/formatting/expression.cpp @@ -27,6 +27,7 @@ #include #include #include +#include // boost @@ -51,7 +52,7 @@ void expression_format::to_xml(boost::property_tree::ptree &xml) const if (child_) child_->to_xml(new_node); } -node_ptr expression_format::from_xml(ptree const& xml) +node_ptr expression_format::from_xml(xml_node const& xml) { expression_format *n = new expression_format(); node_ptr np(n); @@ -72,9 +73,9 @@ node_ptr expression_format::from_xml(ptree const& xml) return np; } -expression_ptr expression_format::get_expression(ptree const& xml, std::string name) +expression_ptr expression_format::get_expression(xml_node const& xml, std::string name) { - boost::optional tmp = get_opt_attr(xml, name); + boost::optional tmp = xml.get_opt_attr(name); if (tmp) return parse_expression(*tmp); return expression_ptr(); } diff --git a/src/formatting/format.cpp b/src/formatting/format.cpp index 860896dce..564902617 100644 --- a/src/formatting/format.cpp +++ b/src/formatting/format.cpp @@ -21,6 +21,7 @@ *****************************************************************************/ #include #include +#include namespace mapnik { using boost::property_tree::ptree; @@ -44,7 +45,7 @@ void format_node::to_xml(ptree &xml) const } -node_ptr format_node::from_xml(ptree const& xml) +node_ptr format_node::from_xml(xml_node const& xml) { format_node *n = new format_node(); node_ptr np(n); @@ -52,19 +53,19 @@ node_ptr format_node::from_xml(ptree const& xml) node_ptr child = node::from_xml(xml); n->set_child(child); - n->face_name = get_opt_attr(xml, "face-name"); + n->face_name = xml.get_opt_attr("face-name"); /*TODO: Fontset is problematic. We don't have the fontsets pointer here... */ - n->text_size = get_opt_attr(xml, "size"); - n->character_spacing = get_opt_attr(xml, "character-spacing"); - n->line_spacing = get_opt_attr(xml, "line-spacing"); - n->text_opacity = get_opt_attr(xml, "opactity"); - boost::optional wrap = get_opt_attr(xml, "wrap-before"); + n->text_size = xml.get_opt_attr("size"); + n->character_spacing = xml.get_opt_attr("character-spacing"); + n->line_spacing = xml.get_opt_attr("line-spacing"); + n->text_opacity = xml.get_opt_attr("opactity"); + boost::optional wrap = xml.get_opt_attr("wrap-before"); if (wrap) n->wrap_before = *wrap; - n->wrap_char = get_opt_attr(xml, "wrap-character"); - n->text_transform = get_opt_attr(xml, "text-transform"); - n->fill = get_opt_attr(xml, "fill"); - n->halo_fill = get_opt_attr(xml, "halo-fill"); - n->halo_radius = get_opt_attr(xml, "halo-radius"); + n->wrap_char = xml.get_opt_attr("wrap-character"); + n->text_transform = xml.get_opt_attr("text-transform"); + n->fill = xml.get_opt_attr("fill"); + n->halo_fill = xml.get_opt_attr("halo-fill"); + n->halo_radius = xml.get_opt_attr("halo-radius"); return np; } diff --git a/src/formatting/registry.cpp b/src/formatting/registry.cpp index 2c3a29a4a..8cfb0c6a4 100644 --- a/src/formatting/registry.cpp +++ b/src/formatting/registry.cpp @@ -24,6 +24,7 @@ #include #include #include +#include namespace mapnik { @@ -46,10 +47,10 @@ void registry::register_name(std::string name, from_xml_function_ptr ptr, bool o } } -node_ptr registry::from_xml(std::string name, const boost::property_tree::ptree &xml) +node_ptr registry::from_xml(xml_node const& xml) { - std::map::const_iterator itr = map_.find(name); - if (itr == map_.end()) throw config_error("Unknown element '" + name + "'"); + std::map::const_iterator itr = map_.find(xml.name()); + if (itr == map_.end()) throw config_error("Unknown element '" + xml.name() + "'"); return itr->second(xml); } } //ns formatting diff --git a/src/formatting/text.cpp b/src/formatting/text.cpp index bfe3e80b2..0be312501 100644 --- a/src/formatting/text.cpp +++ b/src/formatting/text.cpp @@ -26,9 +26,7 @@ #include #include #include - -// boost -#include +#include namespace mapnik { @@ -45,10 +43,9 @@ void text_node::to_xml(ptree &xml) const } -node_ptr text_node::from_xml(boost::property_tree::ptree const& xml) +node_ptr text_node::from_xml(xml_node const& xml) { - std::string data = xml.data(); - boost::trim(data); + std::string data = xml.get_text(); if (data.empty()) return node_ptr(); //No text return node_ptr(new text_node(parse_expression(data, "utf8"))); } diff --git a/src/text_placements/list.cpp b/src/text_placements/list.cpp index a9bc7aa83..d2ce9cad0 100644 --- a/src/text_placements/list.cpp +++ b/src/text_placements/list.cpp @@ -20,9 +20,14 @@ * *****************************************************************************/ +//mapnik #include +#include + +//boost #include + namespace mapnik { @@ -83,20 +88,19 @@ unsigned text_placements_list::size() const return list_.size(); } -text_placements_ptr text_placements_list::from_xml(boost::property_tree::ptree const &xml, fontset_map const & fontsets) +text_placements_ptr text_placements_list::from_xml(xml_node const &xml, fontset_map const & fontsets) { using boost::property_tree::ptree; text_placements_list *list = new text_placements_list; text_placements_ptr ptr = text_placements_ptr(list); list->defaults.from_xml(xml, fontsets); - ptree::const_iterator itr = xml.begin(); - ptree::const_iterator end = xml.end(); + xml_node::const_iterator itr = xml.begin(); + xml_node::const_iterator end = xml.end(); for( ;itr != end; ++itr) { - if ((itr->first.find('<') != std::string::npos) || (itr->first != "Placement")) continue; -//TODO: ensure_attrs(symIter->second, "TextSymbolizer/Placement", s_common.str()); + if (itr->is_text() || itr->name() != "Placement") continue; text_symbolizer_properties &p = list->add(); - p.from_xml(itr->second, fontsets); + p.from_xml(*itr, fontsets); //TODO: if (strict_ && // !p.format.fontset.size()) // ensure_font_face(p.format.face_name); diff --git a/src/text_placements/registry.cpp b/src/text_placements/registry.cpp index 30668b78f..23950a751 100644 --- a/src/text_placements/registry.cpp +++ b/src/text_placements/registry.cpp @@ -44,7 +44,7 @@ void registry::register_name(std::string name, from_xml_function_ptr ptr, bool o } } -text_placements_ptr registry::from_xml(std::string name, const boost::property_tree::ptree &xml, fontset_map const& fontsets) +text_placements_ptr registry::from_xml(std::string name, xml_node const& xml, fontset_map const& fontsets) { std::map::const_iterator itr = map_.find(name); if (itr == map_.end()) throw config_error("Unknown placement-type '" + name + "'"); diff --git a/src/text_placements/simple.cpp b/src/text_placements/simple.cpp index 598082e55..ebaba1fc8 100644 --- a/src/text_placements/simple.cpp +++ b/src/text_placements/simple.cpp @@ -23,6 +23,7 @@ // mapnik #include #include +#include // boost #include @@ -167,10 +168,10 @@ std::string text_placements_simple::get_positions() return positions_; //TODO: Build string from data in direction_ and text_sizes_ } -text_placements_ptr text_placements_simple::from_xml(boost::property_tree::ptree const &xml, fontset_map const & fontsets) +text_placements_ptr text_placements_simple::from_xml(xml_node const &xml, fontset_map const & fontsets) { text_placements_ptr ptr = text_placements_ptr(boost::make_shared( - get_attr(xml, "placements", "X"))); + xml.get_attr("placements", "X"))); ptr->defaults.from_xml(xml, fontsets); return ptr; } diff --git a/src/text_properties.cpp b/src/text_properties.cpp index 864cdd4ec..09f3a85be 100644 --- a/src/text_properties.cpp +++ b/src/text_properties.cpp @@ -25,6 +25,7 @@ #include #include #include +#include namespace mapnik { @@ -72,45 +73,45 @@ formatting::node_ptr text_symbolizer_properties::format_tree() const return tree_; } -void text_symbolizer_properties::from_xml(boost::property_tree::ptree const &sym, fontset_map const & fontsets) +void text_symbolizer_properties::from_xml(xml_node const &sym, fontset_map const & fontsets) { - optional placement_ = get_opt_attr(sym, "placement"); + optional placement_ = sym.get_opt_attr("placement"); if (placement_) label_placement = *placement_; - optional valign_ = get_opt_attr(sym, "vertical-alignment"); + optional valign_ = sym.get_opt_attr("vertical-alignment"); if (valign_) valign = *valign_; - optional text_ratio_ = get_opt_attr(sym, "text-ratio"); + optional text_ratio_ = sym.get_opt_attr("text-ratio"); if (text_ratio_) text_ratio = *text_ratio_; - optional wrap_width_ = get_opt_attr(sym, "wrap-width"); + optional wrap_width_ = sym.get_opt_attr("wrap-width"); if (wrap_width_) wrap_width = *wrap_width_; - optional label_position_tolerance_ = get_opt_attr(sym, "label-position-tolerance"); + optional label_position_tolerance_ = sym.get_opt_attr("label-position-tolerance"); if (label_position_tolerance_) label_position_tolerance = *label_position_tolerance_; - optional spacing_ = get_opt_attr(sym, "spacing"); + optional spacing_ = sym.get_opt_attr("spacing"); if (spacing_) label_spacing = *spacing_; - optional minimum_distance_ = get_opt_attr(sym, "minimum-distance"); + optional minimum_distance_ = sym.get_opt_attr("minimum-distance"); if (minimum_distance_) minimum_distance = *minimum_distance_; - optional min_padding_ = get_opt_attr(sym, "minimum-padding"); + optional min_padding_ = sym.get_opt_attr("minimum-padding"); if (min_padding_) minimum_padding = *min_padding_; - optional min_path_length_ = get_opt_attr(sym, "minimum-path-length"); + optional min_path_length_ = sym.get_opt_attr("minimum-path-length"); if (min_path_length_) minimum_path_length = *min_path_length_; - optional avoid_edges_ = get_opt_attr(sym, "avoid-edges"); + optional avoid_edges_ = sym.get_opt_attr("avoid-edges"); if (avoid_edges_) avoid_edges = *avoid_edges_; - optional allow_overlap_ = get_opt_attr(sym, "allow-overlap"); + optional allow_overlap_ = sym.get_opt_attr("allow-overlap"); if (allow_overlap_) allow_overlap = *allow_overlap_; - optional halign_ = get_opt_attr(sym, "horizontal-alignment"); + optional halign_ = sym.get_opt_attr("horizontal-alignment"); if (halign_) halign = *halign_; - optional jalign_ = get_opt_attr(sym, "justify-alignment"); + optional jalign_ = sym.get_opt_attr("justify-alignment"); if (jalign_) jalign = *jalign_; /* Attributes needing special care */ - optional orientation_ = get_opt_attr(sym, "orientation"); + optional orientation_ = sym.get_opt_attr("orientation"); if (orientation_) orientation = parse_expression(*orientation_, "utf8"); - optional dx = get_opt_attr(sym, "dx"); + optional dx = sym.get_opt_attr("dx"); if (dx) displacement.first = *dx; - optional dy = get_opt_attr(sym, "dy"); + optional dy = sym.get_opt_attr("dy"); if (dy) displacement.second = *dy; - optional max_char_angle_delta_ = get_opt_attr(sym, "max-char-angle-delta"); + optional max_char_angle_delta_ = sym.get_opt_attr("max-char-angle-delta"); if (max_char_angle_delta_) max_char_angle_delta=(*max_char_angle_delta_)*(M_PI/180); - optional name_ = get_opt_attr(sym, "name"); + optional name_ = sym.get_opt_attr("name"); if (name_) { std::clog << "### WARNING: Using 'name' in TextSymbolizer/ShieldSymbolizer is deprecated!\n"; set_old_style_expression(parse_expression(*name_, "utf8")); @@ -230,34 +231,34 @@ char_properties::char_properties() : } -void char_properties::from_xml(boost::property_tree::ptree const &sym, fontset_map const & fontsets) +void char_properties::from_xml(xml_node const& sym, fontset_map const& fontsets) { - optional text_size_ = get_opt_attr(sym, "size"); + optional text_size_ = sym.get_opt_attr("size"); if (text_size_) text_size = *text_size_; - optional character_spacing_ = get_opt_attr(sym, "character-spacing"); + optional character_spacing_ = sym.get_opt_attr("character-spacing"); if (character_spacing_) character_spacing = *character_spacing_; - optional fill_ = get_opt_attr(sym, "fill"); + optional fill_ = sym.get_opt_attr("fill"); if (fill_) fill = *fill_; - optional halo_fill_ = get_opt_attr(sym, "halo-fill"); + optional halo_fill_ = sym.get_opt_attr("halo-fill"); if (halo_fill_) halo_fill = *halo_fill_; - optional halo_radius_ = get_opt_attr(sym, "halo-radius"); + optional halo_radius_ = sym.get_opt_attr("halo-radius"); if (halo_radius_) halo_radius = *halo_radius_; - optional wrap_before_ = get_opt_attr(sym, "wrap-before"); + optional wrap_before_ = sym.get_opt_attr("wrap-before"); if (wrap_before_) wrap_before = *wrap_before_; - optional tconvert_ = get_opt_attr(sym, "text-transform"); + optional tconvert_ = sym.get_opt_attr("text-transform"); if (tconvert_) text_transform = *tconvert_; - optional line_spacing_ = get_opt_attr(sym, "line-spacing"); + optional line_spacing_ = sym.get_opt_attr("line-spacing"); if (line_spacing_) line_spacing = *line_spacing_; - optional opacity_ = get_opt_attr(sym, "opacity"); + optional opacity_ = sym.get_opt_attr("opacity"); if (opacity_) text_opacity = *opacity_; - optional wrap_char_ = get_opt_attr(sym, "wrap-character"); + optional wrap_char_ = sym.get_opt_attr("wrap-character"); if (wrap_char_ && (*wrap_char_).size() > 0) wrap_char = ((*wrap_char_)[0]); - optional face_name_ = get_opt_attr(sym, "face-name"); + optional face_name_ = sym.get_opt_attr("face-name"); if (face_name_) { face_name = *face_name_; } - optional fontset_name_ = get_opt_attr(sym, "fontset-name"); + optional fontset_name_ = sym.get_opt_attr("fontset-name"); if (fontset_name_) { std::map::const_iterator itr = fontsets.find(*fontset_name_); if (itr != fontsets.end()) diff --git a/src/xml_tree.cpp b/src/xml_tree.cpp index 2b98e2dc0..5ed24c351 100644 --- a/src/xml_tree.cpp +++ b/src/xml_tree.cpp @@ -191,7 +191,7 @@ xml_node &xml_node::add_child(std::string const& name, unsigned line, bool text_ return children_.back(); } -xml_node & xml_node::get_child(std::string name) +xml_node & xml_node::get_child(std::string const& name) { std::list::iterator itr = children_.begin(); std::list::iterator end = children_.end(); From 9a05dc18280cd3e30ae55118b5e9afc155ceba97 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Wed, 7 Mar 2012 03:57:31 +0100 Subject: [PATCH 041/238] Add XML functions. --- include/mapnik/xml_tree.hpp | 34 +++++-- src/load_map.cpp | 2 +- src/xml_tree.cpp | 184 +++++++++++++++++++++++++++++++++++- 3 files changed, 207 insertions(+), 13 deletions(-) diff --git a/include/mapnik/xml_tree.hpp b/include/mapnik/xml_tree.hpp index 137fa83ab..534228e79 100644 --- a/include/mapnik/xml_tree.hpp +++ b/include/mapnik/xml_tree.hpp @@ -40,6 +40,7 @@ class color; class xml_attribute { public: + xml_attribute(std::string const& value); std::string value; mutable bool processed; }; @@ -47,16 +48,34 @@ public: class node_not_found: public std::exception { public: - node_not_found(std::string node_name) : node_name_(node_name) {} - virtual const char* what() const throw() - { - return ("Node "+node_name_+ "not found").c_str(); - } + node_not_found(std::string node_name); + virtual const char* what() const throw(); ~node_not_found() throw (); private: std::string node_name_; }; +class attribute_not_found: public std::exception +{ +public: + attribute_not_found(std::string const& node_name, std::string const& attribute_name); + virtual const char* what() const throw(); + ~attribute_not_found() throw (); +private: + std::string node_name_; + std::string attribute_name_; +}; + +class more_than_one_child: public std::exception +{ +public: + more_than_one_child(std::string const& node_name); + virtual const char* what() const throw(); + ~more_than_one_child() throw (); +private: + std::string node_name_; +}; + class xml_node { public: @@ -70,14 +89,15 @@ public: xml_node &add_child(std::string const& name, unsigned line=0, bool text_node = false); void add_attribute(std::string const& name, std::string const& value); - void set_processed(bool processed); + + void set_processed(bool processed) const; const_iterator begin() const; const_iterator end() const; xml_node & get_child(std::string const& name); xml_node const& get_child(std::string const& name) const; - xml_node *get_opt_child(std::string const& name) const; + xml_node const* get_opt_child(std::string const& name) const; bool has_child(std::string const& name) const; template diff --git a/src/load_map.cpp b/src/load_map.cpp index 1fbeff3d5..1d03075d4 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -708,7 +708,7 @@ void map_parser::parse_rule(feature_type_style & style, xml_node const& r) name = r.get_attr("name", std::string()); rule rule(name); - xml_node *child = r.get_opt_child("Filter"); + xml_node const* child = r.get_opt_child("Filter"); if (child) { rule.set_filter(child->get_value()); diff --git a/src/xml_tree.cpp b/src/xml_tree.cpp index 5ed24c351..415107da8 100644 --- a/src/xml_tree.cpp +++ b/src/xml_tree.cpp @@ -25,6 +25,8 @@ #include #include #include +#include +#include //boost #include @@ -152,6 +154,68 @@ xml_node &xml_tree::root() return node_; } +/****************************************************************************/ +xml_attribute::xml_attribute(std::string const& value_) + : value(value_), processed(false) +{ + +} + +/****************************************************************************/ + +node_not_found::node_not_found(std::string node_name) + : node_name_(node_name) +{ + +} + +const char* node_not_found::what() const throw() +{ + return ("Node "+node_name_+ "not found").c_str(); +} + +node_not_found::~node_not_found() throw() +{ + +} + + +attribute_not_found::attribute_not_found( + std::string const& node_name, + std::string const& attribute_name) + : + node_name_(node_name), + attribute_name_(attribute_name) +{ + +} + +const char* attribute_not_found::what() const throw() +{ + return ("Attribute '" + attribute_name_ +"' not found in node '"+node_name_+ "'").c_str(); +} + +attribute_not_found::~attribute_not_found() throw() +{ + +} + +more_than_one_child::more_than_one_child(std::string const& node_name) + : node_name_(node_name) +{ + +} + +const char* more_than_one_child::what() const throw() +{ + return ("More than one child node in node '" + node_name_ +"'").c_str(); +} + +more_than_one_child::~more_than_one_child() throw() +{ + +} + /****************************************************************************/ xml_node::xml_node(xml_tree &tree, std::string name, unsigned line, bool text_node) @@ -169,7 +233,7 @@ std::string xml_node::name() const if (!text_node_) return name_; else - return ""; //TODO: throw + return ""; } std::string xml_node::text() const @@ -180,9 +244,19 @@ std::string xml_node::text() const return "NOT A TEXT NODE"; //TODO: throw } -void xml_node::set_processed(bool processed) +bool xml_node::is_text() const { - processed_ = processed; + return text_node_; +} + +bool xml_node::is(std::string const& name) const +{ + if (name_ == name) + { + processed_ = true; + return true; + } + return false; } xml_node &xml_node::add_child(std::string const& name, unsigned line, bool text_node) @@ -191,6 +265,26 @@ xml_node &xml_node::add_child(std::string const& name, unsigned line, bool text_ return children_.back(); } +void xml_node::add_attribute(std::string const& name, std::string const& value) +{ + attributes_.insert(std::make_pair(name,xml_attribute(value))); +} + +void xml_node::set_processed(bool processed) const +{ + processed_ = processed; +} + +xml_node::const_iterator xml_node::begin() const +{ + return children_.begin(); +} + +xml_node::const_iterator xml_node::end() const +{ + return children_.end(); +} + xml_node & xml_node::get_child(std::string const& name) { std::list::iterator itr = children_.begin(); @@ -206,20 +300,100 @@ xml_node & xml_node::get_child(std::string const& name) throw node_not_found(name); } +xml_node const& xml_node::get_child(std::string const& name) const +{ + xml_node const* node = get_opt_child(name); + if (!node) throw node_not_found(name); + return *node; +} + +xml_node const* xml_node::get_opt_child(std::string const& name) const +{ + const_iterator itr = children_.begin(); + const_iterator end = children_.end(); + for (; itr != end; itr++) + { + if (!(itr->text_node_) && itr->name_ == name) + { + itr->set_processed(true); + return &(*itr); + } + } + return 0; +} + +bool xml_node::has_child(std::string const& name) const +{ + return get_opt_child(name) != 0; +} + template boost::optional xml_node::get_opt_attr(std::string const& name) const { std::map::const_iterator itr = attributes_.find(name); if (itr == attributes_.end()) return boost::optional(); - boost::optional result = fast_cast(itr->second); + itr->second.processed = true; + boost::optional result = fast_cast(tree_, itr->second.value); if (!result) { throw config_error(std::string("Failed to parse attribute '") + name + "'. Expected " + name_trait::name() + - " but got '" + itr->second + "'"); + " but got '" + itr->second.value + "'"); } return result; } +template +T xml_node::get_attr(std::string const& name, T const& default_value) const +{ + boost::optional value = get_opt_attr(name); + if (value) return *value; + return default_value; +} +template +T xml_node::get_attr(std::string const& name) const +{ + boost::optional value = get_opt_attr(name); + if (value) return *value; + throw attribute_not_found(name_, name); +} + +std::string xml_node::get_text() const +{ + if (children_.size() == 0) + { + return ""; + } + if (children_.size() == 1) + { + return children_.front().text(); + } + throw more_than_one_child(name_); +} + + +template +T xml_node::get_value() const +{ + boost::optional result = fast_cast(get_text()); + if (!result) + { + throw config_error(std::string("Failed to parse value in node '") + + name_ + "'. Expected " + name_trait::name() + + " but got '" + get_text() + "'"); + } + return result; +} + +#define compile_get_opt_attr(T) template boost::optional xml_node::get_opt_attr(std::string const&) const + +//compile_get_opt_attr(boolean); +compile_get_opt_attr(std::string); +compile_get_opt_attr(unsigned); +compile_get_opt_attr(float); +compile_get_opt_attr(double); +compile_get_opt_attr(color); +compile_get_opt_attr(gamma_method_e); +compile_get_opt_attr(line_rasterizer_e); } //ns mapnik From daf30ca0d17d01c5ebc59415eb752ddb61ed3024 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Wed, 7 Mar 2012 15:26:13 +0100 Subject: [PATCH 042/238] Update metawriter_factory.hpp --- include/mapnik/metawriter_factory.hpp | 3 ++- src/load_map.cpp | 5 ++--- src/metawriter_factory.cpp | 22 ++++++++++++---------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/include/mapnik/metawriter_factory.hpp b/include/mapnik/metawriter_factory.hpp index d5b8911fd..1af7ff5d7 100644 --- a/include/mapnik/metawriter_factory.hpp +++ b/include/mapnik/metawriter_factory.hpp @@ -30,6 +30,7 @@ #include namespace mapnik { +class xml_node; /** * Creates a metawriter with the properties specified in the property @@ -37,7 +38,7 @@ namespace mapnik { * metawriters, but should provide an easy point to make them a * proper factory method if this is wanted in the future. */ -metawriter_ptr metawriter_create(const boost::property_tree::ptree &pt); +metawriter_ptr metawriter_create(xml_node const& pt); /** * Writes properties into the given property tree representing the diff --git a/src/load_map.cpp b/src/load_map.cpp index 1d03075d4..6120d22c7 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -35,7 +35,6 @@ #include #include -#include #ifdef HAVE_LIBXML2 #include #endif @@ -501,7 +500,7 @@ void map_parser::parse_metawriter(Map & map, xml_node const& pt) try { name = pt.get_attr("name"); - //TODO: writer = metawriter_create(pt); + writer = metawriter_create(pt); map.insert_metawriter(name, writer); } catch (const config_error & ex) { ex.append_context(std::string("in meta writer '") + name + "'"); @@ -620,7 +619,7 @@ void map_parser::parse_layer(Map & map, xml_node const& lay) if (child->is("StyleName")) { - std::string style_name = child->get_value(); //TODO: get_text + std::string style_name = child->get_text(); if (style_name.empty()) { std::ostringstream ss; diff --git a/src/metawriter_factory.cpp b/src/metawriter_factory.cpp index d79201838..2d05a70aa 100644 --- a/src/metawriter_factory.cpp +++ b/src/metawriter_factory.cpp @@ -22,14 +22,13 @@ //$Id$ #include -#include - #include #include +#include +#include #include -using boost::property_tree::ptree; using boost::optional; using std::string; @@ -37,20 +36,21 @@ namespace mapnik { metawriter_ptr -metawriter_create(const boost::property_tree::ptree &pt) { +metawriter_create(xml_node const& pt) +{ metawriter_ptr writer; - string type = get_attr(pt, "type"); + string type = pt.get_attr("type"); - optional properties = get_opt_attr(pt, "default-output"); + optional properties = pt.get_opt_attr("default-output"); if (type == "json") { - string file = get_attr(pt, "file"); + string file = pt.get_attr("file"); metawriter_json_ptr json = metawriter_json_ptr(new metawriter_json(properties, parse_path(file))); - optional output_empty = get_opt_attr(pt, "output-empty"); + optional output_empty = pt.get_opt_attr("output-empty"); if (output_empty) { json->set_output_empty(*output_empty); } - optional pixel_coordinates = get_opt_attr(pt, "pixel-coordinates"); + optional pixel_coordinates = pt.get_opt_attr("pixel-coordinates"); if (pixel_coordinates) { json->set_pixel_coordinates(*pixel_coordinates); } @@ -67,7 +67,9 @@ metawriter_create(const boost::property_tree::ptree &pt) { } void -metawriter_save(const metawriter_ptr &metawriter, ptree &metawriter_node, bool explicit_defaults) { +metawriter_save(const metawriter_ptr &metawriter, + boost::property_tree::ptree &metawriter_node, bool explicit_defaults) +{ metawriter_json *json = dynamic_cast(metawriter.get()); if (json) { From adfa73f64ac14ad9a832fb6616acee53661df827 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 7 Mar 2012 08:54:04 -0800 Subject: [PATCH 043/238] avoid combining default arg with const& --- src/load_map.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/load_map.cpp b/src/load_map.cpp index eea4353a8..3cfd51fb0 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -94,7 +94,7 @@ public: expr_grammar_(tr_) {} - void parse_map(Map & map, ptree const & sty, std::string const& base_path=""); + void parse_map(Map & map, ptree const & sty, std::string const& base_path); private: void parse_map_include( Map & map, ptree const & include); void parse_style(Map & map, ptree const & sty); @@ -181,7 +181,8 @@ void load_map(Map & map, std::string const& filename, bool strict) } #endif map_parser parser( strict, filename); - parser.parse_map(map, pt); + std::string base_path(""); + parser.parse_map(map, pt, base_path); } void load_map_string(Map & map, std::string const& str, bool strict, std::string base_path) From c3cd50ff57b58d21b3c4a7dda6e123f4c6b89ef9 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Wed, 7 Mar 2012 19:16:41 +0100 Subject: [PATCH 044/238] Complete new XML structure. --- include/mapnik/boolean.hpp | 90 ++++ include/mapnik/color.hpp | 8 + include/mapnik/ptree_helpers.hpp | 425 ------------------ include/mapnik/xml_tree.hpp | 4 + plugins/input/csv/csv_datasource.cpp | 3 +- plugins/input/gdal/gdal_datasource.cpp | 2 +- plugins/input/geos/geos_datasource.cpp | 2 +- plugins/input/kismet/kismet_datasource.cpp | 2 +- plugins/input/occi/occi_datasource.cpp | 2 +- plugins/input/ogr/ogr_datasource.cpp | 2 +- plugins/input/postgis/postgis_datasource.cpp | 2 +- .../rasterlite/rasterlite_datasource.cpp | 2 +- plugins/input/sqlite/sqlite_datasource.cpp | 2 +- src/formatting/text.cpp | 2 +- src/xml_tree.cpp | 35 +- 15 files changed, 145 insertions(+), 438 deletions(-) create mode 100644 include/mapnik/boolean.hpp diff --git a/include/mapnik/boolean.hpp b/include/mapnik/boolean.hpp new file mode 100644 index 000000000..dc74d3d8b --- /dev/null +++ b/include/mapnik/boolean.hpp @@ -0,0 +1,90 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2011 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 + * + *****************************************************************************/ +#ifndef MAPNIK_BOOLEAN_HPP +#define MAPNIK_BOOLEAN_HPP +#include + +namespace mapnik +{ +/** Helper for class bool */ +class boolean { +public: + boolean() {} + boolean(bool b) : b_(b) {} + boolean(boolean const& b) : b_(b.b_) {} + + operator bool() const + { + return b_; + } + boolean & operator = (boolean const& other) + { + b_ = other.b_; + return * this; + } + boolean & operator = (bool other) + { + b_ = other; + return * this; + } +private: + bool b_; +}; + +/** Special stream input operator for boolean values */ +template +std::basic_istream & +operator >> ( std::basic_istream & s, boolean & b ) +{ + std::string word; + s >> word; + if ( s ) + { + if ( word == "true" || word == "yes" || word == "on" || + word == "1") + { + b = true; + } + else if ( word == "false" || word == "no" || word == "off" || + word == "0") + { + b = false; + } + else + { + s.setstate( std::ios::failbit ); + } + } + return s; +} + +template +std::basic_ostream & +operator << ( std::basic_ostream & s, boolean const& b ) +{ + s << ( b ? "true" : "false" ); + return s; +} + +} + +#endif // MAPNIK_BOOLEAN_HPP diff --git a/include/mapnik/color.hpp b/include/mapnik/color.hpp index c7f2de610..ad9806276 100644 --- a/include/mapnik/color.hpp +++ b/include/mapnik/color.hpp @@ -137,6 +137,14 @@ public: std::string to_hex_string() const; }; +template +std::basic_ostream & +operator << ( std::basic_ostream & s, mapnik::color const& c ) +{ + std::string hex_string( c.to_string() ); + s << hex_string; + return s; +} } diff --git a/include/mapnik/ptree_helpers.hpp b/include/mapnik/ptree_helpers.hpp index 811317312..64167b590 100644 --- a/include/mapnik/ptree_helpers.hpp +++ b/include/mapnik/ptree_helpers.hpp @@ -23,442 +23,17 @@ #ifndef MAPNIK_PTREE_HELPERS_HPP #define MAPNIK_PTREE_HELPERS_HPP -// mapnik -#include -#include -#include -#include // boost #include -#include -#include - -// stl -#include -#include namespace mapnik { -template -inline boost::optional fast_cast(std::string const& value); - -template -T get(boost::property_tree::ptree const& node, std::string const& name, bool is_attribute, - T const& default_value); -template -T get(boost::property_tree::ptree const& node, std::string const& name, bool is_attribute); -template -T get_value(boost::property_tree::ptree const& node, std::string const& name); -template -boost::optional get_optional(boost::property_tree::ptree const& node, std::string const& name, - bool is_attribute); - -template -boost::optional get_opt_attr( boost::property_tree::ptree const& node, - std::string const& name) -{ - return get_optional( node, name, true); -} - -template -boost::optional get_opt_child( boost::property_tree::ptree const& node, - std::string const& name) -{ - return get_optional( node, name, false); -} - -template -T get_attr( boost::property_tree::ptree const& node, std::string const& name, - T const& default_value ) -{ - return get( node, name, true, default_value); -} - -template -T get_attr( boost::property_tree::ptree const& node, std::string const& name ) -{ - return get( node, name, true ); -} - - -template -std::basic_ostream & -operator << ( std::basic_ostream & s, mapnik::color const& c ) -{ - std::string hex_string( c.to_string() ); - s << hex_string; - return s; -} - -/** Helper for class bool */ -class boolean { -public: - boolean() {} - boolean(bool b) : b_(b) {} - boolean(boolean const& b) : b_(b.b_) {} - - operator bool() const - { - return b_; - } - boolean & operator = (boolean const& other) - { - b_ = other.b_; - return * this; - } - boolean & operator = (bool other) - { - b_ = other; - return * this; - } -private: - bool b_; -}; - -/** Special stream input operator for boolean values */ -template -std::basic_istream & -operator >> ( std::basic_istream & s, boolean & b ) -{ - std::string word; - s >> word; - if ( s ) - { - if ( word == "true" || word == "yes" || word == "on" || - word == "1") - { - b = true; - } - else if ( word == "false" || word == "no" || word == "off" || - word == "0") - { - b = false; - } - else - { - s.setstate( std::ios::failbit ); - } - } - return s; -} - -template -std::basic_ostream & -operator << ( std::basic_ostream & s, boolean const& b ) -{ - s << ( b ? "true" : "false" ); - return s; -} - template void set_attr(boost::property_tree::ptree & pt, std::string const& name, T const& v) { pt.put("." + name, v); } - -class boolean; - -template -struct name_trait -{ - static std::string name() - { - return ""; - } - // missing name_trait for type ... - // if you get here you are probably using a new type - // in the XML file. Just add a name trait for the new - // type below. - BOOST_STATIC_ASSERT( sizeof(T) == 0 ); -}; - -#define DEFINE_NAME_TRAIT( type, type_name ) \ - template <> \ - struct name_trait \ - { \ - static std::string name() { return std::string("type ") + type_name; } \ - }; - - -DEFINE_NAME_TRAIT( double, "double") -DEFINE_NAME_TRAIT( float, "float") -DEFINE_NAME_TRAIT( unsigned, "unsigned") -DEFINE_NAME_TRAIT( boolean, "boolean") -DEFINE_NAME_TRAIT( int, "integer" ) -DEFINE_NAME_TRAIT( std::string, "string" ) -DEFINE_NAME_TRAIT( color, "color" ) - -template -struct name_trait< mapnik::enumeration > -{ - typedef enumeration Enum; - - static std::string name() - { - std::string value_list("one of ["); - for (unsigned i = 0; i < Enum::MAX; ++i) - { - value_list += Enum::get_string( i ); - if ( i + 1 < Enum::MAX ) value_list += ", "; - } - value_list += "]"; - - return value_list; - } -}; - -template -inline boost::optional fast_cast(std::string const& value) -{ - return boost::lexical_cast( value ); -} - -template <> -inline boost::optional fast_cast(std::string const& value) -{ - int result; - if (mapnik::conversions::string2int(value,result)) - return boost::optional(result); - return boost::optional(); -} - -template <> -inline boost::optional fast_cast(std::string const& value) -{ - double result; - if (mapnik::conversions::string2double(value,result)) - return boost::optional(result); - return boost::optional(); -} - -template <> -inline boost::optional fast_cast(std::string const& value) -{ - float result; - if (mapnik::conversions::string2float(value,result)) - return boost::optional(result); - return boost::optional(); -} - -template -T get(boost::property_tree::ptree const& node, - std::string const& name, - bool is_attribute, - T const& default_value) -{ - boost::optional str; - if (is_attribute) - { - str = node.get_optional( std::string(".") + name ); - } - else - { - str = node.get_optional(name + "."); - } - - if ( str ) - { - boost::optional result = fast_cast(*str); - if (result) - { - return *result; - } - else - { - throw config_error(std::string("Failed to parse ") + - (is_attribute ? "attribute" : "child node") + " '" + - name + "'. Expected " + name_trait::name() + - " but got '" + *str + "'"); - } - } - else - { - return default_value; - } -} - -template <> -inline color get(boost::property_tree::ptree const& node, - std::string const& name, - bool is_attribute, - color const& default_value) -{ - boost::optional str; - if (is_attribute) - { - str = node.get_optional( std::string(".") + name ); - } - else - { - str = node.get_optional(name + "."); - } - - if ( str ) - { - try - { - return mapnik::color_factory::from_string((*str).c_str()); - } - catch (...) - { - throw config_error(std::string("Failed to parse ") + - (is_attribute ? "attribute" : "child node") + " '" + - name + "'. Expected " + name_trait::name() + - " but got '" + *str + "'"); - } - } - else - { - return default_value; - } -} - -template -T get(boost::property_tree::ptree const& node, std::string const& name, bool is_attribute) -{ - boost::optional str; - if (is_attribute) - { - str = node.get_optional( std::string(".") + name); - } - else - { - str = node.get_optional(name + "."); - } - - if ( ! str ) - { - throw config_error(std::string("Required ") + - (is_attribute ? "attribute " : "child node ") + - "'" + name + "' is missing"); - } - boost::optional result = fast_cast(*str); - if (result) - { - return *result; - } - else - { - throw config_error(std::string("Failed to parse ") + - (is_attribute ? "attribute" : "child node") + " '" + - name + "'. Expected " + name_trait::name() + - " but got '" + *str + "'"); - } -} - -template -T get_value(boost::property_tree::ptree const& node, std::string const& name) -{ - try - { - /* NOTE: get_child works as long as there is only one child with that name. - If this function is used this used this condition must always be satisfied. - */ - return node.get_child("").get_value(); - } - catch (boost::property_tree::ptree_bad_path) - { - /* If the XML parser did not find any non-empty data element the is no - node. But we don't want to fail here but simply return a - default constructed value of the requested type. - */ - return T(); - } - catch (...) - { - throw config_error(std::string("Failed to parse ") + - name + ". Expected " + name_trait::name() + - " but got '" + node.data() + "'"); - } -} - -template -boost::optional get_optional(boost::property_tree::ptree const& node, - std::string const& name, - bool is_attribute) -{ - boost::optional str; - if (is_attribute) - { - str = node.get_optional( std::string(".") + name); - } - else - { - str = node.get_optional(name + "."); - } - - boost::optional result; - if ( str ) - { - result = fast_cast(*str); - if (!result) - { - throw config_error(std::string("Failed to parse ") + - (is_attribute ? "attribute" : "child node") + " '" + - name + "'. Expected " + name_trait::name() + - " but got '" + *str + "'"); - } - } - - return result; -} - -template <> -inline boost::optional get_optional(boost::property_tree::ptree const& node, - std::string const& name, - bool is_attribute) -{ - if (is_attribute) - { - return node.get_optional( std::string(".") + name); - } - else - { - return node.get_optional(name + "."); - } -} - -template <> -inline boost::optional get_optional(boost::property_tree::ptree const& node, - std::string const& name, - bool is_attribute) -{ - boost::optional str; - if (is_attribute) - { - str = node.get_optional( std::string(".") + name); - } - else - { - str = node.get_optional(name + "."); - } - - boost::optional result; - if ( str ) - { - try - { - result = mapnik::color_factory::from_string((*str).c_str()); - } - catch (...) - { - throw config_error(std::string("Failed to parse ") + - (is_attribute ? "attribute" : "child node") + " '" + - name + "'. Expected " + name_trait::name() + - " but got '" + *str + "'"); - } - } - - return result; -} - -static inline bool has_child(boost::property_tree::ptree const& node, std::string const& name) -{ - boost::optional str = node.get_optional(name); - return str; -} - } // end of namespace mapnik #endif // MAPNIK_PTREE_HELPERS_HPP diff --git a/include/mapnik/xml_tree.hpp b/include/mapnik/xml_tree.hpp index 534228e79..40406382c 100644 --- a/include/mapnik/xml_tree.hpp +++ b/include/mapnik/xml_tree.hpp @@ -23,6 +23,10 @@ #ifndef MAPNIK_XML_TREE_H #define MAPNIK_XML_TREE_H +//mapnik +#include + + //boost #include diff --git a/plugins/input/csv/csv_datasource.cpp b/plugins/input/csv/csv_datasource.cpp index b2848509f..8aa04b667 100644 --- a/plugins/input/csv/csv_datasource.cpp +++ b/plugins/input/csv/csv_datasource.cpp @@ -14,7 +14,8 @@ #include #include #include -#include // mapnik::boolean +#include +#include // stl #include diff --git a/plugins/input/gdal/gdal_datasource.cpp b/plugins/input/gdal/gdal_datasource.cpp index a19842413..1bdf85ce1 100644 --- a/plugins/input/gdal/gdal_datasource.cpp +++ b/plugins/input/gdal/gdal_datasource.cpp @@ -25,7 +25,7 @@ #include "gdal_featureset.hpp" // mapnik -#include +#include #include #include diff --git a/plugins/input/geos/geos_datasource.cpp b/plugins/input/geos/geos_datasource.cpp index 9fcc8c4ab..ddf649ede 100644 --- a/plugins/input/geos/geos_datasource.cpp +++ b/plugins/input/geos/geos_datasource.cpp @@ -30,7 +30,7 @@ #include // mapnik -#include +#include #include // boost diff --git a/plugins/input/kismet/kismet_datasource.cpp b/plugins/input/kismet/kismet_datasource.cpp index 7780b078d..00f604789 100644 --- a/plugins/input/kismet/kismet_datasource.cpp +++ b/plugins/input/kismet/kismet_datasource.cpp @@ -33,7 +33,7 @@ #include // mapnik -#include +#include // boost #include diff --git a/plugins/input/occi/occi_datasource.cpp b/plugins/input/occi/occi_datasource.cpp index b99698d22..e4d3fa4a3 100644 --- a/plugins/input/occi/occi_datasource.cpp +++ b/plugins/input/occi/occi_datasource.cpp @@ -25,7 +25,7 @@ #include "occi_featureset.hpp" // mapnik -#include +#include #include // boost diff --git a/plugins/input/ogr/ogr_datasource.cpp b/plugins/input/ogr/ogr_datasource.cpp index 0e6035a3c..da404cbb9 100644 --- a/plugins/input/ogr/ogr_datasource.cpp +++ b/plugins/input/ogr/ogr_datasource.cpp @@ -33,7 +33,7 @@ #include // mapnik -#include +#include #include // boost diff --git a/plugins/input/postgis/postgis_datasource.cpp b/plugins/input/postgis/postgis_datasource.cpp index 0c5f083e8..26c2b9074 100644 --- a/plugins/input/postgis/postgis_datasource.cpp +++ b/plugins/input/postgis/postgis_datasource.cpp @@ -26,7 +26,7 @@ // mapnik #include -#include +#include #include #include diff --git a/plugins/input/rasterlite/rasterlite_datasource.cpp b/plugins/input/rasterlite/rasterlite_datasource.cpp index e9cd0bf69..ac609f357 100644 --- a/plugins/input/rasterlite/rasterlite_datasource.cpp +++ b/plugins/input/rasterlite/rasterlite_datasource.cpp @@ -29,7 +29,7 @@ #include // mapnik -#include +#include #include using mapnik::datasource; diff --git a/plugins/input/sqlite/sqlite_datasource.cpp b/plugins/input/sqlite/sqlite_datasource.cpp index 02e184d3a..77015ef82 100644 --- a/plugins/input/sqlite/sqlite_datasource.cpp +++ b/plugins/input/sqlite/sqlite_datasource.cpp @@ -26,7 +26,7 @@ #include "sqlite_utils.hpp" // mapnik -#include +#include #include #include #include diff --git a/src/formatting/text.cpp b/src/formatting/text.cpp index 0be312501..a65285200 100644 --- a/src/formatting/text.cpp +++ b/src/formatting/text.cpp @@ -45,7 +45,7 @@ void text_node::to_xml(ptree &xml) const node_ptr text_node::from_xml(xml_node const& xml) { - std::string data = xml.get_text(); + std::string data = xml.text(); if (data.empty()) return node_ptr(); //No text return node_ptr(new text_node(parse_expression(data, "utf8"))); } diff --git a/src/xml_tree.cpp b/src/xml_tree.cpp index 415107da8..2ba5ff709 100644 --- a/src/xml_tree.cpp +++ b/src/xml_tree.cpp @@ -27,6 +27,8 @@ #include #include #include +#include +#include //boost #include @@ -79,6 +81,11 @@ inline boost::optional fast_cast(xml_tree const& tree, std::string const& return mapnik::color_factory::from_string(value); } +template <> +inline boost::optional fast_cast(xml_tree const& tree, std::string const& value) +{ + return parse_expression(value); +} /****************************************************************************/ @@ -112,6 +119,7 @@ DEFINE_NAME_TRAIT( boolean, "boolean") DEFINE_NAME_TRAIT( int, "integer" ) DEFINE_NAME_TRAIT( std::string, "string" ) DEFINE_NAME_TRAIT( color, "color" ) +DEFINE_NAME_TRAIT(expression_ptr, "expression_ptr" ) template struct name_trait< mapnik::enumeration > @@ -376,19 +384,21 @@ std::string xml_node::get_text() const template T xml_node::get_value() const { - boost::optional result = fast_cast(get_text()); + boost::optional result = fast_cast(tree_, get_text()); if (!result) { throw config_error(std::string("Failed to parse value in node '") + name_ + "'. Expected " + name_trait::name() + " but got '" + get_text() + "'"); } - return result; + return *result; } #define compile_get_opt_attr(T) template boost::optional xml_node::get_opt_attr(std::string const&) const +#define compile_get_attr(T) template T xml_node::get_attr(std::string const&) const; template T xml_node::get_attr(std::string const&, T const&) const +#define compile_get_value(T) template T xml_node::get_value() const -//compile_get_opt_attr(boolean); +compile_get_opt_attr(boolean); compile_get_opt_attr(std::string); compile_get_opt_attr(unsigned); compile_get_opt_attr(float); @@ -396,4 +406,23 @@ compile_get_opt_attr(double); compile_get_opt_attr(color); compile_get_opt_attr(gamma_method_e); compile_get_opt_attr(line_rasterizer_e); +compile_get_opt_attr(line_join_e); +compile_get_opt_attr(line_cap_e); +compile_get_opt_attr(text_transform_e); +compile_get_opt_attr(label_placement_e); +compile_get_opt_attr(vertical_alignment_e); +compile_get_opt_attr(horizontal_alignment_e); +compile_get_opt_attr(justify_alignment_e); +compile_get_attr(std::string); +compile_get_attr(filter_mode_e); +compile_get_attr(point_placement_e); +compile_get_attr(marker_placement_e); +compile_get_attr(marker_type_e); +compile_get_attr(pattern_alignment_e); +compile_get_attr(line_rasterizer_e); +compile_get_attr(colorizer_mode); +compile_get_attr(double); +compile_get_value(int); +compile_get_value(double); +compile_get_value(expression_ptr); } //ns mapnik From c47dae692aa7f0e3a6dbff7c3eeeedaab32dde27 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 7 Mar 2012 15:34:59 -0800 Subject: [PATCH 045/238] add no-omit-frame-pointer to debug flags to ensure without question it is not enabled --- SConstruct | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SConstruct b/SConstruct index 28d3cb94f..b860bb58e 100644 --- a/SConstruct +++ b/SConstruct @@ -1411,7 +1411,7 @@ if not preconfigured: pthread = '-pthread' # Common debugging flags. - debug_flags = '-g -DDEBUG -DMAPNIK_DEBUG' + debug_flags = '-g -fno-omit-frame-pointer -DDEBUG -DMAPNIK_DEBUG' ndebug_flags = '-DNDEBUG' From 2a7709a0cf4bada065ca1dd76ef1ec16ce3119d0 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Thu, 8 Mar 2012 01:29:19 +0100 Subject: [PATCH 046/238] Dump xml tree. --- include/mapnik/internal/dump_xml.hpp | 24 ++++++++++++++++-------- include/mapnik/xml_tree.hpp | 4 +++- src/load_map.cpp | 3 ++- src/xml_tree.cpp | 5 +++++ 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/include/mapnik/internal/dump_xml.hpp b/include/mapnik/internal/dump_xml.hpp index 022591f7a..944be73ba 100644 --- a/include/mapnik/internal/dump_xml.hpp +++ b/include/mapnik/internal/dump_xml.hpp @@ -1,10 +1,10 @@ #ifndef DUMP_XML_HPP #define DUMP_XML_HPP -#include +#include /* Debug dump ptree XML representation. */ -void dump_xml(boost::property_tree::ptree const& xml, unsigned level=0) +void dump_xml(xml_node const& xml, unsigned level=0) { std::string indent; int i; @@ -12,15 +12,23 @@ void dump_xml(boost::property_tree::ptree const& xml, unsigned level=0) { indent += " "; } - if (xml.data().length()) std::cout << indent << "data: '" << xml.data() << "'\n"; - boost::property_tree::ptree::const_iterator itr = xml.begin(); - boost::property_tree::ptree::const_iterator end = xml.end(); + xml_node::attribute_map const& attr = xml.get_attributes(); + std::cout << indent <<"[" << xml.name(); + xml_node::attribute_map::const_iterator aitr = attr.begin(); + xml_node::attribute_map::const_iterator aend = attr.end(); + for (;aitr!=aend; aitr++) + { + std::cout << " (" << aitr->first << ", " << aitr->second.value << ", " << aitr->second.processed << ")"; + } + std::cout << "]" << "\n"; + if (xml.is_text()) std::cout << indent << "text: '" << xml.text() << "'\n"; + xml_node::const_iterator itr = xml.begin(); + xml_node::const_iterator end = xml.end(); for (; itr!=end; itr++) { - std::cout << indent <<"[" << itr->first << "]" << "\n"; - dump_xml(itr->second, level+1); - std::cout << indent << "[/" << itr->first << "]" << "\n"; + dump_xml(*itr, level+1); } + std::cout << indent << "[/" << xml.name() << "]" << "\n"; } diff --git a/include/mapnik/xml_tree.hpp b/include/mapnik/xml_tree.hpp index 40406382c..b1b750b97 100644 --- a/include/mapnik/xml_tree.hpp +++ b/include/mapnik/xml_tree.hpp @@ -84,6 +84,7 @@ class xml_node { public: typedef std::list::const_iterator const_iterator; + typedef std::map attribute_map; xml_node(xml_tree &tree, std::string name, unsigned line=0, bool text_node = false); std::string name() const; @@ -93,6 +94,7 @@ public: xml_node &add_child(std::string const& name, unsigned line=0, bool text_node = false); void add_attribute(std::string const& name, std::string const& value); + attribute_map const& get_attributes() const; void set_processed(bool processed) const; @@ -120,7 +122,7 @@ private: xml_tree &tree_; std::string name_; std::list children_; - std::map attributes_; + attribute_map attributes_; bool text_node_; unsigned line_; mutable bool processed_; diff --git a/src/load_map.cpp b/src/load_map.cpp index 6120d22c7..6c6edc281 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -141,7 +141,7 @@ private: }; - +#include void load_map(Map & map, std::string const& filename, bool strict) { xml_tree tree; @@ -161,6 +161,7 @@ void load_map(Map & map, std::string const& filename, bool strict) #endif map_parser parser(strict, filename); parser.parse_map(map, tree.root()); + dump_xml(tree.root()); } void load_map_string(Map & map, std::string const& str, bool strict, std::string const& base_path) diff --git a/src/xml_tree.cpp b/src/xml_tree.cpp index 2ba5ff709..a7714e057 100644 --- a/src/xml_tree.cpp +++ b/src/xml_tree.cpp @@ -278,6 +278,11 @@ void xml_node::add_attribute(std::string const& name, std::string const& value) attributes_.insert(std::make_pair(name,xml_attribute(value))); } +xml_node::attribute_map const& xml_node::get_attributes() const +{ + return attributes_; +} + void xml_node::set_processed(bool processed) const { processed_ = processed; From adc8f9df1b9a566ff06eac04934e5b20d31d01fa Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Thu, 8 Mar 2012 13:00:08 +0100 Subject: [PATCH 047/238] Fix PointSymbolizer problem. --- src/load_map.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/load_map.cpp b/src/load_map.cpp index 6c6edc281..131a4d6c0 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -866,9 +866,6 @@ void map_parser::parse_point_symbolizer(rule & rule, xml_node const & sym) tr.store_to(&matrix[0]); symbol.set_transform(matrix); } - - parse_metawriter_in_symbolizer(symbol, sym); - rule.append(symbol); } catch (image_reader_exception const & ex ) { From f1aee039124ce301789d588a1448f45a66b49da1 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Thu, 8 Mar 2012 08:37:58 -0800 Subject: [PATCH 048/238] forward declare marker so marker_cache api access is cleaner --- include/mapnik/agg_renderer.hpp | 4 ---- include/mapnik/marker_cache.hpp | 9 +-------- src/agg/agg_renderer.cpp | 3 ++- src/agg/process_line_pattern_symbolizer.cpp | 1 + src/agg/process_markers_symbolizer.cpp | 1 + src/agg/process_point_symbolizer.cpp | 1 + src/agg/process_polygon_pattern_symbolizer.cpp | 1 + src/cairo_renderer.cpp | 3 ++- src/grid/grid_renderer.cpp | 3 ++- src/grid/process_markers_symbolizer.cpp | 1 + src/grid/process_point_symbolizer.cpp | 2 +- src/image_util.cpp | 1 - src/marker_cache.cpp | 13 ++++++++----- 13 files changed, 21 insertions(+), 22 deletions(-) diff --git a/include/mapnik/agg_renderer.hpp b/include/mapnik/agg_renderer.hpp index 4522e3aeb..11550fc88 100644 --- a/include/mapnik/agg_renderer.hpp +++ b/include/mapnik/agg_renderer.hpp @@ -29,10 +29,6 @@ #include #include #include -//#include - -// agg -//#include "agg_trans_affine.h" // boost #include diff --git a/include/mapnik/marker_cache.hpp b/include/mapnik/marker_cache.hpp index 5b56770b0..8f5fb8a0d 100644 --- a/include/mapnik/marker_cache.hpp +++ b/include/mapnik/marker_cache.hpp @@ -25,14 +25,7 @@ // mapnik #include -#include #include -#include -#include -#include - -// agg -#include "agg_path_storage.h" // boost #include @@ -43,7 +36,7 @@ namespace mapnik { -using namespace mapnik::svg; +class marker; typedef boost::shared_ptr marker_ptr; diff --git a/src/agg/agg_renderer.cpp b/src/agg/agg_renderer.cpp index ac618ee56..079274cd3 100644 --- a/src/agg/agg_renderer.cpp +++ b/src/agg/agg_renderer.cpp @@ -24,6 +24,7 @@ // mapnik #include #include +#include #include #include #include @@ -246,7 +247,7 @@ void agg_renderer::render_marker(pixel_position const& pos, marker const& mar mtx *= agg::trans_affine_scaling(scale_factor_); // render the marker at the center of the marker box mtx.translate(pos.x+0.5 * marker.width(), pos.y+0.5 * marker.height()); - + using namespace mapnik::svg; vertex_stl_adapter stl_storage((*marker.get_vector_data())->source()); svg_path_adapter svg_path(stl_storage); svg_renderer #include #include +#include #include #include diff --git a/src/agg/process_markers_symbolizer.cpp b/src/agg/process_markers_symbolizer.cpp index c1e75c3f5..6248f004f 100644 --- a/src/agg/process_markers_symbolizer.cpp +++ b/src/agg/process_markers_symbolizer.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include diff --git a/src/agg/process_point_symbolizer.cpp b/src/agg/process_point_symbolizer.cpp index 967ebd082..d29f6d15c 100644 --- a/src/agg/process_point_symbolizer.cpp +++ b/src/agg/process_point_symbolizer.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include diff --git a/src/agg/process_polygon_pattern_symbolizer.cpp b/src/agg/process_polygon_pattern_symbolizer.cpp index 5867b3fae..0f14c4dd0 100644 --- a/src/agg/process_polygon_pattern_symbolizer.cpp +++ b/src/agg/process_polygon_pattern_symbolizer.cpp @@ -24,6 +24,7 @@ // mapnik #include #include +#include #include #include diff --git a/src/cairo_renderer.cpp b/src/cairo_renderer.cpp index 85cc7592d..d0564d587 100644 --- a/src/cairo_renderer.cpp +++ b/src/cairo_renderer.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -903,7 +904,7 @@ void cairo_renderer_base::start_map_processing(Map const& map) typedef coord_transform2 path_type; mapnik::path_ptr vmarker = *marker.get_vector_data(); - + using namespace mapnik::svg; agg::pod_bvector const & attributes_ = vmarker->attributes(); for(unsigned i = 0; i < attributes_.size(); ++i) { diff --git a/src/grid/grid_renderer.cpp b/src/grid/grid_renderer.cpp index 754356a46..1552a6fd1 100644 --- a/src/grid/grid_renderer.cpp +++ b/src/grid/grid_renderer.cpp @@ -29,6 +29,7 @@ #include +#include #include #include #include @@ -144,7 +145,7 @@ void grid_renderer::render_marker(mapnik::feature_ptr const& feature, unsigne mtx *= agg::trans_affine_scaling(scale_factor_*(1.0/step)); // render the marker at the center of the marker box mtx.translate(pos.x+0.5 * marker.width(), pos.y+0.5 * marker.height()); - + using namespace mapnik::svg; vertex_stl_adapter stl_storage((*marker.get_vector_data())->source()); svg_path_adapter svg_path(stl_storage); svg_renderer #include #include +#include #include #include diff --git a/src/grid/process_point_symbolizer.cpp b/src/grid/process_point_symbolizer.cpp index 2db4dd5c2..2c3e2b634 100644 --- a/src/grid/process_point_symbolizer.cpp +++ b/src/grid/process_point_symbolizer.cpp @@ -29,7 +29,7 @@ #include #include #include - +#include #include // stl diff --git a/src/image_util.cpp b/src/image_util.cpp index c3cee7610..5b3b46454 100644 --- a/src/image_util.cpp +++ b/src/image_util.cpp @@ -57,7 +57,6 @@ extern "C" #include // agg -//#include "agg_conv_transform.h" #include "agg_image_accessors.h" #include "agg_pixfmt_rgba.h" #include "agg_rasterizer_scanline_aa.h" diff --git a/src/marker_cache.cpp b/src/marker_cache.cpp index f5139699a..41117bba6 100644 --- a/src/marker_cache.cpp +++ b/src/marker_cache.cpp @@ -23,11 +23,13 @@ //$Id$ // mapnik +#include #include #include #include -#include #include +#include +#include #include #include @@ -35,6 +37,7 @@ #include #include #include +#include namespace mapnik { @@ -72,7 +75,7 @@ boost::optional marker_cache::find(std::string const& uri, bool upda using namespace mapnik::svg; try { - path_ptr marker_path(new svg_storage_type); + path_ptr marker_path(boost::make_shared()); vertex_stl_adapter stl_storage(marker_path->source()); svg_path_adapter svg_path(stl_storage); svg_converter_type svg(svg_path, marker_path->attributes()); @@ -84,7 +87,7 @@ boost::optional marker_cache::find(std::string const& uri, bool upda svg.bounding_rect(&lox, &loy, &hix, &hiy); marker_path->set_bounding_box(lox,loy,hix,hiy); - marker_ptr mark(new marker(marker_path)); + marker_ptr mark(boost::make_shared(marker_path)); result.reset(mark); if (update_cache) { @@ -107,9 +110,9 @@ boost::optional marker_cache::find(std::string const& uri, bool upda unsigned width = reader->width(); unsigned height = reader->height(); BOOST_ASSERT(width > 0 && height > 0); - mapnik::image_ptr image(new mapnik::image_data_32(width,height)); + mapnik::image_ptr image(boost::make_shared(width,height)); reader->read(0,0,*image); - marker_ptr mark(new marker(image)); + marker_ptr mark(boost::make_shared(image)); result.reset(mark); if (update_cache) { From 3add1f984c9646bf08e42cb0e20323ac9b1a5994 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Thu, 8 Mar 2012 08:40:12 -0800 Subject: [PATCH 049/238] add Geos plugin to exported module scope --- bindings/python/mapnik/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bindings/python/mapnik/__init__.py b/bindings/python/mapnik/__init__.py index 9fa035211..7d7a93d2c 100644 --- a/bindings/python/mapnik/__init__.py +++ b/bindings/python/mapnik/__init__.py @@ -682,6 +682,7 @@ __all__ = [ 'SQLite', 'Osm', 'Kismet', + 'Geos', # version and environment 'mapnik_version_string', 'mapnik_version', From 8b0b9ed5fe1f4f5fed31226563a2b15553f728c6 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Thu, 8 Mar 2012 18:29:46 +0100 Subject: [PATCH 050/238] Handle different image sizes correctly. --- tests/visual_tests/compare.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/visual_tests/compare.py b/tests/visual_tests/compare.py index 21a627d99..414f49979 100644 --- a/tests/visual_tests/compare.py +++ b/tests/visual_tests/compare.py @@ -1,4 +1,4 @@ -import math, operator +#import math, operator import Image import sys @@ -27,6 +27,10 @@ def compare(fn1, fn2): return -1 diff = 0 pixels = im1.size[0] * im1.size[1] + delta_pixels = im2.size[0] * im2.size[1] - pixels + if delta_pixels != 0: + errors.append((fn1, delta_pixels)) + return delta_pixels im1 = im1.getdata() im2 = im2.getdata() for i in range(3, pixels - 1, 3): From cd8cfc65273f4622f9150e4ced8bd0a61eae2946 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Thu, 8 Mar 2012 18:51:23 +0100 Subject: [PATCH 051/238] Improve error messages. --- include/mapnik/config_error.hpp | 20 +++++------ include/mapnik/raster_colorizer.hpp | 1 - include/mapnik/value.hpp | 1 - include/mapnik/xml_tree.hpp | 3 ++ src/agg/agg_renderer.cpp | 1 - src/build.py | 1 + src/cairo_renderer.cpp | 1 - src/config_error.cpp | 44 +++++++++++++++++++++++ src/formatting/registry.cpp | 3 +- src/grid/grid_renderer.cpp | 1 - src/libxml2_loader.cpp | 15 ++------ src/load_map.cpp | 14 ++++---- src/metawriter_factory.cpp | 3 +- src/processed_text.cpp | 26 +++++++++++++- src/text_placements/registry.cpp | 3 +- src/text_properties.cpp | 7 ++-- src/xml_tree.cpp | 10 ++++++ tests/visual_tests/shieldsymbolizer-1.xml | 5 ++- 18 files changed, 115 insertions(+), 44 deletions(-) create mode 100644 src/config_error.cpp diff --git a/include/mapnik/config_error.hpp b/include/mapnik/config_error.hpp index b25ae07f1..00ec3be8a 100644 --- a/include/mapnik/config_error.hpp +++ b/include/mapnik/config_error.hpp @@ -28,29 +28,25 @@ namespace mapnik { +class xml_node; class config_error : public std::exception { public: config_error() {} - config_error( const std::string & what ) : - what_( what ) - { - } + config_error(std::string const& what, xml_node const* node = 0, std::string const& filename=""); + config_error(unsigned line_number, std::string const& filename, std::string const& what); virtual ~config_error() throw() {} - virtual const char * what() const throw() - { - return what_.c_str(); - } + virtual const char * what() const throw(); - void append_context(const std::string & ctx) const - { - what_ += " " + ctx; - } + void append_context(const std::string & ctx, xml_node const* node = 0, std::string const& filename="") const; protected: mutable std::string what_; + mutable unsigned line_number_; + mutable std::string file_; + mutable std::string node_name_; }; } diff --git a/include/mapnik/raster_colorizer.hpp b/include/mapnik/raster_colorizer.hpp index 74fcf4853..8903d60f2 100644 --- a/include/mapnik/raster_colorizer.hpp +++ b/include/mapnik/raster_colorizer.hpp @@ -39,7 +39,6 @@ // mapnik #include -#include #include #include #include diff --git a/include/mapnik/value.hpp b/include/mapnik/value.hpp index d657d974c..a3cef23b4 100644 --- a/include/mapnik/value.hpp +++ b/include/mapnik/value.hpp @@ -26,7 +26,6 @@ // mapnik #include #include -#include // boost #include diff --git a/include/mapnik/xml_tree.hpp b/include/mapnik/xml_tree.hpp index b1b750b97..bb01c24da 100644 --- a/include/mapnik/xml_tree.hpp +++ b/include/mapnik/xml_tree.hpp @@ -96,8 +96,11 @@ public: void add_attribute(std::string const& name, std::string const& value); attribute_map const& get_attributes() const; + bool processed() const; void set_processed(bool processed) const; + unsigned line() const; + const_iterator begin() const; const_iterator end() const; diff --git a/src/agg/agg_renderer.cpp b/src/agg/agg_renderer.cpp index ac618ee56..f355b3c84 100644 --- a/src/agg/agg_renderer.cpp +++ b/src/agg/agg_renderer.cpp @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include diff --git a/src/build.py b/src/build.py index b19789d3e..9034a6de2 100644 --- a/src/build.py +++ b/src/build.py @@ -179,6 +179,7 @@ source = Split( text_placements/simple.cpp text_properties.cpp xml_tree.cpp + config_error.cpp """ ) diff --git a/src/cairo_renderer.cpp b/src/cairo_renderer.cpp index 85cc7592d..19909bf34 100644 --- a/src/cairo_renderer.cpp +++ b/src/cairo_renderer.cpp @@ -29,7 +29,6 @@ #include #include #include -#include #include #include #include diff --git a/src/config_error.cpp b/src/config_error.cpp new file mode 100644 index 000000000..6837f3cbd --- /dev/null +++ b/src/config_error.cpp @@ -0,0 +1,44 @@ +#include +#include + +namespace mapnik +{ +config_error::config_error(std::string const& what, xml_node const* node, std::string const& filename) + : what_( what ), file_(filename) +{ + if (node) + { + node_name_ = node->name(); + line_number_ = node->line(); + } +} + + +config_error::config_error(unsigned line_number, std::string const& filename, std::string const& what) + : what_( what ), line_number_(line_number), file_(filename) +{ + +} + + char const* config_error::what() const throw() +{ + std::stringstream s; + s << file_; + if (line_number_ > 0) s << " line " << line_number_; + if (!node_name_.empty()) s << " in node "<< node_name_; + if (line_number_ > 0 || !file_.empty()) s << ": "; + s << what_; + return s.str().c_str(); +} + +void config_error::append_context(const std::string & ctx, xml_node const* node, std::string const& filename) const +{ + what_ += " " + ctx; + if (node) + { + if (!line_number_) line_number_ = node->line(); + if (node_name_.empty()) node_name_ = node->name(); + if (file_.empty()) file_ = filename; + } +} +} diff --git a/src/formatting/registry.cpp b/src/formatting/registry.cpp index 8cfb0c6a4..00b031317 100644 --- a/src/formatting/registry.cpp +++ b/src/formatting/registry.cpp @@ -25,6 +25,7 @@ #include #include #include +#include namespace mapnik { @@ -50,7 +51,7 @@ void registry::register_name(std::string name, from_xml_function_ptr ptr, bool o node_ptr registry::from_xml(xml_node const& xml) { std::map::const_iterator itr = map_.find(xml.name()); - if (itr == map_.end()) throw config_error("Unknown element '" + xml.name() + "'"); + if (itr == map_.end()) throw config_error("Unknown element '" + xml.name() + "'", &xml); return itr->second(xml); } } //ns formatting diff --git a/src/grid/grid_renderer.cpp b/src/grid/grid_renderer.cpp index 754356a46..9cd39dd50 100644 --- a/src/grid/grid_renderer.cpp +++ b/src/grid/grid_renderer.cpp @@ -31,7 +31,6 @@ #include #include -#include #include #include #include diff --git a/src/libxml2_loader.cpp b/src/libxml2_loader.cpp index f67f0e0db..ac5ac6499 100644 --- a/src/libxml2_loader.cpp +++ b/src/libxml2_loader.cpp @@ -77,8 +77,7 @@ public: boost::filesystem::path path(filename); if (!boost::filesystem::exists(path)) { - throw config_error(string("Could not load map file '") + - filename + "': File does not exist"); + throw config_error(string("Could not load map file: File does not exist"), 0, filename); } xmlDocPtr doc = xmlCtxtReadFile(ctx_, filename.c_str(), encoding_, options_); @@ -93,15 +92,7 @@ public: os << ": " << std::endl << error->message; // remove CR std::string msg = os.str().substr(0, os.str().size() - 1); - config_error ex(msg); - - os.str(""); - os << "(encountered in file '" << error->file << "' at line " - << error->line << ")"; - - ex.append_context(os.str()); - - throw ex; + throw config_error(error->line, error->file, msg); } } @@ -148,7 +139,7 @@ public: { os << ": " << std::endl << error->message; } - throw config_error(os.str()); + throw config_error(error->line, error->file, os.str()); } int iXIncludeReturn = xmlXIncludeProcessFlags(doc, options_); diff --git a/src/load_map.cpp b/src/load_map.cpp index 131a4d6c0..731ee6bde 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -195,7 +195,7 @@ expression_ptr map_parser::parse_expr(std::string const& str) expression_ptr expr(boost::make_shared(true)); if (!expression_factory::parse_from_string(expr,str,expr_grammar_)) { - throw mapnik::config_error( "Failed to parse expression: '" + str + "'" ); + throw mapnik::config_error( "Failed to parse expression '" + str + "'" ); } return expr; @@ -354,7 +354,7 @@ void map_parser::parse_map(Map & map, xml_node const& pt, std::string const& bas } catch (const config_error & ex) { - ex.append_context("(in node Map)"); + ex.append_context("", &map_node, filename_); throw; } @@ -458,7 +458,7 @@ void map_parser::parse_map_include(Map & map, xml_node const& include) } } } catch (const config_error & ex) { - ex.append_context(std::string("in map '") + filename_ + "'"); + ex.append_context("", &include, filename_); throw; } @@ -489,7 +489,7 @@ void map_parser::parse_style(Map & map, xml_node const& sty) map.insert_style(name, style); } catch (const config_error & ex) { - ex.append_context(std::string("in style '") + name + "'"); + ex.append_context(std::string("in style '") + name + "'", &sty, filename_); throw; } } @@ -504,7 +504,7 @@ void map_parser::parse_metawriter(Map & map, xml_node const& pt) writer = metawriter_create(pt); map.insert_metawriter(name, writer); } catch (const config_error & ex) { - ex.append_context(std::string("in meta writer '") + name + "'"); + ex.append_context(std::string("in meta writer '") + name + "'", &pt, filename_); } } @@ -533,7 +533,7 @@ void map_parser::parse_fontset(Map & map, xml_node const& fset) // when it's parsed fontsets_.insert(pair(name, fontset)); } catch (const config_error & ex) { - ex.append_context(std::string("in FontSet '") + name + "'"); + ex.append_context(std::string("in FontSet '") + name + "'", &fset, filename_); throw; } } @@ -551,7 +551,7 @@ void map_parser::parse_font(font_set &fset, xml_node const& f) } else { - throw config_error(std::string("Must have 'face-name' set")); + throw config_error("Must have 'face-name' set", &f, filename_); } } diff --git a/src/metawriter_factory.cpp b/src/metawriter_factory.cpp index 2d05a70aa..e0b5b721b 100644 --- a/src/metawriter_factory.cpp +++ b/src/metawriter_factory.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include @@ -60,7 +61,7 @@ metawriter_create(xml_node const& pt) metawriter_inmem_ptr inmem = metawriter_inmem_ptr(new metawriter_inmem(properties)); writer = inmem; } else { - throw config_error(string("Unknown type '") + type + "'"); + throw config_error(string("Unknown type '") + type + "'", &pt); } return writer; diff --git a/src/processed_text.cpp b/src/processed_text.cpp index 3a6949487..921e528c6 100644 --- a/src/processed_text.cpp +++ b/src/processed_text.cpp @@ -1,4 +1,28 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2012 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 + * + *****************************************************************************/ + #include +#include + namespace mapnik { @@ -43,7 +67,7 @@ string_info &processed_text::get_string_info() { if (!p.fontset.get_name().empty()) { - throw config_error("Unable to find specified font set '" + p.face_name + "'"); + throw config_error("Unable to find specified font set '" + p.fontset.get_name() + "'"); } else if (!p.face_name.empty()) { throw config_error("Unable to find specified font face '" + p.face_name + "'"); } else { diff --git a/src/text_placements/registry.cpp b/src/text_placements/registry.cpp index 23950a751..af6751665 100644 --- a/src/text_placements/registry.cpp +++ b/src/text_placements/registry.cpp @@ -23,6 +23,7 @@ #include #include #include +#include namespace mapnik { @@ -47,7 +48,7 @@ void registry::register_name(std::string name, from_xml_function_ptr ptr, bool o text_placements_ptr registry::from_xml(std::string name, xml_node const& xml, fontset_map const& fontsets) { std::map::const_iterator itr = map_.find(name); - if (itr == map_.end()) throw config_error("Unknown placement-type '" + name + "'"); + if (itr == map_.end()) throw config_error("Unknown placement-type '" + name + "'", &xml); return itr->second(xml, fontsets); } } //ns formatting diff --git a/src/text_properties.cpp b/src/text_properties.cpp index 09f3a85be..0eb106ec0 100644 --- a/src/text_properties.cpp +++ b/src/text_properties.cpp @@ -26,6 +26,7 @@ #include #include #include +#include namespace mapnik { @@ -266,16 +267,16 @@ void char_properties::from_xml(xml_node const& sym, fontset_map const& fontsets) fontset = itr->second; } else { - throw config_error("Unable to find any fontset named '" + *fontset_name_ + "'"); + throw config_error("Unable to find any fontset named '" + *fontset_name_ + "'", &sym); } } if (!face_name.empty() && !fontset.get_name().empty()) { - throw config_error(std::string("Can't have both face-name and fontset-name")); + throw config_error("Can't have both face-name and fontset-name", &sym); } if (face_name.empty() && fontset.get_name().empty()) { - throw config_error(std::string("Must have face-name or fontset-name")); + throw config_error("Must have face-name or fontset-name", &sym); } } diff --git a/src/xml_tree.cpp b/src/xml_tree.cpp index a7714e057..ce61a8e12 100644 --- a/src/xml_tree.cpp +++ b/src/xml_tree.cpp @@ -288,6 +288,11 @@ void xml_node::set_processed(bool processed) const processed_ = processed; } +bool xml_node::processed() const +{ + return processed_; +} + xml_node::const_iterator xml_node::begin() const { return children_.begin(); @@ -399,6 +404,11 @@ T xml_node::get_value() const return *result; } +unsigned xml_node::line() const +{ + return line_; +} + #define compile_get_opt_attr(T) template boost::optional xml_node::get_opt_attr(std::string const&) const #define compile_get_attr(T) template T xml_node::get_attr(std::string const&) const; template T xml_node::get_attr(std::string const&, T const&) const #define compile_get_value(T) template T xml_node::get_value() const diff --git a/tests/visual_tests/shieldsymbolizer-1.xml b/tests/visual_tests/shieldsymbolizer-1.xml index 6f780d888..010d93171 100644 --- a/tests/visual_tests/shieldsymbolizer-1.xml +++ b/tests/visual_tests/shieldsymbolizer-1.xml @@ -1,6 +1,6 @@ - + My Style @@ -9,6 +9,9 @@ points.shp + From 2f10469c8d68ffb8a2c16634d0c7ef14136e83f1 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Tue, 13 Mar 2012 07:54:32 -0700 Subject: [PATCH 090/238] apply mapnik_format.el formatting --- include/mapnik/agg_renderer.hpp | 2 +- include/mapnik/color_factory.hpp | 2 +- include/mapnik/expression_grammar.hpp | 4 ++-- include/mapnik/feature.hpp | 24 ++++++++++----------- include/mapnik/font_engine_freetype.hpp | 2 +- include/mapnik/formatting/registry.hpp | 2 +- include/mapnik/geometry.hpp | 2 +- include/mapnik/hextree.hpp | 4 ++-- include/mapnik/internal/dump_xml.hpp | 2 +- include/mapnik/placement_finder.hpp | 12 +++++------ include/mapnik/text_placements/registry.hpp | 2 +- include/mapnik/util/conversions.hpp | 18 ++++++++-------- include/mapnik/vertex_vector.hpp | 8 +++---- 13 files changed, 42 insertions(+), 42 deletions(-) diff --git a/include/mapnik/agg_renderer.hpp b/include/mapnik/agg_renderer.hpp index 592105ef1..d7e09049a 100644 --- a/include/mapnik/agg_renderer.hpp +++ b/include/mapnik/agg_renderer.hpp @@ -66,7 +66,7 @@ public: void start_layer_processing(layer const& lay, box2d const& query_extent); void end_layer_processing(layer const& lay); void render_marker(pixel_position const& pos, marker const& marker, agg::trans_affine const& tr, double opacity); - + void process(point_symbolizer const& sym, mapnik::feature_ptr const& feature, proj_transform const& prj_trans); diff --git a/include/mapnik/color_factory.hpp b/include/mapnik/color_factory.hpp index 93f368a48..418c244a0 100644 --- a/include/mapnik/color_factory.hpp +++ b/include/mapnik/color_factory.hpp @@ -41,7 +41,7 @@ public: static void init_from_string(color & c, std::string const& css_color); static bool parse_from_string(color & c, std::string const& css_color, - mapnik::css_color_grammar const& g); + mapnik::css_color_grammar const& g); static color from_string(std::string const& css_color); }; diff --git a/include/mapnik/expression_grammar.hpp b/include/mapnik/expression_grammar.hpp index b5d478c5e..702e18a9b 100644 --- a/include/mapnik/expression_grammar.hpp +++ b/include/mapnik/expression_grammar.hpp @@ -229,7 +229,7 @@ struct expression_grammar : qi::grammar ("\\r", '\r')("\\t", '\t')("\\v", '\v')("\\\\", '\\') ("\\\'", '\'')("\\\"", '\"') ; - + #if BOOST_VERSION > 104500 quote_char %= char_('\'') | char_('"'); ustring %= omit[quote_char[_a = _1]] @@ -237,7 +237,7 @@ struct expression_grammar : qi::grammar >> lit(_a); attr %= '[' >> no_skip[+~char_(']')] >> ']'; #else - ustring %= lit('\'') + ustring %= lit('\'') >> *(unesc_char | "\\x" >> hex | (char_ - lit('\''))) >> lit('\''); attr %= '[' >> lexeme[+(char_ - ']')] >> ']'; diff --git a/include/mapnik/feature.hpp b/include/mapnik/feature.hpp index 9b636824d..8a8d75bd5 100644 --- a/include/mapnik/feature.hpp +++ b/include/mapnik/feature.hpp @@ -99,11 +99,11 @@ public: feature_impl(context_ptr const& ctx, int id) : id_(id), - ctx_(ctx), - data_(ctx_->mapping_.size()), - geom_cont_(), - raster_() - {} + ctx_(ctx), + data_(ctx_->mapping_.size()), + geom_cont_(), + raster_() + {} inline int id() const { return id_;} @@ -163,25 +163,25 @@ public: { context_type::map_type::const_iterator itr = ctx_->mapping_.find(key); if (itr != ctx_->mapping_.end()) - return get(itr->second); - else - throw std::out_of_range(std::string("Key does not exist: '") + key + "'"); + return get(itr->second); + else + throw std::out_of_range(std::string("Key does not exist: '") + key + "'"); } - + value_type const& get(std::size_t index) const { if (index < data_.size()) return data_[index]; throw std::out_of_range("Index out of range"); } - + boost::optional get_optional(std::size_t index) const { if (index < data_.size()) return boost::optional(data_[index]); return boost::optional(); } - + std::size_t size() const { return data_.size(); @@ -273,7 +273,7 @@ public: if (index < data_.size()) { ss << " " << itr->first << ":" << data_[itr->second] << std::endl; - } + } } ss << ")" << std::endl; return ss.str(); diff --git a/include/mapnik/font_engine_freetype.hpp b/include/mapnik/font_engine_freetype.hpp index ab8cad809..ffb0950e5 100644 --- a/include/mapnik/font_engine_freetype.hpp +++ b/include/mapnik/font_engine_freetype.hpp @@ -153,7 +153,7 @@ class MAPNIK_DECL font_face_set : private boost::noncopyable public: font_face_set(void) : faces_(), - dimension_cache_() {} + dimension_cache_() {} void add(face_ptr face) { diff --git a/include/mapnik/formatting/registry.hpp b/include/mapnik/formatting/registry.hpp index 7c4faa4d7..1c44e68dd 100644 --- a/include/mapnik/formatting/registry.hpp +++ b/include/mapnik/formatting/registry.hpp @@ -41,7 +41,7 @@ namespace formatting typedef node_ptr (*from_xml_function_ptr)(xml_node const& xml); class registry : public singleton, - private boost::noncopyable + private boost::noncopyable { public: registry(); diff --git a/include/mapnik/geometry.hpp b/include/mapnik/geometry.hpp index 16eb3c0cc..691bf5404 100644 --- a/include/mapnik/geometry.hpp +++ b/include/mapnik/geometry.hpp @@ -390,7 +390,7 @@ public: } return false; } - + }; typedef geometry geometry_type; diff --git a/include/mapnik/hextree.hpp b/include/mapnik/hextree.hpp index 36ab40857..50102a812 100644 --- a/include/mapnik/hextree.hpp +++ b/include/mapnik/hextree.hpp @@ -160,7 +160,7 @@ public: ~hextree() {} - + void setMaxColors(unsigned max_colors) { max_colors_ = max_colors; @@ -335,7 +335,7 @@ public: sorted_pal_.reserve(colors_); create_palette_rek(sorted_pal_, root_.get()); - + // sort palette for binary searching in quantization #if BOOST_VERSION >= 104600 boost::sort(sorted_pal_, rgba::mean_sort_cmp()); diff --git a/include/mapnik/internal/dump_xml.hpp b/include/mapnik/internal/dump_xml.hpp index 391b89d15..7bdf78f66 100644 --- a/include/mapnik/internal/dump_xml.hpp +++ b/include/mapnik/internal/dump_xml.hpp @@ -3,7 +3,7 @@ #include /* Debug dump ptree XML representation. -*/ + */ void dump_xml(xml_node const& xml, unsigned level=0) { std::string indent; diff --git a/include/mapnik/placement_finder.hpp b/include/mapnik/placement_finder.hpp index 662078238..1ee636349 100644 --- a/include/mapnik/placement_finder.hpp +++ b/include/mapnik/placement_finder.hpp @@ -124,16 +124,16 @@ private: text_symbolizer_properties const& p; text_placement_info const& pi; /** Length of the longest line after linebreaks. - * Before find_line_breaks() this is the total length of the string. - */ + * Before find_line_breaks() this is the total length of the string. + */ double string_width_; /** Height of the string after linebreaks. - * Before find_line_breaks() this is the total length of the string. - */ + * Before find_line_breaks() this is the total length of the string. + */ double string_height_; /** Height of the tallest font in the first line not including line spacing. - * Used to determine the correct offset for the first line. - */ + * Used to determine the correct offset for the first line. + */ double first_line_space_; vertical_alignment_e valign_; horizontal_alignment_e halign_; diff --git a/include/mapnik/text_placements/registry.hpp b/include/mapnik/text_placements/registry.hpp index 35fd59f46..f3bd123bf 100644 --- a/include/mapnik/text_placements/registry.hpp +++ b/include/mapnik/text_placements/registry.hpp @@ -42,7 +42,7 @@ typedef text_placements_ptr (*from_xml_function_ptr)( xml_node const& xml, fontset_map const & fontsets); class registry : public singleton, - private boost::noncopyable + private boost::noncopyable { public: registry(); diff --git a/include/mapnik/util/conversions.hpp b/include/mapnik/util/conversions.hpp index 1b38dee3d..c4d4a321b 100644 --- a/include/mapnik/util/conversions.hpp +++ b/include/mapnik/util/conversions.hpp @@ -30,16 +30,16 @@ namespace mapnik { namespace conversions { -bool string2int(const char * value, int & result); -bool string2int(std::string const& value, int & result); - -bool string2double(std::string const& value, double & result); -bool string2double(const char * value, double & result); - -bool string2float(std::string const& value, float & result); -bool string2float(const char * value, float & result); + bool string2int(const char * value, int & result); + bool string2int(std::string const& value, int & result); -} + bool string2double(std::string const& value, double & result); + bool string2double(const char * value, double & result); + + bool string2float(std::string const& value, float & result); + bool string2float(const char * value, float & result); + + } } #endif // MAPNIK_CONVERSIONS_UTIL_HPP diff --git a/include/mapnik/vertex_vector.hpp b/include/mapnik/vertex_vector.hpp index e326aa284..85bcd8857 100644 --- a/include/mapnik/vertex_vector.hpp +++ b/include/mapnik/vertex_vector.hpp @@ -55,16 +55,16 @@ public: // required for iterators support typedef boost::tuple value_type; typedef std::size_t size_type; - + private: unsigned num_blocks_; unsigned max_blocks_; coord_type** vertices_; unsigned char** commands_; size_type pos_; - + public: - + vertex_vector() : num_blocks_(0), max_blocks_(0), @@ -114,7 +114,7 @@ public: *y = (*vertex); return commands_[block] [pos & block_mask]; } - + private: void allocate_block(unsigned block) { From 93995d7c4b3330c35f13099b8a1a190e103a1248 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Tue, 13 Mar 2012 07:56:11 -0700 Subject: [PATCH 091/238] apply mapnik_format.el formatting --- src/agg/process_line_symbolizer.cpp | 4 +- src/agg/process_markers_symbolizer.cpp | 2 +- .../process_polygon_pattern_symbolizer.cpp | 2 +- src/agg/process_polygon_symbolizer.cpp | 2 +- src/cairo_renderer.cpp | 24 +-- src/color.cpp | 2 +- src/config_error.cpp | 10 +- src/conversions.cpp | 16 +- src/expression.cpp | 4 +- src/feature_style_processor.cpp | 4 +- src/formatting/expression.cpp | 16 +- src/json/feature_collection_parser.cpp | 36 ++-- src/json/geojson_generator.cpp | 66 +++---- src/libxml2_loader.cpp | 2 +- src/load_map.cpp | 162 +++++++++--------- src/map.cpp | 4 +- src/placement_finder.cpp | 30 ++-- src/rapidxml_loader.cpp | 4 +- src/symbolizer_helpers.cpp | 2 +- src/text_placements/list.cpp | 2 +- src/text_placements/simple.cpp | 4 +- src/xml_tree.cpp | 6 +- 22 files changed, 202 insertions(+), 202 deletions(-) diff --git a/src/agg/process_line_symbolizer.cpp b/src/agg/process_line_symbolizer.cpp index e45226bf3..193e53f43 100644 --- a/src/agg/process_line_symbolizer.cpp +++ b/src/agg/process_line_symbolizer.cpp @@ -63,7 +63,7 @@ void agg_renderer::process(line_symbolizer const& sym, agg::rendering_buffer buf(pixmap_.raw_data(),width_,height_, width_ * 4); agg::pixfmt_rgba32_plain pixf(buf); - + box2d ext = query_extent_ * 1.1; if (sym.get_rasterizer() == RASTERIZER_FAST) { @@ -132,7 +132,7 @@ void agg_renderer::process(line_symbolizer const& sym, clipped_geometry_type clipped(geom); clipped.clip_box(ext.minx(),ext.miny(),ext.maxx(),ext.maxy()); path_type path(t_,clipped,prj_trans); - + if (stroke_.has_dash()) { agg::conv_dash dash(path); diff --git a/src/agg/process_markers_symbolizer.cpp b/src/agg/process_markers_symbolizer.cpp index 1637f9450..57a01448e 100644 --- a/src/agg/process_markers_symbolizer.cpp +++ b/src/agg/process_markers_symbolizer.cpp @@ -253,7 +253,7 @@ void agg_renderer::process(markers_symbolizer const& sym, if (marker_type == ARROW) marker.concat_path(arrow_); - + clipped_geometry_type clipped(geom); clipped.clip_box(query_extent_.minx(),query_extent_.miny(),query_extent_.maxx(),query_extent_.maxy()); path_type path(t_,clipped,prj_trans); diff --git a/src/agg/process_polygon_pattern_symbolizer.cpp b/src/agg/process_polygon_pattern_symbolizer.cpp index 7e0f92c94..670fea9b6 100644 --- a/src/agg/process_polygon_pattern_symbolizer.cpp +++ b/src/agg/process_polygon_pattern_symbolizer.cpp @@ -134,7 +134,7 @@ void agg_renderer::process(polygon_pattern_symbolizer const& sym, if (align == LOCAL_ALIGNMENT) { double x0=0,y0=0; - if (num_geometries>0) // FIXME: hmm...? + if (num_geometries>0) // FIXME: hmm...? { clipped_geometry_type clipped(feature->get_geometry(0)); clipped.clip_box(query_extent_.minx(),query_extent_.miny(),query_extent_.maxx(),query_extent_.maxy()); diff --git a/src/agg/process_polygon_symbolizer.cpp b/src/agg/process_polygon_symbolizer.cpp index 4b1f64bb8..a7187a444 100644 --- a/src/agg/process_polygon_symbolizer.cpp +++ b/src/agg/process_polygon_symbolizer.cpp @@ -63,7 +63,7 @@ void agg_renderer::process(polygon_symbolizer const& sym, unsigned a=fill_.alpha(); //renb.clip_box(0,0,width_,height_); renderer ren(renb); - + ras_ptr->reset(); switch (sym.get_gamma_method()) { diff --git a/src/cairo_renderer.cpp b/src/cairo_renderer.cpp index 76eb9b009..c900491a6 100644 --- a/src/cairo_renderer.cpp +++ b/src/cairo_renderer.cpp @@ -256,7 +256,7 @@ cairo_face_ptr cairo_face_manager::get_face(face_ptr face) entry = boost::make_shared(font_engine_, face); cache_.insert(std::make_pair(face, entry)); } - + return entry; } @@ -712,7 +712,7 @@ void cairo_renderer_base::start_map_processing(Map const& map) } query_extent_ = query_extent; } - + void cairo_renderer_base::end_layer_processing(layer const&) { #ifdef MAPNIK_DEBUG @@ -739,13 +739,13 @@ void cairo_renderer_base::start_map_processing(Map const& map) { clipped_geometry_type clipped(geom); clipped.clip_box(query_extent_.minx(),query_extent_.miny(),query_extent_.maxx(),query_extent_.maxy()); - path_type path(t_,clipped,prj_trans); + path_type path(t_,clipped,prj_trans); context.add_path(path); context.fill(); } } } - + void cairo_renderer_base::process(building_symbolizer const& sym, mapnik::feature_ptr const& feature, proj_transform const& prj_trans) @@ -862,7 +862,7 @@ void cairo_renderer_base::start_map_processing(Map const& map) { typedef agg::conv_clip_polyline clipped_geometry_type; typedef coord_transform2 path_type; - + cairo_context context(context_); mapnik::stroke const& stroke_ = sym.get_stroke(); @@ -1065,7 +1065,7 @@ void cairo_renderer_base::start_map_processing(Map const& map) 1.0 /*scale_factor*/, t_, font_manager_, detector_, query_extent_); cairo_context context(context_); - + while (helper.next()) { placements_type &placements = helper.placements(); for (unsigned int ii = 0; ii < placements.size(); ++ii) @@ -1085,7 +1085,7 @@ void cairo_renderer_base::start_map_processing(Map const& map) { typedef agg::conv_clip_polyline clipped_geometry_type; typedef coord_transform2 path_type; - + std::string filename = path_processor_type::evaluate( *sym.get_filename(), *feature); boost::optional marker = mapnik::marker_cache::instance()->find(filename,true); if (!marker && !(*marker)->is_bitmap()) return; @@ -1109,7 +1109,7 @@ void cairo_renderer_base::start_map_processing(Map const& map) clipped_geometry_type clipped(geom); clipped.clip_box(query_extent_.minx(),query_extent_.miny(),query_extent_.maxx(),query_extent_.maxy()); path_type path(t_,clipped,prj_trans); - + double length(0); double x0(0), y0(0); double x, y; @@ -1158,7 +1158,7 @@ void cairo_renderer_base::start_map_processing(Map const& map) { typedef agg::conv_clip_polygon clipped_geometry_type; typedef coord_transform2 path_type; - + cairo_context context(context_); std::string filename = path_processor_type::evaluate( *sym.get_filename(), *feature); boost::optional marker = mapnik::marker_cache::instance()->find(filename,true); @@ -1178,7 +1178,7 @@ void cairo_renderer_base::start_map_processing(Map const& map) { clipped_geometry_type clipped(geom); clipped.clip_box(query_extent_.minx(),query_extent_.miny(),query_extent_.maxx(),query_extent_.maxy()); - path_type path(t_,clipped,prj_trans); + path_type path(t_,clipped,prj_trans); context.add_path(path); context.fill(); } @@ -1247,9 +1247,9 @@ void cairo_renderer_base::start_map_processing(Map const& map) if (geom.num_points() > 1) { - + path_type path(t_, geom, prj_trans); - + markers_placement placement(path, arrow_.extent(), detector_, sym.get_spacing(), sym.get_max_error(), sym.get_allow_overlap()); double x, y, angle; diff --git a/src/color.cpp b/src/color.cpp index 3a98976f7..480b6b9d2 100644 --- a/src/color.cpp +++ b/src/color.cpp @@ -137,7 +137,7 @@ void color_factory::init_from_string(color & c, std::string const& css_color) } bool color_factory::parse_from_string(color & c, std::string const& css_color, - mapnik::css_color_grammar const& g) + mapnik::css_color_grammar const& g) { std::string::const_iterator first = css_color.begin(); std::string::const_iterator last = css_color.end(); diff --git a/src/config_error.cpp b/src/config_error.cpp index 21ab1ac63..714f5a5e2 100644 --- a/src/config_error.cpp +++ b/src/config_error.cpp @@ -22,7 +22,7 @@ config_error::config_error(std::string const& what, unsigned line_number, std::s } - char const* config_error::what() const throw() +char const* config_error::what() const throw() { std::stringstream s; s << file_; @@ -34,10 +34,10 @@ config_error::config_error(std::string const& what, unsigned line_number, std::s return msg_.c_str(); } - void config_error::append_context(std::string const& ctx) const - { - what_ += " " + ctx; - } +void config_error::append_context(std::string const& ctx) const +{ + what_ += " " + ctx; +} void config_error::append_context(std::string const& ctx, xml_node const& node) const { diff --git a/src/conversions.cpp b/src/conversions.cpp index 7debd7a6c..822b63263 100644 --- a/src/conversions.cpp +++ b/src/conversions.cpp @@ -23,14 +23,14 @@ // boost #include -#define BOOST_SPIRIT_AUTO(domain_, name, expr) \ - typedef boost::proto::result_of:: \ - deep_copy::type name##_expr_type; \ - BOOST_SPIRIT_ASSERT_MATCH( \ - boost::spirit::domain_::domain, name##_expr_type); \ - BOOST_AUTO(name, boost::proto::deep_copy(expr)); \ - - +#define BOOST_SPIRIT_AUTO(domain_, name, expr) \ + typedef boost::proto::result_of:: \ + deep_copy::type name##_expr_type; \ + BOOST_SPIRIT_ASSERT_MATCH( \ + boost::spirit::domain_::domain, name##_expr_type); \ + BOOST_AUTO(name, boost::proto::deep_copy(expr)); \ + \ + \ namespace mapnik { namespace conversions { using namespace boost::spirit; diff --git a/src/expression.cpp b/src/expression.cpp index a99fc9fcc..84cd6b0ec 100644 --- a/src/expression.cpp +++ b/src/expression.cpp @@ -51,8 +51,8 @@ expression_ptr expression_factory::compile(std::string const& str,transcoder con } bool expression_factory::parse_from_string(expression_ptr const& expr, - std::string const& str, - mapnik::expression_grammar const& g) + std::string const& str, + mapnik::expression_grammar const& g) { std::string::const_iterator itr = str.begin(); std::string::const_iterator end = str.end(); diff --git a/src/feature_style_processor.cpp b/src/feature_style_processor.cpp index 9b8c9de12..4b78ab0a3 100644 --- a/src/feature_style_processor.cpp +++ b/src/feature_style_processor.cpp @@ -203,7 +203,7 @@ void feature_style_processor::apply_to_layer(layer const& lay, Proces return; } - + #if defined(RENDERING_STATS) progress_timer layer_timer(std::clog, "rendering total for layer: '" + lay.name() + "'"); @@ -270,7 +270,7 @@ void feature_style_processor::apply_to_layer(layer const& lay, Proces m_.height()/qh); query q(layer_ext,res,scale_denom,unbuffered_extent); - p.start_layer_processing(lay, query_ext); + p.start_layer_processing(lay, query_ext); std::vector active_styles; attribute_collector collector(names); double filt_factor = 1; diff --git a/src/formatting/expression.cpp b/src/formatting/expression.cpp index 1bd199a33..5dfff9972 100644 --- a/src/formatting/expression.cpp +++ b/src/formatting/expression.cpp @@ -85,25 +85,25 @@ void expression_format::apply(char_properties const& p, const Feature &feature, { char_properties new_properties = p; if (face_name) new_properties.face_name = - boost::apply_visitor(evaluate(feature), *face_name).to_string(); + boost::apply_visitor(evaluate(feature), *face_name).to_string(); if (text_size) new_properties.text_size = - boost::apply_visitor(evaluate(feature), *text_size).to_double(); + boost::apply_visitor(evaluate(feature), *text_size).to_double(); if (character_spacing) new_properties.character_spacing = - boost::apply_visitor(evaluate(feature), *character_spacing).to_double(); + boost::apply_visitor(evaluate(feature), *character_spacing).to_double(); if (line_spacing) new_properties.line_spacing = - boost::apply_visitor(evaluate(feature), *line_spacing).to_double(); + boost::apply_visitor(evaluate(feature), *line_spacing).to_double(); if (text_opacity) new_properties.text_opacity = - boost::apply_visitor(evaluate(feature), *text_opacity).to_double(); + boost::apply_visitor(evaluate(feature), *text_opacity).to_double(); if (wrap_before) new_properties.wrap_before = - boost::apply_visitor(evaluate(feature), *wrap_before).to_bool(); + boost::apply_visitor(evaluate(feature), *wrap_before).to_bool(); if (wrap_char) new_properties.wrap_char = - boost::apply_visitor(evaluate(feature), *character_spacing).to_unicode()[0]; + boost::apply_visitor(evaluate(feature), *character_spacing).to_unicode()[0]; // if (fill) new_properties.fill = // boost::apply_visitor(evaluate(feature), *fill).to_color(); // if (halo_fill) new_properties.halo_fill = // boost::apply_visitor(evaluate(feature), *halo_fill).to_color(); if (halo_radius) new_properties.halo_radius = - boost::apply_visitor(evaluate(feature), *halo_radius).to_double(); + boost::apply_visitor(evaluate(feature), *halo_radius).to_double(); if (child_) { child_->apply(new_properties, feature, output); diff --git a/src/json/feature_collection_parser.cpp b/src/json/feature_collection_parser.cpp index fbf9c2b19..a7f2a418a 100644 --- a/src/json/feature_collection_parser.cpp +++ b/src/json/feature_collection_parser.cpp @@ -33,29 +33,29 @@ namespace mapnik { namespace json { #if BOOST_VERSION >= 104700 -template -feature_collection_parser::feature_collection_parser(mapnik::context_ptr const& ctx, mapnik::transcoder const& tr) - : grammar_(new feature_collection_grammar(ctx,tr)) {} + template + feature_collection_parser::feature_collection_parser(mapnik::context_ptr const& ctx, mapnik::transcoder const& tr) + : grammar_(new feature_collection_grammar(ctx,tr)) {} -template -feature_collection_parser::~feature_collection_parser() {} + template + feature_collection_parser::~feature_collection_parser() {} #endif -template -bool feature_collection_parser::parse(iterator_type first, iterator_type last, std::vector & features) -{ + template + bool feature_collection_parser::parse(iterator_type first, iterator_type last, std::vector & features) + { #if BOOST_VERSION >= 104700 - using namespace boost::spirit; - return qi::phrase_parse(first, last, *grammar_, standard_wide::space, features); + using namespace boost::spirit; + return qi::phrase_parse(first, last, *grammar_, standard_wide::space, features); #else - std::ostringstream s; - s << BOOST_VERSION/100000 << "." << BOOST_VERSION/100 % 1000 << "." << BOOST_VERSION % 100; - throw std::runtime_error("mapnik::feature_collection_parser::parse() requires at least boost 1.47 while your build was compiled against boost " + s.str()); - return false; + std::ostringstream s; + s << BOOST_VERSION/100000 << "." << BOOST_VERSION/100 % 1000 << "." << BOOST_VERSION % 100; + throw std::runtime_error("mapnik::feature_collection_parser::parse() requires at least boost 1.47 while your build was compiled against boost " + s.str()); + return false; #endif -} + } -template class feature_collection_parser ; -template class feature_collection_parser > >; -}} + template class feature_collection_parser ; + template class feature_collection_parser > >; + }} diff --git a/src/json/geojson_generator.cpp b/src/json/geojson_generator.cpp index c25440ded..750489690 100644 --- a/src/json/geojson_generator.cpp +++ b/src/json/geojson_generator.cpp @@ -32,30 +32,30 @@ namespace mapnik { namespace json { -feature_generator::feature_generator() - : grammar_(new feature_generator_grammar()) {} - -feature_generator::~feature_generator() {} + feature_generator::feature_generator() + : grammar_(new feature_generator_grammar()) {} -bool feature_generator::generate(std::string & geojson, mapnik::Feature const& f) -{ - sink_type sink(geojson); - return karma::generate(sink, *grammar_,f); -} + feature_generator::~feature_generator() {} + + bool feature_generator::generate(std::string & geojson, mapnik::Feature const& f) + { + sink_type sink(geojson); + return karma::generate(sink, *grammar_,f); + } -geometry_generator::geometry_generator() - : grammar_(new multi_geometry_generator_grammar()) {} + geometry_generator::geometry_generator() + : grammar_(new multi_geometry_generator_grammar()) {} -geometry_generator::~geometry_generator() {} + geometry_generator::~geometry_generator() {} -bool geometry_generator::generate(std::string & geojson, mapnik::geometry_container const& g) -{ - sink_type sink(geojson); - return karma::generate(sink, *grammar_,g); -} + bool geometry_generator::generate(std::string & geojson, mapnik::geometry_container const& g) + { + sink_type sink(geojson); + return karma::generate(sink, *grammar_,g); + } -}} + }} #else @@ -65,22 +65,22 @@ bool geometry_generator::generate(std::string & geojson, mapnik::geometry_contai namespace mapnik { namespace json { -bool feature_generator::generate(std::string & geojson, mapnik::Feature const& f) -{ - std::ostringstream s; - s << BOOST_VERSION/100000 << "." << BOOST_VERSION/100 % 1000 << "." << BOOST_VERSION % 100; - throw std::runtime_error("feature_generator::generate() requires at least boost 1.47 while your build was compiled against boost " + s.str()); - return false; -} + bool feature_generator::generate(std::string & geojson, mapnik::Feature const& f) + { + std::ostringstream s; + s << BOOST_VERSION/100000 << "." << BOOST_VERSION/100 % 1000 << "." << BOOST_VERSION % 100; + throw std::runtime_error("feature_generator::generate() requires at least boost 1.47 while your build was compiled against boost " + s.str()); + return false; + } -bool geometry_generator::generate(std::string & geojson, mapnik::geometry_container const& g) -{ - std::ostringstream s; - s << BOOST_VERSION/100000 << "." << BOOST_VERSION/100 % 1000 << "." << BOOST_VERSION % 100; - throw std::runtime_error("geometry_generator::generate() requires at least boost 1.47 while your build was compiled against boost " + s.str()); - return false; -} + bool geometry_generator::generate(std::string & geojson, mapnik::geometry_container const& g) + { + std::ostringstream s; + s << BOOST_VERSION/100000 << "." << BOOST_VERSION/100 % 1000 << "." << BOOST_VERSION % 100; + throw std::runtime_error("geometry_generator::generate() requires at least boost 1.47 while your build was compiled against boost " + s.str()); + return false; + } -}} + }} #endif diff --git a/src/libxml2_loader.cpp b/src/libxml2_loader.cpp index 964545a00..d95e145ca 100644 --- a/src/libxml2_loader.cpp +++ b/src/libxml2_loader.cpp @@ -191,7 +191,7 @@ private: } break; case XML_COMMENT_NODE: - break; + break; default: break; diff --git a/src/load_map.cpp b/src/load_map.cpp index 5e4680fd4..0b71ba838 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -85,7 +85,7 @@ public: filename_( filename ), relative_to_xml_(true), font_manager_(font_engine_) - {} + {} void parse_map(Map & map, xml_node const& sty, std::string const& base_path); private: @@ -121,7 +121,7 @@ private: std::string ensure_relative_to_xml(boost::optional opt_path); boost::optional get_opt_color_attr(boost::property_tree::ptree const& node, - std::string const& name); + std::string const& name); bool strict_; std::string filename_; @@ -306,93 +306,93 @@ void map_parser::parse_map_include(Map & map, xml_node const& include) { try { - xml_node::const_iterator itr = include.begin(); - xml_node::const_iterator end = include.end(); + xml_node::const_iterator itr = include.begin(); + xml_node::const_iterator end = include.end(); - for (; itr != end; ++itr) - { - if (itr->is_text()) continue; - if (itr->is("Include")) + for (; itr != end; ++itr) { - parse_map_include(map, *itr); - } - else if (itr->is("Style")) - { - parse_style(map, *itr); - } - else if (itr->is("Layer")) - { - parse_layer(map, *itr); - } - else if (itr->is("FontSet")) - { - parse_fontset(map, *itr); - } - else if (itr->is("MetaWriter")) - { - parse_metawriter(map, *itr); - } - else if (itr->is("FileSource")) - { - std::string name = itr->get_attr("name"); - std::string value = itr->get_text(); - file_sources_[name] = value; - } - else if (itr->is("Datasource")) - { - std::string name = itr->get_attr("name", std::string("Unnamed")); - parameters params; - xml_node::const_iterator paramIter = itr->begin(); - xml_node::const_iterator endParam = itr->end(); - for (; paramIter != endParam; ++paramIter) + if (itr->is_text()) continue; + if (itr->is("Include")) { - if (paramIter->is("Parameter")) - { - std::string name = paramIter->get_attr("name"); - std::string value = paramIter->get_text(); - params[name] = value; - } + parse_map_include(map, *itr); } - datasource_templates_[name] = params; - } - else if (itr->is("Parameters")) - { - std::string name = itr->get_attr("name", std::string("Unnamed")); - parameters & params = map.get_extra_parameters(); - xml_node::const_iterator paramIter = itr->begin(); - xml_node::const_iterator endParam = itr->end(); - for (; paramIter != endParam; ++paramIter) + else if (itr->is("Style")) { - if (paramIter->is("Parameter")) + parse_style(map, *itr); + } + else if (itr->is("Layer")) + { + parse_layer(map, *itr); + } + else if (itr->is("FontSet")) + { + parse_fontset(map, *itr); + } + else if (itr->is("MetaWriter")) + { + parse_metawriter(map, *itr); + } + else if (itr->is("FileSource")) + { + std::string name = itr->get_attr("name"); + std::string value = itr->get_text(); + file_sources_[name] = value; + } + else if (itr->is("Datasource")) + { + std::string name = itr->get_attr("name", std::string("Unnamed")); + parameters params; + xml_node::const_iterator paramIter = itr->begin(); + xml_node::const_iterator endParam = itr->end(); + for (; paramIter != endParam; ++paramIter) { - std::string name = paramIter->get_attr("name"); - bool is_string = true; - boost::optional type = paramIter->get_opt_attr("type"); - if (type) - { - if (*type == "int") - { - is_string = false; - int value = paramIter->get_value(); - params[name] = value; - } - else if (*type == "float") - { - is_string = false; - double value = paramIter->get_value(); - params[name] = value; - } - } - - if (is_string) + if (paramIter->is("Parameter")) { + std::string name = paramIter->get_attr("name"); std::string value = paramIter->get_text(); params[name] = value; } } + datasource_templates_[name] = params; + } + else if (itr->is("Parameters")) + { + std::string name = itr->get_attr("name", std::string("Unnamed")); + parameters & params = map.get_extra_parameters(); + xml_node::const_iterator paramIter = itr->begin(); + xml_node::const_iterator endParam = itr->end(); + for (; paramIter != endParam; ++paramIter) + { + if (paramIter->is("Parameter")) + { + std::string name = paramIter->get_attr("name"); + bool is_string = true; + boost::optional type = paramIter->get_opt_attr("type"); + if (type) + { + if (*type == "int") + { + is_string = false; + int value = paramIter->get_value(); + params[name] = value; + } + else if (*type == "float") + { + is_string = false; + double value = paramIter->get_value(); + params[name] = value; + } + } + + if (is_string) + { + std::string value = paramIter->get_text(); + params[name] = value; + } + } + } } } - } } catch (const config_error & ex) { ex.append_context(include); throw; @@ -1060,7 +1060,7 @@ void map_parser::parse_text_symbolizer( rule & rule, xml_node const& sym ) placement_finder->defaults.from_xml(sym, fontsets_); } if (strict_ && - !placement_finder->defaults.format.fontset.size()) + !placement_finder->defaults.format.fontset.size()) ensure_font_face(placement_finder->defaults.format.face_name); text_symbolizer text_symbol = text_symbolizer(placement_finder); @@ -1087,7 +1087,7 @@ void map_parser::parse_shield_symbolizer(rule & rule, xml_node const& sym ) } placement_finder->defaults.from_xml(sym, fontsets_); if (strict_ && - !placement_finder->defaults.format.fontset.size()) + !placement_finder->defaults.format.fontset.size()) ensure_font_face(placement_finder->defaults.format.face_name); shield_symbolizer shield_symbol = shield_symbolizer(placement_finder); @@ -1447,7 +1447,7 @@ void map_parser::parse_raster_colorizer(raster_colorizer_ptr const& rc, maximumValue = *value; optional label = - stopIter->get_opt_attr("label"); + stopIter->get_opt_attr("label"); //append the stop colorizer_stop tmpStop; @@ -1532,8 +1532,8 @@ void map_parser::find_unused_nodes_recursive(xml_node const& node, std::stringst if (!aitr->second.processed) { error_message << "\n* attribute '" << aitr->first << - "' with value '" << aitr->second.value << - "' in line " << node.line(); + "' with value '" << aitr->second.value << + "' in line " << node.line(); } } xml_node::const_iterator itr = node.begin(); diff --git a/src/map.cpp b/src/map.cpp index 5af404a62..661c5c819 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -588,7 +588,7 @@ featureset_ptr Map::query_point(unsigned index, double x, double y) const featureset_ptr fs = ds->features_at_point(mapnik::coord2d(x,y)); if (fs) return boost::make_shared >(fs, - hit_test_filter(x,y,tol)); + hit_test_filter(x,y,tol)); } } catch (...) @@ -634,7 +634,7 @@ featureset_ptr Map::query_map_point(unsigned index, double x, double y) const featureset_ptr fs = ds->features_at_point(mapnik::coord2d(x,y)); if (fs) return boost::make_shared >(fs, - hit_test_filter(x,y,tol)); + hit_test_filter(x,y,tol)); } } catch (...) diff --git a/src/placement_finder.cpp b/src/placement_finder.cpp index f68597edb..387323323 100644 --- a/src/placement_finder.cpp +++ b/src/placement_finder.cpp @@ -269,9 +269,9 @@ void placement_finder::find_line_breaks() // wrap text at first wrap_char after (default) the wrap width or immediately before the current word if ((c == '\n') || (line_width > 0 && - ((line_width > wrap_at && !ci.format->wrap_before) || + ((line_width > wrap_at && !ci.format->wrap_before) || ((line_width + last_wrap_char_width + word_width) > wrap_at && ci.format->wrap_before)) ) - ) + ) { add_line(line_width, line_height, first_line); line_breaks_.push_back(last_wrap_char_pos); @@ -440,8 +440,8 @@ void placement_finder::find_point_placement(double label_x, if (!detector_.extent().intersects(e) || (!p.allow_overlap && - !detector_.has_point_placement(e, pi.get_actual_minimum_distance()))) - { + !detector_.has_point_placement(e, pi.get_actual_minimum_distance()))) + { return; } @@ -675,10 +675,10 @@ void placement_finder::find_line_placements(PathT & shape_path) template std::auto_ptr placement_finder::get_placement_offset(std::vector const& path_positions, - std::vector const& path_distances, - int & orientation, - unsigned index, - double distance) + std::vector const& path_distances, + int & orientation, + unsigned index, + double distance) { //Check that the given distance is on the given index and find the correct index and distance if not while (distance < 0 && index > 1) @@ -718,10 +718,10 @@ std::auto_ptr placement_finder::get_placement_offset(std:: } std::auto_ptr current_placement( - new text_path((old_x + dx*distance/segment_length), - (old_y + dy*distance/segment_length) - ) - ); + new text_path((old_x + dx*distance/segment_length), + (old_y + dy*distance/segment_length) + ) + ); double angle = atan2(-dy, dx); @@ -830,7 +830,7 @@ std::auto_ptr placement_finder::get_placement_offset(std:: render_angle += M_PI; } current_placement->add_node(&ci, - render_x - current_placement->center.x, + render_x - current_placement->center.x, -render_y + current_placement->center.y, render_angle); @@ -870,7 +870,7 @@ std::auto_ptr placement_finder::get_placement_offset(std:: template bool placement_finder::test_placement(std::auto_ptr const& current_placement, - int orientation) + int orientation) { //Create and test envelopes bool status = true; @@ -909,8 +909,8 @@ bool placement_finder::test_placement(std::auto_ptr const& if (!detector_.extent().intersects(e) || (!p.allow_overlap && !detector_.has_placement(e, info_.get_string(), pi.get_actual_minimum_distance()) + ) ) - ) { //std::clog << "No Intersects:" << !dimensions_.intersects(e) << ": " << e << " @ " << dimensions_ << std::endl; //std::clog << "No Placements:" << !detector_.has_placement(e, info.get_string(), p.minimum_distance) << std::endl; diff --git a/src/rapidxml_loader.cpp b/src/rapidxml_loader.cpp index c7be38b5b..4a9293f54 100644 --- a/src/rapidxml_loader.cpp +++ b/src/rapidxml_loader.cpp @@ -73,7 +73,7 @@ public: { stream.unsetf(std::ios::skipws); std::vector v(std::istreambuf_iterator(stream.rdbuf()), - std::istreambuf_iterator()); + std::istreambuf_iterator()); if (!stream.good()) { throw config_error("Could not load map file", 0, filename_); @@ -83,7 +83,7 @@ public: { // Parse using appropriate flags const int f_tws = rapidxml::parse_normalize_whitespace - | rapidxml::parse_trim_whitespace; + | rapidxml::parse_trim_whitespace; rapidxml::xml_document<> doc; doc.parse(&v.front()); diff --git a/src/symbolizer_helpers.cpp b/src/symbolizer_helpers.cpp index 8c59ff272..cfcc83902 100644 --- a/src/symbolizer_helpers.cpp +++ b/src/symbolizer_helpers.cpp @@ -51,7 +51,7 @@ bool text_symbolizer_helper::next_line_placement() geo_itr_ = geometries_to_process_.begin(); continue; //Reexecute size check } - + typedef agg::conv_clip_polyline clipped_geometry_type; typedef coord_transform2 path_type; clipped_geometry_type clipped(**geo_itr_); diff --git a/src/text_placements/list.cpp b/src/text_placements/list.cpp index 6a43e8716..ce2ea33d4 100644 --- a/src/text_placements/list.cpp +++ b/src/text_placements/list.cpp @@ -64,7 +64,7 @@ text_symbolizer_properties & text_placements_list::get(unsigned i) text_placement_info_ptr text_placements_list::get_placement_info(double scale_factor) const { return text_placement_info_ptr( - boost::make_shared(this, scale_factor)); + boost::make_shared(this, scale_factor)); } text_placements_list::text_placements_list() : text_placements(), list_(0) diff --git a/src/text_placements/simple.cpp b/src/text_placements/simple.cpp index f23e79e67..c35f1e7f3 100644 --- a/src/text_placements/simple.cpp +++ b/src/text_placements/simple.cpp @@ -104,7 +104,7 @@ text_placement_info_ptr text_placements_simple::get_placement_info( double scale_factor) const { return text_placement_info_ptr( - boost::make_shared(this, scale_factor)); + boost::make_shared(this, scale_factor)); } /** Position string: [POS][SIZE] @@ -171,7 +171,7 @@ std::string text_placements_simple::get_positions() text_placements_ptr text_placements_simple::from_xml(xml_node const &xml, fontset_map const & fontsets) { text_placements_ptr ptr = boost::make_shared( - xml.get_attr("placements", "X")); + xml.get_attr("placements", "X")); ptr->defaults.from_xml(xml, fontsets); return ptr; } diff --git a/src/xml_tree.cpp b/src/xml_tree.cpp index a1b80b786..ef61e9258 100644 --- a/src/xml_tree.cpp +++ b/src/xml_tree.cpp @@ -127,7 +127,7 @@ struct name_trait BOOST_STATIC_ASSERT( sizeof(T) == 0 ); }; -#define DEFINE_NAME_TRAIT( type, type_name ) \ +#define DEFINE_NAME_TRAIT( type, type_name ) \ template <> \ struct name_trait \ { \ @@ -220,8 +220,8 @@ attribute_not_found::attribute_not_found( std::string const& node_name, std::string const& attribute_name) : - node_name_(node_name), - attribute_name_(attribute_name) + node_name_(node_name), + attribute_name_(attribute_name) { } From 76e108ff9d49e96e6e622600669b2d5ee5c1c863 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Tue, 13 Mar 2012 07:58:38 -0700 Subject: [PATCH 092/238] remove uneeded shared_ptr copy --- src/text_placements/list.cpp | 3 +-- src/text_placements/simple.cpp | 9 +++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/text_placements/list.cpp b/src/text_placements/list.cpp index ce2ea33d4..7aea6812d 100644 --- a/src/text_placements/list.cpp +++ b/src/text_placements/list.cpp @@ -63,8 +63,7 @@ text_symbolizer_properties & text_placements_list::get(unsigned i) text_placement_info_ptr text_placements_list::get_placement_info(double scale_factor) const { - return text_placement_info_ptr( - boost::make_shared(this, scale_factor)); + return boost::make_shared(this, scale_factor); } text_placements_list::text_placements_list() : text_placements(), list_(0) diff --git a/src/text_placements/simple.cpp b/src/text_placements/simple.cpp index c35f1e7f3..cefa199fa 100644 --- a/src/text_placements/simple.cpp +++ b/src/text_placements/simple.cpp @@ -103,8 +103,7 @@ bool text_placement_info_simple::next_position_only() text_placement_info_ptr text_placements_simple::get_placement_info( double scale_factor) const { - return text_placement_info_ptr( - boost::make_shared(this, scale_factor)); + return boost::make_shared(this, scale_factor); } /** Position string: [POS][SIZE] @@ -145,10 +144,12 @@ void text_placements_simple::set_positions(std::string positions) (direction_name[push_back(phoenix::ref(direction_), _1)] % ',') >> *(',' >> qi::float_[push_back(phoenix::ref(text_sizes_), _1)]), space ); - if (first != last) { + if (first != last) + { std::cerr << "WARNING: Could not parse text_placement_simple placement string ('" << positions << "').\n"; } - if (direction_.size() == 0) { + if (direction_.size() == 0) + { std::cerr << "WARNING: text_placements_simple with no valid placements! ('"<< positions<<"')\n"; } } From b36739fd88b41a7348c75f34503f37ce85726d17 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Tue, 13 Mar 2012 07:59:22 -0700 Subject: [PATCH 093/238] apply mapnik_format.el formatting --- bindings/python/mapnik_image.cpp | 2 +- bindings/python/mapnik_text_placement.cpp | 102 +++++++++---------- bindings/python/mapnik_threads.hpp | 14 +-- bindings/python/python_optional.hpp | 8 +- plugins/input/occi/occi_featureset.cpp | 2 +- plugins/input/postgis/postgis_datasource.cpp | 32 +++--- plugins/input/shape/shape_io.cpp | 10 +- 7 files changed, 85 insertions(+), 85 deletions(-) diff --git a/bindings/python/mapnik_image.cpp b/bindings/python/mapnik_image.cpp index 858c53437..5ade76573 100644 --- a/bindings/python/mapnik_image.cpp +++ b/bindings/python/mapnik_image.cpp @@ -125,7 +125,7 @@ bool painted(mapnik::image_32 const& im) void set_pixel(mapnik::image_32 & im, unsigned x, unsigned y, mapnik::color const& c) { im.setPixel(x, y, c.rgba()); -} +} boost::shared_ptr open_from_file(std::string const& filename) { diff --git a/bindings/python/mapnik_text_placement.cpp b/bindings/python/mapnik_text_placement.cpp index 12c8dfa55..c9f1ac4e5 100644 --- a/bindings/python/mapnik_text_placement.cpp +++ b/bindings/python/mapnik_text_placement.cpp @@ -40,15 +40,15 @@ using namespace mapnik; /* Notes: -Overriding functions in inherited classes: -boost.python documentation doesn't really tell you how to do it. -But this helps: -http://www.gamedev.net/topic/446225-inheritance-in-boostpython/ + Overriding functions in inherited classes: + boost.python documentation doesn't really tell you how to do it. + But this helps: + http://www.gamedev.net/topic/446225-inheritance-in-boostpython/ -register_ptr_to_python is required for wrapped classes, but not for unwrapped. + register_ptr_to_python is required for wrapped classes, but not for unwrapped. -Functions don't have to be members of the class, but can also be -normal functions taking a ref to the class as first parameter. + Functions don't have to be members of the class, but can also be + normal functions taking a ref to the class as first parameter. */ namespace { @@ -261,7 +261,7 @@ struct TextPlacementsWrap: text_placements, wrapper struct TextPlacementInfoWrap: text_placement_info, wrapper { TextPlacementInfoWrap(text_placements const* parent, - double scale_factor_) + double scale_factor_) : text_placement_info(parent, scale_factor_) { @@ -339,7 +339,7 @@ void export_text_placement() ; class_("TextSymbolizer", - init<>()) + init<>()) .def(init()) .add_property("placements", &text_symbolizer::get_placement_options, @@ -357,7 +357,7 @@ void export_text_placement() class_with_converter - ("TextSymbolizerProperties") + ("TextSymbolizerProperties") .def_readwrite_convert("label_placement", &text_symbolizer_properties::label_placement) .def_readwrite_convert("horizontal_alignment", &text_symbolizer_properties::halign) .def_readwrite_convert("justify_alignment", &text_symbolizer_properties::jalign) @@ -381,15 +381,15 @@ void export_text_placement() .add_property ("format_tree", &text_symbolizer_properties::format_tree, &text_symbolizer_properties::set_format_tree); - /* from_xml, to_xml operate on mapnik's internal XML tree and don't make sense in python. - add_expressions isn't useful in python either. The result is only needed by - attribute_collector (which isn't exposed in python) and - it just calls add_expressions of the associated formatting tree. - set_old_style expression is just a compatibility wrapper and doesn't need to be exposed in python. */ - ; + /* from_xml, to_xml operate on mapnik's internal XML tree and don't make sense in python. + add_expressions isn't useful in python either. The result is only needed by + attribute_collector (which isn't exposed in python) and + it just calls add_expressions of the associated formatting tree. + set_old_style expression is just a compatibility wrapper and doesn't need to be exposed in python. */ + ; class_ - ("CharProperties") + ("CharProperties") .def(init()) //Copy constructor .def_readwrite("face_name", &char_properties::face_name) .def_readwrite("fontset", &char_properties::fontset) @@ -407,9 +407,9 @@ void export_text_placement() ; class_, - boost::noncopyable> - ("TextPlacements") + boost::shared_ptr, + boost::noncopyable> + ("TextPlacements") .def_readwrite("defaults", &text_placements::defaults) .def("get_placement_info", pure_virtual(&text_placements::get_placement_info)) /* TODO: add_expressions() */ @@ -417,10 +417,10 @@ void export_text_placement() register_ptr_to_python >(); class_, - boost::noncopyable> - ("TextPlacementInfo", - init()) + boost::shared_ptr, + boost::noncopyable> + ("TextPlacementInfo", + init()) .def("next", pure_virtual(&text_placement_info::next)) .def("get_actual_label_spacing", &text_placement_info::get_actual_label_spacing) .def("get_actual_minimum_distance", &text_placement_info::get_actual_minimum_distance) @@ -432,27 +432,27 @@ void export_text_placement() class_, - boost::noncopyable> - ("ProcessedText", no_init) + boost::shared_ptr, + boost::noncopyable> + ("ProcessedText", no_init) .def("push_back", &processed_text::push_back) .def("clear", &processed_text::clear) ; class_, - boost::noncopyable> - ("ExpressionSet") + boost::shared_ptr, + boost::noncopyable> + ("ExpressionSet") .def("insert", &insert_expression); - ; + ; //TODO: Python namespace class_, - boost::noncopyable> - ("FormattingNode") + boost::shared_ptr, + boost::noncopyable> + ("FormattingNode") .def("apply", pure_virtual(&formatting::node::apply)) .def("add_expressions", &formatting::node::add_expressions, @@ -462,10 +462,10 @@ void export_text_placement() class_, - bases, - boost::noncopyable> - ("FormattingText", init()) + boost::shared_ptr, + bases, + boost::noncopyable> + ("FormattingText", init()) .def(init()) .def("apply", &formatting::text_node::apply, &TextNodeWrap::default_apply) .add_property("text", @@ -476,10 +476,10 @@ void export_text_placement() class_with_converter, - bases, - boost::noncopyable> - ("FormattingFormat") + boost::shared_ptr, + bases, + boost::noncopyable> + ("FormattingFormat") .def_readwrite_convert("text_size", &formatting::format_node::text_size) .def_readwrite_convert("face_name", &formatting::format_node::face_name) .def_readwrite_convert("character_spacing", &formatting::format_node::character_spacing) @@ -499,10 +499,10 @@ void export_text_placement() register_ptr_to_python >(); class_, - bases, - boost::noncopyable> - ("FormattingList", init<>()) + boost::shared_ptr, + bases, + boost::noncopyable> + ("FormattingList", init<>()) .def(init()) .def("append", &formatting::list_node::push_back) .def("apply", &formatting::list_node::apply, &ListNodeWrap::default_apply) @@ -510,15 +510,15 @@ void export_text_placement() .def("__getitem__", &ListNodeWrap::get_item) .def("__setitem__", &ListNodeWrap::set_item) .def("append", &ListNodeWrap::append) - ; + ; register_ptr_to_python >(); class_, - bases, - boost::noncopyable> - ("FormattingExpressionFormat") + boost::shared_ptr, + bases, + boost::noncopyable> + ("FormattingExpressionFormat") .def_readwrite("text_size", &formatting::expression_format::text_size) .def_readwrite("face_name", &formatting::expression_format::face_name) .def_readwrite("character_spacing", &formatting::expression_format::character_spacing) diff --git a/bindings/python/mapnik_threads.hpp b/bindings/python/mapnik_threads.hpp index b1ac3f4e6..85789ff61 100644 --- a/bindings/python/mapnik_threads.hpp +++ b/bindings/python/mapnik_threads.hpp @@ -29,8 +29,8 @@ namespace mapnik { class python_thread { /* Docs: - http://docs.python.org/c-api/init.html#thread-state-and-the-global-interpreter-lock - */ + http://docs.python.org/c-api/init.html#thread-state-and-the-global-interpreter-lock + */ public: static void unblock() { @@ -38,8 +38,8 @@ public: if (state.get()) { std::cerr << "ERROR: Python threads are already unblocked. " - "Unblocking again will loose the current state and " - "might crash later. Aborting!\n"; + "Unblocking again will loose the current state and " + "might crash later. Aborting!\n"; abort(); //This is a serious error and can't be handled in any other sane way } #endif @@ -59,9 +59,9 @@ public: if (thread_support && !state.get()) { std::cerr << "ERROR: Trying to restore python thread state, " - "but no state is saved. Can't continue and also " - "can't raise an exception because the python " - "interpreter might be non-function. Aborting!\n"; + "but no state is saved. Can't continue and also " + "can't raise an exception because the python " + "interpreter might be non-function. Aborting!\n"; abort(); } #endif diff --git a/bindings/python/python_optional.hpp b/bindings/python/python_optional.hpp index da3279f2f..7707f0053 100644 --- a/bindings/python/python_optional.hpp +++ b/bindings/python/python_optional.hpp @@ -102,8 +102,8 @@ struct python_optional : public boost::noncopyable /** This class works around a bug in boost python. - See http://osdir.com/ml/python.c++/2003-11/msg00158.html - */ + See http://osdir.com/ml/python.c++/2003-11/msg00158.html +*/ template class class_with_converter : public boost::python::class_ { @@ -131,8 +131,8 @@ public: self& def_readwrite_convert(char const* name, D const& d, char const* doc=0) { this->add_property(name, - boost::python::make_getter(d, boost::python::return_value_policy()), - boost::python::make_setter(d, boost::python::default_call_policies())); + boost::python::make_getter(d, boost::python::return_value_policy()), + boost::python::make_setter(d, boost::python::default_call_policies())); return *this; } }; diff --git a/plugins/input/occi/occi_featureset.cpp b/plugins/input/occi/occi_featureset.cpp index 1cf17a3aa..3a09200f3 100644 --- a/plugins/input/occi/occi_featureset.cpp +++ b/plugins/input/occi/occi_featureset.cpp @@ -373,7 +373,7 @@ void occi_featureset::convert_ordinates(mapnik::feature_ptr feature, if (! is_single_geom && elem_size > SDO_ELEM_INFO_SIZE) { geometry_type* geom = new geometry_type(geom_type); - + for (int i = SDO_ELEM_INFO_SIZE; i < elem_size; i+=3) { int next_offset = elem_info[i]; diff --git a/plugins/input/postgis/postgis_datasource.cpp b/plugins/input/postgis/postgis_datasource.cpp index f9c1af707..05c397892 100644 --- a/plugins/input/postgis/postgis_datasource.cpp +++ b/plugins/input/postgis/postgis_datasource.cpp @@ -681,22 +681,22 @@ box2d postgis_datasource::envelope() const shared_ptr rs = conn->executeQuery(s.str()); if (rs->next() && !rs->isNull(0)) { - double lox; - double loy; - double hix; - double hiy; - if (mapnik::conversions::string2double(rs->getValue(0),lox) && - mapnik::conversions::string2double(rs->getValue(1),loy) && - mapnik::conversions::string2double(rs->getValue(2),hix) && - mapnik::conversions::string2double(rs->getValue(3),hiy)) - { - extent_.init(lox,loy,hix,hiy); - extent_initialized_ = true; - } - else - { - std::clog << boost::format("Postgis Plugin: warning: could not determine extent from query: %s\n") % s.str() << std::endl; - } + double lox; + double loy; + double hix; + double hiy; + if (mapnik::conversions::string2double(rs->getValue(0),lox) && + mapnik::conversions::string2double(rs->getValue(1),loy) && + mapnik::conversions::string2double(rs->getValue(2),hix) && + mapnik::conversions::string2double(rs->getValue(3),hiy)) + { + extent_.init(lox,loy,hix,hiy); + extent_initialized_ = true; + } + else + { + std::clog << boost::format("Postgis Plugin: warning: could not determine extent from query: %s\n") % s.str() << std::endl; + } } rs->close(); } diff --git a/plugins/input/shape/shape_io.cpp b/plugins/input/shape/shape_io.cpp index 4ace58937..81677677b 100644 --- a/plugins/input/shape/shape_io.cpp +++ b/plugins/input/shape/shape_io.cpp @@ -103,12 +103,12 @@ void shape_io::read_polyline(mapnik::geometry_container & geom) { shape_file::record_type record(reclength_ * 2 - 36); shp_.read_record(record); - + int num_parts = record.read_ndr_integer(); int num_points = record.read_ndr_integer(); if (num_parts == 1) { - geometry_type* line = new geometry_type(mapnik::LineString); + geometry_type* line = new geometry_type(mapnik::LineString); record.skip(4); double x = record.read_double(); double y = record.read_double(); @@ -132,7 +132,7 @@ void shape_io::read_polyline(mapnik::geometry_container & geom) int start, end; for (int k = 0; k < num_parts; ++k) { - geometry_type* line = new geometry_type(mapnik::LineString); + geometry_type* line = new geometry_type(mapnik::LineString); start = parts[k]; if (k == num_parts - 1) { @@ -179,11 +179,11 @@ void shape_io::read_polygon(mapnik::geometry_container & geom) { shape_file::record_type record(reclength_ * 2 - 36); shp_.read_record(record); - + int num_parts = record.read_ndr_integer(); int num_points = record.read_ndr_integer(); std::vector parts(num_parts); - + for (int i = 0; i < num_parts; ++i) { parts[i] = record.read_ndr_integer(); From 5601731a89dee31c43b9bcfa9a447abffa66e833 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Tue, 13 Mar 2012 08:05:26 -0700 Subject: [PATCH 094/238] fix macro in conversions.cpp --- src/conversions.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/conversions.cpp b/src/conversions.cpp index 822b63263..d19272162 100644 --- a/src/conversions.cpp +++ b/src/conversions.cpp @@ -29,15 +29,15 @@ BOOST_SPIRIT_ASSERT_MATCH( \ boost::spirit::domain_::domain, name##_expr_type); \ BOOST_AUTO(name, boost::proto::deep_copy(expr)); \ - \ - \ + + namespace mapnik { namespace conversions { using namespace boost::spirit; -BOOST_SPIRIT_AUTO(qi, INTEGER, qi::int_); -BOOST_SPIRIT_AUTO(qi, FLOAT, qi::float_); -BOOST_SPIRIT_AUTO(qi, DOUBLE, qi::double_); +BOOST_SPIRIT_AUTO(qi, INTEGER, qi::int_) +BOOST_SPIRIT_AUTO(qi, FLOAT, qi::float_) +BOOST_SPIRIT_AUTO(qi, DOUBLE, qi::double_) bool string2int(const char * value, int & result) { From 9cbef6059593bfc23a72988c3aaa72bfd00d3b08 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Tue, 13 Mar 2012 08:22:34 -0700 Subject: [PATCH 095/238] pep8 formatting --- bindings/python/mapnik/__init__.py | 16 +-- bindings/python/mapnik/printing.py | 206 ++++++++++++++--------------- 2 files changed, 111 insertions(+), 111 deletions(-) diff --git a/bindings/python/mapnik/__init__.py b/bindings/python/mapnik/__init__.py index 7d7a93d2c..5bd751629 100644 --- a/bindings/python/mapnik/__init__.py +++ b/bindings/python/mapnik/__init__.py @@ -120,7 +120,7 @@ class _Coord(Coord,_injector): Returns the easting (x) and northing (y) as a coordinate pair. - + Example: Project the geographic coordinates of the city center of Stuttgart into the local map projection (GK Zone 3/DHDN, EPSG 31467) @@ -136,7 +136,7 @@ class _Coord(Coord,_injector): into the geographic space. The x component is considered to be the easting, the y component to be the northing. - + Returns the longitude (x) and latitude (y) as a coordinate pair. @@ -153,8 +153,8 @@ class _Coord(Coord,_injector): class _Box2d(Box2d,_injector): """ Represents a spatial envelope (i.e. bounding box). - - + + Following operators are defined for Box2d: Addition: @@ -285,12 +285,12 @@ def Datasource(**keywords): Create a Mapnik Datasource using a dictionary of parameters. Keywords must include: - + type='plugin_name' # e.g. type='gdal' - + See the convenience factory methods of each input plugin for details on additional required keyword arguments. - + """ return CreateDatasource(keywords) @@ -322,7 +322,7 @@ def PostGIS(**keywords): Required keyword arguments: dbname -- database name to connect to table -- table name or subselect query - + *Note: if using subselects for the 'table' value consider also passing the 'geometry_field' and 'srid' and 'extent_from_subquery' options and/or specifying the 'geometry_table' option. diff --git a/bindings/python/mapnik/printing.py b/bindings/python/mapnik/printing.py index 5e6456bd9..8c15d8a8c 100644 --- a/bindings/python/mapnik/printing.py +++ b/bindings/python/mapnik/printing.py @@ -43,7 +43,7 @@ except ImportError: class centering: """Style of centering to use with the map, the default is constrained - + none: map will be placed flush with the margin/box in the top left corner constrained: map will be centered on the most constrained axis (for a portrait page and a square map this will be horizontally) @@ -167,7 +167,7 @@ def sequence_scale(scale,scale_sequence): """Default scale helper, this rounds scale to a 'sensible' value""" factor = math.floor(math.log10(scale)) norm = scale/(10**factor) - + for s in scale_sequence: if norm <= s: return s*10**factor @@ -199,7 +199,7 @@ def deg_min_sec_scale(scale): return x else: return x - + def format_deg_min_sec(value): deg = math.floor(value) min = math.floor((value-deg)/(1.0/60)) @@ -219,12 +219,12 @@ def convert_pdf_pages_to_layers(filename,output_name=None,layer_names=(),reverse opens the given multipage PDF and converts each page to be a layer in a single page PDF layer_names should be a sequence of the user visible names of the layers, if not given or if shorter than num pages generic names will be given to the unnamed layers - + if output_name is not provided a temporary file will be used for the conversion which will then be copied back over the source file. - + requires pyPdf >= 1.13 to be available""" - + if not HAS_PYPDF: raise Exception("pyPdf Not available") @@ -235,13 +235,13 @@ def convert_pdf_pages_to_layers(filename,output_name=None,layer_names=(),reverse else: (outfd,outfilename) = tempfile.mkstemp(dir=os.path.dirname(filename)) outfile = os.fdopen(outfd,'wb') - + i = pyPdf.PdfFileReader(infile) o = pyPdf.PdfFileWriter() - + template_page_size = i.pages[0].mediaBox op = o.addBlankPage(width=template_page_size.getWidth(),height=template_page_size.getHeight()) - + contentkey = pyPdf.generic.NameObject('/Contents') resourcekey = pyPdf.generic.NameObject('/Resources') propertieskey = pyPdf.generic.NameObject('/Properties') @@ -249,7 +249,7 @@ def convert_pdf_pages_to_layers(filename,output_name=None,layer_names=(),reverse op[resourcekey] = pyPdf.generic.DictionaryObject() properties = pyPdf.generic.DictionaryObject() ocgs = pyPdf.generic.ArrayObject() - + for (i, p) in enumerate(i.pages): # first start an OCG for the layer ocgname = pyPdf.generic.NameObject('/oc%d' % i) @@ -262,9 +262,9 @@ def convert_pdf_pages_to_layers(filename,output_name=None,layer_names=(),reverse p[pyPdf.generic.NameObject('/Contents')].append(ocgend) else: p[pyPdf.generic.NameObject('/Contents')] = pyPdf.generic.ArrayObject((ocgstart,p['/Contents'],ocgend)) - + op.mergePage(p) - + ocg = pyPdf.generic.DictionaryObject() ocg[pyPdf.generic.NameObject('/Type')] = pyPdf.generic.NameObject('/OCG') if len(layer_names) > i: @@ -274,9 +274,9 @@ def convert_pdf_pages_to_layers(filename,output_name=None,layer_names=(),reverse indirect_ocg = o._addObject(ocg) properties[ocgname] = indirect_ocg ocgs.append(indirect_ocg) - + op[resourcekey][propertieskey] = o._addObject(properties) - + ocproperties = pyPdf.generic.DictionaryObject() ocproperties[pyPdf.generic.NameObject('/OCGs')] = ocgs defaultview = pyPdf.generic.DictionaryObject() @@ -289,16 +289,16 @@ def convert_pdf_pages_to_layers(filename,output_name=None,layer_names=(),reverse else: defaultview[pyPdf.generic.NameObject('/Order')] = pyPdf.generic.ArrayObject(reversed(ocgs)) defaultview[pyPdf.generic.NameObject('/OFF')] = pyPdf.generic.ArrayObject() - + ocproperties[pyPdf.generic.NameObject('/D')] = o._addObject(defaultview) - + o._root.getObject()[pyPdf.generic.NameObject('/OCProperties')] = o._addObject(ocproperties) - + o.write(outfile) - + outfile.close() infile.close() - + if not output_name: os.rename(outfilename, filename) @@ -318,7 +318,7 @@ class PDFPrinter: is_latlon=False, use_ocg_layers=False): """Creates a cairo surface and context to render a PDF with. - + pagesize: tuple of page size in meters, see predefined sizes in pagessizes dict (default a4) margin: page margin in meters (default 0.01) box: box within the page to render the map into (will not render over margin). This should be @@ -348,54 +348,54 @@ class PDFPrinter: self._centering = centering self._is_latlon = is_latlon self._use_ocg_layers = use_ocg_layers - + self._s = None self._layer_names = [] self._filename = None - + self.map_box = None self.scale = None - + # don't both to round the scale if they are not preserving the aspect ratio if not preserve_aspect: self._scale = any_scale - + if percent_box: self._box = Box2d(percent_box[0]*pagesize[0],percent_box[1]*pagesize[1], percent_box[2]*pagesize[0],percent_box[3]*pagesize[1]) if not HAS_PYCAIRO_MODULE: raise Exception("PDF rendering only available when pycairo is available") - + self.font_name = "DejaVu Sans" - + def finish(self): if self._s: self._s.finish() self._s = None - + if self._use_ocg_layers: convert_pdf_pages_to_layers(self._filename,layer_names=self._layer_names + ["Legend and Information"],reverse_all_but_last=True) - + def add_geospatial_pdf_header(self,m,filename,epsg=None,wkt=None): """ Postprocessing step to add geospatial PDF information to PDF file as per PDF standard 1.7 extension level 3 (also in draft PDF v2 standard at time of writing) - + one of either the epsg code or wkt text for the projection must be provided - + Should be called *after* the page has had .finish() called""" if HAS_PYPDF and (epsg or wkt): infile=file(filename,'rb') (outfd,outfilename) = tempfile.mkstemp(dir=os.path.dirname(filename)) outfile = os.fdopen(outfd,'wb') - + i=pyPdf.PdfFileReader(infile) o=pyPdf.PdfFileWriter() - + # preserve OCProperties at document root if we have one if i.trailer['/Root'].has_key(pyPdf.generic.NameObject('/OCProperties')): o._root.getObject()[pyPdf.generic.NameObject('/OCProperties')] = i.trailer['/Root'].getObject()[pyPdf.generic.NameObject('/OCProperties')] - + for p in i.pages: gcs = pyPdf.generic.DictionaryObject() gcs[pyPdf.generic.NameObject('/Type')]=pyPdf.generic.NameObject('/PROJCS') @@ -403,7 +403,7 @@ class PDFPrinter: gcs[pyPdf.generic.NameObject('/EPSG')]=pyPdf.generic.NumberObject(int(epsg)) if wkt: gcs[pyPdf.generic.NameObject('/WKT')]=pyPdf.generic.TextStringObject(wkt) - + measure = pyPdf.generic.DictionaryObject() measure[pyPdf.generic.NameObject('/Type')]=pyPdf.generic.NameObject('/Measure') measure[pyPdf.generic.NameObject('/Subtype')]=pyPdf.generic.NameObject('/GEO') @@ -414,7 +414,7 @@ class PDFPrinter: measure[pyPdf.generic.NameObject('/Bounds')]=bounds measure[pyPdf.generic.NameObject('/LPTS')]=bounds gpts=pyPdf.generic.ArrayObject() - + proj=Projection(m.srs) env=m.envelope() for x in ((env.minx, env.miny), (env.minx, env.maxy), (env.maxx, env.maxy), (env.maxx, env.miny)): @@ -423,31 +423,31 @@ class PDFPrinter: gpts.append(pyPdf.generic.FloatObject(str(latlon_corner.y))) gpts.append(pyPdf.generic.FloatObject(str(latlon_corner.x))) measure[pyPdf.generic.NameObject('/GPTS')]=gpts - + vp=pyPdf.generic.DictionaryObject() vp[pyPdf.generic.NameObject('/Type')]=pyPdf.generic.NameObject('/Viewport') bbox=pyPdf.generic.ArrayObject() - + for x in self.map_box: bbox.append(pyPdf.generic.FloatObject(str(x))) vp[pyPdf.generic.NameObject('/BBox')]=bbox vp[pyPdf.generic.NameObject('/Measure')]=measure - + vpa = pyPdf.generic.ArrayObject() vpa.append(vp) p[pyPdf.generic.NameObject('/VP')]=vpa o.addPage(p) - + o.write(outfile) infile=None outfile.close() os.rename(outfilename,filename) - - + + def get_context(self): """allow access so that extra 'bits' can be rendered to the page directly""" return cairo.Context(self._s) - + def get_width(self): return self._pagesize[0] @@ -456,7 +456,7 @@ class PDFPrinter: def get_margin(self): return self._margin - + def write_text(self,ctx,text,box_width=None,size=10, fill_color=(0.0, 0.0, 0.0), alignment=None): if HAS_PANGOCAIRO_MODULE: (attr,t,accel) = pango.parse_markup(text) @@ -474,7 +474,7 @@ class PDFPrinter: pctx.set_source_rgb(*fill_color) pctx.show_layout(l) return l.get_pixel_extents()[0] - + else: ctx.rel_move_to(0,size) ctx.select_font_face(self.font_name, cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) @@ -489,18 +489,18 @@ class PDFPrinter: elif HAS_PYCAIRO_MODULE: return cairo.Context(self._s) return None - + def _get_render_area(self): """return a bounding box with the area of the page we are allowed to render out map to in page coordinates (i.e. meters) """ # take off our page margins render_area = Box2d(self._margin,self._margin,self._pagesize[0]-self._margin,self._pagesize[1]-self._margin) - + #then if user specified a box to render get intersection with that if self._box: return render_area.intersect(self._box) - + return render_area def _get_render_area_size(self): @@ -513,7 +513,7 @@ class PDFPrinter: available_area = self._get_render_area_size() map_aspect = m.envelope().width()/m.envelope().height() page_aspect = available_area[0]/available_area[1] - + return map_aspect > page_aspect def _get_meta_info_corner(self,render_size,m): @@ -526,7 +526,7 @@ class PDFPrinter: else: x += render_size[0]+0.005 y = self._margin - + return (x,y) def _get_render_corner(self,render_size,m): @@ -535,9 +535,9 @@ class PDFPrinter: x=available_area[0] y=available_area[1] - + h_is_contrained = self._is_h_contrained(m) - + if (self._centering == centering.both or self._centering == centering.horizontal or (self._centering == centering.constrained and h_is_contrained) or @@ -550,26 +550,26 @@ class PDFPrinter: (self._centering == centering.unconstrained and h_is_contrained)): y+=(available_area.height()-render_size[1])/2 return (x,y) - + def _get_map_pixel_size(self, width_page_m, height_page_m): """for a given map size in paper coordinates return a tuple of the map 'pixel' size we should create at the defined resolution""" return (int(m2px(width_page_m,self._resolution)), int(m2px(height_page_m,self._resolution))) - + def render_map(self,m, filename): """Render the given map to filename""" - + # store this for later so we can post process the PDF self._filename = filename - + # work out the best scale to render out map at given the available space (eff_width,eff_height) = self._get_render_area_size() map_aspect = m.envelope().width()/m.envelope().height() page_aspect = eff_width/eff_height - + scalex=m.envelope().width()/eff_width scaley=m.envelope().height()/eff_height - + scale=max(scalex,scaley) rounded_mapscale=self._scale(scale) @@ -581,26 +581,26 @@ class PDFPrinter: maph=mapw*(1/map_aspect) else: mapw=maph*map_aspect - + # set the map size so that raster elements render at the correct resolution m.resize(*self._get_map_pixel_size(mapw,maph)) # calculate the translation for the map starting point (tx,ty) = self._get_render_corner((mapw,maph),m) - + # create our cairo surface and context and then render the map into it self._s = cairo.PDFSurface(filename, m2pt(self._pagesize[0]),m2pt(self._pagesize[1])) ctx=cairo.Context(self._s) - + for l in m.layers: # extract the layer names for naming layers if we use OCG self._layer_names.append(l.name) - + layer_map = Map(m.width,m.height,m.srs) layer_map.layers.append(l) for s in l.styles: layer_map.append_style(s,m.find_style(s)) layer_map.zoom_to_box(m.envelope()) - + def render_map(): ctx.save() ctx.translate(m2pt(tx),m2pt(ty)) @@ -608,7 +608,7 @@ class PDFPrinter: ctx.scale(72.0/self._resolution,72.0/self._resolution) render(layer_map, ctx) ctx.restore() - + # antimeridian render_map() if self._is_latlon and (m.envelope().minx < -180 or m.envelope().maxx > 180): @@ -621,10 +621,10 @@ class PDFPrinter: render_map() # restore the original env m.zoom_to_box(old_env) - + if self._use_ocg_layers: self._s.show_page() - + self.scale = rounded_mapscale self.map_box = Box2d(tx,ty,tx+mapw,ty+maph) @@ -640,7 +640,7 @@ class PDFPrinter: if p2.inverse(m.envelope().center()).y > latlon_bounds.maxy: latlon_bounds = Box2d(latlon_bounds.miny,latlon_bounds.maxy,latlon_bounds.maxx,latlon_bounds.miny+360) - + latlon_mapwidth = latlon_bounds.width() # render an extra 20% so we generally won't miss the ends of lines latlon_buffer = 0.2*latlon_mapwidth @@ -649,7 +649,7 @@ class PDFPrinter: else: latlon_divsize = deg_min_sec_scale(latlon_mapwidth/7.0) latlon_interpsize = latlon_mapwidth/m.width - + self._render_lat_lon_axis(m,p2,latlon_bounds.minx,latlon_bounds.maxx,latlon_bounds.miny,latlon_bounds.maxy,latlon_buffer,latlon_interpsize,latlon_divsize,dec_degrees,True) self._render_lat_lon_axis(m,p2,latlon_bounds.miny,latlon_bounds.maxy,latlon_bounds.minx,latlon_bounds.maxx,latlon_buffer,latlon_interpsize,latlon_divsize,dec_degrees,False) @@ -658,21 +658,21 @@ class PDFPrinter: ctx.set_source_rgb(1,0,0) ctx.set_line_width(1) latlon_labelsize = 6 - + ctx.translate(m2pt(self.map_box.minx),m2pt(self.map_box.miny)) ctx.rectangle(0,0,m2pt(self.map_box.width()),m2pt(self.map_box.height())) ctx.clip() - + ctx.select_font_face("DejaVu", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) ctx.set_font_size(latlon_labelsize) - + box_top = self.map_box.height() if not is_x_axis: ctx.translate(m2pt(self.map_box.width()/2),m2pt(self.map_box.height()/2)) ctx.rotate(-math.pi/2) ctx.translate(-m2pt(self.map_box.height()/2),-m2pt(self.map_box.width()/2)) box_top = self.map_box.width() - + for xvalue in round_grid_generator(x1 - latlon_buffer,x2 + latlon_buffer,latlon_divsize): yvalue = y1 - latlon_buffer start_cross = None @@ -693,12 +693,12 @@ class PDFPrinter: ctx.move_to(start.x,start.y) ctx.line_to(end.x,end.y) ctx.stroke() - + if cmp(start.y, 0) != cmp(end.y,0): start_cross = end.x if cmp(start.y,m2pt(self.map_box.height())) != cmp(end.y, m2pt(self.map_box.height())): end_cross = end.x - + if dec_degrees: line_text = "%g" % (xvalue) else: @@ -712,19 +712,19 @@ class PDFPrinter: def render_on_map_scale(self,m): (div_size,page_div_size) = self._get_sensible_scalebar_size(m) - + first_value_x = (math.floor(m.envelope().minx / div_size) + 1) * div_size first_value_x_percent = (first_value_x-m.envelope().minx)/m.envelope().width() self._render_scale_axis(first_value_x,first_value_x_percent,self.map_box.minx,self.map_box.maxx,page_div_size,div_size,self.map_box.miny,self.map_box.maxy,True) - + first_value_y = (math.floor(m.envelope().miny / div_size) + 1) * div_size first_value_y_percent = (first_value_y-m.envelope().miny)/m.envelope().height() self._render_scale_axis(first_value_y,first_value_y_percent,self.map_box.miny,self.map_box.maxy,page_div_size,div_size,self.map_box.minx,self.map_box.maxx,False) - + if self._use_ocg_layers: self._s.show_page() self._layer_names.append("Coordinate Grid Overlay") - + def _get_sensible_scalebar_size(self,m,width=-1): # aim for about 8 divisions across the map # also make sure we can fit the bar with in page area width if specified @@ -744,7 +744,7 @@ class PDFPrinter: ctx.set_source_rgb(*stroke_color) ctx.rectangle(x,y,w,h) ctx.stroke() - + if text: ctx.move_to(x+1,y) self.write_text(ctx,text,fill_color=[1-z for z in fill_color],size=h-2) @@ -758,14 +758,14 @@ class PDFPrinter: label_value = first-div_size if self._is_latlon and label_value < -180: label_value += 360 - + ctx=cairo.Context(self._s) - + if not is_x_axis: ctx.translate(m2pt(self.map_box.center().x),m2pt(self.map_box.center().y)) ctx.rotate(-math.pi/2) ctx.translate(-m2pt(self.map_box.center().y),-m2pt(self.map_box.center().x)) - + while value < end: ctx.move_to(m2pt(value),m2pt(boundary_start)) ctx.line_to(m2pt(value),m2pt(boundary_end)) @@ -775,7 +775,7 @@ class PDFPrinter: for bar in (m2pt(boundary_start)-border_size,m2pt(boundary_end)): self._render_box(ctx,m2pt(prev),bar,m2pt(value-prev),border_size,text,fill_color=fill) - + prev = value value+=page_div_size fill = [1-z for z in fill] @@ -787,18 +787,18 @@ class PDFPrinter: for bar in (m2pt(boundary_start)-border_size,m2pt(boundary_end)): self._render_box(ctx,m2pt(prev),bar,m2pt(end-prev),border_size,fill_color=fill) - + def render_scale(self,m,ctx=None,width=0.05): """ m: map to render scale for ctx: A cairo context to render the scale to. If this is None (the default) then automatically create a context and choose the best location for the scale bar. width: Width of area available to render scale bar in (in m) - + will return the size of the rendered scale block in pts """ - + (w,h) = (0,0) - + # don't render scale if we are lat lon # dont report scale if we have warped the aspect ratio if self._preserve_aspect and not self._is_latlon: @@ -808,15 +808,15 @@ class PDFPrinter: ctx=cairo.Context(self._s) (tx,ty) = self._get_meta_info_corner((self.map_box.width(),self.map_box.height()),m) ctx.translate(tx,ty) - + (div_size,page_div_size) = self._get_sensible_scalebar_size(m, width/box_count) - + div_unit = "m" if div_size > 1000: div_size /= 1000 div_unit = "km" - + text = "0%s" % div_unit ctx.save() if width > 0: @@ -846,7 +846,7 @@ class PDFPrinter: text_ext=self.write_text(ctx,"Scale 1:%d" % self.scale,box_width=box_width,size=font_size, alignment=alignment) h+=text_ext[3]+2 - + return (w,h) def render_legend(self,m, page_break=False, ctx=None, collumns=1,width=None, height=None, item_per_rule=False, attribution={}, legend_item_box_size=(0.015,0.0075)): @@ -858,7 +858,7 @@ class PDFPrinter: collumns: number of collumns available in legend box attribution: additional text that will be rendered in gray under the layer name. keyed by layer name legend_item_box_size: two tuple with width and height of legend item box size in meters - + will return the size of the rendered block in pts """ @@ -879,7 +879,7 @@ class PDFPrinter: else: cwidth = None current_collumn = 0 - + processed_layers = [] for l in reversed(m.layers): have_layer_header = False @@ -888,7 +888,7 @@ class PDFPrinter: if layer_title in processed_layers: continue processed_layers.append(layer_title) - + # check through the features to find which combinations of styles are active # for each unique combination add a legend entry for f in l.datasource.all_features(): @@ -913,20 +913,20 @@ class PDFPrinter: active_rules = tuple(active_rules) if added_styles.has_key(active_rules): continue - + added_styles[active_rules] = (f,rule_text) if not item_per_rule: break else: added_styles[l] = (None,None) - + legend_items = added_styles.keys() legend_items.sort() for li in legend_items: if True: (f,rule_text) = added_styles[li] - - + + legend_map_size = (int(m2pt(legend_item_box_size[0])),int(m2pt(legend_item_box_size[1]))) lemap=Map(legend_map_size[0],legend_map_size[1],srs=m.srs) if m.background: @@ -967,11 +967,11 @@ class PDFPrinter: for s in l.styles: lelayer.styles.append(s) lemap.layers.append(lelayer) - + if f is None or f.envelope().width() != 0: lemap.zoom_all() lemap.zoom(1.1) - + item_size = legend_map_size[1] if not have_layer_header: item_size += 8 @@ -998,7 +998,7 @@ class PDFPrinter: ctx.save() render(lemap, ctx) ctx.restore() - + ctx.rectangle(0,0,*legend_map_size) ctx.set_source_rgb(0.5,0.5,0.5) ctx.set_line_width(1) @@ -1017,12 +1017,12 @@ class PDFPrinter: if attribution.has_key(layer_title): e=self.write_text(ctx, attribution[layer_title], m2pt(cwidth-legend_item_box_size[0]-0.005), 6, fill_color=(0.5,0.5,0.5)) legend_text_size += e[3] - + if legend_text_size > legend_entry_size: legend_entry_size=legend_text_size - + y+=legend_entry_size +2 if y > h: h = y return (w,h) - + From 0c537ed9eef6d8e59172248c6c37549dc55a3f04 Mon Sep 17 00:00:00 2001 From: Artem Pavlenko Date: Wed, 14 Mar 2012 14:55:14 +0000 Subject: [PATCH 096/238] + add: #include mapnik/config_error.hpp --- demo/viewer/mapwidget.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/demo/viewer/mapwidget.cpp b/demo/viewer/mapwidget.cpp index dadaba84c..103b4d9cd 100644 --- a/demo/viewer/mapwidget.cpp +++ b/demo/viewer/mapwidget.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include "mapwidget.hpp" #include "info_dialog.hpp" @@ -487,7 +488,7 @@ void MapWidget::updateMap() } catch (mapnik::config_error & ex) { - std::cerr << ex.what() << std::endl; + std::cerr << ex.what() << std::endl; } catch (...) { From 87b22c29b208a1414fffef83fa7a2cb0901cd27e Mon Sep 17 00:00:00 2001 From: Artem Pavlenko Date: Wed, 14 Mar 2012 15:01:31 +0000 Subject: [PATCH 097/238] + add optional polygon smoothing ( 0.0 < smooth < 1.0 ) using agg_conv_poly1_curve --- include/mapnik/polygon_symbolizer.hpp | 6 ++++-- src/agg/process_polygon_symbolizer.cpp | 29 ++++++++++++++++++++------ src/load_map.cpp | 5 ++++- src/polygon_symbolizer.cpp | 16 ++++++++++++-- src/save_map.cpp | 4 ++++ 5 files changed, 49 insertions(+), 11 deletions(-) diff --git a/include/mapnik/polygon_symbolizer.hpp b/include/mapnik/polygon_symbolizer.hpp index a4f8706d5..e7a2a85c7 100644 --- a/include/mapnik/polygon_symbolizer.hpp +++ b/include/mapnik/polygon_symbolizer.hpp @@ -35,7 +35,7 @@ namespace mapnik struct MAPNIK_DECL polygon_symbolizer : public symbolizer_base { polygon_symbolizer(); - polygon_symbolizer(color const& fill); + explicit polygon_symbolizer(color const& fill); color const& get_fill() const; void set_fill(color const& fill); void set_opacity(double opacity); @@ -44,12 +44,14 @@ struct MAPNIK_DECL polygon_symbolizer : public symbolizer_base double get_gamma() const; void set_gamma_method(gamma_method_e gamma_method); gamma_method_e get_gamma_method() const; - + void set_smooth(double smooth); + double smooth() const; private: color fill_; double opacity_; double gamma_; gamma_method_e gamma_method_; + double smooth_; }; } diff --git a/src/agg/process_polygon_symbolizer.cpp b/src/agg/process_polygon_symbolizer.cpp index a7187a444..bcd1a7431 100644 --- a/src/agg/process_polygon_symbolizer.cpp +++ b/src/agg/process_polygon_symbolizer.cpp @@ -35,6 +35,7 @@ // for polygon_symbolizer #include "agg_renderer_scanline.h" #include "agg_conv_clip_polygon.h" +#include "agg_conv_smooth_poly1.h" // stl #include @@ -45,11 +46,10 @@ void agg_renderer::process(polygon_symbolizer const& sym, mapnik::feature_ptr const& feature, proj_transform const& prj_trans) { - typedef agg::conv_clip_polygon clipped_geometry_type; - typedef coord_transform2 path_type; typedef agg::renderer_base ren_base; typedef agg::renderer_scanline_aa_solid renderer; + box2d query_extent = query_extent_ * 1.0; color const& fill_ = sym.get_fill(); agg::scanline_u8 sl; @@ -92,10 +92,27 @@ void agg_renderer::process(polygon_symbolizer const& sym, geometry_type & geom=feature->get_geometry(i); if (geom.num_points() > 2) { - clipped_geometry_type clipped(geom); - clipped.clip_box(query_extent_.minx(),query_extent_.miny(),query_extent_.maxx(),query_extent_.maxy()); - path_type path(t_,clipped,prj_trans); - ras_ptr->add_path(path); + if (sym.smooth() > 0.0) + { + typedef agg::conv_smooth_poly1_curve smooth_type; + typedef agg::conv_clip_polygon clipped_geometry_type; + typedef coord_transform2 path_type; + smooth_type smooth(geom); + smooth.smooth_value(sym.smooth()); + clipped_geometry_type clipped(smooth); + clipped.clip_box(query_extent.minx(),query_extent.miny(),query_extent.maxx(),query_extent.maxy()); + path_type path(t_,clipped,prj_trans); + ras_ptr->add_path(path); + } + else + { + typedef agg::conv_clip_polygon clipped_geometry_type; + typedef coord_transform2 path_type; + clipped_geometry_type clipped(geom); + clipped.clip_box(query_extent.minx(),query_extent.miny(),query_extent.maxx(),query_extent.maxy()); + path_type path(t_,clipped,prj_trans); + ras_ptr->add_path(path); + } //if (writer.first) writer.first->add_polygon(path, *feature, t_, writer.second); } } diff --git a/src/load_map.cpp b/src/load_map.cpp index 0b71ba838..92787c83e 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -1295,7 +1295,10 @@ void map_parser::parse_polygon_symbolizer( rule & rule, xml_node const & sym ) // gamma method optional gamma_method = sym.get_opt_attr("gamma-method"); if (gamma_method) poly_sym.set_gamma_method(*gamma_method); - + // smooth value + optional smooth = sym.get_opt_attr("smooth"); + if (smooth) poly_sym.set_smooth(*smooth); + parse_metawriter_in_symbolizer(poly_sym, sym); rule.append(poly_sym); } diff --git a/src/polygon_symbolizer.cpp b/src/polygon_symbolizer.cpp index 7009e586c..7c783a12d 100644 --- a/src/polygon_symbolizer.cpp +++ b/src/polygon_symbolizer.cpp @@ -32,14 +32,16 @@ polygon_symbolizer::polygon_symbolizer() fill_(color(128,128,128)), opacity_(1.0), gamma_(1.0), - gamma_method_(GAMMA_POWER) {} + gamma_method_(GAMMA_POWER), + smooth_(0.0) {} polygon_symbolizer::polygon_symbolizer(color const& fill) : symbolizer_base(), fill_(fill), opacity_(1.0), gamma_(1.0), - gamma_method_(GAMMA_POWER) {} + gamma_method_(GAMMA_POWER), + smooth_(0.0) {} color const& polygon_symbolizer::get_fill() const { @@ -81,4 +83,14 @@ gamma_method_e polygon_symbolizer::get_gamma_method() const return gamma_method_; } +void polygon_symbolizer::set_smooth(double smooth) +{ + smooth_ = smooth; +} + +double polygon_symbolizer::smooth() const +{ + return smooth_; +} + } diff --git a/src/save_map.cpp b/src/save_map.cpp index cec1faa75..d81508ad3 100644 --- a/src/save_map.cpp +++ b/src/save_map.cpp @@ -125,6 +125,10 @@ public: { set_attr( sym_node, "gamma-method", sym.get_gamma_method() ); } + if ( sym.smooth() != dfl.smooth() || explicit_defaults_ ) + { + set_attr( sym_node, "smooth", sym.smooth() ); + } add_metawriter_attributes(sym_node, sym); } From 108b99725cc9af484d0695e02be94895e7dbb4b2 Mon Sep 17 00:00:00 2001 From: Artem Pavlenko Date: Wed, 14 Mar 2012 15:42:22 +0000 Subject: [PATCH 098/238] + refactor gamma setting logic to avoid code duplication ( agg_helpers.hpp ) --- include/mapnik/agg_helpers.hpp | 57 +++++++++++++++++++ src/agg/process_line_symbolizer.cpp | 37 +++++------- .../process_polygon_pattern_symbolizer.cpp | 24 +------- src/agg/process_polygon_symbolizer.cpp | 23 +------- 4 files changed, 76 insertions(+), 65 deletions(-) create mode 100644 include/mapnik/agg_helpers.hpp diff --git a/include/mapnik/agg_helpers.hpp b/include/mapnik/agg_helpers.hpp new file mode 100644 index 000000000..5b221c9aa --- /dev/null +++ b/include/mapnik/agg_helpers.hpp @@ -0,0 +1,57 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2012 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 + * + *****************************************************************************/ + +#ifndef MAPNIK_AGG_HELPERS_HPP +#define MAPNIK_AGG_HELPERS_HPP + +#include "agg_gamma_functions.h" + +namespace mapnik { + +template +void set_gamma_method(T0 const& obj, T1 & ras_ptr) +{ + switch (obj.get_gamma_method()) + { + case GAMMA_POWER: + ras_ptr->gamma(agg::gamma_power(obj.get_gamma())); + break; + case GAMMA_LINEAR: + ras_ptr->gamma(agg::gamma_linear(0.0, obj.get_gamma())); + break; + case GAMMA_NONE: + ras_ptr->gamma(agg::gamma_none()); + break; + case GAMMA_THRESHOLD: + ras_ptr->gamma(agg::gamma_threshold(obj.get_gamma())); + break; + case GAMMA_MULTIPLY: + ras_ptr->gamma(agg::gamma_multiply(obj.get_gamma())); + break; + default: + ras_ptr->gamma(agg::gamma_power(obj.get_gamma())); + } +} + +} + +#endif //MAPNIK_AGG_HELPERS_HPP diff --git a/src/agg/process_line_symbolizer.cpp b/src/agg/process_line_symbolizer.cpp index 193e53f43..b4f37be67 100644 --- a/src/agg/process_line_symbolizer.cpp +++ b/src/agg/process_line_symbolizer.cpp @@ -23,6 +23,7 @@ // mapnik #include +#include #include #include @@ -71,7 +72,6 @@ void agg_renderer::process(line_symbolizer const& sym, typedef agg::rasterizer_outline_aa rasterizer_type; agg::line_profile_aa profile; - //agg::line_profile_aa profile(stroke_.get_width() * scale_factor_, agg::gamma_none()); profile.width(stroke_.get_width() * scale_factor_); ren_base base_ren(pixf); renderer_type ren(base_ren, profile); @@ -102,26 +102,10 @@ void agg_renderer::process(line_symbolizer const& sym, ren_base renb(pixf); renderer ren(renb); ras_ptr->reset(); - switch (stroke_.get_gamma_method()) - { - case GAMMA_POWER: - ras_ptr->gamma(agg::gamma_power(stroke_.get_gamma())); - break; - case GAMMA_LINEAR: - ras_ptr->gamma(agg::gamma_linear(0.0, stroke_.get_gamma())); - break; - case GAMMA_NONE: - ras_ptr->gamma(agg::gamma_none()); - break; - case GAMMA_THRESHOLD: - ras_ptr->gamma(agg::gamma_threshold(stroke_.get_gamma())); - break; - case GAMMA_MULTIPLY: - ras_ptr->gamma(agg::gamma_multiply(stroke_.get_gamma())); - break; - default: - ras_ptr->gamma(agg::gamma_power(stroke_.get_gamma())); - } + + set_gamma_method(stroke_, ras_ptr); + + //metawriter_with_properties writer = sym.get_metawriter(); for (unsigned i=0;inum_geometries();++i) @@ -129,12 +113,13 @@ void agg_renderer::process(line_symbolizer const& sym, geometry_type & geom = feature->get_geometry(i); if (geom.num_points() > 1) { - clipped_geometry_type clipped(geom); - clipped.clip_box(ext.minx(),ext.miny(),ext.maxx(),ext.maxy()); - path_type path(t_,clipped,prj_trans); if (stroke_.has_dash()) { + clipped_geometry_type clipped(geom); + clipped.clip_box(ext.minx(),ext.miny(),ext.maxx(),ext.maxy()); + path_type path(t_,clipped,prj_trans); + agg::conv_dash dash(path); dash_array const& d = stroke_.get_dash_array(); dash_array::const_iterator itr = d.begin(); @@ -172,6 +157,10 @@ void agg_renderer::process(line_symbolizer const& sym, } else { + clipped_geometry_type clipped(geom); + clipped.clip_box(ext.minx(),ext.miny(),ext.maxx(),ext.maxy()); + path_type path(t_,clipped,prj_trans); + agg::conv_stroke stroke(path); line_join_e join=stroke_.get_line_join(); if ( join == MITER_JOIN) diff --git a/src/agg/process_polygon_pattern_symbolizer.cpp b/src/agg/process_polygon_pattern_symbolizer.cpp index 670fea9b6..1d6fbde1f 100644 --- a/src/agg/process_polygon_pattern_symbolizer.cpp +++ b/src/agg/process_polygon_pattern_symbolizer.cpp @@ -23,6 +23,7 @@ // mapnik #include +#include #include #include #include @@ -72,27 +73,8 @@ void agg_renderer::process(polygon_pattern_symbolizer const& sym, agg::scanline_u8 sl; ras_ptr->reset(); - switch (sym.get_gamma_method()) - { - case GAMMA_POWER: - ras_ptr->gamma(agg::gamma_power(sym.get_gamma())); - break; - case GAMMA_LINEAR: - ras_ptr->gamma(agg::gamma_linear(0.0, sym.get_gamma())); - break; - case GAMMA_NONE: - ras_ptr->gamma(agg::gamma_none()); - break; - case GAMMA_THRESHOLD: - ras_ptr->gamma(agg::gamma_threshold(sym.get_gamma())); - break; - case GAMMA_MULTIPLY: - ras_ptr->gamma(agg::gamma_multiply(sym.get_gamma())); - break; - default: - ras_ptr->gamma(agg::gamma_power(sym.get_gamma())); - } - + set_gamma_method(sym,ras_ptr); + std::string filename = path_processor_type::evaluate( *sym.get_filename(), *feature); boost::optional marker; if ( !filename.empty() ) diff --git a/src/agg/process_polygon_symbolizer.cpp b/src/agg/process_polygon_symbolizer.cpp index bcd1a7431..5baa68642 100644 --- a/src/agg/process_polygon_symbolizer.cpp +++ b/src/agg/process_polygon_symbolizer.cpp @@ -23,6 +23,7 @@ // mapnik #include +#include #include #include @@ -65,27 +66,9 @@ void agg_renderer::process(polygon_symbolizer const& sym, renderer ren(renb); ras_ptr->reset(); - switch (sym.get_gamma_method()) - { - case GAMMA_POWER: - ras_ptr->gamma(agg::gamma_power(sym.get_gamma())); - break; - case GAMMA_LINEAR: - ras_ptr->gamma(agg::gamma_linear(0.0, sym.get_gamma())); - break; - case GAMMA_NONE: - ras_ptr->gamma(agg::gamma_none()); - break; - case GAMMA_THRESHOLD: - ras_ptr->gamma(agg::gamma_threshold(sym.get_gamma())); - break; - case GAMMA_MULTIPLY: - ras_ptr->gamma(agg::gamma_multiply(sym.get_gamma())); - break; - default: - ras_ptr->gamma(agg::gamma_power(sym.get_gamma())); - } + set_gamma_method(sym,ras_ptr); + //metawriter_with_properties writer = sym.get_metawriter(); for (unsigned i=0;inum_geometries();++i) { From 07d4e45521ab17ec6cbba21ca0ce065de3187b9c Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 14 Mar 2012 08:56:07 -0700 Subject: [PATCH 099/238] fix compile with boost 1.42-1.44 after 2b68cea0a10 --- include/mapnik/xml_tree.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/mapnik/xml_tree.hpp b/include/mapnik/xml_tree.hpp index a27a7e78a..27893d262 100644 --- a/include/mapnik/xml_tree.hpp +++ b/include/mapnik/xml_tree.hpp @@ -25,7 +25,15 @@ //mapnik #include #include + +// boost +#include + +#if BOOST_VERSION >= 104500 #include +#else +#include +#endif //stl #include From 81ab02cc723069533f306ef63b6d747b8d6cadbc Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 14 Mar 2012 08:56:59 -0700 Subject: [PATCH 100/238] fix failing test 'bgcolor_broken.xml' when compiled against boost 1.42 --- src/color.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/color.cpp b/src/color.cpp index 480b6b9d2..80464ccbb 100644 --- a/src/color.cpp +++ b/src/color.cpp @@ -166,6 +166,7 @@ bool color_factory::parse_from_string(color & c, std::string const& css_color, c.set_alpha(css_.a); return true; } + return false; #endif } From 8e0ed96756cfbed152ca271296c5de7770d91b73 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 14 Mar 2012 09:12:26 -0700 Subject: [PATCH 101/238] fix whitespace --- include/mapnik/agg_helpers.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/mapnik/agg_helpers.hpp b/include/mapnik/agg_helpers.hpp index 5b221c9aa..2113db113 100644 --- a/include/mapnik/agg_helpers.hpp +++ b/include/mapnik/agg_helpers.hpp @@ -50,8 +50,8 @@ void set_gamma_method(T0 const& obj, T1 & ras_ptr) default: ras_ptr->gamma(agg::gamma_power(obj.get_gamma())); } -} +} } -#endif //MAPNIK_AGG_HELPERS_HPP +#endif //MAPNIK_AGG_HELPERS_HPP From f1a088762851f9fbd95a736e90c980b3f6680c21 Mon Sep 17 00:00:00 2001 From: Artem Pavlenko Date: Wed, 14 Mar 2012 16:12:36 +0000 Subject: [PATCH 102/238] + apply smooth converter after clipping and transformations + inflate query_extent --- src/agg/process_polygon_symbolizer.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/agg/process_polygon_symbolizer.cpp b/src/agg/process_polygon_symbolizer.cpp index 5baa68642..2b532fb9f 100644 --- a/src/agg/process_polygon_symbolizer.cpp +++ b/src/agg/process_polygon_symbolizer.cpp @@ -50,7 +50,6 @@ void agg_renderer::process(polygon_symbolizer const& sym, typedef agg::renderer_base ren_base; typedef agg::renderer_scanline_aa_solid renderer; - box2d query_extent = query_extent_ * 1.0; color const& fill_ = sym.get_fill(); agg::scanline_u8 sl; @@ -70,6 +69,7 @@ void agg_renderer::process(polygon_symbolizer const& sym, set_gamma_method(sym,ras_ptr); //metawriter_with_properties writer = sym.get_metawriter(); + box2d inflated_extent = query_extent_ * 1.1; for (unsigned i=0;inum_geometries();++i) { geometry_type & geom=feature->get_geometry(i); @@ -77,22 +77,22 @@ void agg_renderer::process(polygon_symbolizer const& sym, { if (sym.smooth() > 0.0) { - typedef agg::conv_smooth_poly1_curve smooth_type; - typedef agg::conv_clip_polygon clipped_geometry_type; + typedef agg::conv_clip_polygon clipped_geometry_type; typedef coord_transform2 path_type; - smooth_type smooth(geom); - smooth.smooth_value(sym.smooth()); - clipped_geometry_type clipped(smooth); - clipped.clip_box(query_extent.minx(),query_extent.miny(),query_extent.maxx(),query_extent.maxy()); + typedef agg::conv_smooth_poly1_curve smooth_type; + clipped_geometry_type clipped(geom); + clipped.clip_box(inflated_extent.minx(),inflated_extent.miny(),inflated_extent.maxx(),inflated_extent.maxy()); path_type path(t_,clipped,prj_trans); - ras_ptr->add_path(path); + smooth_type smooth(path); + smooth.smooth_value(sym.smooth()); + ras_ptr->add_path(smooth); } else { typedef agg::conv_clip_polygon clipped_geometry_type; typedef coord_transform2 path_type; clipped_geometry_type clipped(geom); - clipped.clip_box(query_extent.minx(),query_extent.miny(),query_extent.maxx(),query_extent.maxy()); + clipped.clip_box(query_extent_.minx(),query_extent_.miny(),query_extent_.maxx(),query_extent_.maxy()); path_type path(t_,clipped,prj_trans); ras_ptr->add_path(path); } From 72baf5924729d7425ac7fb06e05d767aeef386e4 Mon Sep 17 00:00:00 2001 From: Artem Pavlenko Date: Wed, 14 Mar 2012 16:51:07 +0000 Subject: [PATCH 103/238] + add smooth property + update help strings --- bindings/python/mapnik_polygon_symbolizer.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bindings/python/mapnik_polygon_symbolizer.cpp b/bindings/python/mapnik_polygon_symbolizer.cpp index 4b4fb1b1a..dd809ac5d 100644 --- a/bindings/python/mapnik_polygon_symbolizer.cpp +++ b/bindings/python/mapnik_polygon_symbolizer.cpp @@ -84,7 +84,11 @@ void export_polygon_symbolizer() .add_property("gamma_method", &polygon_symbolizer::get_gamma_method, &polygon_symbolizer::set_gamma_method, - "Set/get the gamma correction method of the polygon") + "gamma correction method") + .add_property("smooth", + &polygon_symbolizer::smooth, + &polygon_symbolizer::set_smooth, + "smooth value (0..1.0)") ; } From d85556840027aea37df9facd9cd0ba564d6b011f Mon Sep 17 00:00:00 2001 From: Artem Pavlenko Date: Wed, 14 Mar 2012 17:06:23 +0000 Subject: [PATCH 104/238] + use smooth=1.0 for water polygons --- demo/python/rundemo.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/demo/python/rundemo.py b/demo/python/rundemo.py index 0fda7526d..bd9639a4b 100644 --- a/demo/python/rundemo.py +++ b/demo/python/rundemo.py @@ -138,7 +138,9 @@ qcdrain_lyr.datasource = mapnik.Shapefile(file='../data/qcdrainage') qcdrain_style = mapnik.Style() qcdrain_rule = mapnik.Rule() qcdrain_rule.filter = mapnik.Expression('[HYC] = 8') -qcdrain_rule.symbols.append(mapnik.PolygonSymbolizer(mapnik.Color(153, 204, 255))) +sym = mapnik.PolygonSymbolizer(mapnik.Color(153, 204, 255)) +sym.smooth = 1.0 # very smooth +qcdrain_rule.symbols.append(sym) qcdrain_style.rules.append(qcdrain_rule) m.append_style('drainage', qcdrain_style) From 3e4733c51a5a621686e386d0fa85fc8f7fc57828 Mon Sep 17 00:00:00 2001 From: Artem Pavlenko Date: Wed, 14 Mar 2012 17:21:04 +0000 Subject: [PATCH 105/238] + refactor join/cap settings (agg_helper.hpp) --- include/mapnik/agg_helpers.hpp | 34 ++++++++++++++++++++++ src/agg/process_line_symbolizer.cpp | 44 ++--------------------------- 2 files changed, 37 insertions(+), 41 deletions(-) diff --git a/include/mapnik/agg_helpers.hpp b/include/mapnik/agg_helpers.hpp index 2113db113..aa13729d4 100644 --- a/include/mapnik/agg_helpers.hpp +++ b/include/mapnik/agg_helpers.hpp @@ -24,6 +24,7 @@ #define MAPNIK_AGG_HELPERS_HPP #include "agg_gamma_functions.h" +#include "agg_math_stroke.h" namespace mapnik { @@ -52,6 +53,39 @@ void set_gamma_method(T0 const& obj, T1 & ras_ptr) } } +template +void set_join_caps(Stroke const& stroke_, PathType & stroke) +{ + line_join_e join=stroke_.get_line_join(); + switch (join) + { + case MITER_JOIN: + stroke.generator().line_join(agg::miter_join); + break; + case MITER_REVERT_JOIN: + stroke.generator().line_join(agg::miter_join); + break; + case ROUND_JOIN: + stroke.generator().line_join(agg::round_join); + break; + default: + stroke.generator().line_join(agg::bevel_join); + } + + line_cap_e cap=stroke_.get_line_cap(); + switch (cap) + { + case BUTT_CAP: + stroke.generator().line_cap(agg::butt_cap); + break; + case SQUARE_CAP: + stroke.generator().line_cap(agg::square_cap); + break; + default: + stroke.generator().line_cap(agg::round_cap); + } +} + } #endif //MAPNIK_AGG_HELPERS_HPP diff --git a/src/agg/process_line_symbolizer.cpp b/src/agg/process_line_symbolizer.cpp index b4f37be67..cc00d2f2e 100644 --- a/src/agg/process_line_symbolizer.cpp +++ b/src/agg/process_line_symbolizer.cpp @@ -113,7 +113,6 @@ void agg_renderer::process(line_symbolizer const& sym, geometry_type & geom = feature->get_geometry(i); if (geom.num_points() > 1) { - if (stroke_.has_dash()) { clipped_geometry_type clipped(geom); @@ -129,27 +128,8 @@ void agg_renderer::process(line_symbolizer const& sym, dash.add_dash(itr->first * scale_factor_, itr->second * scale_factor_); } - agg::conv_stroke > stroke(dash); - - line_join_e join=stroke_.get_line_join(); - if ( join == MITER_JOIN) - stroke.generator().line_join(agg::miter_join); - else if( join == MITER_REVERT_JOIN) - stroke.generator().line_join(agg::miter_join); - else if( join == ROUND_JOIN) - stroke.generator().line_join(agg::round_join); - else - stroke.generator().line_join(agg::bevel_join); - - line_cap_e cap=stroke_.get_line_cap(); - if (cap == BUTT_CAP) - stroke.generator().line_cap(agg::butt_cap); - else if (cap == SQUARE_CAP) - stroke.generator().line_cap(agg::square_cap); - else - stroke.generator().line_cap(agg::round_cap); - + set_join_caps(stroke_,stroke); stroke.generator().miter_limit(4.0); stroke.generator().width(stroke_.get_width() * scale_factor_); ras_ptr->add_path(stroke); @@ -160,26 +140,8 @@ void agg_renderer::process(line_symbolizer const& sym, clipped_geometry_type clipped(geom); clipped.clip_box(ext.minx(),ext.miny(),ext.maxx(),ext.maxy()); path_type path(t_,clipped,prj_trans); - - agg::conv_stroke stroke(path); - line_join_e join=stroke_.get_line_join(); - if ( join == MITER_JOIN) - stroke.generator().line_join(agg::miter_join); - else if( join == MITER_REVERT_JOIN) - stroke.generator().line_join(agg::miter_join); - else if( join == ROUND_JOIN) - stroke.generator().line_join(agg::round_join); - else - stroke.generator().line_join(agg::bevel_join); - - line_cap_e cap=stroke_.get_line_cap(); - if (cap == BUTT_CAP) - stroke.generator().line_cap(agg::butt_cap); - else if (cap == SQUARE_CAP) - stroke.generator().line_cap(agg::square_cap); - else - stroke.generator().line_cap(agg::round_cap); - + agg::conv_stroke stroke(path); + set_join_caps(stroke_,stroke); stroke.generator().miter_limit(4.0); stroke.generator().width(stroke_.get_width() * scale_factor_); ras_ptr->add_path(stroke); From f0bc1064c6c0ba6da1e9f5f4495126ad59fed4fa Mon Sep 17 00:00:00 2001 From: Artem Pavlenko Date: Wed, 14 Mar 2012 17:38:27 +0000 Subject: [PATCH 106/238] + supprt polygon smoothing in cairo backend --- src/cairo_renderer.cpp | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/src/cairo_renderer.cpp b/src/cairo_renderer.cpp index c900491a6..7f10d8cba 100644 --- a/src/cairo_renderer.cpp +++ b/src/cairo_renderer.cpp @@ -53,6 +53,7 @@ // agg #include "agg_conv_clip_polyline.h" #include "agg_conv_clip_polygon.h" +#include "agg_conv_smooth_poly1.h" // stl #ifdef MAPNIK_DEBUG @@ -725,23 +726,37 @@ void cairo_renderer_base::start_map_processing(Map const& map) proj_transform const& prj_trans) { - typedef agg::conv_clip_polygon clipped_geometry_type; - typedef coord_transform2 path_type; - cairo_context context(context_); - context.set_color(sym.get_fill(), sym.get_opacity()); - + box2d inflated_extent = query_extent_ * 1.1; for (unsigned i = 0; i < feature->num_geometries(); ++i) { geometry_type & geom = feature->get_geometry(i); if (geom.num_points() > 2) { - clipped_geometry_type clipped(geom); - clipped.clip_box(query_extent_.minx(),query_extent_.miny(),query_extent_.maxx(),query_extent_.maxy()); - path_type path(t_,clipped,prj_trans); - context.add_path(path); - context.fill(); + if (sym.smooth() > 0.0) + { + typedef agg::conv_clip_polygon clipped_geometry_type; + typedef coord_transform2 path_type; + typedef agg::conv_smooth_poly1_curve smooth_type; + clipped_geometry_type clipped(geom); + clipped.clip_box(inflated_extent.minx(),inflated_extent.miny(),inflated_extent.maxx(),inflated_extent.maxy()); + path_type path(t_,clipped,prj_trans); + smooth_type smooth(path); + smooth.smooth_value(sym.smooth()); + context.add_agg_path(smooth); + context.fill(); + } + else + { + typedef agg::conv_clip_polygon clipped_geometry_type; + typedef coord_transform2 path_type; + clipped_geometry_type clipped(geom); + clipped.clip_box(query_extent_.minx(),query_extent_.miny(),query_extent_.maxx(),query_extent_.maxy()); + path_type path(t_,clipped,prj_trans); + context.add_path(path); + context.fill(); + } } } } From 78b53b4afea38b14f227372196f82d7411ee6a70 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 14 Mar 2012 15:33:55 -0700 Subject: [PATCH 107/238] rename dtd --- utils/xml/{mapnik2.dtd => mapnik.dtd} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename utils/xml/{mapnik2.dtd => mapnik.dtd} (100%) diff --git a/utils/xml/mapnik2.dtd b/utils/xml/mapnik.dtd similarity index 100% rename from utils/xml/mapnik2.dtd rename to utils/xml/mapnik.dtd From 74452f8ed78fec54831e11065432dfdb19d0ca5f Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 14 Mar 2012 15:35:16 -0700 Subject: [PATCH 108/238] remove explicit exports in python using __all__ - way more trouble than it is worth --- bindings/python/mapnik/__init__.py | 110 ----------------------------- 1 file changed, 110 deletions(-) diff --git a/bindings/python/mapnik/__init__.py b/bindings/python/mapnik/__init__.py index 5bd751629..11129f80a 100644 --- a/bindings/python/mapnik/__init__.py +++ b/bindings/python/mapnik/__init__.py @@ -598,113 +598,3 @@ def register_fonts(path=fontscollectionpath,valid_extensions=['.ttf','.otf','.tt # auto-register known plugins and fonts register_plugins() register_fonts() - -# Explicitly export API members to avoid namespace pollution -# and ensure correct documentation processing -__all__ = [ - # classes - 'CharProperties', - 'Color', - 'Coord', - 'Palette', - #'ColorBand', - 'CompositeOp', - 'DatasourceCache', - 'MemoryDatasource', - 'Box2d', - 'Feature', - 'Featureset', - 'FontEngine', - 'FontSet', - 'FormattingNode', - 'FormattingText', - 'FormattingFormat', - 'FormattingList', - 'FormattingExpressionFormat', - 'Geometry2d', - 'Image', - 'ImageView', - 'Grid', - 'GridView', - 'Layer', - 'Layers', - 'LinePatternSymbolizer', - 'LineSymbolizer', - 'Map', - 'MarkersSymbolizer', - 'Names', - 'Path', - 'Parameter', - 'Parameters', - 'PointSymbolizer', - 'PolygonPatternSymbolizer', - 'PolygonSymbolizer', - 'ProcessedText', - 'ProjTransform', - 'Projection', - 'Query', - 'RasterSymbolizer', - 'RasterColorizer', - 'Rule', 'Rules', - 'ShieldSymbolizer', - 'Singleton', - 'Stroke', - 'Style', - 'Symbolizer', - 'Symbolizers', - 'TextPlacements', - 'TextPlacementInfo', - 'TextSymbolizer', - 'TextSymbolizerProperties', - 'ViewTransform', - # enums - 'aspect_fix_mode', - 'point_placement', - 'label_placement', - 'line_cap', - 'line_join', - 'text_transform', - 'vertical_alignment', - 'horizontal_alignment', - 'justify_alignment', - 'pattern_alignment', - 'filter_mode', - # functions - # datasources - 'Datasource', - 'CreateDatasource', - 'Shapefile', - 'PostGIS', - 'Raster', - 'Gdal', - 'Occi', - 'Ogr', - 'SQLite', - 'Osm', - 'Kismet', - 'Geos', - # version and environment - 'mapnik_version_string', - 'mapnik_version', - 'has_cairo', - 'has_pycairo', - # factory methods - 'Expression', - 'PathExpression', - # load/save/render - 'load_map', - 'load_map_from_string', - 'save_map', - 'save_map_to_string', - 'render', - 'render_grid', - 'render_tile_to_file', - 'render_to_file', - # other - 'register_plugins', - 'register_fonts', - 'scale_denominator', - # deprecated - 'Filter', - 'Envelope', - ] From 2abe02bd96104988ea76869d1203aa270cf9a68c Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 14 Mar 2012 17:26:50 -0700 Subject: [PATCH 109/238] make available MAPNIK_VERSION_STRING in c++ header (not just in python) and add MAPNIK_VERSION_IS_RELEASE define that indicates if the code is released --- SConstruct | 17 ++++++----------- bindings/python/mapnik/__init__.py | 7 ------- bindings/python/mapnik_python.cpp | 6 ++++++ include/mapnik/version.hpp | 24 +++++++++++++++++++++++- src/build.py | 4 ++-- 5 files changed, 37 insertions(+), 21 deletions(-) diff --git a/SConstruct b/SConstruct index 5b10da417..71f04991d 100644 --- a/SConstruct +++ b/SConstruct @@ -768,7 +768,7 @@ def GetMapnikLibVersion(context): int main() { - std::cout << MAPNIK_VERSION << std::endl; + std::cout << MAPNIK_VERSION_STRING << std::endl; return 0; } @@ -778,11 +778,7 @@ int main() context.Result(ret[0]) if not ret[1]: return [] - version = int(ret[1].strip()) - patch_level = version % 100 - minor_version = version / 100 % 1000 - major_version = version / 100000 - return [major_version,minor_version,patch_level] + return ret[1].strip() def icu_at_least_four_two(context): ret = context.TryRun(""" @@ -1382,14 +1378,13 @@ if not preconfigured: # fetch the mapnik version header in order to set the # ABI version used to build libmapnik.so on linux in src/build.py abi = conf.GetMapnikLibVersion() - abi_fallback = [2,0,0] + abi_fallback = "2.0.1-pre" if not abi: color_print(1,'Problem encountered parsing mapnik version, falling back to %s' % abi_fallback) - env['ABI_VERSION'] = abi_fallback - else: - env['ABI_VERSION'] = abi - env['MAPNIK_VERSION_STRING'] = '.'.join(['%d' % i for i in env['ABI_VERSION']]) + abi = abi_fallback + env['ABI_VERSION'] = abi.replace('-pre','').split('.') + env['MAPNIK_VERSION_STRING'] = abi # Common C++ flags. if env['THREADING'] == 'multi': diff --git a/bindings/python/mapnik/__init__.py b/bindings/python/mapnik/__init__.py index 11129f80a..8cf54f1d9 100644 --- a/bindings/python/mapnik/__init__.py +++ b/bindings/python/mapnik/__init__.py @@ -572,13 +572,6 @@ def Geos(**keywords): keywords['type'] = 'geos' return CreateDatasource(keywords) -def mapnik_version_string(version=mapnik_version()): - """Return the Mapnik version as a string.""" - patch_level = version % 100 - minor_version = version / 100 % 1000 - major_version = version / 100000 - return '%s.%s.%s' % ( major_version, minor_version,patch_level) - def mapnik_version_from_string(version_string): """Return the Mapnik version from a string.""" n = version_string.split('.') diff --git a/bindings/python/mapnik_python.cpp b/bindings/python/mapnik_python.cpp index e0591977a..4a4f5c4c9 100644 --- a/bindings/python/mapnik_python.cpp +++ b/bindings/python/mapnik_python.cpp @@ -279,6 +279,11 @@ unsigned mapnik_version() return MAPNIK_VERSION; } +std::string mapnik_version_string() +{ + return MAPNIK_VERSION_STRING; +} + // indicator for jpeg read/write support within libmapnik bool has_jpeg() { @@ -573,6 +578,7 @@ BOOST_PYTHON_MODULE(_mapnik) def("save_map_to_string", &save_map_to_string, save_map_to_string_overloads()); def("mapnik_version", &mapnik_version,"Get the Mapnik version number"); + def("mapnik_version_string", &mapnik_version_string,"Get the Mapnik version string"); def("has_jpeg", &has_jpeg, "Get jpeg read/write support status"); def("has_cairo", &has_cairo, "Get cairo library status"); def("has_pycairo", &has_pycairo, "Get pycairo module status"); diff --git a/include/mapnik/version.hpp b/include/mapnik/version.hpp index b79c0f666..b5a96d8ea 100644 --- a/include/mapnik/version.hpp +++ b/include/mapnik/version.hpp @@ -23,6 +23,28 @@ #ifndef MAPNIK_VERSION_HPP #define MAPNIK_VERSION_HPP -#define MAPNIK_VERSION 200000 +#define MAPNIK_VERSION_IS_RELEASE 0 + +#define MAPNIK_MAJOR_VERSION 2 +#define MAPNIK_MINOR_VERSION 1 +#define MAPNIK_PATCH_VERSION 0 + +#define MAPNIK_VERSION (MAPNIK_MAJOR_VERSION*100000) + (MAPNIK_MINOR_VERSION*100) + (MAPNIK_PATCH_VERSION) + +#ifndef MAPNIK_STRINGIFY +#define MAPNIK_STRINGIFY(n) MAPNIK_STRINGIFY_HELPER(n) +#define MAPNIK_STRINGIFY_HELPER(n) #n +#endif + +#if MAPNIK_VERSION_IS_RELEASE +# define MAPNIK_VERSION_STRING MAPNIK_STRINGIFY(MAPNIK_MAJOR_VERSION) "." \ + MAPNIK_STRINGIFY(MAPNIK_MINOR_VERSION) "." \ + MAPNIK_STRINGIFY(MAPNIK_PATCH_VERSION) + +#else +# define MAPNIK_VERSION_STRING MAPNIK_STRINGIFY(MAPNIK_MAJOR_VERSION) "." \ + MAPNIK_STRINGIFY(MAPNIK_MINOR_VERSION) "." \ + MAPNIK_STRINGIFY(MAPNIK_PATCH_VERSION) "-pre" +#endif #endif // MAPNIK_VERSION_HPP diff --git a/src/build.py b/src/build.py index 78df41b46..956b5d550 100644 --- a/src/build.py +++ b/src/build.py @@ -83,7 +83,7 @@ else: if env['PLATFORM'] == 'Darwin': mapnik_libname = 'libmapnik.dylib' else: - mapnik_libname = 'libmapnik.so.' + ("%d.%d" % (ABI_VERSION[0],ABI_VERSION[1])) + mapnik_libname = 'libmapnik.so.' + ("%d.%d" % (int(ABI_VERSION[0]),int(ABI_VERSION[1]))) if env['PLATFORM'] == 'Darwin': if env['FULL_LIB_PATH']: @@ -91,7 +91,7 @@ if env['PLATFORM'] == 'Darwin': else: lib_path = mapnik_libname mapnik_lib_link_flag += ' -Wl,-install_name,%s' % lib_path - _d = {'version':env['MAPNIK_VERSION_STRING']} + _d = {'version':env['MAPNIK_VERSION_STRING'].replace('-pre','')} mapnik_lib_link_flag += ' -current_version %(version)s -compatibility_version %(version)s' % _d elif env['PLATFORM'] == 'SunOS': if env['CXX'].startswith('CC'): From 926404d9aead018bb50c2fa4710cfd9d69ea214e Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Thu, 15 Mar 2012 02:03:33 +0000 Subject: [PATCH 110/238] properly format abi versions in a few more placesa --- src/build.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/build.py b/src/build.py index 956b5d550..25a63496c 100644 --- a/src/build.py +++ b/src/build.py @@ -342,7 +342,7 @@ if env['PLATFORM'] != 'Darwin': major, minor, micro = ABI_VERSION - soFile = "%s.%d.%d.%d" % (os.path.basename(str(mapnik[0])), major, minor, micro) + soFile = "%s.%d.%d.%d" % (os.path.basename(str(mapnik[0])), int(major), int(minor), int(micro)) target = os.path.join(env['MAPNIK_LIB_BASE_DEST'], soFile) if 'uninstall' not in COMMAND_LINE_TARGETS: @@ -353,7 +353,7 @@ if env['PLATFORM'] != 'Darwin': # Install symlinks - target1 = os.path.join(env['MAPNIK_LIB_BASE_DEST'], "%s.%d.%d" % (os.path.basename(str(mapnik[0])),major, minor)) + target1 = os.path.join(env['MAPNIK_LIB_BASE_DEST'], "%s.%d.%d" % (os.path.basename(str(mapnik[0])),int(major), int(minor))) target2 = os.path.join(env['MAPNIK_LIB_BASE_DEST'], os.path.basename(str(mapnik[0]))) if 'uninstall' not in COMMAND_LINE_TARGETS: if 'install' in COMMAND_LINE_TARGETS: From 70512dc03b0d38dd324e19667ba56437069307ea Mon Sep 17 00:00:00 2001 From: Artem Pavlenko Date: Thu, 15 Mar 2012 10:26:53 +0000 Subject: [PATCH 111/238] + add optional smothing to line_symbolizer ( TODO: conv_dash ) --- bindings/python/mapnik_line_symbolizer.cpp | 4 +++ include/mapnik/line_symbolizer.hpp | 20 +++++++++-- src/agg/process_line_symbolizer.cpp | 41 ++++++++++++++++------ src/load_map.cpp | 5 ++- src/save_map.cpp | 6 +++- 5 files changed, 60 insertions(+), 16 deletions(-) diff --git a/bindings/python/mapnik_line_symbolizer.cpp b/bindings/python/mapnik_line_symbolizer.cpp index 062d52618..9c7053b90 100644 --- a/bindings/python/mapnik_line_symbolizer.cpp +++ b/bindings/python/mapnik_line_symbolizer.cpp @@ -62,5 +62,9 @@ void export_line_symbolizer() (&line_symbolizer::get_stroke, return_value_policy()), &line_symbolizer::set_stroke) + .add_property("smooth", + &line_symbolizer::smooth, + &line_symbolizer::set_smooth, + "smooth value (0..1.0)") ; } diff --git a/include/mapnik/line_symbolizer.hpp b/include/mapnik/line_symbolizer.hpp index 84c845098..bcb3ab971 100644 --- a/include/mapnik/line_symbolizer.hpp +++ b/include/mapnik/line_symbolizer.hpp @@ -44,17 +44,20 @@ struct MAPNIK_DECL line_symbolizer : public symbolizer_base explicit line_symbolizer() : symbolizer_base(), stroke_(), - rasterizer_p_(RASTERIZER_FULL) {} + rasterizer_p_(RASTERIZER_FULL), + smooth_(0.0) {} line_symbolizer(stroke const& stroke) : symbolizer_base(), stroke_(stroke), - rasterizer_p_(RASTERIZER_FULL) {} + rasterizer_p_(RASTERIZER_FULL), + smooth_(0.0) {} line_symbolizer(color const& pen,float width=1.0) : symbolizer_base(), stroke_(pen,width), - rasterizer_p_(RASTERIZER_FULL) {} + rasterizer_p_(RASTERIZER_FULL), + smooth_(0.0) {} stroke const& get_stroke() const { @@ -76,9 +79,20 @@ struct MAPNIK_DECL line_symbolizer : public symbolizer_base return rasterizer_p_; } + void set_smooth(double smooth) + { + smooth_ = smooth; + } + + double smooth() const + { + return smooth_; + } + private: stroke stroke_; line_rasterizer_e rasterizer_p_; + double smooth_; }; } diff --git a/src/agg/process_line_symbolizer.cpp b/src/agg/process_line_symbolizer.cpp index cc00d2f2e..d07f47a28 100644 --- a/src/agg/process_line_symbolizer.cpp +++ b/src/agg/process_line_symbolizer.cpp @@ -40,7 +40,7 @@ #include "agg_renderer_outline_aa.h" #include "agg_rasterizer_outline_aa.h" #include "agg_conv_clip_polyline.h" - +#include "agg_conv_smooth_poly1.h" // stl #include @@ -105,8 +105,6 @@ void agg_renderer::process(line_symbolizer const& sym, set_gamma_method(stroke_, ras_ptr); - - //metawriter_with_properties writer = sym.get_metawriter(); for (unsigned i=0;inum_geometries();++i) { @@ -137,14 +135,35 @@ void agg_renderer::process(line_symbolizer const& sym, } else { - clipped_geometry_type clipped(geom); - clipped.clip_box(ext.minx(),ext.miny(),ext.maxx(),ext.maxy()); - path_type path(t_,clipped,prj_trans); - agg::conv_stroke stroke(path); - set_join_caps(stroke_,stroke); - stroke.generator().miter_limit(4.0); - stroke.generator().width(stroke_.get_width() * scale_factor_); - ras_ptr->add_path(stroke); + if (sym.smooth() > 0.0) + { + typedef agg::conv_clip_polyline clipped_geometry_type; + typedef coord_transform2 path_type; + typedef agg::conv_smooth_poly1_curve smooth_type; + clipped_geometry_type clipped(geom); + clipped.clip_box(ext.minx(),ext.miny(),ext.maxx(),ext.maxy()); + path_type path(t_,clipped,prj_trans); + smooth_type smooth(path); + smooth.smooth_value(sym.smooth()); + agg::conv_stroke stroke(smooth); + set_join_caps(stroke_,stroke); + stroke.generator().miter_limit(4.0); + stroke.generator().width(stroke_.get_width() * scale_factor_); + ras_ptr->add_path(stroke); + } + else + { + typedef agg::conv_clip_polyline clipped_geometry_type; + typedef coord_transform2 path_type; + clipped_geometry_type clipped(geom); + clipped.clip_box(ext.minx(),ext.miny(),ext.maxx(),ext.maxy()); + path_type path(t_,clipped,prj_trans); + agg::conv_stroke stroke(path); + set_join_caps(stroke_,stroke); + stroke.generator().miter_limit(4.0); + stroke.generator().width(stroke_.get_width() * scale_factor_); + ras_ptr->add_path(stroke); + } //if (writer.first) writer.first->add_line(path, *feature, t_, writer.second); } } diff --git a/src/load_map.cpp b/src/load_map.cpp index 92787c83e..354817a36 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -1266,7 +1266,10 @@ void map_parser::parse_line_symbolizer( rule & rule, xml_node const & sym ) line_rasterizer_e rasterizer = sym.get_attr("rasterizer", RASTERIZER_FULL); //optional rasterizer_method = sym.get_opt_attr("full"); symbol.set_rasterizer(rasterizer); - + // smooth value + optional smooth = sym.get_opt_attr("smooth"); + if (smooth) symbol.set_smooth(*smooth); + // meta-writer parse_metawriter_in_symbolizer(symbol, sym); rule.append(symbol); } diff --git a/src/save_map.cpp b/src/save_map.cpp index d81508ad3..a890973ce 100644 --- a/src/save_map.cpp +++ b/src/save_map.cpp @@ -91,8 +91,12 @@ public: { set_attr( sym_node, "rasterizer", sym.get_rasterizer() ); } + if ( sym.smooth() != dfl.smooth() || explicit_defaults_ ) + { + set_attr( sym_node, "smooth", sym.smooth() ); + } } - + void operator () ( line_pattern_symbolizer const& sym ) { ptree & sym_node = rule_.push_back( From 66beaaa05b54d7128efba85b3988a2bd271082c2 Mon Sep 17 00:00:00 2001 From: Artem Pavlenko Date: Thu, 15 Mar 2012 10:42:30 +0000 Subject: [PATCH 112/238] + support smoothing in conv_dash --- src/agg/process_line_symbolizer.cpp | 69 ++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/src/agg/process_line_symbolizer.cpp b/src/agg/process_line_symbolizer.cpp index d07f47a28..8763b882f 100644 --- a/src/agg/process_line_symbolizer.cpp +++ b/src/agg/process_line_symbolizer.cpp @@ -52,9 +52,7 @@ void agg_renderer::process(line_symbolizer const& sym, proj_transform const& prj_trans) { typedef agg::renderer_base ren_base; - typedef agg::conv_clip_polyline clipped_geometry_type; - typedef coord_transform2 path_type; - + stroke const& stroke_ = sym.get_stroke(); color const& col = stroke_.get_color(); unsigned r=col.red(); @@ -70,6 +68,8 @@ void agg_renderer::process(line_symbolizer const& sym, { typedef agg::renderer_outline_aa renderer_type; typedef agg::rasterizer_outline_aa rasterizer_type; + typedef agg::conv_clip_polyline clipped_geometry_type; + typedef coord_transform2 path_type; agg::line_profile_aa profile; profile.width(stroke_.get_width() * scale_factor_); @@ -113,25 +113,54 @@ void agg_renderer::process(line_symbolizer const& sym, { if (stroke_.has_dash()) { - clipped_geometry_type clipped(geom); - clipped.clip_box(ext.minx(),ext.miny(),ext.maxx(),ext.maxy()); - path_type path(t_,clipped,prj_trans); - - agg::conv_dash dash(path); - dash_array const& d = stroke_.get_dash_array(); - dash_array::const_iterator itr = d.begin(); - dash_array::const_iterator end = d.end(); - for (;itr != end;++itr) + if (sym.smooth() > 0.0) { - dash.add_dash(itr->first * scale_factor_, - itr->second * scale_factor_); + typedef agg::conv_clip_polyline clipped_geometry_type; + typedef coord_transform2 path_type; + typedef agg::conv_smooth_poly1_curve smooth_type; + clipped_geometry_type clipped(geom); + clipped.clip_box(ext.minx(),ext.miny(),ext.maxx(),ext.maxy()); + path_type path(t_,clipped,prj_trans); + smooth_type smooth(path); + smooth.smooth_value(sym.smooth()); + agg::conv_dash dash(smooth); + dash_array const& d = stroke_.get_dash_array(); + dash_array::const_iterator itr = d.begin(); + dash_array::const_iterator end = d.end(); + for (;itr != end;++itr) + { + dash.add_dash(itr->first * scale_factor_, + itr->second * scale_factor_); + } + agg::conv_stroke > stroke(dash); + set_join_caps(stroke_,stroke); + stroke.generator().miter_limit(4.0); + stroke.generator().width(stroke_.get_width() * scale_factor_); + ras_ptr->add_path(stroke); + } + else + { + typedef agg::conv_clip_polyline clipped_geometry_type; + typedef coord_transform2 path_type; + clipped_geometry_type clipped(geom); + clipped.clip_box(ext.minx(),ext.miny(),ext.maxx(),ext.maxy()); + path_type path(t_,clipped,prj_trans); + + agg::conv_dash dash(path); + dash_array const& d = stroke_.get_dash_array(); + dash_array::const_iterator itr = d.begin(); + dash_array::const_iterator end = d.end(); + for (;itr != end;++itr) + { + dash.add_dash(itr->first * scale_factor_, + itr->second * scale_factor_); + } + agg::conv_stroke > stroke(dash); + set_join_caps(stroke_,stroke); + stroke.generator().miter_limit(4.0); + stroke.generator().width(stroke_.get_width() * scale_factor_); + ras_ptr->add_path(stroke); } - agg::conv_stroke > stroke(dash); - set_join_caps(stroke_,stroke); - stroke.generator().miter_limit(4.0); - stroke.generator().width(stroke_.get_width() * scale_factor_); - ras_ptr->add_path(stroke); - } else { From c95959c549880c2474c9d9dd47ecead08a47b29f Mon Sep 17 00:00:00 2001 From: Artem Pavlenko Date: Thu, 15 Mar 2012 11:29:57 +0000 Subject: [PATCH 113/238] + re-use cairo_context --- src/cairo_renderer.cpp | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/cairo_renderer.cpp b/src/cairo_renderer.cpp index 7f10d8cba..dff4ce123 100644 --- a/src/cairo_renderer.cpp +++ b/src/cairo_renderer.cpp @@ -745,7 +745,6 @@ void cairo_renderer_base::start_map_processing(Map const& map) smooth_type smooth(path); smooth.smooth_value(sym.smooth()); context.add_agg_path(smooth); - context.fill(); } else { @@ -755,10 +754,11 @@ void cairo_renderer_base::start_map_processing(Map const& map) clipped.clip_box(query_extent_.minx(),query_extent_.miny(),query_extent_.maxx(),query_extent_.maxy()); path_type path(t_,clipped,prj_trans); context.add_path(path); - context.fill(); - } + } } } + // fill polygon + context.fill(); } void cairo_renderer_base::process(building_symbolizer const& sym, @@ -877,35 +877,34 @@ void cairo_renderer_base::start_map_processing(Map const& map) { typedef agg::conv_clip_polyline clipped_geometry_type; typedef coord_transform2 path_type; - - cairo_context context(context_); + mapnik::stroke const& stroke_ = sym.get_stroke(); - + cairo_context context(context_); context.set_color(stroke_.get_color(), stroke_.get_opacity()); - + context.set_line_join(stroke_.get_line_join()); + context.set_line_cap(stroke_.get_line_cap()); + context.set_miter_limit(4.0); + context.set_line_width(stroke_.get_width()); + if (stroke_.has_dash()) + { + context.set_dash(stroke_.get_dash_array()); + } + for (unsigned i = 0; i < feature->num_geometries(); ++i) { geometry_type & geom = feature->get_geometry(i); if (geom.num_points() > 1) { - cairo_context context(context_); + //cairo_context context(context_); clipped_geometry_type clipped(geom); clipped.clip_box(query_extent_.minx(),query_extent_.miny(),query_extent_.maxx(),query_extent_.maxy()); path_type path(t_,clipped,prj_trans); - if (stroke_.has_dash()) - { - context.set_dash(stroke_.get_dash_array()); - } - - context.set_line_join(stroke_.get_line_join()); - context.set_line_cap(stroke_.get_line_cap()); - context.set_miter_limit(4.0); - context.set_line_width(stroke_.get_width()); + context.add_path(path); - context.stroke(); } } + context.stroke(); } void cairo_renderer_base::render_marker(pixel_position const& pos, marker const& marker, const agg::trans_affine & tr, double opacity) From f9b2e5fb56e8b2099d47f872638464f065df5240 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Fri, 16 Mar 2012 00:38:03 +0100 Subject: [PATCH 114/238] Add test for RTL text. --- tests/visual_tests/rtl-point.xml | 25 +++++++++++++++++++++++++ tests/visual_tests/test.py | 4 +++- 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 tests/visual_tests/rtl-point.xml diff --git a/tests/visual_tests/rtl-point.xml b/tests/visual_tests/rtl-point.xml new file mode 100644 index 000000000..c29a41797 --- /dev/null +++ b/tests/visual_tests/rtl-point.xml @@ -0,0 +1,25 @@ + + + + + + My Style + + + shape + points.shp + + + + + + diff --git a/tests/visual_tests/test.py b/tests/visual_tests/test.py index f899da1ea..804137c0b 100755 --- a/tests/visual_tests/test.py +++ b/tests/visual_tests/test.py @@ -27,7 +27,9 @@ files = [ ("formating-3", 500), ("formating-4", 500), ("shieldsymbolizer-1", 490, 495, 497, 498, 499, 500, 501, 502, 505, 510), - ("expressionformat", 500)] + ("expressionformat", 500), + ("rtl-point", (200, 200)) + ] def render(filename, width, height=100): print "-"*80 From 1b85f42a883b6df8f0176607c25a62c8d8f9504d Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Fri, 16 Mar 2012 01:01:12 +0100 Subject: [PATCH 115/238] Reapply RTL patch. Fixes #189. --- src/placement_finder.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/placement_finder.cpp b/src/placement_finder.cpp index 387323323..719b56dee 100644 --- a/src/placement_finder.cpp +++ b/src/placement_finder.cpp @@ -384,6 +384,9 @@ void placement_finder::find_point_placement(double label_x, // set for upper left corner of text envelope for the first line, bottom left of first character y = string_height_ / 2.0 - line_height; + // RTL text is converted to a mirrored representation in get_string_info() + // so we have to fix line break order here + if (info_.get_rtl()) y = -y; // adjust for desired justification if (p.jalign == J_LEFT) @@ -408,7 +411,13 @@ void placement_finder::find_point_placement(double label_x, line_width = line_sizes_[line_number].first; line_height= line_sizes_[line_number].second; - y -= line_height; // move position down to line start + if (info_.get_rtl()) + { + y += line_height; + } else + { + y -= line_height; // move position down to line start + } // reset to begining of line position if (p.jalign == J_LEFT) From 725248628d807480ca2c32231559fab3a6c97ded Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Fri, 16 Mar 2012 01:50:55 +0100 Subject: [PATCH 116/238] C++ style. --- src/load_map.cpp | 114 +++++++++++++++++++++++------------------------ 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/src/load_map.cpp b/src/load_map.cpp index 354817a36..2f77db471 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -80,16 +80,16 @@ using boost::optional; class map_parser : boost::noncopyable { public: - map_parser( bool strict, std::string const& filename = "" ) : - strict_( strict ), - filename_( filename ), + map_parser(bool strict, std::string const& filename = "") : + strict_(strict), + filename_(filename), relative_to_xml_(true), font_manager_(font_engine_) {} void parse_map(Map & map, xml_node const& sty, std::string const& base_path); private: - void parse_map_include( Map & map, xml_node const& include); + void parse_map_include(Map & map, xml_node const& include); void parse_style(Map & map, xml_node const& sty); void parse_layer(Map & map, xml_node const& lay); void parse_metawriter(Map & map, xml_node const& lay); @@ -107,9 +107,9 @@ private: void parse_shield_symbolizer(rule & rule, xml_node const& sym); void parse_line_symbolizer(rule & rule, xml_node const& sym); void parse_polygon_symbolizer(rule & rule, xml_node const& sym); - void parse_building_symbolizer(rule & rule, xml_node const& sym ); - void parse_raster_symbolizer(rule & rule, xml_node const& sym ); - void parse_markers_symbolizer(rule & rule, xml_node const& sym ); + void parse_building_symbolizer(rule & rule, xml_node const& sym); + void parse_raster_symbolizer(rule & rule, xml_node const& sym); + void parse_markers_symbolizer(rule & rule, xml_node const& sym); void parse_raster_colorizer(raster_colorizer_ptr const& rc, xml_node const& node); void parse_stroke(stroke & strk, xml_node const & sym); @@ -479,7 +479,7 @@ void map_parser::parse_font(font_set &fset, xml_node const& f) optional face_name = f.get_opt_attr("face-name"); if (face_name) { - if ( strict_ ) + if (strict_) { ensure_font_face(*face_name); } @@ -506,47 +506,47 @@ void map_parser::parse_layer(Map & map, xml_node const& lay) optional status = lay.get_opt_attr("status"); if (status) { - lyr.set_active(* status ); + lyr.set_active(* status); } optional min_zoom = lay.get_opt_attr("minzoom"); if (min_zoom) { - lyr.set_min_zoom( * min_zoom ); + lyr.set_min_zoom(* min_zoom); } optional max_zoom = lay.get_opt_attr("maxzoom"); if (max_zoom) { - lyr.set_max_zoom( * max_zoom ); + lyr.set_max_zoom(* max_zoom); } optional queryable = lay.get_opt_attr("queryable"); if (queryable) { - lyr.set_queryable( * queryable ); + lyr.set_queryable(* queryable); } optional clear_cache = lay.get_opt_attr("clear-label-cache"); if (clear_cache) { - lyr.set_clear_label_cache( * clear_cache ); + lyr.set_clear_label_cache(* clear_cache); } optional cache_features = lay.get_opt_attr("cache-features"); if (cache_features) { - lyr.set_cache_features( * cache_features ); + lyr.set_cache_features(* cache_features); } optional group_by = lay.get_opt_attr("group-by"); if (group_by) { - lyr.set_group_by( * group_by ); + lyr.set_group_by(* group_by); } xml_node::const_iterator child = lay.begin(); @@ -576,7 +576,7 @@ void map_parser::parse_layer(Map & map, xml_node const& lay) { parameters params; optional base = child->get_opt_attr("base"); - if( base ) + if(base) { std::map::const_iterator base_itr = datasource_templates_.find(*base); if (base_itr!=datasource_templates_.end()) @@ -614,9 +614,9 @@ void map_parser::parse_layer(Map & map, xml_node const& lay) lyr.set_datasource(ds); } - catch (const std::exception & ex ) + catch (const std::exception & ex) { - throw config_error( ex.what() ); + throw config_error(ex.what()); } catch (...) @@ -676,10 +676,10 @@ void map_parser::parse_rule(feature_type_style & style, xml_node const& r) xml_node::const_iterator symIter = r.begin(); xml_node::const_iterator endSym = r.end(); - for( ;symIter != endSym; ++symIter) + for(;symIter != endSym; ++symIter) { - if ( symIter->is("PointSymbolizer")) + if (symIter->is("PointSymbolizer")) { parse_point_symbolizer(rule, *symIter); } @@ -713,7 +713,7 @@ void map_parser::parse_rule(feature_type_style & style, xml_node const& r) } else if (symIter->is("RasterSymbolizer")) { - parse_raster_symbolizer( rule, *symIter); + parse_raster_symbolizer(rule, *symIter); } else if (symIter->is("MarkersSymbolizer")) { @@ -725,7 +725,7 @@ void map_parser::parse_rule(feature_type_style & style, xml_node const& r) } catch (const config_error & ex) { - if (!name.empty() ) + if (!name.empty()) { ex.append_context(std::string("in rule '") + name + "'", r); } @@ -755,25 +755,25 @@ void map_parser::parse_point_symbolizer(rule & rule, xml_node const & sym) point_symbolizer symbol; if (allow_overlap) { - symbol.set_allow_overlap( * allow_overlap ); + symbol.set_allow_overlap(* allow_overlap); } if (opacity) { - symbol.set_opacity( * opacity ); + symbol.set_opacity(* opacity); } if (ignore_placement) { - symbol.set_ignore_placement( * ignore_placement ); + symbol.set_ignore_placement(* ignore_placement); } point_placement_e placement = sym.get_attr("placement", CENTROID_POINT_PLACEMENT); - symbol.set_point_placement( placement ); + symbol.set_point_placement(placement); if (file) { try { - if( base ) + if(base) { std::map::const_iterator itr = file_sources_.find(*base); if (itr!=file_sources_.end()) @@ -804,7 +804,7 @@ void map_parser::parse_point_symbolizer(rule & rule, xml_node const & sym) symbol.set_transform(matrix); } } - catch (image_reader_exception const & ex ) + catch (image_reader_exception const & ex) { std::string msg("Failed to load image file '" + * file + "': " + ex.what()); @@ -869,7 +869,7 @@ void map_parser::parse_markers_symbolizer(rule & rule, xml_node const& sym) markers_symbolizer symbol(parse_path(filename)); optional opacity = sym.get_opt_attr("opacity"); - if (opacity) symbol.set_opacity( *opacity ); + if (opacity) symbol.set_opacity(*opacity); if (transform_wkt) { @@ -922,7 +922,7 @@ void map_parser::parse_markers_symbolizer(rule & rule, xml_node const& sym) symbol.set_stroke(strk); marker_placement_e placement = sym.get_attr("placement", MARKER_LINE_PLACEMENT); - symbol.set_marker_placement( placement ); + symbol.set_marker_placement(placement); marker_type_e dfl_marker_type = ARROW; @@ -930,7 +930,7 @@ void map_parser::parse_markers_symbolizer(rule & rule, xml_node const& sym) dfl_marker_type = ELLIPSE; marker_type_e marker_type = sym.get_attr("marker-type", dfl_marker_type); - symbol.set_marker_type( marker_type ); + symbol.set_marker_type(marker_type); parse_metawriter_in_symbolizer(symbol, sym); rule.append(symbol); @@ -942,7 +942,7 @@ void map_parser::parse_markers_symbolizer(rule & rule, xml_node const& sym) } } -void map_parser::parse_line_pattern_symbolizer( rule & rule, xml_node const & sym ) +void map_parser::parse_line_pattern_symbolizer(rule & rule, xml_node const & sym) { try { @@ -951,7 +951,7 @@ void map_parser::parse_line_pattern_symbolizer( rule & rule, xml_node const & sy try { - if( base ) + if(base) { std::map::const_iterator itr = file_sources_.find(*base); if (itr!=file_sources_.end()) @@ -967,7 +967,7 @@ void map_parser::parse_line_pattern_symbolizer( rule & rule, xml_node const & sy parse_metawriter_in_symbolizer(symbol, sym); rule.append(symbol); } - catch (image_reader_exception const & ex ) + catch (image_reader_exception const & ex) { std::string msg("Failed to load image file '" + file + "': " + ex.what()); @@ -988,8 +988,8 @@ void map_parser::parse_line_pattern_symbolizer( rule & rule, xml_node const & sy } } -void map_parser::parse_polygon_pattern_symbolizer( rule & rule, - xml_node const & sym ) +void map_parser::parse_polygon_pattern_symbolizer(rule & rule, + xml_node const & sym) { try { @@ -998,7 +998,7 @@ void map_parser::parse_polygon_pattern_symbolizer( rule & rule, try { - if( base ) + if(base) { std::map::iterator itr = file_sources_.find(*base); if (itr!=file_sources_.end()) @@ -1026,7 +1026,7 @@ void map_parser::parse_polygon_pattern_symbolizer( rule & rule, parse_metawriter_in_symbolizer(symbol, sym); rule.append(symbol); } - catch (image_reader_exception const & ex ) + catch (image_reader_exception const & ex) { std::string msg("Failed to load image file '" + file + "': " + ex.what()); @@ -1047,7 +1047,7 @@ void map_parser::parse_polygon_pattern_symbolizer( rule & rule, } } -void map_parser::parse_text_symbolizer( rule & rule, xml_node const& sym ) +void map_parser::parse_text_symbolizer(rule & rule, xml_node const& sym) { try { @@ -1074,7 +1074,7 @@ void map_parser::parse_text_symbolizer( rule & rule, xml_node const& sym ) } } -void map_parser::parse_shield_symbolizer(rule & rule, xml_node const& sym ) +void map_parser::parse_shield_symbolizer(rule & rule, xml_node const& sym) { try { @@ -1127,7 +1127,7 @@ void map_parser::parse_shield_symbolizer(rule & rule, xml_node const& sym ) sym.get_opt_attr("text-opacity"); if (text_opacity) { - shield_symbol.set_text_opacity( * text_opacity ); + shield_symbol.set_text_opacity(* text_opacity); } // unlock_image @@ -1135,7 +1135,7 @@ void map_parser::parse_shield_symbolizer(rule & rule, xml_node const& sym ) sym.get_opt_attr("unlock-image"); if (unlock_image) { - shield_symbol.set_unlock_image( * unlock_image ); + shield_symbol.set_unlock_image(* unlock_image); } parse_metawriter_in_symbolizer(shield_symbol, sym); @@ -1145,7 +1145,7 @@ void map_parser::parse_shield_symbolizer(rule & rule, xml_node const& sym ) try { - if( base ) + if(base) { std::map::const_iterator itr = file_sources_.find(*base); if (itr!=file_sources_.end()) @@ -1157,7 +1157,7 @@ void map_parser::parse_shield_symbolizer(rule & rule, xml_node const& sym ) image_file = ensure_relative_to_xml(image_file); shield_symbol.set_filename(parse_path(image_file)); } - catch (image_reader_exception const & ex ) + catch (image_reader_exception const & ex) { std::string msg("Failed to load image file '" + image_file + "': " + ex.what()); @@ -1227,7 +1227,7 @@ void map_parser::parse_stroke(stroke & strk, xml_node const & sym) double f = boost::lexical_cast(*itr); dash_array.push_back(f); } - catch ( boost::bad_lexical_cast &) + catch (boost::bad_lexical_cast &) { throw config_error(std::string("Failed to parse dasharray ") + "'. Expected a " + @@ -1237,7 +1237,7 @@ void map_parser::parse_stroke(stroke & strk, xml_node const & sym) if (dash_array.size()) { size_t size = dash_array.size(); - if ( size % 2) + if (size % 2) { for (size_t i=0; i < size ;++i) { @@ -1254,7 +1254,7 @@ void map_parser::parse_stroke(stroke & strk, xml_node const & sym) } } -void map_parser::parse_line_symbolizer( rule & rule, xml_node const & sym ) +void map_parser::parse_line_symbolizer(rule & rule, xml_node const & sym) { try { @@ -1281,7 +1281,7 @@ void map_parser::parse_line_symbolizer( rule & rule, xml_node const & sym ) } -void map_parser::parse_polygon_symbolizer( rule & rule, xml_node const & sym ) +void map_parser::parse_polygon_symbolizer(rule & rule, xml_node const & sym) { try { @@ -1313,7 +1313,7 @@ void map_parser::parse_polygon_symbolizer( rule & rule, xml_node const & sym ) } -void map_parser::parse_building_symbolizer( rule & rule, xml_node const & sym ) +void map_parser::parse_building_symbolizer(rule & rule, xml_node const & sym) { try { @@ -1339,7 +1339,7 @@ void map_parser::parse_building_symbolizer( rule & rule, xml_node const & sym ) } } -void map_parser::parse_raster_symbolizer( rule & rule, xml_node const & sym ) +void map_parser::parse_raster_symbolizer(rule & rule, xml_node const & sym) { try { @@ -1400,13 +1400,13 @@ void map_parser::parse_raster_colorizer(raster_colorizer_ptr const& rc, if(default_mode == COLORIZER_INHERIT) { throw config_error("RasterColorizer mode must not be INHERIT. "); } - rc->set_default_mode( default_mode ); + rc->set_default_mode(default_mode); // default colour optional default_color = node.get_opt_attr("default-color"); if (default_color) { - rc->set_default_color( *default_color ); + rc->set_default_color(*default_color); } @@ -1417,7 +1417,7 @@ void map_parser::parse_raster_colorizer(raster_colorizer_ptr const& rc, if(*eps < 0) { throw config_error("RasterColorizer epsilon must be > 0. "); } - rc->set_epsilon( *eps ); + rc->set_epsilon(*eps); } @@ -1474,22 +1474,22 @@ void map_parser::parse_raster_colorizer(raster_colorizer_ptr const& rc, } } -void map_parser::ensure_font_face( std::string const& face_name ) +void map_parser::ensure_font_face(std::string const& face_name) { - if ( ! font_manager_.get_face( face_name ) ) + if (! font_manager_.get_face(face_name)) { throw config_error("Failed to find font face '" + face_name + "'"); } } -std::string map_parser::ensure_relative_to_xml( boost::optional opt_path ) +std::string map_parser::ensure_relative_to_xml(boost::optional opt_path) { if (relative_to_xml_) { boost::filesystem::path xml_path = filename_; boost::filesystem::path rel_path = *opt_path; - if ( !rel_path.has_root_path() ) + if (!rel_path.has_root_path()) { #if (BOOST_FILESYSTEM_VERSION == 3) // TODO - normalize is now deprecated, use make_preferred? From ff78217276fd68201bb7f78239306e0b3f3d0c2a Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Fri, 16 Mar 2012 01:51:19 +0100 Subject: [PATCH 117/238] Add reference image for RTL test. --- tests/visual_tests/rtl-point-200-reference.png | Bin 0 -> 2175 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/visual_tests/rtl-point-200-reference.png diff --git a/tests/visual_tests/rtl-point-200-reference.png b/tests/visual_tests/rtl-point-200-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..0d8979ed05c14de327b61d6da46bc124eedeb0d8 GIT binary patch literal 2175 zcmeHJc{JPk7SHj}Fw`<_(X>fQv`=d&Izg9+J*Jk{7CBO(sLBY!R0*NlF{!1sOp8vH zGs<)Zu~bPHl1^>4rDK^8A;cD=mXN4KBHpia-oNkt`~G?7-0$~$&iCAVzxUqHy`S^V zzU1w$t$A1z1OjP$U|f8G^Zn-qRtI)^P<=TF1a|Xqar93vTb)X|>$L~#Apy z*G9#Ir0AZ_L;FHlO?D;)qup`lKg3QmkD`*+`J~kCH806BTpf&1-+#m|l-EjXUga@( z9C|Qmto}-C^vEwR4TrAYLSGFPFAlaQ3cZ6Fu}`#(tUHhUln+4X_f#`G-xiv<*Ch(s zg7BeAS0r)tpZ#pMw}f~M@aeW6hKkXFp*$r-csCf~7|U;qCa}`IB*YnlF6Oq>j|tT% zf@CUOaX4IS)Q9qTcEU%_SMrTH_e!kB-fgt9(&EbNdCE}ZHxExDum1t(HHFitI})>@ zT3VVeMo%{KYVffwUaCf5RTbee1 zThr^OGfg=MPfbYv8X>JszT&i}Zi{Xh85wjKEldBhvypEVqyDve|zb*w%k*%O)tgUUz_{>u|BwRBu_H=jv`>?-r27c_1%xo}ac0D91P zuU*Lw-AnZ@Ki}*3PQwYFHA8cL>Yi`0IVRgv7+B!J6;#X2QD=D8rAc(b&gVGOLiuee z*&*5p(0?2b#9fwiG7uLsanZ9o1!1M)0f&W+19gU+4nCh=PDF(avc56qCsq8WVu zf&qpvV5kEcAsvB{YUQO5T-q&~z9^HrKw-^iStmsxJ#q&-LW%^h*^% z|F%=Kg>TGuv8L9AC(`Ysrr)3P|21@XXR8?ii*{;qhpObpZLjidS4e5sV<{9$eH0Un zNYTYu$zIV2;x;>D=lDVe@VvFP^*iW{bYo72QqFees<4pOss_FJ=$u}iYJkiQuYI-Q zjBtqlFfgD6VX>|+jRm?dn-n Date: Fri, 16 Mar 2012 13:43:48 -0700 Subject: [PATCH 118/238] create context in init list --- plugins/input/csv/csv_datasource.cpp | 4 ++-- plugins/input/raster/raster_featureset.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/input/csv/csv_datasource.cpp b/plugins/input/csv/csv_datasource.cpp index c65b47373..dc0b27c5e 100644 --- a/plugins/input/csv/csv_datasource.cpp +++ b/plugins/input/csv/csv_datasource.cpp @@ -46,7 +46,8 @@ csv_datasource::csv_datasource(parameters const& params, bool bind) manual_headers_(boost::trim_copy(*params_.get("headers", ""))), strict_(*params_.get("strict", false)), quiet_(*params_.get("quiet", false)), - filesize_max_(*params_.get("filesize_max", 20.0)) // MB + filesize_max_(*params_.get("filesize_max", 20.0)), // MB + ctx_(boost::make_shared()) { /* TODO: general: @@ -392,7 +393,6 @@ void csv_datasource::parse_csv(T& stream, bool extent_initialized = false; std::size_t num_headers = headers_.size(); - ctx_ = boost::make_shared(); for (std::size_t i = 0; i < headers_.size(); ++i) { ctx_->push(headers_[i]); diff --git a/plugins/input/raster/raster_featureset.cpp b/plugins/input/raster/raster_featureset.cpp index 3abc6d72e..2d08d117b 100644 --- a/plugins/input/raster/raster_featureset.cpp +++ b/plugins/input/raster/raster_featureset.cpp @@ -48,9 +48,9 @@ raster_featureset::raster_featureset(LookupPolicy const& policy, extent_(extent), bbox_(q.get_bbox()), curIter_(policy_.begin()), - endIter_(policy_.end()) + endIter_(policy_.end()), + ctx_(boost::make_shared()) { - ctx_ = boost::make_shared(); } template From cdc63767192a101903a24420605dee3d29f6b280 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Fri, 16 Mar 2012 14:49:56 -0700 Subject: [PATCH 119/238] be explicit about zero-division protection --- include/mapnik/ctrans.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/mapnik/ctrans.hpp b/include/mapnik/ctrans.hpp index 014073906..31c62f391 100644 --- a/include/mapnik/ctrans.hpp +++ b/include/mapnik/ctrans.hpp @@ -397,9 +397,9 @@ public: sx_(1.0), sy_(1.0) { - if (extent_.width()) + if (extent_.width() > 0) sx_ = static_cast(width_) / extent_.width(); - if (extent_.height()) + if (extent_.height() > 0) sy_ = static_cast(height_) / extent_.height(); } From 1542e3e5137daf370a687901287d9d4108f3a118 Mon Sep 17 00:00:00 2001 From: ldp Date: Sun, 18 Mar 2012 22:35:02 +0100 Subject: [PATCH 120/238] add ignore-placement to MarkersSymbolizer --- include/mapnik/markers_symbolizer.hpp | 3 +++ src/agg/process_markers_symbolizer.cpp | 3 ++- src/load_map.cpp | 2 ++ src/markers_symbolizer.cpp | 13 +++++++++++++ 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/include/mapnik/markers_symbolizer.hpp b/include/mapnik/markers_symbolizer.hpp index b6e78a8c2..ccd249da9 100644 --- a/include/mapnik/markers_symbolizer.hpp +++ b/include/mapnik/markers_symbolizer.hpp @@ -56,6 +56,8 @@ public: explicit markers_symbolizer(); markers_symbolizer(path_expression_ptr filename); markers_symbolizer(markers_symbolizer const& rhs); + void set_ignore_placement(bool ignore_placement); + bool get_ignore_placement() const; void set_allow_overlap(bool overlap); bool get_allow_overlap() const; void set_spacing(double spacing); @@ -76,6 +78,7 @@ public: marker_type_e get_marker_type() const; private: + bool ignore_placement_; bool allow_overlap_; color fill_; double spacing_; diff --git a/src/agg/process_markers_symbolizer.cpp b/src/agg/process_markers_symbolizer.cpp index 57a01448e..ae7725bf1 100644 --- a/src/agg/process_markers_symbolizer.cpp +++ b/src/agg/process_markers_symbolizer.cpp @@ -244,7 +244,8 @@ void agg_renderer::process(markers_symbolizer const& sym, ren.color(agg::rgba8(s_r, s_g, s_b, int(s_a*stroke_.get_opacity()))); agg::render_scanlines(*ras_ptr, sl_line, ren); } - detector_->insert(label_ext); + if (!sym.get_ignore_placement()) + detector_->insert(label_ext); if (writer.first) writer.first->add_box(label_ext, *feature, t_, writer.second); } } diff --git a/src/load_map.cpp b/src/load_map.cpp index 2f77db471..38bcfbff7 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -896,7 +896,9 @@ void map_parser::parse_markers_symbolizer(rule & rule, xml_node const& sym) optional max_error = sym.get_opt_attr("max-error"); if (max_error) symbol.set_max_error(*max_error); optional allow_overlap = sym.get_opt_attr("allow-overlap"); + optional ignore_placement = sym.get_opt_attr("ignore-placement"); if (allow_overlap) symbol.set_allow_overlap(*allow_overlap); + if (ignore_placement) symbol.set_ignore_placement(*ignore_placement); optional w = sym.get_opt_attr("width"); optional h = sym.get_opt_attr("height"); diff --git a/src/markers_symbolizer.cpp b/src/markers_symbolizer.cpp index 02afffdd4..a2ccd70ab 100644 --- a/src/markers_symbolizer.cpp +++ b/src/markers_symbolizer.cpp @@ -47,6 +47,7 @@ markers_symbolizer::markers_symbolizer() : symbolizer_with_image(path_expression_ptr(new path_expression)), symbolizer_base(), allow_overlap_(false), + ignore_placement_(false), fill_(color(0,0,255)), spacing_(100.0), max_error_(0.2), @@ -60,6 +61,7 @@ markers_symbolizer::markers_symbolizer(path_expression_ptr filename) : symbolizer_with_image(filename), symbolizer_base(), allow_overlap_(false), + ignore_placement_(false), fill_(color(0,0,255)), spacing_(100.0), max_error_(0.2), @@ -73,6 +75,7 @@ markers_symbolizer::markers_symbolizer(markers_symbolizer const& rhs) : symbolizer_with_image(rhs), symbolizer_base(rhs), allow_overlap_(rhs.allow_overlap_), + ignore_placement_(rhs.ignore_placement_), fill_(rhs.fill_), spacing_(rhs.spacing_), max_error_(rhs.max_error_), @@ -82,6 +85,16 @@ markers_symbolizer::markers_symbolizer(markers_symbolizer const& rhs) marker_p_(rhs.marker_p_), marker_type_(rhs.marker_type_) {} +void markers_symbolizer::set_ignore_placement(bool ignore_placement) +{ + ignore_placement_ = ignore_placement; +} + +bool markers_symbolizer::get_ignore_placement() const +{ + return ignore_placement_; +} + void markers_symbolizer::set_allow_overlap(bool overlap) { allow_overlap_ = overlap; From 0ecd5bae45c79f02d72d8f59b54158d9fa72713c Mon Sep 17 00:00:00 2001 From: novldp Date: Sun, 18 Mar 2012 23:30:37 +0100 Subject: [PATCH 121/238] +add MarkersSymbolizer ignore-placement to save_map and python bindings --- bindings/python/mapnik_markers_symbolizer.cpp | 9 +++++++-- src/save_map.cpp | 4 ++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/bindings/python/mapnik_markers_symbolizer.cpp b/bindings/python/mapnik_markers_symbolizer.cpp index df0ae4606..999bb40e6 100644 --- a/bindings/python/mapnik_markers_symbolizer.cpp +++ b/bindings/python/mapnik_markers_symbolizer.cpp @@ -60,7 +60,8 @@ struct markers_symbolizer_pickle_suite : boost::python::pickle_suite static boost::python::tuple getstate(markers_symbolizer const& p) { - return boost::python::make_tuple(p.get_allow_overlap());//,p.get_opacity()); + return boost::python::make_tuple(p.get_allow_overlap(), + p.get_ignore_placement());//,p.get_opacity()); } static void @@ -77,7 +78,8 @@ struct markers_symbolizer_pickle_suite : boost::python::pickle_suite } p.set_allow_overlap(extract(state[0])); - //p.set_opacity(extract(state[1])); + p.set_ignore_placement(extract(state[1])); + //p.set_opacity(extract(state[2])); } @@ -108,6 +110,9 @@ void export_markers_symbolizer() &markers_symbolizer::get_opacity, &markers_symbolizer::set_opacity, "Set/get the text opacity") + .add_property("ignore_placement", + &markers_symbolizer::get_ignore_placement, + &markers_symbolizer::set_ignore_placement) .add_property("transform", &mapnik::get_svg_transform, &mapnik::set_svg_transform) diff --git a/src/save_map.cpp b/src/save_map.cpp index a890973ce..6aa9fe5d5 100644 --- a/src/save_map.cpp +++ b/src/save_map.cpp @@ -273,6 +273,10 @@ public: { set_attr( sym_node, "allow-overlap", sym.get_allow_overlap() ); } + if (sym.get_ignore_placement() != dfl.get_ignore_placement() || explicit_defaults_) + { + set_attr( sym_node, "ignore-placement", sym.get_ignore_placement() ); + } if (sym.get_spacing() != dfl.get_spacing() || explicit_defaults_) { set_attr( sym_node, "spacing", sym.get_spacing() ); From aef214292ecd67af8915b8f57d8291299a43808b Mon Sep 17 00:00:00 2001 From: Artem Pavlenko Date: Mon, 19 Mar 2012 10:11:55 +0000 Subject: [PATCH 122/238] don't rely on compiler for-loop optimisation --- include/mapnik/font_engine_freetype.hpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/include/mapnik/font_engine_freetype.hpp b/include/mapnik/font_engine_freetype.hpp index ffb0950e5..54baaa04d 100644 --- a/include/mapnik/font_engine_freetype.hpp +++ b/include/mapnik/font_engine_freetype.hpp @@ -168,13 +168,14 @@ public: glyph_ptr get_glyph(unsigned c) const { - for (std::vector::const_iterator face = faces_.begin(); face != faces_.end(); ++face) + std::vector::const_iterator face = faces_.begin(); + std::vector::const_iterator end = faces_.end(); + for (; face != end; ++face) { FT_UInt g = (*face)->get_char(c); - if (g) return boost::make_shared(*face, g); } - + // Final fallback to empty square if nothing better in any font return boost::make_shared(*faces_.begin(), 0); } From e06c4567895732e2c7d8f3a91f02b3b16b43b88e Mon Sep 17 00:00:00 2001 From: Artem Pavlenko Date: Mon, 19 Mar 2012 10:46:44 +0000 Subject: [PATCH 123/238] even better, use BOOST_FOREACH --- include/mapnik/font_engine_freetype.hpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/include/mapnik/font_engine_freetype.hpp b/include/mapnik/font_engine_freetype.hpp index 54baaa04d..5b4b87b40 100644 --- a/include/mapnik/font_engine_freetype.hpp +++ b/include/mapnik/font_engine_freetype.hpp @@ -46,6 +46,7 @@ extern "C" #include #include #include +#include #ifdef MAPNIK_THREADSAFE #include #endif @@ -168,12 +169,10 @@ public: glyph_ptr get_glyph(unsigned c) const { - std::vector::const_iterator face = faces_.begin(); - std::vector::const_iterator end = faces_.end(); - for (; face != end; ++face) + BOOST_FOREACH ( face_ptr const& face, faces_) { - FT_UInt g = (*face)->get_char(c); - if (g) return boost::make_shared(*face, g); + FT_UInt g = face->get_char(c); + if (g) return boost::make_shared(face, g); } // Final fallback to empty square if nothing better in any font @@ -186,17 +185,17 @@ public: void set_pixel_sizes(unsigned size) { - for (std::vector::iterator face = faces_.begin(); face != faces_.end(); ++face) + BOOST_FOREACH ( face_ptr const& face, faces_) { - (*face)->set_pixel_sizes(size); + face->set_pixel_sizes(size); } } void set_character_sizes(float size) { - for (std::vector::iterator face = faces_.begin(); face != faces_.end(); ++face) + BOOST_FOREACH ( face_ptr const& face, faces_) { - (*face)->set_character_sizes(size); + face->set_character_sizes(size); } } private: From 9b62a19cf42b3daab1da50f2e89352edeb031644 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Mon, 19 Mar 2012 17:12:53 +0100 Subject: [PATCH 124/238] Implement justify-alignment=auto. Closes #1125. --- bindings/python/mapnik_text_placement.cpp | 1 + include/mapnik/placement_finder.hpp | 1 + include/mapnik/text_properties.hpp | 1 + src/placement_finder.cpp | 44 ++++++++++++++---- src/text_properties.cpp | 2 +- src/text_symbolizer.cpp | 1 + .../jalign-auto-200-reference.png | Bin 0 -> 2316 bytes tests/visual_tests/jalign-auto.xml | 30 ++++++++++++ tests/visual_tests/test.py | 3 +- 9 files changed, 71 insertions(+), 12 deletions(-) create mode 100644 tests/visual_tests/jalign-auto-200-reference.png create mode 100644 tests/visual_tests/jalign-auto.xml diff --git a/bindings/python/mapnik_text_placement.cpp b/bindings/python/mapnik_text_placement.cpp index c9f1ac4e5..13de8478b 100644 --- a/bindings/python/mapnik_text_placement.cpp +++ b/bindings/python/mapnik_text_placement.cpp @@ -329,6 +329,7 @@ void export_text_placement() .value("LEFT",J_LEFT) .value("MIDDLE",J_MIDDLE) .value("RIGHT",J_RIGHT) + .value("AUTO", J_AUTO) ; enumeration_("text_transform") diff --git a/include/mapnik/placement_finder.hpp b/include/mapnik/placement_finder.hpp index 1ee636349..57d6ee2ab 100644 --- a/include/mapnik/placement_finder.hpp +++ b/include/mapnik/placement_finder.hpp @@ -137,6 +137,7 @@ private: double first_line_space_; vertical_alignment_e valign_; horizontal_alignment_e halign_; + justify_alignment_e jalign_; std::vector line_breaks_; std::vector > line_sizes_; std::queue< box2d > envelopes_; diff --git a/include/mapnik/text_properties.hpp b/include/mapnik/text_properties.hpp index 9f12b3663..7b6ab953a 100644 --- a/include/mapnik/text_properties.hpp +++ b/include/mapnik/text_properties.hpp @@ -110,6 +110,7 @@ enum justify_alignment J_LEFT = 0, J_MIDDLE, J_RIGHT, + J_AUTO, justify_alignment_MAX }; diff --git a/src/placement_finder.cpp b/src/placement_finder.cpp index 719b56dee..5419cb8b6 100644 --- a/src/placement_finder.cpp +++ b/src/placement_finder.cpp @@ -305,23 +305,47 @@ template void placement_finder::init_alignment() { valign_ = p.valign; - if (valign_ == V_AUTO) { + if (valign_ == V_AUTO) + { if (p.displacement.second > 0.0) + { valign_ = V_BOTTOM; - else if (p.displacement.second < 0.0) + } else if (p.displacement.second < 0.0) + { valign_ = V_TOP; - else + } else + { valign_ = V_MIDDLE; + } } halign_ = p.halign; - if (halign_ == H_AUTO) { + if (halign_ == H_AUTO) + { if (p.displacement.first > 0.0) + { halign_ = H_RIGHT; - else if (p.displacement.first < 0.0) + } else if (p.displacement.first < 0.0) + { halign_ = H_LEFT; - else + } else + { halign_ = H_MIDDLE; + } + } + + jalign_ = p.jalign; + if (jalign_ == J_AUTO) + { + if (p.displacement.first > 0.0) + { + jalign_ = J_LEFT; + } else if (p.displacement.first < 0.0) + { + jalign_ = J_RIGHT; + } else { + jalign_ = J_MIDDLE; + } } } @@ -389,9 +413,9 @@ void placement_finder::find_point_placement(double label_x, if (info_.get_rtl()) y = -y; // adjust for desired justification - if (p.jalign == J_LEFT) + if (jalign_ == J_LEFT) x = -(string_width_ / 2.0); - else if (p.jalign == J_RIGHT) + else if (jalign_ == J_RIGHT) x = (string_width_ / 2.0) - line_width; else /* J_MIDDLE */ x = -(line_width / 2.0); @@ -420,9 +444,9 @@ void placement_finder::find_point_placement(double label_x, } // reset to begining of line position - if (p.jalign == J_LEFT) + if (jalign_ == J_LEFT) x = -(string_width_ / 2.0); - else if (p.jalign == J_RIGHT) + else if (jalign_ == J_RIGHT) x = (string_width_ / 2.0) - line_width; else x = -(line_width / 2.0); diff --git a/src/text_properties.cpp b/src/text_properties.cpp index 4587cb5e4..89e4ee6cd 100644 --- a/src/text_properties.cpp +++ b/src/text_properties.cpp @@ -40,7 +40,7 @@ text_symbolizer_properties::text_symbolizer_properties() : displacement(0,0), label_placement(POINT_PLACEMENT), halign(H_AUTO), - jalign(J_MIDDLE), + jalign(J_AUTO), valign(V_AUTO), label_spacing(0), label_position_tolerance(0), diff --git a/src/text_symbolizer.cpp b/src/text_symbolizer.cpp index c060e43d1..75e76849b 100644 --- a/src/text_symbolizer.cpp +++ b/src/text_symbolizer.cpp @@ -69,6 +69,7 @@ static const char * justify_alignment_strings[] = { "left", "center", "right", + "auto", "" }; diff --git a/tests/visual_tests/jalign-auto-200-reference.png b/tests/visual_tests/jalign-auto-200-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..6acf39cdcda902403d7997b85918ab1092898dfa GIT binary patch literal 2316 zcmd5;Yc$(w9u7*CYHeGlrD+*dl^Rv0?xE5!BwA7FP)w=1Z;qPQR4T%Z7S%;f-APdq zRI+Yy>1b-)gLEng73{VWl1Mn@LNx5(?0lF#XV2Nw`LrM2%kR9u_dL(<^1SEGxPpLb zsOhMIKp+hdcQ+qk9o!wND!|-uom372sWLp=TztPT70kljeFKjV#3jVO|61L|B)y5K zYDr7a^m~|6|Nh92N6gO{W?H+rKv!}|Pv59krq1<7>ECoyhU&bAv|7XdB)I2a1G_i^(f1^fJs_hZ;9fR^39d4M_Evg2Mhidn`0H-#{QB zrT;JEQjo%?m|wtZk2BzZWYl=yZ`fFTi#OPoY_5k-x1ob&wkCn}ANC$x5YE2hY%MY8 z)5gh-KcOJlPaFF73BvEPuhshvRYD(+?Z_p9nbLxF&Zuq?aF9-=Chr8@9xXNse*GY~ z3YN`bw7kqW4?I(kBGWrbUZP=be`>!^cg=NU@AB``=px~)z3si_!sWTH8SuVwfC+nz zJWYPD@KA7z^)YkBZ#<0`$>)}ReNpAWXbH(2di zFmg9+{nO_9&uR$j2Og^NzIr;=fsS_$HowuXckrasPkoQS7zX$(Y)My|ZVeqmwUpSP zX_;E4EZCBW%V2NIx7L=Yn?;sB%HEDttla;UcexoxDtrcvpnSpZI&FeuAPIdK@0(LvfrdAaR9o5hsqr>V>?dF)NBgL zIhNIa^)|iKB<~db4%9h8cwDX%4urj8qb43)U1NK0N(3b<&}Q?=3!L`614mU=ztz*j z=@W4qCuY_ch7Q|eBrk7t-03^ZFz)hXO-ww)B3#LbxbTkrd~GhDzdruUPMlm;oSCa z$f_skt6Azf^-Gq*XkK>LS0)<*=H3^pxx-+Obg}Q|=7c0EKS`X) zW+ZWZG^YN*D6y_?ZIWIXrBkV=^dUYbdrazGW;dZxv1+81GajFm)aC2v*9pG7V!K4I z5;2}rD1LVK_SLnu9#K(IBWxaq$K(CndY(}H@bm`G5Ij?L$p}DS{M)iqn0oq&A|#DQ zV>QpMjxql38!$XP40AmD4(v1F97#>es0CO%>3IOGO}Yv2lFKX?iGBdYKOlfXu2HP) z=@mzmO;1)-RQzR_FBIOJcb08!by0ppqTv=SDKMLb{IdbKwj=^*WHbqA!8@G!<|i&Z zf#6wYX6DUHL%$(pTI%u8p%@{n!@$VMNJxBHV=Ma=Uo3kX4N*~1VXc^2ot6;C^Sww z9*Qvc@<%3&oNDoNb;!o)Ztr@LdML=sA8{Gg`0?# zTYyvdUcf1@f-vN^>qG}%fS;n_P6Im)U`NC)nD#e1R#XvspkU4CQ~&Pi&x`i=JNL)Z zu&D^M9C!VOQ)$0D)#e$+pALh2&ZQ`OwP;uCGGtlCYT zVq;^+7>vA#ZxYkoXNU!UcO`^Bis#%2x9sx;bEv0ut`_&AoX_W1H#Ln3EE38^?P^wb%N{Wa3kwr|gZ`cydA+WI z&1P4X=Pw*vM8c!=S)sciukp-C5t#m|8UCPh=_>Zwa! + + + + + My Style + + shape + points.shp + + + + + + diff --git a/tests/visual_tests/test.py b/tests/visual_tests/test.py index 804137c0b..8d9d3166f 100755 --- a/tests/visual_tests/test.py +++ b/tests/visual_tests/test.py @@ -28,7 +28,8 @@ files = [ ("formating-4", 500), ("shieldsymbolizer-1", 490, 495, 497, 498, 499, 500, 501, 502, 505, 510), ("expressionformat", 500), - ("rtl-point", (200, 200)) + ("rtl-point", (200, 200)), + ("jalign-auto", (200, 200)) ] def render(filename, width, height=100): From ad5bcb70df903f49ff824d46841d0c08faa3a3e0 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Mon, 19 Mar 2012 17:37:21 +0100 Subject: [PATCH 125/238] Update changelog. --- CHANGELOG | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 8dfa52f59..de227fb68 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -26,7 +26,9 @@ Mapnik 2.1.0 - Added parameter in OGR plugin to select a layer by SQL query (besides name or index): see http://www.gdal.org/ogr/ogr_sql.html for specifications (kunitoki) (#472) -- Added suppport for output maps as tiff files (addresses #967 partially) +- Added support for output maps as tiff files (addresses #967 partially) + +- Added support for justify-alignment=auto. This is the new default. (#1125) Mapnik 2.0.0 From a84fcdb1331ee27caa806bdc20705f0175abe127 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Mon, 19 Mar 2012 17:40:51 +0100 Subject: [PATCH 126/238] Update tests. Refs #1125. --- tests/python_tests/object_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/python_tests/object_test.py b/tests/python_tests/object_test.py index 9c9787f4b..cf0920d55 100644 --- a/tests/python_tests/object_test.py +++ b/tests/python_tests/object_test.py @@ -54,7 +54,7 @@ def test_shieldsymbolizer_init(): # r1341 eq_(s.wrap_before, False) eq_(s.horizontal_alignment, mapnik.horizontal_alignment.AUTO) - eq_(s.justify_alignment, mapnik.justify_alignment.MIDDLE) + eq_(s.justify_alignment, mapnik.justify_alignment.AUTO) eq_(s.opacity, 1.0) # r2300 From b2ca10f3a0996fdf6b954f59d687f22fd63824fe Mon Sep 17 00:00:00 2001 From: Artem Pavlenko Date: Mon, 19 Mar 2012 21:19:52 +0000 Subject: [PATCH 127/238] + always premultiply destination buffer for correct ops (ensure no color component exceeds alpha channel) --- include/mapnik/image_compositing.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/mapnik/image_compositing.hpp b/include/mapnik/image_compositing.hpp index c6a334ac0..9dff58045 100644 --- a/include/mapnik/image_compositing.hpp +++ b/include/mapnik/image_compositing.hpp @@ -84,6 +84,7 @@ void composite(T1 & im, T2 & im2, composite_mode_e mode) agg::rendering_buffer mask(im2.getBytes(),im2.width(),im2.height(),im2.width() * 4); agg::pixfmt_custom_blend_rgba pixf(source); + pixf.premultiply(); agg::pixfmt_custom_blend_rgba pixf_mask(mask); switch(mode) From 4843572e1b59ac430da7631405e7747f6de2e342 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Mon, 19 Mar 2012 15:42:30 -0700 Subject: [PATCH 128/238] move image_compositing to cpp file --- include/mapnik/image_compositing.hpp | 117 +-------------------------- src/build.py | 1 + 2 files changed, 2 insertions(+), 116 deletions(-) diff --git a/include/mapnik/image_compositing.hpp b/include/mapnik/image_compositing.hpp index 9dff58045..92320c4f8 100644 --- a/include/mapnik/image_compositing.hpp +++ b/include/mapnik/image_compositing.hpp @@ -24,11 +24,6 @@ #define MAPNIK_IMAGE_COMPOSITING_HPP // agg -#include "agg_rendering_buffer.h" -#include "agg_rasterizer_scanline_aa.h" -#include "agg_scanline_u.h" -#include "agg_renderer_scanline.h" -#include "agg_pixfmt_rgba.h" namespace mapnik { @@ -69,117 +64,7 @@ enum composite_mode_e }; template -void composite(T1 & im, T2 & im2, composite_mode_e mode) -{ - typedef agg::rgba8 color; - typedef agg::order_bgra order; - typedef agg::pixel32_type pixel_type; - typedef agg::comp_op_adaptor_rgba blender_type; - typedef agg::pixfmt_custom_blend_rgba pixfmt_type; - typedef agg::renderer_base renderer_type; - typedef agg::comp_op_adaptor_rgba blender_type; - typedef agg::renderer_base renderer_type; - - agg::rendering_buffer source(im.getBytes(),im.width(),im.height(),im.width() * 4); - agg::rendering_buffer mask(im2.getBytes(),im2.width(),im2.height(),im2.width() * 4); - - agg::pixfmt_custom_blend_rgba pixf(source); - pixf.premultiply(); - agg::pixfmt_custom_blend_rgba pixf_mask(mask); - - switch(mode) - { - case clear : - pixf.comp_op(agg::comp_op_clear); - break; - case src: - pixf.comp_op(agg::comp_op_src); - break; - case dst: - pixf.comp_op(agg::comp_op_dst); - break; - case src_over: - pixf.comp_op(agg::comp_op_src_over); - break; - case dst_over: - pixf.comp_op(agg::comp_op_dst_over); - break; - case src_in: - pixf.comp_op(agg::comp_op_src_in); - break; - case dst_in: - pixf.comp_op(agg::comp_op_dst_in); - break; - case src_out: - pixf.comp_op(agg::comp_op_src_out); - break; - case dst_out: - pixf.comp_op(agg::comp_op_dst_out); - break; - case src_atop: - pixf.comp_op(agg::comp_op_src_atop); - break; - case dst_atop: - pixf.comp_op(agg::comp_op_dst_atop); - break; - case _xor: - pixf.comp_op(agg::comp_op_xor); - break; - case plus: - pixf.comp_op(agg::comp_op_plus); - break; - case minus: - pixf.comp_op(agg::comp_op_minus); - break; - case multiply: - pixf.comp_op(agg::comp_op_multiply); - break; - case screen: - pixf.comp_op(agg::comp_op_screen); - break; - case overlay: - pixf.comp_op(agg::comp_op_overlay); - break; - case darken: - pixf.comp_op(agg::comp_op_darken); - break; - case lighten: - pixf.comp_op(agg::comp_op_lighten); - break; - case color_dodge: - pixf.comp_op(agg::comp_op_color_dodge); - break; - case color_burn: - pixf.comp_op(agg::comp_op_color_burn); - break; - case hard_light: - pixf.comp_op(agg::comp_op_hard_light); - break; - case soft_light: - pixf.comp_op(agg::comp_op_soft_light); - break; - case difference: - pixf.comp_op(agg::comp_op_difference); - break; - case exclusion: - pixf.comp_op(agg::comp_op_exclusion); - break; - case contrast: - pixf.comp_op(agg::comp_op_contrast); - break; - case invert: - pixf.comp_op(agg::comp_op_invert); - break; - case invert_rgb: - pixf.comp_op(agg::comp_op_invert_rgb); - break; - } - renderer_type ren(pixf); - agg::renderer_base rb(pixf); - rb.blend_from(pixf_mask,0,0,0,255); +void composite(T1 & im, T2 & im2, composite_mode_e mode); } - -} - #endif // MAPNIK_IMAGE_COMPOSITING_HPP diff --git a/src/build.py b/src/build.py index 25a63496c..59bf949ca 100644 --- a/src/build.py +++ b/src/build.py @@ -105,6 +105,7 @@ source = Split( """ color.cpp conversions.cpp + image_compositing.cpp box2d.cpp building_symbolizer.cpp datasource_cache.cpp From 89877edc670e5421754dacb7c008ea6fdac6bb51 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Mon, 19 Mar 2012 15:42:44 -0700 Subject: [PATCH 129/238] move image_compositing to cpp file --- src/image_compositing.cpp | 153 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 src/image_compositing.cpp diff --git a/src/image_compositing.cpp b/src/image_compositing.cpp new file mode 100644 index 000000000..e4dde9f6f --- /dev/null +++ b/src/image_compositing.cpp @@ -0,0 +1,153 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2011 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 + * + *****************************************************************************/ + +// mapnik +#include +#include + +// agg +#include "agg_rendering_buffer.h" +#include "agg_rasterizer_scanline_aa.h" +#include "agg_scanline_u.h" +#include "agg_renderer_scanline.h" +#include "agg_pixfmt_rgba.h" + + +namespace mapnik +{ + + +template +void composite(T1 & im, T2 & im2, composite_mode_e mode) +{ + typedef agg::rgba8 color; + typedef agg::order_bgra order; + typedef agg::pixel32_type pixel_type; + typedef agg::comp_op_adaptor_rgba blender_type; + typedef agg::pixfmt_custom_blend_rgba pixfmt_type; + typedef agg::renderer_base renderer_type; + typedef agg::comp_op_adaptor_rgba blender_type; + typedef agg::renderer_base renderer_type; + + agg::rendering_buffer source(im.getBytes(),im.width(),im.height(),im.width() * 4); + agg::rendering_buffer mask(im2.getBytes(),im2.width(),im2.height(),im2.width() * 4); + + agg::pixfmt_custom_blend_rgba pixf(source); + pixf.premultiply(); + agg::pixfmt_custom_blend_rgba pixf_mask(mask); + + switch(mode) + { + case clear : + pixf.comp_op(agg::comp_op_clear); + break; + case src: + pixf.comp_op(agg::comp_op_src); + break; + case dst: + pixf.comp_op(agg::comp_op_dst); + break; + case src_over: + pixf.comp_op(agg::comp_op_src_over); + break; + case dst_over: + pixf.comp_op(agg::comp_op_dst_over); + break; + case src_in: + pixf.comp_op(agg::comp_op_src_in); + break; + case dst_in: + pixf.comp_op(agg::comp_op_dst_in); + break; + case src_out: + pixf.comp_op(agg::comp_op_src_out); + break; + case dst_out: + pixf.comp_op(agg::comp_op_dst_out); + break; + case src_atop: + pixf.comp_op(agg::comp_op_src_atop); + break; + case dst_atop: + pixf.comp_op(agg::comp_op_dst_atop); + break; + case _xor: + pixf.comp_op(agg::comp_op_xor); + break; + case plus: + pixf.comp_op(agg::comp_op_plus); + break; + case minus: + pixf.comp_op(agg::comp_op_minus); + break; + case multiply: + pixf.comp_op(agg::comp_op_multiply); + break; + case screen: + pixf.comp_op(agg::comp_op_screen); + break; + case overlay: + pixf.comp_op(agg::comp_op_overlay); + break; + case darken: + pixf.comp_op(agg::comp_op_darken); + break; + case lighten: + pixf.comp_op(agg::comp_op_lighten); + break; + case color_dodge: + pixf.comp_op(agg::comp_op_color_dodge); + break; + case color_burn: + pixf.comp_op(agg::comp_op_color_burn); + break; + case hard_light: + pixf.comp_op(agg::comp_op_hard_light); + break; + case soft_light: + pixf.comp_op(agg::comp_op_soft_light); + break; + case difference: + pixf.comp_op(agg::comp_op_difference); + break; + case exclusion: + pixf.comp_op(agg::comp_op_exclusion); + break; + case contrast: + pixf.comp_op(agg::comp_op_contrast); + break; + case invert: + pixf.comp_op(agg::comp_op_invert); + break; + case invert_rgb: + pixf.comp_op(agg::comp_op_invert_rgb); + break; + } + renderer_type ren(pixf); + agg::renderer_base rb(pixf); + rb.blend_from(pixf_mask,0,0,0,255); +} + + +template void composite(mapnik::image_data_32 & im, mapnik::image_data_32 & im2, composite_mode_e mode); + +} From f7a387f8af3a58646a89225eeec6f1d481105ff7 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Tue, 20 Mar 2012 21:28:21 +0100 Subject: [PATCH 130/238] Change visual tests directory structure. --- tests/visual_tests/clean.sh | 4 +-- tests/visual_tests/{ => data}/lines.osm | 0 tests/visual_tests/{ => data}/points.dbf | Bin tests/visual_tests/{ => data}/points.osm | 0 tests/visual_tests/{ => data}/points.shp | Bin .../expressionformat-500-reference.png | Bin .../formating-1-500-reference.png | Bin .../formating-2-500-reference.png | Bin .../formating-3-500-reference.png | Bin .../formating-4-500-reference.png | Bin .../{ => images}/formating-500-reference.png | Bin .../jalign-auto-200-reference.png | Bin .../{ => images}/lines-1-200-reference.png | Bin .../{ => images}/lines-1-400-reference.png | Bin .../{ => images}/lines-1-600-reference.png | Bin .../{ => images}/lines-1-800-reference.png | Bin .../{ => images}/lines-2-200-reference.png | Bin .../{ => images}/lines-2-400-reference.png | Bin .../{ => images}/lines-2-600-reference.png | Bin .../{ => images}/lines-2-800-reference.png | Bin .../{ => images}/lines-3-200-reference.png | Bin .../{ => images}/lines-3-400-reference.png | Bin .../{ => images}/lines-3-600-reference.png | Bin .../{ => images}/lines-3-800-reference.png | Bin .../lines-shield-200-reference.png | Bin .../lines-shield-400-reference.png | Bin .../lines-shield-600-reference.png | Bin .../lines-shield-800-reference.png | Bin .../{ => images}/list-100-reference.png | Bin .../{ => images}/list-150-reference.png | Bin .../{ => images}/list-200-reference.png | Bin .../{ => images}/list-250-reference.png | Bin .../{ => images}/list-300-reference.png | Bin .../{ => images}/list-400-reference.png | Bin .../{ => images}/list-600-reference.png | Bin .../{ => images}/list-800-reference.png | Bin .../{ => images}/python-IfElse-reference.png | Bin .../{ => images}/python-MyText-reference.png | Bin .../python-TextNode-reference.png | Bin .../{ => images}/rtl-point-200-reference.png | Bin .../shieldsymbolizer-1-490-reference.png | Bin .../shieldsymbolizer-1-495-reference.png | Bin .../shieldsymbolizer-1-497-reference.png | Bin .../shieldsymbolizer-1-498-reference.png | Bin .../shieldsymbolizer-1-499-reference.png | Bin .../shieldsymbolizer-1-500-reference.png | Bin .../shieldsymbolizer-1-501-reference.png | Bin .../shieldsymbolizer-1-502-reference.png | Bin .../shieldsymbolizer-1-505-reference.png | Bin .../shieldsymbolizer-1-510-reference.png | Bin .../{ => images}/simple-100-reference.png | Bin .../{ => images}/simple-150-reference.png | Bin .../{ => images}/simple-200-reference.png | Bin .../{ => images}/simple-250-reference.png | Bin .../{ => images}/simple-300-reference.png | Bin .../{ => images}/simple-400-reference.png | Bin .../{ => images}/simple-600-reference.png | Bin .../{ => images}/simple-800-reference.png | Bin .../{ => images}/simple-E-500-reference.png | Bin .../{ => images}/simple-N-500-reference.png | Bin .../{ => images}/simple-NE-500-reference.png | Bin .../{ => images}/simple-NW-500-reference.png | Bin .../{ => images}/simple-S-500-reference.png | Bin .../{ => images}/simple-SE-500-reference.png | Bin .../{ => images}/simple-SW-500-reference.png | Bin .../{ => images}/simple-W-500-reference.png | Bin .../{ => styles}/expressionformat.xml | 2 +- .../visual_tests/{ => styles}/formating-1.xml | 2 +- .../visual_tests/{ => styles}/formating-2.xml | 2 +- .../visual_tests/{ => styles}/formating-3.xml | 2 +- .../visual_tests/{ => styles}/formating-4.xml | 2 +- tests/visual_tests/{ => styles}/formating.xml | 2 +- .../visual_tests/{ => styles}/jalign-auto.xml | 2 +- tests/visual_tests/{ => styles}/lines-1.xml | 2 +- tests/visual_tests/{ => styles}/lines-2.xml | 2 +- tests/visual_tests/{ => styles}/lines-3.xml | 2 +- .../{ => styles}/lines-shield.xml | 4 +-- tests/visual_tests/{ => styles}/list.xml | 4 +-- tests/visual_tests/{ => styles}/rtl-point.xml | 2 +- .../{ => styles}/shieldsymbolizer-1.xml | 24 +++++++++--------- tests/visual_tests/{ => styles}/simple-E.xml | 2 +- tests/visual_tests/{ => styles}/simple-N.xml | 2 +- tests/visual_tests/{ => styles}/simple-NE.xml | 2 +- tests/visual_tests/{ => styles}/simple-NW.xml | 2 +- tests/visual_tests/{ => styles}/simple-S.xml | 2 +- tests/visual_tests/{ => styles}/simple-SE.xml | 2 +- tests/visual_tests/{ => styles}/simple-SW.xml | 2 +- tests/visual_tests/{ => styles}/simple-W.xml | 2 +- tests/visual_tests/{ => styles}/simple.xml | 6 +---- tests/visual_tests/test.py | 6 ++--- tests/visual_tests/test_python.py | 6 +++-- 91 files changed, 45 insertions(+), 47 deletions(-) rename tests/visual_tests/{ => data}/lines.osm (100%) rename tests/visual_tests/{ => data}/points.dbf (100%) rename tests/visual_tests/{ => data}/points.osm (100%) rename tests/visual_tests/{ => data}/points.shp (100%) rename tests/visual_tests/{ => images}/expressionformat-500-reference.png (100%) rename tests/visual_tests/{ => images}/formating-1-500-reference.png (100%) rename tests/visual_tests/{ => images}/formating-2-500-reference.png (100%) rename tests/visual_tests/{ => images}/formating-3-500-reference.png (100%) rename tests/visual_tests/{ => images}/formating-4-500-reference.png (100%) rename tests/visual_tests/{ => images}/formating-500-reference.png (100%) rename tests/visual_tests/{ => images}/jalign-auto-200-reference.png (100%) rename tests/visual_tests/{ => images}/lines-1-200-reference.png (100%) rename tests/visual_tests/{ => images}/lines-1-400-reference.png (100%) rename tests/visual_tests/{ => images}/lines-1-600-reference.png (100%) rename tests/visual_tests/{ => images}/lines-1-800-reference.png (100%) rename tests/visual_tests/{ => images}/lines-2-200-reference.png (100%) rename tests/visual_tests/{ => images}/lines-2-400-reference.png (100%) rename tests/visual_tests/{ => images}/lines-2-600-reference.png (100%) rename tests/visual_tests/{ => images}/lines-2-800-reference.png (100%) rename tests/visual_tests/{ => images}/lines-3-200-reference.png (100%) rename tests/visual_tests/{ => images}/lines-3-400-reference.png (100%) rename tests/visual_tests/{ => images}/lines-3-600-reference.png (100%) rename tests/visual_tests/{ => images}/lines-3-800-reference.png (100%) rename tests/visual_tests/{ => images}/lines-shield-200-reference.png (100%) rename tests/visual_tests/{ => images}/lines-shield-400-reference.png (100%) rename tests/visual_tests/{ => images}/lines-shield-600-reference.png (100%) rename tests/visual_tests/{ => images}/lines-shield-800-reference.png (100%) rename tests/visual_tests/{ => images}/list-100-reference.png (100%) rename tests/visual_tests/{ => images}/list-150-reference.png (100%) rename tests/visual_tests/{ => images}/list-200-reference.png (100%) rename tests/visual_tests/{ => images}/list-250-reference.png (100%) rename tests/visual_tests/{ => images}/list-300-reference.png (100%) rename tests/visual_tests/{ => images}/list-400-reference.png (100%) rename tests/visual_tests/{ => images}/list-600-reference.png (100%) rename tests/visual_tests/{ => images}/list-800-reference.png (100%) rename tests/visual_tests/{ => images}/python-IfElse-reference.png (100%) rename tests/visual_tests/{ => images}/python-MyText-reference.png (100%) rename tests/visual_tests/{ => images}/python-TextNode-reference.png (100%) rename tests/visual_tests/{ => images}/rtl-point-200-reference.png (100%) rename tests/visual_tests/{ => images}/shieldsymbolizer-1-490-reference.png (100%) rename tests/visual_tests/{ => images}/shieldsymbolizer-1-495-reference.png (100%) rename tests/visual_tests/{ => images}/shieldsymbolizer-1-497-reference.png (100%) rename tests/visual_tests/{ => images}/shieldsymbolizer-1-498-reference.png (100%) rename tests/visual_tests/{ => images}/shieldsymbolizer-1-499-reference.png (100%) rename tests/visual_tests/{ => images}/shieldsymbolizer-1-500-reference.png (100%) rename tests/visual_tests/{ => images}/shieldsymbolizer-1-501-reference.png (100%) rename tests/visual_tests/{ => images}/shieldsymbolizer-1-502-reference.png (100%) rename tests/visual_tests/{ => images}/shieldsymbolizer-1-505-reference.png (100%) rename tests/visual_tests/{ => images}/shieldsymbolizer-1-510-reference.png (100%) rename tests/visual_tests/{ => images}/simple-100-reference.png (100%) rename tests/visual_tests/{ => images}/simple-150-reference.png (100%) rename tests/visual_tests/{ => images}/simple-200-reference.png (100%) rename tests/visual_tests/{ => images}/simple-250-reference.png (100%) rename tests/visual_tests/{ => images}/simple-300-reference.png (100%) rename tests/visual_tests/{ => images}/simple-400-reference.png (100%) rename tests/visual_tests/{ => images}/simple-600-reference.png (100%) rename tests/visual_tests/{ => images}/simple-800-reference.png (100%) rename tests/visual_tests/{ => images}/simple-E-500-reference.png (100%) rename tests/visual_tests/{ => images}/simple-N-500-reference.png (100%) rename tests/visual_tests/{ => images}/simple-NE-500-reference.png (100%) rename tests/visual_tests/{ => images}/simple-NW-500-reference.png (100%) rename tests/visual_tests/{ => images}/simple-S-500-reference.png (100%) rename tests/visual_tests/{ => images}/simple-SE-500-reference.png (100%) rename tests/visual_tests/{ => images}/simple-SW-500-reference.png (100%) rename tests/visual_tests/{ => images}/simple-W-500-reference.png (100%) rename tests/visual_tests/{ => styles}/expressionformat.xml (90%) rename tests/visual_tests/{ => styles}/formating-1.xml (91%) rename tests/visual_tests/{ => styles}/formating-2.xml (91%) rename tests/visual_tests/{ => styles}/formating-3.xml (91%) rename tests/visual_tests/{ => styles}/formating-4.xml (89%) rename tests/visual_tests/{ => styles}/formating.xml (91%) rename tests/visual_tests/{ => styles}/jalign-auto.xml (96%) rename tests/visual_tests/{ => styles}/lines-1.xml (89%) rename tests/visual_tests/{ => styles}/lines-2.xml (90%) rename tests/visual_tests/{ => styles}/lines-3.xml (90%) rename tests/visual_tests/{ => styles}/lines-shield.xml (76%) rename tests/visual_tests/{ => styles}/list.xml (85%) rename tests/visual_tests/{ => styles}/rtl-point.xml (92%) rename tests/visual_tests/{ => styles}/shieldsymbolizer-1.xml (58%) rename tests/visual_tests/{ => styles}/simple-E.xml (90%) rename tests/visual_tests/{ => styles}/simple-N.xml (90%) rename tests/visual_tests/{ => styles}/simple-NE.xml (90%) rename tests/visual_tests/{ => styles}/simple-NW.xml (90%) rename tests/visual_tests/{ => styles}/simple-S.xml (90%) rename tests/visual_tests/{ => styles}/simple-SE.xml (90%) rename tests/visual_tests/{ => styles}/simple-SW.xml (90%) rename tests/visual_tests/{ => styles}/simple-W.xml (90%) rename tests/visual_tests/{ => styles}/simple.xml (77%) diff --git a/tests/visual_tests/clean.sh b/tests/visual_tests/clean.sh index ba31ecae5..1278159f4 100755 --- a/tests/visual_tests/clean.sh +++ b/tests/visual_tests/clean.sh @@ -1,3 +1,3 @@ -rm -f *-agg.png -rm -f *-out.xml +rm -f images/*-agg.png +rm -f xml_output/*-out.xml diff --git a/tests/visual_tests/lines.osm b/tests/visual_tests/data/lines.osm similarity index 100% rename from tests/visual_tests/lines.osm rename to tests/visual_tests/data/lines.osm diff --git a/tests/visual_tests/points.dbf b/tests/visual_tests/data/points.dbf similarity index 100% rename from tests/visual_tests/points.dbf rename to tests/visual_tests/data/points.dbf diff --git a/tests/visual_tests/points.osm b/tests/visual_tests/data/points.osm similarity index 100% rename from tests/visual_tests/points.osm rename to tests/visual_tests/data/points.osm diff --git a/tests/visual_tests/points.shp b/tests/visual_tests/data/points.shp similarity index 100% rename from tests/visual_tests/points.shp rename to tests/visual_tests/data/points.shp diff --git a/tests/visual_tests/expressionformat-500-reference.png b/tests/visual_tests/images/expressionformat-500-reference.png similarity index 100% rename from tests/visual_tests/expressionformat-500-reference.png rename to tests/visual_tests/images/expressionformat-500-reference.png diff --git a/tests/visual_tests/formating-1-500-reference.png b/tests/visual_tests/images/formating-1-500-reference.png similarity index 100% rename from tests/visual_tests/formating-1-500-reference.png rename to tests/visual_tests/images/formating-1-500-reference.png diff --git a/tests/visual_tests/formating-2-500-reference.png b/tests/visual_tests/images/formating-2-500-reference.png similarity index 100% rename from tests/visual_tests/formating-2-500-reference.png rename to tests/visual_tests/images/formating-2-500-reference.png diff --git a/tests/visual_tests/formating-3-500-reference.png b/tests/visual_tests/images/formating-3-500-reference.png similarity index 100% rename from tests/visual_tests/formating-3-500-reference.png rename to tests/visual_tests/images/formating-3-500-reference.png diff --git a/tests/visual_tests/formating-4-500-reference.png b/tests/visual_tests/images/formating-4-500-reference.png similarity index 100% rename from tests/visual_tests/formating-4-500-reference.png rename to tests/visual_tests/images/formating-4-500-reference.png diff --git a/tests/visual_tests/formating-500-reference.png b/tests/visual_tests/images/formating-500-reference.png similarity index 100% rename from tests/visual_tests/formating-500-reference.png rename to tests/visual_tests/images/formating-500-reference.png diff --git a/tests/visual_tests/jalign-auto-200-reference.png b/tests/visual_tests/images/jalign-auto-200-reference.png similarity index 100% rename from tests/visual_tests/jalign-auto-200-reference.png rename to tests/visual_tests/images/jalign-auto-200-reference.png diff --git a/tests/visual_tests/lines-1-200-reference.png b/tests/visual_tests/images/lines-1-200-reference.png similarity index 100% rename from tests/visual_tests/lines-1-200-reference.png rename to tests/visual_tests/images/lines-1-200-reference.png diff --git a/tests/visual_tests/lines-1-400-reference.png b/tests/visual_tests/images/lines-1-400-reference.png similarity index 100% rename from tests/visual_tests/lines-1-400-reference.png rename to tests/visual_tests/images/lines-1-400-reference.png diff --git a/tests/visual_tests/lines-1-600-reference.png b/tests/visual_tests/images/lines-1-600-reference.png similarity index 100% rename from tests/visual_tests/lines-1-600-reference.png rename to tests/visual_tests/images/lines-1-600-reference.png diff --git a/tests/visual_tests/lines-1-800-reference.png b/tests/visual_tests/images/lines-1-800-reference.png similarity index 100% rename from tests/visual_tests/lines-1-800-reference.png rename to tests/visual_tests/images/lines-1-800-reference.png diff --git a/tests/visual_tests/lines-2-200-reference.png b/tests/visual_tests/images/lines-2-200-reference.png similarity index 100% rename from tests/visual_tests/lines-2-200-reference.png rename to tests/visual_tests/images/lines-2-200-reference.png diff --git a/tests/visual_tests/lines-2-400-reference.png b/tests/visual_tests/images/lines-2-400-reference.png similarity index 100% rename from tests/visual_tests/lines-2-400-reference.png rename to tests/visual_tests/images/lines-2-400-reference.png diff --git a/tests/visual_tests/lines-2-600-reference.png b/tests/visual_tests/images/lines-2-600-reference.png similarity index 100% rename from tests/visual_tests/lines-2-600-reference.png rename to tests/visual_tests/images/lines-2-600-reference.png diff --git a/tests/visual_tests/lines-2-800-reference.png b/tests/visual_tests/images/lines-2-800-reference.png similarity index 100% rename from tests/visual_tests/lines-2-800-reference.png rename to tests/visual_tests/images/lines-2-800-reference.png diff --git a/tests/visual_tests/lines-3-200-reference.png b/tests/visual_tests/images/lines-3-200-reference.png similarity index 100% rename from tests/visual_tests/lines-3-200-reference.png rename to tests/visual_tests/images/lines-3-200-reference.png diff --git a/tests/visual_tests/lines-3-400-reference.png b/tests/visual_tests/images/lines-3-400-reference.png similarity index 100% rename from tests/visual_tests/lines-3-400-reference.png rename to tests/visual_tests/images/lines-3-400-reference.png diff --git a/tests/visual_tests/lines-3-600-reference.png b/tests/visual_tests/images/lines-3-600-reference.png similarity index 100% rename from tests/visual_tests/lines-3-600-reference.png rename to tests/visual_tests/images/lines-3-600-reference.png diff --git a/tests/visual_tests/lines-3-800-reference.png b/tests/visual_tests/images/lines-3-800-reference.png similarity index 100% rename from tests/visual_tests/lines-3-800-reference.png rename to tests/visual_tests/images/lines-3-800-reference.png diff --git a/tests/visual_tests/lines-shield-200-reference.png b/tests/visual_tests/images/lines-shield-200-reference.png similarity index 100% rename from tests/visual_tests/lines-shield-200-reference.png rename to tests/visual_tests/images/lines-shield-200-reference.png diff --git a/tests/visual_tests/lines-shield-400-reference.png b/tests/visual_tests/images/lines-shield-400-reference.png similarity index 100% rename from tests/visual_tests/lines-shield-400-reference.png rename to tests/visual_tests/images/lines-shield-400-reference.png diff --git a/tests/visual_tests/lines-shield-600-reference.png b/tests/visual_tests/images/lines-shield-600-reference.png similarity index 100% rename from tests/visual_tests/lines-shield-600-reference.png rename to tests/visual_tests/images/lines-shield-600-reference.png diff --git a/tests/visual_tests/lines-shield-800-reference.png b/tests/visual_tests/images/lines-shield-800-reference.png similarity index 100% rename from tests/visual_tests/lines-shield-800-reference.png rename to tests/visual_tests/images/lines-shield-800-reference.png diff --git a/tests/visual_tests/list-100-reference.png b/tests/visual_tests/images/list-100-reference.png similarity index 100% rename from tests/visual_tests/list-100-reference.png rename to tests/visual_tests/images/list-100-reference.png diff --git a/tests/visual_tests/list-150-reference.png b/tests/visual_tests/images/list-150-reference.png similarity index 100% rename from tests/visual_tests/list-150-reference.png rename to tests/visual_tests/images/list-150-reference.png diff --git a/tests/visual_tests/list-200-reference.png b/tests/visual_tests/images/list-200-reference.png similarity index 100% rename from tests/visual_tests/list-200-reference.png rename to tests/visual_tests/images/list-200-reference.png diff --git a/tests/visual_tests/list-250-reference.png b/tests/visual_tests/images/list-250-reference.png similarity index 100% rename from tests/visual_tests/list-250-reference.png rename to tests/visual_tests/images/list-250-reference.png diff --git a/tests/visual_tests/list-300-reference.png b/tests/visual_tests/images/list-300-reference.png similarity index 100% rename from tests/visual_tests/list-300-reference.png rename to tests/visual_tests/images/list-300-reference.png diff --git a/tests/visual_tests/list-400-reference.png b/tests/visual_tests/images/list-400-reference.png similarity index 100% rename from tests/visual_tests/list-400-reference.png rename to tests/visual_tests/images/list-400-reference.png diff --git a/tests/visual_tests/list-600-reference.png b/tests/visual_tests/images/list-600-reference.png similarity index 100% rename from tests/visual_tests/list-600-reference.png rename to tests/visual_tests/images/list-600-reference.png diff --git a/tests/visual_tests/list-800-reference.png b/tests/visual_tests/images/list-800-reference.png similarity index 100% rename from tests/visual_tests/list-800-reference.png rename to tests/visual_tests/images/list-800-reference.png diff --git a/tests/visual_tests/python-IfElse-reference.png b/tests/visual_tests/images/python-IfElse-reference.png similarity index 100% rename from tests/visual_tests/python-IfElse-reference.png rename to tests/visual_tests/images/python-IfElse-reference.png diff --git a/tests/visual_tests/python-MyText-reference.png b/tests/visual_tests/images/python-MyText-reference.png similarity index 100% rename from tests/visual_tests/python-MyText-reference.png rename to tests/visual_tests/images/python-MyText-reference.png diff --git a/tests/visual_tests/python-TextNode-reference.png b/tests/visual_tests/images/python-TextNode-reference.png similarity index 100% rename from tests/visual_tests/python-TextNode-reference.png rename to tests/visual_tests/images/python-TextNode-reference.png diff --git a/tests/visual_tests/rtl-point-200-reference.png b/tests/visual_tests/images/rtl-point-200-reference.png similarity index 100% rename from tests/visual_tests/rtl-point-200-reference.png rename to tests/visual_tests/images/rtl-point-200-reference.png diff --git a/tests/visual_tests/shieldsymbolizer-1-490-reference.png b/tests/visual_tests/images/shieldsymbolizer-1-490-reference.png similarity index 100% rename from tests/visual_tests/shieldsymbolizer-1-490-reference.png rename to tests/visual_tests/images/shieldsymbolizer-1-490-reference.png diff --git a/tests/visual_tests/shieldsymbolizer-1-495-reference.png b/tests/visual_tests/images/shieldsymbolizer-1-495-reference.png similarity index 100% rename from tests/visual_tests/shieldsymbolizer-1-495-reference.png rename to tests/visual_tests/images/shieldsymbolizer-1-495-reference.png diff --git a/tests/visual_tests/shieldsymbolizer-1-497-reference.png b/tests/visual_tests/images/shieldsymbolizer-1-497-reference.png similarity index 100% rename from tests/visual_tests/shieldsymbolizer-1-497-reference.png rename to tests/visual_tests/images/shieldsymbolizer-1-497-reference.png diff --git a/tests/visual_tests/shieldsymbolizer-1-498-reference.png b/tests/visual_tests/images/shieldsymbolizer-1-498-reference.png similarity index 100% rename from tests/visual_tests/shieldsymbolizer-1-498-reference.png rename to tests/visual_tests/images/shieldsymbolizer-1-498-reference.png diff --git a/tests/visual_tests/shieldsymbolizer-1-499-reference.png b/tests/visual_tests/images/shieldsymbolizer-1-499-reference.png similarity index 100% rename from tests/visual_tests/shieldsymbolizer-1-499-reference.png rename to tests/visual_tests/images/shieldsymbolizer-1-499-reference.png diff --git a/tests/visual_tests/shieldsymbolizer-1-500-reference.png b/tests/visual_tests/images/shieldsymbolizer-1-500-reference.png similarity index 100% rename from tests/visual_tests/shieldsymbolizer-1-500-reference.png rename to tests/visual_tests/images/shieldsymbolizer-1-500-reference.png diff --git a/tests/visual_tests/shieldsymbolizer-1-501-reference.png b/tests/visual_tests/images/shieldsymbolizer-1-501-reference.png similarity index 100% rename from tests/visual_tests/shieldsymbolizer-1-501-reference.png rename to tests/visual_tests/images/shieldsymbolizer-1-501-reference.png diff --git a/tests/visual_tests/shieldsymbolizer-1-502-reference.png b/tests/visual_tests/images/shieldsymbolizer-1-502-reference.png similarity index 100% rename from tests/visual_tests/shieldsymbolizer-1-502-reference.png rename to tests/visual_tests/images/shieldsymbolizer-1-502-reference.png diff --git a/tests/visual_tests/shieldsymbolizer-1-505-reference.png b/tests/visual_tests/images/shieldsymbolizer-1-505-reference.png similarity index 100% rename from tests/visual_tests/shieldsymbolizer-1-505-reference.png rename to tests/visual_tests/images/shieldsymbolizer-1-505-reference.png diff --git a/tests/visual_tests/shieldsymbolizer-1-510-reference.png b/tests/visual_tests/images/shieldsymbolizer-1-510-reference.png similarity index 100% rename from tests/visual_tests/shieldsymbolizer-1-510-reference.png rename to tests/visual_tests/images/shieldsymbolizer-1-510-reference.png diff --git a/tests/visual_tests/simple-100-reference.png b/tests/visual_tests/images/simple-100-reference.png similarity index 100% rename from tests/visual_tests/simple-100-reference.png rename to tests/visual_tests/images/simple-100-reference.png diff --git a/tests/visual_tests/simple-150-reference.png b/tests/visual_tests/images/simple-150-reference.png similarity index 100% rename from tests/visual_tests/simple-150-reference.png rename to tests/visual_tests/images/simple-150-reference.png diff --git a/tests/visual_tests/simple-200-reference.png b/tests/visual_tests/images/simple-200-reference.png similarity index 100% rename from tests/visual_tests/simple-200-reference.png rename to tests/visual_tests/images/simple-200-reference.png diff --git a/tests/visual_tests/simple-250-reference.png b/tests/visual_tests/images/simple-250-reference.png similarity index 100% rename from tests/visual_tests/simple-250-reference.png rename to tests/visual_tests/images/simple-250-reference.png diff --git a/tests/visual_tests/simple-300-reference.png b/tests/visual_tests/images/simple-300-reference.png similarity index 100% rename from tests/visual_tests/simple-300-reference.png rename to tests/visual_tests/images/simple-300-reference.png diff --git a/tests/visual_tests/simple-400-reference.png b/tests/visual_tests/images/simple-400-reference.png similarity index 100% rename from tests/visual_tests/simple-400-reference.png rename to tests/visual_tests/images/simple-400-reference.png diff --git a/tests/visual_tests/simple-600-reference.png b/tests/visual_tests/images/simple-600-reference.png similarity index 100% rename from tests/visual_tests/simple-600-reference.png rename to tests/visual_tests/images/simple-600-reference.png diff --git a/tests/visual_tests/simple-800-reference.png b/tests/visual_tests/images/simple-800-reference.png similarity index 100% rename from tests/visual_tests/simple-800-reference.png rename to tests/visual_tests/images/simple-800-reference.png diff --git a/tests/visual_tests/simple-E-500-reference.png b/tests/visual_tests/images/simple-E-500-reference.png similarity index 100% rename from tests/visual_tests/simple-E-500-reference.png rename to tests/visual_tests/images/simple-E-500-reference.png diff --git a/tests/visual_tests/simple-N-500-reference.png b/tests/visual_tests/images/simple-N-500-reference.png similarity index 100% rename from tests/visual_tests/simple-N-500-reference.png rename to tests/visual_tests/images/simple-N-500-reference.png diff --git a/tests/visual_tests/simple-NE-500-reference.png b/tests/visual_tests/images/simple-NE-500-reference.png similarity index 100% rename from tests/visual_tests/simple-NE-500-reference.png rename to tests/visual_tests/images/simple-NE-500-reference.png diff --git a/tests/visual_tests/simple-NW-500-reference.png b/tests/visual_tests/images/simple-NW-500-reference.png similarity index 100% rename from tests/visual_tests/simple-NW-500-reference.png rename to tests/visual_tests/images/simple-NW-500-reference.png diff --git a/tests/visual_tests/simple-S-500-reference.png b/tests/visual_tests/images/simple-S-500-reference.png similarity index 100% rename from tests/visual_tests/simple-S-500-reference.png rename to tests/visual_tests/images/simple-S-500-reference.png diff --git a/tests/visual_tests/simple-SE-500-reference.png b/tests/visual_tests/images/simple-SE-500-reference.png similarity index 100% rename from tests/visual_tests/simple-SE-500-reference.png rename to tests/visual_tests/images/simple-SE-500-reference.png diff --git a/tests/visual_tests/simple-SW-500-reference.png b/tests/visual_tests/images/simple-SW-500-reference.png similarity index 100% rename from tests/visual_tests/simple-SW-500-reference.png rename to tests/visual_tests/images/simple-SW-500-reference.png diff --git a/tests/visual_tests/simple-W-500-reference.png b/tests/visual_tests/images/simple-W-500-reference.png similarity index 100% rename from tests/visual_tests/simple-W-500-reference.png rename to tests/visual_tests/images/simple-W-500-reference.png diff --git a/tests/visual_tests/expressionformat.xml b/tests/visual_tests/styles/expressionformat.xml similarity index 90% rename from tests/visual_tests/expressionformat.xml rename to tests/visual_tests/styles/expressionformat.xml index 3c7b74b03..5c66ab54c 100644 --- a/tests/visual_tests/expressionformat.xml +++ b/tests/visual_tests/styles/expressionformat.xml @@ -6,7 +6,7 @@ My Style shape - points.shp + ../data/points.shp diff --git a/tests/visual_tests/formating-1.xml b/tests/visual_tests/styles/formating-1.xml similarity index 91% rename from tests/visual_tests/formating-1.xml rename to tests/visual_tests/styles/formating-1.xml index 75158e8b5..a044f662c 100644 --- a/tests/visual_tests/formating-1.xml +++ b/tests/visual_tests/styles/formating-1.xml @@ -6,7 +6,7 @@ My Style shape - points.shp + ../data/points.shp diff --git a/tests/visual_tests/formating-2.xml b/tests/visual_tests/styles/formating-2.xml similarity index 91% rename from tests/visual_tests/formating-2.xml rename to tests/visual_tests/styles/formating-2.xml index 4fda094db..285646515 100644 --- a/tests/visual_tests/formating-2.xml +++ b/tests/visual_tests/styles/formating-2.xml @@ -6,7 +6,7 @@ My Style shape - points.shp + ../data/points.shp diff --git a/tests/visual_tests/formating-3.xml b/tests/visual_tests/styles/formating-3.xml similarity index 91% rename from tests/visual_tests/formating-3.xml rename to tests/visual_tests/styles/formating-3.xml index 1e1bbd048..159ef880c 100644 --- a/tests/visual_tests/formating-3.xml +++ b/tests/visual_tests/styles/formating-3.xml @@ -6,7 +6,7 @@ My Style shape - points.shp + ../data/points.shp diff --git a/tests/visual_tests/formating-4.xml b/tests/visual_tests/styles/formating-4.xml similarity index 89% rename from tests/visual_tests/formating-4.xml rename to tests/visual_tests/styles/formating-4.xml index 746f38d02..551cd09d5 100644 --- a/tests/visual_tests/formating-4.xml +++ b/tests/visual_tests/styles/formating-4.xml @@ -6,7 +6,7 @@ My Style shape - points.shp + ../data/points.shp diff --git a/tests/visual_tests/formating.xml b/tests/visual_tests/styles/formating.xml similarity index 91% rename from tests/visual_tests/formating.xml rename to tests/visual_tests/styles/formating.xml index d59c7b8da..acef45dd7 100644 --- a/tests/visual_tests/formating.xml +++ b/tests/visual_tests/styles/formating.xml @@ -6,7 +6,7 @@ My Style shape - points.shp + ../data/points.shp diff --git a/tests/visual_tests/jalign-auto.xml b/tests/visual_tests/styles/jalign-auto.xml similarity index 96% rename from tests/visual_tests/jalign-auto.xml rename to tests/visual_tests/styles/jalign-auto.xml index d3c8631f0..8956751e5 100644 --- a/tests/visual_tests/jalign-auto.xml +++ b/tests/visual_tests/styles/jalign-auto.xml @@ -6,7 +6,7 @@ My Style shape - points.shp + ../data/points.shp diff --git a/tests/visual_tests/lines-1.xml b/tests/visual_tests/styles/lines-1.xml similarity index 89% rename from tests/visual_tests/lines-1.xml rename to tests/visual_tests/styles/lines-1.xml index 7df2294c7..5b8fe6a58 100644 --- a/tests/visual_tests/lines-1.xml +++ b/tests/visual_tests/styles/lines-1.xml @@ -6,7 +6,7 @@ My Style osm - lines.osm + ../data/lines.osm diff --git a/tests/visual_tests/lines-2.xml b/tests/visual_tests/styles/lines-2.xml similarity index 90% rename from tests/visual_tests/lines-2.xml rename to tests/visual_tests/styles/lines-2.xml index 1c77a3115..9b32abb73 100644 --- a/tests/visual_tests/lines-2.xml +++ b/tests/visual_tests/styles/lines-2.xml @@ -6,7 +6,7 @@ My Style osm - lines.osm + ../data/lines.osm diff --git a/tests/visual_tests/lines-3.xml b/tests/visual_tests/styles/lines-3.xml similarity index 90% rename from tests/visual_tests/lines-3.xml rename to tests/visual_tests/styles/lines-3.xml index ef37dc51c..13f5cc332 100644 --- a/tests/visual_tests/lines-3.xml +++ b/tests/visual_tests/styles/lines-3.xml @@ -6,7 +6,7 @@ My Style osm - lines.osm + ../data/lines.osm diff --git a/tests/visual_tests/lines-shield.xml b/tests/visual_tests/styles/lines-shield.xml similarity index 76% rename from tests/visual_tests/lines-shield.xml rename to tests/visual_tests/styles/lines-shield.xml index 05a0f4fd2..4402411a7 100644 --- a/tests/visual_tests/lines-shield.xml +++ b/tests/visual_tests/styles/lines-shield.xml @@ -6,14 +6,14 @@ My Style osm - lines.osm + ../data/lines.osm diff --git a/tests/visual_tests/list.xml b/tests/visual_tests/styles/list.xml similarity index 85% rename from tests/visual_tests/list.xml rename to tests/visual_tests/styles/list.xml index 93ef3726d..e146ded22 100644 --- a/tests/visual_tests/list.xml +++ b/tests/visual_tests/styles/list.xml @@ -7,9 +7,9 @@ + ../data/points.osm--> shape - points.shp + ../data/points.shp diff --git a/tests/visual_tests/rtl-point.xml b/tests/visual_tests/styles/rtl-point.xml similarity index 92% rename from tests/visual_tests/rtl-point.xml rename to tests/visual_tests/styles/rtl-point.xml index c29a41797..54edac854 100644 --- a/tests/visual_tests/rtl-point.xml +++ b/tests/visual_tests/styles/rtl-point.xml @@ -10,7 +10,7 @@ points.osm --> shape - points.shp + ../data/points.shp diff --git a/tests/visual_tests/shieldsymbolizer-1.xml b/tests/visual_tests/styles/shieldsymbolizer-1.xml similarity index 58% rename from tests/visual_tests/shieldsymbolizer-1.xml rename to tests/visual_tests/styles/shieldsymbolizer-1.xml index 69ec6f2f5..90b456890 100644 --- a/tests/visual_tests/shieldsymbolizer-1.xml +++ b/tests/visual_tests/styles/shieldsymbolizer-1.xml @@ -6,53 +6,53 @@ My Style shape - points.shp + ../../data/points.shp diff --git a/tests/visual_tests/simple-E.xml b/tests/visual_tests/styles/simple-E.xml similarity index 90% rename from tests/visual_tests/simple-E.xml rename to tests/visual_tests/styles/simple-E.xml index 31e11acaa..9a0668bd0 100644 --- a/tests/visual_tests/simple-E.xml +++ b/tests/visual_tests/styles/simple-E.xml @@ -6,7 +6,7 @@ My Style shape - points.shp + ../data/points.shp diff --git a/tests/visual_tests/simple-N.xml b/tests/visual_tests/styles/simple-N.xml similarity index 90% rename from tests/visual_tests/simple-N.xml rename to tests/visual_tests/styles/simple-N.xml index 919e0d2b6..74265ec5a 100644 --- a/tests/visual_tests/simple-N.xml +++ b/tests/visual_tests/styles/simple-N.xml @@ -6,7 +6,7 @@ My Style shape - points.shp + ../data/points.shp diff --git a/tests/visual_tests/simple-NE.xml b/tests/visual_tests/styles/simple-NE.xml similarity index 90% rename from tests/visual_tests/simple-NE.xml rename to tests/visual_tests/styles/simple-NE.xml index 74c6a5d09..df1095195 100644 --- a/tests/visual_tests/simple-NE.xml +++ b/tests/visual_tests/styles/simple-NE.xml @@ -6,7 +6,7 @@ My Style shape - points.shp + ../data/points.shp diff --git a/tests/visual_tests/simple-NW.xml b/tests/visual_tests/styles/simple-NW.xml similarity index 90% rename from tests/visual_tests/simple-NW.xml rename to tests/visual_tests/styles/simple-NW.xml index 5e80dc607..120e26fcb 100644 --- a/tests/visual_tests/simple-NW.xml +++ b/tests/visual_tests/styles/simple-NW.xml @@ -6,7 +6,7 @@ My Style shape - points.shp + ../data/points.shp diff --git a/tests/visual_tests/simple-S.xml b/tests/visual_tests/styles/simple-S.xml similarity index 90% rename from tests/visual_tests/simple-S.xml rename to tests/visual_tests/styles/simple-S.xml index 1ada1baca..e738ab8b4 100644 --- a/tests/visual_tests/simple-S.xml +++ b/tests/visual_tests/styles/simple-S.xml @@ -6,7 +6,7 @@ My Style shape - points.shp + ../data/points.shp diff --git a/tests/visual_tests/simple-SE.xml b/tests/visual_tests/styles/simple-SE.xml similarity index 90% rename from tests/visual_tests/simple-SE.xml rename to tests/visual_tests/styles/simple-SE.xml index c8ee4f6d5..ae43761b0 100644 --- a/tests/visual_tests/simple-SE.xml +++ b/tests/visual_tests/styles/simple-SE.xml @@ -6,7 +6,7 @@ My Style shape - points.shp + ../data/points.shp diff --git a/tests/visual_tests/simple-SW.xml b/tests/visual_tests/styles/simple-SW.xml similarity index 90% rename from tests/visual_tests/simple-SW.xml rename to tests/visual_tests/styles/simple-SW.xml index 641452d4b..7b206417c 100644 --- a/tests/visual_tests/simple-SW.xml +++ b/tests/visual_tests/styles/simple-SW.xml @@ -6,7 +6,7 @@ My Style shape - points.shp + ../data/points.shp diff --git a/tests/visual_tests/simple-W.xml b/tests/visual_tests/styles/simple-W.xml similarity index 90% rename from tests/visual_tests/simple-W.xml rename to tests/visual_tests/styles/simple-W.xml index 012dc4375..cd0708a7a 100644 --- a/tests/visual_tests/simple-W.xml +++ b/tests/visual_tests/styles/simple-W.xml @@ -6,7 +6,7 @@ My Style shape - points.shp + ../data/points.shp diff --git a/tests/visual_tests/simple.xml b/tests/visual_tests/styles/simple.xml similarity index 77% rename from tests/visual_tests/simple.xml rename to tests/visual_tests/styles/simple.xml index cc008f598..61bb07748 100644 --- a/tests/visual_tests/simple.xml +++ b/tests/visual_tests/styles/simple.xml @@ -5,12 +5,8 @@ My Style - shape - points.shp + ../data/points.shp diff --git a/tests/visual_tests/test.py b/tests/visual_tests/test.py index 8d9d3166f..cbc36bcad 100755 --- a/tests/visual_tests/test.py +++ b/tests/visual_tests/test.py @@ -39,10 +39,10 @@ def render(filename, width, height=100): width = int(width) height = int(height) m = mapnik.Map(width, height) - mapnik.load_map(m, os.path.join(dirname, "%s.xml" % filename), False) + mapnik.load_map(m, os.path.join(dirname, "styles", "%s.xml" % filename), False) bbox = mapnik.Box2d(-0.05, -0.01, 0.95, 0.01) m.zoom_to_box(bbox) - basefn = os.path.join(dirname,'%s-%d' % (filename, width)) + basefn = os.path.join(dirname, "images", '%s-%d' % (filename, width)) mapnik.render_to_file(m, basefn+'-agg.png') diff = compare(basefn + '-agg.png', basefn + '-reference.png') if diff > 0: @@ -63,6 +63,6 @@ for f in files: m = render(f[0], width[0], width[1]) else: m = render(f[0], width) - mapnik.save_map(m, os.path.join(dirname,"%s-out.xml" % f[0])) + mapnik.save_map(m, os.path.join(dirname, 'xml_output', "%s-out.xml" % f[0])) summary() diff --git a/tests/visual_tests/test_python.py b/tests/visual_tests/test_python.py index e45ab6e55..db6117c4c 100755 --- a/tests/visual_tests/test_python.py +++ b/tests/visual_tests/test_python.py @@ -1,6 +1,8 @@ #!/usr/bin/env python import mapnik import sys +import os.path +import compare class MyText(mapnik.FormattingNode): def __init__(self): @@ -67,7 +69,7 @@ m.append_style('Style', style) layer = mapnik.Layer('Layer') -layer.datasource = mapnik.Shapefile(file="points.shp") +layer.datasource = mapnik.Shapefile(file="data/points.shp") layer.styles.append('Style') m.layers.append(layer) @@ -95,4 +97,4 @@ format_trees = [ for format_tree in format_trees: text.placements.defaults.format_tree = format_tree[1] - mapnik.render_to_file(m, 'python-%s.png' % format_tree[0], 'png') + mapnik.render_to_file(m, os.path.join("images", 'python-%s.png' % format_tree[0]), 'png') From fe2879ac42dc43a382317c01749de1db50aed13b Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Tue, 20 Mar 2012 21:34:16 +0100 Subject: [PATCH 131/238] Update python tests. --- .../images/python-Format-reference.png | Bin 0 -> 2958 bytes .../images/python-List-reference.png | Bin 0 -> 4599 bytes tests/visual_tests/test_python.py | 7 ++++++- 3 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 tests/visual_tests/images/python-Format-reference.png create mode 100644 tests/visual_tests/images/python-List-reference.png diff --git a/tests/visual_tests/images/python-Format-reference.png b/tests/visual_tests/images/python-Format-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..1e0426a233f4a00b8fa84046a701ae0041f49ee6 GIT binary patch literal 2958 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 literal 0 HcmV?d00001 diff --git a/tests/visual_tests/images/python-List-reference.png b/tests/visual_tests/images/python-List-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..21f6e4f58211a895320794b7b9505f22b4125f50 GIT binary patch literal 4599 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 literal 0 HcmV?d00001 diff --git a/tests/visual_tests/test_python.py b/tests/visual_tests/test_python.py index db6117c4c..0cba25907 100755 --- a/tests/visual_tests/test_python.py +++ b/tests/visual_tests/test_python.py @@ -2,7 +2,7 @@ import mapnik import sys import os.path -import compare +from compare import compare, summary class MyText(mapnik.FormattingNode): def __init__(self): @@ -98,3 +98,8 @@ format_trees = [ for format_tree in format_trees: text.placements.defaults.format_tree = format_tree[1] mapnik.render_to_file(m, os.path.join("images", 'python-%s.png' % format_tree[0]), 'png') + compare(os.path.join("images", 'python-%s.png' % format_tree[0]), + os.path.join("images", 'python-%s-reference.png' % format_tree[0]) + ) + +summary() From eb911deca5cd52109ac112cff08cf2a3c8a7dcee Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Tue, 20 Mar 2012 22:06:22 +0100 Subject: [PATCH 132/238] Change visual tests config format. --- ...nce.png => formatting-1-500-reference.png} | Bin ...nce.png => formatting-2-500-reference.png} | Bin ...nce.png => formatting-3-500-reference.png} | Bin ...nce.png => formatting-4-500-reference.png} | Bin .../{formating-1.xml => formatting-1.xml} | 0 .../{formating-2.xml => formatting-2.xml} | 0 .../{formating-3.xml => formatting-3.xml} | 0 .../{formating-4.xml => formatting-4.xml} | 0 .../styles/shieldsymbolizer-1.xml | 2 +- tests/visual_tests/test.py | 72 ++++++++++-------- tests/visual_tests/xml_output/.gitignore | 1 + 11 files changed, 42 insertions(+), 33 deletions(-) rename tests/visual_tests/images/{formating-1-500-reference.png => formatting-1-500-reference.png} (100%) rename tests/visual_tests/images/{formating-2-500-reference.png => formatting-2-500-reference.png} (100%) rename tests/visual_tests/images/{formating-3-500-reference.png => formatting-3-500-reference.png} (100%) rename tests/visual_tests/images/{formating-4-500-reference.png => formatting-4-500-reference.png} (100%) rename tests/visual_tests/styles/{formating-1.xml => formatting-1.xml} (100%) rename tests/visual_tests/styles/{formating-2.xml => formatting-2.xml} (100%) rename tests/visual_tests/styles/{formating-3.xml => formatting-3.xml} (100%) rename tests/visual_tests/styles/{formating-4.xml => formatting-4.xml} (100%) create mode 100644 tests/visual_tests/xml_output/.gitignore diff --git a/tests/visual_tests/images/formating-1-500-reference.png b/tests/visual_tests/images/formatting-1-500-reference.png similarity index 100% rename from tests/visual_tests/images/formating-1-500-reference.png rename to tests/visual_tests/images/formatting-1-500-reference.png diff --git a/tests/visual_tests/images/formating-2-500-reference.png b/tests/visual_tests/images/formatting-2-500-reference.png similarity index 100% rename from tests/visual_tests/images/formating-2-500-reference.png rename to tests/visual_tests/images/formatting-2-500-reference.png diff --git a/tests/visual_tests/images/formating-3-500-reference.png b/tests/visual_tests/images/formatting-3-500-reference.png similarity index 100% rename from tests/visual_tests/images/formating-3-500-reference.png rename to tests/visual_tests/images/formatting-3-500-reference.png diff --git a/tests/visual_tests/images/formating-4-500-reference.png b/tests/visual_tests/images/formatting-4-500-reference.png similarity index 100% rename from tests/visual_tests/images/formating-4-500-reference.png rename to tests/visual_tests/images/formatting-4-500-reference.png diff --git a/tests/visual_tests/styles/formating-1.xml b/tests/visual_tests/styles/formatting-1.xml similarity index 100% rename from tests/visual_tests/styles/formating-1.xml rename to tests/visual_tests/styles/formatting-1.xml diff --git a/tests/visual_tests/styles/formating-2.xml b/tests/visual_tests/styles/formatting-2.xml similarity index 100% rename from tests/visual_tests/styles/formating-2.xml rename to tests/visual_tests/styles/formatting-2.xml diff --git a/tests/visual_tests/styles/formating-3.xml b/tests/visual_tests/styles/formatting-3.xml similarity index 100% rename from tests/visual_tests/styles/formating-3.xml rename to tests/visual_tests/styles/formatting-3.xml diff --git a/tests/visual_tests/styles/formating-4.xml b/tests/visual_tests/styles/formatting-4.xml similarity index 100% rename from tests/visual_tests/styles/formating-4.xml rename to tests/visual_tests/styles/formatting-4.xml diff --git a/tests/visual_tests/styles/shieldsymbolizer-1.xml b/tests/visual_tests/styles/shieldsymbolizer-1.xml index 90b456890..329343461 100644 --- a/tests/visual_tests/styles/shieldsymbolizer-1.xml +++ b/tests/visual_tests/styles/shieldsymbolizer-1.xml @@ -6,7 +6,7 @@ My Style shape - ../../data/points.shp + ../data/points.shp diff --git a/tests/visual_tests/test.py b/tests/visual_tests/test.py index cbc36bcad..442aad86d 100755 --- a/tests/visual_tests/test.py +++ b/tests/visual_tests/test.py @@ -6,41 +6,50 @@ import sys import os.path from compare import compare, summary +defaults = { + 'sizes': [(500, 100)], + 'bbox': mapnik.Box2d(-0.05, -0.01, 0.95, 0.01) +} + +sizes_many_in_big_range = [(800, 100), (600, 100), (400, 100), + (300, 100), (250, 100), (150, 100), (100, 100)] + +sizes_few_square = [(800, 800), (600, 600), (400, 400), (200, 200)] +sizes_many_in_small_range = [(490, 100), (495, 100), (497, 100), (498, 100), + (499, 100), (500, 100), (501, 100), (502, 100), (505, 100), (510, 100)] + dirname = os.path.dirname(__file__) files = [ - ("list", 800, 600, 400, 300, 250, 200, 150, 100), - ("simple", 800, 600, 400, 300, 250, 200, 150, 100), - ("lines-1", (800, 800), (600, 600), (400, 400), (200, 200)), - ("lines-2", (800, 800), (600, 600), (400, 400), (200, 200)), - ("lines-3", (800, 800), (600, 600), (400, 400), (200, 200)), - ("lines-shield", (800, 800), (600, 600), (400, 400), (200, 200)), - ("simple-E", 500), - ("simple-NE", 500), - ("simple-NW", 500), - ("simple-N", 500), - ("simple-SE", 500), - ("simple-SW", 500), - ("simple-S", 500), - ("simple-W", 500), - ("formating-1", 500), - ("formating-2", 500), - ("formating-3", 500), - ("formating-4", 500), - ("shieldsymbolizer-1", 490, 495, 497, 498, 499, 500, 501, 502, 505, 510), - ("expressionformat", 500), - ("rtl-point", (200, 200)), - ("jalign-auto", (200, 200)) + {'name': "list", 'sizes': sizes_many_in_big_range}, + {'name': "simple", 'sizes': sizes_many_in_big_range}, + {'name': "lines-1", 'sizes': sizes_few_square}, + {'name': "lines-2", 'sizes': sizes_few_square}, + {'name': "lines-3", 'sizes': sizes_few_square}, + {'name': "lines-shield", 'sizes': sizes_few_square}, + {'name': "simple-E"}, + {'name': "simple-NE"}, + {'name': "simple-NW"}, + {'name': "simple-N"}, + {'name': "simple-SE"}, + {'name': "simple-SW"}, + {'name': "simple-S"}, + {'name': "simple-W"}, + {'name': "formatting-1"}, + {'name': "formatting-2"}, + {'name': "formatting-3"}, + {'name': "formatting-4"}, + {'name': "expressionformat"}, + {'name': "shieldsymbolizer-1", 'sizes': sizes_many_in_small_range}, + {'name': "rtl-point", 'sizes': [(200, 200)]}, + {'name': "jalign-auto", 'sizes': [(200, 200)]} ] -def render(filename, width, height=100): +def render(filename, width, height, bbox): print "-"*80 print "Rendering style \"%s\" with size %dx%d ... " % (filename, width, height) print "-"*80 - width = int(width) - height = int(height) m = mapnik.Map(width, height) mapnik.load_map(m, os.path.join(dirname, "styles", "%s.xml" % filename), False) - bbox = mapnik.Box2d(-0.05, -0.01, 0.95, 0.01) m.zoom_to_box(bbox) basefn = os.path.join(dirname, "images", '%s-%d' % (filename, width)) mapnik.render_to_file(m, basefn+'-agg.png') @@ -58,11 +67,10 @@ elif len(sys.argv) > 2: files = [sys.argv[1:]] for f in files: - for width in f[1:]: - if isinstance(width, tuple): - m = render(f[0], width[0], width[1]) - else: - m = render(f[0], width) - mapnik.save_map(m, os.path.join(dirname, 'xml_output', "%s-out.xml" % f[0])) + config = dict(defaults) + config.update(f) + for size in config['sizes']: + m = render(config['name'], size[0], size[1], config['bbox']) + mapnik.save_map(m, os.path.join(dirname, 'xml_output', "%s-out.xml" % config['name'])) summary() diff --git a/tests/visual_tests/xml_output/.gitignore b/tests/visual_tests/xml_output/.gitignore new file mode 100644 index 000000000..6722cd96e --- /dev/null +++ b/tests/visual_tests/xml_output/.gitignore @@ -0,0 +1 @@ +*.xml From bd9fe739e5d47b13a83c9acb2d0b63c66d547f04 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Tue, 20 Mar 2012 23:02:19 +0100 Subject: [PATCH 133/238] Throw in OSM datasource if not all parameters are specified but bind is requested. --- plugins/input/osm/osm_datasource.cpp | 60 +++++++++++++--------------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/plugins/input/osm/osm_datasource.cpp b/plugins/input/osm/osm_datasource.cpp index 7c4db20c2..90cebc0fe 100644 --- a/plugins/input/osm/osm_datasource.cpp +++ b/plugins/input/osm/osm_datasource.cpp @@ -52,6 +52,7 @@ DATASOURCE_PLUGIN(osm_datasource) osm_datasource::osm_datasource(const parameters& params, bool bind) : datasource (params), + extent_(), type_(datasource::Vector), desc_(*params_.get("type"), *params_.get("encoding", "utf-8")) { @@ -71,13 +72,11 @@ void osm_datasource::bind() const std::string url = *params_.get("url", ""); std::string bbox = *params_.get("bbox", ""); - bool do_process = false; // load the data - // if we supplied a filename, load from file if (url != "" && bbox != "") { - // otherwise if we supplied a url and a bounding box, load from the url + // if we supplied a url and a bounding box, load from the url #ifdef MAPNIK_DEBUG std::clog << "Osm Plugin: loading_from_url: url=" << url << " bbox=" << bbox << std::endl; #endif @@ -85,44 +84,40 @@ void osm_datasource::bind() const { throw datasource_exception("Error loading from URL"); } - - do_process = true; } else if (osm_filename != "") { - if ((osm_data_= dataset_deliverer::load_from_file(osm_filename, parser)) == NULL) + // if we supplied a filename, load from file + if ((osm_data_ = dataset_deliverer::load_from_file(osm_filename, parser)) == NULL) { std::ostringstream s; s << "OSM Plugin: Error loading from file '" << osm_filename << "'"; throw datasource_exception(s.str()); } - - do_process = true; + } else { + throw datasource_exception("OSM Plugin: Neither 'file' nor 'url' and 'bbox' specified"); } - if (do_process == true) + + osm_tag_types tagtypes; + tagtypes.add_type("maxspeed", mapnik::Integer); + tagtypes.add_type("z_order", mapnik::Integer); + + osm_data_->rewind(); + + // Need code to get the attributes of all the data + std::set keys = osm_data_->get_keys(); + + // Add the attributes to the datasource descriptor - assume they are + // all of type String + for (std::set::iterator i = keys.begin(); i != keys.end(); i++) { - osm_tag_types tagtypes; - tagtypes.add_type("maxspeed", mapnik::Integer); - tagtypes.add_type("z_order", mapnik::Integer); - - osm_data_->rewind(); - - // Need code to get the attributes of all the data - std::set keys = osm_data_->get_keys(); - - // Add the attributes to the datasource descriptor - assume they are - // all of type String - for (std::set::iterator i = keys.begin(); i != keys.end(); i++) - { - desc_.add_descriptor(attribute_descriptor(*i, tagtypes.get_type(*i))); - } - - // Get the bounds of the data and set extent_ accordingly - bounds b = osm_data_->get_bounds(); - extent_ = box2d(b.w, b.s, b.e, b.n); + desc_.add_descriptor(attribute_descriptor(*i, tagtypes.get_type(*i))); } + // Get the bounds of the data and set extent_ accordingly + bounds b = osm_data_->get_bounds(); + extent_ = box2d(b.w, b.s, b.e, b.n); is_bound_ = true; } @@ -149,7 +144,7 @@ layer_descriptor osm_datasource::get_descriptor() const featureset_ptr osm_datasource::features(const query& q) const { - if (! is_bound_) bind(); + if (!is_bound_) bind(); filter_in_box filter(q.get_bbox()); // so we need to filter osm features by bbox here... @@ -162,7 +157,7 @@ featureset_ptr osm_datasource::features(const query& q) const featureset_ptr osm_datasource::features_at_point(coord2d const& pt) const { - if (! is_bound_) bind(); + if (!is_bound_) bind(); filter_at_point filter(pt); // collect all attribute names @@ -185,8 +180,7 @@ featureset_ptr osm_datasource::features_at_point(coord2d const& pt) const box2d osm_datasource::envelope() const { - if (! is_bound_) bind(); - + if (!is_bound_) bind(); return extent_; } @@ -194,4 +188,4 @@ boost::optional osm_datasource::get_geometry_typ { if (! is_bound_) bind(); return boost::optional(mapnik::datasource::Collection); -} \ No newline at end of file +} From 94634d469118e85f945cfad652a1c63e3bb3d0ae Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Tue, 20 Mar 2012 23:03:01 +0100 Subject: [PATCH 134/238] Correctly parse empty elements in OSM datasource. --- plugins/input/osm/osmparser.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/input/osm/osmparser.cpp b/plugins/input/osm/osmparser.cpp index fcc7b5d53..9b83d2b79 100644 --- a/plugins/input/osm/osmparser.cpp +++ b/plugins/input/osm/osmparser.cpp @@ -103,6 +103,11 @@ void osmparser::startElement(xmlTextReaderPtr reader, const xmlChar *name) xmlFree(xk); xmlFree(xv); } + if (xmlTextReaderIsEmptyElement(reader)) + { + // Fake endElement for empty nodes + endElement(name); + } } void osmparser::endElement(const xmlChar* name) From eac133952ce87aad32187aec9fe0ef83f5415209 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Tue, 20 Mar 2012 23:12:39 +0100 Subject: [PATCH 135/238] Add line-offset test. --- tests/visual_tests/data/line-offset.osm | 435 ++++++++++++++++++++++ tests/visual_tests/styles/line-offset.xml | 29 ++ tests/visual_tests/test.py | 9 +- 3 files changed, 471 insertions(+), 2 deletions(-) create mode 100644 tests/visual_tests/data/line-offset.osm create mode 100644 tests/visual_tests/styles/line-offset.xml diff --git a/tests/visual_tests/data/line-offset.osm b/tests/visual_tests/data/line-offset.osm new file mode 100644 index 000000000..9a568dc30 --- /dev/null +++ b/tests/visual_tests/data/line-offset.osm @@ -0,0 +1,435 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/visual_tests/styles/line-offset.xml b/tests/visual_tests/styles/line-offset.xml new file mode 100644 index 000000000..ed738555d --- /dev/null +++ b/tests/visual_tests/styles/line-offset.xml @@ -0,0 +1,29 @@ + + + + + My Style + + osm + ../data/line-offset.osm + + + + + \ No newline at end of file diff --git a/tests/visual_tests/test.py b/tests/visual_tests/test.py index 442aad86d..554c1c5c5 100755 --- a/tests/visual_tests/test.py +++ b/tests/visual_tests/test.py @@ -41,7 +41,9 @@ files = [ {'name': "expressionformat"}, {'name': "shieldsymbolizer-1", 'sizes': sizes_many_in_small_range}, {'name': "rtl-point", 'sizes': [(200, 200)]}, - {'name': "jalign-auto", 'sizes': [(200, 200)]} + {'name': "jalign-auto", 'sizes': [(200, 200)]}, + {'name': "line-offset", 'sizes':[(900, 250)], + 'bbox': mapnik.Box2d(-5.192, 50.189, -5.174, 50.195)} ] def render(filename, width, height, bbox): @@ -50,7 +52,10 @@ def render(filename, width, height, bbox): print "-"*80 m = mapnik.Map(width, height) mapnik.load_map(m, os.path.join(dirname, "styles", "%s.xml" % filename), False) - m.zoom_to_box(bbox) + if bbox is not None: + m.zoom_to_box(bbox) + else: + m.zoom_all() basefn = os.path.join(dirname, "images", '%s-%d' % (filename, width)) mapnik.render_to_file(m, basefn+'-agg.png') diff = compare(basefn + '-agg.png', basefn + '-reference.png') From 8b5e5fbb64016083c132e333403a0bb341ea24dd Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Tue, 20 Mar 2012 23:50:20 +0100 Subject: [PATCH 136/238] Always place text at the same side of a line. Fixes #608. --- src/placement_finder.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/placement_finder.cpp b/src/placement_finder.cpp index 5419cb8b6..a1892708b 100644 --- a/src/placement_finder.cpp +++ b/src/placement_finder.cpp @@ -660,15 +660,24 @@ void placement_finder::find_line_placements(PathT & shape_path) double anglesum = 0; for (unsigned i = 0; i < current_placement->nodes_.size(); i++) { - anglesum += current_placement->nodes_[i].angle; + double angle = current_placement->nodes_[i].angle; + //Normalize angle in range -PI ... PI + while (angle > M_PI) { + angle -= 2*M_PI; + } + anglesum += angle; } anglesum /= current_placement->nodes_.size(); //Now it is angle average + double cosa = orientation * cos(anglesum); + double sina = orientation * sin(anglesum); //Offset all the characters by this angle for (unsigned i = 0; i < current_placement->nodes_.size(); i++) { - current_placement->nodes_[i].pos.x += pi.get_scale_factor() * displacement*cos(anglesum+M_PI/2); - current_placement->nodes_[i].pos.y += pi.get_scale_factor() * displacement*sin(anglesum+M_PI/2); + current_placement->nodes_[i].pos.x -= + pi.get_scale_factor() * displacement * sina; + current_placement->nodes_[i].pos.y += + pi.get_scale_factor() * displacement * cosa; } } From 620f3f943e2a6e165d008fa3f84aa97a0abdecda Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Tue, 20 Mar 2012 23:52:13 +0100 Subject: [PATCH 137/238] Add reference image. --- .../images/line-offset-900-reference.png | Bin 0 -> 32175 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/visual_tests/images/line-offset-900-reference.png diff --git a/tests/visual_tests/images/line-offset-900-reference.png b/tests/visual_tests/images/line-offset-900-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..37f3c4b6e1d525cf94b1258864cc6bbbfd31ab0f GIT binary patch literal 32175 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%0AV%Z`Mf~7Mf(~qv>_bX@OJ5 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 R9>k8IEUzwCFJlq Date: Tue, 20 Mar 2012 23:56:35 +0100 Subject: [PATCH 138/238] Remove old comment. --- src/placement_finder.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/placement_finder.cpp b/src/placement_finder.cpp index a1892708b..0132ed656 100644 --- a/src/placement_finder.cpp +++ b/src/placement_finder.cpp @@ -656,7 +656,6 @@ void placement_finder::find_line_placements(PathT & shape_path) if (displacement != 0) { //Average the angle of all characters and then offset them all by that angle - //NOTE: This probably calculates a bad angle due to going around the circle, test this! double anglesum = 0; for (unsigned i = 0; i < current_placement->nodes_.size(); i++) { From 3f067474070cbcecc29836acf35605b009b15a84 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Tue, 20 Mar 2012 16:40:07 -0700 Subject: [PATCH 139/238] braces for readibility --- src/load_map.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/load_map.cpp b/src/load_map.cpp index 38bcfbff7..ef5c58f7e 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -1063,7 +1063,9 @@ void map_parser::parse_text_symbolizer(rule & rule, xml_node const& sym) } if (strict_ && !placement_finder->defaults.format.fontset.size()) + { ensure_font_face(placement_finder->defaults.format.face_name); + } text_symbolizer text_symbol = text_symbolizer(placement_finder); parse_metawriter_in_symbolizer(text_symbol, sym); @@ -1090,7 +1092,9 @@ void map_parser::parse_shield_symbolizer(rule & rule, xml_node const& sym) placement_finder->defaults.from_xml(sym, fontsets_); if (strict_ && !placement_finder->defaults.format.fontset.size()) + { ensure_font_face(placement_finder->defaults.format.face_name); + } shield_symbolizer shield_symbol = shield_symbolizer(placement_finder); /* Symbolizer specific attributes. */ From d55ad5a4d21c4ae0541aa056151df2aca69946fc Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 21 Mar 2012 15:34:01 -0700 Subject: [PATCH 140/238] check which cairo surfaces are supported --- src/build.py | 1 + src/image_util.cpp | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/build.py b/src/build.py index 59bf949ca..34ba537d1 100644 --- a/src/build.py +++ b/src/build.py @@ -73,6 +73,7 @@ if env['THREADING'] == 'multi': if env['RUNTIME_LINK'] == 'static': if 'icuuc' in env['ICU_LIB_NAME']: lib_env['LIBS'].append('icudata') + lib_env['LIBS'].append('icui18n') else: if env['INTERNAL_LIBAGG']: lib_env['LIBS'].insert(0, 'agg') diff --git a/src/image_util.cpp b/src/image_util.cpp index 5b3b46454..35ccb4ba1 100644 --- a/src/image_util.cpp +++ b/src/image_util.cpp @@ -45,6 +45,7 @@ extern "C" #ifdef HAVE_CAIRO #include +#include #endif #include @@ -371,17 +372,39 @@ void save_to_cairo_file(mapnik::Map const& map, unsigned width = map.width(); unsigned height = map.height(); if (type == "pdf") + { +#if defined(CAIRO_HAS_PDF_SURFACE) surface = Cairo::PdfSurface::create(filename,width,height); + throw ImageWriterException("PDFSurface not supported in the cairo backend"); +#endif + } +#if defined(CAIRO_HAS_SVG_SURFACE) else if (type == "svg") + { surface = Cairo::SvgSurface::create(filename,width,height); + } +#endif +#if defined(CAIRO_HAS_PS_SURFACE) else if (type == "ps") + { surface = Cairo::PsSurface::create(filename,width,height); + } +#endif +#if defined(CAIRO_HAS_IMAGE_SURFACE) else if (type == "ARGB32") + { surface = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32,width,height); + } else if (type == "RGB24") + { surface = Cairo::ImageSurface::create(Cairo::FORMAT_RGB24,width,height); + } +#endif else + { throw ImageWriterException("unknown file type: " + type); + } + Cairo::RefPtr context = Cairo::Context::create(surface); // TODO - expose as user option From 0beca49b2aa410363b4cb1c9b4ef0ad86d4eae8c Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 21 Mar 2012 15:38:32 -0700 Subject: [PATCH 141/238] scons: when parsing xml2-config only pull --cflags to avoid uneeded pollution of other libs on osx (namely libicucore) --- SConstruct | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SConstruct b/SConstruct index 71f04991d..60352732c 100644 --- a/SConstruct +++ b/SConstruct @@ -1068,7 +1068,7 @@ if not preconfigured: # libxml2 should be optional but is currently not # https://github.com/mapnik/mapnik/issues/913 - if conf.parse_config('XML2_CONFIG'): + if conf.parse_config('XML2_CONFIG',checks='--cflags'): env['HAS_LIBXML2'] = True LIBSHEADERS = [ From 40b0ab8d6c0e1aa49a60fc2c1c1d37c825fda64c Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 21 Mar 2012 17:45:19 -0700 Subject: [PATCH 142/238] add missing #else --- src/image_util.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/image_util.cpp b/src/image_util.cpp index 35ccb4ba1..dfc1c54c6 100644 --- a/src/image_util.cpp +++ b/src/image_util.cpp @@ -375,6 +375,7 @@ void save_to_cairo_file(mapnik::Map const& map, { #if defined(CAIRO_HAS_PDF_SURFACE) surface = Cairo::PdfSurface::create(filename,width,height); +#else throw ImageWriterException("PDFSurface not supported in the cairo backend"); #endif } From 2e2bce312619510a6ed7b9a5b813816b561efa1b Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Thu, 22 Mar 2012 16:31:57 -0700 Subject: [PATCH 143/238] formatting --- include/mapnik/agg_helpers.hpp | 4 ++-- include/mapnik/font_engine_freetype.hpp | 8 ++++---- include/mapnik/line_symbolizer.hpp | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/mapnik/agg_helpers.hpp b/include/mapnik/agg_helpers.hpp index aa13729d4..e605194fe 100644 --- a/include/mapnik/agg_helpers.hpp +++ b/include/mapnik/agg_helpers.hpp @@ -71,7 +71,7 @@ void set_join_caps(Stroke const& stroke_, PathType & stroke) default: stroke.generator().line_join(agg::bevel_join); } - + line_cap_e cap=stroke_.get_line_cap(); switch (cap) { @@ -85,7 +85,7 @@ void set_join_caps(Stroke const& stroke_, PathType & stroke) stroke.generator().line_cap(agg::round_cap); } } - + } #endif //MAPNIK_AGG_HELPERS_HPP diff --git a/include/mapnik/font_engine_freetype.hpp b/include/mapnik/font_engine_freetype.hpp index 5b4b87b40..a08a6454d 100644 --- a/include/mapnik/font_engine_freetype.hpp +++ b/include/mapnik/font_engine_freetype.hpp @@ -169,12 +169,12 @@ public: glyph_ptr get_glyph(unsigned c) const { - BOOST_FOREACH ( face_ptr const& face, faces_) + BOOST_FOREACH ( face_ptr const& face, faces_) { FT_UInt g = face->get_char(c); if (g) return boost::make_shared(face, g); } - + // Final fallback to empty square if nothing better in any font return boost::make_shared(*faces_.begin(), 0); } @@ -185,7 +185,7 @@ public: void set_pixel_sizes(unsigned size) { - BOOST_FOREACH ( face_ptr const& face, faces_) + BOOST_FOREACH ( face_ptr const& face, faces_) { face->set_pixel_sizes(size); } @@ -193,7 +193,7 @@ public: void set_character_sizes(float size) { - BOOST_FOREACH ( face_ptr const& face, faces_) + BOOST_FOREACH ( face_ptr const& face, faces_) { face->set_character_sizes(size); } diff --git a/include/mapnik/line_symbolizer.hpp b/include/mapnik/line_symbolizer.hpp index bcb3ab971..29634db1a 100644 --- a/include/mapnik/line_symbolizer.hpp +++ b/include/mapnik/line_symbolizer.hpp @@ -83,12 +83,12 @@ struct MAPNIK_DECL line_symbolizer : public symbolizer_base { smooth_ = smooth; } - + double smooth() const { return smooth_; } - + private: stroke stroke_; line_rasterizer_e rasterizer_p_; From 61c38d0ec7e8c745a84c17b34da81262aa6a444e Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Thu, 22 Mar 2012 16:35:33 -0700 Subject: [PATCH 144/238] avoid copying unbuffered bbox --- src/feature_style_processor.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/feature_style_processor.cpp b/src/feature_style_processor.cpp index 4b78ab0a3..7cb02042b 100644 --- a/src/feature_style_processor.cpp +++ b/src/feature_style_processor.cpp @@ -248,9 +248,11 @@ void feature_style_processor::apply_to_layer(layer const& lay, Proces layer_ext.clip(map_ext); // forward project layer extent back into native projection if (!prj_trans.forward(layer_ext, PROJ_ENVELOPE_POINTS)) + { std::clog << "WARNING: layer " << lay.name() << " extent " << layer_ext << " in map projection " << " did not reproject properly back to layer projection\n"; + } } else { @@ -262,14 +264,13 @@ void feature_style_processor::apply_to_layer(layer const& lay, Proces } box2d query_ext = m_.get_current_extent(); - box2d unbuffered_extent = m_.get_current_extent(); prj_trans.forward(query_ext, PROJ_ENVELOPE_POINTS); double qw = query_ext.width()>0 ? query_ext.width() : 1; double qh = query_ext.height()>0 ? query_ext.height() : 1; query::resolution_type res(m_.width()/qw, m_.height()/qh); - query q(layer_ext,res,scale_denom,unbuffered_extent); + query q(layer_ext,res,scale_denom,m_.get_current_extent()); p.start_layer_processing(lay, query_ext); std::vector active_styles; attribute_collector collector(names); From d23a4b63b9dfb4e5d82c080c873e256dc0767b2d Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Thu, 22 Mar 2012 16:36:25 -0700 Subject: [PATCH 145/238] expose both a clipped and unclipped labeling path type --- include/mapnik/placement_finder.hpp | 17 +++++++++++++++-- src/placement_finder.cpp | 14 +++++--------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/include/mapnik/placement_finder.hpp b/include/mapnik/placement_finder.hpp index 57d6ee2ab..bf8844808 100644 --- a/include/mapnik/placement_finder.hpp +++ b/include/mapnik/placement_finder.hpp @@ -23,13 +23,20 @@ #ifndef MAPNIK_PLACEMENT_FINDER_HPP #define MAPNIK_PLACEMENT_FINDER_HPP -//mapnik +// mapnik #include #include #include #include +#include +#include -//stl + +// agg +#include "agg_conv_clip_polyline.h" + + +// stl #include namespace mapnik @@ -39,6 +46,12 @@ class text_placement_info; class string_info; class text_path; +typedef agg::conv_clip_polyline clipped_geometry_type; +typedef coord_transform2 ClippedPathType; +typedef coord_transform2 PathType; + +typedef label_collision_detector4 DetectorType; + template class placement_finder : boost::noncopyable diff --git a/src/placement_finder.cpp b/src/placement_finder.cpp index 0132ed656..b7e797203 100644 --- a/src/placement_finder.cpp +++ b/src/placement_finder.cpp @@ -26,14 +26,12 @@ #include #include #include -#include #include #include -#include // agg #include "agg_path_length.h" -#include "agg_conv_clip_polyline.h" + // boost #include #include @@ -674,9 +672,9 @@ void placement_finder::find_line_placements(PathT & shape_path) for (unsigned i = 0; i < current_placement->nodes_.size(); i++) { current_placement->nodes_[i].pos.x -= - pi.get_scale_factor() * displacement * sina; + pi.get_scale_factor() * displacement * sina; current_placement->nodes_[i].pos.y += - pi.get_scale_factor() * displacement * cosa; + pi.get_scale_factor() * displacement * cosa; } } @@ -1058,11 +1056,9 @@ void placement_finder::clear_placements() while (!envelopes_.empty()) envelopes_.pop(); } -typedef agg::conv_clip_polyline clipped_geometry_type; -typedef coord_transform2 PathType; -typedef label_collision_detector4 DetectorType; - template class placement_finder; +template void placement_finder::find_point_placements(ClippedPathType &); +template void placement_finder::find_line_placements(ClippedPathType &); template void placement_finder::find_point_placements(PathType &); template void placement_finder::find_line_placements(PathType &); } // namespace From d991427737fc3bb2e3cd23e5c472a4642a5bccab Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Thu, 22 Mar 2012 16:37:24 -0700 Subject: [PATCH 146/238] formatting --- src/agg/process_line_symbolizer.cpp | 8 ++++---- src/agg/process_polygon_pattern_symbolizer.cpp | 2 +- src/agg/process_polygon_symbolizer.cpp | 2 +- src/cairo_renderer.cpp | 8 ++++---- src/load_map.cpp | 4 ++-- src/save_map.cpp | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/agg/process_line_symbolizer.cpp b/src/agg/process_line_symbolizer.cpp index 8763b882f..6046e116e 100644 --- a/src/agg/process_line_symbolizer.cpp +++ b/src/agg/process_line_symbolizer.cpp @@ -52,7 +52,7 @@ void agg_renderer::process(line_symbolizer const& sym, proj_transform const& prj_trans) { typedef agg::renderer_base ren_base; - + stroke const& stroke_ = sym.get_stroke(); color const& col = stroke_.get_color(); unsigned r=col.red(); @@ -104,7 +104,7 @@ void agg_renderer::process(line_symbolizer const& sym, ras_ptr->reset(); set_gamma_method(stroke_, ras_ptr); - + //metawriter_with_properties writer = sym.get_metawriter(); for (unsigned i=0;inum_geometries();++i) { @@ -145,7 +145,7 @@ void agg_renderer::process(line_symbolizer const& sym, clipped_geometry_type clipped(geom); clipped.clip_box(ext.minx(),ext.miny(),ext.maxx(),ext.maxy()); path_type path(t_,clipped,prj_trans); - + agg::conv_dash dash(path); dash_array const& d = stroke_.get_dash_array(); dash_array::const_iterator itr = d.begin(); @@ -191,7 +191,7 @@ void agg_renderer::process(line_symbolizer const& sym, set_join_caps(stroke_,stroke); stroke.generator().miter_limit(4.0); stroke.generator().width(stroke_.get_width() * scale_factor_); - ras_ptr->add_path(stroke); + ras_ptr->add_path(stroke); } //if (writer.first) writer.first->add_line(path, *feature, t_, writer.second); } diff --git a/src/agg/process_polygon_pattern_symbolizer.cpp b/src/agg/process_polygon_pattern_symbolizer.cpp index 1d6fbde1f..56e1293ef 100644 --- a/src/agg/process_polygon_pattern_symbolizer.cpp +++ b/src/agg/process_polygon_pattern_symbolizer.cpp @@ -74,7 +74,7 @@ void agg_renderer::process(polygon_pattern_symbolizer const& sym, agg::scanline_u8 sl; ras_ptr->reset(); set_gamma_method(sym,ras_ptr); - + std::string filename = path_processor_type::evaluate( *sym.get_filename(), *feature); boost::optional marker; if ( !filename.empty() ) diff --git a/src/agg/process_polygon_symbolizer.cpp b/src/agg/process_polygon_symbolizer.cpp index 2b532fb9f..0cc902b04 100644 --- a/src/agg/process_polygon_symbolizer.cpp +++ b/src/agg/process_polygon_symbolizer.cpp @@ -67,7 +67,7 @@ void agg_renderer::process(polygon_symbolizer const& sym, ras_ptr->reset(); set_gamma_method(sym,ras_ptr); - + //metawriter_with_properties writer = sym.get_metawriter(); box2d inflated_extent = query_extent_ * 1.1; for (unsigned i=0;inum_geometries();++i) diff --git a/src/cairo_renderer.cpp b/src/cairo_renderer.cpp index dff4ce123..589247e0f 100644 --- a/src/cairo_renderer.cpp +++ b/src/cairo_renderer.cpp @@ -754,7 +754,7 @@ void cairo_renderer_base::start_map_processing(Map const& map) clipped.clip_box(query_extent_.minx(),query_extent_.miny(),query_extent_.maxx(),query_extent_.maxy()); path_type path(t_,clipped,prj_trans); context.add_path(path); - } + } } } // fill polygon @@ -877,7 +877,7 @@ void cairo_renderer_base::start_map_processing(Map const& map) { typedef agg::conv_clip_polyline clipped_geometry_type; typedef coord_transform2 path_type; - + mapnik::stroke const& stroke_ = sym.get_stroke(); cairo_context context(context_); context.set_color(stroke_.get_color(), stroke_.get_opacity()); @@ -889,7 +889,7 @@ void cairo_renderer_base::start_map_processing(Map const& map) { context.set_dash(stroke_.get_dash_array()); } - + for (unsigned i = 0; i < feature->num_geometries(); ++i) { geometry_type & geom = feature->get_geometry(i); @@ -900,7 +900,7 @@ void cairo_renderer_base::start_map_processing(Map const& map) clipped_geometry_type clipped(geom); clipped.clip_box(query_extent_.minx(),query_extent_.miny(),query_extent_.maxx(),query_extent_.maxy()); path_type path(t_,clipped,prj_trans); - + context.add_path(path); } } diff --git a/src/load_map.cpp b/src/load_map.cpp index ef5c58f7e..a1344448b 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -991,7 +991,7 @@ void map_parser::parse_line_pattern_symbolizer(rule & rule, xml_node const & sym } void map_parser::parse_polygon_pattern_symbolizer(rule & rule, - xml_node const & sym) + xml_node const & sym) { try { @@ -1307,7 +1307,7 @@ void map_parser::parse_polygon_symbolizer(rule & rule, xml_node const & sym) // smooth value optional smooth = sym.get_opt_attr("smooth"); if (smooth) poly_sym.set_smooth(*smooth); - + parse_metawriter_in_symbolizer(poly_sym, sym); rule.append(poly_sym); } diff --git a/src/save_map.cpp b/src/save_map.cpp index 6aa9fe5d5..8b25e2aff 100644 --- a/src/save_map.cpp +++ b/src/save_map.cpp @@ -96,7 +96,7 @@ public: set_attr( sym_node, "smooth", sym.smooth() ); } } - + void operator () ( line_pattern_symbolizer const& sym ) { ptree & sym_node = rule_.push_back( From caaa8b5392efadbe687aa4d33fedb0f6e58e641a Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Thu, 22 Mar 2012 16:41:44 -0700 Subject: [PATCH 147/238] disable stderr from svg parsing unless in debug mode --- src/svg_parser.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/svg_parser.cpp b/src/svg_parser.cpp index 4d4d30a98..e62f45deb 100644 --- a/src/svg_parser.cpp +++ b/src/svg_parser.cpp @@ -226,10 +226,12 @@ void svg_parser::start_element(xmlTextReaderPtr reader) { parse_gradient_stop(reader); } +#ifdef MAPNIK_DEBUG else if (!xmlStrEqual(name, BAD_CAST "svg")) { std::clog << "notice: unhandled svg element: " << name << "\n"; } +#endif } void svg_parser::end_element(xmlTextReaderPtr reader) From e9940c6add9310515dabff025b07779eba953952 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Thu, 22 Mar 2012 16:44:27 -0700 Subject: [PATCH 148/238] update osm test now that extent is not hardcoded --- tests/python_tests/osm_test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/python_tests/osm_test.py b/tests/python_tests/osm_test.py index ae063e6f3..91ad2a1ed 100644 --- a/tests/python_tests/osm_test.py +++ b/tests/python_tests/osm_test.py @@ -20,10 +20,10 @@ if 'osm' in mapnik.DatasourceCache.instance().plugin_names(): e = ds.envelope() # these are hardcoded in the plugin… ugh - assert_almost_equal(e.minx, -180.0) - assert_almost_equal(e.miny, -90.0) - assert_almost_equal(e.maxx, 180.0) - assert_almost_equal(e.maxy, 90) + eq_(e.minx >= -180.0,True) + eq_(e.miny >= -90.0,True) + eq_(e.maxx <= 180.0,True) + eq_(e.maxy <= 90,True) @raises(RuntimeError) def test_that_nonexistant_query_field_throws(**kwargs): From 242385f16d156ad2f5aa5c3d1cb4a0d451cdbe45 Mon Sep 17 00:00:00 2001 From: Artem Pavlenko Date: Fri, 23 Mar 2012 11:01:18 +0000 Subject: [PATCH 149/238] use mapnik::util namespace for conversions --- include/mapnik/util/conversions.hpp | 11 +++++------ plugins/input/postgis/postgis_datasource.cpp | 12 ++++++------ plugins/input/postgis/postgis_featureset.cpp | 2 +- src/conversions.cpp | 2 +- src/image_util.cpp | 10 +++++----- src/xml_tree.cpp | 6 +++--- 6 files changed, 21 insertions(+), 22 deletions(-) diff --git a/include/mapnik/util/conversions.hpp b/include/mapnik/util/conversions.hpp index c4d4a321b..b57f55ff9 100644 --- a/include/mapnik/util/conversions.hpp +++ b/include/mapnik/util/conversions.hpp @@ -20,15 +20,15 @@ * *****************************************************************************/ -#ifndef MAPNIK_CONVERSIONS_UTIL_HPP -#define MAPNIK_CONVERSIONS_UTIL_HPP +#ifndef MAPNIK_UTIL_CONVERSIONS_HPP +#define MAPNIK_UTIL_CONVERSIONS_HPP // mapnik // stl #include -namespace mapnik { namespace conversions { +namespace mapnik { namespace util { bool string2int(const char * value, int & result); bool string2int(std::string const& value, int & result); @@ -39,7 +39,6 @@ namespace mapnik { namespace conversions { bool string2float(std::string const& value, float & result); bool string2float(const char * value, float & result); - } -} +}} -#endif // MAPNIK_CONVERSIONS_UTIL_HPP +#endif // MAPNIK_UTIL_CONVERSIONS_HPP diff --git a/plugins/input/postgis/postgis_datasource.cpp b/plugins/input/postgis/postgis_datasource.cpp index 05c397892..316ee02c1 100644 --- a/plugins/input/postgis/postgis_datasource.cpp +++ b/plugins/input/postgis/postgis_datasource.cpp @@ -175,7 +175,7 @@ void postgis_datasource::bind() const if (srid_c != NULL) { int result; - if (mapnik::conversions::string2int(srid_c,result)) + if (mapnik::util::string2int(srid_c,result)) srid_ = result; } } @@ -205,7 +205,7 @@ void postgis_datasource::bind() const if (srid_c != NULL) { int result; - if (mapnik::conversions::string2int(srid_c,result)) + if (mapnik::util::string2int(srid_c,result)) srid_ = result; } } @@ -685,10 +685,10 @@ box2d postgis_datasource::envelope() const double loy; double hix; double hiy; - if (mapnik::conversions::string2double(rs->getValue(0),lox) && - mapnik::conversions::string2double(rs->getValue(1),loy) && - mapnik::conversions::string2double(rs->getValue(2),hix) && - mapnik::conversions::string2double(rs->getValue(3),hiy)) + if (mapnik::util::string2double(rs->getValue(0),lox) && + mapnik::util::string2double(rs->getValue(1),loy) && + mapnik::util::string2double(rs->getValue(2),hix) && + mapnik::util::string2double(rs->getValue(3),hiy)) { extent_.init(lox,loy,hix,hiy); extent_initialized_ = true; diff --git a/plugins/input/postgis/postgis_featureset.cpp b/plugins/input/postgis/postgis_featureset.cpp index f4bbfe4b2..177413fee 100644 --- a/plugins/input/postgis/postgis_featureset.cpp +++ b/plugins/input/postgis/postgis_featureset.cpp @@ -162,7 +162,7 @@ feature_ptr postgis_featureset::next() { std::string str = mapnik::sql_utils::numeric2string(buf); double val; - if (mapnik::conversions::string2double(str,val)) + if (mapnik::util::string2double(str,val)) feature->put(name,val); } else diff --git a/src/conversions.cpp b/src/conversions.cpp index d19272162..76e9d0531 100644 --- a/src/conversions.cpp +++ b/src/conversions.cpp @@ -31,7 +31,7 @@ BOOST_AUTO(name, boost::proto::deep_copy(expr)); \ -namespace mapnik { namespace conversions { +namespace mapnik { namespace util { using namespace boost::spirit; diff --git a/src/image_util.cpp b/src/image_util.cpp index dfc1c54c6..cde2ba9e2 100644 --- a/src/image_util.cpp +++ b/src/image_util.cpp @@ -158,7 +158,7 @@ void handle_png_options(std::string const& type, if (*colors < 0) throw ImageWriterException("invalid color parameter: unavailable for true color images"); - if (!mapnik::conversions::string2int(t.substr(2),*colors) || *colors < 0 || *colors > 256) + if (!mapnik::util::string2int(t.substr(2),*colors) || *colors < 0 || *colors > 256) throw ImageWriterException("invalid color parameter: " + t.substr(2)); } else if (boost::algorithm::starts_with(t, "t=")) @@ -166,14 +166,14 @@ void handle_png_options(std::string const& type, if (*colors < 0) throw ImageWriterException("invalid trans_mode parameter: unavailable for true color images"); - if (!mapnik::conversions::string2int(t.substr(2),*trans_mode) || *trans_mode < 0 || *trans_mode > 2) + if (!mapnik::util::string2int(t.substr(2),*trans_mode) || *trans_mode < 0 || *trans_mode > 2) throw ImageWriterException("invalid trans_mode parameter: " + t.substr(2)); } else if (boost::algorithm::starts_with(t, "g=")) { if (*colors < 0) throw ImageWriterException("invalid gamma parameter: unavailable for true color images"); - if (!mapnik::conversions::string2double(t.substr(2),*gamma) || *gamma < 0) + if (!mapnik::util::string2double(t.substr(2),*gamma) || *gamma < 0) { throw ImageWriterException("invalid gamma parameter: " + t.substr(2)); } @@ -186,7 +186,7 @@ void handle_png_options(std::string const& type, #define Z_BEST_COMPRESSION 9 #define Z_DEFAULT_COMPRESSION (-1) */ - if (!mapnik::conversions::string2int(t.substr(2),*compression) + if (!mapnik::util::string2int(t.substr(2),*compression) || *compression < Z_DEFAULT_COMPRESSION || *compression > Z_BEST_COMPRESSION) { @@ -317,7 +317,7 @@ void save_to_stream(T const& image, std::string const& val = t.substr(4); if (!val.empty()) { - if (!mapnik::conversions::string2int(val,quality) || quality < 0 || quality > 100) + if (!mapnik::util::string2int(val,quality) || quality < 0 || quality > 100) { throw ImageWriterException("invalid jpeg quality: '" + val + "'"); } diff --git a/src/xml_tree.cpp b/src/xml_tree.cpp index ef61e9258..36995166f 100644 --- a/src/xml_tree.cpp +++ b/src/xml_tree.cpp @@ -54,7 +54,7 @@ template <> inline boost::optional fast_cast(xml_tree const& tree, std::string const& value) { int result; - if (mapnik::conversions::string2int(value, result)) + if (mapnik::util::string2int(value, result)) return boost::optional(result); return boost::optional(); } @@ -63,7 +63,7 @@ template <> inline boost::optional fast_cast(xml_tree const& tree, std::string const& value) { double result; - if (mapnik::conversions::string2double(value, result)) + if (mapnik::util::string2double(value, result)) return boost::optional(result); return boost::optional(); } @@ -72,7 +72,7 @@ template <> inline boost::optional fast_cast(xml_tree const& tree, std::string const& value) { float result; - if (mapnik::conversions::string2float(value, result)) + if (mapnik::util::string2float(value, result)) return boost::optional(result); return boost::optional(); } From b4e96c35b558a0b0a4e999f5e7f2bba0e08fb569 Mon Sep 17 00:00:00 2001 From: Artem Pavlenko Date: Fri, 23 Mar 2012 11:56:23 +0000 Subject: [PATCH 150/238] + impl dasharray parser (supports 'none') + skip 0,0 dashes in load_map --- include/mapnik/util/dasharray_parser.hpp | 62 ++++++++++++++++++++++++ src/load_map.cpp | 47 ++++++++---------- 2 files changed, 81 insertions(+), 28 deletions(-) create mode 100644 include/mapnik/util/dasharray_parser.hpp diff --git a/include/mapnik/util/dasharray_parser.hpp b/include/mapnik/util/dasharray_parser.hpp new file mode 100644 index 000000000..e9a3753f2 --- /dev/null +++ b/include/mapnik/util/dasharray_parser.hpp @@ -0,0 +1,62 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2012 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 + * + *****************************************************************************/ + +#ifndef MAPNIK_UTIL_DASHARRAY_PARSER_HPP +#define MAPNIK_UTIL_DASHARRAY_PARSER_HPP + +#include +#include +#include +#include + +namespace mapnik { namespace util { + +template +bool parse_dasharray(Iterator first, Iterator last, std::vector& dasharray) +{ + using qi::double_; + using qi::phrase_parse; + using qi::_1; + using qi::lit; + using ascii::space; + using phoenix::push_back; + // SVG + // dasharray ::= (length | percentage) (comma-wsp dasharray)? + // no support for 'percentage' as viewport is unknown at load_map + // + bool r = phrase_parse(first, last, + ( + lit("none") | (double_[push_back(phoenix::ref(dasharray), _1)] + >> *(',' >> double_[push_back(phoenix::ref(dasharray), _1)])) + ) + , + space); + + if (first != last) + return false; + + return r; +} + +}} + +#endif // MAPNIK_UTIL_DASHARRAY_PARSER_HPP diff --git a/src/load_map.cpp b/src/load_map.cpp index a1344448b..ee4313d91 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -49,6 +49,7 @@ #include #include #include +#include // boost #include @@ -1223,39 +1224,29 @@ void map_parser::parse_stroke(stroke & strk, xml_node const & sym) optional str = sym.get_opt_attr("stroke-dasharray"); if (str) { - tokenizer<> tok (*str); std::vector dash_array; - tokenizer<>::iterator itr = tok.begin(); - for (; itr != tok.end(); ++itr) + if (util::parse_dasharray((*str).begin(),(*str).end(),dash_array)) { - try + if (!dash_array.empty()) { - double f = boost::lexical_cast(*itr); - dash_array.push_back(f); - } - catch (boost::bad_lexical_cast &) - { - throw config_error(std::string("Failed to parse dasharray ") + - "'. Expected a " + - "list of floats but got '" + (*str) + "'"); - } - } - if (dash_array.size()) - { - size_t size = dash_array.size(); - if (size % 2) - { - for (size_t i=0; i < size ;++i) + size_t size = dash_array.size(); + if (size % 2 == 1) + dash_array.insert(dash_array.end(),dash_array.begin(),dash_array.end()); + + std::vector::const_iterator pos = dash_array.begin(); + while (pos != dash_array.end()) { - dash_array.push_back(dash_array[i]); + if (*pos > 0.0 || *(pos+1) > 0.0) // avoid both dash and gap eq 0.0 + strk.add_dash(*pos,*(pos + 1)); + pos +=2; } - } - std::vector::const_iterator pos = dash_array.begin(); - while (pos != dash_array.end()) - { - strk.add_dash(*pos,*(pos + 1)); - pos +=2; - } + } + } + else + { + throw config_error(std::string("Failed to parse dasharray ") + + "'. Expected a " + + "list of floats or 'none' but got '" + (*str) + "'"); } } } From 97a9abc8f3428ac59c89e937e369f5e5b6abaee0 Mon Sep 17 00:00:00 2001 From: Artem Pavlenko Date: Fri, 23 Mar 2012 12:56:26 +0000 Subject: [PATCH 151/238] + add 'wsp' delimiter support + re-arrange grammar a bit --- include/mapnik/util/dasharray_parser.hpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/include/mapnik/util/dasharray_parser.hpp b/include/mapnik/util/dasharray_parser.hpp index e9a3753f2..32d405bed 100644 --- a/include/mapnik/util/dasharray_parser.hpp +++ b/include/mapnik/util/dasharray_parser.hpp @@ -37,20 +37,17 @@ bool parse_dasharray(Iterator first, Iterator last, std::vector& dasharr using qi::phrase_parse; using qi::_1; using qi::lit; - using ascii::space; + using qi::char_; + using qi::no_skip; using phoenix::push_back; // SVG // dasharray ::= (length | percentage) (comma-wsp dasharray)? // no support for 'percentage' as viewport is unknown at load_map // bool r = phrase_parse(first, last, - ( - lit("none") | (double_[push_back(phoenix::ref(dasharray), _1)] - >> *(',' >> double_[push_back(phoenix::ref(dasharray), _1)])) - ) - , - space); - + (double_[push_back(phoenix::ref(dasharray), _1)] % no_skip[char_(", ")] | lit("none")), + qi::ascii::space); + if (first != last) return false; From 30f59aac0cc09660744a5afb925a21169634fdaf Mon Sep 17 00:00:00 2001 From: Datendelphin Date: Fri, 23 Mar 2012 17:33:35 +0100 Subject: [PATCH 152/238] fix invalid memory access in font engine acess of member face->num_faces after free() fixed by making local copy num_faces before FT_Done_Face(face) --- src/font_engine_freetype.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/font_engine_freetype.cpp b/src/font_engine_freetype.cpp index f30c41480..8e0746f29 100644 --- a/src/font_engine_freetype.cpp +++ b/src/font_engine_freetype.cpp @@ -83,10 +83,11 @@ bool freetype_engine::register_font(std::string const& file_name) } FT_Face face = 0; + int num_faces = 0; // some font files have multiple fonts in a file // the count is in the 'root' face library[0] // see the FT_FaceRec in freetype.h - for ( int i = 0; face == 0 || i < face->num_faces; i++ ) { + for ( int i = 0; face == 0 || i < num_faces; i++ ) { // if face is null then this is the first face error = FT_New_Face (library,file_name.c_str(),i,&face); if (error) @@ -94,6 +95,9 @@ bool freetype_engine::register_font(std::string const& file_name) FT_Done_FreeType(library); return false; } + // store num_faces locally, after FT_Done_Face it can not be accessed any more + if (!num_faces) + num_faces = face->num_faces; // some fonts can lack names, skip them // http://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_FaceRec if (face->family_name && face->style_name) { From 2ff8c3344e8cc9981c871410dd856331de98207f Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Fri, 23 Mar 2012 11:22:00 -0700 Subject: [PATCH 153/238] avoid copy --- include/mapnik/feature.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/mapnik/feature.hpp b/include/mapnik/feature.hpp index 8a8d75bd5..fa517101c 100644 --- a/include/mapnik/feature.hpp +++ b/include/mapnik/feature.hpp @@ -224,13 +224,14 @@ public: box2d envelope() const { + // TODO - cache this box2d result; for (unsigned i=0;i box = geom.envelope(); + box2d const& box = geom.envelope(); result.init(box.minx(),box.miny(),box.maxx(),box.maxy()); } else From e570ea3a9890cc1c9588b5149cbb393b859416db Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Fri, 23 Mar 2012 12:42:11 -0700 Subject: [PATCH 154/238] hook up visual tests to the 'make test' target --- Makefile | 1 + tests/visual_tests/compare.py | 8 ++++++-- tests/visual_tests/test.py | 1 + tests/visual_tests/test_python.py | 10 ++++++---- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 51a2abbab..e8957a80d 100755 --- a/Makefile +++ b/Makefile @@ -19,6 +19,7 @@ uninstall: test: @python tests/run_tests.py -q + @python tests/visual_tests/test.py pep8: # https://gist.github.com/1903033 diff --git a/tests/visual_tests/compare.py b/tests/visual_tests/compare.py index 414f49979..46bba923e 100644 --- a/tests/visual_tests/compare.py +++ b/tests/visual_tests/compare.py @@ -42,9 +42,9 @@ def compare(fn1, fn2): def summary(): global errors + print "-"*80 + print "Summary:" if len(errors) != 0: - print "-"*80 - print "Summary:" for error in errors: if (error[1] is None): print "Could not verify %s: No reference image found!" % error[0] @@ -52,3 +52,7 @@ def summary(): print "%s failed: %d different pixels" % error print "-"*80 sys.exit(1) + else: + print 'No errors detected!' + print "-"*80 + sys.exit(0) diff --git a/tests/visual_tests/test.py b/tests/visual_tests/test.py index 554c1c5c5..29716e6bd 100755 --- a/tests/visual_tests/test.py +++ b/tests/visual_tests/test.py @@ -19,6 +19,7 @@ sizes_many_in_small_range = [(490, 100), (495, 100), (497, 100), (498, 100), (499, 100), (500, 100), (501, 100), (502, 100), (505, 100), (510, 100)] dirname = os.path.dirname(__file__) + files = [ {'name': "list", 'sizes': sizes_many_in_big_range}, {'name': "simple", 'sizes': sizes_many_in_big_range}, diff --git a/tests/visual_tests/test_python.py b/tests/visual_tests/test_python.py index 0cba25907..9648a8626 100755 --- a/tests/visual_tests/test_python.py +++ b/tests/visual_tests/test_python.py @@ -4,6 +4,8 @@ import sys import os.path from compare import compare, summary +dirname = os.path.dirname(__file__) + class MyText(mapnik.FormattingNode): def __init__(self): mapnik.FormattingNode.__init__(self) @@ -69,7 +71,7 @@ m.append_style('Style', style) layer = mapnik.Layer('Layer') -layer.datasource = mapnik.Shapefile(file="data/points.shp") +layer.datasource = mapnik.Shapefile(file=os.path.join(dirname,"data/points.shp")) layer.styles.append('Style') m.layers.append(layer) @@ -97,9 +99,9 @@ format_trees = [ for format_tree in format_trees: text.placements.defaults.format_tree = format_tree[1] - mapnik.render_to_file(m, os.path.join("images", 'python-%s.png' % format_tree[0]), 'png') - compare(os.path.join("images", 'python-%s.png' % format_tree[0]), - os.path.join("images", 'python-%s-reference.png' % format_tree[0]) + mapnik.render_to_file(m, os.path.join(dirname,"images", 'python-%s.png' % format_tree[0]), 'png') + compare(os.path.join(dirname,"images", 'python-%s.png' % format_tree[0]), + os.path.join(dirname,"images", 'python-%s-reference.png' % format_tree[0]) ) summary() From 5e6632f6ebe76153763d3cb9a9a5866a8916b8b3 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Fri, 23 Mar 2012 14:22:47 -0700 Subject: [PATCH 155/238] declare register_fonts as static --- bindings/python/mapnik_font_engine.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/bindings/python/mapnik_font_engine.cpp b/bindings/python/mapnik_font_engine.cpp index 3992b1287..1b2596e3c 100644 --- a/bindings/python/mapnik_font_engine.cpp +++ b/bindings/python/mapnik_font_engine.cpp @@ -42,6 +42,7 @@ void export_font_engine() .def("register_fonts",&freetype_engine::register_fonts) .def("face_names",&freetype_engine::face_names) .staticmethod("register_font") + .staticmethod("register_fonts") .staticmethod("face_names") ; } From 3b498efbd99b68bc46995c97dd34bcc3d0896a5f Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Fri, 23 Mar 2012 15:07:28 -0700 Subject: [PATCH 156/238] fixup font registration code ensuring invalid fonts will warn but not throw and register_fonts will only return success if > one font is registered and none have failed --- Makefile | 3 ++ SConstruct | 2 +- src/font_engine_freetype.cpp | 60 +++++++++++---------- src/load_map.cpp | 8 ++- tests/cpp_tests/build.py | 2 + tests/cpp_tests/font_registration_test.cpp | 27 ++++++---- tests/data/fonts/fake.ttf | 0 tests/data/fonts/intentionally-broken.ttf | Bin 0 -> 49420 bytes tests/data/good_maps/rtl_text_map.xml | 4 +- 9 files changed, 65 insertions(+), 41 deletions(-) create mode 100644 tests/data/fonts/fake.ttf create mode 100644 tests/data/fonts/intentionally-broken.ttf diff --git a/Makefile b/Makefile index e8957a80d..33de62e87 100755 --- a/Makefile +++ b/Makefile @@ -27,4 +27,7 @@ pep8: @pep8 -r --select=W293 -q --filename=*.py `pwd`/tests/ | xargs gsed -i 's/^[ \r\t]*$//' @pep8 -r --select=W391 -q --filename=*.py `pwd`/tests/ | xargs gsed -i -e :a -e '/^\n*$/{$d;N;ba' -e '}' +grind: + @valgrind --leak-check=full tests/cpp_tests/font_registration_test + .PHONY: clean reset uninstall test install diff --git a/SConstruct b/SConstruct index 60352732c..c014b9bb5 100644 --- a/SConstruct +++ b/SConstruct @@ -1716,7 +1716,7 @@ if not HELP_REQUESTED: # build C++ tests # not ready for release - #SConscript('tests/cpp_tests/build.py') + SConscript('tests/cpp_tests/build.py') # not ready for release #if env['SVG_RENDERER']: diff --git a/src/font_engine_freetype.cpp b/src/font_engine_freetype.cpp index 8e0746f29..cef4551d8 100644 --- a/src/font_engine_freetype.cpp +++ b/src/font_engine_freetype.cpp @@ -71,11 +71,10 @@ bool freetype_engine::is_font_file(std::string const& file_name) bool freetype_engine::register_font(std::string const& file_name) { - if (!boost::filesystem::is_regular_file(file_name) || !is_font_file(file_name)) return false; #ifdef MAPNIK_THREADSAFE mutex::scoped_lock lock(mutex_); #endif - FT_Library library; + FT_Library library = 0; FT_Error error = FT_Init_FreeType(&library); if (error) { @@ -84,6 +83,7 @@ bool freetype_engine::register_font(std::string const& file_name) FT_Face face = 0; int num_faces = 0; + bool success = false; // some font files have multiple fonts in a file // the count is in the 'root' face library[0] // see the FT_FaceRec in freetype.h @@ -92,31 +92,37 @@ bool freetype_engine::register_font(std::string const& file_name) error = FT_New_Face (library,file_name.c_str(),i,&face); if (error) { - FT_Done_FreeType(library); - return false; + break; } // store num_faces locally, after FT_Done_Face it can not be accessed any more if (!num_faces) num_faces = face->num_faces; // some fonts can lack names, skip them // http://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_FaceRec - if (face->family_name && face->style_name) { + if (face->family_name && face->style_name) + { + success = true; std::string name = std::string(face->family_name) + " " + std::string(face->style_name); name2file_.insert(std::make_pair(name, std::make_pair(i,file_name))); - FT_Done_Face(face); - //FT_Done_FreeType(library); - //return true; - } else { - FT_Done_Face(face); - FT_Done_FreeType(library); + } + else + { std::ostringstream s; - s << "Error: unable to load invalid font file which lacks identifiable family and style name: '" - << file_name << "'"; - throw std::runtime_error(s.str()); + s << "Warning: unable to load font file '" << file_name << "' "; + if (!face->family_name && !face->style_name) + s << "which lacks both a family name and style name"; + else if (face->family_name) + s << "which reports a family name of '" << std::string(face->family_name) << "' and lacks a style name"; + else if (face->style_name) + s << "which reports a style name of '" << std::string(face->style_name) << "' and lacks a family name"; + std::clog << s.str() << std::endl; } } - FT_Done_FreeType(library); - return true; + if (face) + FT_Done_Face(face); + if (library) + FT_Done_FreeType(library); + return success; } bool freetype_engine::register_fonts(std::string const& dir, bool recurse) @@ -130,26 +136,24 @@ bool freetype_engine::register_fonts(std::string const& dir, bool recurse) return mapnik::freetype_engine::register_font(dir); boost::filesystem::directory_iterator end_itr; + bool success = false; for (boost::filesystem::directory_iterator itr(dir); itr != end_itr; ++itr) { +#if (BOOST_FILESYSTEM_VERSION == 3) + std::string const& file_name = itr->path().string(); +#else // v2 + std::string const& file_name = itr->string(); +#endif if (boost::filesystem::is_directory(*itr) && recurse) { -#if (BOOST_FILESYSTEM_VERSION == 3) - if (!register_fonts(itr->path().string(), true)) return false; -#else // v2 - if (!register_fonts(itr->string(), true)) return false; -#endif + success = register_fonts(file_name, true); } - else + else if (boost::filesystem::is_regular_file(file_name) && is_font_file(file_name)) { -#if (BOOST_FILESYSTEM_VERSION == 3) - mapnik::freetype_engine::register_font(itr->path().string()); -#else // v2 - mapnik::freetype_engine::register_font(itr->string()); -#endif + success = mapnik::freetype_engine::register_font(file_name); } } - return true; + return success; } diff --git a/src/load_map.cpp b/src/load_map.cpp index ee4313d91..2682015fd 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -242,7 +242,13 @@ void map_parser::parse_map(Map & map, xml_node const& pt, std::string const& bas if (font_directory) { extra_attr["font-directory"] = *font_directory; - freetype_engine::register_fonts(ensure_relative_to_xml(font_directory), false); + if (!freetype_engine::register_fonts(ensure_relative_to_xml(font_directory), false)) + { + if (strict_) + { + throw config_error(std::string("Failed to load fonts from: ") << *font_directory); + } + } } optional min_version_string = map_node.get_opt_attr("minimum-version"); diff --git a/tests/cpp_tests/build.py b/tests/cpp_tests/build.py index 11cd33f45..bcc31bc94 100644 --- a/tests/cpp_tests/build.py +++ b/tests/cpp_tests/build.py @@ -11,6 +11,8 @@ headers = env['CPPPATH'] libraries = copy(env['LIBMAPNIK_LIBS']) libraries.append('mapnik') +test_env.Append(CXXFLAGS='-g') + for cpp_test in glob.glob('*_test.cpp'): test_program = test_env.Program(cpp_test.replace('.cpp',''), [cpp_test], CPPPATH=headers, LIBS=libraries, LINKFLAGS=env['CUSTOM_LDFLAGS']) Depends(test_program, env.subst('../../src/%s' % env['MAPNIK_LIB_NAME'])) diff --git a/tests/cpp_tests/font_registration_test.cpp b/tests/cpp_tests/font_registration_test.cpp index 1906d896b..55715b9b1 100644 --- a/tests/cpp_tests/font_registration_test.cpp +++ b/tests/cpp_tests/font_registration_test.cpp @@ -6,8 +6,6 @@ using fs::path; namespace sys = boost::system; #include -//#include -//#include #include #include @@ -39,14 +37,23 @@ int main( int, char*[] ) // directories without fonts std::string src("src"); - // a legitimate directory will return true even it is does not - // successfully register a font... - BOOST_TEST( mapnik::freetype_engine::register_fonts(src , true ) ); - face_names = mapnik::freetype_engine::face_names(); - BOOST_TEST( face_names.size() == 0 ); - std::clog << "number of registered fonts: " << face_names.size() << std::endl; + // an empty directory will not return true + // we need to register at least one font and not fail on any + // to return true + BOOST_TEST( mapnik::freetype_engine::register_font(src) == false ); + BOOST_TEST( mapnik::freetype_engine::register_fonts(src, true) == false ); + BOOST_TEST( mapnik::freetype_engine::face_names().size() == 0 ); - // register unifont + // bogus, emtpy file that looks like font + BOOST_TEST( mapnik::freetype_engine::register_font("tests/data/fonts/fake.ttf") == false ); + BOOST_TEST( mapnik::freetype_engine::register_fonts("tests/data/fonts/fake.ttf") == false ); + BOOST_TEST( mapnik::freetype_engine::face_names().size() == 0 ); + + BOOST_TEST( mapnik::freetype_engine::register_font("tests/data/fonts/intentionally-broken.ttf") == false ); + BOOST_TEST( mapnik::freetype_engine::register_fonts("tests/data/fonts/intentionally-broken.ttf") == false ); + BOOST_TEST( mapnik::freetype_engine::face_names().size() == 0 ); + + // register unifont, since we know it sits in the root fonts/ dir BOOST_TEST( mapnik::freetype_engine::register_fonts(fontdir) ); face_names = mapnik::freetype_engine::face_names(); std::clog << "number of registered fonts: " << face_names.size() << std::endl; @@ -71,7 +78,7 @@ int main( int, char*[] ) BOOST_TEST( mapnik::freetype_engine::register_fonts(fontdir, true) ); face_names = mapnik::freetype_engine::face_names(); std::clog << "number of registered fonts: " << face_names.size() << std::endl; - BOOST_TEST( face_names.size() == 21 ); + BOOST_TEST( face_names.size() == 22 ); return ::boost::report_errors(); } diff --git a/tests/data/fonts/fake.ttf b/tests/data/fonts/fake.ttf new file mode 100644 index 000000000..e69de29bb diff --git a/tests/data/fonts/intentionally-broken.ttf b/tests/data/fonts/intentionally-broken.ttf new file mode 100644 index 0000000000000000000000000000000000000000..887230d766e322b73024f482f859abecf6818fb2 GIT binary patch literal 49420 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 literal 0 HcmV?d00001 diff --git a/tests/data/good_maps/rtl_text_map.xml b/tests/data/good_maps/rtl_text_map.xml index 3892c69af..38f46aca1 100644 --- a/tests/data/good_maps/rtl_text_map.xml +++ b/tests/data/good_maps/rtl_text_map.xml @@ -1,5 +1,7 @@ - + + + diff --git a/tests/data/good_maps/markers_symbolizer_lines_file.xml b/tests/data/good_maps/markers_symbolizer_lines_file.xml new file mode 100644 index 000000000..39d2f1dbd --- /dev/null +++ b/tests/data/good_maps/markers_symbolizer_lines_file.xml @@ -0,0 +1,19 @@ + + + + + + 1 + + sqlite + ../sqlite/qgis_spatiallite.sqlite + lines + + + + \ No newline at end of file diff --git a/tests/data/good_maps/markers_symbolizer_points_file.xml b/tests/data/good_maps/markers_symbolizer_points_file.xml new file mode 100644 index 000000000..304adc572 --- /dev/null +++ b/tests/data/good_maps/markers_symbolizer_points_file.xml @@ -0,0 +1,33 @@ + + + + + + 1 + + sqlite + ../sqlite/qgis_spatiallite.sqlite + point + + + + \ No newline at end of file From 67f6d0f6724b7181fd83dda0b17c6123ab9be1d9 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Tue, 27 Mar 2012 18:55:33 -0400 Subject: [PATCH 171/238] fixup pycairo rendering test --- tests/python_tests/cairo_test.py | 70 +++++++++++++++++--------------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/tests/python_tests/cairo_test.py b/tests/python_tests/cairo_test.py index ff2ab76fe..bd566a373 100644 --- a/tests/python_tests/cairo_test.py +++ b/tests/python_tests/cairo_test.py @@ -10,49 +10,53 @@ def setup(): # from another directory we need to chdir() os.chdir(execution_path('.')) -def _pycairo_surface(type,sym): - if mapnik.has_pycairo(): - import cairo - test_cairo_file = 'test.%s' % type - m = mapnik.Map(256,256) - mapnik.load_map(m,'../data/good_maps/%s_symbolizer.xml' % sym) - surface = getattr(cairo,'%sSurface' % type.upper())(test_cairo_file, m.width,m.height) - mapnik.render(m, surface) - surface.finish() +if mapnik.has_pycairo(): - if os.path.exists(test_cairo_file): - os.remove(test_cairo_file) - return True - else: - # Fail, the file wasn't written - return False + def _pycairo_surface(type,sym): + import cairo + test_cairo_file = '/tmp/test.%s' % type + m = mapnik.Map(256,256) + mapnik.load_map(m,'../data/good_maps/%s_symbolizer.xml' % sym) + if hasattr(cairo,'%sSurface' % type.upper()): + surface = getattr(cairo,'%sSurface' % type.upper())(test_cairo_file, m.width,m.height) + mapnik.render(m, surface) + surface.finish() + if os.path.exists(test_cairo_file): + os.remove(test_cairo_file) + return True + else: + # Fail, the file wasn't written + return False + else: + print 'skipping cairo.%s test since surface is not available' % type.upper() + return True -def test_pycairo_svg_surface(): - return _pycairo_surface('svg','point') + def test_pycairo_svg_surface1(): + eq_(_pycairo_surface('svg','point'),True) -def test_pycairo_svg_surface(): - return _pycairo_surface('svg','building') + def test_pycairo_svg_surface2(): + eq_(_pycairo_surface('svg','building'),True) -def test_pycairo_svg_surface(): - return _pycairo_surface('svg','polygon') + def test_pycairo_svg_surface3(): + eq_(_pycairo_surface('svg','polygon'),True) -def test_pycairo_svg_surface(): - return _pycairo_surface('pdf','point') + def test_pycairo_pdf_surface1(): + eq_(_pycairo_surface('pdf','point'),True) -def test_pycairo_svg_surface(): - return _pycairo_surface('pdf','building') + def test_pycairo_pdf_surface2(): + eq_(_pycairo_surface('pdf','building'),True) -def test_pycairo_svg_surface(): - return _pycairo_surface('pdf','polygon') + def test_pycairo_pdf_surface3(): + eq_(_pycairo_surface('pdf','polygon'),True) -def test_pycairo_svg_surface(): - return _pycairo_surface('ps','point') + def test_pycairo_ps_surface1(): + eq_(_pycairo_surface('ps','point'),True) -def test_pycairo_svg_surface(): - return _pycairo_surface('ps','building') + def test_pycairo_ps_surface2(): + eq_(_pycairo_surface('ps','building'),True) -def test_pycairo_svg_surface(): - return _pycairo_surface('ps','polygon') + def test_pycairo_ps_surface3(): + eq_(_pycairo_surface('ps','polygon'),True) if __name__ == "__main__": setup() From e22e47dfccdcdb3f2ff5a91bf7fc898e34e288d2 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Tue, 27 Mar 2012 19:14:01 -0400 Subject: [PATCH 172/238] scons: when statically linking: link extra icu libs needed by regex --- SConstruct | 3 +++ 1 file changed, 3 insertions(+) diff --git a/SConstruct b/SConstruct index c014b9bb5..95726262f 100644 --- a/SConstruct +++ b/SConstruct @@ -810,6 +810,9 @@ int main() return False def boost_regex_has_icu(context): + if env['RUNTIME_LINK'] == 'static': + context.env.Append(LIBS='icui18n') + context.env.Append(LIBS='icudata') ret = context.TryRun(""" #include From 2edaefd0d661b34c0c888ecc0d01707f8f853f46 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Tue, 27 Mar 2012 19:21:11 -0400 Subject: [PATCH 173/238] cairo: match AGG functionality, adding support for dynamic ellipse drawing and loading from svg icons - closes #1071 (refs #952 - this is stopgap until we refactor and merge with point_symbolizer) --- include/mapnik/cairo_renderer.hpp | 2 +- src/cairo_renderer.cpp | 281 +++++++++++++++++++++++++++--- 2 files changed, 254 insertions(+), 29 deletions(-) diff --git a/include/mapnik/cairo_renderer.hpp b/include/mapnik/cairo_renderer.hpp index dd37ac906..408662ef9 100644 --- a/include/mapnik/cairo_renderer.hpp +++ b/include/mapnik/cairo_renderer.hpp @@ -123,7 +123,7 @@ public: } protected: - void render_marker(pixel_position const& pos, marker const& marker, const agg::trans_affine & mtx, double opacity=1.0); + void render_marker(pixel_position const& pos, marker const& marker, const agg::trans_affine & mtx, double opacity=1.0, bool recenter=true); Map const& m_; Cairo::RefPtr context_; diff --git a/src/cairo_renderer.cpp b/src/cairo_renderer.cpp index 589247e0f..a87f48749 100644 --- a/src/cairo_renderer.cpp +++ b/src/cairo_renderer.cpp @@ -55,6 +55,11 @@ #include "agg_conv_clip_polygon.h" #include "agg_conv_smooth_poly1.h" +// markers +#include "agg_path_storage.h" +#include "agg_ellipse.h" + + // stl #ifdef MAPNIK_DEBUG #include @@ -907,7 +912,7 @@ void cairo_renderer_base::start_map_processing(Map const& map) context.stroke(); } - void cairo_renderer_base::render_marker(pixel_position const& pos, marker const& marker, const agg::trans_affine & tr, double opacity) + void cairo_renderer_base::render_marker(pixel_position const& pos, marker const& marker, const agg::trans_affine & tr, double opacity, bool recenter) { cairo_context context(context_); @@ -916,15 +921,21 @@ void cairo_renderer_base::start_map_processing(Map const& map) box2d bbox; bbox = (*marker.get_vector_data())->bounding_box(); - coord c = bbox.center(); - // center the svg marker on '0,0' - agg::trans_affine mtx = agg::trans_affine_translation(-c.x,-c.y); - // apply symbol transformation to get to map space - mtx *= tr; - // render the marker at the center of the marker box - mtx.translate(pos.x+0.5 * marker.width(), pos.y+0.5 * marker.height()); + agg::trans_affine mtx = tr; + + if (recenter) + { + coord c = bbox.center(); + // center the svg marker on '0,0' + mtx = agg::trans_affine_translation(-c.x,-c.y); + // apply symbol transformation to get to map space + mtx *= tr; + // render the marker at the center of the marker box + mtx.translate(pos.x+0.5 * marker.width(), pos.y+0.5 * marker.height()); + } typedef coord_transform2 path_type; + agg::trans_affine transform; mapnik::path_ptr vmarker = *marker.get_vector_data(); using namespace mapnik::svg; agg::pod_bvector const & attributes_ = vmarker->attributes(); @@ -936,10 +947,13 @@ void cairo_renderer_base::start_map_processing(Map const& map) context.save(); - agg::trans_affine transform = attr.transform; + transform = attr.transform; transform *= mtx; - if (transform.is_valid() && !transform.is_identity()) + // TODO - this 'is_valid' check is not used in the AGG renderer and also + // appears to lead to bogus results with + // tests/data/good_maps/markers_symbolizer_lines_file.xml + if (/*transform.is_valid() && */ !transform.is_identity()) { double m[6]; transform.store_to(m); @@ -974,7 +988,6 @@ void cairo_renderer_base::start_map_processing(Map const& map) } } - if(attr.stroke_gradient.get_gradient_type() != NO_GRADIENT || attr.stroke_flag) { context.add_agg_path(svg_path,attr.index); @@ -1243,37 +1256,249 @@ void cairo_renderer_base::start_map_processing(Map const& map) } } -// TODO - this is woefully behind the AGG version. void cairo_renderer_base::process(markers_symbolizer const& sym, mapnik::feature_ptr const& feature, proj_transform const& prj_trans) { - typedef coord_transform2 path_type; - arrow arrow_; cairo_context context(context_); - color const& fill_ = sym.get_fill(); - context.set_color(fill_.red(), fill_.green(), fill_.blue(), fill_.alpha()); + double scale_factor_(1); - for (unsigned i = 0; i < feature->num_geometries(); ++i) + typedef agg::conv_clip_polyline clipped_geometry_type; + typedef coord_transform2 path_type; + + agg::trans_affine tr; + boost::array const& m = sym.get_transform(); + tr.load_from(&m[0]); + // TODO - use this? + //tr = agg::trans_affine_scaling(scale_factor_) * tr; + std::string filename = path_processor_type::evaluate(*sym.get_filename(), *feature); + marker_placement_e placement_method = sym.get_marker_placement(); + marker_type_e marker_type = sym.get_marker_type(); + metawriter_with_properties writer = sym.get_metawriter(); + + if (!filename.empty()) { - geometry_type & geom = feature->get_geometry(i); - - if (geom.num_points() > 1) + boost::optional mark = mapnik::marker_cache::instance()->find(filename, true); + if (mark && *mark) { + if (!(*mark)->is_vector()) { + std::clog << "### Warning only svg markers are supported in the markers_symbolizer\n"; + return; + } + boost::optional marker = (*mark)->get_vector_data(); + box2d const& bbox = (*marker)->bounding_box(); + double x1 = bbox.minx(); + double y1 = bbox.miny(); + double x2 = bbox.maxx(); + double y2 = bbox.maxy(); + double w = (*mark)->width(); + double h = (*mark)->height(); - path_type path(t_, geom, prj_trans); + agg::trans_affine recenter = agg::trans_affine_translation(-0.5*(x1+x2),-0.5*(y1+y2)); + tr.transform(&x1,&y1); + tr.transform(&x2,&y2); + box2d extent(x1,y1,x2,y2); + using namespace mapnik::svg; - markers_placement placement(path, arrow_.extent(), detector_, sym.get_spacing(), sym.get_max_error(), sym.get_allow_overlap()); + for (unsigned i=0; inum_geometries(); ++i) + { + geometry_type & geom = feature->get_geometry(i); + // TODO - merge this code with point_symbolizer rendering + if (placement_method == MARKER_POINT_PLACEMENT || geom.num_points() <= 1) + { + double x; + double y; + double z=0; + geom.label_interior_position(&x, &y); + prj_trans.backward(x,y,z); + t_.forward(&x,&y); + extent.re_center(x,y); - double x, y, angle; - while (placement.get_point(&x, &y, &angle)) { - Cairo::Matrix matrix = Cairo::rotation_matrix(angle) * Cairo::translation_matrix(x,y) ; - context.set_matrix(matrix); - context.add_path(arrow_); + if (sym.get_allow_overlap() || + detector_.has_placement(extent)) + { + render_marker(pixel_position(x - 0.5 * w, y - 0.5 * h) ,**mark, tr, sym.get_opacity()); + + // TODO - impl this for markers? + //if (!sym.get_ignore_placement()) + // detector_.insert(label_ext); + metawriter_with_properties writer = sym.get_metawriter(); + if (writer.first) writer.first->add_box(extent, *feature, t_, writer.second); + } + } + else + { + clipped_geometry_type clipped(geom); + clipped.clip_box(query_extent_.minx(),query_extent_.miny(),query_extent_.maxx(),query_extent_.maxy()); + path_type path(t_,clipped,prj_trans); + markers_placement placement(path, extent, detector_, + sym.get_spacing() * scale_factor_, + sym.get_max_error(), + sym.get_allow_overlap()); + double x, y, angle; + + while (placement.get_point(&x, &y, &angle)) + { + agg::trans_affine matrix = recenter * tr * agg::trans_affine_rotation(angle) * agg::trans_affine_translation(x, y); + render_marker(pixel_position(x - 0.5 * w, y - 0.5 * h), **mark, matrix, sym.get_opacity(),false); + + if (writer.first) + //writer.first->add_box(label_ext, feature, t_, writer.second); + std::clog << "### Warning metawriter not yet supported for LINE placement\n"; + } + } + context.fill(); + } + } + } + else + { + color const& fill_ = sym.get_fill(); + unsigned r = fill_.red(); + unsigned g = fill_.green(); + unsigned b = fill_.blue(); + unsigned a = fill_.alpha(); + stroke const& stroke_ = sym.get_stroke(); + color const& col = stroke_.get_color(); + double strk_width = stroke_.get_width(); + unsigned s_r=col.red(); + unsigned s_g=col.green(); + unsigned s_b=col.blue(); + unsigned s_a=col.alpha(); + double w = sym.get_width(); + double h = sym.get_height(); + double rx = w/2.0; + double ry = h/2.0; + + arrow arrow_; + box2d extent; + + double dx = w + (2*strk_width); + double dy = h + (2*strk_width); + + if (marker_type == ARROW) + { + extent = arrow_.extent(); + double x1 = extent.minx(); + double y1 = extent.miny(); + double x2 = extent.maxx(); + double y2 = extent.maxy(); + tr.transform(&x1,&y1); + tr.transform(&x2,&y2); + extent.init(x1,y1,x2,y2); + } + else + { + double x1 = -1 *(dx); + double y1 = -1 *(dy); + double x2 = dx; + double y2 = dy; + tr.transform(&x1,&y1); + tr.transform(&x2,&y2); + extent.init(x1,y1,x2,y2); + } + + double x; + double y; + double z=0; + + agg::path_storage marker; + + for (unsigned i=0; inum_geometries(); ++i) + { + geometry_type & geom = feature->get_geometry(i); + if (placement_method == MARKER_POINT_PLACEMENT || geom.num_points() <= 1) + { + geom.label_position(&x,&y); + prj_trans.backward(x,y,z); + t_.forward(&x,&y); + int px = int(floor(x - 0.5 * dx)); + int py = int(floor(y - 0.5 * dy)); + box2d label_ext (px, py, px + dx +1, py + dy +1); + if (sym.get_allow_overlap() || + detector_.has_placement(label_ext)) + { + agg::ellipse c(x, y, rx, ry); + marker.concat_path(c); + context.set_color(fill_,sym.get_opacity()); + context.add_agg_path(marker); + context.fill(); + + if (strk_width) + { + //context.restore(); + context.set_color(col,stroke_.get_opacity()); + context.set_line_width(stroke_.get_width()); + if (stroke_.has_dash()) + { + context.set_dash(stroke_.get_dash_array()); + } + context.add_agg_path(marker); + context.stroke(); + } + if (!sym.get_ignore_placement()) + detector_.insert(label_ext); + if (writer.first) writer.first->add_box(label_ext, *feature, t_, writer.second); + } + } + else + { + if (marker_type == ARROW) + marker.concat_path(arrow_); + + clipped_geometry_type clipped(geom); + clipped.clip_box(query_extent_.minx(),query_extent_.miny(),query_extent_.maxx(),query_extent_.maxy()); + path_type path(t_,clipped,prj_trans); + markers_placement placement(path, extent, detector_, + sym.get_spacing() * scale_factor_, + sym.get_max_error(), + sym.get_allow_overlap()); + double x_t, y_t, angle; + + while (placement.get_point(&x_t, &y_t, &angle)) + { + agg::trans_affine matrix; + + if (marker_type == ELLIPSE) + { + agg::ellipse c(x_t, y_t, rx, ry); + marker.concat_path(c); + agg::trans_affine matrix; + matrix *= agg::trans_affine_translation(-x_t,-y_t); + matrix *= agg::trans_affine_rotation(angle); + matrix *= agg::trans_affine_translation(x_t,y_t); + marker.transform(matrix); + } + else + { + matrix = tr * agg::trans_affine_rotation(angle) * agg::trans_affine_translation(x_t, y_t); + } + + // TODO + if (writer.first) + //writer.first->add_box(label_ext, feature, t_, writer.second); + std::clog << "### Warning metawriter not yet supported for LINE placement\n"; + + agg::conv_transform trans(marker, matrix); + context.set_color(fill_,sym.get_opacity()); + context.add_agg_path(trans); + context.fill(); + + if (strk_width) + { + context.set_color(col,stroke_.get_opacity()); + context.set_line_width(stroke_.get_width()); + if (stroke_.has_dash()) + { + context.set_dash(stroke_.get_dash_array()); + } + context.add_agg_path(trans); + context.stroke(); + } + } } } - context.fill(); } } From d516cf9f46d2d6b905252d3565e1013fcea95320 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Wed, 28 Mar 2012 21:59:40 +0200 Subject: [PATCH 174/238] - cosmetics --- include/mapnik/datasource.hpp | 45 +++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/include/mapnik/datasource.hpp b/include/mapnik/datasource.hpp index 5cfd1bde5..6ae828a94 100644 --- a/include/mapnik/datasource.hpp +++ b/include/mapnik/datasource.hpp @@ -45,25 +45,30 @@ typedef MAPNIK_DECL boost::shared_ptr feature_ptr; struct MAPNIK_DECL Featureset : private boost::noncopyable { - virtual feature_ptr next()=0; - virtual ~Featureset() {}; + virtual feature_ptr next() = 0; + virtual ~Featureset() {} }; typedef MAPNIK_DECL boost::shared_ptr featureset_ptr; class MAPNIK_DECL datasource_exception : public std::exception { -private: - std::string message_; public: - datasource_exception(const std::string& message=std::string("no reason")) - :message_(message) {} + datasource_exception(const std::string& message = std::string("no reason")) + : message_(message) + { + } + + ~datasource_exception() throw() + { + } - ~datasource_exception() throw() {} virtual const char* what() const throw() { return message_.c_str(); } +private: + std::string message_; }; class MAPNIK_DECL datasource : private boost::noncopyable @@ -82,9 +87,10 @@ public: }; datasource (parameters const& params) - : params_(params), + : params_(params), is_bound_(false) - {} + { + } /*! * @brief Get the configuration parameters of the data source. @@ -102,19 +108,19 @@ public: * @brief Get the type of the datasource * @return The type of the datasource (Vector or Raster) */ - virtual datasource_t type() const=0; + virtual datasource_t type() const = 0; /*! * @brief Connect to the datasource */ - virtual void bind() const {}; + virtual void bind() const {} - virtual featureset_ptr features(const query& q) const=0; - virtual featureset_ptr features_at_point(coord2d const& pt) const=0; - virtual box2d envelope() const=0; - virtual boost::optional get_geometry_type() const=0; - virtual layer_descriptor get_descriptor() const=0; - virtual ~datasource() {}; + virtual featureset_ptr features(const query& q) const = 0; + virtual featureset_ptr features_at_point(coord2d const& pt) const = 0; + virtual box2d envelope() const = 0; + virtual boost::optional get_geometry_type() const = 0; + virtual layer_descriptor get_descriptor() const = 0; + virtual ~datasource() {} protected: parameters params_; mutable bool is_bound_; @@ -124,7 +130,6 @@ typedef std::string datasource_name(); typedef datasource* create_ds(const parameters& params, bool bind); typedef void destroy_ds(datasource *ds); - class datasource_deleter { public: @@ -136,7 +141,6 @@ public: typedef boost::shared_ptr datasource_ptr; - #define DATASOURCE_PLUGIN(classname) \ extern "C" MAPNIK_EXP std::string datasource_name() \ { \ @@ -149,8 +153,7 @@ typedef boost::shared_ptr datasource_ptr; extern "C" MAPNIK_EXP void destroy(datasource *ds) \ { \ delete ds; \ - } \ - // + } } From 1ee425519563f2a3e041fd6d1b58f0cec842c056 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Wed, 28 Mar 2012 22:01:26 +0200 Subject: [PATCH 175/238] - updated linux project files --- workspace/mapnik.pro | 2 ++ workspace/plugins.pri | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/workspace/mapnik.pro b/workspace/mapnik.pro index 5f9f9c615..6afa5ce50 100644 --- a/workspace/mapnik.pro +++ b/workspace/mapnik.pro @@ -35,6 +35,7 @@ HEADERS += \ ../include/mapnik/svg/svg_renderer.hpp \ ../include/mapnik/svg/svg_storage.hpp \ ../include/mapnik/svg/svg_transform_grammar.hpp \ + ../include/mapnik/util/conversions.hpp \ ../include/mapnik/wkt/wkt_factory.hpp \ ../include/mapnik/wkt/wkt_grammar.hpp \ ../include/mapnik/agg_pattern_source.hpp \ @@ -45,6 +46,7 @@ HEADERS += \ ../include/mapnik/attribute_descriptor.hpp \ ../include/mapnik/attribute.hpp \ ../include/mapnik/box2d.hpp \ + ../include/mapnik/boolean.hpp \ ../include/mapnik/cairo_renderer.hpp \ ../include/mapnik/color_factory.hpp \ ../include/mapnik/color.hpp \ diff --git a/workspace/plugins.pri b/workspace/plugins.pri index 623ab4567..0f52001e8 100644 --- a/workspace/plugins.pri +++ b/workspace/plugins.pri @@ -30,8 +30,8 @@ HEADERS += \ $$PWD/../plugins/input/postgis/connection.hpp \ $$PWD/../plugins/input/postgis/connection_manager.hpp \ $$PWD/../plugins/input/postgis/cursorresultset.hpp \ - $$PWD/../plugins/input/postgis/postgis.hpp \ - $$PWD/../plugins/input/postgis/property_index.hpp \ + $$PWD/../plugins/input/postgis/postgis_datasource.hpp \ + $$PWD/../plugins/input/postgis/postgis_featureset.hpp \ $$PWD/../plugins/input/postgis/resultset.hpp \ $$PWD/../plugins/input/raster/raster_info.hpp \ $$PWD/../plugins/input/raster/raster_featureset.hpp \ @@ -41,6 +41,7 @@ HEADERS += \ $$PWD/../plugins/input/rasterlite/rasterlite_datasource.hpp \ $$PWD/../plugins/input/shape/dbffile.hpp \ $$PWD/../plugins/input/shape/shape.hpp \ + $$PWD/../plugins/input/shape/shape_datasource.hpp \ $$PWD/../plugins/input/shape/shape_featureset.hpp \ $$PWD/../plugins/input/shape/shapefile.hpp \ $$PWD/../plugins/input/shape/shape_index_featureset.hpp \ @@ -83,6 +84,7 @@ SOURCES += \ $$PWD/../plugins/input/rasterlite/rasterlite_datasource.cpp \ $$PWD/../plugins/input/shape/dbffile.cpp \ $$PWD/../plugins/input/shape/shape.cpp \ + $$PWD/../plugins/input/shape/shape_datasource.cpp \ $$PWD/../plugins/input/shape/shape_featureset.cpp \ $$PWD/../plugins/input/shape/shape_index_featureset.cpp \ $$PWD/../plugins/input/shape/shape_io.cpp \ From 7dbe1279bf27ee3c553073e991b4bf9696d8f223 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Wed, 28 Mar 2012 22:01:45 +0200 Subject: [PATCH 176/238] - reorder constructor initialization list to avoid warnings in raster datasource --- plugins/input/raster/raster_featureset.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/input/raster/raster_featureset.cpp b/plugins/input/raster/raster_featureset.cpp index 2d08d117b..2ed50cd9d 100644 --- a/plugins/input/raster/raster_featureset.cpp +++ b/plugins/input/raster/raster_featureset.cpp @@ -45,11 +45,11 @@ raster_featureset::raster_featureset(LookupPolicy const& policy, query const& q) : policy_(policy), feature_id_(1), + ctx_(boost::make_shared()), extent_(extent), bbox_(q.get_bbox()), curIter_(policy_.begin()), - endIter_(policy_.end()), - ctx_(boost::make_shared()) + endIter_(policy_.end()) { } From 14bc34489811c0dd515bb861fe91ff17b4b23940 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Wed, 28 Mar 2012 22:10:48 +0200 Subject: [PATCH 177/238] - cosmetics in postgis datasource --- plugins/input/postgis/postgis_datasource.cpp | 362 +++++++++++-------- plugins/input/postgis/postgis_datasource.hpp | 41 ++- plugins/input/postgis/postgis_featureset.cpp | 149 +++++--- plugins/input/postgis/postgis_featureset.hpp | 15 +- 4 files changed, 339 insertions(+), 228 deletions(-) diff --git a/plugins/input/postgis/postgis_datasource.cpp b/plugins/input/postgis/postgis_datasource.cpp index 316ee02c1..e992ea051 100644 --- a/plugins/input/postgis/postgis_datasource.cpp +++ b/plugins/input/postgis/postgis_datasource.cpp @@ -43,12 +43,11 @@ #include #include -#define FMAX std::numeric_limits::max() - DATASOURCE_PLUGIN(postgis_datasource) -const std::string postgis_datasource::GEOMETRY_COLUMNS="geometry_columns"; -const std::string postgis_datasource::SPATIAL_REF_SYS="spatial_ref_system"; +const double postgis_datasource::FMAX = std::numeric_limits::max(); +const std::string postgis_datasource::GEOMETRY_COLUMNS = "geometry_columns"; +const std::string postgis_datasource::SPATIAL_REF_SYS = "spatial_ref_system"; using boost::shared_ptr; using mapnik::PoolGuard; @@ -56,36 +55,42 @@ using mapnik::attribute_descriptor; postgis_datasource::postgis_datasource(parameters const& params, bool bind) : datasource(params), - table_(*params_.get("table","")), + table_(*params_.get("table", "")), schema_(""), - geometry_table_(*params_.get("geometry_table","")), - geometry_field_(*params_.get("geometry_field","")), - key_field_(*params_.get("key_field","")), - cursor_fetch_size_(*params_.get("cursor_size",0)), - row_limit_(*params_.get("row_limit",0)), + geometry_table_(*params_.get("geometry_table", "")), + geometry_field_(*params_.get("geometry_field", "")), + key_field_(*params_.get("key_field", "")), + cursor_fetch_size_(*params_.get("cursor_size", 0)), + row_limit_(*params_.get("row_limit", 0)), type_(datasource::Vector), - srid_(*params_.get("srid",0)), + srid_(*params_.get("srid", 0)), extent_initialized_(false), - desc_(*params_.get("type"),"utf-8"), + desc_(*params_.get("type"), "utf-8"), creator_(params.get("host"), params.get("port"), params.get("dbname"), params.get("user"), params.get("password"), - params.get("connect_timeout","4")), + params.get("connect_timeout", "4")), bbox_token_("!bbox!"), scale_denom_token_("!scale_denominator!"), - persist_connection_(*params_.get("persist_connection",true)), - extent_from_subquery_(*params_.get("extent_from_subquery",false)), + persist_connection_(*params_.get("persist_connection", true)), + extent_from_subquery_(*params_.get("extent_from_subquery", false)), // params below are for testing purposes only (will likely be removed at any time) - intersect_min_scale_(*params_.get("intersect_min_scale",0)), - intersect_max_scale_(*params_.get("intersect_max_scale",0)) + intersect_min_scale_(*params_.get("intersect_min_scale", 0)), + intersect_max_scale_(*params_.get("intersect_max_scale", 0)) //show_queries_(*params_.get("show_queries",false)) { - if (table_.empty()) throw mapnik::datasource_exception("Postgis Plugin: missing parameter"); + if (table_.empty()) + { + throw mapnik::datasource_exception("Postgis Plugin: missing
parameter"); + } - boost::optional ext = params_.get("extent"); - if (ext) extent_initialized_ = extent_.from_string(*ext); + boost::optional ext = params_.get("extent"); + if (ext) + { + extent_initialized_ = extent_.from_string(*ext); + } if (bind) { @@ -95,37 +100,38 @@ postgis_datasource::postgis_datasource(parameters const& params, bool bind) void postgis_datasource::bind() const { - if (is_bound_) return; + if (is_bound_) + { + return; + } - boost::optional initial_size = params_.get("initial_size",1); - boost::optional max_size = params_.get("max_size",10); + boost::optional initial_size = params_.get("initial_size", 1); + boost::optional max_size = params_.get("max_size", 10); - ConnectionManager *mgr=ConnectionManager::instance(); + ConnectionManager* mgr = ConnectionManager::instance(); mgr->registerPool(creator_, *initial_size, *max_size); - std::string g_type; - - shared_ptr > pool=mgr->getPool(creator_.id()); + shared_ptr< Pool > pool = mgr->getPool(creator_.id()); if (pool) { shared_ptr conn = pool->borrowObject(); if (conn && conn->isOK()) { - PoolGuard, - shared_ptr > > guard(conn,pool); + shared_ptr< Pool > > guard(conn, pool); desc_.set_encoding(conn->client_encoding()); - if(geometry_table_.empty()) + if (geometry_table_.empty()) { geometry_table_ = mapnik::sql_utils::table_from_sql(table_); } + std::string::size_type idx = geometry_table_.find_last_of('.'); - if (idx!=std::string::npos) + if (idx != std::string::npos) { - schema_ = geometry_table_.substr(0,idx); - geometry_table_ = geometry_table_.substr(idx+1); + schema_ = geometry_table_.substr(0, idx); + geometry_table_ = geometry_table_.substr(idx + 1); } else { @@ -139,7 +145,7 @@ void postgis_datasource::bind() const // the table parameter references a table, view, or subselect not // registered in the geometry columns. geometryColumn_ = geometry_field_; - if (!geometryColumn_.length() > 0 || srid_ == 0) + if (geometryColumn_.empty() || srid_ == 0) { std::ostringstream s; s << "SELECT f_geometry_column, srid FROM " @@ -147,15 +153,19 @@ void postgis_datasource::bind() const << mapnik::sql_utils::unquote_double(geometry_table_) << "'"; - if (schema_.length() > 0) + if (! schema_.empty()) + { s << " AND f_table_schema='" << mapnik::sql_utils::unquote_double(schema_) << "'"; + } - if (geometry_field_.length() > 0) + if (! geometry_field_.empty()) + { s << " AND f_geometry_column='" << mapnik::sql_utils::unquote_double(geometry_field_) << "'"; + } /* if (show_queries_) @@ -171,12 +181,14 @@ void postgis_datasource::bind() const if (srid_ == 0) { - const char * srid_c = rs->getValue("srid"); + const char* srid_c = rs->getValue("srid"); if (srid_c != NULL) { - int result; - if (mapnik::util::string2int(srid_c,result)) + int result = 0; + if (mapnik::util::string2int(srid_c, result)) + { srid_ = result; + } } } } @@ -185,11 +197,12 @@ void postgis_datasource::bind() const // If we still do not know the srid then we can try to fetch // it from the 'table_' parameter, which should work even if it is // a subselect as long as we know the geometry_field to query - if (geometryColumn_.length() && srid_ <= 0) + if (! geometryColumn_.empty() && srid_ <= 0) { s.str(""); - s << "SELECT ST_SRID(\"" << geometryColumn_ << "\") AS srid FROM "; - s << populate_tokens(table_) << " WHERE \"" << geometryColumn_ << "\" IS NOT NULL LIMIT 1;"; + + s << "SELECT ST_SRID(\"" << geometryColumn_ << "\") AS srid FROM " + << populate_tokens(table_) << " WHERE \"" << geometryColumn_ << "\" IS NOT NULL LIMIT 1;"; /* if (show_queries_) @@ -201,12 +214,14 @@ void postgis_datasource::bind() const shared_ptr rs = conn->executeQuery(s.str()); if (rs->next()) { - const char * srid_c = rs->getValue("srid"); + const char* srid_c = rs->getValue("srid"); if (srid_c != NULL) { - int result; - if (mapnik::util::string2int(srid_c,result)) + int result = 0; + if (mapnik::util::string2int(srid_c, result)) + { srid_ = result; + } } } rs->close(); @@ -216,7 +231,10 @@ void postgis_datasource::bind() const if (srid_ == 0) { srid_ = -1; + +#ifdef MAPNIK_DEBUG std::clog << "Postgis Plugin: SRID warning, using srid=-1 for '" << table_ << "'" << std::endl; +#endif } // At this point the geometry_field may still not be known @@ -242,25 +260,27 @@ void postgis_datasource::bind() const shared_ptr rs = conn->executeQuery(s.str()); int count = rs->getNumFields(); bool found_key_field = false; - for (int i=0;igetFieldName(i); + std::string fld_name = rs->getFieldName(i); int type_oid = rs->getTypeOID(i); // validate type of key_field - if (!found_key_field && !key_field_.empty() && fld_name == key_field_) + if (! found_key_field && ! key_field_.empty() && fld_name == key_field_) { found_key_field = true; if (type_oid == 20 || type_oid == 21 || type_oid == 23) { - desc_.add_descriptor(attribute_descriptor(fld_name,mapnik::Integer)); + desc_.add_descriptor(attribute_descriptor(fld_name, mapnik::Integer)); } else { std::ostringstream error_s; error_s << "invalid type '"; + std::ostringstream type_s; type_s << "SELECT oid, typname FROM pg_type WHERE oid = " << type_oid; + shared_ptr rs_oid = conn->executeQuery(type_s.str()); if (rs_oid->next()) { @@ -271,11 +291,13 @@ void postgis_datasource::bind() const { error_s << "oid:" << type_oid << "'"; } + rs_oid->close(); error_s << " for key_field '" << fld_name << "' - " << "must be an integer primary key"; + rs->close(); - throw mapnik::datasource_exception( error_s.str() ); + throw mapnik::datasource_exception(error_s.str()); } } else @@ -283,21 +305,21 @@ void postgis_datasource::bind() const switch (type_oid) { case 16: // bool - desc_.add_descriptor(attribute_descriptor(fld_name,mapnik::Boolean)); + desc_.add_descriptor(attribute_descriptor(fld_name, mapnik::Boolean)); break; case 20: // int8 case 21: // int2 case 23: // int4 - desc_.add_descriptor(attribute_descriptor(fld_name,mapnik::Integer)); + desc_.add_descriptor(attribute_descriptor(fld_name, mapnik::Integer)); break; case 700: // float4 case 701: // float8 case 1700: // numeric ?? - desc_.add_descriptor(attribute_descriptor(fld_name,mapnik::Double)); + desc_.add_descriptor(attribute_descriptor(fld_name, mapnik::Double)); case 1042: // bpchar case 1043: // varchar case 25: // text - desc_.add_descriptor(attribute_descriptor(fld_name,mapnik::String)); + desc_.add_descriptor(attribute_descriptor(fld_name, mapnik::String)); break; default: // should not get here #ifdef MAPNIK_DEBUG @@ -331,7 +353,6 @@ void postgis_datasource::bind() const rs->close(); is_bound_ = true; - } } } @@ -348,22 +369,33 @@ mapnik::datasource::datasource_t postgis_datasource::type() const layer_descriptor postgis_datasource::get_descriptor() const { - if (!is_bound_) bind(); + if (! is_bound_) + { + bind(); + } + return desc_; } - std::string postgis_datasource::sql_bbox(box2d const& env) const { std::ostringstream b; + if (srid_ > 0) + { b << "ST_SetSRID("; + } + b << "'BOX3D("; b << std::setprecision(16); b << env.minx() << " " << env.miny() << ","; b << env.maxx() << " " << env.maxy() << ")'::box3d"; + if (srid_ > 0) + { b << ", " << srid_ << ")"; + } + return b.str(); } @@ -371,18 +403,20 @@ std::string postgis_datasource::populate_tokens(const std::string& sql) const { std::string populated_sql = sql; - if ( boost::algorithm::icontains(sql,bbox_token_) ) + if (boost::algorithm::icontains(sql, bbox_token_)) { - box2d max_env(-1 * FMAX,-1 * FMAX,FMAX,FMAX); - std::string max_box = sql_bbox(max_env); - boost::algorithm::replace_all(populated_sql,bbox_token_,max_box); + box2d max_env(-1.0 * FMAX, -1.0 * FMAX, FMAX, FMAX); + const std::string max_box = sql_bbox(max_env); + boost::algorithm::replace_all(populated_sql, bbox_token_, max_box); } - if ( boost::algorithm::icontains(sql,scale_denom_token_) ) + + if (boost::algorithm::icontains(sql, scale_denom_token_)) { std::ostringstream ss; ss << FMAX; - boost::algorithm::replace_all(populated_sql,scale_denom_token_,ss.str()); + boost::algorithm::replace_all(populated_sql, scale_denom_token_, ss.str()); } + return populated_sql; } @@ -391,26 +425,27 @@ std::string postgis_datasource::populate_tokens(const std::string& sql, double s std::string populated_sql = sql; std::string box = sql_bbox(env); - if ( boost::algorithm::icontains(populated_sql,scale_denom_token_) ) + if (boost::algorithm::icontains(populated_sql, scale_denom_token_)) { std::ostringstream ss; ss << scale_denom; - boost::algorithm::replace_all(populated_sql,scale_denom_token_,ss.str()); + boost::algorithm::replace_all(populated_sql, scale_denom_token_, ss.str()); } - if ( boost::algorithm::icontains(populated_sql,bbox_token_) ) + if (boost::algorithm::icontains(populated_sql, bbox_token_)) { - boost::algorithm::replace_all(populated_sql,bbox_token_,box); + boost::algorithm::replace_all(populated_sql, bbox_token_, box); return populated_sql; } else { std::ostringstream s; - if (intersect_min_scale_ > 0 && (scale_denom <= intersect_min_scale_ )) + + if (intersect_min_scale_ > 0 && (scale_denom <= intersect_min_scale_)) { s << " WHERE ST_Intersects(\"" << geometryColumn_ << "\"," << box << ")"; } - else if (intersect_max_scale_ > 0 && (scale_denom >= intersect_max_scale_ )) + else if (intersect_max_scale_ > 0 && (scale_denom >= intersect_max_scale_)) { // do no bbox restriction } @@ -418,6 +453,7 @@ std::string postgis_datasource::populate_tokens(const std::string& sql, double s { s << " WHERE \"" << geometryColumn_ << "\" && " << box; } + return populated_sql + s.str(); } } @@ -440,9 +476,11 @@ boost::shared_ptr postgis_datasource::get_resultset(boost::shared_pt } */ - if (!conn->execute(csql.str())) + if (! conn->execute(csql.str())) + { // TODO - better error throw mapnik::datasource_exception("Postgis Plugin: error creating cursor for data select." ); + } return boost::make_shared(conn, cursor_name, cursor_fetch_size_); @@ -458,41 +496,48 @@ boost::shared_ptr postgis_datasource::get_resultset(boost::shared_pt } */ - return conn->executeQuery(sql,1); + return conn->executeQuery(sql, 1); } } featureset_ptr postgis_datasource::features(const query& q) const { - if (!is_bound_) bind(); + if (! is_bound_) + { + bind(); + } box2d const& box = q.get_bbox(); double scale_denom = q.scale_denominator(); - ConnectionManager *mgr=ConnectionManager::instance(); - shared_ptr > pool=mgr->getPool(creator_.id()); + + ConnectionManager* mgr = ConnectionManager::instance(); + shared_ptr< Pool > pool = mgr->getPool(creator_.id()); if (pool) { shared_ptr conn = pool->borrowObject(); if (conn && conn->isOK()) { - PoolGuard,shared_ptr > > guard(conn,pool); + PoolGuard, shared_ptr< Pool > > guard(conn ,pool); - if (!geometryColumn_.length() > 0) + if (geometryColumn_.empty()) { std::ostringstream s_error; s_error << "PostGIS: geometry name lookup failed for table '"; - if (schema_.length() > 0) + + if (! schema_.empty()) { s_error << schema_ << "."; } s_error << geometry_table_ << "'. Please manually provide the 'geometry_field' parameter or add an entry " << "in the geometry_columns for '"; - if (schema_.length() > 0) + + if (! schema_.empty()) { s_error << schema_ << "."; } s_error << geometry_table_ << "'."; + throw mapnik::datasource_exception(s_error.str()); } @@ -501,27 +546,28 @@ featureset_ptr postgis_datasource::features(const query& q) const mapnik::context_ptr ctx = boost::make_shared(); - if (!key_field_.empty()) + if (! key_field_.empty()) { - mapnik::sql_utils::quote_attr(s,key_field_); + mapnik::sql_utils::quote_attr(s, key_field_); ctx->push(key_field_); } - std::set const& props=q.property_names(); - std::set::const_iterator pos=props.begin(); - std::set::const_iterator end=props.end(); + std::set const& props = q.property_names(); + std::set::const_iterator pos = props.begin(); + std::set::const_iterator end = props.end(); - for ( ;pos != end;++pos) + for (; pos != end; ++pos) { - mapnik::sql_utils::quote_attr(s,*pos); + mapnik::sql_utils::quote_attr(s, *pos); ctx->push(*pos); } - std::string table_with_bbox = populate_tokens(table_,scale_denom,box); + std::string table_with_bbox = populate_tokens(table_, scale_denom, box); - s << " from " << table_with_bbox; + s << " FROM " << table_with_bbox; - if (row_limit_ > 0) { + if (row_limit_ > 0) + { s << " LIMIT " << row_limit_; } @@ -533,38 +579,45 @@ featureset_ptr postgis_datasource::features(const query& q) const throw mapnik::datasource_exception("Postgis Plugin: bad connection"); } } + return featureset_ptr(); } featureset_ptr postgis_datasource::features_at_point(coord2d const& pt) const { - if (!is_bound_) bind(); + if (! is_bound_) + { + bind(); + } - ConnectionManager *mgr=ConnectionManager::instance(); - shared_ptr > pool=mgr->getPool(creator_.id()); + ConnectionManager* mgr = ConnectionManager::instance(); + shared_ptr< Pool > pool = mgr->getPool(creator_.id()); if (pool) { shared_ptr conn = pool->borrowObject(); if (conn && conn->isOK()) { - PoolGuard,shared_ptr > > guard(conn,pool); + PoolGuard, shared_ptr< Pool > > guard(conn, pool); - if (!geometryColumn_.length() > 0) + if (geometryColumn_.empty()) { std::ostringstream s_error; s_error << "PostGIS: geometry name lookup failed for table '"; - if (schema_.length() > 0) + + if (! schema_.empty()) { s_error << schema_ << "."; } s_error << geometry_table_ << "'. Please manually provide the 'geometry_field' parameter or add an entry " << "in the geometry_columns for '"; - if (schema_.length() > 0) + + if (! schema_.empty()) { s_error << schema_ << "."; } s_error << geometry_table_ << "'."; + throw mapnik::datasource_exception(s_error.str()); } @@ -573,27 +626,28 @@ featureset_ptr postgis_datasource::features_at_point(coord2d const& pt) const mapnik::context_ptr ctx = boost::make_shared(); - if (!key_field_.empty()) + if (! key_field_.empty()) { - mapnik::sql_utils::quote_attr(s,key_field_); + mapnik::sql_utils::quote_attr(s, key_field_); ctx->push(key_field_); } std::vector::const_iterator itr = desc_.get_descriptors().begin(); std::vector::const_iterator end = desc_.get_descriptors().end(); - for ( ; itr != end; ++itr) + for (; itr != end; ++itr) { - mapnik::sql_utils::quote_attr(s,itr->get_name()); + mapnik::sql_utils::quote_attr(s, itr->get_name()); ctx->push(itr->get_name()); } - box2d box(pt.x,pt.y,pt.x,pt.y); - std::string table_with_bbox = populate_tokens(table_,FMAX,box); + box2d box(pt.x, pt.y, pt.x, pt.y); + std::string table_with_bbox = populate_tokens(table_, FMAX, box); - s << " from " << table_with_bbox; + s << " FROM " << table_with_bbox; - if (row_limit_ > 0) { + if (row_limit_ > 0) + { s << " LIMIT " << row_limit_; } @@ -601,30 +655,42 @@ featureset_ptr postgis_datasource::features_at_point(coord2d const& pt) const return boost::make_shared(rs, ctx, desc_.get_encoding(), !key_field_.empty()); } } + return featureset_ptr(); } box2d postgis_datasource::envelope() const { - if (extent_initialized_) return extent_; - if (!is_bound_) bind(); + if (extent_initialized_) + { + return extent_; + } - ConnectionManager *mgr=ConnectionManager::instance(); - shared_ptr > pool=mgr->getPool(creator_.id()); + if (! is_bound_) + { + bind(); + } + + ConnectionManager* mgr = ConnectionManager::instance(); + shared_ptr< Pool > pool = mgr->getPool(creator_.id()); if (pool) { shared_ptr conn = pool->borrowObject(); if (conn && conn->isOK()) { - PoolGuard,shared_ptr > > guard(conn,pool); - std::ostringstream s; - boost::optional estimate_extent = params_.get("estimate_extent",false); + PoolGuard, shared_ptr< Pool > > guard(conn, pool); - if (!geometryColumn_.length() > 0) + std::ostringstream s; + + boost::optional estimate_extent = + params_.get("estimate_extent", false); + + if (geometryColumn_.empty()) { std::ostringstream s_error; s_error << "PostGIS: unable to query the layer extent of table '"; - if (schema_.length() > 0) + + if (! schema_.empty()) { s_error << schema_ << "."; } @@ -632,6 +698,7 @@ box2d postgis_datasource::envelope() const << "\nPlease provide either an 'extent' parameter to skip this query, " << "a 'geometry_field' and/or 'geometry_table' parameter, or add a " << "record to the 'geometry_columns' for your table."; + throw mapnik::datasource_exception("Postgis Plugin: " + s_error.str()); } @@ -640,7 +707,7 @@ box2d postgis_datasource::envelope() const s << "SELECT ST_XMin(ext),ST_YMin(ext),ST_XMax(ext),ST_YMax(ext)" << " FROM (SELECT ST_Estimated_Extent('"; - if (schema_.length() > 0) + if (! schema_.empty()) { s << mapnik::sql_utils::unquote_double(schema_) << "','"; } @@ -652,6 +719,7 @@ box2d postgis_datasource::envelope() const { s << "SELECT ST_XMin(ext),ST_YMin(ext),ST_XMax(ext),ST_YMax(ext)" << " FROM (SELECT ST_Extent(" < postgis_datasource::envelope() const } else { - if (schema_.length() > 0) + if (! schema_.empty()) { s << schema_ << "."; } @@ -679,18 +747,15 @@ box2d postgis_datasource::envelope() const */ shared_ptr rs = conn->executeQuery(s.str()); - if (rs->next() && !rs->isNull(0)) + if (rs->next() && ! rs->isNull(0)) { - double lox; - double loy; - double hix; - double hiy; - if (mapnik::util::string2double(rs->getValue(0),lox) && - mapnik::util::string2double(rs->getValue(1),loy) && - mapnik::util::string2double(rs->getValue(2),hix) && - mapnik::util::string2double(rs->getValue(3),hiy)) + double lox, loy, hix, hiy; + if (mapnik::util::string2double(rs->getValue(0), lox) && + mapnik::util::string2double(rs->getValue(1), loy) && + mapnik::util::string2double(rs->getValue(2), hix) && + mapnik::util::string2double(rs->getValue(3), hiy)) { - extent_.init(lox,loy,hix,hiy); + extent_.init(lox, loy, hix, hiy); extent_initialized_ = true; } else @@ -701,22 +766,27 @@ box2d postgis_datasource::envelope() const rs->close(); } } + return extent_; } boost::optional postgis_datasource::get_geometry_type() const { - if (! is_bound_) bind(); + if (! is_bound_) + { + bind(); + } + boost::optional result; - ConnectionManager *mgr=ConnectionManager::instance(); - shared_ptr > pool=mgr->getPool(creator_.id()); + ConnectionManager* mgr = ConnectionManager::instance(); + shared_ptr< Pool > pool = mgr->getPool(creator_.id()); if (pool) { shared_ptr conn = pool->borrowObject(); if (conn && conn->isOK()) { - PoolGuard,shared_ptr > > guard(conn,pool); + PoolGuard, shared_ptr< Pool > > guard(conn, pool); std::ostringstream s; std::string g_type; @@ -726,31 +796,35 @@ boost::optional postgis_datasource::get_geometry << mapnik::sql_utils::unquote_double(geometry_table_) << "'"; - if (schema_.length() > 0) + if (! schema_.empty()) + { s << " AND f_table_schema='" << mapnik::sql_utils::unquote_double(schema_) << "'"; + } - if (geometry_field_.length() > 0) + if (! geometry_field_.empty()) + { s << " AND f_geometry_column='" << mapnik::sql_utils::unquote_double(geometry_field_) << "'"; + } shared_ptr rs = conn->executeQuery(s.str()); if (rs->next()) { g_type = rs->getValue("type"); - if (boost::algorithm::contains(g_type,"line")) + if (boost::algorithm::contains(g_type, "line")) { result.reset(mapnik::datasource::LineString); return result; } - else if (boost::algorithm::contains(g_type,"point")) + else if (boost::algorithm::contains(g_type, "point")) { result.reset(mapnik::datasource::Point); return result; } - else if (boost::algorithm::contains(g_type,"polygon")) + else if (boost::algorithm::contains(g_type, "polygon")) { result.reset(mapnik::datasource::Polygon); return result; @@ -763,13 +837,16 @@ boost::optional postgis_datasource::get_geometry } // fallback to querying first several features - if (g_type.empty() && !geometryColumn_.empty()) + if (g_type.empty() && ! geometryColumn_.empty()) { s.str(""); + std::string prev_type(""); + s << "SELECT ST_GeometryType(\"" << geometryColumn_ << "\") AS geom" << " FROM " << populate_tokens(table_); + if (row_limit_ > 0 && row_limit_ < 5) { s << " LIMIT " << row_limit_; @@ -778,21 +855,22 @@ boost::optional postgis_datasource::get_geometry { s << " LIMIT 5"; } + shared_ptr rs = conn->executeQuery(s.str()); - while (rs->next() && !rs->isNull(0)) + while (rs->next() && ! rs->isNull(0)) { const char* data = rs->getValue(0); - if (boost::algorithm::icontains(data,"line")) + if (boost::algorithm::icontains(data, "line")) { g_type = "linestring"; result.reset(mapnik::datasource::LineString); } - else if (boost::algorithm::icontains(data,"point")) + else if (boost::algorithm::icontains(data, "point")) { g_type = "point"; result.reset(mapnik::datasource::Point); } - else if (boost::algorithm::icontains(data,"polygon")) + else if (boost::algorithm::icontains(data, "polygon")) { g_type = "polygon"; result.reset(mapnik::datasource::Polygon); @@ -802,7 +880,7 @@ boost::optional postgis_datasource::get_geometry result.reset(mapnik::datasource::Collection); return result; } - if (!prev_type.empty() && g_type != prev_type) + if (! prev_type.empty() && g_type != prev_type) { result.reset(mapnik::datasource::Collection); return result; @@ -818,10 +896,10 @@ boost::optional postgis_datasource::get_geometry postgis_datasource::~postgis_datasource() { - if (is_bound_ && !persist_connection_) + if (is_bound_ && ! persist_connection_) { - ConnectionManager *mgr=ConnectionManager::instance(); - shared_ptr > pool=mgr->getPool(creator_.id()); + ConnectionManager* mgr = ConnectionManager::instance(); + shared_ptr< Pool > pool = mgr->getPool(creator_.id()); if (pool) { shared_ptr conn = pool->borrowObject(); diff --git a/plugins/input/postgis/postgis_datasource.hpp b/plugins/input/postgis/postgis_datasource.hpp index 047796e4f..c353c5216 100644 --- a/plugins/input/postgis/postgis_datasource.hpp +++ b/plugins/input/postgis/postgis_datasource.hpp @@ -49,8 +49,30 @@ using mapnik::coord2d; class postgis_datasource : public datasource { +public: + postgis_datasource(const parameters ¶ms, bool bind=true); + ~postgis_datasource(); + mapnik::datasource::datasource_t type() const; + static std::string name(); + featureset_ptr features(const query& q) const; + featureset_ptr features_at_point(coord2d const& pt) const; + mapnik::box2d envelope() const; + boost::optional get_geometry_type() const; + layer_descriptor get_descriptor() const; + void bind() const; + +private: + std::string sql_bbox(box2d const& env) const; + std::string populate_tokens(const std::string& sql, double scale_denom, box2d const& env) const; + std::string populate_tokens(const std::string& sql) const; + static std::string unquote(const std::string& sql); + boost::shared_ptr get_resultset(boost::shared_ptr const &conn, std::string const& sql) const; + postgis_datasource(const postgis_datasource&); + postgis_datasource& operator=(const postgis_datasource&); + static const std::string GEOMETRY_COLUMNS; static const std::string SPATIAL_REF_SYS; + static const double FMAX; const std::string uri_; const std::string username_; const std::string password_; @@ -76,25 +98,6 @@ class postgis_datasource : public datasource int intersect_min_scale_; int intersect_max_scale_; //bool show_queries_; -public: - static std::string name(); - mapnik::datasource::datasource_t type() const; - featureset_ptr features(const query& q) const; - featureset_ptr features_at_point(coord2d const& pt) const; - mapnik::box2d envelope() const; - boost::optional get_geometry_type() const; - layer_descriptor get_descriptor() const; - postgis_datasource(const parameters ¶ms, bool bind=true); - ~postgis_datasource(); - void bind() const; -private: - std::string sql_bbox(box2d const& env) const; - std::string populate_tokens(const std::string& sql, double scale_denom, box2d const& env) const; - std::string populate_tokens(const std::string& sql) const; - static std::string unquote(const std::string& sql); - boost::shared_ptr get_resultset(boost::shared_ptr const &conn, std::string const& sql) const; - postgis_datasource(const postgis_datasource&); - postgis_datasource& operator=(const postgis_datasource&); }; #endif //POSTGIS_DATASOURCE_HPP diff --git a/plugins/input/postgis/postgis_featureset.cpp b/plugins/input/postgis/postgis_featureset.cpp index 177413fee..ac2f8d9b4 100644 --- a/plugins/input/postgis/postgis_featureset.cpp +++ b/plugins/input/postgis/postgis_featureset.cpp @@ -56,22 +56,25 @@ postgis_featureset::postgis_featureset(boost::shared_ptr const& rs, tr_(new transcoder(encoding)), totalGeomSize_(0), feature_id_(1), - key_field_(key_field) {} + key_field_(key_field) +{ +} feature_ptr postgis_featureset::next() { if (rs_->next()) { // new feature + unsigned pos = 1; feature_ptr feature; - unsigned pos = 1; - - if (key_field_) { + if (key_field_) + { // create feature with user driven id from attribute int oid = rs_->getTypeOID(pos); const char* buf = rs_->getValue(pos); std::string name = rs_->getFieldName(pos); + // validation happens of this type at bind() int val; if (oid == 20) @@ -86,15 +89,18 @@ feature_ptr postgis_featureset::next() { val = int4net(buf); } + feature = feature_factory::create(ctx_, val); // TODO - extend feature class to know // that its id is also an attribute to avoid // this duplication feature->put(name,val); ++pos; - } else { + } + else + { // fallback to auto-incrementing id - feature = feature_factory::create(ctx_,feature_id_); + feature = feature_factory::create(ctx_, feature_id_); ++feature_id_; } @@ -102,74 +108,97 @@ feature_ptr postgis_featureset::next() int size = rs_->getFieldLength(0); const char *data = rs_->getValue(0); geometry_utils::from_wkb(feature->paths(), data, size); - totalGeomSize_+=size; + totalGeomSize_ += size; int num_attrs = ctx_->size() + 1; - for ( ; pos < num_attrs; ++pos) + for (; pos < num_attrs; ++pos) { std::string name = rs_->getFieldName(pos); if (rs_->isNull(pos)) { - feature->put(name,mapnik::value_null()); + feature->put(name, mapnik::value_null()); } else { const char* buf = rs_->getValue(pos); - int oid = rs_->getTypeOID(pos); + const int oid = rs_->getTypeOID(pos); + switch (oid) + { + case 16: //bool + { + feature->put(name, (buf[0] != 0)); + break; + } - if (oid==16) //bool - { - feature->put(name,(buf[0] != 0)); - } - else if (oid==23) //int4 - { - int val = int4net(buf); - feature->put(name,val); - } - else if (oid==21) //int2 - { - int val = int2net(buf); - feature->put(name,val); - } - else if (oid==20) //int8/BigInt - { - int val = int8net(buf); - feature->put(name,val); - } - else if (oid == 700) // float4 - { - float val; - float4net(val,buf); - feature->put(name,val); - } - else if (oid == 701) // float8 - { - double val; - float8net(val,buf); - feature->put(name,val); - } - else if (oid==25 || oid==1043) // text or varchar - { - feature->put(name,tr_->transcode(buf)); - } - else if (oid==1042) - { - // bpchar - feature->put(name,tr_->transcode(trim_copy(std::string(buf)).c_str())); - } - else if (oid == 1700) // numeric - { - std::string str = mapnik::sql_utils::numeric2string(buf); - double val; - if (mapnik::util::string2double(str,val)) - feature->put(name,val); - } - else - { + case 23: //int4 + { + int val = int4net(buf); + feature->put(name, val); + break; + } + + case 21: //int2 + { + int val = int2net(buf); + feature->put(name, val); + break; + } + + case 20: //int8/BigInt + { + int val = int8net(buf); + feature->put(name, val); + break; + } + + case 700: //float4 + { + float val; + float4net(val, buf); + feature->put(name, val); + break; + } + + case 701: //float8 + { + double val; + float8net(val, buf); + feature->put(name, val); + break; + } + + case 25: //text + case 1043: //varchar + { + feature->put(name, tr_->transcode(buf)); + break; + } + + case 1042: //bpchar + { + feature->put(name, tr_->transcode(trim_copy(std::string(buf)).c_str())); + break; + } + + case 1700: //numeric + { + double val; + std::string str = mapnik::sql_utils::numeric2string(buf); + if (mapnik::util::string2double(str, val)) + { + feature->put(name, val); + } + break; + } + + default: + { #ifdef MAPNIK_DEBUG - std::clog << "Postgis Plugin: uknown OID = " << oid << " FIXME " << std::endl; + std::clog << "Postgis Plugin: uknown OID = " << oid << std::endl; #endif + break; + } } } } diff --git a/plugins/input/postgis/postgis_featureset.hpp b/plugins/input/postgis/postgis_featureset.hpp index 7ed317a32..80c011c83 100644 --- a/plugins/input/postgis/postgis_featureset.hpp +++ b/plugins/input/postgis/postgis_featureset.hpp @@ -43,13 +43,6 @@ class IResultSet; class postgis_featureset : public mapnik::Featureset { -private: - boost::shared_ptr rs_; - context_ptr ctx_; - boost::scoped_ptr tr_; - int totalGeomSize_; - int feature_id_; - bool key_field_; public: postgis_featureset(boost::shared_ptr const& rs, context_ptr const& ctx, @@ -57,6 +50,14 @@ public: bool key_field = false); feature_ptr next(); ~postgis_featureset(); + +private: + boost::shared_ptr rs_; + context_ptr ctx_; + boost::scoped_ptr tr_; + int totalGeomSize_; + int feature_id_; + bool key_field_; }; #endif // POSTGIS_FEATURESET_HPP From 4a7966498c76e98684a06a32c572167aef9f90de Mon Sep 17 00:00:00 2001 From: kunitoki Date: Sat, 31 Mar 2012 22:24:32 +0200 Subject: [PATCH 178/238] - postgis resultset cosmetics --- plugins/input/postgis/resultset.hpp | 95 ++++++++++++++++------------- 1 file changed, 54 insertions(+), 41 deletions(-) diff --git a/plugins/input/postgis/resultset.hpp b/plugins/input/postgis/resultset.hpp index 4e43763a3..beab2d729 100644 --- a/plugins/input/postgis/resultset.hpp +++ b/plugins/input/postgis/resultset.hpp @@ -47,57 +47,61 @@ public: class ResultSet : public IResultSet { -private: - PGresult* res_; - int pos_; - int numTuples_; - int *refCount_; - public: ResultSet(PGresult *res) - : res_(res), - pos_(-1), - refCount_(new int(1)) + : res_(res), + pos_(-1), + refCount_(new int(1)) { - numTuples_=PQntuples(res_); + numTuples_ = PQntuples(res_); } ResultSet(const ResultSet& rhs) - : res_(rhs.res_), - pos_(rhs.pos_), - numTuples_(rhs.numTuples_), - refCount_(rhs.refCount_) + : res_(rhs.res_), + pos_(rhs.pos_), + numTuples_(rhs.numTuples_), + refCount_(rhs.refCount_) { (*refCount_)++; } ResultSet& operator=(const ResultSet& rhs) { - if (this==&rhs) return *this; - if (--(refCount_)==0) + if (this == &rhs) + { + return *this; + } + + if (--(refCount_) == 0) { close(); - delete refCount_,refCount_=0; + + delete refCount_; + refCount_ = 0; } - res_=rhs.res_; - pos_=rhs.pos_; - numTuples_=rhs.numTuples_; - refCount_=rhs.refCount_; + + res_ = rhs.res_; + pos_ = rhs.pos_; + numTuples_ = rhs.numTuples_; + refCount_ = rhs.refCount_; (*refCount_)++; return *this; } virtual void close() { - PQclear(res_),res_=0; + PQclear(res_); + res_ = 0; } virtual ~ResultSet() { - if (--(*refCount_)==0) + if (--(*refCount_) == 0) { PQclear(res_); - delete refCount_,refCount_=0; + + delete refCount_; + refCount_ = 0; } } @@ -118,60 +122,69 @@ public: virtual bool next() { - return (++pos_=0) - return PQgetlength(res_,pos_,col); + int col = PQfnumber(res_, name); + if (col >= 0) + { + return PQgetlength(res_, pos_, col); + } return 0; } virtual int getTypeOID(int index) const { - return PQftype(res_,index); + return PQftype(res_, index); } virtual int getTypeOID(const char* name) const { - int col=PQfnumber(res_,name); - if (col>=0) - return PQftype(res_,col); + int col = PQfnumber(res_, name); + if (col >= 0) + { + return PQftype(res_, col); + } return 0; } virtual bool isNull(int index) const { - return static_cast(PQgetisnull(res_,pos_,index)); + return static_cast(PQgetisnull(res_, pos_, index)); } virtual const char* getValue(int index) const { - return PQgetvalue(res_,pos_,index); + return PQgetvalue(res_, pos_, index); } virtual const char* getValue(const char* name) const { - int col=PQfnumber(res_,name); - if (col>=0) + int col = PQfnumber(res_, name); + if (col >= 0) + { return getValue(col); + } return 0; } + +private: + PGresult* res_; + int pos_; + int numTuples_; + int *refCount_; }; #endif //RESULTSET_HPP - - - From 9d6c47844cd892be6e8fbe36a62cd88c0d99b32b Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Mon, 2 Apr 2012 11:33:29 -0700 Subject: [PATCH 179/238] use more robust transform method in map.zoom_all --- src/map.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/map.cpp b/src/map.cpp index 428c796c1..e04ffd4db 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -28,6 +28,7 @@ #include #include #include +#include // for PROJ_ENVELOPE_POINTS // boost #include @@ -405,8 +406,7 @@ void Map::zoom_all() proj_transform prj_trans(proj0,proj1); box2d layer_ext = itr->envelope(); - // TODO - consider using more robust method: http://trac.mapnik.org/ticket/751 - if (prj_trans.backward(layer_ext)) + if (prj_trans.backward(layer_ext, PROJ_ENVELOPE_POINTS)) { success = true; #ifdef MAPNIK_DEBUG From f6a0f80f323903cde32b0672e22582b58712908c Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Mon, 2 Apr 2012 15:42:52 -0700 Subject: [PATCH 180/238] make note of need for 64bit int support in code comments for postgis plugin - refs #895 --- plugins/input/postgis/postgis_featureset.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/input/postgis/postgis_featureset.cpp b/plugins/input/postgis/postgis_featureset.cpp index ac2f8d9b4..66d65c4ab 100644 --- a/plugins/input/postgis/postgis_featureset.cpp +++ b/plugins/input/postgis/postgis_featureset.cpp @@ -147,6 +147,8 @@ feature_ptr postgis_featureset::next() case 20: //int8/BigInt { + // TODO - need to support boost::uint64_t in mapnik::value + // https://github.com/mapnik/mapnik/issues/895 int val = int8net(buf); feature->put(name, val); break; From 763e84a6e9c39c3b0bc5f454da607e6b674aaf43 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Mon, 2 Apr 2012 16:20:41 -0700 Subject: [PATCH 181/238] postgis: add support for auto-detection of primary key field - closes #804 - refs #753 --- plugins/input/postgis/postgis_datasource.cpp | 119 ++++++++++++++--- plugins/input/postgis/postgis_datasource.hpp | 2 +- tests/python_tests/postgis_test.py | 131 ++++++++++++++++++- 3 files changed, 232 insertions(+), 20 deletions(-) diff --git a/plugins/input/postgis/postgis_datasource.cpp b/plugins/input/postgis/postgis_datasource.cpp index e992ea051..34f95d495 100644 --- a/plugins/input/postgis/postgis_datasource.cpp +++ b/plugins/input/postgis/postgis_datasource.cpp @@ -107,6 +107,7 @@ void postgis_datasource::bind() const boost::optional initial_size = params_.get("initial_size", 1); boost::optional max_size = params_.get("max_size", 10); + boost::optional require_key = params_.get("require_key", false); ConnectionManager* mgr = ConnectionManager::instance(); mgr->registerPool(creator_, *initial_size, *max_size); @@ -228,6 +229,71 @@ void postgis_datasource::bind() const } } + // detect primary key + if (key_field_.empty()) + { + std::ostringstream s; + s << "SELECT a.attname, a.attnum, t.typname, t.typname in ('int2','int4','int8') " + "AS is_int FROM pg_class c, pg_attribute a, pg_type t, pg_namespace n, pg_index i " + "WHERE a.attnum > 0 AND a.attrelid = c.oid " + "AND a.atttypid = t.oid AND c.relnamespace = n.oid " + "AND c.oid = i.indrelid AND i.indisprimary = 't' " + "AND t.typname !~ '^geom' AND c.relname =" + << " '" << mapnik::sql_utils::unquote_double(geometry_table_) << "' " + //"AND a.attnum = ANY (i.indkey) " // postgres >= 8.1 + << "AND (i.indkey[0]=a.attnum OR i.indkey[1]=a.attnum OR i.indkey[2]=a.attnum " + "OR i.indkey[3]=a.attnum OR i.indkey[4]=a.attnum OR i.indkey[5]=a.attnum " + "OR i.indkey[6]=a.attnum OR i.indkey[7]=a.attnum OR i.indkey[8]=a.attnum " + "OR i.indkey[9]=a.attnum) "; + if (! schema_.empty()) + { + s << "AND n.nspname='" + << mapnik::sql_utils::unquote_double(schema_) + << "' "; + } + s << "ORDER BY a.attnum"; + + shared_ptr rs_key = conn->executeQuery(s.str()); + if (rs_key->next()) + { + unsigned int result_rows = rs_key->size(); + if (result_rows == 1) + { + bool is_int = (std::string(rs_key->getValue(3)) == "t"); + if (is_int) + { + const char* key_field_string = rs_key->getValue(0); + if (key_field_string) + { + key_field_ = std::string(key_field_string); +#ifdef MAPNIK_DEBUG + std::clog << "Postgis Plugin: auto-detected key field of '" << key_field_ << "' on '" << geometry_table_ << "'\n"; +#endif + } + } + } + else if (result_rows > 1) + { + std::clog << "PostGIS Plugin: warning, multi column primary key detected but is not supported\n"; + } + } +#ifdef MAPNIK_DEBUG + else + { + std::clog << "Postgis Plugin: no primary key could be detected for '" << geometry_table_ << "'\n"; + } +#endif + rs_key->close(); + } + + // if a globally unique key field/primary key is required + // but still not known at this point, then throw + if (*require_key && key_field_.empty()) + { + throw mapnik::datasource_exception(std::string("PostGIS Plugin: Error: primary key required for table '") + + geometry_table_ + "', please supply 'key_field' option to specify field to use for primary key"); + } + if (srid_ == 0) { srid_ = -1; @@ -268,9 +334,9 @@ void postgis_datasource::bind() const // validate type of key_field if (! found_key_field && ! key_field_.empty() && fld_name == key_field_) { - found_key_field = true; if (type_oid == 20 || type_oid == 21 || type_oid == 23) { + found_key_field = true; desc_.add_descriptor(attribute_descriptor(fld_name, mapnik::Integer)); } else @@ -314,7 +380,7 @@ void postgis_datasource::bind() const break; case 700: // float4 case 701: // float8 - case 1700: // numeric ?? + case 1700: // numeric desc_.add_descriptor(attribute_descriptor(fld_name, mapnik::Double)); case 1042: // bpchar case 1043: // varchar @@ -545,21 +611,31 @@ featureset_ptr postgis_datasource::features(const query& q) const s << "SELECT ST_AsBinary(\"" << geometryColumn_ << "\") AS geom"; mapnik::context_ptr ctx = boost::make_shared(); + std::set const& props = q.property_names(); + std::set::const_iterator pos = props.begin(); + std::set::const_iterator end = props.end(); if (! key_field_.empty()) { mapnik::sql_utils::quote_attr(s, key_field_); ctx->push(key_field_); + + for (; pos != end; ++pos) + { + if (*pos != key_field_) + { + mapnik::sql_utils::quote_attr(s, *pos); + ctx->push(*pos); + } + } } - - std::set const& props = q.property_names(); - std::set::const_iterator pos = props.begin(); - std::set::const_iterator end = props.end(); - - for (; pos != end; ++pos) + else { - mapnik::sql_utils::quote_attr(s, *pos); - ctx->push(*pos); + for (; pos != end; ++pos) + { + mapnik::sql_utils::quote_attr(s, *pos); + ctx->push(*pos); + } } std::string table_with_bbox = populate_tokens(table_, scale_denom, box); @@ -625,20 +701,29 @@ featureset_ptr postgis_datasource::features_at_point(coord2d const& pt) const s << "SELECT ST_AsBinary(\"" << geometryColumn_ << "\") AS geom"; mapnik::context_ptr ctx = boost::make_shared(); + std::vector::const_iterator itr = desc_.get_descriptors().begin(); + std::vector::const_iterator end = desc_.get_descriptors().end(); if (! key_field_.empty()) { mapnik::sql_utils::quote_attr(s, key_field_); ctx->push(key_field_); + for (; itr != end; ++itr) + { + if (itr->get_name() != key_field_) + { + mapnik::sql_utils::quote_attr(s, itr->get_name()); + ctx->push(itr->get_name()); + } + } } - - std::vector::const_iterator itr = desc_.get_descriptors().begin(); - std::vector::const_iterator end = desc_.get_descriptors().end(); - - for (; itr != end; ++itr) + else { - mapnik::sql_utils::quote_attr(s, itr->get_name()); - ctx->push(itr->get_name()); + for (; itr != end; ++itr) + { + mapnik::sql_utils::quote_attr(s, itr->get_name()); + ctx->push(itr->get_name()); + } } box2d box(pt.x, pt.y, pt.x, pt.y); diff --git a/plugins/input/postgis/postgis_datasource.hpp b/plugins/input/postgis/postgis_datasource.hpp index c353c5216..f309f47fe 100644 --- a/plugins/input/postgis/postgis_datasource.hpp +++ b/plugins/input/postgis/postgis_datasource.hpp @@ -80,7 +80,7 @@ private: mutable std::string schema_; mutable std::string geometry_table_; const std::string geometry_field_; - const std::string key_field_; + mutable std::string key_field_; const int cursor_fetch_size_; const int row_limit_; mutable std::string geometryColumn_; diff --git a/tests/python_tests/postgis_test.py b/tests/python_tests/postgis_test.py index aec77cabc..b77922a30 100644 --- a/tests/python_tests/postgis_test.py +++ b/tests/python_tests/postgis_test.py @@ -63,7 +63,8 @@ def createdb_and_dropdb_on_path(): print 'Notice: skipping postgis tests (createdb/dropdb)' return False -insert_sql = """ +insert_table_1 = """ +CREATE TABLE test(gid serial PRIMARY KEY, geom geometry); INSERT INTO test(geom) values (GeomFromEWKT('SRID=4326;POINT(0 0)')); INSERT INTO test(geom) values (GeomFromEWKT('SRID=4326;POINT(-2 2)')); INSERT INTO test(geom) values (GeomFromEWKT('SRID=4326;MULTIPOINT(2 1,1 2)')); @@ -74,12 +75,46 @@ INSERT INTO test(geom) values (GeomFromEWKT('SRID=4326;MULTIPOLYGON(((1 1,3 1,3 INSERT INTO test(geom) values (GeomFromEWKT('SRID=4326;GEOMETRYCOLLECTION(POLYGON((1 1, 2 1, 2 2, 1 2,1 1)),POINT(2 3),LINESTRING(2 3,3 4))')); """ +insert_table_2 = """ +CREATE TABLE test2(manual_id int4 PRIMARY KEY, geom geometry); +INSERT INTO test2(manual_id, geom) values (0, GeomFromEWKT('SRID=4326;POINT(0 0)')); +INSERT INTO test2(manual_id, geom) values (1, GeomFromEWKT('SRID=4326;POINT(0 0)')); +INSERT INTO test2(manual_id, geom) values (1000, GeomFromEWKT('SRID=4326;POINT(0 0)')); +INSERT INTO test2(manual_id, geom) values (-1000, GeomFromEWKT('SRID=4326;POINT(0 0)')); +INSERT INTO test2(manual_id, geom) values (2147483647, GeomFromEWKT('SRID=4326;POINT(0 0)')); +INSERT INTO test2(manual_id, geom) values (-2147483648, GeomFromEWKT('SRID=4326;POINT(0 0)')); +""" + +insert_table_3 = """ +CREATE TABLE test3(non_id bigint, manual_id int4, geom geometry); +INSERT INTO test3(non_id, manual_id, geom) values (9223372036854775807, 0, GeomFromEWKT('SRID=4326;POINT(0 0)')); +INSERT INTO test3(non_id, manual_id, geom) values (9223372036854775807, 1, GeomFromEWKT('SRID=4326;POINT(0 0)')); +INSERT INTO test3(non_id, manual_id, geom) values (9223372036854775807, 1000, GeomFromEWKT('SRID=4326;POINT(0 0)')); +INSERT INTO test3(non_id, manual_id, geom) values (9223372036854775807, -1000, GeomFromEWKT('SRID=4326;POINT(0 0)')); +INSERT INTO test3(non_id, manual_id, geom) values (9223372036854775807, 2147483647, GeomFromEWKT('SRID=4326;POINT(0 0)')); +INSERT INTO test3(non_id, manual_id, geom) values (9223372036854775807, -2147483648, GeomFromEWKT('SRID=4326;POINT(0 0)')); +""" + +insert_table_4 = """ +CREATE TABLE test4(non_id int4, manual_id int8 PRIMARY KEY, geom geometry); +INSERT INTO test4(non_id, manual_id, geom) values (0, 0, GeomFromEWKT('SRID=4326;POINT(0 0)')); +INSERT INTO test4(non_id, manual_id, geom) values (0, 1, GeomFromEWKT('SRID=4326;POINT(0 0)')); +INSERT INTO test4(non_id, manual_id, geom) values (0, 1000, GeomFromEWKT('SRID=4326;POINT(0 0)')); +INSERT INTO test4(non_id, manual_id, geom) values (0, -1000, GeomFromEWKT('SRID=4326;POINT(0 0)')); +INSERT INTO test4(non_id, manual_id, geom) values (0, 2147483647, GeomFromEWKT('SRID=4326;POINT(0 0)')); +INSERT INTO test4(non_id, manual_id, geom) values (0, -2147483648, GeomFromEWKT('SRID=4326;POINT(0 0)')); +""" + def postgis_setup(): call('dropdb %s' % MAPNIK_TEST_DBNAME,silent=True) call('createdb -T %s %s' % (POSTGIS_TEMPLATE_DBNAME,MAPNIK_TEST_DBNAME),silent=False) call('shp2pgsql -s 3857 -g geom -W LATIN1 %s world_merc | psql -q %s' % (SHAPEFILE,MAPNIK_TEST_DBNAME), silent=True) call('''psql -q %s -c "CREATE TABLE \"empty\" (key serial);SELECT AddGeometryColumn('','empty','geom','-1','GEOMETRY',4);"''' % MAPNIK_TEST_DBNAME,silent=False) - call('''psql -q %s -c "create table test(gid serial PRIMARY KEY, geom geometry);%s"''' % (MAPNIK_TEST_DBNAME,insert_sql),silent=False) + call('''psql -q %s -c "%s"''' % (MAPNIK_TEST_DBNAME,insert_table_1),silent=False) + call('''psql -q %s -c "%s"''' % (MAPNIK_TEST_DBNAME,insert_table_2),silent=False) + call('''psql -q %s -c "%s"''' % (MAPNIK_TEST_DBNAME,insert_table_3),silent=False) + call('''psql -q %s -c "%s"''' % (MAPNIK_TEST_DBNAME,insert_table_4),silent=False) + def postgis_takedown(): pass # fails as the db is in use: https://github.com/mapnik/mapnik/issues/960 @@ -182,6 +217,98 @@ if 'postgis' in mapnik.DatasourceCache.instance().plugin_names() \ query.add_property_name('bogus') fs = ds.features(query) + def test_auto_detection_of_unique_feature_id_32_bit(): + ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,table='test2', + geometry_field='geom') + fs = ds.featureset() + eq_(fs.next()['manual_id'],0) + eq_(fs.next()['manual_id'],1) + eq_(fs.next()['manual_id'],1000) + eq_(fs.next()['manual_id'],-1000) + eq_(fs.next()['manual_id'],2147483647) + eq_(fs.next()['manual_id'],-2147483648) + + fs = ds.featureset() + eq_(fs.next().id(),0) + eq_(fs.next().id(),1) + eq_(fs.next().id(),1000) + eq_(fs.next().id(),-1000) + eq_(fs.next().id(),2147483647) + eq_(fs.next().id(),-2147483648) + + def test_auto_detection_will_fail_since_no_primary_key(): + ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,table='test3', + geometry_field='geom', + require_key=False) + fs = ds.featureset() + feat = fs.next() + eq_(feat['manual_id'],0) + # will fail: https://github.com/mapnik/mapnik/issues/895 + #eq_(feat['non_id'],9223372036854775807) + eq_(fs.next()['manual_id'],1) + eq_(fs.next()['manual_id'],1000) + eq_(fs.next()['manual_id'],-1000) + eq_(fs.next()['manual_id'],2147483647) + eq_(fs.next()['manual_id'],-2147483648) + + # since no valid primary key will be detected the fallback + # is auto-incrementing counter + fs = ds.featureset() + eq_(fs.next().id(),1) + eq_(fs.next().id(),2) + eq_(fs.next().id(),3) + eq_(fs.next().id(),4) + eq_(fs.next().id(),5) + eq_(fs.next().id(),6) + + @raises(RuntimeError) + def test_auto_detection_will_fail_and_should_throw(): + ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,table='test3', + geometry_field='geom', + require_key=True) + fs = ds.featureset() + + def test_auto_detection_of_unique_feature_id_64_bit(): + ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,table='test4', + geometry_field='geom', + require_key=False) + fs = ds.featureset() + eq_(fs.next()['manual_id'],0) + eq_(fs.next()['manual_id'],1) + eq_(fs.next()['manual_id'],1000) + eq_(fs.next()['manual_id'],-1000) + eq_(fs.next()['manual_id'],2147483647) + eq_(fs.next()['manual_id'],-2147483648) + + fs = ds.featureset() + eq_(fs.next().id(),0) + eq_(fs.next().id(),1) + eq_(fs.next().id(),1000) + eq_(fs.next().id(),-1000) + eq_(fs.next().id(),2147483647) + eq_(fs.next().id(),-2147483648) + + def test_manually_specified_feature_id_field(): + ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,table='test4', + geometry_field='geom', + key_field='manual_id', + require_key=True) + fs = ds.featureset() + eq_(fs.next()['manual_id'],0) + eq_(fs.next()['manual_id'],1) + eq_(fs.next()['manual_id'],1000) + eq_(fs.next()['manual_id'],-1000) + eq_(fs.next()['manual_id'],2147483647) + eq_(fs.next()['manual_id'],-2147483648) + + fs = ds.featureset() + eq_(fs.next().id(),0) + eq_(fs.next().id(),1) + eq_(fs.next().id(),1000) + eq_(fs.next().id(),-1000) + eq_(fs.next().id(),2147483647) + eq_(fs.next().id(),-2147483648) + atexit.register(postgis_takedown) if __name__ == "__main__": From f99db72e5ad717f6c82454136b3a6a8e7f225211 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Mon, 2 Apr 2012 16:23:08 -0700 Subject: [PATCH 182/238] update changelog after fixing #804 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66bc71981..4e42b0dda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ For a complete change history, see the SVN log. ## Mapnik 2.1.0 +- PostGIS: the primary key, for tables containing one, is now auto-detected allowing for globally unique feature id values (#804) + - Fix Markers rendering so that ellipse height/width units are pixels (previously were unintentially radii) - Removed PointDatasource - use more robust MemoryDatasource instead (#1032) From 10848f9d0455fc9af41f0ad8f711368200f8cc30 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Mon, 2 Apr 2012 18:13:27 -0700 Subject: [PATCH 183/238] postgis: warn in the unlikely case of a numeric primary key --- plugins/input/postgis/postgis_datasource.cpp | 6 +++++- tests/python_tests/postgis_test.py | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/plugins/input/postgis/postgis_datasource.cpp b/plugins/input/postgis/postgis_datasource.cpp index 34f95d495..6ff78bcd5 100644 --- a/plugins/input/postgis/postgis_datasource.cpp +++ b/plugins/input/postgis/postgis_datasource.cpp @@ -267,10 +267,14 @@ void postgis_datasource::bind() const { key_field_ = std::string(key_field_string); #ifdef MAPNIK_DEBUG - std::clog << "Postgis Plugin: auto-detected key field of '" << key_field_ << "' on '" << geometry_table_ << "'\n"; + std::clog << "PostGIS Plugin: auto-detected key field of '" << key_field_ << "' on table '" << geometry_table_ << "'\n"; #endif } } + else // warn with odd cases like numeric primary key + { + std::clog << "PostGIS Plugin: Warning: '" << rs_key->getValue(0) << "' on table '" << geometry_table_ << "' is not a valid integer primary key field\n"; + } } else if (result_rows > 1) { diff --git a/tests/python_tests/postgis_test.py b/tests/python_tests/postgis_test.py index b77922a30..e4908e1e9 100644 --- a/tests/python_tests/postgis_test.py +++ b/tests/python_tests/postgis_test.py @@ -105,6 +105,12 @@ INSERT INTO test4(non_id, manual_id, geom) values (0, 2147483647, GeomFromEWKT(' INSERT INTO test4(non_id, manual_id, geom) values (0, -2147483648, GeomFromEWKT('SRID=4326;POINT(0 0)')); """ +insert_table_5 = """ +CREATE TABLE test5(non_id int4, manual_id numeric PRIMARY KEY, geom geometry); +INSERT INTO test5(non_id, manual_id, geom) values (0, -1, GeomFromEWKT('SRID=4326;POINT(0 0)')); +INSERT INTO test5(non_id, manual_id, geom) values (0, 1, GeomFromEWKT('SRID=4326;POINT(0 0)')); +""" + def postgis_setup(): call('dropdb %s' % MAPNIK_TEST_DBNAME,silent=True) call('createdb -T %s %s' % (POSTGIS_TEMPLATE_DBNAME,MAPNIK_TEST_DBNAME),silent=False) @@ -114,6 +120,7 @@ def postgis_setup(): call('''psql -q %s -c "%s"''' % (MAPNIK_TEST_DBNAME,insert_table_2),silent=False) call('''psql -q %s -c "%s"''' % (MAPNIK_TEST_DBNAME,insert_table_3),silent=False) call('''psql -q %s -c "%s"''' % (MAPNIK_TEST_DBNAME,insert_table_4),silent=False) + call('''psql -q %s -c "%s"''' % (MAPNIK_TEST_DBNAME,insert_table_5),silent=False) def postgis_takedown(): pass @@ -309,6 +316,18 @@ if 'postgis' in mapnik.DatasourceCache.instance().plugin_names() \ eq_(fs.next().id(),2147483647) eq_(fs.next().id(),-2147483648) + def test_numeric_type_feature_id_field(): + ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,table='test5', + geometry_field='geom', + require_key=False) + fs = ds.featureset() + eq_(fs.next()['manual_id'],-1) + eq_(fs.next()['manual_id'],1) + + fs = ds.featureset() + eq_(fs.next().id(),1) + eq_(fs.next().id(),2) + atexit.register(postgis_takedown) if __name__ == "__main__": From 253a1e4b4bd588ef49a64641552e5dac9ac6ae42 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Mon, 2 Apr 2012 19:20:09 -0700 Subject: [PATCH 184/238] occi: fix invalid concatenation uncovered by latest clang++ --- plugins/input/occi/occi_datasource.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/plugins/input/occi/occi_datasource.cpp b/plugins/input/occi/occi_datasource.cpp index e4d3fa4a3..16fc1b780 100644 --- a/plugins/input/occi/occi_datasource.cpp +++ b/plugins/input/occi/occi_datasource.cpp @@ -540,15 +540,15 @@ featureset_ptr occi_datasource::features(query const& q) const if (row_limit_ > 0) { - std::string row_limit_string = "rownum < " + row_limit_; - + std::ostringstream row_limit_string; + row_limit_string << "rownum < " << row_limit_; if (boost::algorithm::ifind_first(query, "WHERE")) { - boost::algorithm::ireplace_first(query, "WHERE", row_limit_string + " AND "); + boost::algorithm::ireplace_first(query, "WHERE", row_limit_string.str() + " AND "); } else if (boost::algorithm::ifind_first(query, table_name_)) { - boost::algorithm::ireplace_first(query, table_name_, table_name_ + " " + row_limit_string); + boost::algorithm::ireplace_first(query, table_name_, table_name_ + " " + row_limit_string.str()); } else { @@ -621,15 +621,15 @@ featureset_ptr occi_datasource::features_at_point(coord2d const& pt) const if (row_limit_ > 0) { - std::string row_limit_string = "rownum < " + row_limit_; - + std::ostringstream row_limit_string; + row_limit_string << "rownum < " << row_limit_; if (boost::algorithm::ifind_first(query, "WHERE")) { - boost::algorithm::ireplace_first(query, "WHERE", row_limit_string + " AND "); + boost::algorithm::ireplace_first(query, "WHERE", row_limit_string.str() + " AND "); } else if (boost::algorithm::ifind_first(query, table_name_)) { - boost::algorithm::ireplace_first(query, table_name_, table_name_ + " " + row_limit_string); + boost::algorithm::ireplace_first(query, table_name_, table_name_ + " " + row_limit_string.str()); } else { From d9cb76f7a1637ed9252f81709ecff4c86506f303 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Mon, 2 Apr 2012 19:21:14 -0700 Subject: [PATCH 185/238] postgis: support mixed case table names - closes #1159 --- include/mapnik/sql_utils.hpp | 9 +++++---- tests/python_tests/postgis_test.py | 31 ++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/include/mapnik/sql_utils.hpp b/include/mapnik/sql_utils.hpp index d4a3b05ec..3a6d2e34e 100644 --- a/include/mapnik/sql_utils.hpp +++ b/include/mapnik/sql_utils.hpp @@ -31,14 +31,14 @@ namespace mapnik { namespace sql_utils { inline std::string unquote_double(const std::string& sql) { - std::string table_name = boost::algorithm::to_lower_copy(sql); + std::string table_name = sql; boost::algorithm::trim_if(table_name,boost::algorithm::is_any_of("\"")); return table_name; } inline std::string unquote(const std::string& sql) { - std::string table_name = boost::algorithm::to_lower_copy(sql); + std::string table_name = sql; boost::algorithm::trim_if(table_name,boost::algorithm::is_any_of("\"\'")); return table_name; } @@ -58,10 +58,11 @@ namespace mapnik { namespace sql_utils { inline std::string table_from_sql(const std::string& sql) { - std::string table_name = boost::algorithm::to_lower_copy(sql); + std::string table_name = sql; boost::algorithm::replace_all(table_name,"\n"," "); + boost::algorithm::ireplace_all(table_name," from "," FROM "); - std::string::size_type idx = table_name.rfind(" from "); + std::string::size_type idx = table_name.rfind(" FROM "); if (idx!=std::string::npos) { idx = table_name.find_first_not_of(" ",idx+5); diff --git a/tests/python_tests/postgis_test.py b/tests/python_tests/postgis_test.py index e4908e1e9..6259a379c 100644 --- a/tests/python_tests/postgis_test.py +++ b/tests/python_tests/postgis_test.py @@ -111,6 +111,14 @@ INSERT INTO test5(non_id, manual_id, geom) values (0, -1, GeomFromEWKT('SRID=432 INSERT INTO test5(non_id, manual_id, geom) values (0, 1, GeomFromEWKT('SRID=4326;POINT(0 0)')); """ +insert_table_6 = ''' +CREATE TABLE "tableWithMixedCase"(gid serial PRIMARY KEY, geom geometry); +INSERT INTO "tableWithMixedCase"(geom) values (ST_MakePoint(0,0)); +INSERT INTO "tableWithMixedCase"(geom) values (ST_MakePoint(0,1)); +INSERT INTO "tableWithMixedCase"(geom) values (ST_MakePoint(1,0)); +INSERT INTO "tableWithMixedCase"(geom) values (ST_MakePoint(1,1)); +''' + def postgis_setup(): call('dropdb %s' % MAPNIK_TEST_DBNAME,silent=True) call('createdb -T %s %s' % (POSTGIS_TEMPLATE_DBNAME,MAPNIK_TEST_DBNAME),silent=False) @@ -121,6 +129,7 @@ def postgis_setup(): call('''psql -q %s -c "%s"''' % (MAPNIK_TEST_DBNAME,insert_table_3),silent=False) call('''psql -q %s -c "%s"''' % (MAPNIK_TEST_DBNAME,insert_table_4),silent=False) call('''psql -q %s -c "%s"''' % (MAPNIK_TEST_DBNAME,insert_table_5),silent=False) + call("""psql -q %s -c '%s'""" % (MAPNIK_TEST_DBNAME,insert_table_6),silent=False) def postgis_takedown(): pass @@ -328,6 +337,28 @@ if 'postgis' in mapnik.DatasourceCache.instance().plugin_names() \ eq_(fs.next().id(),1) eq_(fs.next().id(),2) + def test_querying_table_with_mixed_case(): + ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,table='"tableWithMixedCase"', + geometry_field='geom', + require_key=True) + fs = ds.featureset() + eq_(fs.next().id(),1) + eq_(fs.next().id(),2) + eq_(fs.next().id(),3) + eq_(fs.next().id(),4) + eq_(fs.next(),None) + + def test_querying_subquery_with_mixed_case(): + ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,table='(SeLeCt * FrOm "tableWithMixedCase") as MixedCaseQuery', + geometry_field='geom', + require_key=True) + fs = ds.featureset() + eq_(fs.next().id(),1) + eq_(fs.next().id(),2) + eq_(fs.next().id(),3) + eq_(fs.next().id(),4) + eq_(fs.next(),None) + atexit.register(postgis_takedown) if __name__ == "__main__": From a7e150a59319831415fc1120e7f4b8efbc22953d Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Mon, 2 Apr 2012 19:29:06 -0700 Subject: [PATCH 186/238] tests: add simple tests for postgis 'bbox' token replacement --- plugins/input/sqlite/sqlite_datasource.cpp | 26 ++++++++++++++-------- tests/python_tests/postgis_test.py | 24 ++++++++++++++++++++ 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/plugins/input/sqlite/sqlite_datasource.cpp b/plugins/input/sqlite/sqlite_datasource.cpp index 77015ef82..57569cfb5 100644 --- a/plugins/input/sqlite/sqlite_datasource.cpp +++ b/plugins/input/sqlite/sqlite_datasource.cpp @@ -548,23 +548,31 @@ featureset_ptr sqlite_datasource::features(query const& q) const std::ostringstream s; mapnik::context_ptr ctx = boost::make_shared(); + std::set const& props = q.property_names(); + std::set::const_iterator pos = props.begin(); + std::set::const_iterator end = props.end(); s << "SELECT " << geometry_field_; if (!key_field_.empty()) { s << "," << key_field_; ctx->push(key_field_); + for ( ;pos != end;++pos) + { + if (*pos != key_field_) + { + s << ",[" << *pos << "]"; + ctx->push(*pos); + } + } } - std::set const& props = q.property_names(); - std::set::const_iterator pos = props.begin(); - std::set::const_iterator end = props.end(); - - for ( ;pos != end;++pos) + else { - // TODO - should we restrict duplicate key query? - //if (*pos != key_field_) - s << ",[" << *pos << "]"; - ctx->push(*pos); + for ( ;pos != end;++pos) + { + s << ",[" << *pos << "]"; + ctx->push(*pos); + } } s << " FROM "; diff --git a/tests/python_tests/postgis_test.py b/tests/python_tests/postgis_test.py index 6259a379c..128d4c998 100644 --- a/tests/python_tests/postgis_test.py +++ b/tests/python_tests/postgis_test.py @@ -359,6 +359,30 @@ if 'postgis' in mapnik.DatasourceCache.instance().plugin_names() \ eq_(fs.next().id(),4) eq_(fs.next(),None) + def test_bbox_token_in_subquery1(): + ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,table=''' + (SeLeCt * FrOm "tableWithMixedCase" where geom && !bbox! ) as MixedCaseQuery''', + geometry_field='geom', + require_key=True) + fs = ds.featureset() + eq_(fs.next().id(),1) + eq_(fs.next().id(),2) + eq_(fs.next().id(),3) + eq_(fs.next().id(),4) + eq_(fs.next(),None) + + def test_bbox_token_in_subquery2(): + ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,table=''' + (SeLeCt * FrOm "tableWithMixedCase" where ST_Intersects(geom,!bbox!) ) as MixedCaseQuery''', + geometry_field='geom', + require_key=True) + fs = ds.featureset() + eq_(fs.next().id(),1) + eq_(fs.next().id(),2) + eq_(fs.next().id(),3) + eq_(fs.next().id(),4) + eq_(fs.next(),None) + atexit.register(postgis_takedown) if __name__ == "__main__": From 46b16c917e404befe471998f56ba0fe6dea66001 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Tue, 3 Apr 2012 16:06:36 -0700 Subject: [PATCH 187/238] support nodata for paletted images and allow user to set nodata on-the-fly - closes #1160 and #1161 - refs #688 and refs #730 and refs #50 and refs #1018 --- CHANGELOG.md | 4 + plugins/input/gdal/gdal_datasource.cpp | 9 +- plugins/input/gdal/gdal_datasource.hpp | 1 + plugins/input/gdal/gdal_featureset.cpp | 79 +++++++++++++++--- plugins/input/gdal/gdal_featureset.hpp | 5 +- tests/data/good_maps/tiff_colortable.xml | 16 ++++ tests/data/good_maps/vrt_colortable.xml | 15 ++++ tests/data/raster/dataraster.vrt | 45 ++++++++++ .../images/support/tif_colortable.png | Bin 0 -> 26726 bytes .../images/support/vrt_colortable.png | Bin 0 -> 7308 bytes tests/python_tests/raster_colormapped_test.py | 38 +++++++++ 11 files changed, 196 insertions(+), 16 deletions(-) create mode 100644 tests/data/good_maps/tiff_colortable.xml create mode 100644 tests/data/good_maps/vrt_colortable.xml create mode 100644 tests/data/raster/dataraster.vrt create mode 100644 tests/python_tests/images/support/tif_colortable.png create mode 100644 tests/python_tests/images/support/vrt_colortable.png create mode 100644 tests/python_tests/raster_colormapped_test.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e42b0dda..e84eb42f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ For a complete change history, see the SVN log. ## Mapnik 2.1.0 +- GDAL: allow setting nodata value on the fly (will override value if nodata is set in data) (#1161) + +- GDAL: respect nodata for paletted/colormapped images (#1160) + - PostGIS: the primary key, for tables containing one, is now auto-detected allowing for globally unique feature id values (#804) - Fix Markers rendering so that ellipse height/width units are pixels (previously were unintentially radii) diff --git a/plugins/input/gdal/gdal_datasource.cpp b/plugins/input/gdal/gdal_datasource.cpp index 1bdf85ce1..29303bd5e 100644 --- a/plugins/input/gdal/gdal_datasource.cpp +++ b/plugins/input/gdal/gdal_datasource.cpp @@ -78,7 +78,8 @@ inline GDALDataset* gdal_datasource::open_dataset() const gdal_datasource::gdal_datasource(parameters const& params, bool bind) : datasource(params), desc_(*params.get("type"), "utf-8"), - filter_factor_(*params_.get("filter_factor", 0.0)) + filter_factor_(*params_.get("filter_factor", 0.0)), + nodata_value_(params_.get("nodata")) { #ifdef MAPNIK_DEBUG std::clog << "GDAL Plugin: Initializing..." << std::endl; @@ -242,7 +243,8 @@ featureset_ptr gdal_datasource::features(query const& q) const nbands_, dx_, dy_, - filter_factor_)); + filter_factor_, + nodata_value_)); } featureset_ptr gdal_datasource::features_at_point(coord2d const& pt) const @@ -261,5 +263,6 @@ featureset_ptr gdal_datasource::features_at_point(coord2d const& pt) const nbands_, dx_, dy_, - filter_factor_)); + filter_factor_, + nodata_value_)); } diff --git a/plugins/input/gdal/gdal_datasource.hpp b/plugins/input/gdal/gdal_datasource.hpp index 4aa841371..4b2d2ea9d 100644 --- a/plugins/input/gdal/gdal_datasource.hpp +++ b/plugins/input/gdal/gdal_datasource.hpp @@ -57,6 +57,7 @@ private: mutable int nbands_; mutable bool shared_dataset_; double filter_factor_; + boost::optional nodata_value_; inline GDALDataset* open_dataset() const; }; diff --git a/plugins/input/gdal/gdal_featureset.cpp b/plugins/input/gdal/gdal_featureset.cpp index 7546adf3f..14bf11ee5 100644 --- a/plugins/input/gdal/gdal_featureset.cpp +++ b/plugins/input/gdal/gdal_featureset.cpp @@ -53,7 +53,8 @@ gdal_featureset::gdal_featureset(GDALDataset& dataset, int nbands, double dx, double dy, - double filter_factor) + double filter_factor, + boost::optional const& nodata) : dataset_(dataset), ctx_(boost::make_shared()), band_(band), @@ -65,6 +66,7 @@ gdal_featureset::gdal_featureset(GDALDataset& dataset, dy_(dy), nbands_(nbands), filter_factor_(filter_factor), + nodata_value_(nodata), first_(true) { ctx_->push("value"); @@ -241,8 +243,17 @@ feature_ptr gdal_featureset::get_feature(mapnik::query const& q) float* imageData = (float*)image.getBytes(); GDALRasterBand * band = dataset_.GetRasterBand(band_); - int hasNoData; - double nodata = band->GetNoDataValue(&hasNoData); + int hasNoData(0); + double nodata(0); + if (nodata_value_) + { + hasNoData = 1; + nodata = *nodata_value_; + } + else + { + nodata = band->GetNoDataValue(&hasNoData); + } band->RasterIO(GF_Read, x_off, y_off, width, height, imageData, image.width(), image.height(), GDT_Float32, 0, 0); @@ -340,8 +351,21 @@ feature_ptr gdal_featureset::get_feature(mapnik::query const& q) #ifdef MAPNIK_DEBUG std::clog << "GDAL Plugin: processing rgb bands..." << std::endl; #endif - int hasNoData; - float nodata = red->GetNoDataValue(&hasNoData); + int hasNoData(0); + double nodata(0); + if (nodata_value_) + { + hasNoData = 1; + nodata = *nodata_value_; + } + else + { + nodata = red->GetNoDataValue(&hasNoData); + } + if (hasNoData) + { + feature->put("NODATA",nodata); + } GDALColorTable *color_table = red->GetColorTable(); if (! alpha && hasNoData && ! color_table) @@ -380,12 +404,25 @@ feature_ptr gdal_featureset::get_feature(mapnik::query const& q) #ifdef MAPNIK_DEBUG std::clog << "GDAL Plugin: processing gray band..." << std::endl; #endif - int hasNoData; - float nodata = grey->GetNoDataValue(&hasNoData); + int hasNoData(0); + double nodata(0); + if (nodata_value_) + { + hasNoData = 1; + nodata = *nodata_value_; + } + else + { + nodata = grey->GetNoDataValue(&hasNoData); + } GDALColorTable* color_table = grey->GetColorTable(); if (hasNoData && ! color_table) { + if (hasNoData) + { + feature->put("NODATA",nodata); + } #ifdef MAPNIK_DEBUG std::clog << "\tno data value for layer: " << nodata << std::endl; #endif @@ -422,23 +459,41 @@ feature_ptr gdal_featureset::get_feature(mapnik::query const& q) #ifdef MAPNIK_DEBUG std::clog << "GDAL Plugin: Loading colour table..." << std::endl; #endif + unsigned nodata_value = static_cast(nodata); + if (hasNoData) + { + feature->put("NODATA",static_cast(nodata_value)); + } for (unsigned y = 0; y < image.height(); ++y) { unsigned int* row = image.getRow(y); for (unsigned x = 0; x < image.width(); ++x) { unsigned value = row[x] & 0xff; - const GDALColorEntry *ce = color_table->GetColorEntry(value); - if (ce ) + if (hasNoData && (value == nodata_value)) { - // TODO - big endian support - row[x] = (ce->c4 << 24)| (ce->c3 << 16) | (ce->c2 << 8) | (ce->c1) ; + // make no data fully alpha + row[x] = 0; + } + else + { + const GDALColorEntry *ce = color_table->GetColorEntry(value); + if (ce) + { + // TODO - big endian support + row[x] = (ce->c4 << 24)| (ce->c3 << 16) | (ce->c2 << 8) | (ce->c1) ; + } + else + { + // make lacking color entry fully alpha + // note - gdal_translate makes black + row[x] = 0; + } } } } } } - if (alpha) { #ifdef MAPNIK_DEBUG diff --git a/plugins/input/gdal/gdal_featureset.hpp b/plugins/input/gdal/gdal_featureset.hpp index d4a2e9db8..b812da815 100644 --- a/plugins/input/gdal/gdal_featureset.hpp +++ b/plugins/input/gdal/gdal_featureset.hpp @@ -29,6 +29,7 @@ // boost #include +#include class GDALDataset; class GDALRasterBand; @@ -47,7 +48,8 @@ public: int nbands, double dx, double dy, - double filter_factor); + double filter_factor, + boost::optional const& nodata); virtual ~gdal_featureset(); mapnik::feature_ptr next(); private: @@ -67,6 +69,7 @@ private: double dy_; int nbands_; double filter_factor_; + boost::optional nodata_value_; bool first_; }; diff --git a/tests/data/good_maps/tiff_colortable.xml b/tests/data/good_maps/tiff_colortable.xml new file mode 100644 index 000000000..c102ff363 --- /dev/null +++ b/tests/data/good_maps/tiff_colortable.xml @@ -0,0 +1,16 @@ + + + + + 2011_5km_vrt_nodata_style + + ../raster/dataraster.tif + 20 + gdal + + + \ No newline at end of file diff --git a/tests/data/good_maps/vrt_colortable.xml b/tests/data/good_maps/vrt_colortable.xml new file mode 100644 index 000000000..d20367c72 --- /dev/null +++ b/tests/data/good_maps/vrt_colortable.xml @@ -0,0 +1,15 @@ + + + + + 2011_5km_vrt_nodata_style + + ../raster/dataraster.vrt + gdal + + + \ No newline at end of file diff --git a/tests/data/raster/dataraster.vrt b/tests/data/raster/dataraster.vrt new file mode 100644 index 000000000..26f6b5cfe --- /dev/null +++ b/tests/data/raster/dataraster.vrt @@ -0,0 +1,45 @@ + + PROJCS["WGS 84 / UTM zone 30N",GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433],AUTHORITY["EPSG","4326"]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",-3],PARAMETER["scale_factor",0.9996],PARAMETER["false_easting",500000],PARAMETER["false_northing",0],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AUTHORITY["EPSG","32630"]] + -1.4637000000000000e+04, 5.0000000000000000e+02, 0.0000000000000000e+00, 4.8596780000000000e+06, 0.0000000000000000e+00, -5.0000000000000000e+02 + + -9.99000000000000E+02 + 0 + Palette + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dataraster.tif + 1 + + + + -999 + + + diff --git a/tests/python_tests/images/support/tif_colortable.png b/tests/python_tests/images/support/tif_colortable.png new file mode 100644 index 0000000000000000000000000000000000000000..aa0021b38d731fbd7d0b259d07341fff6893cd8b GIT binary patch literal 26726 zcmeEt`9GB1ANRRtVQd*?7e?A_g+WEkD4`N6yNYf*S-P!}=Ax9lTWC?1FhzEeC0m-Q z?%PsiPxk3nmSoG$n0b!x^DjI~Wg zs2_P7yN#zR&9`nmV+Xd$+4G)|j8agL8P*T!YH(JVvLw&pSI?a!3YmWnhI`S2vp>0$6r z?y^63`9xt`FJ-ff5+kRQzY%x`{^RR{dW2^A&v%oB&j$Wl9LFc`oW>&4HW{2L2FHD3 zw5@;E?_z$4`SAT}*Uxg;UO{;P*KEK7M zM~aTRXGWpQAePDG2kmC-7PR#)iDDeWCs35)L?*D)6ka`I3yD#@H#h9lyw7mnE)WgZtA*r-zl0ST`FT8J$r z(FfRBKz-r5|0{ZSmyW{o9CM*d8+ud_O~0eWbv^K=ZAo(VYrYK?C^^}VXV=*xnK@w&*utj_X9 zA`^+S2~wK=$8+e@Zuc=P1@y}&i8g_YD9FTjdH z2NJ6*mSoOz^++TaG`C5*Cc!chkDgAsa@@G1&gB>}=qYFQbTd_@g~1Q@E|BhNMR0d_9&h}ZC6~{aI(y*AdzaFXi0152@?RQ z2?WTj=Vx&Xj;t5Fc1O^gQ7waOMVBK4!y4Wcl1Tg*sl?ih(BtoZDiH|LIg1-k7jnY5 zQyRSoMib0=!v^3JU!+P*Hym%L+7DNCvTfh}&C9RpZs)aL_~XduJ{QK@jp0UPl+_uZ z3JcS*buPE2mCL!-U{%*L;<$OR^1kE8e_sR@lAOP=v!>XgY21@UbpdSQz3?TUlBQd( zz}e&eE}UTjhjX)WcNQq^TvY?Hr?BXLT-arj51kyb7>ZEE2%cjlX9TR0T|^gm^&aQJ zJa^#3of9(BIt>ilZ8kS~y>AaSyzOG|0d#x#WL)}8ff-d4SG1D<+1ctQwW406%Te2* zM13>8z>FL5jD^M%qlfoLobK+5W9$I#WMpxL?0!;cBpOxjD#6@L8gsh*XG#SvX|e=7 zu-|W2{9fcWixOfDT{M}F!#Fd&+LKA zoP8Ly@gK!H5(bba2=EQtWIq?gjYODKUK}fN1F8f`LsI`W6T$OjWA9ZF0P|a{Q4Eh3 zHG6#5;N*QX8KEu3j-+uihosj~bNI3a3@)*zby7JP16u!VS4Bgz=ps1H^y0>&Y71?j zH*KYogO7?4>6MX5YoRH>nm+DYWWt;wwWd)!@t>b>FVAT}l+6(vXvdl~gp$EKjbf<>k3zQpRDB&=I)&OdSRSnvWw z4Vo}$32G8K{)5CMTbBxeqeNm#Lpz{e4(P4H;7JLbZw>;$)!96fIKFTM~8?3GAc3A7XeF~R(#{K7OghBLjYt<3W~un;g^y9zx}xPtL=99#m|M zlE(K{2pQCRw~bKhEmnPu$_M-ilJXQR$U1vXfm&_bVbh zQ(+7wdxj27-2biLJ}_yUE8zj0lhzz%ePi-X;%-d|Hkx16a`A2%iy4K*uRAoQAUY&Skn-JzG{hG&vUFqN5Uh@WZGkk=#X-x3w z(I&lRqC^JRq|*L1^=X(ZUU}Y7ghY=Aj^2=u!uX4$g$Ny}e?bC(`laJ?F6&cH+;zPZ zf>(YlS>x-V}=}MVub>R#LPU>e4tD-!hGdN_) zU-8N%Zmv?uI(NoALL8VM=9!S}6D~-Wza)g@rcpfF#l{W>CuXSPHm~R+=+skxz{){( z0j5-^m=wsn1=ip2gX9>v4)XYd{kRqYT6H=^xI!Q|*)wJhi=i8?V~bu~?saLPg&Kn1 zs0=?&QVu1g>B7bYcd};ebNy1CAPCDrvSpE)d{8SI@MiC9NW%E-)EyEO&g!Uym!+lt z9%^P|3cbqaGLPxe-t?Z=cU$g`Uq3~pmXv#g!>vXt2&=;7=IhfmP-Vd8Y_3+{y}~R~ z$k+ykmC)F0rm`=3uD*<5QQu{Jf1|6SU5F0VQ~ z{Jjsy!N{3}K{9kv0*D)%#iOxjZKQqN_5$LZcQfr#i&OY7Rh(7aT?-ug)1^}PWZ`Hw zqg*amHdIGIJ)|?E6|L|BN?3IPa0q8fE`-t1wO?4NaD$lz=!Voope2r@Z@e~LHNr6; z46Nv-D+aIc1VBrPBRpz4;=)OBbG7({vr1uj;4<*QV*Dg+JPJRv>d`{`l~=3_{7x_l zaO60^Gz8j&zx+ktD`Z_SlDN_NgcL+0k{MfYt4 zRB}={HkszG()W;zh~>|D4jtzKGx2|(;J5oIZ4~dAwr%bJ?;BnBT4nSm*aj*@e0ad_ z<9|m3ULZy+{bTW9WseU>gpkJr{O>iZf?q@clpk}@jlXwqUc`cf1S>a;vh(H2Nn2Os zx#F408tZ^ZO8nme9IW5a#~$5qs2i!mV3b?U$ld(lYtnizVCwqYgZ8)nUzE`ufR*O| zRwG!Gu+!P9UAna%!poziL=Dpf10kT9@2+w>QRA+<`H2x@B6j0Jr^Xj${N$u~rC09Z!-VL{hM`qkXaNtP*mcE0TLu{PN!+W(bk-BBgB zJWZP~9}D5xspspJ-7qZ_(2Hx&2a|8Y1@B1@oFX)t$#`b^cdwEGDLwZ9m>c#vhC%bz zOj_7eB@E6@cSAr-ln8C?mgQ_xR|Ee=dnF#-MCZ+;F6>41sxA@c0F?)qy+YdsjPC9aMNkh;CI;mt995)!p4+3Z zu!A)pGoQhqT02|Xq^9wP&`48jHn1Q`WH=Fqj6NbQir!x=Gk zKjJ@zAk@4(aNzO08^XjQ_at_na zGFMiQ43Ce3ECT`HmnF~bX&HGa7JH2OOY|LIgTEUHaYYJH(w_%v{!{bvqF3y+EZ~*& z7dKkrZ7&1e-&JoiT3gGr=pp3_77fn56HXY#>A@f@%9GeB2grx84*=6=#znsNPj7m7 zxk4*jk>o7(Hs~|i*lgf?^Y6h)t_*nDI9L_Wq4vB}|b>8m7h7 zNx6$7MTi9tf^USFAz=tp&))z--xtUk0ucxrpOveJxoZP*=0&m^s!V%Rqkfsq2!WtdD4$^83O&IH^$65wbhP+APAJ)8JQN|KBy;sUxb+Xby$Gk0Yct{kx@)nPD2hh>4L*F=gIdq(j1UV%ge z{iw=}57fjkk-d>RpuMhUIvOpKn$_W&=_vzd*2Nt8lYtQm%>Hlp-UD7_up8yUCP^vH zq!vp5YkMI<9N1NYj647w4r*e7m_6!wxAvHd0-Yad5m<6EDLi?%6##ntX-dg$)E*rE zmu(S+9$0V^LN3^#ocL6l7|zIGJjLgae=AilhMom;#$up-%0(Nv@^@xnkX(TBAU%MZ zzzt_eWOIE0RWn#v)JVaZ2bhz&J4nv3DJaQ|0={*&d_1s=O#y9-)Cy6;w8QmpyAft?2;8ZUOryQC0OkF5jj(qZol`# zA-Cm}v;V0AyI@SJhR0o;%1{1=_{d;ui7TUEuG;^(ErnwEGWV^xg$pbQ+CR34WbGRoLWX> zxbByIX^b!LY_aHamES^Dnb0|Lbn4e+@a{9h?Y{sXldppwLsxV!@_`PY1M3+OV)fp! z5?mDpO`;fg>otcY;+zqXOgO|324BTb=YjwlLs)4~%jU8$cDu9Hn(F zi^Zn;xH~+5zKt~SnIGi6#J9&~lDSt?=7L*go%`DmAdItIV7T5ZbvNnS*Rt6r`8G+U z$sMD5ZKXPMNMPhA+iR(c2kGnvwq)iRUQ;PViJYi(H$5skrT=D&6$qWS0?aUd(@KT2 zfMpOW3_Q;x4-`BE%T9?AEmQFAB$5L!3lF9bzZiW!on08yFna)y_`r7DpmAG$Libg7 zRC9Dg_n$E#Bd#z}gLx(|KZDkqVdpj5+-iT#nyPI719-_Q4&Y`z+F) zuX&hoth27SHMOM*!~H?C($=Mjx z8-(?1p^fau`xHDnA93Wy!ENf(27sm8%l}~|`p%)qYd@Q4PrkJ!(;C`6j@?B3&pL}^ zc3(Y@yChi(Gn2P3HCl?8__8l42DRbY&ky>wAb!m7Leq^i@zKAu(9mmt{QJtW3y3G$ zcY%)klqw40&^qH~9oDQ)0#ulX>;Xn&CqgA@-q>jL*7IF@GCiKmktr(elki))vbicp{yJV?M$c`!B@ zSPxQ;&}KeA+X*i8vXF)$EU@9ND|mK)(e2J!;Ox~0lMrHmnW*nHGfd-8Mxq1-UA5Bm zla=<7W62Onw5}+6``o#CeT0;6IQBZJ?(aH8Qd8~as zVLot7UOVy=leRvYTUs$bVo7pCD$GzAnP@)NqgDV z`4iZ<02Ox1#HBb0=Qv43x`9`%f3f}LOJ*_4whS2LplbN zogWjomXJl`XLV8+IC5|=)q~R7vvQ!+1}Wk}WiS&bp6Of2Y?jC^nf$$3A;K+MGa)$_ z3C~?21}fuPs5h4nTR84vB_qIZ4+y>IrohAiUu{#^4rqQ=1)e3L+d&)=q6x@bIbK$O z`+C{n>DuXh8lYzFl0v8Ew*#vWXzug{6Qy_WR#za`$iGk`xnvRrS3CxUXJ`@HotXl7 zl}AxGTYQQDy3zksZGSp~&If)0OaY|-5vpCg0!}nQ$_iG(U3ZUBgZ{c)!Ad;6+10_F z&=?JUHX!4K08z{vKssY#6~+AN5}*3kebpW$j&0~6%rrb)abBK~=4kAs|Kq-^?*3;~ zJc)FKipQDuj{nkv{nOE$DeXo6uBuCc+Q8~P8u|ievwRoV;nmtthbnCMFscsP8^aCg z_5{27MVF-c60=R)RLGku;_PnId+fpl^vQcKZPq^Mi^#A|;)^xMwj6``2*KqxOpya< z4ru%ZHtUxM?LQ>ABV|B6n(AJ~d`KdBb1w97|8C(zwae}#R3%bHF)v5bojFg;wP?lt zjDEHns6O-lq4Y2x(vh?q0>}5qv-d)r-|vko6G{QIZXX)7O^uLv7hCS`>n33Xm;|Ul zgz4gpTAWU829DJD9lQY5tI`4;?&%7$9IZ|4zv-jH-lGX2@9!^d)*)yrju-62*_}R+ zHCBI)oT%{brpzfEJy<(UbXLQr$AH#lS2 z%426**)`X=e)5C#Wu6M3-kppW)>)h0pt=GJ4s`6I`lMImFd3T?wF>CRhv~mj)EzyS zbMF}X<)u9#7DL5h%IpCjVPdwvZAb`^?NM1GJ&MV{FiyHx7)|uuY5p`kbGvtWa~I#4Djj{)+6nd?#rC*AgqN^Kx*&jb-o1>G=4?HUL7Xg zlWYgH1E+ubnKBuYk(jB{&(?Q?yl7DDUrr^Fh17jG zJGTaU2nULMfn_R>Qij<2TT%-_(8|#8d%dP~!Z^qND?#MY_6iwwf>c}M9NAJU;Jqgwj z<~&|IsYnw7wHxf?-NRs_a1#*gD`&kIa%V5QA+;oy)q;G&@l>QQapl*vYi@te`HC5JJRT3as*9F>x z*)b!SflcF%cu-+XO+7F-9Pun%nMwY=;>V}DRV|9tueasNk7e3KVCl4`fweXs;OuKm zN`1ZZx(5r+?S-wX>8F8;d*|Mq-errWz7}WpAG~}EuhZ1mBl8i>S2aGXRCClwxS@QHO%%7KzS-bFccnB>C2r{iTUfmvAE)z8Jn|Yt!Uu za)&_ac8sR)Umz-&Ek~%%#U9nmv0ovyYby3Yp#&Q@odsA3;`Nc)?{(I}dlEs5)&hYg zf>;Jzwap=voG*dzdKZhsI!&RxX7 zf(kc z=?*9qMZJsfjom_^4f?bCu$TcY$>$IZqN+GkK<^Q3AMa4Y7l$R@1pFW+4)Egpb=UnC z+H%(O*Fsn7<#U#KsD`aZ62K}Ms!=4L<&bu-)+&{s+Or6z3@!j>4C5e z%)L%g`^!-*F8?ug;EHK9@VQAZT5Id(bv0Bdw{MGEbNV?GL9v8UiVl~CTWf2`&fCAX zzuUh>M(BL(2qDD%5`av@c3cZo)cx0K>vb2A`Ys@k(!pS{-p!;bD;{0R{iq~gd z%36lVYNh<3dIRTa4-SGj-v@CS8y9R5pnqeA`*bo8e9Px!-9;J$l?>rgUyzFwHO{Eh z;@D5%8IX8h79tIx#*$z0@R@2fFxJz#0j zwq1WS{^+Kd*TOE9hjd?^-D39PIXKNOj0k9AMlT*NS#xwGb&bx>gi^Lrb?4n=D^VV0 z#Q9F2?J;VI>!gN#iO97*^*Onbi~gP0bv&%P<2=P73er9+1ZLV=H&VjOGr(+d^o$kha?mLXQMM>X6(p8HMXh`NO>rEG!*92{+DMAJDS@eQB$xndbkvSkCtbN# z2&}Et3lFV3SH%**VG5&;M4qckv@G3++?-V1*+=f_WHRhBxQ2k`!-TJ1tM|_`Y=$|j4XXOutitcTQWIxg zqtFD3cFEw~0JTu4T`;m?6`B(2N+N5vs&1+_hahM-zk5{Pg$Ikx0CzR`Em-NGr2bD9!5^GZC$m@A#_&9wm4zIlzE^CAvp@;O98VvRF?<~Jeb)mS`H|=Y>4LWC1HRF z;jjqYb9S+C&s{1J%NfBV`%e6$^(G-9XH!4zU(R?*m&~lPQrL*~Rtj-k+P?iQr#RKTGYqR}q_tvbk^i4c-e3QH>ijkIS~>ti-qdS$gd69>1({-7q0If95ZbZwQzTcDZ;S zc2TdryAT+a4ZIy7rf-V{hInw{=ZuR&6&zGKIE6b&WL91dRro_3Xq{jBCFb7@$>=3y z^lCsC2o+~K@Hr#QWPI|bsyPOo*PjeFB!l5Ur~;Vh!1DecC|1`w#dJc~Q=w7ea|;Y= zPH2i>F9f7CPE||r7q$|x3{1y^&!yRWe9pz_)*nKqj?J z-tG%EsPUpVX;FKALMkMT1#-A&;h;XdgWmv@!F%XnT|c|k&eA-jjNMODNvviXJXI^9zzkroI9a*<7}SQw9snUH9g2a z(y#*uftnv^XC!i%AbF_fTMph`Q;bZcyQOCP zoUJvlnL4SPnWB&66}<>9Cpx=r4eJR}z}!46*uQR}J!5SM^@O*RW~6VkQ}$|`o)BFO z_Zgr%LQF%Z9r7}Y`NC5pEBH!=PbB+SjZY${#SNJm89G$|-%F+Gr{M^-U$Uw8vZdAj z?>FD-(h>QRg8M>%bzxy#_nI)SC6#*$wvC_)R)1uwT9w*FMS~)JCTZ(>Q?&&+RE(&8 z!(5%y3+tl%!B4ILY(S&MqT<-WVk|`#;%@Zic1a)HuEvS2P~MVlytx(D}ORA#Gyu4j7n4efm{y{An)Sc7B1P zplA~~b1cun2}%}2!oxZ78Ux~9kcdax0&C1u$@O{7K7l>fV-&}ohaeX8#quI9I!qUH zm%Y%YR8FKv3P0~HgM5b<(8W{l!LpD{vC3ZNxgHk+E81>Xr;<+g9smjJm3bFWMQ+Q) zu;!Hg!-V*C#j%o$!sX{NgZr)f^%K*!xBtXeFZ?(nu^9LyKGQf@oT^LvqDl2p+zTMG zfm1vsMXg1nv=AJ{ck zVqK7skfME~#i@WVR#mF|^Y~`p`Tgym#UvcQ7)fvXoItH1 zz?C@GxRsWC%2VPCOW%~ina%WlNnv>(ls`eA!1bY<<+F;Fa# z$fBlhW98sfNmQTqukcU6YJ z8|=ux1euhVm;QizuZ|nM64~yumGs>+NBVn+MBme2*tSfpY^67Fe!(uj@9}!#VA&)8 zq%{)*mu)$!L{<*|F!m7(-O#IYO~m4+j||Ha8r$yFJ=R)y0+^2Kd<%EP;h`k2l&X1Q z$jdfl(R#0fRL!9RW3JrRUK9gH#_WA@>wCNuFA3nXtqyyP?Ie26kxqV8HkFcl=}skw zn-9)+>DU5lD1%qu9HxV5Yf$MvVEE;5!E?>cHt4R?aBQIXaB^k1vScX3@Q0BPJgx~` z!e0m z@s)korHL&S=QSUCE|by_mf2tNoce$N*bV~5W-oqn@@0$lJ`R~FX7zthAy?6zpFUi1 z6)utnlj3#TMF%rC2qB$qUpmkEoqFEMp6%VrrO~Z?@Ba9?Mn%!mZNJn)2@fdLjT8q* zr*IQq$Zm0F+y3OV?>jHLzK8-E$|n-dz*>pe9^EFl#J=n~}ypCw{{iKzYzc zJgbg!D;`1#gL8#vzG}ul98cChUn)1Naow9L2a>gEgc`z!V7hSp z->o9>m&~{Np|9oX8?XhO^|5~Jn6=TgllxL+wdkAUd36;)|4;facdVy0NAt_oML4t2 zk;oO^lSJwh{$Ua`>JA?CyBj^7PnaDTS}{yG4;Rv$49Nk|MpzVWh3*7Q(aJ?%PN+<1 z?g$K!na&@FfG2eS0{C8MxC8!-&F51d7JI46ghc>_4u3$O?H!smOxQXc&Xvo~ojR|7 z#^K}8L!qXOthjmGghdsz5dq{eUmJ$H7zx!cUQD$$?3V|@TmpFZG)g=MyNPom z&PGF#GK*V~%EUWFbh&v;dRmg9=DWt(sb6(J%ACGPPR$W!Y-)3J);LKc_5z-EroUK*Hqd zUVBkslgt=T&4{Xx;NIzoom7SmbZ=-Xq*$P{0(|ki^>|*fRakB7SU2y}DwO~Y=A{FR zw#l@hzlWgLSZ+aUf#H(#>Vg|2ut zOE}0WXeNwz-G%p2iF?z(T3OlM16XR;M_UdCo_~JvTPtvdgi56taE{E#=#?!wQLCn& z&I4;o4-%J#?GQuygSRc*+4jGI`rb2AXGAR6TOM>gk~F|2c=T6z-F+Ee>dXs%O3rn1amrjP%Q zeZ^huvS8#(OKZ7}4MWPs`Va_nI;e?PH^&Z+wVi<;JLbLr5YT>kD+#@F*PyZWz=svT z%7_PRCJ5^`nm*Qcj}6<{)(1vWEF-{r838*kW%Q~%Dgqj&qx2)70fK&fYnMOFC1;#m z^8lnjKJS}U8e@Ez(9h>8gXCuZr#SFnZ3NI?a2u1rpgZ-P2&vvaako#JQ!=H(aBEpl z=)S!xQoHoVnx}YZQ`Kj6I6ppb-ZqV$yj*4FwWlw2^=pSI5i0NJOXJVz#{j-|CW-GJSM&UlyR`zJp_+dSXBhgzoJV?}a{TBItNYx*8u=R-8l$rjeImYORC>H`n zZ;sT%c`NAjUlDVcM^HgQPmQ$T0#LRM%(-_?EVUxg2q}s)zG_^80AB2N=(nS{!VFMM zy7<#Xuf~E5&C}J7Xbl$(pcjV-3m!S^Cu{)`&>7k&y_xU;o-R=9wd+HYDO!ar?d}m) zZnJVlnQnF6!wsVa#%|d=u3Ft+6-Croe@m_&WU#k?5rqDB^z;T6TvRi?^tIbVv_fMfMbu%Sxc)S}dDKP1} zDfL1DFV;-R7`Wx_d=tc7;x`&#pvs7>)?h;yEz2`VtYqq{0&-<=3 zIO~OZs<<#J_0%R7TpvjjG57C!VgoIYnF!jqNDF1~q#yKack@;8z2LOTyQEdDLsQye z>B~tXCTep}_vhV!2nw2%I4YVDBG2V00iW*{djB>}IAPJ04q7OUNB_9PSWj)qKauGg zy^h={{WE8wViHsscSB6n&g+KncZ&CyRkekLoJDnW#0C17lMt(~^yk z7>{5->zvc+bZQGe=P3j9LMVKHS8Ew=*Nc=}mDgd9P$%Ssjq+S#h<13P`w*)5gBSN1 zZk(WWhARikT{@eSGYq=NN#)Mhei}l+ZhtZZI#stA_Uq7yNbau?JM=pjkcwBFG}fQI z=1k92(Z~OC%s*lK8UX1>lTErTXYo^SWUD+H4L-BUQ)9OA>y;R0iWt(d#NCH5Rbk(V z+EnclV-Fc0O+8XIm8mo9^r}PHn46hDFa6W+@0FQ{+_3B1<_(;i8Dp1AY1zX}4Xm#( z*RN|un%4cRc-OnT7$)?V_IZI~XKRSK!3s0EKuLWX&>h|eUo8a2QGb1i@HkU^v?_b-P*txE3YIj6Gu@wWIUXB8mZeJkh zd<*z~$P@wDN4On}N+2L~J*y$(3{2#Vefj3!--f*9ayAaG&(zJ{*r=*&<%88^zCdSk zUkWV0Vb9khNTf780AE2s8~1!y4p9I25m+JAPr(eXeX`!$;HbKdH zaTK4`$CC*n_8*+QV2_W1*P1+}p^a;0Neg=?srO>TsuJ*w*bL0#4*Y7oKS01#tW&(% z4n5q+MxBZ4R})Q7{0~w1`mfxA6*a8*r>X^c(|&XohYqEneUMuR$vrQ9QpuZUKPbGw zh1VQ`Cw`vfW%T`(Qw1w6~PTfPC|IMUKw?-^|cH3y?Upo+pRqctZkSVC% zMsAQlx#^Qd%O^7oonI3zQ&;Y+WgeWYO^nOLFngV7zQ>r=@3S|;ws;axd)XmAS$`hj zK57NN;*m@OAoniEtwjK;!+K+D-zmTv!VzLq->7+Tw6MgJT$d__hmTfp^RFZK#%T4Q zg%AnJxRMj!{(R%Ut$7<426Ixe4k(r5fWBmn3xBg4%dZBJlPnHxvju4X{D8#FE4 znDnD%bWm)o|3yF*hKfa!x#cI$3`sAIC~`HHC_mSs=D9L;GT>T!E0n3`4%?5VYL9<3 zUvXVn@#EL>;nb0wZ$&kYW^J+BPjN{3!e&M{W$0UrCavEvkVqQ($E2=C7_g4i-Ww$M zKRL(M+O%-8z@j%_-iHUtxxUY#wz)%Y@H!9k00Dp^ENBy=y~*NwFEmbY?Q6I0!i)wQ zgX-2yna#l7FPD2yMquS~&3Kq(VcPqN5uahPem;;P9rf`F(;IvZR)e%hUk9b0djyYO z{UDxC9@m}V2ZvoRN&FF%wzQuQ?B5+AeGjS!J{);47jR9_DPht!WYB)p$#=0c;ujC3 z*yuvfp+9rfE;N+8d`9#vea) z=sI=B9p+f6RX3ry@x-Mz=%R&waClLL6`3grjQY>=Au3d72@n+h?w2UE1h)UYG9w3q zWSmX{H1LGbu+MUTg-8_*7kXaFt8e7lRzM$r)MUn9HrD(4Yr#WrXg{!`qKBUEmDS#C znleG?(ga;-?l)t$@B@|2+5Gk#sCRLT>-2{=Mo0%qE<3>W4 zG}#ks&+6LlJYVLy^3ct=BM$>+f}!8wu2_zL{hh|e`jSd%j50r87NA0v*e>+{Ob-YnbxF^X7@sFJuJp}vw;<7kJ>7KEY`muN~RL>@r5}9yU z>5e%eFWI0}lcW`bpl(oncO6NCmrX(9viFlQ?&KiXb1|!7IcUWrv6vsMvg(e#KIB}d z+7;Vy0K^6jt7z^hZ#ZIYIau^6G?0UOmjNL#{2!&2ZL)uI z<`XH?9XL0zg)O*FFjgVfsqx)PZU2~!-v})o0<6dpvCQM*7-i(YNq7^AD+ulMDF~=# zL(nj@lC->kVy2NEej?5(Z3QZ$FU1kp`R55wC$%E(>@lShs0VWEHbboLl zJfWdXD~kUhu}Ye2k0!$ACHK z+4Ip0^1#+&B4OXxp2Y5*T=b#IBX#(7MRQ`cOB}a0GS)yC<>694Iuj z7w{|21$o;vR)1vIti($PjvXe?emgRv*FyPS(?&(DDWpmh@?spkjKY+HrnbSxF$8*> z-hhC~^?2B`h#}z!IDxkBX=c|AB*9kAK}AJNm#TX&;K7#-*C(Rh z)AN$Lyg2JI3k7F9X!n9QMkWkEc_Hu=LPPGnpSdCMvzIrS`vRI?l>gwdzqy$F?SM;y zm6wU)Mz~6jQ+Dq~1XiA6444hSG4)DnL0*B50okMdcrd68ra#{r+7u7EE$7F1vNf$( z1*_Do<*K;+h%RQ|9>>0>EuWLS8STf0Z3xm96ORF}!EFOb zT6o&>0Oc}}l;bZI@%Vw=jnBEE|s$p|qjiL%auxUjTLMjQ;rp#3&X7g-Rh z;(P$>YzX_Lb=a)XFWQ5NXw=L@9)DYwsP5BL&NHh${L$spFN3f~r7?brf39!YT*lKYVA5GrM1mMdye}Fo` zOBoh>fL*4)+fOG40caO^VoZJvSeN)L@gTK!L(Q|qz`0KzQfl#MdnTNwa28UVu(vjQ z>befq&?6>fz`Q7&(WaBO+^h5cS>5-m5r|PjrkqOs?Z%L+F!^{u?asuPfc^>i4{1ZY zl_|qSgH?wEs{z(=oFhA$Bx$xhFkP)AgDdX7{c|&NF+BN*L?N8bIvEi4zrf4u_UKs$ zA!za(Wmt{AjDop?+p{$XvFTshJCb3|5OlrA^#XLMHC0p3`%%m*0htlGYtV(dxSpc_ z`Bi$SC{tfZ-H+gE$PBZk>WVRkhI|KA(3j#9GbN<``EW7vuiR-%(FUy9R|23wlTWVP zK?vy_zm&e*2AJ?uu-K6sY9BOSpT%x@1+}0w3QgrG`ckyIzjw&P&=La9!{^Ad2GdOi z@EQu13`#$?6pT%UCNp?IUz@UY|M;6O_GEZi?eUC5JBT%pz3L{-QRyvXYml<@({1!m zm=_ywyJWF-8-)*V)$K7jwKbm0zU!N`FUhvYPznAwKOyAY(&tqu78W8-u4fqTi>Vc< zz~b@b+b>$B&wTG;(8qZC5*Q2ut^9FBR0$n9jcJ+*G2Bu;_qR&UHRsIwjzZ%J9_iy} z%Ywv|&U{Ty>{?*jcTXa*nR>EO`*omdQ#rg4%s=Ofsh&g*2(%9!4F33WcW{;PIT&pd zuN+0Ff*qYLhapm+zh^`7sexS%h$j~u9lwQ>5jjG8{NM2$CD4A1c~u+6sQL=imz~w1 z3e<4dU4&4)4~lN-4GCRwd{AKM2+6nA=^Lf@l!p5aM8KIngv z+(NH>JbsC#emfT%b}^egc)?+)u!TLXMd;FR)6zUO6Ap#Tg!&%@(@4 zn^=mH&OIR_z(S3hV}N3qkynN`I}cp#Enb!isgl^5OQY8pV6MS|7hG|mDV4TZp0CCJ z^}C(`FPpIJh;%VQ_kNw%5dK^cN68ykpHPc+P;+ct3>2s8@tEo2R8wic_p_4# z5k#Ya(uty@0v4(w!~{n{!GZ-4kTTLi+JF=pNW_^@k!Ar6Dr5vi1VIG`1T;9lBPd|# zyddorhoaI98cLFV{@b&@yYJ$=`7X{{=f>p<+1WeKe)hlpN@f^nCqQeWobqwoH0S=-JKDl}hpe zyVUCkmT))`bl|d&nczJgd|#=f8Qn{&LZf4KjK6da;tSb>OuI_jJ$>wzmj*VZw1!pl z{3mf5pd_hIT_+C9o>QXJ6-W+QhS2+6RQ_wPgY%rz669ctX;Q+*@o+Wz9bo}@3t$l0 z+uTvNxi&V`evgzFq%nf~!|jP_R{gZ>T7nUZJXdEc6p@I306Wm5D?sb)SElMfqc?*> zYN&td*jrNVAD~@BLo-mIv_8;$EvRF4H8BL2Rfkf?j3gF%JbjCWQX}?T&l-sAu6a9- zo<~78EEfs$FJ#Itq^56?yAIP*8e+iy+~J}obkF$H1JlkbjP@o?#ylaE)xF}v*0bQ? z0OAwCAeNE*U7u!vjx&s2$7SBZMc$ad)sTdd`_%wu%#`>uEOKglad@fiJE!M(@fDkt zGb4;BRb0y<$MpxvpbW}3V66wHpoi;Tt2L}%jUl6tO(C2%2DT2qCwlfLhP7buO^#gM zxiHfx-cPgF(jN!~qJ3|+VZv4vWBpIhsp=#bFe~A`n(9$ABw@V?5c8=#7HbL{dbY-% zLV7g?aj&v^@wu~GeP6%qMi|X$>OL&f8J*QrtkNJkzOtp=Pj%1*RjPLrlSI05;5-Fu z8?jhL3Cm@Nna-fkB=2)^a4HJE9xUZL$UD56QGdwU47ovlqEz{yC}Q zr|+2xDDSi!qst=u_7YyVV{{xZov1_n>-m9tp1;66Wb7Ed&a1khT$k($*-b4}8CAXC zUD6P;3e~oww%Q$TKa4(Iwvn=r3)4_gqo=qnXqmEiE{q}o)vFi(l{{wJ-LFkJzLeV< zHRz$~-GJsWSCC#UdsfX@G4{X&S3}dqf2Wl^tkj$EBX$K9#gy1hmgS>uyE(^c`>Z%M zq2DX>6STT`*|tblw8swJK=EwW#}J)$V0-iM>e9CM*N_@~*K#=3-L3&rkSsY4#U*_* z0a`I+N^R8Q2qSyXT3O2xK?S$wi2W<^Qy?pRcCofT&zVdJbL;~qCr*U|$iY}Ee%8Fz z;H4hWJk~qjnwA&32a5*;p1wUfK2qS-Lv^GD^Cri|a#CHxsFEqW+91qv_^~S&O$vGS zgZ;QwDH!DRW*E*LSmo)v?x{TG?zTriYHhQhF+Wk$y|+!;)wZ(;rQeco*djNp3`J<2 z9;y7f((QSGw#)m0Lg_;)KMU8iJdvPmJp2+V=JIn$SkV*G2LH}j(e1d`pUX5PVIzV~ zPvm)^614?Tu`02O>K&W35h{!xg$MG$#WdN*CWg;E2Y2#V1L-(ul_b>%B}(9+fGle! z99V$s_g>$#3Kb_-1qyF)Y(djZ*(b=5Crw4P^mkj)a^YreIz`b}^vj>gNmsm|(4Q)F z|7oG;3cp|(NV15MZn+%leL9lhr($m{2vsJTaMC>=)|8&7!`$D$_22)AYi7mu9cyAL zn7WN<#^!LnnrPK$cv^&&+7cnx`6SaM(7D2AzoZ!kXKY66R0B>t$m%G0t0hzXXaVu31 z`k~&)XtDOOo2CM#X0TN{v>jT9i@TBOkDD}rSie~btSk<#K+^{f$JFg1Q$qU_H~cu% zJmk;Iomk$|A8loS1@56<9jJHsr{jz`yQSAg5qHIGJUygb=7x&l5H;a3F%TN7W z;}72GZCr7G{3?nHJkAqW5BwFhHk6FyvBTSN7}2DxJGva83U?v2f~ParW^Jw-tdWPW zba3G$LM;~{O;22HT6=0;Sv$6YW7TQ6CsjA7r3ho)viX)*$RJRT6+%(+pNxq2+GsjS z`*PGTM_mJIKr@y-JTY1H-4JZY?lRw_0ona6kE$6*uDJ(@0himn-~GPvG9;m zF^Q8rZ!^;Ou!HaOrBVvex5P?wX>w_!mcHD^whoAbb~#GPIt!kVpMlvBnCj=F&i-xQ zHMo()TGy$$sbu&-KU_Q1CUsuY+cO>u|GA)ZoHk@c^3viI>4Tdk|bWjJ{;?Wq{~ypF27$5e?$sgj7}LJ&bt@P55my2oGXsN82h6oP`d3@EoPzb+5&nuD(7BNf-0t%)^@M|TaV2>zr!N$Aid3EZAZU(Tu}Qh>8gNzLC<70QveyR>y2E zNTGELlh`32*cv!tPO%50XTb!Xl@4=^m}Flf;aP68 z$5v)xj<@&~>>AM--{zV(bAG_Y+h{}L?6Y!(ko$&U95xU@_!b$?K0}`0YSw;{SIwg4 z_xoYFJ8Sn;bN#l<@O@InEVZ33O0E=s>s=|Lp6^r()@1}Q~^k5E+yQ-;$BG0js2 zvtC25rqFZ~#(bdE(N#8G06R8Dh9~!rQA4%%+otTdEq=T;3R|;Y+qd61D$PLY(4y=A zM4*LbFf(=xjfb)I@Kp7p8udHyxErc?W8s)K`D<2!4DvRXEmKnn+|pyt23h4e#M5cS zah%V=IN_VpnlwbrRNpxx0-vehl3Iofz_lDR?_Le}36>TdUy9cd^w`ukf0}4YFIlWiu-w#?CXH};`%77$zzfX@9Vc_jXCOzxV7ZqH z+Dv_jk5lJ(vJfBLoG!2>78Y$erhe)ch#vUGVPvb?#Oqf%rj8*Sm;voM?TFPoFL0Fz zf=TQ8l`oP}Fn->aR<&zzGJ%??4*i8$YvlClED$9+fv^YeL+o$!rHbdICPCE?5g)nP~EnJeT(-R7|%Qd4!OUQUlh!@)H~r)@uvF=A6D?qK8lLR1zG&n zsQ5_ODwOvvVj}l%wmWJGq2xi9h$9sPw;%BccKc?iw5aH_5xekCk-}p3T@^xJ=#Fke zenjc=sNxx(Ph#nl?O}M2ex*CY>5k+Bl0sLiv02o*$M*_Ff*^r9-)8}g8J<|<16otH zsO}+ZvBtNfWc;i(?xM)?W1>be!w%o}<$`+&)DZV!)($xA5q5_?q}u{6I5-tIhpl}# zU|YXFMG7`&owzb3?O};M+ z27_CK0#aRmGQsm8#fqNhjn>tO)5s`jfLT3%7&MWD(_^>jZcSPu&$nW`Cs&Us)S*$q zCryDc)SIcQ}U1MnW+Tcxhb9zP#@%fN5}%6zu;T zc3rR9ci`H9PR{5ZSs22r1mNw>IjOdO&?91*m(s=q0*?bQLZBn*Z;cttm63_CTV8s9 zJ}y(@JY=E(91?q_@6Y|ljQR$KZwU91EH*HXDQMO@U*1q40hY-M>cv#=+~0j)8!r@~ zAvdW_3Lo;psKQr7o3(&QcN`!Eu68=2XH`sRF%b_sHd@^?k1lbUzH#|KY!iu&GoEA0 z-ZTO6M2(aaRI5NNsSlve@VCrpmu|lfa*adF@+T z7T}2`Q&P#4L>RNBbnu0E%~&+JygpqwoZZt`Z`@ld%h}H5%OXd0HdtuL0B^K*;sf%$ zFh4l@t9Fs!yhn$aaZBQ#GweS%EE4aE?w2T17*Kj_><&QyatmA%Nh8{J+#;Q90t;x@uk(tFef!jn+krs|S1{3De~}Yj;1RP|yMHwo`XQE`#FZ;kbbo5| zNRDZ_0l14voYC~5CHum7jDekRTOZ}mCscn(dpB1f;m1^EQV6o?iMt@3=puRBNOE93 zbDf)Mn|BN)?>nEk)WExW&?w<-tv0Q#*Y@()^4(0qL3WjfxDGb!V@Ogf!$&MEWIR)* z2Q$CHfYa>G9WBKO-|L1z2?o4x@g{>yd-$+yq;#{StWA3Ku@1$GP|Y=Z=MR-d%mG(z zr1V8ydlUcEhn)TAZRBPAmJR@^&O9`&eDvpfAGPPlhy3!z3=6z9LRV{1;(SIVL!%I< zHEM=ZYguu3sw%#L%U<`j+2L=yMarHge&Abv+w{}!_X(vHX<5?EjkA2oH`ms=iWIhG zF8d~vqE!mpQ^~adbA+E6CFzjd?ZAbea(3ifS0ibIwUvGn4xEi`Y(@EEuc6FY=X>lR zF~59r&=zkWR%PZ+?m~%l2UxBL_h*hZ`!2C`Mq4ymg1;O=Ck#JAkhM*7a*{$D6}0!f z--oHf&jr5=z|A3_-8Po9XjubJp_D?n_L(oKpL37Nwia@(-IIM1G+)tL>NmiDDK5H) z49<)Gbh8!NKr83NJ|eXFsFSD-D4eBjj|yd_!Jf<5;dAF7r2KMMjWkuu_Ze&35(O4U zninec=z()rXW~W{Dy~R_L9@e5lG4WrO0&&Jvoq?(HA@^PR34G3#j&wzOWOE?ycXk; z(qKOeGJ;K9k)|Rv({a2^CSrvHnbk2@ z*kkv*PXDWJBdPxt9k={&4~_o8AE}9baF|U1yxEtmhtZ9eZ{fy{n3v@yfWVjtkjGFu zu!E$!u)mgrktMO^bPB<&`;v+rrcAlJs2IsYG92#Os$`R68B0rcDs1zTy8JZw`M!s7 zfZ>)?!v>JA&lH_z$r zjlW`^5d5#LDGTFYx4GN(4_f^9Mb>{B&0$LqY62J1{_$N;n}eL|Qhu|S@4&9L!NIjZ z1ROtYMSv;8ms97SWr&B>6Po9q)zgC;Ow{q1=9Zv`i#4)V_8?Y?`E3Vvx=ZGB-${Q$ zPRsW$))rQdynKdOipO`pq9X6i83)3%Art5!^s#VQo}fG*%m_U?r3W`fB~Iw%FcYW9 zbWw)ft(tOtXZD+vUmkSLIHfF1)p$g)`E;s0l+>kHugly=oI7!peL|>$As(o`>|Q#c zd)TKI3i3@!O=lAVYTRFoEAH+JDcAY$3hX-U5bpthL&Dg(}+<}D?&usV^$t6j)YulcL@<=Uc#)5_kR#{Rq& z=V*t0_7(nYPOrCd;FZ}<8*OJrdl1kZ$t@TnLwm9Ktwb66^s;GBMRrDPwPf@yOKEo)quN0>qJ{m8(#%=-~v( zfYnk@EI=m}X1NW0n*OWMI=U)WS0i<^75b$x!For$2o%^|U0L0glRtX)S8WKC9{rDp zNF*A3b;<@UUu%Z#3I%PE?TgAES0=eF=*bdLk#v>D@k?0`z8ZzU^ea?s*S zWfSh`{XyU`VAnY}up)u*(PF1TV?3)4=RQ;21T>QEijRUmdF-tJBm<}`bk7BCxY0at z=@+8kH|4w)*>r!7YoE*5B$XddQ=SdqB>wz$2|UT&kmgFS$)KZ~>-11d_EZn)W*17< z{k(!yWzOFDakPp3>m{!F{GvE0O@~=f{5VM612>CWUKzC{xJ=o57zL|T0?hhzO%)>3ieEh37_9O^r02N5KUv*z2C9IO1`QS^%+Fc=x zUqpVg;A?O7-73U6T5i@CIoi6a(c#q2n7mA%rZn~XPB5vz3@!v08;liMDX|-r!)UB2 z#chemNrUxAoy*$oj*Y~R0j!75*z=)q$;AOB0OVWZ$bim(HfM1_!Uae9c*09!#e^(p zO!#yGg-pdRp@wYa@vlWa{Uo&nKYn5|2woeD4>+GZf}-oR)Hn^F!iy+*>e#ZSc?W~5 znajc4e>E&@3)5th76VGrq9Hd};e(1`P?G+H(v}+4pCbcTsz8`)=zxsd7{&-PD$$x9 zQnQegWk0Wmh)Hh%AqUUus4VYe)Qdi^b&b z=VoG|5>5QRkJ9No6a-Pn`J5wixB>0>RSc>$!;n8)jD=Eo6oaFIAzRtm% zEc~mqf0P2h6QKv40{urAywh1mFqHI^!WoDy##$C$M7Yy*-S@D=VK4+M53nQW_Isf8 z>OkY7d%SOiJy-yR2z5cVVmtcz4bX5_S_MbsgwFya8(#G%UVWgB#)ilGm`9=Lu+jmn ziiI^Jw0*B|NQxI zcH*A2B*5pxH%{b9;8Wl&6stuz0Nz4P{04ce`}5 zW4Qt9MjSE=N%8-3D0~!+G678m96+b`5_f=jsoVp;Ch^k$^ELcW{-6*E@e>_`-5m(+#uW@Bdj11Lgny&;P$gc!kTf<&Vmgx?Z{kqfxYf@1Z>s4|d#kIe>sD7+S69`I+qH9> z>U^#F0032*vx6G|7zkp3vJ!j=!dry^=P`0P2?i9YDqzAm+$qO5mER z4Iq=HZNohxCKhe@vvQ$yriVpu#MQLvU#{;P;DwBu{qaw zut>m?Cybx@`HdqM)vL(v_ov*Dtp~(|W!T=XI@u0>HvuG?NWjKT3xT&`mnyo_Obktv zH+&p8c+MsP;PkN$#8%I0BOvki7es^csL32SJ^u~pwEO_!an-#kn-t{3n=RiVTTPZ0 z#?{NSDLOGNMiso)1WBbKES(DW&+aVno!baL3r8XYVu5+`3S7;0f#R+|F*R@SKvMzC zTfZ4x$u;}K5(At*G=Qt7I{t(-p2~GRK)Dyy(NdMypV;@=PxU&|h-7M0yehgH*W2yr zGg5rw!J)rrBBn#5z|&4G5T?fAB_y}6X|HU$U)F&cdtZPA4S>YzoVz_IH%~@5Fn{{< zCksW&ME8$~e&)UWZ1PO32>0geL>b66Q+DHj3{P^56MwE*e@BTbIJYXPbx(2e^=#or zYQ%l4cceuV{WCYF!0OCon^#hshpKBdJ}0{o(LeFPC49IKOi45WHzp(h*{#!461pV3 zCU`%tMzD=mac{hx&0af6!5x)o0e`dov}Y~d*D(Lg%GodGKS!F@0mDl;TfRSV;LIBJ ztA%GH`m1bvBdQhkS6S)*2?y{Qh&azziJe@)0M73^I@{^k;km0E-T*5j|N7cfl8~Ha zb|BK=NW!PT3Oq4Vj174pZ3FaNbsWv6eRD9nNj!)et?xN&iuBLUNAn+I-hI{qW?Zwg zv`9kGEkwUibbIpTs2KBUZYi4o6)W9j1#VO5qNw!N$QpWO59WZ#KfQI2f#y```13NX z_bQ>|8d{jvi#KISzPfsVYvxHa`Kam3iau2kNQ%(af|&g@ldjC*V#U%x>j)x9|M1kd zC;4U=*QG1K92gF5Sr91`pQ!%{Hcl8`m+emfd+}f22fN!(^&QMosu8r!FmDL20$WB&glqEEu;i|uj zRm4q&!ttkU*6quamrv_Tm)3OmWaU2?^%87fa;AfL1<|KjcT@(EBK#wi&2R64qObD^ z*@li`{pVS}%C<}qFBgNiukV_wqgkc6b;7z; zwDlc209V||QR|KG(JF5r7(HwwGwWh)Zp$bVMf2HqlhW>~$$r@vP(JY}D##x+IC6}Q z3Xgjb8mbaxh1j`Q?l>3Ki`#zI-x_%{5qwZG`LeBGu5WJYK(r}&z*k)CW!wl-^RZsK zz^UdPBcfO9%Mci#D&DzZ_R{O%%oC1SXfL&_}~( zHfn)DoeG~^wr%>}dyOhi=N60|fCdpr?79Xk2|rY@zB8vqog9flxfT3$B5!4qVP-%q zGIm@+AlQe}XJ+0m2;y0I;L*8|Q$m?Rxp)b>p6j}pX7c5$nf8no*zjk~C-p=o{z^)1 zTEa}xU(<6lp(*#?fLP`kXSbrAs;GfM?|Y9L<=ARjR?3S-GjGMADGg9<>mo0son9sE z@cSDvxqMma>zsGsKqrnuit;Xlq2%(G^pj;Fb$&mKdh3cyk&LDu$K_bPO? zz{s%v&ykxq6CWO~P)j*?nZ=n}P+|?Fo;=gTVix&l4j#}VTet~hT$Rvk?7#JhSOQC_ zvle{TMp&k20|O@0I&4g^)bvo3kTlb|>iA?-4Odg#5fus^bbi`OVwvB;QKxXTf2A1E zeA}Pp@Fi7MGw#E|t86e2JF{b7e|vNYXJ;TluR>2ZNoyr6LU-`t8r^V zf~@H^$zdGXwf)zu6O)*OK?)8j7fL(I#IjOy@$SXipwxkrYFJp7EIwaT@tCtTrw%EZ zmiaSw<{4XplX1)iG`HeRE#U4%9ktMq*d%_N26v5wb$P*FwxsHKX=lOi)$73_ZDew$ zN_@aIddbnnSvcx%T(j#2>cTH9Q(>^#mjQoC!wq|VyAPj~uWp_^6ad{iuty-f-^0dM zSb>uX%ojS0-Zp7>C1js8`#74wS)gzCDXkdOss~Crx=@|_jOXWW>H*h{B5&L2Ezf2v z_+1ZI2G)Lz7eA1F*N?4kgvVJMxNobuR(7X);n*97%~g0O)?dRNhC>&WoR+CRxR2}-GO9%fTq$E**NJtSz>QL*hFTls zgl{(RF5-!P3|4D zbxUOX(FD%kqB)h-$AAtI;m&ZI7@4t1b1HA8l$%^#?jXuG!<|y?Ju_Zph%0P&I9*JWQiQb`m@ARuoyi@+X=i9eHU|au7 z-Xr(X1X=rQxzU|7iO8My@L+CW4ALCSt36WOPGHeOI&^`N6V-|+COz9WedRUNSCjwZMj&wNxdyWPfr^rdXd=5!p={D?<@mv4VV9Sg5S zeVt1WPhY%b<`?5Q&CMah2b?t)DjL%k?7o6D|HUKxWoW0{56RHST_D*PoHhKH3>TC3 zFL{axbmN%ITt2kyPC=UE{zZmp0@Va26nU)mcVK1u?;JMLe3wUH8#?|I49)AvQ8pTi zi-pEqA*?f|mFB4`Q4MYr90e+S(oZ@>;ZY?Aj=53c{wMW7SwnIjW*uA{w)Y6vYzNIm zmthI?mP)o{<}c(@u)X$Nvjene+I{9g4{~PaS6b*{dl7k#ut~dbogBI4N`78i3^IoD z2u}=k<2>=`yIgv6`je$Zu*8v5V{}4u2Oiy$OK(koLTJzgijJIeqZ6wSSc3WS%yZj6 z>@PV67B7`7&&rP|sRMWQg^FghJ^vpn?$yQSoaYg$3}5JBbJBSPPJX8x%H?6HZtr`Q z<+xvvu^ie;b*t{h%b^QYw}-uJhh7zq(+zxLqSx_Z-qhcE!sjEn7CkEKZQl z5J0sPb#@S|s0lJ1NQ={dTC1X2ORMJtBTc!mL+S4X>jAlA&IDx0jm7Y$nY5h&4opSh zvJT8oAeR_s`ahf4Cfh>2`$BHQ=JdS}Pk`AklsvfBSUGv5#|+HIiga=?a&uz0yH(`k zKS?>8jv z#r2LoN>iG)FF^T;$VW~j;xqTSgeoM*?ZqNInvkomoLOH-U#l2)GeS)h5a0=X)?Jt| zn`v!8wLV{Ydytzpn1KY@3zq%Wx3B6m-&4#8q57WByV7){Xt1Sw@*ODG7v?lG3(SQ4 zZtlkg-pYP@KUPV^mcTTYY%Hk$KpCM#kI!TRFXd$8aa#{^4<1#?RmY!lpQsndm`+=B zj5HXmRU0~U?-KJUGWq!U^=j=W>5dY9V}$_E$aO$K?;yokAqy|&PbKOy zZp?O9-Re|a^gi!E<4)=Pb4cn2Vft5#4PbsO`9S1Iv*WYc1leB;dhdf(d8BK4zSta; zSNnTL{9%DvGU`TZR&KTx1`R!u@MbLA`M%w$a^gKTG={UV*8{n}t;5vr_T#pQVxgo# zdm*Dd(QOI|I-=mIi%~*v`*rlcp7NPK)LoJBR2Xdz0^-S@PQz))ShKP@5X3+AVtP{c zAFkKVYPrJJR;p3u#?yB0m%HPumR}a3uhBRZaz2~8pttY(}`=_9D#EnzO74X_gqK#pX zq!FH3#p4MC+5UfJ;TmE;KG!E(VDO+S_jCfG`|InF(bbH{2^=I9UPkLqmYT(uvb<`B ztg+s2{94-mqepTX-w6vFVWCW2&c!v{wL5KZJN9e?IBV1XWLmg6a=d$!u~T?5QSQZ- zdDE?RaIxV>@kEevcCdsjdF1#0QUZj4u&ZYAD3M>K*L-5G$c1ihHQ=k=N>yU29F9; z3X#JP*#OIQ|*j^*yt`AqcB-8eSLCR6ipZg1nL38!G6!)aTBlRC( zfZ|=C`5XsNTAG@qs(|bxThG4&GmuMo^m8^5B;I)!{OHIkd1nAA(9r}IYjG%B672AF z(77EkCom42_~9vLpJI-)3M~N9{q`VD2>THSR&6ttHvJBQ+^-D6#G~e%d}`A%u&cwk zNgbPW4!b?#%y@B6F-s;dK&c0u zQVQvUl~YuMDtbvCXj%3tcvO{(i_d4C*V6>rx$28)48T78I*QJ+Vv}lxzJaE2WtBDtope=Gm zJzMIS(*55kdR8Vgwc&Vl(Jx+M#cWfth&=-vQ6jxv(;bxM*xnpot&7_isi)Z2p!cUp zZ}&h4*#z5*@-DczRWPdslu8~)ES7DN&HI{mtg`AL5#St12h!g?5ogOLzR`z00EHB& ze=xuoE(+nsT**Hp(L%I)WM*^4xa2jpr*y6%B?iKh^u15#ilyw7b6Nx$J7u1#B?xsO z$#+g1CiSO_ttslt$H9YGa*m;0+hu};Lej=)0u^pdTK>(cfQuceNr?F(c{jy_?^Le( zg$Or`E>1twR=FTx>jtHoI|-cp(Ba?sY}WB19959ul|C@#2WO>0-%&Z79{KdSszKM& zG?-aEA2aHI80KGgB%@=4CG=h<(AYsxc6xW&qt&MoYB+`== zQy`YC0rP-E8KIDZYZM|HwVB#Q7wJ z7>A6l6?&(&2AD6$dan?sLRT<1!Fn$fmZY`rG`EyPHU9xQjZF3#h=a?;6}2^ZbO~SW z0uM_YE@->NG7$z>L~DC{E7imoZPgc=r$_oRzp}d%fnSQ;U+fL1n#C_EBv6$Mh?)Ol zO1md~rF2&NI6$G@aE>GgH+9$%YYE{zMu6N497!$^Q!yL10q8H1yU_*O@G>kA9sxRw z8RoR2nZYPo0*uSxlm*zoM*Tc*EA3>}N4j{>SJrHW^*%C6jU`{r|KuU5V?9PRBy+mY zR8td?u{pl4n2Sqo_4nWidjJ|*06SK=WUDPKQ|T?K5*0dv{?LAZ&bVtJXO=$_O0`r% z^F69Ww`7qOv#c&P=M|%HxBkfAt@U+A8j}cX@vIvx8B6*)TL~Qd9XLPhrRhETO@#5ARDM$cCcP z2B=aYM*140W~}ICAA#N~Kkg_CqEBF?ngLLZd3t+5#9``wxUvlUQ1}$sJHcnGy&Q8E z!K%U7T)x7m za*~2A{BEm5&0{$DO#xP1sf*hMk&4;SF!Sfd!e^wdKI~@>Jwiia8&GeaUjiJYp-=If z<$i5Tlc_+5(Kq@z*_Q`ba6RKB=$!K{*d6I5xHG^y-0y=lYDI$Qopu*<)%^?4)~hfZ zi!oqSiwQC`7~b2B4oMq0!q?dA$HC3TjM(k&QNysddV~(Z6w;A2;8u@}u9N2+eg#Xh z?Y{Ets@G+$;*%y|Y%FZfJfzIKfb2RT_`2E=CxB_Np-|&WE=K4L_AX{iIdlF~Z-0f(5YJLgnC5eI+zfop%G#_@@kMjiSN~lw0%YT(ZVUWR#Ruy$cq zKilMjo9V>p`% zt298#vcdN9-6dY)`v1Uw!PSW4IpTw?#st}Sp5nu4$2s0$NPlp}eSrwu?gJFw+5)Dj ziMsSCXYDrA01wJQVZ!*A6!&4DXJfUW<(gCH5+a_gBsXRANjYnwUUTp&eMJPW!95_1 z2+qM@6A}wyZu@qS0dG@7;kWik0Lxb^W8s#LlfR%hWeT2R`t8aax zzs3R;%XwH>6!^UnhbbpkgKapfmlAdB09`S*8dT0=6B~hFLo6LR(DzbZ1J?5m&RBSa zVb~px0H7I%&9S$WhG78Qi{_le@Xv+B5@>yz03Zh%tQNK&fS?KZ9k}141myPY8{lmA zR`KbrU8m%&oII)a#1cTQUVU}uI|x9~IF6X$Q)RFl+dI-1c@N<03cJ%{k=}3W<||f4 zZOety`%XU(8+ePDtEz!lec>dBq6Cha44e3Nh@85RAhn^0aKtPFrg_0O0DLTgk`Dl| z05sPpu)s7CU@HYGGFv0y7Sz7-@6Z=v Date: Tue, 3 Apr 2012 16:33:06 -0700 Subject: [PATCH 188/238] revert unintended change to sqlite in a7e150a59319831 --- plugins/input/sqlite/sqlite_datasource.cpp | 29 ++++++++-------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/plugins/input/sqlite/sqlite_datasource.cpp b/plugins/input/sqlite/sqlite_datasource.cpp index 57569cfb5..b14cf1dbc 100644 --- a/plugins/input/sqlite/sqlite_datasource.cpp +++ b/plugins/input/sqlite/sqlite_datasource.cpp @@ -548,33 +548,24 @@ featureset_ptr sqlite_datasource::features(query const& q) const std::ostringstream s; mapnik::context_ptr ctx = boost::make_shared(); - std::set const& props = q.property_names(); - std::set::const_iterator pos = props.begin(); - std::set::const_iterator end = props.end(); s << "SELECT " << geometry_field_; if (!key_field_.empty()) { s << "," << key_field_; ctx->push(key_field_); - for ( ;pos != end;++pos) - { - if (*pos != key_field_) - { - s << ",[" << *pos << "]"; - ctx->push(*pos); - } - } - } - else - { - for ( ;pos != end;++pos) - { - s << ",[" << *pos << "]"; - ctx->push(*pos); - } } + std::set const& props = q.property_names(); + std::set::const_iterator pos = props.begin(); + std::set::const_iterator end = props.end(); + for ( ;pos != end;++pos) + { + // TODO - should we restrict duplicate key query? + //if (*pos != key_field_) + s << ",[" << *pos << "]"; + ctx->push(*pos); + } s << " FROM "; std::string query(table_); From 40e8ceca55c40be9c828a0bbf9f1663a154252ee Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Tue, 3 Apr 2012 16:49:05 -0700 Subject: [PATCH 189/238] add missing set_color_to_alpha impl - TODO - add advanced algo that supports tolerance - refs #1018 --- src/graphics.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/graphics.cpp b/src/graphics.cpp index 4b276562a..0f0a708c4 100644 --- a/src/graphics.cpp +++ b/src/graphics.cpp @@ -25,6 +25,7 @@ #include #include #include +#include // cairo #ifdef HAVE_CAIRO @@ -117,9 +118,23 @@ void image_32::set_grayscale_to_alpha() } } -void image_32::set_color_to_alpha(const color& /*c*/) +void image_32::set_color_to_alpha(const color& c) { - // TODO - function to set all pixels to a % alpha based on distance to a given color + for (unsigned y = 0; y < height_; ++y) + { + unsigned int* row_from = data_.getRow(y); + for (unsigned x = 0; x < width_; ++x) + { + unsigned rgba = row_from[x]; + unsigned r = rgba & 0xff; + unsigned g = (rgba >> 8 ) & 0xff; + unsigned b = (rgba >> 16) & 0xff; + if (r == c.red() && g == c.green() && b == c.blue()) + { + row_from[x] = 0; + } + } + } } void image_32::set_alpha(float opacity) From 8c41bdc040b285b8e45b033db11f04101fed4713 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 4 Apr 2012 10:30:37 -0700 Subject: [PATCH 190/238] remove unused variables --- src/cairo_renderer.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/cairo_renderer.cpp b/src/cairo_renderer.cpp index a87f48749..9a15e055d 100644 --- a/src/cairo_renderer.cpp +++ b/src/cairo_renderer.cpp @@ -1355,17 +1355,9 @@ void cairo_renderer_base::start_map_processing(Map const& map) else { color const& fill_ = sym.get_fill(); - unsigned r = fill_.red(); - unsigned g = fill_.green(); - unsigned b = fill_.blue(); - unsigned a = fill_.alpha(); stroke const& stroke_ = sym.get_stroke(); color const& col = stroke_.get_color(); double strk_width = stroke_.get_width(); - unsigned s_r=col.red(); - unsigned s_g=col.green(); - unsigned s_b=col.blue(); - unsigned s_a=col.alpha(); double w = sym.get_width(); double h = sym.get_height(); double rx = w/2.0; From d3cc8f92941dedfaff9e9daa8a388d6047b88eec Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 4 Apr 2012 11:47:20 -0700 Subject: [PATCH 191/238] update changelog with cairo markers work --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e84eb42f1..9a453f6a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,11 @@ For a complete change history, see the SVN log. - PostGIS: the primary key, for tables containing one, is now auto-detected allowing for globally unique feature id values (#804) -- Fix Markers rendering so that ellipse height/width units are pixels (previously were unintentially radii) +- Cairo: Add full rendering support for markers to match AGG renderer functionality (#1071) + +- Fix Markers rendering so that ellipse height/width units are pixels (previously were unintentially radii) (#1134) + +- Added 'ignore-placement` attribute to markers-symbolizer (#1135) - Removed PointDatasource - use more robust MemoryDatasource instead (#1032) From d29ae14104dfebdd3119e01fde50dc3d362c0f47 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 4 Apr 2012 12:07:15 -0700 Subject: [PATCH 192/238] make it possible to set the map.maximum_extent back to None/uninitialized from python --- bindings/python/mapnik_map.cpp | 19 ++++++++++++++++--- include/mapnik/map.hpp | 4 ++++ src/map.cpp | 5 +++++ tests/python_tests/object_test.py | 10 +++++++++- 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/bindings/python/mapnik_map.cpp b/bindings/python/mapnik_map.cpp index 542cc08f3..c6e2467cd 100644 --- a/bindings/python/mapnik_map.cpp +++ b/bindings/python/mapnik_map.cpp @@ -118,6 +118,7 @@ struct map_pickle_suite : boost::python::pickle_suite std::vector& (Map::*layers_nonconst)() = &Map::layers; std::vector const& (Map::*layers_const)() const = &Map::layers; mapnik::parameters& (Map::*params_nonconst)() = &Map::get_extra_parameters; +boost::optional > const& (Map::*maximum_extent_const)() const = &Map::maximum_extent; mapnik::feature_type_style find_style(mapnik::Map const& m, std::string const& name) { @@ -150,7 +151,7 @@ bool has_metawriter(mapnik::Map const& m) // returns empty shared_ptr when the metawriter isn't found, or is // of the wrong type. empty pointers make it back to Python as a None. -mapnik::metawriter_inmem_ptr find_inmem_metawriter(const mapnik::Map &m, std::string const&name) { +mapnik::metawriter_inmem_ptr find_inmem_metawriter(const mapnik::Map & m, std::string const& name) { mapnik::metawriter_ptr metawriter = m.find_metawriter(name); mapnik::metawriter_inmem_ptr inmem; @@ -192,6 +193,18 @@ mapnik::Map map_deepcopy(mapnik::Map & m, boost::python::dict memo) return result; } +// TODO - find a simplier way to set optional to uninitialized +void set_maximum_extent(mapnik::Map & m, boost::optional > const& box) +{ + if (box) + { + m.set_maximum_extent(*box); + } + else + { + m.maximum_extent().reset(); + } +} void export_map() { @@ -550,8 +563,8 @@ void export_map() ) .add_property("maximum_extent",make_function - (&Map::maximum_extent,return_value_policy()), - &Map::set_maximum_extent, + (maximum_extent_const,return_value_policy()), + &set_maximum_extent, "The maximum extent of the map.\n" "\n" "Usage:\n" diff --git a/include/mapnik/map.hpp b/include/mapnik/map.hpp index 95663360b..bfbf87edd 100644 --- a/include/mapnik/map.hpp +++ b/include/mapnik/map.hpp @@ -337,6 +337,10 @@ public: */ boost::optional > const& maximum_extent() const; + /*! \brief Get the non-const map maximum extent as box2d + */ + boost::optional > & maximum_extent(); + /*! \brief Get the map base path where paths should be relative to. */ std::string const& base_path() const; diff --git a/src/map.cpp b/src/map.cpp index e04ffd4db..ed5ba50b3 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -357,6 +357,11 @@ boost::optional > const& Map::maximum_extent() const return maximum_extent_; } +boost::optional > & Map::maximum_extent() +{ + return maximum_extent_; +} + std::string const& Map::base_path() const { return base_path_; diff --git a/tests/python_tests/object_test.py b/tests/python_tests/object_test.py index cf0920d55..109efc6dd 100644 --- a/tests/python_tests/object_test.py +++ b/tests/python_tests/object_test.py @@ -221,10 +221,19 @@ def test_map_init(): eq_(m.height, 256) eq_(m.srs, '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs') eq_(m.base, '') + eq_(m.maximum_extent, None) m = mapnik.Map(256, 256, '+proj=latlong') eq_(m.srs, '+proj=latlong') +def test_map_maximum_extent_modification(): + m = mapnik.Map(256, 256) + eq_(m.maximum_extent, None) + m.maximum_extent = mapnik.Box2d() + eq_(m.maximum_extent, mapnik.Box2d()) + m.maximum_extent = None + eq_(m.maximum_extent, None) + # Map initialization from string def test_map_init_from_string(): map_string = ''' @@ -267,7 +276,6 @@ def test_map_init_from_string(): raise RuntimeError(e) # Color initialization - @raises(Exception) # Boost.Python.ArgumentError def test_color_init_errors(): c = mapnik.Color() From 4dbadaa7cddb31f858f5256cac48ecf8d5e2ec1f Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 4 Apr 2012 12:43:32 -0700 Subject: [PATCH 193/238] ensure proper reprojection of unbuffered bbox used for geometry clipping - closes #1138 --- src/feature_style_processor.cpp | 44 ++++++--- .../mapnik-merc2merc-reprojection-render1.png | Bin 0 -> 43573 bytes .../mapnik-merc2merc-reprojection-render2.png | Bin 0 -> 43689 bytes .../mapnik-merc2wgs84-reprojection-render.png | Bin 0 -> 41304 bytes .../mapnik-wgs842merc-reprojection-render.png | Bin 0 -> 49146 bytes tests/python_tests/reprojection_test.py | 88 ++++++++++++++++++ 6 files changed, 121 insertions(+), 11 deletions(-) create mode 100644 tests/python_tests/images/support/mapnik-merc2merc-reprojection-render1.png create mode 100644 tests/python_tests/images/support/mapnik-merc2merc-reprojection-render2.png create mode 100644 tests/python_tests/images/support/mapnik-merc2wgs84-reprojection-render.png create mode 100644 tests/python_tests/images/support/mapnik-wgs842merc-reprojection-render.png create mode 100644 tests/python_tests/reprojection_test.py diff --git a/src/feature_style_processor.cpp b/src/feature_style_processor.cpp index 7cb02042b..55888d6d2 100644 --- a/src/feature_style_processor.cpp +++ b/src/feature_style_processor.cpp @@ -203,8 +203,6 @@ void feature_style_processor::apply_to_layer(layer const& lay, Proces return; } - - #if defined(RENDERING_STATS) progress_timer layer_timer(std::clog, "rendering total for layer: '" + lay.name() + "'"); #endif @@ -219,20 +217,22 @@ void feature_style_processor::apply_to_layer(layer const& lay, Proces << m_.srs() << "'\n"; #endif - box2d map_ext = m_.get_buffered_extent(); + box2d buffered_query_ext = m_.get_buffered_extent(); // buffered // clip buffered extent by maximum extent, if supplied boost::optional > const& maximum_extent = m_.maximum_extent(); if (maximum_extent) { - map_ext.clip(*maximum_extent); + buffered_query_ext.clip(*maximum_extent); } box2d layer_ext = lay.envelope(); + bool fw_success = false; // first, try intersection of map extent forward projected into layer srs - if (prj_trans.forward(map_ext, PROJ_ENVELOPE_POINTS) && map_ext.intersects(layer_ext)) + if (prj_trans.forward(buffered_query_ext, PROJ_ENVELOPE_POINTS) && buffered_query_ext.intersects(layer_ext)) { - layer_ext.clip(map_ext); + fw_success = true; + layer_ext.clip(buffered_query_ext); } // if no intersection and projections are also equal, early return else if (prj_trans.equal()) @@ -243,9 +243,9 @@ void feature_style_processor::apply_to_layer(layer const& lay, Proces return; } // next try intersection of layer extent back projected into map srs - else if (prj_trans.backward(layer_ext, PROJ_ENVELOPE_POINTS) && map_ext.intersects(layer_ext)) + else if (prj_trans.backward(layer_ext, PROJ_ENVELOPE_POINTS) && buffered_query_ext.intersects(layer_ext)) { - layer_ext.clip(map_ext); + layer_ext.clip(buffered_query_ext); // forward project layer extent back into native projection if (!prj_trans.forward(layer_ext, PROJ_ENVELOPE_POINTS)) { @@ -263,15 +263,37 @@ void feature_style_processor::apply_to_layer(layer const& lay, Proces return; } - box2d query_ext = m_.get_current_extent(); - prj_trans.forward(query_ext, PROJ_ENVELOPE_POINTS); + // if we've got this far, now prepare the unbuffered extent + // which is used as a bbox for clipping geometries + box2d query_ext = m_.get_current_extent(); // unbuffered + if (maximum_extent) { + query_ext.clip(*maximum_extent); + } + box2d layer_ext2 = lay.envelope(); + if (fw_success) + { + if (prj_trans.forward(query_ext, PROJ_ENVELOPE_POINTS)) + { + layer_ext2.clip(query_ext); + } + } + else + { + if (prj_trans.backward(layer_ext2, PROJ_ENVELOPE_POINTS)) + { + layer_ext2.clip(query_ext); + prj_trans.forward(layer_ext2, PROJ_ENVELOPE_POINTS); + } + } + + p.start_layer_processing(lay, layer_ext2); + double qw = query_ext.width()>0 ? query_ext.width() : 1; double qh = query_ext.height()>0 ? query_ext.height() : 1; query::resolution_type res(m_.width()/qw, m_.height()/qh); query q(layer_ext,res,scale_denom,m_.get_current_extent()); - p.start_layer_processing(lay, query_ext); std::vector active_styles; attribute_collector collector(names); double filt_factor = 1; diff --git a/tests/python_tests/images/support/mapnik-merc2merc-reprojection-render1.png b/tests/python_tests/images/support/mapnik-merc2merc-reprojection-render1.png new file mode 100644 index 0000000000000000000000000000000000000000..ca1ed8ff387e05f86ae034e4fd16b89c2b08af93 GIT binary patch literal 43573 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 Y))R{#WaU5opATxN>pri3YV+}b0c{5yr2qf` literal 0 HcmV?d00001 diff --git a/tests/python_tests/images/support/mapnik-merc2merc-reprojection-render2.png b/tests/python_tests/images/support/mapnik-merc2merc-reprojection-render2.png new file mode 100644 index 0000000000000000000000000000000000000000..58cb69120fa56bc19d6e5ae78136db25a164fa7a GIT binary patch literal 43689 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 P?t#L^HDqZ?K$Q0mxR9B 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 z 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

4d;zdu;fpdr~BD52$;=E&nK7XH(ZPA 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 Date: Wed, 4 Apr 2012 14:37:27 -0700 Subject: [PATCH 194/238] add missing test files --- .../data/good_maps/merc2wgs84_reprojection.xml | 15 +++++++++++++++ .../data/good_maps/wgs842merc_reprojection.xml | 15 +++++++++++++++ tests/data/shp/ne_110m_admin_0_countries.dbf | Bin 0 -> 84860 bytes tests/data/shp/ne_110m_admin_0_countries.prj | 1 + tests/data/shp/ne_110m_admin_0_countries.shp | Bin 0 -> 179876 bytes tests/data/shp/ne_110m_admin_0_countries.shx | Bin 0 -> 1516 bytes 6 files changed, 31 insertions(+) create mode 100644 tests/data/good_maps/merc2wgs84_reprojection.xml create mode 100644 tests/data/good_maps/wgs842merc_reprojection.xml create mode 100644 tests/data/shp/ne_110m_admin_0_countries.dbf create mode 100644 tests/data/shp/ne_110m_admin_0_countries.prj create mode 100644 tests/data/shp/ne_110m_admin_0_countries.shp create mode 100644 tests/data/shp/ne_110m_admin_0_countries.shx diff --git a/tests/data/good_maps/merc2wgs84_reprojection.xml b/tests/data/good_maps/merc2wgs84_reprojection.xml new file mode 100644 index 000000000..df53e6e28 --- /dev/null +++ b/tests/data/good_maps/merc2wgs84_reprojection.xml @@ -0,0 +1,15 @@ + + + + + My Style + + shape + ../shp/world_merc.shp + + + \ No newline at end of file diff --git a/tests/data/good_maps/wgs842merc_reprojection.xml b/tests/data/good_maps/wgs842merc_reprojection.xml new file mode 100644 index 000000000..a18b65f29 --- /dev/null +++ b/tests/data/good_maps/wgs842merc_reprojection.xml @@ -0,0 +1,15 @@ + + + + + My Style + + shape + + ../shp/ne_110m_admin_0_countries.shp + + + \ No newline at end of file diff --git a/tests/data/shp/ne_110m_admin_0_countries.dbf b/tests/data/shp/ne_110m_admin_0_countries.dbf new file mode 100644 index 0000000000000000000000000000000000000000..5fc769dd7d77e26591b18d3dcb5a15e405e0f693 GIT binary patch literal 84860 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 C+CyRAwb}G$~0Dk~yIiQi+wMNK%v% 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 z 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 literal 0 HcmV?d00001 diff --git a/tests/data/shp/ne_110m_admin_0_countries.shx b/tests/data/shp/ne_110m_admin_0_countries.shx new file mode 100644 index 0000000000000000000000000000000000000000..eacce7b013ebcfbabd9d4635b2aa2e4080be3e06 GIT binary patch literal 1516 zcmai!eQ1?s7>BRtY)x4)hK!g|xXR|7Yi_pmgf>nhvOr=)%{9h2M64A>!_3zD@bo?U zuw@o&m972|3yNiBOGHm8>sVHj5oTdy3|YfsNXQ|Ebv>MagATmA-rxQG-0%AimXz|J zzr?xeBDg@`Kk>JajPC#8&e=F2i z$RsTH9&0shkmfXMkNq1b^!LFv){lLgD%HlV)L+sIYX-Ep%PEwEO8o2`+U52<6Y zuoiARum1#eob(*;Lzj8;hN0VY1n=+MW4s}E?bSaG_j;Bv0{3~2q9km3LM`tRlB*2`?xpNGeL)uZs_ zV)cm3sb}Ho{jvp~HJ+o=I+!=G&U*L2bD!uhhv#Fw{a)Oq-^bAy z+i#Ngc_kjYRkoS;HN0xy;v~GDRyV-#4%rTG%*qLP>qlwdw^Qb29kEzl4)0XLd`<6N zYQFIw^eHlO&c402@(Kog^oaH`*prmU;Ny$*Tl3Q?wK03mjdOh#?auOfllvcn{l>=k z>Oh5TfP9w8oJ*MUvm~WqOS+g7@Ec!u~6D~hp$h-1(*+N+Sq47Dw zRsC{|P;Eco*RW)P{v@Hs_tWn(EOkD=n^0^0P(@faqux!ZdrjR=xcaQxdF#97EMa+^ z*Z9Wu>QVW({VNGg?lq(d*V-@S2&o<40zZ${~J_E9ZwLb5%QXgsTYu%fx&Ce5Vy+YkbXxnLAKVhT$kM%ZY3ikFn>zLQ+ MvrlO1D)Ue3Ut*LG;Q#;t literal 0 HcmV?d00001 From 0e64cc157ecef02046edb2921e8c8b7e6d60c3b5 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 4 Apr 2012 14:54:07 -0700 Subject: [PATCH 195/238] tests: allow the visual tests to be run in quiet mode --- tests/visual_tests/compare.py | 12 +++++++---- tests/visual_tests/test.py | 39 +++++++++++++++++++++-------------- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/tests/visual_tests/compare.py b/tests/visual_tests/compare.py index 46bba923e..b558e38ea 100644 --- a/tests/visual_tests/compare.py +++ b/tests/visual_tests/compare.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + #import math, operator import Image import sys @@ -5,6 +7,7 @@ import sys COMPUTE_THRESHOLD = 16 errors = [] +passed = 0 # returns true if pixels are not identical def compare_pixels(pixel1, pixel2): @@ -19,6 +22,7 @@ def compare_pixels(pixel1, pixel2): # compare tow images and return number of different pixels def compare(fn1, fn2): global errors + global passed im1 = Image.open(fn1) try: im2 = Image.open(fn2) @@ -38,21 +42,21 @@ def compare(fn1, fn2): diff = diff + 1 if diff != 0: errors.append((fn1, diff)) + passed += 1 return diff def summary(): global errors + global passed print "-"*80 - print "Summary:" + print "Visual text rendering summary:", if len(errors) != 0: for error in errors: if (error[1] is None): print "Could not verify %s: No reference image found!" % error[0] else: print "%s failed: %d different pixels" % error - print "-"*80 sys.exit(1) else: - print 'No errors detected!' - print "-"*80 + print 'All %s tests passed: \x1b[1;32m✓ \x1b[0m' % passed sys.exit(0) diff --git a/tests/visual_tests/test.py b/tests/visual_tests/test.py index 29716e6bd..15c9640e6 100755 --- a/tests/visual_tests/test.py +++ b/tests/visual_tests/test.py @@ -47,10 +47,10 @@ files = [ 'bbox': mapnik.Box2d(-5.192, 50.189, -5.174, 50.195)} ] -def render(filename, width, height, bbox): - print "-"*80 - print "Rendering style \"%s\" with size %dx%d ... " % (filename, width, height) - print "-"*80 +def render(filename, width, height, bbox, quiet=False): + if not quiet: + print "Rendering style \"%s\" with size %dx%d ... \x1b[1;32m✓ \x1b[0m" % (filename, width, height) + print "-"*80 m = mapnik.Map(width, height) mapnik.load_map(m, os.path.join(dirname, "styles", "%s.xml" % filename), False) if bbox is not None: @@ -62,21 +62,28 @@ def render(filename, width, height, bbox): diff = compare(basefn + '-agg.png', basefn + '-reference.png') if diff > 0: print "-"*80 - print 'Error: %u different pixels' % diff + print '\x1b[33mError:\x1b[0m %u different pixels' % diff print "-"*80 return m -if len(sys.argv) == 2: - files = [(sys.argv[1], (500, 500))] -elif len(sys.argv) > 2: - files = [sys.argv[1:]] +if __name__ == "__main__": + if '-q' in sys.argv: + quiet = True + sys.argv.remove('-q') + else: + quiet = False -for f in files: - config = dict(defaults) - config.update(f) - for size in config['sizes']: - m = render(config['name'], size[0], size[1], config['bbox']) - mapnik.save_map(m, os.path.join(dirname, 'xml_output', "%s-out.xml" % config['name'])) + if len(sys.argv) == 2: + files = [(sys.argv[1], (500, 500))] + elif len(sys.argv) > 2: + files = [sys.argv[1:]] -summary() + for f in files: + config = dict(defaults) + config.update(f) + for size in config['sizes']: + m = render(config['name'], size[0], size[1], config['bbox'], quiet=quiet) + mapnik.save_map(m, os.path.join(dirname, 'xml_output', "%s-out.xml" % config['name'])) + + summary() From d6c4e7f7fcc68db7dec5ee0cd15a7f0565d7ecbb Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 4 Apr 2012 15:17:37 -0700 Subject: [PATCH 196/238] fix encoding of rundemo.cpp (windows1 -> utf8) and correctly declare the encoding of the boundaries shapefile (fixing white background to be green) --- demo/c++/rundemo.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/demo/c++/rundemo.cpp b/demo/c++/rundemo.cpp index cb6c26c90..eb22729e9 100644 --- a/demo/c++/rundemo.cpp +++ b/demo/c++/rundemo.cpp @@ -70,7 +70,7 @@ int main ( int argc , char** argv) provpoly_style.add_rule(provpoly_rule_on); rule provpoly_rule_qc; - provpoly_rule_qc.set_filter(parse_expression("[NOM_FR] = 'Québec'")); + provpoly_rule_qc.set_filter(parse_expression("[NOM_FR] = 'Québec'")); provpoly_rule_qc.append(polygon_symbolizer(color(217, 235, 203))); provpoly_style.add_rule(provpoly_rule_qc); @@ -175,6 +175,7 @@ int main ( int argc , char** argv) parameters p; p["type"]="shape"; p["file"]="../data/boundaries"; + p["encoding"]="latin1"; layer lyr("Provinces"); lyr.set_datasource(datasource_cache::instance()->create(p)); From b5af9e36392795913c2d7a1809ff3fee3fce833e Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 4 Apr 2012 15:17:52 -0700 Subject: [PATCH 197/238] use default env compiler --- demo/c++/Makefile | 2 -- 1 file changed, 2 deletions(-) diff --git a/demo/c++/Makefile b/demo/c++/Makefile index 09ba0378c..d30b71b40 100644 --- a/demo/c++/Makefile +++ b/demo/c++/Makefile @@ -1,5 +1,3 @@ -CXX = g++ - CXXFLAGS = $(shell mapnik-config --cflags) LDFLAGS = $(shell mapnik-config --libs --dep-libs --ldflags) From 3e3bdf66070dd9b116a025d09084374a628f5516 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 4 Apr 2012 15:18:28 -0700 Subject: [PATCH 198/238] compile rundemo even during install --- demo/c++/build.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/demo/c++/build.py b/demo/c++/build.py index a9b87265c..9738347f9 100644 --- a/demo/c++/build.py +++ b/demo/c++/build.py @@ -47,7 +47,6 @@ rundemo = demo_env.Program('rundemo', source, LIBS=libraries, LINKFLAGS=env["CUS Depends(rundemo, env.subst('../../src/%s' % env['MAPNIK_LIB_NAME'])) -# we don't install this app because the datasource paths are relative -# and we're not going to install the sample data. -#env.Install(install_prefix + '/bin', rundemo) -#env.Alias('install', install_prefix + '/bin') +# build locally if installing +if 'install' in COMMAND_LINE_TARGETS: + env.Alias('install',rundemo) From a50ea0750e032bece7528c9c51e248443362af38 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 4 Apr 2012 15:19:37 -0700 Subject: [PATCH 199/238] compile c++ tests even if only installing and hook them all up automatically to make test target --- Makefile | 5 ++--- tests/cpp_tests/build.py | 5 ++++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 624f1cb69..ac4e62b99 100755 --- a/Makefile +++ b/Makefile @@ -18,9 +18,8 @@ uninstall: python scons/scons.py uninstall test: - @python tests/visual_tests/test.py - @tests/cpp_tests/font_registration_test - @tests/cpp_tests/params_test + @python tests/visual_tests/test.py -q + @tests/cpp_tests/*-bin @python tests/run_tests.py -q pep8: diff --git a/tests/cpp_tests/build.py b/tests/cpp_tests/build.py index bcc31bc94..3750717fd 100644 --- a/tests/cpp_tests/build.py +++ b/tests/cpp_tests/build.py @@ -14,5 +14,8 @@ libraries.append('mapnik') test_env.Append(CXXFLAGS='-g') for cpp_test in glob.glob('*_test.cpp'): - test_program = test_env.Program(cpp_test.replace('.cpp',''), [cpp_test], CPPPATH=headers, LIBS=libraries, LINKFLAGS=env['CUSTOM_LDFLAGS']) + test_program = test_env.Program(cpp_test.replace('.cpp','-bin'), [cpp_test], CPPPATH=headers, LIBS=libraries, LINKFLAGS=env['CUSTOM_LDFLAGS']) Depends(test_program, env.subst('../../src/%s' % env['MAPNIK_LIB_NAME'])) + # build locally if installing + if 'install' in COMMAND_LINE_TARGETS: + env.Alias('install',test_program) From ba4d81597258a8817ede06d9a43a5396158fdd54 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 4 Apr 2012 15:20:18 -0700 Subject: [PATCH 200/238] compile the c++ demo by default --- SConstruct | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SConstruct b/SConstruct index 95726262f..569ff4b09 100644 --- a/SConstruct +++ b/SConstruct @@ -359,7 +359,7 @@ opts.AddVariables( EnumVariable('THREADING','Set threading support','multi', ['multi','single']), EnumVariable('XMLPARSER','Set xml parser','libxml2', ['libxml2','ptree']), ('JOBS', 'Set the number of parallel compilations', "1", lambda key, value, env: int(value), int), - BoolVariable('DEMO', 'Compile demo c++ application', 'False'), + BoolVariable('DEMO', 'Compile demo c++ application', 'True'), BoolVariable('PGSQL2SQLITE', 'Compile and install a utility to convert postgres tables to sqlite', 'False'), BoolVariable('COLOR_PRINT', 'Print build status information in color', 'True'), BoolVariable('SAMPLE_INPUT_PLUGINS', 'Compile and install sample plugins', 'False'), From 9084ea600ba2553040aa4e9581bf5405e89539cf Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 4 Apr 2012 15:20:48 -0700 Subject: [PATCH 201/238] ignore the tif output --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 1da77f7c9..ecef215ae 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ tests/data/sqlite/*index demo/c++/cairo-demo.pdf demo/c++/cairo-demo.png demo/c++/cairo-demo256.png +demo/c++/demo.tif demo/c++/demo.jpg demo/c++/demo.png demo/c++/demo256.png From 977f78a237ba4734a5570bc0a6f9295be2b993b1 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 4 Apr 2012 15:21:16 -0700 Subject: [PATCH 202/238] ignore the c++ test programs --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ecef215ae..5d2536c3e 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,4 @@ demo/c++/demo.tif demo/c++/demo.jpg demo/c++/demo.png demo/c++/demo256.png - +tests/cpp_tests/*-bin From 9c3d3ab285cf0ea804aece9c5373f5acae162678 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Thu, 5 Apr 2012 08:35:28 -0700 Subject: [PATCH 203/238] more concise output from c++ tests --- tests/cpp_tests/font_registration_test.cpp | 21 ++++++++++++--------- tests/cpp_tests/params_test.cpp | 6 +++++- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/tests/cpp_tests/font_registration_test.cpp b/tests/cpp_tests/font_registration_test.cpp index 55715b9b1..e051fd3c1 100644 --- a/tests/cpp_tests/font_registration_test.cpp +++ b/tests/cpp_tests/font_registration_test.cpp @@ -49,36 +49,39 @@ int main( int, char*[] ) BOOST_TEST( mapnik::freetype_engine::register_fonts("tests/data/fonts/fake.ttf") == false ); BOOST_TEST( mapnik::freetype_engine::face_names().size() == 0 ); - BOOST_TEST( mapnik::freetype_engine::register_font("tests/data/fonts/intentionally-broken.ttf") == false ); - BOOST_TEST( mapnik::freetype_engine::register_fonts("tests/data/fonts/intentionally-broken.ttf") == false ); - BOOST_TEST( mapnik::freetype_engine::face_names().size() == 0 ); + //BOOST_TEST( mapnik::freetype_engine::register_font("tests/data/fonts/intentionally-broken.ttf") == false ); + //BOOST_TEST( mapnik::freetype_engine::register_fonts("tests/data/fonts/intentionally-broken.ttf") == false ); + //BOOST_TEST( mapnik::freetype_engine::face_names().size() == 0 ); // register unifont, since we know it sits in the root fonts/ dir BOOST_TEST( mapnik::freetype_engine::register_fonts(fontdir) ); face_names = mapnik::freetype_engine::face_names(); - std::clog << "number of registered fonts: " << face_names.size() << std::endl; + //std::clog << "number of registered fonts: " << face_names.size() << std::endl; BOOST_TEST( face_names.size() > 0 ); BOOST_TEST( face_names.size() == 1 ); // re-register unifont, should not have any affect BOOST_TEST( mapnik::freetype_engine::register_fonts(fontdir, false) ); face_names = mapnik::freetype_engine::face_names(); - std::clog << "number of registered fonts: " << face_names.size() << std::endl; + //std::clog << "number of registered fonts: " << face_names.size() << std::endl; BOOST_TEST( face_names.size() == 1 ); // register a single dejavu font std::string dejavu_bold_oblique("tests/data/fonts/DejaVuSansMono-BoldOblique.ttf"); BOOST_TEST( mapnik::freetype_engine::register_font(dejavu_bold_oblique) ); face_names = mapnik::freetype_engine::face_names(); - std::clog << "number of registered fonts: " << face_names.size() << std::endl; + //std::clog << "number of registered fonts: " << face_names.size() << std::endl; BOOST_TEST( face_names.size() == 2 ); - // recurse to find all dejavu fonts BOOST_TEST( mapnik::freetype_engine::register_fonts(fontdir, true) ); face_names = mapnik::freetype_engine::face_names(); - std::clog << "number of registered fonts: " << face_names.size() << std::endl; + //std::clog << "number of registered fonts: " << face_names.size() << std::endl; BOOST_TEST( face_names.size() == 22 ); - return ::boost::report_errors(); + if (!::boost::detail::test_errors()) { + std::clog << "C++ fonts registration: \x1b[1;32m✓ \x1b[0m\n"; + } else { + return ::boost::report_errors(); + } } diff --git a/tests/cpp_tests/params_test.cpp b/tests/cpp_tests/params_test.cpp index 86e702366..9caecafcd 100644 --- a/tests/cpp_tests/params_test.cpp +++ b/tests/cpp_tests/params_test.cpp @@ -53,5 +53,9 @@ int main( int, char*[] ) params["bool"] = "no"; BOOST_TEST( (params.get("bool") && *params.get("bool") == false)); - return ::boost::report_errors(); + if (!::boost::detail::test_errors()) { + std::clog << "C++ parameters: \x1b[1;32m✓ \x1b[0m\n"; + } else { + return ::boost::report_errors(); + } } From 8e708c89fd61cc9ffc5ed36e04c85ab3eb6b77da Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Thu, 5 Apr 2012 08:41:01 -0700 Subject: [PATCH 204/238] run all cpp tests available --- Makefile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index ac4e62b99..e6a05572f 100755 --- a/Makefile +++ b/Makefile @@ -18,8 +18,13 @@ uninstall: python scons/scons.py uninstall test: + @echo "*** Running visual tests…" @python tests/visual_tests/test.py -q - @tests/cpp_tests/*-bin + @echo "*** Running C++ tests..." + @for FILE in tests/cpp_tests/*-bin; do \ + $${FILE}; \ + done + @echo "*** Running python tests..." @python tests/run_tests.py -q pep8: From 17e2b831179bcb51360e67d93f51a4fa69a5678b Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Thu, 5 Apr 2012 08:48:46 -0700 Subject: [PATCH 205/238] run valgrind over all C++ tests but only output 'definitely lost' values --- Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e6a05572f..cd956cedc 100755 --- a/Makefile +++ b/Makefile @@ -34,6 +34,8 @@ pep8: @pep8 -r --select=W391 -q --filename=*.py `pwd`/tests/ | xargs gsed -i -e :a -e '/^\n*$/{$d;N;ba' -e '}' grind: - @valgrind --leak-check=full tests/cpp_tests/font_registration_test + @for FILE in tests/cpp_tests/*-bin; do \ + valgrind --leak-check=full --log-fd=1 $${FILE} | grep definitely; \ + done .PHONY: clean reset uninstall test install From b7f714f7ec7eb38be9536fb68e981f0bc8cb4ffd Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Thu, 5 Apr 2012 11:05:14 -0700 Subject: [PATCH 206/238] postgis: primary key autodetection will now only happen if 'autodetect_key_field' is set to true - maintains current behavior - refs #804 --- CHANGELOG.md | 4 +- plugins/input/postgis/postgis_datasource.cpp | 36 +++++--- tests/python_tests/postgis_test.py | 90 +++++++++++++++++--- 3 files changed, 105 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a453f6a3..a2fa8c594 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,9 @@ For a complete change history, see the SVN log. - GDAL: respect nodata for paletted/colormapped images (#1160) -- PostGIS: the primary key, for tables containing one, is now auto-detected allowing for globally unique feature id values (#804) +- PostGIS: Added a new option called 'autodetect_key_field' (by default false) that if true will + trigger autodetection of a given tables' primary key allowing for feature.id() to represent + globally unique ids. This option has no effect if the user has not manually supplied the 'key_field' option. (#804) - Cairo: Add full rendering support for markers to match AGG renderer functionality (#1071) diff --git a/plugins/input/postgis/postgis_datasource.cpp b/plugins/input/postgis/postgis_datasource.cpp index 6ff78bcd5..59434803a 100644 --- a/plugins/input/postgis/postgis_datasource.cpp +++ b/plugins/input/postgis/postgis_datasource.cpp @@ -107,7 +107,7 @@ void postgis_datasource::bind() const boost::optional initial_size = params_.get("initial_size", 1); boost::optional max_size = params_.get("max_size", 10); - boost::optional require_key = params_.get("require_key", false); + boost::optional autodetect_key_field = params_.get("autodetect_key_field", false); ConnectionManager* mgr = ConnectionManager::instance(); mgr->registerPool(creator_, *initial_size, *max_size); @@ -230,7 +230,7 @@ void postgis_datasource::bind() const } // detect primary key - if (key_field_.empty()) + if (*autodetect_key_field && key_field_.empty()) { std::ostringstream s; s << "SELECT a.attname, a.attnum, t.typname, t.typname in ('int2','int4','int8') " @@ -267,34 +267,42 @@ void postgis_datasource::bind() const { key_field_ = std::string(key_field_string); #ifdef MAPNIK_DEBUG - std::clog << "PostGIS Plugin: auto-detected key field of '" << key_field_ << "' on table '" << geometry_table_ << "'\n"; + std::clog << "PostGIS Plugin: auto-detected key field of '" + << key_field_ << "' on table '" + << geometry_table_ << "'\n"; #endif } } - else // warn with odd cases like numeric primary key + else { - std::clog << "PostGIS Plugin: Warning: '" << rs_key->getValue(0) << "' on table '" << geometry_table_ << "' is not a valid integer primary key field\n"; + // throw for cases like a numeric primary key, which is invalid + // as it should be floating point (int numerics are useless) + std::ostringstream err; + err << "PostGIS Plugin: Error: '" + << rs_key->getValue(0) + << "' on table '" + << geometry_table_ + << "' is not a valid integer primary key field\n"; + throw mapnik::datasource_exception(err.str()); } } else if (result_rows > 1) { - std::clog << "PostGIS Plugin: warning, multi column primary key detected but is not supported\n"; + std::ostringstream err; + err << "PostGIS Plugin: Error: '" + << "multi column primary key detected but is not supported"; + throw mapnik::datasource_exception(err.str()); } } -#ifdef MAPNIK_DEBUG - else - { - std::clog << "Postgis Plugin: no primary key could be detected for '" << geometry_table_ << "'\n"; - } -#endif rs_key->close(); } // if a globally unique key field/primary key is required // but still not known at this point, then throw - if (*require_key && key_field_.empty()) + if (*autodetect_key_field && key_field_.empty()) { - throw mapnik::datasource_exception(std::string("PostGIS Plugin: Error: primary key required for table '") + + throw mapnik::datasource_exception(std::string("PostGIS Plugin: Error: primary key required") + + " but could not be detected for table '" + geometry_table_ + "', please supply 'key_field' option to specify field to use for primary key"); } diff --git a/tests/python_tests/postgis_test.py b/tests/python_tests/postgis_test.py index 128d4c998..9c8604f3d 100644 --- a/tests/python_tests/postgis_test.py +++ b/tests/python_tests/postgis_test.py @@ -119,6 +119,11 @@ INSERT INTO "tableWithMixedCase"(geom) values (ST_MakePoint(1,0)); INSERT INTO "tableWithMixedCase"(geom) values (ST_MakePoint(1,1)); ''' +insert_table_7 = ''' +CREATE TABLE test6(first_id int4, second_id int4,PRIMARY KEY (first_id,second_id), geom geometry); +INSERT INTO test6(first_id, second_id, geom) values (0, 0, GeomFromEWKT('SRID=4326;POINT(0 0)')); +''' + def postgis_setup(): call('dropdb %s' % MAPNIK_TEST_DBNAME,silent=True) call('createdb -T %s %s' % (POSTGIS_TEMPLATE_DBNAME,MAPNIK_TEST_DBNAME),silent=False) @@ -130,6 +135,7 @@ def postgis_setup(): call('''psql -q %s -c "%s"''' % (MAPNIK_TEST_DBNAME,insert_table_4),silent=False) call('''psql -q %s -c "%s"''' % (MAPNIK_TEST_DBNAME,insert_table_5),silent=False) call("""psql -q %s -c '%s'""" % (MAPNIK_TEST_DBNAME,insert_table_6),silent=False) + call('''psql -q %s -c "%s"''' % (MAPNIK_TEST_DBNAME,insert_table_7),silent=False) def postgis_takedown(): pass @@ -235,7 +241,8 @@ if 'postgis' in mapnik.DatasourceCache.instance().plugin_names() \ def test_auto_detection_of_unique_feature_id_32_bit(): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,table='test2', - geometry_field='geom') + geometry_field='geom', + autodetect_key_field=True) fs = ds.featureset() eq_(fs.next()['manual_id'],0) eq_(fs.next()['manual_id'],1) @@ -255,7 +262,7 @@ if 'postgis' in mapnik.DatasourceCache.instance().plugin_names() \ def test_auto_detection_will_fail_since_no_primary_key(): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,table='test3', geometry_field='geom', - require_key=False) + autodetect_key_field=False) fs = ds.featureset() feat = fs.next() eq_(feat['manual_id'],0) @@ -281,13 +288,13 @@ if 'postgis' in mapnik.DatasourceCache.instance().plugin_names() \ def test_auto_detection_will_fail_and_should_throw(): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,table='test3', geometry_field='geom', - require_key=True) + autodetect_key_field=True) fs = ds.featureset() def test_auto_detection_of_unique_feature_id_64_bit(): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,table='test4', geometry_field='geom', - require_key=False) + autodetect_key_field=True) fs = ds.featureset() eq_(fs.next()['manual_id'],0) eq_(fs.next()['manual_id'],1) @@ -304,11 +311,73 @@ if 'postgis' in mapnik.DatasourceCache.instance().plugin_names() \ eq_(fs.next().id(),2147483647) eq_(fs.next().id(),-2147483648) + def test_disabled_auto_detection_and_subquery(): + ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,table='''(select geom, 'a'::varchar as name from test2) as t''', + geometry_field='geom', + autodetect_key_field=False) + fs = ds.featureset() + feat = fs.next() + eq_(feat.id(),1) + eq_(feat['name'],'a') + feat = fs.next() + eq_(feat.id(),2) + eq_(feat['name'],'a') + feat = fs.next() + eq_(feat.id(),3) + eq_(feat['name'],'a') + feat = fs.next() + eq_(feat.id(),4) + eq_(feat['name'],'a') + feat = fs.next() + eq_(feat.id(),5) + eq_(feat['name'],'a') + feat = fs.next() + eq_(feat.id(),6) + eq_(feat['name'],'a') + + def test_auto_detection_and_subquery_including_key(): + ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,table='''(select geom, manual_id from test2) as t''', + geometry_field='geom', + autodetect_key_field=True) + fs = ds.featureset() + eq_(fs.next()['manual_id'],0) + eq_(fs.next()['manual_id'],1) + eq_(fs.next()['manual_id'],1000) + eq_(fs.next()['manual_id'],-1000) + eq_(fs.next()['manual_id'],2147483647) + eq_(fs.next()['manual_id'],-2147483648) + + fs = ds.featureset() + eq_(fs.next().id(),0) + eq_(fs.next().id(),1) + eq_(fs.next().id(),1000) + eq_(fs.next().id(),-1000) + eq_(fs.next().id(),2147483647) + eq_(fs.next().id(),-2147483648) + + @raises(RuntimeError) + def test_auto_detection_of_invalid_numeric_primary_key(): + ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,table='''(select geom, manual_id::numeric from test2) as t''', + geometry_field='geom', + autodetect_key_field=True) + + @raises(RuntimeError) + def test_auto_detection_of_invalid_multiple_keys(): + ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,table='''test6''', + geometry_field='geom', + autodetect_key_field=True) + + @raises(RuntimeError) + def test_auto_detection_of_invalid_multiple_keys_subquery(): + ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,table='''(select first_id,second_id,geom from test6) as t''', + geometry_field='geom', + autodetect_key_field=True) + def test_manually_specified_feature_id_field(): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,table='test4', geometry_field='geom', key_field='manual_id', - require_key=True) + autodetect_key_field=True) fs = ds.featureset() eq_(fs.next()['manual_id'],0) eq_(fs.next()['manual_id'],1) @@ -328,7 +397,7 @@ if 'postgis' in mapnik.DatasourceCache.instance().plugin_names() \ def test_numeric_type_feature_id_field(): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,table='test5', geometry_field='geom', - require_key=False) + autodetect_key_field=False) fs = ds.featureset() eq_(fs.next()['manual_id'],-1) eq_(fs.next()['manual_id'],1) @@ -340,7 +409,7 @@ if 'postgis' in mapnik.DatasourceCache.instance().plugin_names() \ def test_querying_table_with_mixed_case(): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,table='"tableWithMixedCase"', geometry_field='geom', - require_key=True) + autodetect_key_field=True) fs = ds.featureset() eq_(fs.next().id(),1) eq_(fs.next().id(),2) @@ -351,7 +420,7 @@ if 'postgis' in mapnik.DatasourceCache.instance().plugin_names() \ def test_querying_subquery_with_mixed_case(): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,table='(SeLeCt * FrOm "tableWithMixedCase") as MixedCaseQuery', geometry_field='geom', - require_key=True) + autodetect_key_field=True) fs = ds.featureset() eq_(fs.next().id(),1) eq_(fs.next().id(),2) @@ -363,7 +432,7 @@ if 'postgis' in mapnik.DatasourceCache.instance().plugin_names() \ ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,table=''' (SeLeCt * FrOm "tableWithMixedCase" where geom && !bbox! ) as MixedCaseQuery''', geometry_field='geom', - require_key=True) + autodetect_key_field=True) fs = ds.featureset() eq_(fs.next().id(),1) eq_(fs.next().id(),2) @@ -375,7 +444,7 @@ if 'postgis' in mapnik.DatasourceCache.instance().plugin_names() \ ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,table=''' (SeLeCt * FrOm "tableWithMixedCase" where ST_Intersects(geom,!bbox!) ) as MixedCaseQuery''', geometry_field='geom', - require_key=True) + autodetect_key_field=True) fs = ds.featureset() eq_(fs.next().id(),1) eq_(fs.next().id(),2) @@ -387,4 +456,5 @@ if 'postgis' in mapnik.DatasourceCache.instance().plugin_names() \ if __name__ == "__main__": setup() + #test_auto_detection_and_subquery() [eval(run)() for run in dir() if 'test_' in run] From f90a07eb94dec7fec108da0577670f066a01c7f6 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Thu, 5 Apr 2012 14:41:52 -0700 Subject: [PATCH 207/238] add explicit include for MAPNIK_DECL --- include/mapnik/load_map.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/mapnik/load_map.hpp b/include/mapnik/load_map.hpp index 7858c9395..231a8bd37 100644 --- a/include/mapnik/load_map.hpp +++ b/include/mapnik/load_map.hpp @@ -25,6 +25,7 @@ // mapnik #include +#include // for MAPNIK_DECL // stl #include From 7b84a2ce94889caf73520b28e145af8799a67d51 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Thu, 5 Apr 2012 16:59:36 -0700 Subject: [PATCH 208/238] fix linking of postgis.input by using MAPNIK_DECL and ensuring the header is included in src/conversions.cpp - closes #1127 --- include/mapnik/util/conversions.hpp | 13 +++++++------ src/conversions.cpp | 3 +++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/include/mapnik/util/conversions.hpp b/include/mapnik/util/conversions.hpp index b57f55ff9..266b0f844 100644 --- a/include/mapnik/util/conversions.hpp +++ b/include/mapnik/util/conversions.hpp @@ -24,20 +24,21 @@ #define MAPNIK_UTIL_CONVERSIONS_HPP // mapnik +#include // stl #include namespace mapnik { namespace util { - bool string2int(const char * value, int & result); - bool string2int(std::string const& value, int & result); + MAPNIK_DECL bool string2int(std::string const& value, int & result); + MAPNIK_DECL bool string2int(const char * value, int & result); - bool string2double(std::string const& value, double & result); - bool string2double(const char * value, double & result); + MAPNIK_DECL bool string2double(std::string const& value, double & result); + MAPNIK_DECL bool string2double(const char * value, double & result); - bool string2float(std::string const& value, float & result); - bool string2float(const char * value, float & result); + MAPNIK_DECL bool string2float(std::string const& value, float & result); + MAPNIK_DECL bool string2float(const char * value, float & result); }} diff --git a/src/conversions.cpp b/src/conversions.cpp index 76e9d0531..6411388c1 100644 --- a/src/conversions.cpp +++ b/src/conversions.cpp @@ -20,6 +20,9 @@ * *****************************************************************************/ +// mapnik +#include + // boost #include From 43d646c759e3caf794dc59b84f3bf86b37f5c907 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Fri, 6 Apr 2012 11:42:36 -0700 Subject: [PATCH 209/238] formatting --- src/xml_tree.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/xml_tree.cpp b/src/xml_tree.cpp index 36995166f..940832782 100644 --- a/src/xml_tree.cpp +++ b/src/xml_tree.cpp @@ -104,7 +104,8 @@ inline boost::optional fast_cast(xml_tree const& tree, std::stri if (expression_factory::parse_from_string(expr, value, tree.expr_grammar)) { return expr; - } else + } + else { throw mapnik::config_error("Failed to parse expression '" + value + "'"); } From cb048b92d46041180afef71b9b80556e1ef43d8f Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Fri, 6 Apr 2012 12:29:51 -0700 Subject: [PATCH 210/238] formatting --- include/mapnik/enumeration.hpp | 38 +++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/include/mapnik/enumeration.hpp b/include/mapnik/enumeration.hpp index b3c00dfa0..db7c7d6d3 100644 --- a/include/mapnik/enumeration.hpp +++ b/include/mapnik/enumeration.hpp @@ -73,7 +73,7 @@ protected: * underscores (_) and dashes (-). * * - * @warning At the moment the verify() method is called during static initialization. + * @warning At the moment the verify_mapnik_enum() method is called during static initialization. * It quits the application with exit code 1 if any error is detected. The other solution * i thought of is to do the checks at compile time (using boost::mpl). * @@ -139,22 +139,27 @@ template class MAPNIK_DECL enumeration { public: typedef ENUM native_type; - enumeration(): - value_() {}; - enumeration( ENUM v ) : value_(v) {} - enumeration( const enumeration & other ) : value_(other.value_) {} + + enumeration() + : value_() {} + + enumeration( ENUM v ) + : value_(v) {} + + enumeration( const enumeration & other ) + : value_(other.value_) {} /** Assignment operator for native enum values. */ void operator=(ENUM v) - { - value_ = v; - } + { + value_ = v; + } /** Assignment operator. */ void operator=(const enumeration & other) - { - value_ = other.value_; - } + { + value_ = other.value_; + } /** Conversion operator for native enum values. */ operator ENUM() const @@ -166,10 +171,12 @@ public: { MAX = THE_MAX }; + ENUM max() const { return THE_MAX; } + /** Converts @p str to an enum. * @throw illegal_enum_value @p str is not a legal identifier. * */ @@ -247,7 +254,7 @@ public: /** Performs some simple checks and quits the application if * any error is detected. Tries to print helpful error messages. */ - static bool verify(const char * filename, unsigned line_no) + static bool verify_mapnik_enum(const char * filename, unsigned line_no) { for (unsigned i = 0; i < THE_MAX; ++i) { @@ -269,10 +276,12 @@ public: } return true; } + static std::string const& get_full_qualified_name() { return our_name_; } + static std::string get_name() { std::string::size_type idx = our_name_.find_last_of(":"); @@ -283,6 +292,7 @@ public: return our_name_.substr( idx + 1 ); } } + private: ENUM value_; static const char ** our_strings_ ; @@ -320,13 +330,13 @@ operator>>(std::istream & is, mapnik::enumeration & e) #define DEFINE_ENUM( name, e) \ typedef enumeration name -/** Helper macro. Runs the verify() method during static initialization. +/** Helper macro. Runs the verify_mapnik_enum() method during static initialization. * @relates mapnik::enumeration */ #define IMPLEMENT_ENUM( name, strings ) \ template <> const char ** name ::our_strings_ = strings; \ template <> std::string name ::our_name_ = #name; \ - template <> bool name ::our_verified_flag_( name ::verify(__FILE__, __LINE__)); + template <> bool name ::our_verified_flag_( name ::verify_mapnik_enum(__FILE__, __LINE__)); #endif // MAPNIK_ENUMERATION_HPP From ab776edbf51096ac50d33ed565c0ebf92a8b6b92 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Fri, 6 Apr 2012 12:58:08 -0700 Subject: [PATCH 211/238] reduce string copying slightly in populate_tree - refs #1055 --- include/mapnik/xml_node.hpp | 4 ++-- src/libxml2_loader.cpp | 3 ++- src/xml_tree.cpp | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/include/mapnik/xml_node.hpp b/include/mapnik/xml_node.hpp index f0db516e1..82c8ded0b 100644 --- a/include/mapnik/xml_node.hpp +++ b/include/mapnik/xml_node.hpp @@ -51,7 +51,7 @@ public: class node_not_found: public std::exception { public: - node_not_found(std::string node_name); + node_not_found(std::string const& node_name); virtual const char* what() const throw(); ~node_not_found() throw (); private: @@ -84,7 +84,7 @@ class xml_node public: typedef std::list::const_iterator const_iterator; typedef std::map attribute_map; - xml_node(xml_tree &tree, std::string name, unsigned line=0, bool text_node = false); + xml_node(xml_tree &tree, std::string const& name, unsigned line=0, bool text_node = false); std::string const& name() const; std::string const& text() const; diff --git a/src/libxml2_loader.cpp b/src/libxml2_loader.cpp index d95e145ca..31b235cb6 100644 --- a/src/libxml2_loader.cpp +++ b/src/libxml2_loader.cpp @@ -185,7 +185,8 @@ private: break; case XML_TEXT_NODE: { - std::string trimmed = boost::algorithm::trim_copy(std::string((char*)cur_node->content)); + std::string trimmed((char*)cur_node->content); + boost::algorithm::trim(trimmed); if (trimmed.empty()) break; //Don't add empty text nodes node.add_child(trimmed, cur_node->line, true); } diff --git a/src/xml_tree.cpp b/src/xml_tree.cpp index 940832782..26c3a3bf7 100644 --- a/src/xml_tree.cpp +++ b/src/xml_tree.cpp @@ -200,7 +200,7 @@ xml_attribute::xml_attribute(std::string const& value_) /****************************************************************************/ -node_not_found::node_not_found(std::string node_name) +node_not_found::node_not_found(std::string const& node_name) : node_name_(node_name) { @@ -255,7 +255,7 @@ more_than_one_child::~more_than_one_child() throw() /****************************************************************************/ -xml_node::xml_node(xml_tree &tree, std::string name, unsigned line, bool text_node) +xml_node::xml_node(xml_tree &tree, std::string const& name, unsigned line, bool text_node) : tree_(tree), name_(name), text_node_(text_node), From e57eb47ba0fcf70c2523d32b09bb397226213f88 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Fri, 6 Apr 2012 13:05:59 -0700 Subject: [PATCH 212/238] cast to const char --- src/libxml2_loader.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libxml2_loader.cpp b/src/libxml2_loader.cpp index 31b235cb6..bbf2d55ae 100644 --- a/src/libxml2_loader.cpp +++ b/src/libxml2_loader.cpp @@ -165,7 +165,7 @@ private: { for (; attributes; attributes = attributes->next ) { - node.add_attribute((char *)attributes->name, (char *)attributes->children->content); + node.add_attribute((const char *)attributes->name, (const char *)attributes->children->content); } } @@ -178,14 +178,14 @@ private: case XML_ELEMENT_NODE: { - xml_node &new_node = node.add_child((char *)cur_node->name, cur_node->line, false); + xml_node &new_node = node.add_child((const char *)cur_node->name, cur_node->line, false); append_attributes(cur_node->properties, new_node); populate_tree(cur_node->children, new_node); } break; case XML_TEXT_NODE: { - std::string trimmed((char*)cur_node->content); + std::string trimmed((const char*)cur_node->content); boost::algorithm::trim(trimmed); if (trimmed.empty()) break; //Don't add empty text nodes node.add_child(trimmed, cur_node->line, true); From 98e470903cec463a5b61baaac8fc73de132582e8 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Fri, 6 Apr 2012 13:39:13 -0700 Subject: [PATCH 213/238] less lexical cast - refs #1055 --- src/box2d.cpp | 27 ++++++++++++++------------- src/load_map.cpp | 15 ++++++--------- src/projection.cpp | 3 ++- src/rapidxml_loader.cpp | 3 ++- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/box2d.cpp b/src/box2d.cpp index c6061dc06..68cda38c3 100644 --- a/src/box2d.cpp +++ b/src/box2d.cpp @@ -21,6 +21,7 @@ *****************************************************************************/ //$Id: envelope.cpp 17 2005-03-08 23:58:43Z pavlenko $ +// mapnik #include // stl @@ -28,8 +29,8 @@ // boost #include -#include #include +#include namespace mapnik { @@ -332,31 +333,31 @@ inline #endif bool box2d::from_string(const std::string& s) { - bool success = false; - - boost::char_separator sep(", "); - boost::tokenizer > tok(s, sep); - unsigned i = 0; double d[4]; + bool success = false; + boost::char_separator sep(", "); + boost::tokenizer > tok(s, sep); for (boost::tokenizer >::iterator beg = tok.begin(); beg != tok.end(); ++beg) { - try - { - d[i] = boost::lexical_cast(boost::trim_copy(*beg)); - } - catch (boost::bad_lexical_cast & ex) + std::string item(*beg); + boost::trim(item); + // note: we intentionally do not use mapnik::util::conversions::string2double + // here to ensure that shapeindex can statically compile mapnik::box2d without + // needing to link to libmapnik + std::string::const_iterator str_beg = item.begin(); + std::string::const_iterator str_end = item.end(); + bool r = boost::spirit::qi::phrase_parse(str_beg,str_end,boost::spirit::qi::double_,boost::spirit::ascii::space,d[i]); + if (!(r && (str_beg == str_end))) { break; } - if (i == 3) { success = true; break; } - ++i; } diff --git a/src/load_map.cpp b/src/load_map.cpp index 68239b48a..901098411 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -50,12 +50,12 @@ #include #include #include +#include // boost #include #include #include -#include #include #include #include @@ -69,8 +69,6 @@ #include #include -using boost::lexical_cast; -using boost::bad_lexical_cast; using boost::tokenizer; using std::endl; @@ -260,13 +258,12 @@ void map_parser::parse_map(Map & map, xml_node const& pt, std::string const& bas for (boost::tokenizer >::iterator beg = tokens.begin(); beg != tokens.end(); ++beg) { - try + std::string item(*beg); + boost::trim(item); + if (!mapnik::util::string2int(item,n[i])) { - n[i] = boost::lexical_cast(boost::trim_copy(*beg)); - } - catch (boost::bad_lexical_cast & ex) - { - std::clog << *beg << " : " << ex.what() << "\n"; + throw config_error(std::string("Invalid version string encountered: '") + + *beg + "' in '" + *min_version_string + "'"); break; } if (i==2) diff --git a/src/projection.cpp b/src/projection.cpp index 5fc97e150..4c6ec153a 100644 --- a/src/projection.cpp +++ b/src/projection.cpp @@ -149,7 +149,8 @@ std::string projection::expanded() const if (proj_) { std::string def(pj_get_def( proj_, 0 )); //boost::algorithm::ireplace_first(def,params_,""); - return boost::trim_copy(def); + boost::trim(def); + return def; } return std::string(""); } diff --git a/src/rapidxml_loader.cpp b/src/rapidxml_loader.cpp index 4a9293f54..4c8398a69 100644 --- a/src/rapidxml_loader.cpp +++ b/src/rapidxml_loader.cpp @@ -144,7 +144,8 @@ private: case rapidxml::node_data: case rapidxml::node_cdata: { - std::string trimmed = boost::algorithm::trim_copy(std::string(cur_node->value())); + std::string trimmed(cur_node->value()); + boost::trim(trimmed); if (trimmed.empty()) break; //Don't add empty text nodes node.add_child(trimmed, 0, true); } From 8220acce829835705706137649ddcd96ace6a0df Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Fri, 6 Apr 2012 14:20:04 -0700 Subject: [PATCH 214/238] more params tests --- tests/cpp_tests/params_test.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/cpp_tests/params_test.cpp b/tests/cpp_tests/params_test.cpp index 9caecafcd..baf430e19 100644 --- a/tests/cpp_tests/params_test.cpp +++ b/tests/cpp_tests/params_test.cpp @@ -53,6 +53,22 @@ int main( int, char*[] ) params["bool"] = "no"; BOOST_TEST( (params.get("bool") && *params.get("bool") == false)); + // strings + params["string"] = "hello"; + BOOST_TEST( (params.get("string") && *params.get("string") == "hello") ); + + // int + params["int"] = 1; + BOOST_TEST( (params.get("int") && *params.get("int") == 1) ); + + // double + params["double"] = 1.5; + BOOST_TEST( (params.get("double") && *params.get("double") == 1.5) ); + + // value_null + params["null"] = mapnik::value_null(); + //BOOST_TEST( (params.get("null")/* && *params.get("null") == mapnik::value_null()*/) ); + if (!::boost::detail::test_errors()) { std::clog << "C++ parameters: \x1b[1;32m✓ \x1b[0m\n"; } else { From 215f7a911cbf703b0523e8c97117f92ba7ca65a2 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Fri, 6 Apr 2012 14:20:32 -0700 Subject: [PATCH 215/238] remove lexical cast from value_type conversions - refs #1055 --- include/mapnik/value.hpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/include/mapnik/value.hpp b/include/mapnik/value.hpp index a3cef23b4..a45d17c74 100644 --- a/include/mapnik/value.hpp +++ b/include/mapnik/value.hpp @@ -26,12 +26,12 @@ // mapnik #include #include +#include // boost #include #include #include -#include // stl #include @@ -651,13 +651,17 @@ struct to_double : public boost::static_visitor double operator() (std::string const& val) const { - return boost::lexical_cast(val); + double ret(0); + mapnik::util::string2double(val,ret); + return ret; } double operator() (UnicodeString const& val) const { std::string utf8; to_utf8(val,utf8); - return boost::lexical_cast(utf8); + double ret(0); + mapnik::util::string2double(utf8,ret); + return ret; } double operator() (value_null const& val) const @@ -681,13 +685,17 @@ struct to_int : public boost::static_visitor int operator() (std::string const& val) const { - return boost::lexical_cast(val); + int ret(0); + mapnik::util::string2int(val,ret); + return ret; } int operator() (UnicodeString const& val) const { std::string utf8; to_utf8(val,utf8); - return boost::lexical_cast(utf8); + int ret(0); + mapnik::util::string2int(utf8,ret); + return ret; } int operator() (value_null const& val) const From b21d8a995789778615f363d6e2cc88b8794a3e5a Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Fri, 6 Apr 2012 14:29:13 -0700 Subject: [PATCH 216/238] avoid copy --- plugins/input/postgis/postgis_featureset.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/input/postgis/postgis_featureset.cpp b/plugins/input/postgis/postgis_featureset.cpp index 66d65c4ab..dbd8d529c 100644 --- a/plugins/input/postgis/postgis_featureset.cpp +++ b/plugins/input/postgis/postgis_featureset.cpp @@ -40,7 +40,6 @@ #include #include -using boost::trim_copy; using mapnik::geometry_type; using mapnik::byte; using mapnik::geometry_utils; @@ -179,7 +178,9 @@ feature_ptr postgis_featureset::next() case 1042: //bpchar { - feature->put(name, tr_->transcode(trim_copy(std::string(buf)).c_str())); + std::string str(buf); + boost::trim(str); + feature->put(name, tr_->transcode(str.c_str())); break; } From 19f5f7741752ea9d2d251b07d994aadeb60ea0ab Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Fri, 6 Apr 2012 16:50:11 -0700 Subject: [PATCH 217/238] reuse path_expression_grammar - finishes and closes #1028 --- include/mapnik/parse_path.hpp | 5 +++ include/mapnik/path_expression_grammar.hpp | 1 + include/mapnik/xml_node.hpp | 5 +++ include/mapnik/xml_tree.hpp | 2 ++ src/load_map.cpp | 38 ++++++++++++++++++---- src/parse_path.cpp | 13 +++++++- src/xml_tree.cpp | 3 +- 7 files changed, 59 insertions(+), 8 deletions(-) diff --git a/include/mapnik/parse_path.hpp b/include/mapnik/parse_path.hpp index 7990e9fb5..66cf0ddfe 100644 --- a/include/mapnik/parse_path.hpp +++ b/include/mapnik/parse_path.hpp @@ -43,8 +43,13 @@ namespace mapnik { typedef boost::variant path_component; typedef std::vector path_expression; typedef boost::shared_ptr path_expression_ptr; +template struct path_expression_grammar; MAPNIK_DECL path_expression_ptr parse_path(std::string const & str); +MAPNIK_DECL bool parse_path_from_string(path_expression_ptr const& path, + std::string const & str, + path_expression_grammar const& g); + template struct path_processor diff --git a/include/mapnik/path_expression_grammar.hpp b/include/mapnik/path_expression_grammar.hpp index 5da749b11..9c11e29eb 100644 --- a/include/mapnik/path_expression_grammar.hpp +++ b/include/mapnik/path_expression_grammar.hpp @@ -60,6 +60,7 @@ namespace standard_wide = boost::spirit::standard_wide; using standard_wide::space_type; using standard_wide::space; +typedef boost::variant path_component; template struct path_expression_grammar : qi::grammar(), space_type> diff --git a/include/mapnik/xml_node.hpp b/include/mapnik/xml_node.hpp index 82c8ded0b..8d2858e46 100644 --- a/include/mapnik/xml_node.hpp +++ b/include/mapnik/xml_node.hpp @@ -119,6 +119,11 @@ public: std::string get_text() const; + xml_tree const& get_tree() const + { + return tree_; + } + template T get_value() const; private: diff --git a/include/mapnik/xml_tree.hpp b/include/mapnik/xml_tree.hpp index 27893d262..d5beb9614 100644 --- a/include/mapnik/xml_tree.hpp +++ b/include/mapnik/xml_tree.hpp @@ -25,6 +25,7 @@ //mapnik #include #include +#include // boost #include @@ -55,6 +56,7 @@ private: public: mapnik::css_color_grammar color_grammar; mapnik::expression_grammar expr_grammar; + path_expression_grammar path_expr_grammar; }; } //ns mapnik diff --git a/src/load_map.cpp b/src/load_map.cpp index 901098411..0db27b8c3 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -782,7 +782,13 @@ void map_parser::parse_point_symbolizer(rule & rule, xml_node const & sym) *file = ensure_relative_to_xml(file); - symbol.set_filename(parse_path(*file)); + path_expression_ptr expr(boost::make_shared()); + if (!parse_path_from_string(expr, *file, sym.get_tree().path_expr_grammar)) + { + throw mapnik::config_error("Failed to parse path_expression '" + *file + "'"); + } + + symbol.set_filename(expr); if (transform_wkt) { @@ -865,7 +871,13 @@ void map_parser::parse_markers_symbolizer(rule & rule, xml_node const& sym) } } - markers_symbolizer symbol(parse_path(filename)); + path_expression_ptr expr(boost::make_shared()); + if (!parse_path_from_string(expr, filename, sym.get_tree().path_expr_grammar)) + { + throw mapnik::config_error("Failed to parse path_expression '" + filename + "'"); + } + markers_symbolizer symbol(expr); + optional opacity = sym.get_opt_attr("opacity"); if (opacity) symbol.set_opacity(*opacity); @@ -961,8 +973,12 @@ void map_parser::parse_line_pattern_symbolizer(rule & rule, xml_node const & sym } file = ensure_relative_to_xml(file); - - line_pattern_symbolizer symbol(parse_path(file)); + path_expression_ptr expr(boost::make_shared()); + if (!parse_path_from_string(expr, file, sym.get_tree().path_expr_grammar)) + { + throw mapnik::config_error("Failed to parse path_expression '" + file + "'"); + } + line_pattern_symbolizer symbol(expr); parse_metawriter_in_symbolizer(symbol, sym); rule.append(symbol); @@ -1009,7 +1025,12 @@ void map_parser::parse_polygon_pattern_symbolizer(rule & rule, file = ensure_relative_to_xml(file); - polygon_pattern_symbolizer symbol(parse_path(file)); + path_expression_ptr expr(boost::make_shared()); + if (!parse_path_from_string(expr, file, sym.get_tree().path_expr_grammar)) + { + throw mapnik::config_error("Failed to parse path_expression '" + file + "'"); + } + polygon_pattern_symbolizer symbol(expr); // pattern alignment pattern_alignment_e p_alignment = sym.get_attr("alignment",LOCAL_ALIGNMENT); @@ -1159,7 +1180,12 @@ void map_parser::parse_shield_symbolizer(rule & rule, xml_node const& sym) } image_file = ensure_relative_to_xml(image_file); - shield_symbol.set_filename(parse_path(image_file)); + path_expression_ptr expr(boost::make_shared()); + if (!parse_path_from_string(expr, image_file, sym.get_tree().path_expr_grammar)) + { + throw mapnik::config_error("Failed to parse path_expression '" + image_file + "'"); + } + shield_symbol.set_filename(expr); } catch (image_reader_exception const & ex) { diff --git a/src/parse_path.cpp b/src/parse_path.cpp index 2c9fb9030..9e564653a 100644 --- a/src/parse_path.cpp +++ b/src/parse_path.cpp @@ -29,7 +29,7 @@ namespace mapnik { path_expression_ptr parse_path(std::string const & str) { - path_expression_ptr path = boost::make_shared() ; + path_expression_ptr path = boost::make_shared(); path_expression_grammar g; std::string::const_iterator itr = str.begin(); @@ -44,4 +44,15 @@ path_expression_ptr parse_path(std::string const & str) throw std::runtime_error("Failed to parse path expression"); } } + +bool parse_path_from_string(path_expression_ptr const& path, + std::string const & str, + path_expression_grammar const& g) +{ + std::string::const_iterator itr = str.begin(); + std::string::const_iterator end = str.end(); + bool r = qi::phrase_parse(itr, end, g, space, *path); + return (r && itr==end); +} + } diff --git a/src/xml_tree.cpp b/src/xml_tree.cpp index 26c3a3bf7..6ff31e7d9 100644 --- a/src/xml_tree.cpp +++ b/src/xml_tree.cpp @@ -171,7 +171,8 @@ xml_tree::xml_tree(std::string const& encoding) file_(), tr_(encoding), color_grammar(), - expr_grammar(tr_) + expr_grammar(tr_), + path_expr_grammar() { node_.set_processed(true); //root node is always processed } From 1f351e0e097270940c7c9257b5b685176737fac2 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Sun, 8 Apr 2012 02:20:56 +0200 Subject: [PATCH 218/238] implement new debug system --- SConstruct | 33 +++- bindings/python/mapnik_color.cpp | 1 - bindings/python/mapnik_coord.cpp | 1 - bindings/python/mapnik_datasource.cpp | 4 +- bindings/python/mapnik_datasource_cache.cpp | 2 - bindings/python/mapnik_envelope.cpp | 1 - bindings/python/mapnik_expression.cpp | 5 +- bindings/python/mapnik_feature.cpp | 3 - bindings/python/mapnik_featureset.cpp | 2 +- bindings/python/mapnik_font_engine.cpp | 1 - bindings/python/mapnik_geometry.cpp | 1 - bindings/python/mapnik_grid.cpp | 1 - bindings/python/mapnik_grid_view.cpp | 1 - bindings/python/mapnik_image.cpp | 1 - bindings/python/mapnik_image_view.cpp | 1 - bindings/python/mapnik_inmem_metawriter.cpp | 1 - bindings/python/mapnik_layer.cpp | 2 - .../python/mapnik_line_pattern_symbolizer.cpp | 1 - bindings/python/mapnik_line_symbolizer.cpp | 1 - bindings/python/mapnik_map.cpp | 1 - bindings/python/mapnik_markers_symbolizer.cpp | 2 +- bindings/python/mapnik_palette.cpp | 1 - bindings/python/mapnik_parameters.cpp | 5 +- bindings/python/mapnik_point_symbolizer.cpp | 1 - .../mapnik_polygon_pattern_symbolizer.cpp | 1 - bindings/python/mapnik_polygon_symbolizer.cpp | 1 - bindings/python/mapnik_proj_transform.cpp | 2 +- bindings/python/mapnik_projection.cpp | 3 +- bindings/python/mapnik_query.cpp | 5 +- bindings/python/mapnik_raster_colorizer.cpp | 4 +- bindings/python/mapnik_raster_symbolizer.cpp | 4 +- bindings/python/mapnik_rule.cpp | 3 +- bindings/python/mapnik_shield_symbolizer.cpp | 4 +- bindings/python/mapnik_stroke.cpp | 1 - bindings/python/mapnik_style.cpp | 3 +- bindings/python/mapnik_symbolizer.cpp | 3 +- bindings/python/mapnik_view_transform.cpp | 1 - bindings/python/python_cairo.cpp | 1 - bindings/python/python_grid_utils.hpp | 5 +- bindings/python/python_optional.hpp | 1 - include/mapnik/ctrans.hpp | 41 ++--- include/mapnik/datasource.hpp | 11 ++ include/mapnik/debug.hpp | 127 +++++++++++++ include/mapnik/font_engine_freetype.hpp | 12 +- include/mapnik/grid/grid.hpp | 10 +- .../json/feature_collection_grammar.hpp | 2 +- include/mapnik/memory_featureset.hpp | 8 +- include/mapnik/metawriter_json.hpp | 2 +- include/mapnik/pool.hpp | 17 +- include/mapnik/rule.hpp | 2 +- include/mapnik/svg/svg_renderer.hpp | 13 +- include/mapnik/timer.hpp | 80 ++++++--- include/mapnik/utils.hpp | 24 ++- plugins/input/csv/csv_datasource.cpp | 123 ++++++++----- plugins/input/csv/csv_datasource.hpp | 25 ++- plugins/input/gdal/gdal_datasource.cpp | 49 +++-- plugins/input/gdal/gdal_datasource.hpp | 3 +- plugins/input/gdal/gdal_featureset.cpp | 128 ++++++------- plugins/input/gdal/gdal_featureset.hpp | 2 +- plugins/input/geos/geos_datasource.cpp | 59 ++++-- plugins/input/geos/geos_featureset.cpp | 5 +- plugins/input/kismet/kismet_datasource.cpp | 40 +++-- plugins/input/occi/occi_datasource.cpp | 145 ++++++++++----- plugins/input/occi/occi_datasource.hpp | 4 +- plugins/input/occi/occi_featureset.cpp | 82 +++++---- plugins/input/occi/occi_featureset.hpp | 5 + plugins/input/occi/occi_types.hpp | 9 +- plugins/input/ogr/ogr_converter.cpp | 9 +- plugins/input/ogr/ogr_datasource.cpp | 50 ++++-- plugins/input/ogr/ogr_featureset.cpp | 27 +-- plugins/input/ogr/ogr_index_featureset.cpp | 22 +-- plugins/input/ogr/ogr_layer_ptr.hpp | 27 +-- plugins/input/osm/dataset_deliverer.cpp | 13 +- plugins/input/osm/osm.cpp | 38 ++-- plugins/input/osm/osm_datasource.cpp | 21 ++- plugins/input/osm/osm_datasource.hpp | 6 +- plugins/input/postgis/connection.hpp | 102 ++++++----- plugins/input/postgis/connection_manager.hpp | 19 +- plugins/input/postgis/cursorresultset.hpp | 100 +++++++---- plugins/input/postgis/postgis_datasource.cpp | 168 ++++++++---------- plugins/input/postgis/postgis_datasource.hpp | 7 +- plugins/input/postgis/postgis_featureset.cpp | 5 +- plugins/input/postgis/resultset.hpp | 6 +- plugins/input/raster/raster_datasource.cpp | 38 ++-- plugins/input/raster/raster_datasource.hpp | 5 +- plugins/input/raster/raster_featureset.cpp | 7 +- plugins/input/raster/raster_featureset.hpp | 21 ++- .../rasterlite/rasterlite_datasource.cpp | 71 ++++---- .../rasterlite/rasterlite_datasource.hpp | 3 +- .../rasterlite/rasterlite_featureset.cpp | 32 ++-- plugins/input/shape/shape_datasource.cpp | 89 +++++++--- plugins/input/shape/shape_datasource.hpp | 6 +- plugins/input/shape/shape_featureset.cpp | 11 +- .../input/shape/shape_index_featureset.cpp | 9 +- plugins/input/shape/shape_io.cpp | 5 +- plugins/input/sqlite/sqlite_connection.hpp | 13 +- plugins/input/sqlite/sqlite_datasource.cpp | 81 ++++++--- plugins/input/sqlite/sqlite_datasource.hpp | 10 +- plugins/input/sqlite/sqlite_featureset.cpp | 6 +- src/agg/agg_renderer.cpp | 30 ++-- src/agg/process_line_pattern_symbolizer.cpp | 5 +- src/agg/process_markers_symbolizer.cpp | 26 ++- .../process_polygon_pattern_symbolizer.cpp | 9 +- src/cairo_renderer.cpp | 51 +++--- src/datasource_cache.cpp | 35 ++-- src/feature_style_processor.cpp | 38 ++-- src/font_engine_freetype.cpp | 12 +- src/grid/grid_renderer.cpp | 38 ++-- src/grid/process_markers_symbolizer.cpp | 6 +- src/grid/process_raster_symbolizer.cpp | 5 +- src/jpeg_reader.cpp | 4 - src/libxml2_loader.cpp | 4 +- src/load_map.cpp | 86 ++++++--- src/map.cpp | 28 ++- src/memory.cpp | 4 +- src/memory_datasource.cpp | 8 +- src/metawriter.cpp | 19 +- src/placement_finder.cpp | 16 +- src/png_reader.cpp | 11 +- src/proj_transform.cpp | 2 +- src/raster_colorizer.cpp | 26 +-- src/save_map.cpp | 1 - src/svg/svg_renderer.cpp | 20 +-- src/svg_parser.cpp | 6 +- src/text_properties.cpp | 5 +- src/text_symbolizer.cpp | 7 +- src/tiff_reader.cpp | 12 +- src/unicode.cpp | 4 - src/wkb.cpp | 18 +- tests/python_tests/csv_test.py | 81 +++++++-- tests/python_tests/datasource_test.py | 18 +- workspace/mapnik.pro | 127 +++++++++---- workspace/plugins.pri | 3 +- 133 files changed, 1870 insertions(+), 1065 deletions(-) create mode 100644 include/mapnik/debug.hpp diff --git a/SConstruct b/SConstruct index 569ff4b09..419bc56d5 100644 --- a/SConstruct +++ b/SConstruct @@ -325,7 +325,7 @@ opts.AddVariables( # Variables affecting rendering back-ends BoolVariable('RENDERING_STATS', 'Output rendering statistics during style processing', 'False'), - + BoolVariable('INTERNAL_LIBAGG', 'Use provided libagg', 'True'), BoolVariable('SVG_RENDERER', 'build support for native svg renderer', 'False'), @@ -345,7 +345,12 @@ opts.AddVariables( PathVariable('SQLITE_LIBS', 'Search path for SQLITE library files', '/usr/' + LIBDIR_SCHEMA, PathVariable.PathAccept), PathVariable('RASTERLITE_INCLUDES', 'Search path for RASTERLITE include files', '/usr/include/', PathVariable.PathAccept), PathVariable('RASTERLITE_LIBS', 'Search path for RASTERLITE library files', '/usr/' + LIBDIR_SCHEMA, PathVariable.PathAccept), - + + # Variables for logging and statistics + BoolVariable('ENABLE_LOG', 'Enable logging, which is enabled by default when building in *debug*', 'False'), + BoolVariable('ENABLE_STATS', 'Enable global statistics during map processing', 'False'), + ('LOG_FORMAT_STRING', 'The format string used before log output string, piped through strftime (max length of 255 characters)', 'Mapnik LOG> %Y-%m-%d %H:%M:%S:'), + # Other variables BoolVariable('SHAPE_MEMORY_MAPPED_FILE', 'Utilize memory-mapped files in Shapefile Plugin (higher memory usage, better performance)', 'True'), ('SYSTEM_FONTS','Provide location for python bindings to register fonts (if given aborts installation of bundled DejaVu fonts)',''), @@ -1163,7 +1168,7 @@ if not preconfigured: env.Append(CXXFLAGS = '-DBOOST_REGEX_HAS_ICU') else: env['SKIPPED_DEPS'].append('boost_regex_icu') - + env['REQUESTED_PLUGINS'] = [ driver.strip() for driver in Split(env['INPUT_PLUGINS'])] if len(env['REQUESTED_PLUGINS']): @@ -1411,10 +1416,26 @@ if not preconfigured: # Common debugging flags. # http://lists.fedoraproject.org/pipermail/devel/2010-November/144952.html debug_flags = '-g -fno-omit-frame-pointer -DDEBUG -DMAPNIK_DEBUG' - ndebug_flags = '-DNDEBUG' - - + + # Enable logging in debug mode (always) and release mode (when specified) + log_enabled = ' -DMAPNIK_LOG -DMAPNIK_LOG_FORMAT="%s"' % env['LOG_FORMAT_STRING'] + + if env['DEBUG']: + debug_flags += log_enabled + else: + if env['ENABLE_LOG']: + ndebug_flags += log_enabled + + # Enable statistics reporting + if env['ENABLE_STATS']: + debug_flags += ' -DMAPNIK_STATS' + ndebug_flags += ' -DMAPNIK_STATS' + + # Add rdynamic to allow using statics between application and plugins + # http://stackoverflow.com/questions/8623657/multiple-instances-of-singleton-across-shared-libraries-on-linux + env.MergeFlags('-rdynamic') + # Customizing the C++ compiler flags depending on: # (1) the C++ compiler used; and # (2) whether debug binaries are requested. diff --git a/bindings/python/mapnik_color.cpp b/bindings/python/mapnik_color.cpp index 1b6e34fb2..82611ff48 100644 --- a/bindings/python/mapnik_color.cpp +++ b/bindings/python/mapnik_color.cpp @@ -19,7 +19,6 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ // boost #include diff --git a/bindings/python/mapnik_coord.cpp b/bindings/python/mapnik_coord.cpp index 103dad3f8..b9b4e6277 100644 --- a/bindings/python/mapnik_coord.cpp +++ b/bindings/python/mapnik_coord.cpp @@ -19,7 +19,6 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ // boost #include diff --git a/bindings/python/mapnik_datasource.cpp b/bindings/python/mapnik_datasource.cpp index d7b83040e..7d424431d 100644 --- a/bindings/python/mapnik_datasource.cpp +++ b/bindings/python/mapnik_datasource.cpp @@ -19,10 +19,11 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ + // boost #include #include + // stl #include #include @@ -89,6 +90,7 @@ boost::python::dict describe(boost::shared_ptr const& ds) description["name"] = ld.get_name(); description["geometry_type"] = ds->get_geometry_type(); description["encoding"] = ld.get_encoding(); + description["log"] = ds->log_enabled(); return description; } diff --git a/bindings/python/mapnik_datasource_cache.cpp b/bindings/python/mapnik_datasource_cache.cpp index 6b62b7129..c4381837b 100644 --- a/bindings/python/mapnik_datasource_cache.cpp +++ b/bindings/python/mapnik_datasource_cache.cpp @@ -20,8 +20,6 @@ * *****************************************************************************/ -//$Id$ - #include #include diff --git a/bindings/python/mapnik_envelope.cpp b/bindings/python/mapnik_envelope.cpp index 5aa35a0bf..099f319cc 100644 --- a/bindings/python/mapnik_envelope.cpp +++ b/bindings/python/mapnik_envelope.cpp @@ -19,7 +19,6 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id: mapnik_envelope.cc 27 2005-03-30 21:45:40Z pavlenko $ // boost #include diff --git a/bindings/python/mapnik_expression.cpp b/bindings/python/mapnik_expression.cpp index d95d5879b..b75281394 100644 --- a/bindings/python/mapnik_expression.cpp +++ b/bindings/python/mapnik_expression.cpp @@ -19,9 +19,11 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ +// boost #include +#include + // mapnik #include #include @@ -30,7 +32,6 @@ #include #include -#include using mapnik::Feature; using mapnik::expression_ptr; diff --git a/bindings/python/mapnik_feature.cpp b/bindings/python/mapnik_feature.cpp index e55842e26..c4d7f75b6 100644 --- a/bindings/python/mapnik_feature.cpp +++ b/bindings/python/mapnik_feature.cpp @@ -19,13 +19,10 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ // boost - #include //#include - #include #include #include diff --git a/bindings/python/mapnik_featureset.cpp b/bindings/python/mapnik_featureset.cpp index c7f4722d3..21eb7b796 100644 --- a/bindings/python/mapnik_featureset.cpp +++ b/bindings/python/mapnik_featureset.cpp @@ -19,10 +19,10 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ // boost #include + // mapnik #include #include diff --git a/bindings/python/mapnik_font_engine.cpp b/bindings/python/mapnik_font_engine.cpp index 1b2596e3c..5f4160756 100644 --- a/bindings/python/mapnik_font_engine.cpp +++ b/bindings/python/mapnik_font_engine.cpp @@ -19,7 +19,6 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ #include #include diff --git a/bindings/python/mapnik_geometry.cpp b/bindings/python/mapnik_geometry.cpp index 06aa96207..cd2506628 100644 --- a/bindings/python/mapnik_geometry.cpp +++ b/bindings/python/mapnik_geometry.cpp @@ -17,7 +17,6 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ // boost #include diff --git a/bindings/python/mapnik_grid.cpp b/bindings/python/mapnik_grid.cpp index 2cfce2b7b..95a20efaf 100644 --- a/bindings/python/mapnik_grid.cpp +++ b/bindings/python/mapnik_grid.cpp @@ -19,7 +19,6 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ // boost #include diff --git a/bindings/python/mapnik_grid_view.cpp b/bindings/python/mapnik_grid_view.cpp index bb39a5b88..926ee18f6 100644 --- a/bindings/python/mapnik_grid_view.cpp +++ b/bindings/python/mapnik_grid_view.cpp @@ -19,7 +19,6 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ // boost #include diff --git a/bindings/python/mapnik_image.cpp b/bindings/python/mapnik_image.cpp index 5ade76573..ff4d17c0b 100644 --- a/bindings/python/mapnik_image.cpp +++ b/bindings/python/mapnik_image.cpp @@ -19,7 +19,6 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ extern "C" { diff --git a/bindings/python/mapnik_image_view.cpp b/bindings/python/mapnik_image_view.cpp index 9e07a29e6..f3cf2e44f 100644 --- a/bindings/python/mapnik_image_view.cpp +++ b/bindings/python/mapnik_image_view.cpp @@ -19,7 +19,6 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ extern "C" { diff --git a/bindings/python/mapnik_inmem_metawriter.cpp b/bindings/python/mapnik_inmem_metawriter.cpp index 44ebbdd08..34760f45b 100644 --- a/bindings/python/mapnik_inmem_metawriter.cpp +++ b/bindings/python/mapnik_inmem_metawriter.cpp @@ -19,7 +19,6 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ // boost #include diff --git a/bindings/python/mapnik_layer.cpp b/bindings/python/mapnik_layer.cpp index 512d895b4..0b2e8d63b 100644 --- a/bindings/python/mapnik_layer.cpp +++ b/bindings/python/mapnik_layer.cpp @@ -19,8 +19,6 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id: mapnik_layer.cc 17 2005-03-08 23:58:43Z pavlenko $ - // boost #include diff --git a/bindings/python/mapnik_line_pattern_symbolizer.cpp b/bindings/python/mapnik_line_pattern_symbolizer.cpp index aae24bac0..e982edf05 100644 --- a/bindings/python/mapnik_line_pattern_symbolizer.cpp +++ b/bindings/python/mapnik_line_pattern_symbolizer.cpp @@ -19,7 +19,6 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ #include diff --git a/bindings/python/mapnik_line_symbolizer.cpp b/bindings/python/mapnik_line_symbolizer.cpp index 9c7053b90..93f96e693 100644 --- a/bindings/python/mapnik_line_symbolizer.cpp +++ b/bindings/python/mapnik_line_symbolizer.cpp @@ -19,7 +19,6 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ #include #include "mapnik_enumeration.hpp" diff --git a/bindings/python/mapnik_map.cpp b/bindings/python/mapnik_map.cpp index c6e2467cd..3bc1ca983 100644 --- a/bindings/python/mapnik_map.cpp +++ b/bindings/python/mapnik_map.cpp @@ -19,7 +19,6 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id: mapnik_map.cc 17 2005-03-08 23:58:43Z pavlenko $ // boost #include diff --git a/bindings/python/mapnik_markers_symbolizer.cpp b/bindings/python/mapnik_markers_symbolizer.cpp index 17d860f11..d6388c6f7 100644 --- a/bindings/python/mapnik_markers_symbolizer.cpp +++ b/bindings/python/mapnik_markers_symbolizer.cpp @@ -19,9 +19,9 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ #include + #include #include #include diff --git a/bindings/python/mapnik_palette.cpp b/bindings/python/mapnik_palette.cpp index 4e6c39666..54b0b3146 100644 --- a/bindings/python/mapnik_palette.cpp +++ b/bindings/python/mapnik_palette.cpp @@ -19,7 +19,6 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ // boost #include diff --git a/bindings/python/mapnik_parameters.cpp b/bindings/python/mapnik_parameters.cpp index 5a015c0ae..1bc7d853a 100644 --- a/bindings/python/mapnik_parameters.cpp +++ b/bindings/python/mapnik_parameters.cpp @@ -25,6 +25,7 @@ #include // mapnik +#include #include #include #include @@ -102,7 +103,9 @@ struct parameters_pickle_suite : boost::python::pickle_suite } else { - std::clog << "could not unpickle key: " << key << "\n"; +#ifdef MAPNIK_LOG + mapnik::log() << "parameters_pickle_suite: Could not unpickle key=" << key; +#endif } } } diff --git a/bindings/python/mapnik_point_symbolizer.cpp b/bindings/python/mapnik_point_symbolizer.cpp index ddd10bf41..2ca6c9acb 100644 --- a/bindings/python/mapnik_point_symbolizer.cpp +++ b/bindings/python/mapnik_point_symbolizer.cpp @@ -19,7 +19,6 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ #include #include "mapnik_enumeration.hpp" diff --git a/bindings/python/mapnik_polygon_pattern_symbolizer.cpp b/bindings/python/mapnik_polygon_pattern_symbolizer.cpp index 6aee342b8..dc155c5f6 100644 --- a/bindings/python/mapnik_polygon_pattern_symbolizer.cpp +++ b/bindings/python/mapnik_polygon_pattern_symbolizer.cpp @@ -19,7 +19,6 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ #include #include diff --git a/bindings/python/mapnik_polygon_symbolizer.cpp b/bindings/python/mapnik_polygon_symbolizer.cpp index dd809ac5d..06f35f0d8 100644 --- a/bindings/python/mapnik_polygon_symbolizer.cpp +++ b/bindings/python/mapnik_polygon_symbolizer.cpp @@ -19,7 +19,6 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ #include #include "mapnik_enumeration.hpp" diff --git a/bindings/python/mapnik_proj_transform.cpp b/bindings/python/mapnik_proj_transform.cpp index ed5e7c9a0..7dec3b36b 100644 --- a/bindings/python/mapnik_proj_transform.cpp +++ b/bindings/python/mapnik_proj_transform.cpp @@ -19,10 +19,10 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ // mapnik #include + // boost #include diff --git a/bindings/python/mapnik_projection.cpp b/bindings/python/mapnik_projection.cpp index f555ab11a..54fba845f 100644 --- a/bindings/python/mapnik_projection.cpp +++ b/bindings/python/mapnik_projection.cpp @@ -19,9 +19,8 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ -//boost +// boost #include // mapnik diff --git a/bindings/python/mapnik_query.cpp b/bindings/python/mapnik_query.cpp index 08133b17a..f2b5c368f 100644 --- a/bindings/python/mapnik_query.cpp +++ b/bindings/python/mapnik_query.cpp @@ -19,11 +19,14 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ +// boost #include + +// mapnik #include #include + using mapnik::query; using mapnik::box2d; diff --git a/bindings/python/mapnik_raster_colorizer.cpp b/bindings/python/mapnik_raster_colorizer.cpp index 0aa43b3f2..7d9d6b755 100644 --- a/bindings/python/mapnik_raster_colorizer.cpp +++ b/bindings/python/mapnik_raster_colorizer.cpp @@ -19,10 +19,12 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ +// boost #include #include + +// mapnik #include using mapnik::raster_colorizer; diff --git a/bindings/python/mapnik_raster_symbolizer.cpp b/bindings/python/mapnik_raster_symbolizer.cpp index 63a18b851..dacaea5de 100644 --- a/bindings/python/mapnik_raster_symbolizer.cpp +++ b/bindings/python/mapnik_raster_symbolizer.cpp @@ -19,9 +19,11 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ +// boost #include + +// mapnik #include using mapnik::raster_symbolizer; diff --git a/bindings/python/mapnik_rule.cpp b/bindings/python/mapnik_rule.cpp index 528a87a63..378e22f10 100644 --- a/bindings/python/mapnik_rule.cpp +++ b/bindings/python/mapnik_rule.cpp @@ -19,13 +19,14 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ +// boost #include #include #include #include +// mapnik #include #include #include diff --git a/bindings/python/mapnik_shield_symbolizer.cpp b/bindings/python/mapnik_shield_symbolizer.cpp index 41eff7536..28ae7083e 100644 --- a/bindings/python/mapnik_shield_symbolizer.cpp +++ b/bindings/python/mapnik_shield_symbolizer.cpp @@ -20,9 +20,11 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ +// boost #include + +// mapnik #include #include #include diff --git a/bindings/python/mapnik_stroke.cpp b/bindings/python/mapnik_stroke.cpp index a75f614c6..1f21071c3 100644 --- a/bindings/python/mapnik_stroke.cpp +++ b/bindings/python/mapnik_stroke.cpp @@ -19,7 +19,6 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ // boost #include diff --git a/bindings/python/mapnik_style.cpp b/bindings/python/mapnik_style.cpp index 1ab130c04..9437e87d5 100644 --- a/bindings/python/mapnik_style.cpp +++ b/bindings/python/mapnik_style.cpp @@ -19,11 +19,12 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ +// boost #include #include +// mapnik #include "mapnik_enumeration.hpp" #include diff --git a/bindings/python/mapnik_symbolizer.cpp b/bindings/python/mapnik_symbolizer.cpp index a4c4c3512..bd87b0db8 100644 --- a/bindings/python/mapnik_symbolizer.cpp +++ b/bindings/python/mapnik_symbolizer.cpp @@ -19,10 +19,11 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ +// boost #include +// mapnik //symbolizer typdef here rather than mapnik/symbolizer.hpp #include diff --git a/bindings/python/mapnik_view_transform.cpp b/bindings/python/mapnik_view_transform.cpp index 7a9da32d5..090e65f9c 100644 --- a/bindings/python/mapnik_view_transform.cpp +++ b/bindings/python/mapnik_view_transform.cpp @@ -19,7 +19,6 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ // boost #include diff --git a/bindings/python/python_cairo.cpp b/bindings/python/python_cairo.cpp index e5420c1b1..ee6c900c3 100644 --- a/bindings/python/python_cairo.cpp +++ b/bindings/python/python_cairo.cpp @@ -19,7 +19,6 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ #if defined(HAVE_CAIRO) && defined(HAVE_PYCAIRO) diff --git a/bindings/python/python_grid_utils.hpp b/bindings/python/python_grid_utils.hpp index 09b72573c..2a9b21cf4 100644 --- a/bindings/python/python_grid_utils.hpp +++ b/bindings/python/python_grid_utils.hpp @@ -28,6 +28,7 @@ #include // mapnik +#include #include #include #include @@ -271,7 +272,9 @@ static void write_features(T const& grid_type, } else { - std::clog << "should not get here: key '" << key << "' not found in grid feature properties\n"; +#ifdef MAPNIK_LOG + mapnik::log() << "write_features: Should not get here: key " << key << " not found in grid feature properties"; +#endif } } } diff --git a/bindings/python/python_optional.hpp b/bindings/python/python_optional.hpp index 7707f0053..29c1990db 100644 --- a/bindings/python/python_optional.hpp +++ b/bindings/python/python_optional.hpp @@ -19,7 +19,6 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -//$Id$ #include #include diff --git a/include/mapnik/ctrans.hpp b/include/mapnik/ctrans.hpp index 31c62f391..2d8423d85 100644 --- a/include/mapnik/ctrans.hpp +++ b/include/mapnik/ctrans.hpp @@ -24,6 +24,7 @@ #define MAPNIK_CTRANS_HPP // mapnik +#include #include #include #include @@ -239,9 +240,9 @@ struct MAPNIK_DECL coord_transform_parallel angle_a = atan2((m_pre_y-m_cur_y),(m_pre_x-m_cur_x)); dx_pre = cos(angle_a + pi_by_2); dy_pre = sin(angle_a + pi_by_2); -#ifdef MAPNIK_DEBUG - std::clog << "offsetting line by: " << offset_ << "\n"; - std::clog << "initial dx=" << (dx_pre * offset_) << " dy=" << (dy_pre * offset_) << "\n"; +#ifdef MAPNIK_LOG + mapnik::log() << "coord_transform_parallel: Offsetting line by=" << offset_; + mapnik::log() << "coord_transform_parallel: Initial dx=" << (dx_pre * offset_) << ",dy=" << (dy_pre * offset_); #endif *x = m_pre_x + (dx_pre * offset_); *y = m_pre_y + (dy_pre * offset_); @@ -290,14 +291,14 @@ struct MAPNIK_DECL coord_transform_parallel else // skip sharp spikes { -#ifdef MAPNIK_DEBUG +#ifdef MAPNIK_LOG dx_curr = cos(angle_a + pi_by_2); dy_curr = sin(angle_a + pi_by_2); sin_curve = dx_curr*dy_pre-dy_curr*dx_pre; - std::clog << "angle a: " << angle_a << "\n"; - std::clog << "angle b: " << angle_b << "\n"; - std::clog << "h: " << h << "\n"; - std::clog << "sin_curve: " << sin_curve << "\n"; + mapnik::log() << "coord_transform_parallel: angle a=" << angle_a; + mapnik::log() << "coord_transform_parallel: angle b=" << angle_b; + mapnik::log() << "coord_transform_parallel: h=" << h; + mapnik::log() << "coord_transform_parallel: sin_curve=" << sin_curve; #endif m_status = process; break; @@ -309,21 +310,21 @@ struct MAPNIK_DECL coord_transform_parallel sin_curve = dx_curr*dy_pre-dy_curr*dx_pre; cos_curve = -dx_pre*dx_curr-dy_pre*dy_curr; - #ifdef MAPNIK_DEBUG - std::clog << "sin_curve value: " << sin_curve << "\n"; + #ifdef MAPNIK_LOG + mapnik::log() << "coord_transform_parallel: sin_curve value=" << sin_curve; #endif if(sin_curve > -0.3 && sin_curve < 0.3) { - angle_b = atan2((m_cur_y-m_next_y),(m_cur_x-m_next_x)); - h = tan((angle_b - angle_a)/2.0); - *x = m_cur_x + (dx_curr * offset_) - h * (dy_curr * offset_); - *y = m_cur_y + (dy_curr * offset_) + h * (dx_curr * offset_); + angle_b = atan2((m_cur_y-m_next_y),(m_cur_x-m_next_x)); + h = tan((angle_b - angle_a)/2.0); + *x = m_cur_x + (dx_curr * offset_) - h * (dy_curr * offset_); + *y = m_cur_y + (dy_curr * offset_) + h * (dx_curr * offset_); } else { - if (angle_b - angle_a > 0) - h = -1.0*(1.0+cos_curve)/sin_curve; - else - h = (1.0+cos_curve)/sin_curve; - *x = m_cur_x + (dx_curr + base_shift*dy_curr)*offset_; - *y = m_cur_y + (dy_curr - base_shift*dx_curr)*offset_; + if (angle_b - angle_a > 0) + h = -1.0*(1.0+cos_curve)/sin_curve; + else + h = (1.0+cos_curve)/sin_curve; + *x = m_cur_x + (dx_curr + base_shift*dy_curr)*offset_; + *y = m_cur_y + (dy_curr - base_shift*dx_curr)*offset_; } */ diff --git a/include/mapnik/datasource.hpp b/include/mapnik/datasource.hpp index 6ae828a94..0e7b71f7c 100644 --- a/include/mapnik/datasource.hpp +++ b/include/mapnik/datasource.hpp @@ -88,6 +88,7 @@ public: datasource (parameters const& params) : params_(params), + log_enabled_(false), is_bound_(false) { } @@ -104,6 +105,15 @@ public: return params_; } + /*! + * @brief Get the status of detasource logging + * @return Return true if log is enabled, false otherwise + */ + bool log_enabled() const + { + return log_enabled_; + } + /*! * @brief Get the type of the datasource * @return The type of the datasource (Vector or Raster) @@ -123,6 +133,7 @@ public: virtual ~datasource() {} protected: parameters params_; + bool log_enabled_; mutable bool is_bound_; }; diff --git a/include/mapnik/debug.hpp b/include/mapnik/debug.hpp new file mode 100644 index 000000000..3abe24538 --- /dev/null +++ b/include/mapnik/debug.hpp @@ -0,0 +1,127 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2011 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 + * + *****************************************************************************/ + +#ifndef MAPNIK_DEBUG_HPP +#define MAPNIK_DEBUG_HPP + +// mapnik +#include + +// boost +#ifdef MAPNIK_THREADSAFE +#include +#endif + +// std +#include +#include +#include +#include +#include +#include + +#ifdef MAPNIK_DEBUG +#define MAPNIK_DEBUG_AS_BOOL true +#else +#define MAPNIK_DEBUG_AS_BOOL false +#endif + +#ifndef MAPNIK_LOG_FORMAT +#error Must run configure again to regenerate the correct log format string. See LOG_FORMAT_STRING scons option. +#endif + +namespace mapnik { + + namespace logger { + +#define __xstr__(s) __str__(s) +#define __str__(s) #s + + static inline std::string format_logger() { + char buf[256]; + const time_t tm = time(0); + strftime(buf, sizeof(buf), __xstr__(MAPNIK_LOG_FORMAT), localtime(&tm)); + return buf; + } + +#undef __xstr__ +#undef __str__ + + template + class no_output { + private: + struct null_buffer { + template + null_buffer &operator<<(const T &) { + return *this; + } + }; + public: + typedef null_buffer stream_buffer; + + public: + void operator()(const stream_buffer &) { + } + }; + + template + class output_to_clog { + public: + typedef std::basic_ostringstream stream_buffer; + public: + void operator()(const stream_buffer &s) { +#ifdef MAPNIK_THREADSAFE + static boost::mutex mutex; + boost::mutex::scoped_lock lock(mutex); +#endif + std::clog << format_logger() << " " << s.str() << std::endl; + } + }; + + template