mapnik/include/mapnik/feature_style_processor_impl.hpp
Dane Springmeyer 3b2bdba7b0 iwyu
2013-06-02 20:19:33 -07:00

699 lines
22 KiB
C++

/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2011 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
*
*****************************************************************************/
// NOTE: This is an implementation header file and is only meant to be included
// from implementation files. It therefore doesn't have an include guard. To
// create a custom feature_style_processor, include this file and instantiate
// the template with the desired template arguments.
// mapnik
#include <mapnik/map.hpp>
#include <mapnik/debug.hpp>
#include <mapnik/feature.hpp>
#include <mapnik/feature_style_processor.hpp>
#include <mapnik/query.hpp>
#include <mapnik/feature.hpp>
#include <mapnik/datasource.hpp>
#include <mapnik/memory_datasource.hpp>
#include <mapnik/feature_type_style.hpp>
#include <mapnik/box2d.hpp>
#include <mapnik/layer.hpp>
#include <mapnik/rule.hpp>
#include <mapnik/rule_cache.hpp>
#include <mapnik/attribute_collector.hpp>
#include <mapnik/expression_evaluator.hpp>
#include <mapnik/utils.hpp>
#include <mapnik/scale_denominator.hpp>
#include <mapnik/projection.hpp>
#include <mapnik/proj_transform.hpp>
// boost
#include <boost/variant/apply_visitor.hpp>
#include <boost/variant/static_visitor.hpp>
#include <boost/foreach.hpp>
#include <boost/concept_check.hpp>
// stl
#include <vector>
#include <stdexcept>
#if defined(RENDERING_STATS)
#include <mapnik/timer.hpp>
#include <iomanip>
#include <sstream>
#endif
namespace mapnik
{
template <typename T0,typename T1> struct has_process;
template <bool>
struct process_impl
{
template <typename T0, typename T1, typename T2, typename T3>
static void process(T0 & ren, T1 const& sym, T2 & f, T3 const& tr)
{
ren.process(sym,f,tr);
}
};
template <> // No-op specialization
struct process_impl<false>
{
template <typename T0, typename T1, typename T2, typename T3>
static void process(T0 & ren, T1 const& sym, T2 & f, T3 const& tr)
{
boost::ignore_unused_variable_warning(ren);
boost::ignore_unused_variable_warning(f);
boost::ignore_unused_variable_warning(tr);
#ifdef MAPNIK_DEBUG
std::clog << "NO-OP ...\n";
#endif
}
};
/** Calls the renderer's process function,
* \param output Renderer
* \param f Feature to process
* \param prj_trans Projection
* \param sym Symbolizer object
*/
template <typename Processor>
struct feature_style_processor<Processor>::symbol_dispatch : public boost::static_visitor<>
{
symbol_dispatch (Processor & output,
mapnik::feature_impl & f,
proj_transform const& prj_trans)
: output_(output),
f_(f),
prj_trans_(prj_trans) {}
template <typename T>
void operator () (T const& sym) const
{
process_impl<has_process<Processor,T>::value>::process(output_,sym,f_,prj_trans_);
}
Processor & output_;
mapnik::feature_impl & f_;
proj_transform const& prj_trans_;
};
typedef char (&no_tag)[1];
typedef char (&yes_tag)[2];
template <typename T0, typename T1, void (T0::*)(T1 const&, mapnik::feature_impl &, proj_transform const&) >
struct process_memfun_helper {};
template <typename T0, typename T1> no_tag has_process_helper(...);
template <typename T0, typename T1> yes_tag has_process_helper(process_memfun_helper<T0, T1, &T0::process>* p);
template<typename T0,typename T1>
struct has_process
{
typedef typename T0::processor_impl_type processor_impl_type;
BOOST_STATIC_CONSTANT(bool
, value = sizeof(has_process_helper<processor_impl_type,T1>(0)) == sizeof(yes_tag)
);
};
template <typename Processor>
feature_style_processor<Processor>::feature_style_processor(Map const& m, double scale_factor)
: m_(m),
scale_factor_(scale_factor)
{
// https://github.com/mapnik/mapnik/issues/1100
if (scale_factor_ <= 0)
{
throw std::runtime_error("scale_factor must be greater than 0.0");
}
}
template <typename Processor>
void feature_style_processor<Processor>::apply(double scale_denom)
{
#if defined(RENDERING_STATS)
std::clog << "\n//-- starting rendering timer...\n";
mapnik::progress_timer t(std::clog, "total map rendering");
#endif
Processor & p = static_cast<Processor&>(*this);
p.start_map_processing(m_);
projection proj(m_.srs(),true);
if (scale_denom <= 0.0)
scale_denom = mapnik::scale_denominator(m_.scale(),proj.is_geographic());
scale_denom *= scale_factor_;
BOOST_FOREACH ( layer const& lyr, m_.layers() )
{
if (lyr.visible(scale_denom))
{
std::set<std::string> names;
apply_to_layer(lyr,
p,
proj,
m_.scale(),
scale_denom,
m_.width(),
m_.height(),
m_.get_current_extent(),
m_.buffer_size(),
names);
}
}
p.end_map_processing(m_);
#if defined(RENDERING_STATS)
t.stop();
std::clog << "//-- rendering timer stopped...\n\n";
#endif
}
template <typename Processor>
void feature_style_processor<Processor>::apply(mapnik::layer const& lyr,
std::set<std::string>& names,
double scale_denom)
{
Processor & p = static_cast<Processor&>(*this);
p.start_map_processing(m_);
projection proj(m_.srs(),true);
if (scale_denom <= 0.0)
scale_denom = mapnik::scale_denominator(m_.scale(),proj.is_geographic());
scale_denom *= scale_factor_;
if (lyr.visible(scale_denom))
{
apply_to_layer(lyr,
p,
proj,
m_.scale(),
scale_denom,
m_.width(),
m_.height(),
m_.get_current_extent(),
m_.buffer_size(),
names);
}
p.end_map_processing(m_);
}
template <typename Processor>
void feature_style_processor<Processor>::apply_to_layer(layer const& lay, Processor & p,
projection const& proj0,
double scale,
double scale_denom,
unsigned width,
unsigned height,
box2d<double> const& extent,
int buffer_size,
std::set<std::string>& names)
{
std::vector<std::string> const& style_names = lay.styles();
unsigned int num_styles = style_names.size();
if (! num_styles)
{
MAPNIK_LOG_DEBUG(feature_style_processor) << "feature_style_processor: No style for layer=" << lay.name();
return;
}
mapnik::datasource_ptr ds = lay.datasource();
if (! ds)
{
MAPNIK_LOG_DEBUG(feature_style_processor) << "feature_style_processor: No datasource for layer=" << lay.name();
return;
}
#if defined(RENDERING_STATS)
progress_timer layer_timer(std::clog, "rendering total for layer: '" + lay.name() + "'");
#endif
projection proj1(lay.srs(),true);
proj_transform prj_trans(proj0,proj1);
#if defined(RENDERING_STATS)
if (! prj_trans.equal())
{
std::clog << "notice: reprojecting layer: '" << lay.name() << "' from/to:\n\t'"
<< lay.srs() << "'\n\t'"
<< m_.srs() << "'\n";
}
#endif
box2d<double> query_ext = extent; // unbuffered
box2d<double> buffered_query_ext(query_ext); // buffered
double buffer_padding = 2.0 * scale;
boost::optional<int> layer_buffer_size = lay.buffer_size();
if (layer_buffer_size) // if layer overrides buffer size, use this value to compute buffered extent
{
buffer_padding *= *layer_buffer_size;
}
else
{
buffer_padding *= buffer_size;
}
buffered_query_ext.width(query_ext.width() + buffer_padding);
buffered_query_ext.height(query_ext.height() + buffer_padding);
// clip buffered extent by maximum extent, if supplied
boost::optional<box2d<double> > const& maximum_extent = m_.maximum_extent();
if (maximum_extent) {
buffered_query_ext.clip(*maximum_extent);
}
box2d<double> layer_ext = lay.envelope();
bool fw_success = false;
bool early_return = false;
// first, try intersection of map extent forward projected into layer srs
if (prj_trans.forward(buffered_query_ext, PROJ_ENVELOPE_POINTS) && buffered_query_ext.intersects(layer_ext))
{
fw_success = true;
layer_ext.clip(buffered_query_ext);
}
// if no intersection and projections are also equal, early return
else if (prj_trans.equal())
{
early_return = true;
}
// next try intersection of layer extent back projected into map srs
else if (prj_trans.backward(layer_ext, PROJ_ENVELOPE_POINTS) && buffered_query_ext.intersects(layer_ext))
{
layer_ext.clip(buffered_query_ext);
// forward project layer extent back into native projection
if (! prj_trans.forward(layer_ext, PROJ_ENVELOPE_POINTS))
{
MAPNIK_LOG_ERROR(feature_style_processor)
<< "feature_style_processor: Layer=" << lay.name()
<< " extent=" << layer_ext << " in map projection "
<< " did not reproject properly back to layer projection";
}
}
else
{
// if no intersection then nothing to do for layer
early_return = true;
}
if (early_return)
{
// check for styles needing compositing operations applied
// https://github.com/mapnik/mapnik/issues/1477
BOOST_FOREACH(std::string const& style_name, style_names)
{
boost::optional<feature_type_style const&> style=m_.find_style(style_name);
if (!style)
{
continue;
}
if (style->comp_op() || style->image_filters().size() > 0)
{
if (style->active(scale_denom))
{
// trigger any needed compositing ops
p.start_style_processing(*style);
p.end_style_processing(*style);
}
}
}
#if defined(RENDERING_STATS)
layer_timer.discard();
#endif
return;
}
// if we've got this far, now prepare the unbuffered extent
// which is used as a bbox for clipping geometries
if (maximum_extent)
{
query_ext.clip(*maximum_extent);
}
box2d<double> layer_ext2 = lay.envelope();
if (fw_success)
{
if (prj_trans.forward(query_ext, PROJ_ENVELOPE_POINTS))
{
layer_ext2.clip(query_ext);
}
}
else
{
if (prj_trans.backward(layer_ext2, PROJ_ENVELOPE_POINTS))
{
layer_ext2.clip(query_ext);
prj_trans.forward(layer_ext2, PROJ_ENVELOPE_POINTS);
}
}
p.start_layer_processing(lay, layer_ext2);
double qw = query_ext.width()>0 ? query_ext.width() : 1;
double qh = query_ext.height()>0 ? query_ext.height() : 1;
query::resolution_type res(width/qw,
height/qh);
query q(layer_ext,res,scale_denom,extent);
std::vector<feature_type_style const*> active_styles;
attribute_collector collector(names);
double filt_factor = 1.0;
directive_collector d_collector(filt_factor);
boost::ptr_vector<rule_cache> rule_caches;
// iterate through all named styles collecting active styles and attribute names
BOOST_FOREACH(std::string const& style_name, style_names)
{
boost::optional<feature_type_style const&> style=m_.find_style(style_name);
if (!style)
{
MAPNIK_LOG_DEBUG(feature_style_processor)
<< "feature_style_processor: Style=" << style_name
<< " required for layer=" << lay.name() << " does not exist.";
continue;
}
std::vector<rule> const& rules = style->get_rules();
bool active_rules = false;
std::auto_ptr<rule_cache> rc(new rule_cache);
BOOST_FOREACH(rule const& r, rules)
{
if (r.active(scale_denom))
{
rc->add_rule(r);
active_rules = true;
if (ds->type() == datasource::Vector)
{
collector(r);
}
// TODO - in the future rasters should be able to be filtered.
}
}
if (active_rules)
{
rule_caches.push_back(rc);
active_styles.push_back(&(*style));
}
}
// Don't even try to do more work if there are no active styles.
if (active_styles.size() > 0)
{
if (p.attribute_collection_policy() == COLLECT_ALL)
{
layer_descriptor lay_desc = ds->get_descriptor();
BOOST_FOREACH(attribute_descriptor const& desc, lay_desc.get_descriptors())
{
q.add_property_name(desc.get_name());
}
}
else
{
BOOST_FOREACH(std::string const& name, names)
{
q.add_property_name(name);
}
}
// Update filter_factor for all enabled raster layers.
BOOST_FOREACH (feature_type_style const* style, active_styles)
{
BOOST_FOREACH(rule const& r, style->get_rules())
{
if (r.active(scale_denom) &&
ds->type() == datasource::Raster &&
ds->params().get<double>("filter_factor",0.0) == 0.0)
{
BOOST_FOREACH (rule::symbolizers::value_type sym, r.get_symbolizers())
{
// if multiple raster symbolizers, last will be respected
// should we warn or throw?
boost::apply_visitor(d_collector,sym);
}
q.set_filter_factor(filt_factor);
}
}
}
// Also query the group by attribute
std::string group_by = lay.group_by();
if (group_by != "")
{
q.add_property_name(group_by);
}
bool cache_features = lay.cache_features() && active_styles.size() > 1;
// Render incrementally when the column that we group by
// changes value.
if (group_by != "")
{
featureset_ptr features = ds->features(q);
if (features) {
// Cache all features into the memory_datasource before rendering.
memory_datasource cache(ds->type(),false);
feature_ptr feature, prev;
while ((feature = features->next()))
{
if (prev && prev->get(group_by) != feature->get(group_by))
{
// We're at a value boundary, so render what we have
// up to this point.
int i = 0;
BOOST_FOREACH (feature_type_style const* style, active_styles)
{
render_style(lay, p, style, rule_caches[i], style_names[i],
cache.features(q), prj_trans);
i++;
}
cache.clear();
}
cache.push(feature);
prev = feature;
}
int i = 0;
BOOST_FOREACH (feature_type_style const* style, active_styles)
{
render_style(lay, p, style, rule_caches[i], style_names[i],
cache.features(q), prj_trans);
i++;
}
}
}
else if (cache_features)
{
memory_datasource cache(ds->type(),false);
featureset_ptr features = ds->features(q);
if (features) {
// Cache all features into the memory_datasource before rendering.
feature_ptr feature;
while ((feature = features->next()))
{
cache.push(feature);
}
}
int i = 0;
BOOST_FOREACH (feature_type_style const* style, active_styles)
{
render_style(lay, p, style, rule_caches[i], style_names[i],
cache.features(q), prj_trans);
i++;
}
}
// We only have a single style and no grouping.
else
{
int i = 0;
BOOST_FOREACH (feature_type_style const* style, active_styles)
{
render_style(lay, p, style, rule_caches[i], style_names[i],
ds->features(q), prj_trans);
i++;
}
}
}
#if defined(RENDERING_STATS)
layer_timer.stop();
#endif
p.end_layer_processing(lay);
}
template <typename Processor>
void feature_style_processor<Processor>::render_style(
layer const& lay,
Processor & p,
feature_type_style const* style,
rule_cache const& rc,
std::string const& style_name,
featureset_ptr features,
proj_transform const& prj_trans)
{
p.start_style_processing(*style);
if (!features)
{
p.end_style_processing(*style);
return;
}
#if defined(RENDERING_STATS)
std::ostringstream s1;
s1 << "rendering style for layer: '" << lay.name()
<< "' and style '" << style_name << "'";
mapnik::progress_timer style_timer(std::clog, s1.str());
int feature_processed_count = 0;
int feature_count = 0;
#endif
feature_ptr feature;
while ((feature = features->next()))
{
#if defined(RENDERING_STATS)
feature_count++;
bool feat_processed = false;
#endif
bool do_else = true;
bool do_also = false;
BOOST_FOREACH(rule const* r, rc.get_if_rules() )
{
expression_ptr const& expr=r->get_filter();
value_type result = boost::apply_visitor(evaluate<feature_impl,value_type>(*feature),*expr);
if (result.to_bool())
{
#if defined(RENDERING_STATS)
feat_processed = true;
#endif
p.painted(true);
do_else=false;
do_also=true;
rule::symbolizers const& symbols = r->get_symbolizers();
// if the underlying renderer is not able to process the complete set of symbolizers,
// process one by one.
if(!p.process(symbols,*feature,prj_trans))
{
BOOST_FOREACH (symbolizer const& sym, symbols)
{
boost::apply_visitor(symbol_dispatch(p,*feature,prj_trans),sym);
}
}
if (style->get_filter_mode() == FILTER_FIRST)
{
// Stop iterating over rules and proceed with next feature.
do_also=false;
break;
}
}
}
if (do_else)
{
BOOST_FOREACH( rule const* r, rc.get_else_rules() )
{
#if defined(RENDERING_STATS)
feat_processed = true;
#endif
p.painted(true);
rule::symbolizers const& symbols = r->get_symbolizers();
// if the underlying renderer is not able to process the complete set of symbolizers,
// process one by one.
if(!p.process(symbols,*feature,prj_trans))
{
BOOST_FOREACH (symbolizer const& sym, symbols)
{
boost::apply_visitor(symbol_dispatch(p,*feature,prj_trans),sym);
}
}
}
}
if (do_also)
{
BOOST_FOREACH( rule const* r, rc.get_also_rules() )
{
#if defined(RENDERING_STATS)
feat_processed = true;
#endif
p.painted(true);
rule::symbolizers const& symbols = r->get_symbolizers();
// if the underlying renderer is not able to process the complete set of symbolizers,
// process one by one.
if(!p.process(symbols,*feature,prj_trans))
{
BOOST_FOREACH (symbolizer const& sym, symbols)
{
boost::apply_visitor(symbol_dispatch(p,*feature,prj_trans),sym);
}
}
}
}
#if defined(RENDERING_STATS)
if (feat_processed)
feature_processed_count++;
#endif
}
#if defined(RENDERING_STATS)
style_timer.stop();
// done with style
std::ostringstream s;
if (feature_count > 0)
{
double perc_processed = ((double)feature_processed_count/(double)feature_count)*100.0;
s << "percent rendered: " << perc_processed << "% - " << feature_processed_count
<< " rendered for " << feature_count << " queried for ";
s << std::setw(15 - (int)s.tellp()) << " layer '" << lay.name() << "' and style '" << style_name << "'\n";
}
else
{
s << "" << std::setw(15) << "- no features returned from query for layer '" << lay.name() << "' and style '" << style_name << "'\n";
}
std::clog << s.str();
style_timer.discard();
#endif
p.end_style_processing(*style);
}
}