Skip to content

Instantly share code, notes, and snippets.

@springmeyer
Created May 25, 2011 22:32
Show Gist options
  • Save springmeyer/992150 to your computer and use it in GitHub Desktop.
Save springmeyer/992150 to your computer and use it in GitHub Desktop.
line offsets in mapnik
Index: bindings/python/mapnik_stroke.cpp
===================================================================
--- bindings/python/mapnik_stroke.cpp (revision 2900)
+++ bindings/python/mapnik_stroke.cpp (working copy)
@@ -68,14 +68,15 @@
dashes,
s.get_line_cap(),
s.get_line_join(),
- s.get_gamma());
+ s.get_gamma(),
+ s.get_offset());
}
static void
setstate (stroke& s, boost::python::tuple state)
{
using namespace boost::python;
- if (len(state) != 5)
+ if (len(state) != 6)
{
PyErr_SetObject(PyExc_ValueError,
("expected 5-item tuple in call to __setstate__; got %s"
@@ -99,6 +100,7 @@
s.set_line_cap(extract<line_cap_e>(state[2]));
s.set_line_join(extract<line_join_e>(state[3]));
s.set_gamma(extract<double>(state[4]));
+ s.set_offset(extract<double>(state[5]));
}
@@ -159,6 +161,10 @@
&stroke::get_line_join,
&stroke::set_line_join,
"Returns the line join mode of this stroke.\n")
+ .add_property("offset",
+ &stroke::get_offset,
+ &stroke::set_offset,
+ "Gets or sets the offset of this stroke will be rendered with.\n")
// todo consider providing a single get/set property
.def("add_dash",&stroke::add_dash,
(arg("length"),arg("gap")),
Index: include/mapnik/ctrans.hpp
===================================================================
--- include/mapnik/ctrans.hpp (revision 2900)
+++ include/mapnik/ctrans.hpp (working copy)
@@ -1,5 +1,5 @@
/*****************************************************************************
- *
+ *
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2006 Artem Pavlenko
@@ -25,52 +25,60 @@
#ifndef CTRANS_HPP
#define CTRANS_HPP
-#include <algorithm>
-
+// mapnik
#include <mapnik/box2d.hpp>
#include <mapnik/vertex.hpp>
#include <mapnik/coord_array.hpp>
#include <mapnik/proj_transform.hpp>
+// boost
+#include <boost/math/constants/constants.hpp>
+
+// stl
+#include <algorithm>
+
+const double pi = boost::math::constants::pi<double>();
+const double pi_by_2 = pi/2.0;
+
namespace mapnik {
typedef coord_array<coord2d> CoordinateArray;
-
+
template <typename Transform,typename Geometry>
struct MAPNIK_DECL coord_transform
{
coord_transform(Transform const& t, Geometry& geom)
: t_(t), geom_(geom) {}
-
+
unsigned vertex(double *x , double *y) const
{
unsigned command = geom_.vertex(x,y);
t_.forward(x,y);
return command;
}
-
+
void rewind (unsigned pos)
{
geom_.rewind(pos);
}
-
+
private:
Transform const& t_;
Geometry& geom_;
};
template <typename Transform,typename Geometry>
-struct MAPNIK_DECL coord_transform2
+struct MAPNIK_DECL _coord_transform2
{
typedef std::size_t size_type;
typedef typename Geometry::value_type value_type;
- coord_transform2(Transform const& t,
- Geometry const& geom,
+ _coord_transform2(Transform const& t,
+ Geometry const& geom,
proj_transform const& prj_trans)
- : t_(t),
- geom_(geom),
+ : t_(t),
+ geom_(geom),
prj_trans_(prj_trans) {}
-
+
unsigned vertex(double * x , double * y) const
{
unsigned command(SEG_MOVETO);
@@ -102,7 +110,7 @@
t_.forward(x,y);
return command;
}*/
-
+
void rewind (unsigned pos)
{
geom_.rewind(pos);
@@ -112,26 +120,26 @@
{
return geom_;
}
-
+
private:
Transform const& t_;
Geometry const& geom_;
proj_transform const& prj_trans_;
};
-
+
template <typename Transform,typename Geometry>
struct MAPNIK_DECL coord_transform3
{
- coord_transform3(Transform const& t,
- Geometry const& geom,
+ coord_transform3(Transform const& t,
+ Geometry const& geom,
proj_transform const& prj_trans,
int dx, int dy)
- : t_(t),
- geom_(geom),
+ : t_(t),
+ geom_(geom),
prj_trans_(prj_trans),
dx_(dx), dy_(dy) {}
-
+
unsigned vertex(double * x , double * y) const
{
unsigned command = geom_.vertex(x,y);
@@ -142,12 +150,12 @@
*y+=dy_;
return command;
}
-
+
void rewind (unsigned pos)
{
geom_.rewind(pos);
}
-
+
private:
Transform const& t_;
Geometry const& geom_;
@@ -155,7 +163,226 @@
int dx_;
int dy_;
};
+
+
+template <typename Transform,typename Geometry>
+struct MAPNIK_DECL coord_transform2
+{
+ typedef std::size_t size_type;
+ typedef typename Geometry::value_type value_type;
+
+ coord_transform2(Transform const& t,
+ Geometry const& geom,
+ proj_transform const& prj_trans )
+ : t_(t),
+ geom_(geom),
+ prj_trans_(prj_trans),
+ offset_(0.0),
+ threshold_(10),
+ m_status(initial) {}
+ enum status
+ {
+ initial,
+ start,
+ first,
+ process,
+ last_vertex,
+ angle_joint,
+ end
+ };
+
+ double get_offset() const
+ {
+ return offset_;
+ }
+
+ void set_offset(double offset)
+ {
+ offset_ = offset;
+ }
+
+ unsigned int get_threshold() const
+ {
+ return threshold_;
+ }
+
+ void set_threshold(unsigned int t)
+ {
+ threshold_ = t;
+ }
+
+ unsigned vertex(double * x , double * y)
+ {
+ double z=0;
+
+ if (offset_==0.0)
+ {
+ unsigned command = geom_.vertex(x,y);
+ prj_trans_.backward(*x,*y,z);
+ t_.forward(x,y);
+ return command;
+ }
+ else
+ {
+ while(true){
+ switch(m_status)
+ {
+ case end:
+ return SEG_END;
+ break;
+ case initial:
+ m_pre_cmd = geom_.vertex(x,y);
+ prj_trans_.backward(*x,*y,z);
+ t_.forward(x,y);
+ m_pre_x = *x;
+ m_pre_y = *y;
+ //m_status = (m_pre_cmd!=SEG_END)?start:end; //
+ case start:
+ m_cur_cmd = geom_.vertex(&m_cur_x, &m_cur_y);
+ prj_trans_.backward(m_cur_x,m_cur_y,z);
+ t_.forward(&m_cur_x,&m_cur_y);
+ case first:
+ angle_a = atan2((m_pre_y-m_cur_y),(m_pre_x-m_cur_x));
+ dx_pre = cos(angle_a + pi_by_2);
+ dy_pre = sin(angle_a + pi_by_2);
+ #ifdef MAPNIK_DEBUG
+ std::clog << "offsetting line by: " << offset_ << "\n";
+ std::clog << "initial dx=" << (dx_pre * offset_) << " dy=" << (dy_pre * offset_) << "\n";
+ #endif
+ *x = m_pre_x + (dx_pre * offset_);
+ *y = m_pre_y + (dy_pre * offset_);
+ m_status = process;
+ return SEG_MOVETO;
+ case process:
+ switch(m_cur_cmd)
+ {
+ case SEG_LINETO:
+ m_next_cmd = geom_.vertex(&m_next_x, &m_next_y);
+ prj_trans_.backward(m_next_x,m_next_y,z);
+ t_.forward(&m_next_x,&m_next_y);
+ switch(m_next_cmd)
+ {
+ case SEG_LINETO:
+ m_status = angle_joint;
+ break;
+ default:
+ m_status = last_vertex;
+ break;
+ }
+ break;
+ case SEG_END:
+ m_status = end;
+ return SEG_END;
+ }
+ break;
+ case last_vertex:
+ dx_curr = cos(angle_a + pi_by_2);
+ dy_curr = sin(angle_a + pi_by_2);
+ *x = m_cur_x + (dx_curr * offset_);
+ *y = m_cur_y + (dy_curr * offset_);
+ m_status = end;
+ return m_cur_cmd;
+ case angle_joint:
+ angle_b = atan2((m_cur_y-m_next_y),(m_cur_x-m_next_x));
+ h = tan((angle_b - angle_a)/2.0);
+
+ if (fabs(h) < threshold_)
+ {
+ dx_curr = cos(angle_a + pi_by_2);
+ dy_curr = sin(angle_a + pi_by_2);
+ *x = m_cur_x + (dx_curr * offset_) - h * (dy_curr * offset_);
+ *y = m_cur_y + (dy_curr * offset_) + h * (dx_curr * offset_);
+ //*x = m_cur_x + (dx_curr - h * dy_curr) * offset_;
+ //*y = m_cur_y + (dy_curr + h * dx_curr) * offset_;
+ }
+ else // skip sharp spikes
+ {
+
+ #ifdef MAPNIK_DEBUG
+ dx_curr = cos(angle_a + pi_by_2);
+ dy_curr = sin(angle_a + pi_by_2);
+ sin_curve = dx_curr*dy_pre-dy_curr*dx_pre;
+ std::clog << "angle a: " << angle_a << "\n";
+ std::clog << "angle b: " << angle_b << "\n";
+ std::clog << "h: " << h << "\n";
+ std::clog << "sin_curve: " << sin_curve << "\n";
+ #endif
+ m_status = process;
+ break;
+ }
+
+ // alternate sharp spike fix, does not work...
+
+ /*
+ sin_curve = dx_curr*dy_pre-dy_curr*dx_pre;
+ cos_curve = -dx_pre*dx_curr-dy_pre*dy_curr;
+
+ #ifdef MAPNIK_DEBUG
+ std::clog << "sin_curve value: " << sin_curve << "\n";
+ #endif
+ if(sin_curve > -0.3 && sin_curve < 0.3) {
+ angle_b = atan2((m_cur_y-m_next_y),(m_cur_x-m_next_x));
+ h = tan((angle_b - angle_a)/2.0);
+ *x = m_cur_x + (dx_curr * offset_) - h * (dy_curr * offset_);
+ *y = m_cur_y + (dy_curr * offset_) + h * (dx_curr * offset_);
+ } else {
+ if (angle_b - angle_a > 0)
+ h = -1.0*(1.0+cos_curve)/sin_curve;
+ else
+ h = (1.0+cos_curve)/sin_curve;
+ *x = m_cur_x + (dx_curr + base_shift*dy_curr)*offset_;
+ *y = m_cur_y + (dy_curr - base_shift*dx_curr)*offset_;
+ }
+ */
+
+ m_pre_x = *x;
+ m_pre_x = *y;
+ m_cur_x = m_next_x;
+ m_cur_y = m_next_y;
+ angle_a = angle_b;
+ m_pre_cmd = m_cur_cmd;
+ m_cur_cmd = m_next_cmd;
+ m_status = process;
+ return m_pre_cmd;
+ }
+ }
+ }
+ }
+
+ void rewind (unsigned pos)
+ {
+ geom_.rewind(pos);
+ m_status = initial;
+ }
+
+ private:
+ Transform const& t_;
+ Geometry const& geom_;
+ proj_transform const& prj_trans_;
+ int offset_;
+ unsigned int threshold_;
+ status m_status;
+ double dx_pre;
+ double dy_pre;
+ double dx_curr;
+ double dy_curr;
+ double sin_curve;
+ double cos_curve;
+ double angle_a;
+ double angle_b;
+ double h;
+ unsigned m_pre_cmd;
+ double m_pre_x;
+ double m_pre_y;
+ unsigned m_cur_cmd;
+ double m_cur_x;
+ double m_cur_y;
+ unsigned m_next_cmd;
+ double m_next_x;
+ double m_next_y;
+ };
+
class CoordTransform
{
private:
@@ -174,45 +401,45 @@
sx_ = (double(width_))/extent_.width();
sy_ = (double(height_))/extent_.height();
}
-
+
inline int width() const
{
return width_;
}
-
+
inline int height() const
{
return height_;
}
-
+
inline double scale_x() const
{
return sx_;
}
-
+
inline double scale_y() const
{
return sy_;
}
-
+
inline void forward(double * x, double * y) const
{
*x = (*x - extent_.minx()) * sx_ - offset_x_;
*y = (extent_.maxy() - *y) * sy_ - offset_y_;
}
-
+
inline void backward(double * x, double * y) const
{
*x = extent_.minx() + (*x + offset_x_)/sx_;
*y = extent_.maxy() - (*y + offset_y_)/sy_;
}
-
+
inline coord2d& forward(coord2d& c) const
{
forward(&c.x,&c.y);
return c;
}
-
+
inline coord2d& backward(coord2d& c) const
{
backward(&c.x,&c.y);
@@ -277,7 +504,7 @@
}
return coords;
}
-
+
inline CoordinateArray& backward(CoordinateArray& coords) const
{
for (unsigned i=0;i<coords.size();++i)
Index: src/agg/process_line_symbolizer.cpp
===================================================================
--- src/agg/process_line_symbolizer.cpp (revision 2900)
+++ src/agg/process_line_symbolizer.cpp (working copy)
@@ -77,6 +77,7 @@
if (geom.num_points() > 1)
{
path_type path(t_,geom,prj_trans);
+ path.set_offset(stroke_.get_offset());
if (stroke_.has_dash())
{
Index: include/mapnik/stroke.hpp
===================================================================
--- include/mapnik/stroke.hpp (revision 2900)
+++ include/mapnik/stroke.hpp (working copy)
@@ -70,6 +70,7 @@
double gamma_;
dash_array dash_;
double dash_offset_;
+ double offset_;
public:
explicit stroke();
stroke(color const& c, double width=1.0);
@@ -101,6 +102,9 @@
double dash_offset() const;
dash_array const& get_dash_array() const;
+
+ void set_offset(double opacity);
+ double get_offset() const;
private:
void swap(const stroke& other) throw();
Index: src/stroke.cpp
===================================================================
--- src/stroke.cpp (revision 2900)
+++ src/stroke.cpp (working copy)
@@ -55,7 +55,8 @@
line_join_(MITER_JOIN),
gamma_(1.0),
dash_(),
- dash_offset_(0) {}
+ dash_offset_(0),
+ offset_(0.0) {}
stroke::stroke(color const& c, double width)
: c_(c),
@@ -65,7 +66,8 @@
line_join_(MITER_JOIN),
gamma_(1.0),
dash_(),
- dash_offset_(0.0) {}
+ dash_offset_(0.0),
+ offset_(0.0) {}
stroke::stroke(stroke const& other)
: c_(other.c_),
@@ -75,7 +77,8 @@
line_join_(other.line_join_),
gamma_(other.gamma_),
dash_(other.dash_),
- dash_offset_(other.dash_offset_) {}
+ dash_offset_(other.dash_offset_),
+ offset_(other.offset_) {}
stroke & stroke::operator=(const stroke& rhs)
{
@@ -98,6 +101,7 @@
{
return width_;
}
+
void stroke::set_width(double w)
{
width_=w;
@@ -170,6 +174,16 @@
return dash_;
}
+double stroke::get_offset() const
+{
+ return offset_;
+}
+
+void stroke::set_offset(double offset)
+{
+ offset_=offset;
+}
+
void stroke::swap(const stroke& other) throw()
{
c_=other.c_;
@@ -180,5 +194,7 @@
gamma_=other.gamma_;
dash_ = other.dash_;
dash_offset_ = other.dash_offset_;
+ offset_ = other.offset_;
}
+
}
Index: src/load_map.cpp
===================================================================
--- src/load_map.cpp (revision 2909)
+++ src/load_map.cpp (working copy)
@@ -947,7 +947,7 @@
<< "width,height,placement,marker-type,"
<< "stroke,stroke-width,stroke-opacity,stroke-linejoin,"
<< "stroke-linecap,stroke-dash-offset,stroke-dasharray,"
- // note: stroke-gamma intentionally left off here as markers do not support them
+ // note: stroke-gamma and stroke-offset intentionally left off here as markers do not support them
<< "meta-writer,meta-output";
ensure_attrs(sym, "MarkersSymbolizer", s.str());
@@ -1692,6 +1692,10 @@
optional<double> gamma = get_opt_attr<double>(sym, "stroke-gamma");
if (gamma) strk.set_gamma(*gamma);
+ // stroke-offset
+ optional<double> offset = get_opt_attr<double>(sym, "stroke-offset");
+ if (offset) strk.set_offset(*offset);
+
// stroke-dash-offset
optional<double> dash_offset = get_opt_attr<double>(sym, "stroke-dash-offset");
if (dash_offset) strk.set_dash_offset(*dash_offset);
@@ -1741,7 +1745,7 @@
{
std::stringstream s;
s << "stroke,stroke-width,stroke-opacity,stroke-linejoin,"
- << "stroke-linecap,stroke-gamma,stroke-dash-offset,stroke-dasharray,"
+ << "stroke-linecap,stroke-gamma,stroke-offset,stroke-dash-offset,stroke-dasharray,"
<< "meta-writer,meta-output";
ensure_attrs(sym, "LineSymbolizer", s.str());
Index: src/save_map.cpp
===================================================================
--- src/save_map.cpp (revision 2909)
+++ src/save_map.cpp (working copy)
@@ -584,6 +584,10 @@
{
set_attr( node, "stroke-gamma", strk.get_gamma());
}
+ if ( strk.get_offset() != dfl.get_offset() || explicit_defaults_ )
+ {
+ set_attr( node, "stroke-offset", strk.get_offset());
+ }
if ( strk.dash_offset() != dfl.dash_offset() || explicit_defaults_ )
{
set_attr( node, "stroke-dash-offset", strk.dash_offset());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment