/***************************************************************************** * * This file is part of Mapnik (c++ mapping toolkit) * * Copyright (C) 2014 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //agg #include "agg_conv_clip_polyline.h" namespace mapnik { namespace detail { template struct apply_vertex_placement { apply_vertex_placement(Points & points, view_transform const& tr, proj_transform const& prj_trans) : points_(points), tr_(tr), prj_trans_(prj_trans) {} template void operator() (Adapter const& va) const { double label_x, label_y, z = 0; va.rewind(0); for (unsigned cmd; (cmd = va.vertex(&label_x, &label_y)) != SEG_END; ) { if (cmd != SEG_CLOSE) { prj_trans_.backward(label_x, label_y, z); tr_.forward(&label_x, &label_y); points_.emplace_back(label_x, label_y); } } } Points & points_; view_transform const& tr_; proj_transform const& prj_trans_; }; template struct split_multi_geometries { using container_type = T; split_multi_geometries(container_type & cont, view_transform const& t, proj_transform const& prj_trans, double minimum_path_length) : cont_(cont), t_(t), prj_trans_(prj_trans), minimum_path_length_(minimum_path_length) {} void operator() (geometry::geometry_empty const&) const {} void operator() (geometry::multi_point const& multi_pt) const { for ( auto const& pt : multi_pt ) { cont_.push_back(std::move(base_symbolizer_helper::geometry_cref(std::cref(pt)))); } } void operator() (geometry::multi_line_string const& multi_line) const { for ( auto const& line : multi_line ) { cont_.push_back(std::move(base_symbolizer_helper::geometry_cref(std::cref(line)))); } } void operator() (geometry::polygon const& poly) const { if (minimum_path_length_ > 0) { box2d bbox = t_.forward(geometry::envelope(poly), prj_trans_); if (bbox.width() >= minimum_path_length_) { cont_.push_back(std::move(base_symbolizer_helper::geometry_cref(std::cref(poly)))); } } else { cont_.push_back(std::move(base_symbolizer_helper::geometry_cref(std::cref(poly)))); } } void operator() (geometry::multi_polygon const& multi_poly) const { for ( auto const& poly : multi_poly ) { (*this)(poly); } } void operator() (geometry::geometry_collection const& collection) const { for ( auto const& geom : collection) { util::apply_visitor(*this, geom); } } template void operator() (Geometry const& geom) const { cont_.push_back(std::move(base_symbolizer_helper::geometry_cref(std::cref(geom)))); } container_type & cont_; view_transform const& t_; proj_transform const& prj_trans_; double minimum_path_length_; }; } // ns detail base_symbolizer_helper::base_symbolizer_helper( symbolizer_base const& sym, feature_impl const& feature, attributes const& vars, proj_transform const& prj_trans, unsigned width, unsigned height, double scale_factor, view_transform const& t, box2d const& query_extent) : sym_(sym), feature_(feature), vars_(vars), prj_trans_(prj_trans), t_(t), dims_(0, 0, width, height), query_extent_(query_extent), scale_factor_(scale_factor), info_ptr_(mapnik::get(sym_, keys::text_placements_)->get_placement_info(scale_factor,feature_,vars_)), text_props_(evaluate_text_properties(info_ptr_->properties,feature_,vars_)) { initialize_geometries(); if (!geometries_to_process_.size()) return; // FIXME - bad practise initialize_points(); } struct largest_bbox_first { bool operator() (geometry::geometry const* g0, geometry::geometry const* g1) const { box2d b0 = geometry::envelope(*g0); box2d b1 = geometry::envelope(*g1); return b0.width() * b0.height() > b1.width() * b1.height(); } bool operator() (base_symbolizer_helper::geometry_cref const& g0, base_symbolizer_helper::geometry_cref const& g1) const { box2d b0 = geometry::envelope(g0); box2d b1 = geometry::envelope(g1); return b0.width() * b0.height() > b1.width() * b1.height(); } }; void base_symbolizer_helper::initialize_geometries() const { bool largest_box_only = text_props_->largest_bbox_only; double minimum_path_length = text_props_->minimum_path_length; util::apply_visitor(detail::split_multi_geometries (geometries_to_process_, t_, prj_trans_, minimum_path_length ), feature_.get_geometry()); // FIXME: return early if geometries_to_process_.empty() ? if (largest_box_only) { geometries_to_process_.sort(largest_bbox_first()); geo_itr_ = geometries_to_process_.begin(); geometries_to_process_.erase(++geo_itr_, geometries_to_process_.end()); } geo_itr_ = geometries_to_process_.begin(); } void base_symbolizer_helper::initialize_points() const { label_placement_enum how_placed = text_props_->label_placement; if (how_placed == LINE_PLACEMENT) { point_placement_ = false; return; } else { point_placement_ = true; } double label_x=0.0; double label_y=0.0; double z=0.0; for (auto const& geom : geometries_to_process_) { if (how_placed == VERTEX_PLACEMENT) { using apply_vertex_placement = detail::apply_vertex_placement >; apply_vertex_placement apply(points_, t_, prj_trans_); util::apply_visitor(geometry::vertex_processor(apply), geom); } else { // https://github.com/mapnik/mapnik/issues/1423 bool success = false; // https://github.com/mapnik/mapnik/issues/1350 auto type = geometry::geometry_type(geom); // FIXME: how to handle MultiLineString? if (type == geometry::geometry_types::LineString) { auto const& line = mapnik::util::get >(geom); geometry::line_string_vertex_adapter va(line); success = label::middle_point(va, label_x,label_y); } else if (how_placed == POINT_PLACEMENT) { geometry::point pt; geometry::centroid(geom, pt); label_x = pt.x; label_y = pt.y; success = true; } else if (how_placed == INTERIOR_PLACEMENT) // polygon { if (type == geometry::geometry_types::Polygon) { auto const& poly = mapnik::util::get >(geom); geometry::polygon_vertex_adapter va(poly); success = label::interior_position(va, label_x, label_y); } } else { MAPNIK_LOG_ERROR(symbolizer_helpers) << "ERROR: Unknown placement type in initialize_points()"; } if (success) { prj_trans_.backward(label_x, label_y, z); t_.forward(&label_x, &label_y); points_.emplace_back(label_x, label_y); } } } point_itr_ = points_.begin(); } template text_symbolizer_helper::text_symbolizer_helper( text_symbolizer const& sym, feature_impl const& feature, attributes const& vars, proj_transform const& prj_trans, unsigned width, unsigned height, double scale_factor, view_transform const& t, FaceManagerT & font_manager, DetectorT &detector, box2d const& query_extent, agg::trans_affine const& affine_trans) : base_symbolizer_helper(sym, feature, vars, prj_trans, width, height, scale_factor, t, query_extent), finder_(feature, vars, detector, dims_, *info_ptr_, font_manager, scale_factor), adapter_(finder_,false), converter_(query_extent_, sym_, t, prj_trans, affine_trans, feature, vars, scale_factor) { // setup vertex converter value_bool clip = mapnik::get(sym_, feature_, vars_); value_double simplify_tolerance = mapnik::get(sym_, feature_, vars_); value_double smooth = mapnik::get(sym_, feature_, vars_); if (clip) converter_.template set(); //optional clip (default: true) converter_.template set(); //always transform converter_.template set(); if (simplify_tolerance > 0.0) converter_.template set(); // optional simplify converter if (smooth > 0.0) converter_.template set(); // optional smooth converter if (geometries_to_process_.size()) finder_.next_position(); } placements_list const& text_symbolizer_helper::get() const { if (point_placement_) { while (next_point_placement()); } else { while (next_line_placement()); } return finder_.placements(); } class apply_line_placement_visitor { public: apply_line_placement_visitor(vertex_converter_type & converter, placement_finder_adapter const & adapter) : converter_(converter), adapter_(adapter) { } bool operator()(geometry::line_string const & geo) const { geometry::line_string_vertex_adapter va(geo); converter_.apply(va, adapter_); return adapter_.status(); } bool operator()(geometry::polygon const & geo) const { geometry::polygon_vertex_adapter va(geo); converter_.apply(va, adapter_); return adapter_.status(); } template bool operator()(T const & geo) const { return false; } private: vertex_converter_type & converter_; placement_finder_adapter const & adapter_; }; bool text_symbolizer_helper::next_line_placement() const { while (!geometries_to_process_.empty()) { if (geo_itr_ == geometries_to_process_.end()) { //Just processed the last geometry. Try next placement. if (!finder_.next_position()) return false; //No more placements //Start again from begin of list geo_itr_ = geometries_to_process_.begin(); continue; //Reexecute size check } if (mapnik::util::apply_visitor(apply_line_placement_visitor(converter_, adapter_), *geo_itr_)) { //Found a placement geo_itr_ = geometries_to_process_.erase(geo_itr_); return true; } // No placement for this geometry. Keep it in geometries_to_process_ for next try. ++geo_itr_; } return false; } bool text_symbolizer_helper::next_point_placement() const { while (!points_.empty()) { if (point_itr_ == points_.end()) { //Just processed the last point. Try next placement. if (!finder_.next_position()) return false; //No more placements //Start again from begin of list point_itr_ = points_.begin(); continue; //Reexecute size check } if (finder_.find_point_placement(*point_itr_)) { //Found a placement point_itr_ = points_.erase(point_itr_); return true; } //No placement for this point. Keep it in points_ for next try. point_itr_++; } return false; } template text_symbolizer_helper::text_symbolizer_helper( shield_symbolizer const& sym, feature_impl const& feature, attributes const& vars, proj_transform const& prj_trans, unsigned width, unsigned height, double scale_factor, view_transform const& t, FaceManagerT & font_manager, DetectorT & detector, box2d const& query_extent, agg::trans_affine const& affine_trans) : base_symbolizer_helper(sym, feature, vars, prj_trans, width, height, scale_factor, t, query_extent), finder_(feature, vars, detector, dims_, *info_ptr_, font_manager, scale_factor), adapter_(finder_,true), converter_(query_extent_, sym_, t, prj_trans, affine_trans, feature, vars, scale_factor) { // setup vertex converter value_bool clip = mapnik::get(sym_, feature_, vars_); value_double simplify_tolerance = mapnik::get(sym_, feature_, vars_); value_double smooth = mapnik::get(sym_, feature_, vars_); if (clip) converter_.template set(); //optional clip (default: true) converter_.template set(); //always transform converter_.template set(); if (simplify_tolerance > 0.0) converter_.template set(); // optional simplify converter if (smooth > 0.0) converter_.template set(); // optional smooth converter if (geometries_to_process_.size()) { init_marker(); finder_.next_position(); } } void text_symbolizer_helper::init_marker() const { std::string filename = mapnik::get(sym_, feature_, vars_); if (filename.empty()) return; std::shared_ptr marker = marker_cache::instance().find(filename, true); if (marker->is()) return; agg::trans_affine trans; auto image_transform = get_optional(sym_, keys::image_transform); if (image_transform) evaluate_transform(trans, feature_, vars_, *image_transform); double width = marker->width(); double height = marker->height(); double px0 = - 0.5 * width; double py0 = - 0.5 * height; double px1 = 0.5 * width; double py1 = 0.5 * height; double px2 = px1; double py2 = py0; double px3 = px0; double py3 = py1; trans.transform(&px0, &py0); trans.transform(&px1, &py1); trans.transform(&px2, &py2); trans.transform(&px3, &py3); box2d bbox(px0, py0, px1, py1); bbox.expand_to_include(px2, py2); bbox.expand_to_include(px3, py3); value_bool unlock_image = mapnik::get(sym_, feature_, vars_); value_double shield_dx = mapnik::get(sym_, feature_, vars_); value_double shield_dy = mapnik::get(sym_, feature_, vars_); pixel_position marker_displacement; marker_displacement.set(shield_dx,shield_dy); finder_.set_marker(std::make_shared(marker, trans), bbox, unlock_image, marker_displacement); } template text_symbolizer_helper::text_symbolizer_helper( text_symbolizer const& sym, feature_impl const& feature, attributes const& vars, proj_transform const& prj_trans, unsigned width, unsigned height, double scale_factor, view_transform const& t, face_manager_freetype & font_manager, label_collision_detector4 &detector, box2d const& query_extent, agg::trans_affine const&); template text_symbolizer_helper::text_symbolizer_helper( shield_symbolizer const& sym, feature_impl const& feature, attributes const& vars, proj_transform const& prj_trans, unsigned width, unsigned height, double scale_factor, view_transform const& t, face_manager_freetype & font_manager, label_collision_detector4 &detector, box2d const& query_extent, agg::trans_affine const&); } //namespace