Patch from Toby: adds a maximum angle delta for text placement (if the
angle changes too much between characters it finds an alternate placement), this is specified in the max_char_angle_delta property in radians (feel free to change it to degrees). It also improves the text placement around corners trying to minimise the distance between the center of the character and the line on each side. This is the major portion of the patch.
This commit is contained in:
parent
e924b597f4
commit
889ac41694
7 changed files with 196 additions and 91 deletions
|
@ -43,6 +43,9 @@ void export_text_symbolizer()
|
|||
&text_symbolizer::get_halo_fill,
|
||||
return_value_policy<copy_const_reference>()),
|
||||
&text_symbolizer::set_halo_fill)
|
||||
.add_property("halo_radius",
|
||||
&text_symbolizer::get_halo_radius,
|
||||
&text_symbolizer::set_halo_radius)
|
||||
.add_property("wrap_width",
|
||||
&text_symbolizer::get_wrap_width,
|
||||
&text_symbolizer::set_wrap_width)
|
||||
|
@ -52,9 +55,9 @@ void export_text_symbolizer()
|
|||
.add_property("label_spacing",
|
||||
&text_symbolizer::get_label_spacing,
|
||||
&text_symbolizer::set_label_spacing)
|
||||
.add_property("halo_radius",
|
||||
&text_symbolizer::get_halo_radius,
|
||||
&text_symbolizer::set_halo_radius)
|
||||
.add_property("max_char_angle_delta",
|
||||
&text_symbolizer::get_max_char_angle_delta,
|
||||
&text_symbolizer::set_max_char_angle_delta)
|
||||
.def("set_label_placement",&text_symbolizer::set_label_placement,
|
||||
"Set the placement of the label")
|
||||
;
|
||||
|
|
|
@ -290,6 +290,8 @@ popplaces_text_symbolizer.set_label_placement=label_placement.POINT_PLACEMENT
|
|||
popplaces_text_symbolizer.halo_fill = Color('white')
|
||||
popplaces_text_symbolizer.halo_radius = 1
|
||||
popplaces_rule.symbols.append(popplaces_text_symbolizer)
|
||||
popplaces_rule.symbols.append(PointSymbolizer("/home/artem/dot.png","png",10,10))
|
||||
|
||||
popplaces_style.rules.append(popplaces_rule)
|
||||
|
||||
m.append_style('popplaces', popplaces_style)
|
||||
|
|
|
@ -89,6 +89,8 @@ namespace mapnik
|
|||
int text_ratio;
|
||||
|
||||
int label_spacing; // distance between repeated labels on a single geometry
|
||||
|
||||
double max_char_angle_delta;
|
||||
};
|
||||
|
||||
|
||||
|
@ -110,6 +112,7 @@ namespace mapnik
|
|||
void update_detector(placement *p);
|
||||
|
||||
label_collision_detector3 detector_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -49,6 +49,8 @@ namespace mapnik
|
|||
void set_wrap_width(unsigned ratio);
|
||||
unsigned get_label_spacing() const; // spacing between repeated labels on lines
|
||||
void set_label_spacing(unsigned spacing);
|
||||
double get_max_char_angle_delta() const; // maximum change in angle between adjacent characters
|
||||
void set_max_char_angle_delta(double angle);
|
||||
unsigned get_text_size() const;
|
||||
std::string const& get_face_name() const;
|
||||
Color const& get_fill() const;
|
||||
|
@ -69,6 +71,7 @@ namespace mapnik
|
|||
unsigned text_ratio_;
|
||||
unsigned wrap_width_;
|
||||
unsigned label_spacing_;
|
||||
double max_char_angle_delta_;
|
||||
Color fill_;
|
||||
Color halo_fill_;
|
||||
unsigned halo_radius_;
|
||||
|
|
|
@ -507,7 +507,8 @@ namespace mapnik
|
|||
text_placement.text_ratio = sym.get_text_ratio();
|
||||
text_placement.wrap_width = sym.get_wrap_width();
|
||||
text_placement.label_spacing = sym.get_label_spacing();
|
||||
|
||||
text_placement.max_char_angle_delta = sym.get_max_char_angle_delta();
|
||||
|
||||
bool found = finder_.find_placements(&text_placement);
|
||||
if (!found) {
|
||||
return;
|
||||
|
|
|
@ -41,13 +41,13 @@
|
|||
namespace mapnik
|
||||
{
|
||||
placement::placement(string_info *info_, CoordTransform *ctrans_, const proj_transform *proj_trans_, geometry_ptr geom_, std::pair<double, double> dimensions_)
|
||||
: info(info_), ctrans(ctrans_), proj_trans(proj_trans_), geom(geom_), label_placement(point_placement), dimensions(dimensions_), has_dimensions(true), shape_path(*ctrans_, *geom_, *proj_trans_), total_distance_(-1.0), wrap_width(0), text_ratio(0), label_spacing(0)
|
||||
: info(info_), ctrans(ctrans_), proj_trans(proj_trans_), geom(geom_), label_placement(point_placement), dimensions(dimensions_), has_dimensions(true), shape_path(*ctrans_, *geom_, *proj_trans_), total_distance_(-1.0), wrap_width(0), text_ratio(0), label_spacing(0), max_char_angle_delta(0.0)
|
||||
{
|
||||
}
|
||||
|
||||
//For text
|
||||
placement::placement(string_info *info_, CoordTransform *ctrans_, const proj_transform *proj_trans_, geometry_ptr geom_, label_placement_e placement_)
|
||||
: info(info_), ctrans(ctrans_), proj_trans(proj_trans_), geom(geom_), label_placement(placement_), has_dimensions(false), shape_path(*ctrans_, *geom_, *proj_trans_), total_distance_(-1.0), wrap_width(0), text_ratio(0), label_spacing(0)
|
||||
: info(info_), ctrans(ctrans_), proj_trans(proj_trans_), geom(geom_), label_placement(placement_), has_dimensions(false), shape_path(*ctrans_, *geom_, *proj_trans_), total_distance_(-1.0), wrap_width(0), text_ratio(0), label_spacing(0), max_char_angle_delta(0.0)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -162,17 +162,15 @@ namespace mapnik
|
|||
double string_width = string_dimensions.first;
|
||||
// double string_height = string_dimensions.second;
|
||||
|
||||
|
||||
std::clog << "trying to place string: ";
|
||||
for (unsigned int ii = 0; ii < p->info->num_characters(); ++ii)
|
||||
std::clog << static_cast<char> (p->info->at(ii).character);
|
||||
std::clog << std::endl;
|
||||
// for (unsigned int ii = 0; ii < p->info->num_characters(); ++ii)
|
||||
// std::clog << static_cast<char> (p->info->at(ii).character);
|
||||
// std::clog << std::endl;
|
||||
|
||||
double distance = p->get_total_distance();
|
||||
|
||||
if (string_width > distance)
|
||||
{
|
||||
std::clog << "String longer than segment, bailing" << std::endl;
|
||||
//std::clog << "String longer than segment, bailing" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -192,7 +190,7 @@ namespace mapnik
|
|||
bool FoundPlacement = false;
|
||||
for (std::vector<double>::const_iterator itr = ideal_label_distances.begin(); itr < ideal_label_distances.end(); ++itr)
|
||||
{
|
||||
std::clog << "Trying to find txt placement at distance: " << *itr << std::endl;
|
||||
//std::clog << "Trying to find txt placement at distance: " << *itr << std::endl;
|
||||
for (double i = 0; i < ideal_spacing; i += delta)
|
||||
{
|
||||
p->clear_envelopes();
|
||||
|
@ -213,8 +211,8 @@ namespace mapnik
|
|||
}
|
||||
}
|
||||
|
||||
if (FoundPlacement)
|
||||
std::clog << "Found Placement" << string_width << " " << distance << std::endl;
|
||||
// if (FoundPlacement)
|
||||
// std::clog << "Found Placement" << string_width << " " << distance << std::endl;
|
||||
|
||||
return FoundPlacement;
|
||||
}
|
||||
|
@ -270,6 +268,8 @@ namespace mapnik
|
|||
{
|
||||
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;
|
||||
|
@ -285,8 +285,11 @@ namespace mapnik
|
|||
// 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;
|
||||
for (unsigned i = 0; i < p->geom->num_points() - 1; i++)
|
||||
{
|
||||
double dx, dy;
|
||||
|
@ -306,17 +309,13 @@ namespace mapnik
|
|||
distance += segment_length;
|
||||
if (distance > target_distance)
|
||||
{
|
||||
// this segment is greater that the target starting distance so start here
|
||||
p->current_placement.starting_x = new_x - dx*(distance - target_distance)/segment_length;
|
||||
p->current_placement.starting_y = new_y - dy*(distance - target_distance)/segment_length;
|
||||
|
||||
// angle text starts at and orientation
|
||||
angle = atan2(-dy, dx);
|
||||
|
||||
if (angle > M_PI/2 || angle <= -M_PI/2) {
|
||||
orientation = -1;
|
||||
}
|
||||
else {
|
||||
orientation = 1;
|
||||
}
|
||||
orientation = fabs(angle) > M_PI/2 ? -1 : 1;
|
||||
|
||||
distance -= target_distance;
|
||||
|
||||
|
@ -324,42 +323,59 @@ namespace mapnik
|
|||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
while (distance <= 0)
|
||||
{
|
||||
double dx, dy;
|
||||
|
||||
cur_node++;
|
||||
|
||||
if (cur_node >= p->geom->num_points()) {
|
||||
break;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (orientation == -1) {
|
||||
ci = p->info->at(p->info->num_characters() - i - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
ci = p->info->at(i);
|
||||
}
|
||||
// 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()) {
|
||||
break;
|
||||
}
|
||||
|
||||
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 -= M_PI;
|
||||
while (angle_delta < -M_PI)
|
||||
angle_delta += M_PI;
|
||||
if (p->max_char_angle_delta > 0 && fabs(angle_delta) > p->max_char_angle_delta)
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
|
||||
Envelope<double> e;
|
||||
if (p->has_dimensions)
|
||||
|
@ -367,48 +383,114 @@ namespace mapnik
|
|||
e.init(x, y, x + p->dimensions.first, y + p->dimensions.second);
|
||||
}
|
||||
|
||||
if (orientation == -1) {
|
||||
x = new_x - (distance - ci.width)*cos(angle);
|
||||
y = new_y + (distance - ci.width)*sin(angle);
|
||||
|
||||
//Center the text on the line.
|
||||
x += (((double)string_height/2.0) - 1.0)*cos(angle+M_PI/2);
|
||||
y -= (((double)string_height/2.0) - 1.0)*sin(angle+M_PI/2);
|
||||
|
||||
if (!p->has_dimensions)
|
||||
double render_angle = angle;
|
||||
|
||||
if (fabs(angle_delta) > 0.05 && i > 0)
|
||||
{
|
||||
// paramatise the new line segment
|
||||
double last_dist_from_line = string_height;
|
||||
double line_origin_x = sqrt(pow(old_x-x,2)+pow(old_y-y,2));
|
||||
double line_origin_y = 0;
|
||||
double closest_lp_x = cos(fabs(angle_delta));
|
||||
double closest_lp_y = sin(fabs(angle_delta));
|
||||
|
||||
// iterate over placement points to find the angle to actually render the letter at
|
||||
for (double pax = 0; pax < string_height/2 && pax < line_origin_x; pax += 0.1)
|
||||
{
|
||||
e.init(x, y, x + ci.width*cos(angle+M_PI), y - ci.width*sin(angle+M_PI));
|
||||
e.expand_to_include(x - ci.height*sin(angle+M_PI), y - ci.height*cos(angle+M_PI));
|
||||
e.expand_to_include(x + (ci.width*cos(angle+M_PI) - ci.height*sin(angle+M_PI)), y - (ci.width*sin(angle+M_PI) + ci.height*cos(angle+M_PI)));
|
||||
// calculate dependant parameters
|
||||
double letter_angle = asin(pax/(string_height/2));
|
||||
double pbx = pax+ci.width*cos(letter_angle);
|
||||
double pby = ci.width*sin(letter_angle);
|
||||
|
||||
// find closest point on the new segment
|
||||
double closest_param = ((pbx - line_origin_x)*closest_lp_x + (pby - line_origin_y)*closest_lp_y)/(closest_lp_x*closest_lp_x + closest_lp_y*closest_lp_y);
|
||||
double closest_point_x = line_origin_x + closest_param*closest_lp_x;
|
||||
double closest_point_y = line_origin_y + closest_param*closest_lp_y;
|
||||
|
||||
// calculate the error between this and the letter
|
||||
double dist_from_line = sqrt(pow(pbx - closest_point_x,2) + pow(pby - closest_point_y,2));
|
||||
|
||||
// if our error is getting worse then stop
|
||||
if (dist_from_line > last_dist_from_line)
|
||||
{
|
||||
double pcx, pcy;
|
||||
double extra_space = (ci.height/2)*sin(fabs(angle_delta)-letter_angle);
|
||||
double extra_space_x = extra_space * cos(fabs(angle_delta));
|
||||
double extra_space_y = extra_space * sin(fabs(angle_delta));
|
||||
// remove extra distance used in corner
|
||||
distance -= line_origin_x + closest_param + extra_space;
|
||||
|
||||
// transform local calculation space to a global position for placement
|
||||
if (angle_delta < 0)
|
||||
{
|
||||
// left turn
|
||||
render_angle = letter_angle + last_angle;
|
||||
pcx = 2*pax;
|
||||
pcy = 0;//-(ci.height/2)*cos(letter_angle);
|
||||
}
|
||||
else
|
||||
{ // right turn
|
||||
render_angle = -letter_angle + last_angle;
|
||||
pcx = 0;
|
||||
pcy = 0;//-(ci.height/2)*cos(letter_angle);
|
||||
}
|
||||
double rdx = pcx * cos(-last_angle) - pcy*sin(-last_angle);
|
||||
double rdy = pcy*cos(-last_angle) + pcx * sin(-last_angle);
|
||||
x += rdx;
|
||||
y += rdy;
|
||||
next_char_x = (ci.width+extra_space_x)*cos(render_angle) - extra_space_y*sin(render_angle);
|
||||
next_char_y = (ci.width+extra_space_x)*sin(render_angle) + extra_space_y*cos(render_angle);
|
||||
|
||||
//distance -= 5;
|
||||
break;
|
||||
|
||||
}
|
||||
last_dist_from_line = dist_from_line;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
x = new_x - distance*cos(angle);
|
||||
y = new_y + distance*sin(angle);
|
||||
|
||||
x = new_x - (distance)*cos(angle);
|
||||
y = new_y + (distance)*sin(angle);
|
||||
//Center the text on the line.
|
||||
x += (((double)string_height/2.0) - 1.0)*cos(angle-M_PI/2);
|
||||
y -= (((double)string_height/2.0) - 1.0)*sin(angle-M_PI/2);
|
||||
|
||||
if (!p->has_dimensions)
|
||||
{
|
||||
e.init(x, y, x + ci.width*cos(angle), y - ci.width*sin(angle));
|
||||
e.expand_to_include(x - ci.height*sin(angle), y - ci.height*cos(angle));
|
||||
e.expand_to_include(x + (ci.width*cos(angle) - ci.height*sin(angle)), y - (ci.width*sin(angle) + ci.height*cos(angle)));
|
||||
}
|
||||
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);
|
||||
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))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
p->current_placement.path.add_node(c, x - p->current_placement.starting_x, -y + p->current_placement.starting_y, (orientation == -1 ? angle + M_PI : angle));
|
||||
|
||||
distance -= ci.width;
|
||||
p->current_placement.path.add_node(c, render_x - p->current_placement.starting_x, -render_y + p->current_placement.starting_y, render_angle);
|
||||
x += next_char_x;
|
||||
y -= next_char_y;
|
||||
}
|
||||
p->placements.push_back(p->current_placement);
|
||||
|
||||
|
@ -434,7 +516,6 @@ namespace mapnik
|
|||
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;
|
||||
//std::clog << "Wrapping string at" << wrap_at << std::endl;
|
||||
}
|
||||
|
||||
// work out where our line breaks need to be
|
||||
|
@ -492,7 +573,7 @@ namespace mapnik
|
|||
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)
|
||||
|
@ -527,7 +608,6 @@ namespace mapnik
|
|||
ci = p->info->at(i);
|
||||
|
||||
unsigned c = ci.character;
|
||||
|
||||
if (i == index_to_wrap_at)
|
||||
{
|
||||
index_to_wrap_at = line_breaks[++line_number];
|
||||
|
|
|
@ -30,18 +30,19 @@
|
|||
namespace mapnik
|
||||
{
|
||||
text_symbolizer::text_symbolizer(std::string const& name, std::string const& face_name, unsigned size,Color const& fill)
|
||||
: name_(name),
|
||||
: name_(name),
|
||||
face_name_(face_name),
|
||||
size_(size),
|
||||
size_(size),
|
||||
text_ratio_(0),
|
||||
wrap_width_(0),
|
||||
label_spacing_(0),
|
||||
fill_(fill),
|
||||
halo_fill_(Color(255,255,255)),
|
||||
halo_radius_(0),
|
||||
label_p_(point_placement),
|
||||
anchor_(0.0,0.5),
|
||||
displacement_(0.0,0.0) {}
|
||||
max_char_angle_delta_(0),
|
||||
fill_(fill),
|
||||
halo_fill_(Color(255,255,255)),
|
||||
halo_radius_(0),
|
||||
label_p_(point_placement),
|
||||
anchor_(0.0,0.5),
|
||||
displacement_(0.0,0.0) {}
|
||||
|
||||
text_symbolizer::text_symbolizer(text_symbolizer const& rhs)
|
||||
: name_(rhs.name_),
|
||||
|
@ -50,6 +51,7 @@ namespace mapnik
|
|||
text_ratio_(rhs.text_ratio_),
|
||||
wrap_width_(rhs.wrap_width_),
|
||||
label_spacing_(rhs.label_spacing_),
|
||||
max_char_angle_delta_(rhs.max_char_angle_delta_),
|
||||
fill_(rhs.fill_),
|
||||
halo_fill_(rhs.halo_fill_),
|
||||
halo_radius_(rhs.halo_radius_),
|
||||
|
@ -67,6 +69,7 @@ namespace mapnik
|
|||
text_ratio_ = other.text_ratio_;
|
||||
wrap_width_ = other.wrap_width_;
|
||||
label_spacing_ = other.label_spacing_;
|
||||
max_char_angle_delta_ = other.max_char_angle_delta_;
|
||||
fill_ = other.fill_;
|
||||
halo_fill_ = other.halo_fill_;
|
||||
halo_radius_ = other.halo_radius_;
|
||||
|
@ -108,7 +111,7 @@ namespace mapnik
|
|||
|
||||
unsigned text_symbolizer::get_label_spacing() const
|
||||
{
|
||||
return label_spacing_;
|
||||
return label_spacing_;
|
||||
}
|
||||
|
||||
void text_symbolizer::set_label_spacing(unsigned spacing)
|
||||
|
@ -116,6 +119,16 @@ namespace mapnik
|
|||
label_spacing_ = spacing;
|
||||
}
|
||||
|
||||
double text_symbolizer::get_max_char_angle_delta() const
|
||||
{
|
||||
return max_char_angle_delta_;
|
||||
}
|
||||
|
||||
void text_symbolizer::set_max_char_angle_delta(double angle)
|
||||
{
|
||||
max_char_angle_delta_ = angle;
|
||||
}
|
||||
|
||||
unsigned text_symbolizer::get_text_size() const
|
||||
{
|
||||
return size_;
|
||||
|
|
Loading…
Add table
Reference in a new issue