From 8d51cb421bf0ca98ee9bb721e2276ab5207904a8 Mon Sep 17 00:00:00 2001 From: Artem Pavlenko Date: Fri, 2 Nov 2007 12:50:15 +0000 Subject: [PATCH] 1. re-factored placement_finder to be more efficient. 'find_*' methods are templated on PathType. 2. PostGIS plug-in - optional 'multiple_geometries' parameter to control how Multi* geometries built. 3. MarkersSymbolizer (work in progress) to render vector shapes (markers) alonh a path with collision detection. --- bindings/python/mapnik_rule.cpp | 2 + include/mapnik/agg_renderer.hpp | 5 +- include/mapnik/arrow.hpp | 47 + include/mapnik/feature_style_processor.hpp | 2 +- include/mapnik/markers_converter.hpp | 166 ++++ include/mapnik/markers_symbolizer.hpp | 41 + include/mapnik/placement_finder.hpp | 78 +- include/mapnik/rule.hpp | 10 +- include/mapnik/text_path.hpp | 11 +- include/mapnik/wkb.hpp | 2 +- plugins/input/postgis/postgis.cpp | 21 +- plugins/input/postgis/postgis.hpp | 3 + plugins/input/postgis/postgisfs.cpp | 4 +- src/SConscript | 1 + src/agg_renderer.cpp | 139 ++- src/arrow.cpp | 66 ++ src/load_map.cpp | 16 +- src/placement_finder.cpp | 1028 ++++++++++++-------- src/save_map.cpp | 5 + src/wkb.cpp | 107 +- 20 files changed, 1239 insertions(+), 515 deletions(-) create mode 100644 include/mapnik/arrow.hpp create mode 100644 include/mapnik/markers_converter.hpp create mode 100644 include/mapnik/markers_symbolizer.hpp create mode 100644 src/arrow.cpp diff --git a/bindings/python/mapnik_rule.cpp b/bindings/python/mapnik_rule.cpp index 4ae78602c..d10feccc6 100644 --- a/bindings/python/mapnik_rule.cpp +++ b/bindings/python/mapnik_rule.cpp @@ -43,6 +43,7 @@ void export_rule() using mapnik::raster_symbolizer; using mapnik::shield_symbolizer; using mapnik::text_symbolizer; + using mapnik::building_symbolizer; using mapnik::symbolizer; using mapnik::symbolizers; @@ -51,6 +52,7 @@ void export_rule() implicitly_convertible(); implicitly_convertible(); implicitly_convertible(); + implicitly_convertible(); implicitly_convertible(); implicitly_convertible(); implicitly_convertible(); diff --git a/include/mapnik/agg_renderer.hpp b/include/mapnik/agg_renderer.hpp index 45907cea4..744569f16 100644 --- a/include/mapnik/agg_renderer.hpp +++ b/include/mapnik/agg_renderer.hpp @@ -81,6 +81,9 @@ namespace mapnik { void process(building_symbolizer const& sym, Feature const& feature, proj_transform const& prj_trans); + void process(markers_symbolizer const& sym, + Feature const& feature, + proj_transform const& prj_trans); private: T & pixmap_; unsigned width_; @@ -91,7 +94,7 @@ namespace mapnik { freetype_engine font_engine_; face_manager font_manager_; label_collision_detector4 detector_; - placement_finder finder_; + //placement_finder finder_; agg::rasterizer_scanline_aa<> ras_; }; } diff --git a/include/mapnik/arrow.hpp b/include/mapnik/arrow.hpp new file mode 100644 index 000000000..57176adcd --- /dev/null +++ b/include/mapnik/arrow.hpp @@ -0,0 +1,47 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2006 Artem Pavlenko + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + *****************************************************************************/ + +//$Id$ + +#ifndef ARROW_HPP +#define ARROW_HPP + +#include + +namespace mapnik { + + class arrow + { + public: + arrow(); + void rewind(unsigned path_id); + unsigned vertex(double* x, double* y); + Envelope extent() const; + private: + unsigned pos_; + double x_[7]; + double y_[7]; + unsigned cmd_[9]; + }; +} + +#endif // ARROW_HPP diff --git a/include/mapnik/feature_style_processor.hpp b/include/mapnik/feature_style_processor.hpp index 55b359dd3..6b1fb1a63 100644 --- a/include/mapnik/feature_style_processor.hpp +++ b/include/mapnik/feature_style_processor.hpp @@ -58,7 +58,7 @@ namespace mapnik { output_.process(sym,f_,prj_trans_); } - + Processor & output_; Feature const& f_; proj_transform const& prj_trans_; diff --git a/include/mapnik/markers_converter.hpp b/include/mapnik/markers_converter.hpp new file mode 100644 index 000000000..c37a7fbe0 --- /dev/null +++ b/include/mapnik/markers_converter.hpp @@ -0,0 +1,166 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2006 Artem Pavlenko + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + *****************************************************************************/ + +//$Id$ + +#ifndef MARKERS_CONVERTER_HPP +#define MARKERS_CONVERTER_HPP + +#include "agg_basics.h" +#include "agg_trans_affine.h" +#include + +namespace mapnik { + template + class markers_converter : boost::noncopyable + { + public: + markers_converter(Locator & locator,Shape & shape, Detector & detector); + void rewind(unsigned path_id); + unsigned vertex(double * x, double * y); + private: + enum status_e + { + initial, + markers, + polygon, + stop + }; + + Locator & locator_; + Shape & shape_; + Detector & detector_; + status_e status_; + agg::trans_affine transform_; + agg::trans_affine mtx_; + unsigned marker_; + unsigned num_markers_; + }; + + template + markers_converter::markers_converter(Locator & locator,Shape & shape, + Detector & detector) + : locator_(locator), + shape_(shape), + detector_(detector), + status_(initial), + num_markers_(1), + marker_(0) {} + + template + void markers_converter::rewind(unsigned path_id) + { + status_ = initial; + marker_ = 0; + num_markers_ = 1; + } + + template + unsigned markers_converter::vertex( double * x, double * y) + { + unsigned cmd = agg::path_cmd_move_to; + + double x1, y1, x2, y2; + while (!agg::is_stop(cmd)) + { + switch (status_) + { + case initial: + if (num_markers_ == 0 ) + { + cmd = agg::path_cmd_stop; + break; + } + locator_.rewind(marker_++); + status_ = markers; + num_markers_ = 0; + break; + case markers: + { + unsigned cmd1; + while (agg::is_move_to(cmd1 = locator_.vertex(&x1,&y1))); + if (agg::is_stop(cmd1)) + { + status_ = stop; + break; + } + unsigned cmd2 = locator_.vertex(&x2,&y2); + if (agg::is_stop(cmd2)) + { + status_ = stop; + break; + } + + + ++num_markers_; + double dx = x2 - x1; + double dy = y2 - y1; + double d = ::sqrt(dx * dx + dy * dy); + Envelope ext = shape_.extent(); + if (d > ext.width()) + { + mtx_ = transform_; + mtx_ *= agg::trans_affine_rotation(::atan2(dy,dx)); + double marker_x = x1 + dx * 0.5; + double marker_y = y1 + dy * 0.5; + + mtx_ *= agg::trans_affine_translation(marker_x,marker_y); + + double minx = ext.minx(); + double miny = ext.miny(); + double maxx = ext.maxx(); + double maxy = ext.maxy(); + mtx_.transform(&minx,&miny); + mtx_.transform(&maxx,&maxy); + + Envelope e0(minx,miny,maxx,maxy); + + if (detector_.has_placement(e0)) + { + shape_.rewind(0); + status_ = polygon; + detector_.insert(ext); + } + } + + break; + } + case polygon: + cmd = shape_.vertex(x,y); + if (agg::is_stop(cmd)) + { + cmd = agg::path_cmd_move_to; + status_ = markers; + break; + } + mtx_.transform(x,y); + return cmd; + case stop: + cmd = agg::path_cmd_stop; + break; + } + } + return cmd; + } +} + +#endif // MARKERS_CONVERTER_HPP diff --git a/include/mapnik/markers_symbolizer.hpp b/include/mapnik/markers_symbolizer.hpp new file mode 100644 index 000000000..c43310b18 --- /dev/null +++ b/include/mapnik/markers_symbolizer.hpp @@ -0,0 +1,41 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2006 Artem Pavlenko + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + *****************************************************************************/ + +//$Id$ + +#ifndef MARKERS_SYMBOLIZER_HPP +#define MARKERS_SYMBOLIZER_HPP + +namespace mapnik { + + struct MAPNIK_DECL markers_symbolizer + { + public: + markers_symbolizer() + : allow_overlap_(false) {} + private: + bool allow_overlap_; + + }; +} + +#endif // MARKERS_SYMBOLIZER_HPP diff --git a/include/mapnik/placement_finder.hpp b/include/mapnik/placement_finder.hpp index 2c0f2e6c3..7d5ed8e2a 100644 --- a/include/mapnik/placement_finder.hpp +++ b/include/mapnik/placement_finder.hpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -39,33 +40,24 @@ namespace mapnik typedef text_path placement_element; struct placement : boost::noncopyable - { - typedef coord_transform2 path_type; + { + placement(string_info & info_, + //path_type & shape_path_, + shield_symbolizer const& sym); - template - placement(string_info *info_, - CoordTransform *ctrans_, - const proj_transform *proj_trans_, - geometry2d const& geom_, - SymbolizerT const& sym); + placement(string_info & info_, + //path_type & shape_path_, + text_symbolizer const& sym); ~placement(); //helpers - std::pair get_position_at_distance(double target_distance); - double get_total_distance(); - void clear_envelopes(); - - unsigned path_size() const; + //std::pair get_position_at_distance(double target_distance); + //double get_total_distance(); - string_info *info; - - CoordTransform *ctrans; - const proj_transform *proj_trans; - geometry2d const& geom; - path_type shape_path; - - double total_distance_; //cache for distance + string_info & info; + //path_type & shape_path; + //double total_distance_; //cache for distance position displacement_; label_placement_e label_placement; @@ -84,33 +76,47 @@ namespace mapnik double max_char_angle_delta; double minimum_distance; - bool avoid_edges; - bool has_dimensions; std::pair dimensions; }; - - - + template class placement_finder : boost::noncopyable { public: - //e is the dimensions of the map, buffer is the buffer used for collission detection. + //e is the dimensions of the map, buffer is the buffer used for collision detection. placement_finder(DetectorT & detector,Envelope const& e); - void find_placements(placement *p); + + template + void find_placements(placement & p, T & path); + + void find_point_placement(placement & p, double, double); + + template + void find_placements_with_spacing(placement & p, T & path); + void clear(); - protected: - bool find_placement_follow(placement *p); - bool find_placement_horizontal(placement *p); - bool build_path_follow(placement *p, double target_distance); - bool build_path_horizontal(placement *p, double target_distance); - std::vector get_ideal_placements(placement *p); - void update_detector(placement *p); + private: + template + bool find_placement_follow(placement & p, T & path); + + template + bool find_placement_horizontal(placement & p, T & path); + + template + bool build_path_follow(placement & p, double target_distance, T & path); + + template + bool build_path_horizontal(placement & p, double target_distance, T & path); + + void get_ideal_placements(placement & p, double distance, std::vector&); + + void update_detector(placement & p); + DetectorT & detector_; - Envelope dimensions_; + Envelope const& dimensions_; }; } diff --git a/include/mapnik/rule.hpp b/include/mapnik/rule.hpp index 98c3ab361..181475b63 100644 --- a/include/mapnik/rule.hpp +++ b/include/mapnik/rule.hpp @@ -31,8 +31,10 @@ #include #include #include +#include #include #include + // boost #include #include @@ -94,6 +96,11 @@ namespace mapnik return (&lhs == &rhs); } + inline bool operator==(markers_symbolizer const& lhs, + markers_symbolizer const& rhs) + { + return (&lhs == &rhs); + } typedef boost::variant symbolizer; + building_symbolizer, + markers_symbolizer> symbolizer; typedef std::vector symbolizers; diff --git a/include/mapnik/text_path.hpp b/include/mapnik/text_path.hpp index 2ba8d374f..ef38af0d6 100644 --- a/include/mapnik/text_path.hpp +++ b/include/mapnik/text_path.hpp @@ -66,17 +66,17 @@ namespace mapnik characters_.push_back(new character_info(c, width, height)); } - unsigned num_characters() + unsigned num_characters() const { return characters_.size(); } - character_info at(unsigned i) + character_info at(unsigned i) const { return characters_[i]; } - character_info operator[](unsigned i) + character_info operator[](unsigned i) const { return at(i); } @@ -87,12 +87,13 @@ namespace mapnik height_ = height; } - std::pair get_dimensions() + std::pair get_dimensions() const { return std::pair(width_, height_); } - std::wstring get_string() { + std::wstring const& get_string() const + { return string_; } }; diff --git a/include/mapnik/wkb.hpp b/include/mapnik/wkb.hpp index a3032e804..ac07a3ccd 100644 --- a/include/mapnik/wkb.hpp +++ b/include/mapnik/wkb.hpp @@ -33,7 +33,7 @@ namespace mapnik class MAPNIK_DECL geometry_utils { public: - static void from_wkb(Feature & feature,const char* wkb, unsigned size); + static void from_wkb(Feature & feature,const char* wkb, unsigned size, bool multiple_geometries = false); private: geometry_utils(); geometry_utils(geometry_utils const&); diff --git a/plugins/input/postgis/postgis.cpp b/plugins/input/postgis/postgis.cpp index 01595330a..09a0e6268 100644 --- a/plugins/input/postgis/postgis.cpp +++ b/plugins/input/postgis/postgis.cpp @@ -22,14 +22,21 @@ //$Id: postgis.cc 44 2005-04-22 18:53:54Z pavlenko $ +// mapnik +#include "connection_manager.hpp" +#include "postgis.hpp" +#include + +// boost +#include + +// stl #include #include #include #include #include -#include -#include "connection_manager.hpp" -#include "postgis.hpp" + DATASOURCE_PLUGIN(postgis_datasource) @@ -61,7 +68,9 @@ postgis_datasource::postgis_datasource(parameters const& params) boost::optional initial_size = params_.get("inital_size",1); boost::optional max_size = params_.get("max_size",10); - + + multiple_geometries_ = *params_.get("multiple_geometries",false); + ConnectionManager *mgr=ConnectionManager::instance(); mgr->registerPool(creator_, *initial_size, *max_size); @@ -194,7 +203,7 @@ featureset_ptr postgis_datasource::features(const query& q) const std::clog << s.str() << "\n"; #endif shared_ptr rs=conn->executeQuery(s.str(),1); - return featureset_ptr(new postgis_featureset(rs,desc_.get_encoding(),props.size())); + return featureset_ptr(new postgis_featureset(rs,desc_.get_encoding(),multiple_geometries_,props.size())); } } return featureset_ptr(); @@ -233,7 +242,7 @@ featureset_ptr postgis_datasource::features_at_point(coord2d const& pt) const std::clog << s.str() << "\n"; #endif shared_ptr rs=conn->executeQuery(s.str(),1); - return featureset_ptr(new postgis_featureset(rs,desc_.get_encoding(),size)); + return featureset_ptr(new postgis_featureset(rs,desc_.get_encoding(),multiple_geometries_, size)); } } return featureset_ptr(); diff --git a/plugins/input/postgis/postgis.hpp b/plugins/input/postgis/postgis.hpp index 08fb5c28e..49119c5ee 100644 --- a/plugins/input/postgis/postgis.hpp +++ b/plugins/input/postgis/postgis.hpp @@ -62,6 +62,7 @@ class postgis_datasource : public datasource mutable mapnik::Envelope extent_; layer_descriptor desc_; ConnectionCreator creator_; + bool multiple_geometries_; static const std::string name_; public: static std::string name(); @@ -82,6 +83,7 @@ class postgis_featureset : public mapnik::Featureset { private: boost::shared_ptr rs_; + bool multiple_geometries_; unsigned num_attrs_; boost::scoped_ptr tr_; mutable int totalGeomSize_; @@ -89,6 +91,7 @@ class postgis_featureset : public mapnik::Featureset public: postgis_featureset(boost::shared_ptr const& rs, std::string const& encoding, + bool multiple_geometries, unsigned num_attrs); feature_ptr next(); ~postgis_featureset(); diff --git a/plugins/input/postgis/postgisfs.cpp b/plugins/input/postgis/postgisfs.cpp index 0b3be6881..7ec8aee67 100644 --- a/plugins/input/postgis/postgisfs.cpp +++ b/plugins/input/postgis/postgisfs.cpp @@ -39,8 +39,10 @@ using mapnik::geometry_utils; postgis_featureset::postgis_featureset(boost::shared_ptr const& rs, std::string const& encoding, + bool multiple_geometries, unsigned num_attrs=0) : rs_(rs), + multiple_geometries_(multiple_geometries), num_attrs_(num_attrs), tr_(new transcoder(encoding)), totalGeomSize_(0), @@ -53,7 +55,7 @@ feature_ptr postgis_featureset::next() feature_ptr feature(new Feature(count_)); int size=rs_->getFieldLength(0); const char *data = rs_->getValue(0); - geometry_utils::from_wkb(*feature,data,size); + geometry_utils::from_wkb(*feature,data,size,multiple_geometries_); totalGeomSize_+=size; for (unsigned pos=1;pos - #include #include #include +#include +#include // agg #include "agg_basics.h" -//#include "agg_rendering_buffer.h" -//#include "agg_rasterizer_scanline_aa.h" #include "agg_scanline_p.h" #include "agg_scanline_u.h" #include "agg_renderer_scanline.h" -//#include "agg_pixfmt_rgba.h" #include "agg_path_storage.h" #include "agg_span_allocator.h" #include "agg_span_pattern_rgba.h" @@ -43,11 +41,11 @@ #include "agg_conv_stroke.h" #include "agg_conv_dash.h" #include "agg_conv_contour.h" +#include "agg_conv_clip_polyline.h" #include "agg_vcgen_stroke.h" #include "agg_conv_adaptor_vcgen.h" #include "agg_conv_smooth_poly1.h" #include "agg_conv_marker.h" -#include "agg_arrowhead.h" #include "agg_vcgen_markers_term.h" #include "agg_renderer_outline_aa.h" #include "agg_rasterizer_outline_aa.h" @@ -59,6 +57,7 @@ #include "agg_pattern_filters_rgba.h" #include "agg_renderer_outline_image.h" #include "agg_vpgen_clip_polyline.h" +#include "agg_arrowhead.h" // boost #include @@ -107,8 +106,8 @@ namespace mapnik t_(m.getWidth(),m.getHeight(),m.getCurrentExtent(),offset_x,offset_y), font_engine_(), font_manager_(font_engine_), - detector_(Envelope(-64 ,-64, m.getWidth() + 64 ,m.getHeight() + 64)), - finder_(detector_,Envelope(0 ,0, m.getWidth(), m.getHeight())) + detector_(Envelope(-64 ,-64, m.getWidth() + 64 ,m.getHeight() + 64)) + //finder_(detector_,Envelope(0 ,0, m.getWidth(), m.getHeight())) { boost::optional bg = m.background(); if (bg) pixmap_.setBackground(*bg); @@ -144,7 +143,7 @@ namespace mapnik #endif if (lay.clear_label_cache()) { - finder_.clear(); + //finder_.clear(); // FIXME!!!!!!!! } } @@ -165,7 +164,7 @@ namespace mapnik typedef agg::renderer_base ren_base; typedef agg::renderer_scanline_aa_solid renderer; - Color const& fill_ = sym.get_fill(); + Color const& fill_ = sym.get_fill(); ras_.reset(); agg::scanline_u8 sl; ren_base renb(pixf_); @@ -178,8 +177,12 @@ namespace mapnik geometry2d const& geom=feature.get_geometry(i); if (geom.num_points() > 2) { - path_type path(t_,geom,prj_trans); + path_type path(t_,geom,prj_trans); + //agg::conv_smooth_poly1 smooth(path); + //smooth.smooth_value(0.6); + //agg::conv_curve > curve(smooth); ras_.add_path(path); + //ras_.add_path(curve); } } ren.color(agg::rgba8(r, g, b, int(255 * sym.get_opacity()))); @@ -229,7 +232,7 @@ namespace mapnik for (unsigned j=1;jmove_to(x,y); @@ -367,7 +370,6 @@ namespace mapnik else { agg::conv_stroke stroke(path); - line_join_e join=stroke_.get_line_join(); if ( join == MITER_JOIN) stroke.generator().line_join(agg::miter_join); @@ -437,6 +439,7 @@ namespace mapnik Feature const& feature, proj_transform const& prj_trans) { + typedef coord_transform2 path_type; std::wstring text = feature[sym.get_name()].to_unicode(); boost::shared_ptr const& data = sym.get_image(); if (text.length() > 0 && data) @@ -449,17 +452,21 @@ namespace mapnik ren.set_fill(sym.get_fill()); string_info info(text); ren.get_string_info(&info); + + Envelope box(-64,-64,width_+64,height_+64); + placement_finder finder(detector_,box); + unsigned num_geom = feature.num_geometries(); for (unsigned i=0;i 0) // don't bother with empty geometries { - placement text_placement(&info, &t_, &prj_trans, geom, sym); + path_type path(t_,geom,prj_trans); + placement text_placement(info, sym); text_placement.avoid_edges = sym.get_avoid_edges(); + finder.find_placements(text_placement,path); - finder_.find_placements(&text_placement); - for (unsigned int ii = 0; ii < text_placement.placements.size(); ++ ii) { int w = data->width(); @@ -589,12 +596,53 @@ namespace mapnik } } + template + void agg_renderer::process(markers_symbolizer const& sym, + Feature const& feature, + proj_transform const& prj_trans) + { + typedef coord_transform2 path_type; + typedef agg::renderer_base ren_base; + typedef agg::renderer_scanline_aa_solid renderer; + arrow arrow_; + //double k = ::pow(1.2, 0.7); + //arrow_.head(4 * k, 4 * k, 3 * k, 2 * k); + //Color const& fill_ = sym.get_fill(); + ras_.reset(); + agg::scanline_u8 sl; + ren_base renb(pixf_); + unsigned r = 0;// fill_.red(); + unsigned g = 0; //fill_.green(); + unsigned b = 255; //fill_.blue(); + renderer ren(renb); + for (unsigned i=0;i 1) + { + path_type path(t_,geom,prj_trans); + + agg::conv_dash dash(path); + dash.add_dash(20.0,200.0); + //dash.dash_start(100.0); + markers_converter, + arrow, + label_collision_detector4> + marker(dash, arrow_, detector_); + ras_.add_path(marker); + } + } + ren.color(agg::rgba8(r, g, b, 255)); + agg::render_scanlines(ras_, sl, ren); +} + template void agg_renderer::process(text_symbolizer const& sym, Feature const& feature, proj_transform const& prj_trans) { - + typedef coord_transform2 path_type; + std::wstring text = feature[sym.get_name()].to_unicode(); if ( text.length() > 0 ) { @@ -608,7 +656,9 @@ namespace mapnik ren.set_halo_fill(sym.get_halo_fill()); ren.set_halo_radius(sym.get_halo_radius()); - Envelope ext = t_.extent(); + Envelope box(-64,-64,width_+64,height_+64); + placement_finder finder(detector_,box); + string_info info(text); ren.get_string_info(&info); unsigned num_geom = feature.num_geometries(); @@ -617,38 +667,31 @@ namespace mapnik geometry2d const& geom = feature.get_geometry(i); if (geom.num_points() > 0) // don't bother with empty geometries { - //agg::vpgen_clip_polyline clipped_path; - // clip to the bbox + path_type path(t_,geom,prj_trans); + //agg::conv_clip_polyline clipped_path(path); + //clipped_path.clip_box(0,0,width_,height_); + //placement > + // text_placement(&info, &t_, &prj_trans, clipped_path, sym); + //placement_finder, + // label_collision_detector4> finder(detector_,box); + placement text_placement(info,sym); + if (sym.get_label_placement() == POINT_PLACEMENT) + { + double label_x, label_y, z=0.0; + geom.label_position(&label_x, &label_y); + prj_trans.backward(label_x,label_y, z); + t_.forward(&label_x,&label_y); + finder.find_point_placement(text_placement,label_x,label_y); + } + else if (sym.get_label_spacing() > 0 ) + { + finder.find_placements_with_spacing(text_placement,path); + } + else + { + finder.find_placements(text_placement,path); + } - //clipped_path.clip_box(ext.minx(),ext.miny(),ext.maxx(),ext.maxy()); - - //for (unsigned j=0;j + +#include + +namespace mapnik { + + arrow::arrow() + : pos_(0) + { + x_[0] = -7.0; y_[0] = 1.0; cmd_[0] = agg::path_cmd_move_to; + x_[1] = 1.0; y_[1] = 1.0; cmd_[1] = agg::path_cmd_line_to; + x_[2] = 1.0; y_[2] = 3.0; cmd_[2] = agg::path_cmd_line_to; + x_[3] = 7.0; y_[3] = 0.0; cmd_[3] = agg::path_cmd_line_to; + x_[4] = 1.0; y_[4] =-3.0; cmd_[4] = agg::path_cmd_line_to; + x_[5] = 1.0; y_[5] =-1.0; cmd_[5] = agg::path_cmd_line_to; + x_[6] = -7.0; y_[6] =-1.0; cmd_[6] = agg::path_cmd_line_to; + cmd_[7] = agg::path_cmd_end_poly | agg::path_flags_close | agg::path_flags_ccw; + cmd_[8] = agg::path_cmd_stop; + } + + void arrow::rewind(unsigned ) + { + pos_ = 0; + } + + unsigned arrow::vertex(double* x, double* y) + { + if(pos_ < 7 ) + { + *x = x_[pos_]; + *y = y_[pos_]; + return cmd_[pos_++]; + } + return agg::path_cmd_stop; + } + + Envelope arrow::extent() const + { + return Envelope(-7,-3,7,3); + } +} + diff --git a/src/load_map.cpp b/src/load_map.cpp index 4fe359ff0..169ad2300 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -77,6 +77,7 @@ namespace mapnik void parse_line_symbolizer( rule_type & rule, ptree const & sym); void parse_polygon_symbolizer( rule_type & rule, ptree const & sym); void parse_building_symbolizer( rule_type & rule, ptree const & sym ); + void parse_markers_symbolizer( rule_type & rule, ptree const & sym ); void ensure_font_face( const text_symbolizer & text_symbol ); @@ -364,6 +365,11 @@ namespace mapnik { rule.append(raster_symbolizer()); } + else if ( sym.first == "MarkersSymbolizer") + { + rule.append(markers_symbolizer()); + } + else if ( sym.first != "MinScaleDenominator" && sym.first != "MaxScaleDenominator" && sym.first != "Filter" && @@ -584,7 +590,15 @@ namespace mapnik { text_symbol.set_allow_overlap( * allow_overlap ); } - + + // max_char_angle_delta + optional max_char_angle_delta = + get_opt_attr(sym, "max_char_angle_delta"); + if (max_char_angle_delta) + { + text_symbol.set_max_char_angle_delta( * max_char_angle_delta); + } + if ( strict_ ) { ensure_font_face( text_symbol ); diff --git a/src/placement_finder.cpp b/src/placement_finder.cpp index b0736d186..38ef41c98 100644 --- a/src/placement_finder.cpp +++ b/src/placement_finder.cpp @@ -25,11 +25,12 @@ //mapnik #include - #include #include -#include -#include + +// agg +#include "agg_path_length.h" +#include "agg_conv_clip_polyline.h" // boost #include @@ -48,18 +49,11 @@ namespace mapnik { - template<> - placement::placement(string_info *info_, - CoordTransform *ctrans_, - const proj_transform *proj_trans_, - geometry2d const& geom_, + placement::placement(string_info & info_, shield_symbolizer const& sym) : info(info_), - ctrans(ctrans_), - proj_trans(proj_trans_), - geom(geom_), - shape_path(*ctrans_, geom_, *proj_trans_), - total_distance_(-1.0), + //shape_path(shape_path_), + //total_distance_(-1.0), displacement_(sym.get_displacement()), label_placement(sym.get_label_placement()), wrap_width(sym.get_wrap_width()), @@ -75,19 +69,12 @@ namespace mapnik sym.get_image()->height())) { } - - template<> - placement::placement(string_info *info_, - CoordTransform *ctrans_, - const proj_transform *proj_trans_, - geometry2d const& geom_, + + placement::placement(string_info & info_, text_symbolizer const& sym) : info(info_), - ctrans(ctrans_), - proj_trans(proj_trans_), - geom(geom_), - shape_path(*ctrans_, geom_, *proj_trans_), - total_distance_(-1.0), + // shape_path(shape_path_), + //total_distance_(-1.0), displacement_(sym.get_displacement()), label_placement(sym.get_label_placement()), wrap_width(sym.get_wrap_width()), @@ -98,97 +85,60 @@ namespace mapnik max_char_angle_delta(sym.get_max_char_angle_delta()), minimum_distance(sym.get_minimum_distance()), avoid_edges(sym.get_avoid_edges()), - has_dimensions(false), + has_dimensions(false), dimensions() { } - - placement::~placement() { } - - unsigned placement::path_size() const + + template + std::pair get_position_at_distance(double target_distance, T & shape_path) { - return geom.num_points(); - } - - std::pair placement::get_position_at_distance(double target_distance) - { - double old_x, old_y, new_x, new_y; - double x, y; - x = y = 0.0; - + double x1 = 0.0; + double y1 = 0.0; + double x2 = 0.0; + double y2 = 0.0; double distance = 0.0; - + bool first = true; + unsigned cmd; + double x = 0.0; + double y = 0.0; shape_path.rewind(0); - shape_path.vertex(&new_x,&new_y); - unsigned num_points = geom.num_points(); - for (unsigned i = 1; i < num_points; ++i) + while (!agg::is_stop(cmd = shape_path.vertex(&x2,&y2))) { - double dx, dy; - - old_x = new_x; - old_y = new_y; - - shape_path.vertex(&new_x,&new_y); - - dx = new_x - old_x; - dy = new_y - old_y; - - double segment_length = sqrt(dx*dx + dy*dy); - - distance += segment_length; - if (distance > target_distance) + if (first || agg::is_move_to(cmd)) { - x = new_x - dx*(distance - target_distance)/segment_length; - y = new_y - dy*(distance - target_distance)/segment_length; - - break; + first = false; } + else + { + double dx = x2-x1; + double dy = y2-y1; + + double segment_length = ::sqrt(dx*dx + dy*dy); + distance +=segment_length; + + if (distance > target_distance) + { + x = x2 - dx * (distance - target_distance)/segment_length; + y = y2 - dy * (distance - target_distance)/segment_length; + break; + } + } + x1 = x2; + y1 = y2; } - return std::pair(x, y); } - - double placement::get_total_distance() + + template + double get_total_distance(T & shape_path) { - if (total_distance_ < 0.0) - { - double old_x, old_y, new_x, new_y; - - shape_path.rewind(0); - - shape_path.vertex(&old_x,&old_y); - - total_distance_ = 0.0; - - unsigned num_points = geom.num_points(); - for (unsigned i = 1; i < num_points ; ++i) - { - double dx, dy; - shape_path.vertex(&new_x,&new_y); - - dx = new_x - old_x; - dy = new_y - old_y; - - total_distance_ += sqrt(dx*dx + dy*dy); - - old_x = new_x; - old_y = new_y; - } - } - - return total_distance_; + return agg::path_length(shape_path); } - - void placement::clear_envelopes() - { - while (!envelopes.empty()) - envelopes.pop(); - } - - + template placement_finder::placement_finder(DetectorT & detector,Envelope const& e) : detector_(detector), @@ -197,32 +147,28 @@ namespace mapnik } template - std::vector placement_finder::get_ideal_placements(placement *p) + void placement_finder::get_ideal_placements(placement & p, double distance, std::vector & ideal_label_distances) { - std::vector ideal_label_distances; - - std::pair string_dimensions = p->info->get_dimensions(); + std::pair string_dimensions = p.info.get_dimensions(); double string_width = string_dimensions.first; - - double distance = p->get_total_distance(); - if (p->label_placement == LINE_PLACEMENT && string_width > distance) + if (p.label_placement == LINE_PLACEMENT && string_width > distance) { //Empty! - return ideal_label_distances; + return ; } int num_labels = 0; - if (p->label_spacing && p->label_placement == LINE_PLACEMENT) + if (p.label_spacing && p.label_placement == LINE_PLACEMENT) { - num_labels = static_cast (floor(distance / (p->label_spacing + string_width))); + num_labels = static_cast (floor(distance / (p.label_spacing + string_width))); } - else if (p->label_spacing && p->label_placement == POINT_PLACEMENT) + else if (p.label_spacing && p.label_placement == POINT_PLACEMENT) { - num_labels = static_cast (floor(distance / p->label_spacing)); + num_labels = static_cast (floor(distance / p.label_spacing)); } - if (p->force_odd_labels && num_labels%2 == 0) + if (p.force_odd_labels && num_labels%2 == 0) num_labels--; if (num_labels <= 0) num_labels = 1; @@ -231,9 +177,9 @@ namespace mapnik double ideal_spacing = distance/num_labels; double middle; //try draw text centered - if (p->label_placement == LINE_PLACEMENT) + if (p.label_placement == LINE_PLACEMENT) middle = (distance / 2.0) - (string_width/2.0); - else // (p->label_placement == point_placement) + else // (p.label_placement == point_placement) middle = distance / 2.0; if (num_labels % 2) //odd amount of labels @@ -255,287 +201,60 @@ namespace mapnik } } - if (p->label_position_tolerance == 0) + if (p.label_position_tolerance == 0) { - p->label_position_tolerance = unsigned(ideal_spacing/2.0); + p.label_position_tolerance = unsigned(ideal_spacing/2.0); } - - return ideal_label_distances; } - + template - void placement_finder::find_placements(placement *p) + template + void placement_finder::find_placements(placement & p, T & shape_path) { - if (p->path_size() == 1) // point geometry - { - if ( build_path_horizontal(p, 0) ) - { - update_detector(p); - } - return; - } - - std::vector ideal_label_distances = get_ideal_placements(p); - - double delta, tolerance, distance; - distance = p->get_total_distance(); - tolerance = p->label_position_tolerance; - delta = std::max ( 1.0, tolerance/100.0); - + double distance = get_total_distance(shape_path); + std::vector ideal_label_distances; + get_ideal_placements(p,distance,ideal_label_distances); std::vector::const_iterator itr = ideal_label_distances.begin(); std::vector::const_iterator end = ideal_label_distances.end(); for (; itr != end; ++itr) { - bool placed = false; - for (double i = 0; i < tolerance && !placed; i += delta) + + if ((p.label_placement == LINE_PLACEMENT && + build_path_follow(p, *itr , shape_path ) ) || + (p.label_placement == POINT_PLACEMENT && + build_path_horizontal(p, *itr, shape_path)) ) { - for (int s = 1; s != -1; s-=2) { - if (*itr + i*s > distance || *itr + i*s < 0.0) { - continue; - } - p->clear_envelopes(); - // check position +- delta for valid placement - if ((p->label_placement == LINE_PLACEMENT && - build_path_follow(p, *itr + (i*s)) ) || - (p->label_placement == POINT_PLACEMENT && - build_path_horizontal(p, *itr + (i*s))) ) - { - update_detector(p); - placed = true; - break; - } - } + update_detector(p); + break; } } } template - void placement_finder::update_detector(placement *p) - { - while (!p->envelopes.empty()) - { - Envelope e = p->envelopes.front(); - - detector_.insert(e, p->info->get_string()); - - p->envelopes.pop(); - } - } - - template - bool placement_finder::build_path_follow(placement *p, double target_distance) - { - double new_x, new_y, old_x, old_y; - unsigned cur_node = 0; - double next_char_x = 0; - double next_char_y = 0; - - double angle = 0.0; - int orientation = 0; - double displacement = boost::tuples::get<1>(p->displacement_); // displace by dy - - //p->current_placement.clear(); //TODO !! - std::auto_ptr current_placement(new placement_element); - - double x, y; - x = y = 0.0; - - double distance = 0.0; - - std::pair string_dimensions = p->info->get_dimensions(); - // double string_width = string_dimensions.first; - double string_height = string_dimensions.second; - - // find the segment that our text should start on - p->shape_path.rewind(0); - p->shape_path.vertex(&new_x,&new_y); - old_x = new_x; - old_y = new_y; - unsigned num_points = p->geom.num_points(); - for (unsigned i = 1; i < num_points; ++i) - { - double dx, dy; - - cur_node++; - - old_x = new_x; - old_y = new_y; - - p->shape_path.vertex(&new_x,&new_y); - - dx = new_x - old_x; - dy = new_y - old_y; - - double segment_length = sqrt(dx*dx + dy*dy); - - distance += segment_length; - if (distance > target_distance) - { - // this segment is greater that the target starting distance so start here - current_placement->starting_x = new_x - dx*(distance - target_distance)/segment_length; - current_placement->starting_y = new_y - dy*(distance - target_distance)/segment_length; - - // angle text starts at and orientation - angle = atan2(-dy, dx); - orientation = (angle > 0.55*M_PI || angle < -0.45*M_PI) ? -1 : 1; - - distance -= target_distance; - - break; - } - } - - // now find the placement of each character starting from our initial segment - // determined above - double last_angle = angle; - for (unsigned i = 0; i < p->info->num_characters(); i++) - { - character_info ci; - unsigned c; - - // grab the next character according to the orientation - ci = orientation > 0 ? p->info->at(i) : p->info->at(p->info->num_characters() - i - 1); - c = ci.character; - - double angle_delta = 0; - - // if the distance remaining in this segment is less than the character width - // move to the next segment - if (distance <= ci.width) - { - last_angle = angle; - while (distance <= ci.width) - { - double dx, dy; - - cur_node++; - - if (cur_node >= p->geom.num_points()) { - return false; - } - - old_x = new_x; - old_y = new_y; - - p->shape_path.vertex(&new_x,&new_y); - - dx = new_x - old_x; - dy = new_y - old_y; - - angle = atan2(-dy, dx ); - distance += sqrt(dx*dx+dy*dy); - } - // since our rendering angle has changed then check against our - // max allowable angle change. - angle_delta = last_angle - angle; - // normalise between -180 and 180 - while (angle_delta > M_PI) - angle_delta -= 2*M_PI; - while (angle_delta < -M_PI) - angle_delta += 2*M_PI; - if (p->max_char_angle_delta > 0 && fabs(angle_delta) > p->max_char_angle_delta*(M_PI/180)) - { - return false; - } - } - - - Envelope e; - if (p->has_dimensions) - { - e.init(x, y, x + p->dimensions.first, y + p->dimensions.second); - } - - - double render_angle = angle; - - x = new_x - (distance)*cos(angle); - y = new_y + (distance)*sin(angle); - //Center the text on the line, unless displacement != 0 - if (displacement == 0.0) { - x -= (((double)string_height/2.0) - 1.0)*cos(render_angle+M_PI/2); - y += (((double)string_height/2.0) - 1.0)*sin(render_angle+M_PI/2); - } else if (displacement*orientation > 0.0) { - x -= ((fabs(displacement) - (double)string_height) + 1.0)*cos(render_angle+M_PI/2); - y += ((fabs(displacement) - (double)string_height) + 1.0)*sin(render_angle+M_PI/2); - } else { // displacement < 0 - x -= ((fabs(displacement) + (double)string_height) - 1.0)*cos(render_angle+M_PI/2); - y += ((fabs(displacement) + (double)string_height) - 1.0)*sin(render_angle+M_PI/2); - } - distance -= ci.width; - next_char_x = ci.width*cos(render_angle); - next_char_y = ci.width*sin(render_angle); - - double render_x = x; - double render_y = y; - - if (!p->has_dimensions) - { - // put four corners of the letter into envelope - e.init(render_x, render_y, render_x + ci.width*cos(render_angle), render_y - ci.width*sin(render_angle)); - e.expand_to_include(render_x - ci.height*sin(render_angle), render_y - ci.height*cos(render_angle)); - e.expand_to_include(render_x + (ci.width*cos(render_angle) - ci.height*sin(render_angle)), render_y - (ci.width*sin(render_angle) + ci.height*cos(render_angle))); - } - - if (!detector_.has_placement(e, p->info->get_string(), p->minimum_distance)) - { - return false; - } - - if (p->avoid_edges && !dimensions_.contains(e)) - { - return false; - } - - p->envelopes.push(e); - - if (orientation < 0) - { - // rotate in place - render_x += ci.width*cos(render_angle) - (string_height-2)*sin(render_angle); - render_y -= ci.width*sin(render_angle) + (string_height-2)*cos(render_angle); - render_angle += M_PI; - } - - - current_placement->add_node(c,render_x - current_placement->starting_x, - -render_y + current_placement->starting_y, - render_angle); - x += next_char_x; - y -= next_char_y; - } - - p->placements.push_back(current_placement.release()); - - return true; - } - - template - bool placement_finder::build_path_horizontal(placement *p, double target_distance) + void placement_finder::find_point_placement(placement & p, + double label_x, double label_y) { double x, y; - - //p->current_placement.clear(); // TODO std::auto_ptr current_placement(new placement_element); - std::pair string_dimensions = p->info->get_dimensions(); + std::pair string_dimensions = p.info.get_dimensions(); double string_width = string_dimensions.first; double string_height = string_dimensions.second; - + // check if we need to wrap the string double wrap_at = string_width + 1; - if (p->wrap_width && string_width > p->wrap_width) + if (p.wrap_width && string_width > p.wrap_width) { - if (p->text_ratio) - for (int i = 1; ((wrap_at = string_width/i)/(string_height*i)) > p->text_ratio && (string_width/i) > p->wrap_width; ++i); + if (p.text_ratio) + for (int i = 1; ((wrap_at = string_width/i)/(string_height*i)) > p.text_ratio && (string_width/i) > p.wrap_width; ++i); else - wrap_at = p->wrap_width; + wrap_at = p.wrap_width; } // work out where our line breaks need to be std::vector line_breaks; std::vector line_widths; - if (wrap_at < string_width && p->info->num_characters() > 0) + if (wrap_at < string_width && p.info.num_characters() > 0) { int line_count=0; int last_space = 0; @@ -545,10 +264,10 @@ namespace mapnik double line_height = 0; double word_width = 0; double word_height = 0; - for (unsigned int ii = 0; ii < p->info->num_characters(); ii++) + for (unsigned int ii = 0; ii < p.info.num_characters(); ii++) { character_info ci; - ci = p->info->at(ii); + ci = p.info.at(ii); unsigned c = ci.character; word_width += ci.width; @@ -579,38 +298,24 @@ namespace mapnik } line_width += word_width; string_width = string_width > line_width ? string_width : line_width; - line_breaks.push_back(p->info->num_characters() + 1); + line_breaks.push_back(p.info.num_characters() + 1); line_widths.push_back(line_width); } if (line_breaks.size() == 0) { - line_breaks.push_back(p->info->num_characters() + 1); + line_breaks.push_back(p.info.num_characters() + 1); line_widths.push_back(string_width); } - p->info->set_dimensions(string_width, string_height); - - if (p->geom.type() == LineString) - { - std::pair starting_pos = p->get_position_at_distance(target_distance); - - current_placement->starting_x = starting_pos.first; - current_placement->starting_y = starting_pos.second; - } - else - { - p->geom.label_position(¤t_placement->starting_x, ¤t_placement->starting_y); - // TODO: - // We would only want label position in final 'paper' coords. - // Move view and proj transforms to e.g. label_position(x,y,proj_trans,ctrans)? - double z=0; - p->proj_trans->backward(current_placement->starting_x,current_placement->starting_y, z); - p->ctrans->forward(¤t_placement->starting_x, ¤t_placement->starting_y); - // apply displacement ( in pixels ) - current_placement->starting_x += boost::tuples::get<0>(p->displacement_); - current_placement->starting_y += boost::tuples::get<1>(p->displacement_); - } - + p.info.set_dimensions(string_width, string_height); + + + current_placement->starting_x = label_x; + current_placement->starting_y = label_y; + + current_placement->starting_x += boost::tuples::get<0>(p.displacement_); + current_placement->starting_y += boost::tuples::get<1>(p.displacement_); + double line_height = 0; unsigned int line_number = 0; unsigned int index_to_wrap_at = line_breaks[line_number]; @@ -619,10 +324,10 @@ namespace mapnik x = -line_width/2.0 - 1.0; y = -string_height/2.0 + 1.0; - for (unsigned i = 0; i < p->info->num_characters(); i++) + for (unsigned i = 0; i < p.info.num_characters(); i++) { character_info ci;; - ci = p->info->at(i); + ci = p.info.at(i); unsigned c = ci.character; if (i == index_to_wrap_at) @@ -639,12 +344,12 @@ namespace mapnik current_placement->add_node(c, x, y, 0.0); Envelope e; - if (p->has_dimensions) + if (p.has_dimensions) { - e.init(current_placement->starting_x - (p->dimensions.first/2.0), - current_placement->starting_y - (p->dimensions.second/2.0), - current_placement->starting_x + (p->dimensions.first/2.0), - current_placement->starting_y + (p->dimensions.second/2.0)); + e.init(current_placement->starting_x - (p.dimensions.first/2.0), + current_placement->starting_y - (p.dimensions.second/2.0), + current_placement->starting_x + (p.dimensions.first/2.0), + current_placement->starting_y + (p.dimensions.second/2.0)); } else { @@ -654,22 +359,520 @@ namespace mapnik current_placement->starting_y - y - ci.height); } - if (!detector_.has_placement(e, p->info->get_string(), p->minimum_distance)) + if (!dimensions_.intersects(e) || !detector_.has_placement(e, p.info.get_string(), p.minimum_distance)) { - return false; + return ; } - - if (p->avoid_edges && !dimensions_.contains(e)) - { - return false; - } - - p->envelopes.push(e); + + p.envelopes.push(e); } x += ci.width; line_height = line_height > ci.height ? line_height : ci.height; } - p->placements.push_back(current_placement.release()); + p.placements.push_back(current_placement.release()); + update_detector(p); + } + + + template + template + void placement_finder::find_placements_with_spacing(placement & p, PathT & shape_path) + { + double new_x = 0.0; + double new_y = 0.0; + double old_x = 0.0; + double old_y = 0.0; + double x = 0.0; + double y = 0.0; + + double next_char_x = 0.0; + double next_char_y = 0.0; + + shape_path.rewind(0); + unsigned cmd; + bool first = true; + double distance = 0.0; + std::pair string_dimensions = p.info.get_dimensions(); + + double string_height = string_dimensions.second; + double spacing = p.label_spacing; + + double angle = 0.0; + int orientation = 0; + double displacement = boost::tuples::get<1>(p.displacement_); // displace by dy + double target_distance = spacing; + while (!agg::is_stop(cmd = shape_path.vertex(&new_x,&new_y))) + { + if (first || agg::is_move_to(cmd)) + { + first = false; + } + else + { + double dx = new_x - old_x; + double dy = new_y - old_y; + double segment_length = ::sqrt(dx*dx + dy*dy); + distance += segment_length; + + while (distance > target_distance) + { + // got initial segment + std::auto_ptr current_placement(new placement_element); + current_placement->starting_x = new_x - dx*(distance - target_distance)/segment_length; + current_placement->starting_y = new_y - dy*(distance - target_distance)/segment_length; + angle = atan2(-dy, dx); + orientation = (angle > 0.55*M_PI || angle < -0.45*M_PI) ? -1 : 1; + distance -= target_distance; + // now find the placement of each character starting from our initial segment + // determined above + double last_angle = angle; + bool status = true; + for (unsigned i = 0; i < p.info.num_characters(); ++i) + { + character_info ci; + unsigned c; + + // grab the next character according to the orientation + ci = orientation > 0 ? p.info.at(i) : p.info.at(p.info.num_characters() - i - 1); + c = ci.character; + + double angle_delta = 0; + + // if the distance remaining in this segment is less than the character width + // move to the next segment + if (distance <= ci.width) + { + last_angle = angle; + while (distance <= ci.width) + { + old_x = new_x; + old_y = new_y; + if (agg::is_stop(shape_path.vertex(&new_x,&new_y))) + { + status = false; + break; + } + dx = new_x - old_x; + dy = new_y - old_y; + + angle = atan2(-dy, dx ); + distance += sqrt(dx*dx+dy*dy); + } + // since our rendering angle has changed then check against our + // max allowable angle change. + angle_delta = last_angle - angle; + // normalise between -180 and 180 + while (angle_delta > M_PI) + angle_delta -= 2*M_PI; + while (angle_delta < -M_PI) + angle_delta += 2*M_PI; + if (p.max_char_angle_delta > 0 && fabs(angle_delta) > p.max_char_angle_delta*(M_PI/180)) + { + status = false; + } + } + + Envelope e; + if (p.has_dimensions) + { + e.init(x, y, x + p.dimensions.first, y + p.dimensions.second); + } + + double render_angle = angle; + + x = new_x - (distance)*cos(angle); + y = new_y + (distance)*sin(angle); + + //Center the text on the line, unless displacement != 0 + if (displacement == 0.0) + { + x -= (((double)string_height/2.0) - 1.0)*cos(render_angle+M_PI/2); + y += (((double)string_height/2.0) - 1.0)*sin(render_angle+M_PI/2); + } + else if (displacement*orientation > 0.0) + { + x -= ((fabs(displacement) - (double)string_height) + 1.0)*cos(render_angle+M_PI/2); + y += ((fabs(displacement) - (double)string_height) + 1.0)*sin(render_angle+M_PI/2); + } + else + { // displacement < 0 + x -= ((fabs(displacement) + (double)string_height) - 1.0)*cos(render_angle+M_PI/2); + y += ((fabs(displacement) + (double)string_height) - 1.0)*sin(render_angle+M_PI/2); + } + + distance -= ci.width; + next_char_x = ci.width*cos(render_angle); + next_char_y = ci.width*sin(render_angle); + + double render_x = x; + double render_y = y; + + if (!p.has_dimensions) + { + // put four corners of the letter into envelope + e.init(render_x, render_y, render_x + ci.width*cos(render_angle), render_y - ci.width*sin(render_angle)); + e.expand_to_include(render_x - ci.height*sin(render_angle), render_y - ci.height*cos(render_angle)); + e.expand_to_include(render_x + (ci.width*cos(render_angle) - ci.height*sin(render_angle)), + render_y - (ci.width*sin(render_angle) + ci.height*cos(render_angle))); + } + + if (!dimensions_.intersects(e) || !detector_.has_placement(e, p.info.get_string(), p.minimum_distance)) + { + status = false; + } + + p.envelopes.push(e); + + if (orientation < 0) + { + // rotate in place + render_x += ci.width*cos(render_angle) - (string_height-2)*sin(render_angle); + render_y -= ci.width*sin(render_angle) + (string_height-2)*cos(render_angle); + render_angle += M_PI; + } + + current_placement->add_node(c,render_x - current_placement->starting_x, + -render_y + current_placement->starting_y, + render_angle); + x += next_char_x; + y -= next_char_y; + } + + if (status) + { + p.placements.push_back(current_placement.release()); + update_detector(p); + } + } + } + + old_x = new_x; + old_y = new_y; + } + } + + + template + void placement_finder::update_detector(placement & p) + { + while (!p.envelopes.empty()) + { + Envelope e = p.envelopes.front(); + detector_.insert(e, p.info.get_string()); + p.envelopes.pop(); + } + } + + template + template + bool placement_finder::build_path_follow(placement & p, double target_distance, PathT & shape_path) + { + double new_x = 0.0; + double new_y = 0.0; + double old_x = 0.0; + double old_y = 0.0; + double next_char_x = 0.0; + double next_char_y = 0.0; + + double angle = 0.0; + int orientation = 0; + double displacement = boost::tuples::get<1>(p.displacement_); // displace by dy + + std::auto_ptr current_placement(new placement_element); + + double x = 0.0; + double y = 0.0; + + double distance = 0.0; + + std::pair string_dimensions = p.info.get_dimensions(); + double string_height = string_dimensions.second; + + // find the segment that our text should start on + shape_path.rewind(0); + + unsigned cmd; + bool first = true; + while (!agg::is_stop(cmd = shape_path.vertex(&new_x,&new_y))) + { + if (first || agg::is_move_to(cmd)) + { + first = false; + } + else + { + double dx = new_x - old_x; + double dy = new_y - old_y; + double segment_length = sqrt(dx*dx + dy*dy); + distance += segment_length; + if (distance > target_distance) + { + current_placement->starting_x = new_x - dx*(distance - target_distance)/segment_length; + current_placement->starting_y = new_y - dy*(distance - target_distance)/segment_length; + + // angle text starts at and orientation + angle = atan2(-dy, dx); + orientation = (angle > 0.55*M_PI || angle < -0.45*M_PI) ? -1 : 1; + + distance -= target_distance; + + break; + } + } + old_x = new_x; + old_y = new_y; + } + + // now find the placement of each character starting from our initial segment + // determined above + double last_angle = angle; + for (unsigned i = 0; i < p.info.num_characters(); ++i) + { + character_info ci; + unsigned c; + + // grab the next character according to the orientation + ci = orientation > 0 ? p.info.at(i) : p.info.at(p.info.num_characters() - i - 1); + c = ci.character; + + double angle_delta = 0; + + // if the distance remaining in this segment is less than the character width + // move to the next segment + if (distance <= ci.width) + { + last_angle = angle; + while (distance <= ci.width) + { + double dx, dy; + old_x = new_x; + old_y = new_y; + + if (agg::is_stop(shape_path.vertex(&new_x,&new_y))) + return false; + dx = new_x - old_x; + dy = new_y - old_y; + + angle = atan2(-dy, dx ); + distance += sqrt(dx*dx+dy*dy); + } + // since our rendering angle has changed then check against our + // max allowable angle change. + angle_delta = last_angle - angle; + // normalise between -180 and 180 + while (angle_delta > M_PI) + angle_delta -= 2*M_PI; + while (angle_delta < -M_PI) + angle_delta += 2*M_PI; + if (p.max_char_angle_delta > 0 && fabs(angle_delta) > p.max_char_angle_delta*(M_PI/180)) + { + return false; + } + } + + Envelope e; + if (p.has_dimensions) + { + e.init(x, y, x + p.dimensions.first, y + p.dimensions.second); + } + + double render_angle = angle; + + x = new_x - (distance)*cos(angle); + y = new_y + (distance)*sin(angle); + //Center the text on the line, unless displacement != 0 + if (displacement == 0.0) { + x -= (((double)string_height/2.0) - 1.0)*cos(render_angle+M_PI/2); + y += (((double)string_height/2.0) - 1.0)*sin(render_angle+M_PI/2); + } else if (displacement*orientation > 0.0) { + x -= ((fabs(displacement) - (double)string_height) + 1.0)*cos(render_angle+M_PI/2); + y += ((fabs(displacement) - (double)string_height) + 1.0)*sin(render_angle+M_PI/2); + } else { // displacement < 0 + x -= ((fabs(displacement) + (double)string_height) - 1.0)*cos(render_angle+M_PI/2); + y += ((fabs(displacement) + (double)string_height) - 1.0)*sin(render_angle+M_PI/2); + } + distance -= ci.width; + next_char_x = ci.width*cos(render_angle); + next_char_y = ci.width*sin(render_angle); + + double render_x = x; + double render_y = y; + + if (!p.has_dimensions) + { + // put four corners of the letter into envelope + e.init(render_x, render_y, render_x + ci.width*cos(render_angle), render_y - ci.width*sin(render_angle)); + e.expand_to_include(render_x - ci.height*sin(render_angle), render_y - ci.height*cos(render_angle)); + e.expand_to_include(render_x + (ci.width*cos(render_angle) - ci.height*sin(render_angle)), + render_y - (ci.width*sin(render_angle) + ci.height*cos(render_angle))); + } + + if (!dimensions_.intersects(e) || !detector_.has_placement(e, p.info.get_string(), p.minimum_distance)) + { + return false; + } + + p.envelopes.push(e); + + if (orientation < 0) + { + // rotate in place + render_x += ci.width*cos(render_angle) - (string_height-2)*sin(render_angle); + render_y -= ci.width*sin(render_angle) + (string_height-2)*cos(render_angle); + render_angle += M_PI; + } + + + current_placement->add_node(c,render_x - current_placement->starting_x, + -render_y + current_placement->starting_y, + render_angle); + x += next_char_x; + y -= next_char_y; + } + + p.placements.push_back(current_placement.release()); + + return true; + } + + template + template + bool placement_finder::build_path_horizontal(placement & p, double target_distance, PathT & shape_path) + { + double x, y; + std::auto_ptr current_placement(new placement_element); + + std::pair string_dimensions = p.info.get_dimensions(); + double string_width = string_dimensions.first; + double string_height = string_dimensions.second; + + // check if we need to wrap the string + double wrap_at = string_width + 1; + if (p.wrap_width && string_width > p.wrap_width) + { + if (p.text_ratio) + for (int i = 1; ((wrap_at = string_width/i)/(string_height*i)) > p.text_ratio && (string_width/i) > p.wrap_width; ++i); + else + wrap_at = p.wrap_width; + } + + // work out where our line breaks need to be + std::vector line_breaks; + std::vector line_widths; + if (wrap_at < string_width && p.info.num_characters() > 0) + { + int line_count=0; + int last_space = 0; + string_width = 0; + string_height = 0; + double line_width = 0; + double line_height = 0; + double word_width = 0; + double word_height = 0; + for (unsigned int ii = 0; ii < p.info.num_characters(); ii++) + { + character_info ci; + ci = p.info.at(ii); + + unsigned c = ci.character; + word_width += ci.width; + word_height = word_height > ci.height ? word_height : ci.height; + ++line_count; + + if (c == ' ') + { + last_space = ii; + line_width += word_width; + line_height = line_height > word_height ? line_height : word_height; + word_width = 0; + word_height = 0; + } + if (line_width > 0 && line_width > wrap_at) + { + string_width = string_width > line_width ? string_width : line_width; + string_height += line_height; + line_breaks.push_back(last_space); + line_widths.push_back(line_width); + ii = last_space; + line_count = 0; + line_width = 0; + line_height = 0; + word_width = 0; + word_height = 0; + } + } + line_width += word_width; + string_width = string_width > line_width ? string_width : line_width; + line_breaks.push_back(p.info.num_characters() + 1); + line_widths.push_back(line_width); + } + if (line_breaks.size() == 0) + { + line_breaks.push_back(p.info.num_characters() + 1); + line_widths.push_back(string_width); + } + + p.info.set_dimensions(string_width, string_height); + + std::pair starting_pos = + get_position_at_distance(target_distance,shape_path); + current_placement->starting_x = starting_pos.first; + current_placement->starting_y = starting_pos.second; + + double line_height = 0; + unsigned int line_number = 0; + unsigned int index_to_wrap_at = line_breaks[line_number]; + double line_width = line_widths[line_number]; + + x = -line_width/2.0 - 1.0; + y = -string_height/2.0 + 1.0; + + for (unsigned i = 0; i < p.info.num_characters(); i++) + { + character_info ci;; + ci = p.info.at(i); + + unsigned c = ci.character; + if (i == index_to_wrap_at) + { + index_to_wrap_at = line_breaks[++line_number]; + line_width = line_widths[line_number]; + y -= line_height; + x = -line_width/2.0; + line_height = 0; + continue; + } + else + { + current_placement->add_node(c, x, y, 0.0); + + Envelope e; + if (p.has_dimensions) + { + e.init(current_placement->starting_x - (p.dimensions.first/2.0), + current_placement->starting_y - (p.dimensions.second/2.0), + current_placement->starting_x + (p.dimensions.first/2.0), + current_placement->starting_y + (p.dimensions.second/2.0)); + } + else + { + e.init(current_placement->starting_x + x, + current_placement->starting_y - y, + current_placement->starting_x + x + ci.width, + current_placement->starting_y - y - ci.height); + } + + if (!dimensions_.intersects(e) || !detector_.has_placement(e, p.info.get_string(), p.minimum_distance)) + { + return false; + } + + p.envelopes.push(e); + } + x += ci.width; + line_height = line_height > ci.height ? line_height : ci.height; + } + p.placements.push_back(current_placement.release()); return true; } @@ -680,6 +883,11 @@ namespace mapnik detector_.clear(); } - template class placement_finder; + typedef coord_transform2 PathType; + typedef label_collision_detector4 DetectorType; -} // namespace + template class placement_finder; + template void placement_finder::find_placements (placement&, PathType & ); + template void placement_finder::find_placements_with_spacing (placement&, PathType & ); + +} // namespace diff --git a/src/save_map.cpp b/src/save_map.cpp index 9d0741434..18696cb77 100644 --- a/src/save_map.cpp +++ b/src/save_map.cpp @@ -176,6 +176,11 @@ namespace mapnik set_css( sym_node, "fill-opacity", sym.get_opacity() ); } } + + void operator () ( markers_symbolizer const& ) + { + // FIXME!!!!! + } private: serialize_symbolizer(); void add_image_attributes(ptree & node, const symbolizer_with_image & sym) diff --git a/src/wkb.cpp b/src/wkb.cpp index 26c2d88c7..2f2e3b8dd 100644 --- a/src/wkb.cpp +++ b/src/wkb.cpp @@ -70,7 +70,7 @@ namespace mapnik ~wkb_reader() {} - void read(Feature & feature) + void read_multi(Feature & feature) { int type=read_integer(); switch (type) @@ -99,6 +99,36 @@ namespace mapnik break; } } + + void read(Feature & feature) + { + int type=read_integer(); + switch (type) + { + case wkbPoint: + read_point(feature); + break; + case wkbLineString: + read_linestring(feature); + break; + case wkbPolygon: + read_polygon(feature); + break; + case wkbMultiPoint: + read_multipoint_2(feature); + break; + case wkbMultiLineString: + read_multilinestring_2(feature); + break; + case wkbMultiPolygon: + read_multipolygon_2(feature); + break; + case wkbGeometryCollection: + break; + default: + break; + } + } private: wkb_reader(const wkb_reader&); @@ -189,6 +219,20 @@ namespace mapnik } } + void read_multipoint_2(Feature & feature) + { + geometry2d * pt = new point; + int num_points = read_integer(); + for (int i=0;imove_to(x,y); + } + feature.add_geometry(pt); + } + void read_linestring(Feature & feature) { geometry2d * line = new line_string; @@ -214,17 +258,41 @@ namespace mapnik } } + void read_multilinestring_2(Feature & feature) + { + geometry2d * line = new line_string; + int num_lines=read_integer(); + unsigned capacity = 0; + for (int i=0;iset_capacity(capacity); + line->move_to(ar[0].x,ar[0].y); + for (int i=1;iline_to(ar[i].x,ar[i].y); + } + } + feature.add_geometry(line); + } + void read_polygon(Feature & feature) { geometry2d * poly = new polygon; int num_rings=read_integer(); + unsigned capacity = 0; for (int i=0;iset_capacity(capacity); poly->move_to(ar[0].x,ar[0].y); - for (int j=1;jline_to(ar[j].x,ar[j].y); @@ -243,11 +311,42 @@ namespace mapnik read_polygon(feature); } } + + void read_multipolygon_2(Feature & feature) + { + geometry2d * poly = new polygon; + int num_polys=read_integer(); + unsigned capacity = 0; + for (int i=0;iset_capacity(capacity); + poly->move_to(ar[0].x,ar[0].y); + + for (int j=1;jline_to(ar[j].x,ar[j].y); + } + poly->line_to(ar[0].x,ar[0].y); + } + } + feature.add_geometry(poly); + } }; - void geometry_utils::from_wkb(Feature & feature,const char* wkb, unsigned size) + void geometry_utils::from_wkb(Feature & feature,const char* wkb, unsigned size, bool multiple_geometries) { wkb_reader reader(wkb,size); - return reader.read(feature); + if (multiple_geometries) + return reader.read_multi(feature); + else + return reader.read(feature); } }