362 lines
13 KiB
C++
362 lines
13 KiB
C++
/*****************************************************************************
|
|
*
|
|
* This file is part of Mapnik (c++ mapping toolkit)
|
|
*
|
|
* Copyright (C) 2013 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 <mapnik/text/symbolizer_helpers.hpp>
|
|
#include <mapnik/label_collision_detector.hpp>
|
|
#include <mapnik/font_engine_freetype.hpp>
|
|
#include <mapnik/text/text_layout.hpp>
|
|
#include <mapnik/geom_util.hpp>
|
|
#include <mapnik/parse_path.hpp>
|
|
#include <mapnik/debug.hpp>
|
|
#include <mapnik/symbolizer.hpp>
|
|
#include <mapnik/value_types.hpp>
|
|
#include <mapnik/text/placement_finder_impl.hpp>
|
|
#include <mapnik/text/placements/base.hpp>
|
|
#include <mapnik/text/placements/dummy.hpp>
|
|
|
|
//agg
|
|
#include "agg_conv_clip_polyline.h"
|
|
|
|
namespace mapnik {
|
|
|
|
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<double> 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),
|
|
clipped_(get<bool>(sym_, keys::clip, feature_, vars_, false)),
|
|
placement_(get<text_placements_ptr>(sym_, keys::text_placements_)->get_placement_info(scale_factor))
|
|
{
|
|
placement_->properties.evaluate_text_properties(feature_, vars_);
|
|
initialize_geometries();
|
|
if (!geometries_to_process_.size()) return; // FIXME - bad practise
|
|
initialize_points();
|
|
}
|
|
|
|
struct largest_bbox_first
|
|
{
|
|
bool operator() (geometry_type const* g0, geometry_type const* g1) const
|
|
{
|
|
box2d<double> b0 = g0->envelope();
|
|
box2d<double> b1 = g1->envelope();
|
|
return b0.width()*b0.height() > b1.width()*b1.height();
|
|
}
|
|
};
|
|
|
|
void base_symbolizer_helper::initialize_geometries()
|
|
{
|
|
bool largest_box_only = placement_->properties.largest_bbox_only;
|
|
double minimum_path_length = placement_->properties.minimum_path_length;
|
|
for ( auto const& geom : feature_.paths())
|
|
{
|
|
// don't bother with empty geometries
|
|
if (geom.size() == 0) continue;
|
|
mapnik::geometry_type::types type = geom.type();
|
|
if (type == geometry_type::types::Polygon)
|
|
{
|
|
if (minimum_path_length > 0)
|
|
{
|
|
box2d<double> gbox = t_.forward(geom.envelope(), prj_trans_);
|
|
if (gbox.width() < minimum_path_length)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
// TODO - calculate length here as well
|
|
geometries_to_process_.push_back(const_cast<geometry_type*>(&geom));
|
|
}
|
|
|
|
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()
|
|
{
|
|
label_placement_enum how_placed = placement_->properties.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 * geom_ptr : geometries_to_process_)
|
|
{
|
|
geometry_type const& geom = *geom_ptr;
|
|
if (how_placed == VERTEX_PLACEMENT)
|
|
{
|
|
geom.rewind(0);
|
|
for(unsigned i = 0; i < geom.size(); ++i)
|
|
{
|
|
geom.vertex(&label_x, &label_y);
|
|
prj_trans_.backward(label_x, label_y, z);
|
|
t_.forward(&label_x, &label_y);
|
|
points_.push_back(pixel_position(label_x, label_y));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// https://github.com/mapnik/mapnik/issues/1423
|
|
bool success = false;
|
|
// https://github.com/mapnik/mapnik/issues/1350
|
|
if (geom.type() == geometry_type::types::LineString)
|
|
{
|
|
success = label::middle_point(geom, label_x,label_y);
|
|
}
|
|
else if (how_placed == POINT_PLACEMENT)
|
|
{
|
|
success = label::centroid(geom, label_x, label_y);
|
|
}
|
|
else if (how_placed == INTERIOR_PLACEMENT)
|
|
{
|
|
success = label::interior_position(geom, 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_.push_back(pixel_position(label_x, label_y));
|
|
}
|
|
}
|
|
}
|
|
point_itr_ = points_.begin();
|
|
}
|
|
|
|
template <typename FaceManagerT, typename DetectorT>
|
|
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<double> 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_, *placement_, font_manager, scale_factor),
|
|
adapter_(finder_,false),
|
|
converter_(query_extent_, adapter_, sym_, t, prj_trans, affine_trans, feature, vars, scale_factor)
|
|
{
|
|
|
|
// setup vertex converter
|
|
bool clip = mapnik::get<bool>(sym_, keys::clip, feature_, vars_, false);
|
|
double simplify_tolerance = mapnik::get<double>(sym_, keys::simplify_tolerance, feature_, vars_, 0.0);
|
|
double smooth = mapnik::get<double>(sym_, keys::smooth, feature_, vars_, 0.0);
|
|
|
|
if (clip) converter_.template set<clip_line_tag>(); //optional clip (default: true)
|
|
converter_.template set<transform_tag>(); //always transform
|
|
converter_.template set<affine_transform_tag>();
|
|
if (simplify_tolerance > 0.0) converter_.template set<simplify_tag>(); // optional simplify converter
|
|
if (smooth > 0.0) converter_.template set<smooth_tag>(); // optional smooth converter
|
|
|
|
if (geometries_to_process_.size()) finder_.next_position();
|
|
}
|
|
|
|
placements_list const& text_symbolizer_helper::get()
|
|
{
|
|
if (point_placement_)
|
|
{
|
|
while (next_point_placement());
|
|
}
|
|
else
|
|
{
|
|
while (next_line_placement(clipped_));
|
|
}
|
|
return finder_.placements();
|
|
}
|
|
|
|
bool text_symbolizer_helper::next_line_placement(bool clipped)
|
|
{
|
|
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
|
|
}
|
|
|
|
converter_.apply(**geo_itr_);
|
|
if (adapter_.status())
|
|
{
|
|
//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()
|
|
{
|
|
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 <typename FaceManagerT, typename DetectorT>
|
|
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<double> 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_, *placement_, font_manager, scale_factor),
|
|
adapter_(finder_,true),
|
|
converter_(query_extent_, adapter_, sym_, t, prj_trans, affine_trans, feature, vars, scale_factor)
|
|
{
|
|
// setup vertex converter
|
|
bool clip = mapnik::get<bool>(sym_, keys::clip, feature_, vars_, false);
|
|
double simplify_tolerance = mapnik::get<double>(sym_, keys::simplify_tolerance, feature_, vars_, 0.0);
|
|
double smooth = mapnik::get<double>(sym_, keys::smooth, feature_, vars_, 0.0);
|
|
|
|
if (clip) converter_.template set<clip_line_tag>(); //optional clip (default: true)
|
|
converter_.template set<transform_tag>(); //always transform
|
|
converter_.template set<affine_transform_tag>();
|
|
if (simplify_tolerance > 0.0) converter_.template set<simplify_tag>(); // optional simplify converter
|
|
if (smooth > 0.0) converter_.template set<smooth_tag>(); // optional smooth converter
|
|
if (geometries_to_process_.size())
|
|
{
|
|
init_marker();
|
|
finder_.next_position();
|
|
}
|
|
}
|
|
|
|
|
|
void text_symbolizer_helper::init_marker()
|
|
{
|
|
std::string filename = mapnik::get<std::string>(sym_, keys::file, feature_, vars_);
|
|
if (filename.empty()) return;
|
|
boost::optional<mapnik::marker_ptr> marker = marker_cache::instance().find(filename, true);
|
|
if (!marker) return;
|
|
//FIXME - need to test this
|
|
//std::string filename = path_processor_type::evaluate(filename_string, feature_);
|
|
agg::trans_affine trans;
|
|
auto image_transform = get_optional<transform_type>(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<double> bbox(px0, py0, px1, py1);
|
|
bbox.expand_to_include(px2, py2);
|
|
bbox.expand_to_include(px3, py3);
|
|
bool unlock_image = mapnik::get<value_bool>(sym_, keys::unlock_image, feature_, vars_, false);
|
|
double shield_dx = mapnik::get<value_double>(sym_, keys::shield_dx, feature_, vars_, 0.0);
|
|
double shield_dy = mapnik::get<value_double>(sym_, keys::shield_dy, feature_, vars_, 0.0);
|
|
pixel_position marker_displacement;
|
|
marker_displacement.set(shield_dx,shield_dy);
|
|
finder_.set_marker(std::make_shared<marker_info>(*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_engine> &font_manager,
|
|
label_collision_detector4 &detector,
|
|
box2d<double> 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_engine> &font_manager,
|
|
label_collision_detector4 &detector,
|
|
box2d<double> const& query_extent,
|
|
agg::trans_affine const&);
|
|
} //namespace
|