1730 lines
57 KiB
C++
1730 lines
57 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
|
|
*
|
|
*****************************************************************************/
|
|
|
|
// mapnik
|
|
#include <mapnik/debug.hpp>
|
|
#include <mapnik/load_map.hpp>
|
|
#include <mapnik/xml_tree.hpp>
|
|
#include <mapnik/version.hpp>
|
|
#include <mapnik/image_compositing.hpp>
|
|
#include <mapnik/image_scaling.hpp>
|
|
#include <mapnik/color.hpp>
|
|
#include <mapnik/color_factory.hpp>
|
|
#include <mapnik/symbolizer.hpp>
|
|
#include <mapnik/feature_type_style.hpp>
|
|
#include <mapnik/layer.hpp>
|
|
#include <mapnik/datasource_cache.hpp>
|
|
#include <mapnik/font_engine_freetype.hpp>
|
|
#include <mapnik/font_set.hpp>
|
|
#include <mapnik/xml_loader.hpp>
|
|
#include <mapnik/expression.hpp>
|
|
#include <mapnik/parse_path.hpp>
|
|
#include <mapnik/parse_transform.hpp>
|
|
#include <mapnik/raster_colorizer.hpp>
|
|
#include <mapnik/svg/svg_path_parser.hpp>
|
|
#include <mapnik/text_placements/registry.hpp>
|
|
#include <mapnik/text_placements/dummy.hpp>
|
|
#include <mapnik/symbolizer.hpp>
|
|
#include <mapnik/rule.hpp>
|
|
#include <mapnik/config_error.hpp>
|
|
#include <mapnik/util/dasharray_parser.hpp>
|
|
#include <mapnik/util/conversions.hpp>
|
|
#include <mapnik/util/trim.hpp>
|
|
#include <mapnik/marker_cache.hpp>
|
|
#include <mapnik/noncopyable.hpp>
|
|
|
|
// boost
|
|
#include <boost/optional.hpp>
|
|
#include <boost/algorithm/string.hpp>
|
|
#include <boost/tokenizer.hpp>
|
|
#include <boost/property_tree/ptree.hpp>
|
|
#include <boost/property_tree/xml_parser.hpp>
|
|
#include <boost/static_assert.hpp>
|
|
#include <boost/filesystem/operations.hpp>
|
|
|
|
// agg
|
|
#include "agg_trans_affine.h"
|
|
|
|
using boost::tokenizer;
|
|
|
|
namespace mapnik
|
|
{
|
|
using boost::optional;
|
|
|
|
class map_parser : mapnik::noncopyable {
|
|
public:
|
|
map_parser(bool strict, std::string const& filename = "") :
|
|
strict_(strict),
|
|
filename_(filename),
|
|
relative_to_xml_(true),
|
|
font_manager_(font_engine_)
|
|
{}
|
|
|
|
void parse_map(Map & map, xml_node const& sty, std::string const& base_path);
|
|
private:
|
|
void parse_map_include(Map & map, xml_node const& include);
|
|
void parse_style(Map & map, xml_node const& sty);
|
|
void parse_layer(Map & map, xml_node const& lay);
|
|
void parse_symbolizer_base(symbolizer_base &sym, xml_node const& pt);
|
|
|
|
void parse_fontset(Map & map, xml_node const & fset);
|
|
bool parse_font(font_set & fset, xml_node const& f);
|
|
|
|
void parse_rule(feature_type_style & style, xml_node const & r);
|
|
|
|
void parse_point_symbolizer(rule & rule, xml_node const& sym);
|
|
void parse_line_pattern_symbolizer(rule & rule, xml_node const& sym);
|
|
void parse_polygon_pattern_symbolizer(rule & rule, xml_node const& sym);
|
|
void parse_text_symbolizer(rule & rule, xml_node const& sym);
|
|
void parse_shield_symbolizer(rule & rule, xml_node const& sym);
|
|
void parse_line_symbolizer(rule & rule, xml_node const& sym);
|
|
void parse_polygon_symbolizer(rule & rule, xml_node const& sym);
|
|
void parse_building_symbolizer(rule & rule, xml_node const& sym);
|
|
void parse_raster_symbolizer(rule & rule, xml_node const& sym);
|
|
void parse_markers_symbolizer(rule & rule, xml_node const& sym);
|
|
void parse_debug_symbolizer(rule & rule, xml_node const& sym);
|
|
|
|
bool parse_raster_colorizer(raster_colorizer_ptr const& rc, xml_node const& node);
|
|
bool parse_stroke(stroke & strk, xml_node const & sym);
|
|
|
|
void ensure_font_face(std::string const& face_name);
|
|
void find_unused_nodes(xml_node const& root);
|
|
void find_unused_nodes_recursive(xml_node const& node, std::string & error_text);
|
|
|
|
|
|
std::string ensure_relative_to_xml(boost::optional<std::string> opt_path);
|
|
void ensure_exists(std::string const& file_path);
|
|
boost::optional<color> get_opt_color_attr(boost::property_tree::ptree const& node,
|
|
std::string const& name);
|
|
|
|
bool strict_;
|
|
std::string filename_;
|
|
bool relative_to_xml_;
|
|
std::map<std::string,parameters> datasource_templates_;
|
|
freetype_engine font_engine_;
|
|
face_manager<freetype_engine> font_manager_;
|
|
std::map<std::string,std::string> file_sources_;
|
|
std::map<std::string,font_set> fontsets_;
|
|
};
|
|
|
|
//#include <mapnik/internal/dump_xml.hpp>
|
|
void load_map(Map & map, std::string const& filename, bool strict)
|
|
{
|
|
// TODO - use xml encoding?
|
|
xml_tree tree("utf8");
|
|
tree.set_filename(filename);
|
|
read_xml(filename, tree.root());
|
|
map_parser parser(strict, filename);
|
|
parser.parse_map(map, tree.root(), "");
|
|
//dump_xml(tree.root());
|
|
}
|
|
|
|
void load_map_string(Map & map, std::string const& str, bool strict, std::string base_path)
|
|
{
|
|
// TODO - use xml encoding?
|
|
xml_tree tree("utf8");
|
|
if (!base_path.empty())
|
|
read_xml_string(str, tree.root(), base_path); // accept base_path passed into function
|
|
else
|
|
read_xml_string(str, tree.root(), map.base_path()); // default to map base_path
|
|
map_parser parser(strict, base_path);
|
|
parser.parse_map(map, tree.root(), base_path);
|
|
}
|
|
|
|
void map_parser::parse_map(Map & map, xml_node const& pt, std::string const& base_path)
|
|
{
|
|
try
|
|
{
|
|
xml_node const& map_node = pt.get_child("Map");
|
|
try
|
|
{
|
|
// Check if relative paths should be interpreted as relative to/from XML location
|
|
// Default is true, and map_parser::ensure_relative_to_xml will be called to modify path
|
|
optional<boolean> paths_from_xml = map_node.get_opt_attr<boolean>("paths-from-xml");
|
|
if (paths_from_xml)
|
|
{
|
|
relative_to_xml_ = *paths_from_xml;
|
|
}
|
|
|
|
optional<std::string> base_path_from_xml = map_node.get_opt_attr<std::string>("base");
|
|
if (!base_path.empty())
|
|
{
|
|
map.set_base_path(base_path);
|
|
}
|
|
else if (base_path_from_xml)
|
|
{
|
|
map.set_base_path(*base_path_from_xml);
|
|
}
|
|
else
|
|
{
|
|
boost::filesystem::path xml_path(filename_);
|
|
// TODO - should we make this absolute?
|
|
#if (BOOST_FILESYSTEM_VERSION == 3)
|
|
std::string base = xml_path.parent_path().string();
|
|
#else // v2
|
|
std::string base = xml_path.branch_path().string();
|
|
#endif
|
|
|
|
map.set_base_path(base);
|
|
}
|
|
|
|
optional<color> bgcolor = map_node.get_opt_attr<color>("background-color");
|
|
if (bgcolor)
|
|
{
|
|
map.set_background(*bgcolor);
|
|
}
|
|
|
|
optional<std::string> image_filename = map_node.get_opt_attr<std::string>("background-image");
|
|
if (image_filename)
|
|
{
|
|
map.set_background_image(ensure_relative_to_xml(image_filename));
|
|
}
|
|
|
|
map.set_srs(map_node.get_attr("srs", map.srs()));
|
|
|
|
optional<unsigned> buffer_size = map_node.get_opt_attr<unsigned>("buffer-size");
|
|
if (buffer_size)
|
|
{
|
|
map.set_buffer_size(*buffer_size);
|
|
}
|
|
|
|
optional<std::string> maximum_extent = map_node.get_opt_attr<std::string>("maximum-extent");
|
|
if (maximum_extent)
|
|
{
|
|
box2d<double> box;
|
|
if (box.from_string(*maximum_extent))
|
|
{
|
|
map.set_maximum_extent(box);
|
|
}
|
|
else
|
|
{
|
|
std::string s_err("failed to parse Map maximum-extent '");
|
|
s_err += *maximum_extent + "'";
|
|
if (strict_)
|
|
{
|
|
throw config_error(s_err);
|
|
}
|
|
else
|
|
{
|
|
MAPNIK_LOG_ERROR(load_map) << "map_parser: " << s_err;
|
|
}
|
|
}
|
|
}
|
|
|
|
optional<std::string> font_directory = map_node.get_opt_attr<std::string>("font-directory");
|
|
if (font_directory)
|
|
{
|
|
if (!freetype_engine::register_fonts(ensure_relative_to_xml(font_directory), false))
|
|
{
|
|
if (strict_)
|
|
{
|
|
throw config_error(std::string("Failed to load fonts from: ") + *font_directory);
|
|
}
|
|
}
|
|
}
|
|
|
|
optional<std::string> min_version_string = map_node.get_opt_attr<std::string>("minimum-version");
|
|
|
|
if (min_version_string)
|
|
{
|
|
boost::char_separator<char> sep(".");
|
|
boost::tokenizer<boost::char_separator<char> > tokens(*min_version_string, sep);
|
|
unsigned i = 0;
|
|
bool success = false;
|
|
int n[3];
|
|
for (boost::tokenizer<boost::char_separator<char> >::iterator beg = tokens.begin();
|
|
beg != tokens.end(); ++beg)
|
|
{
|
|
std::string item = mapnik::util::trim_copy(*beg);
|
|
if (!mapnik::util::string2int(item,n[i]))
|
|
{
|
|
throw config_error(std::string("Invalid version string encountered: '")
|
|
+ *beg + "' in '" + *min_version_string + "'");
|
|
|
|
break;
|
|
}
|
|
if (i==2)
|
|
{
|
|
success = true;
|
|
break;
|
|
}
|
|
++i;
|
|
}
|
|
if (success)
|
|
{
|
|
int min_version = (n[0] * 100000) + (n[1] * 100) + (n[2]);
|
|
if (min_version > MAPNIK_VERSION)
|
|
{
|
|
throw config_error(std::string("This map uses features only present in Mapnik version ") + *min_version_string + " and newer");
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
catch (const config_error & ex)
|
|
{
|
|
ex.append_context(map_node);
|
|
throw;
|
|
}
|
|
|
|
parse_map_include(map, map_node);
|
|
}
|
|
catch (node_not_found const&)
|
|
{
|
|
throw config_error("Not a map file. Node 'Map' not found.");
|
|
}
|
|
find_unused_nodes(pt);
|
|
}
|
|
|
|
void map_parser::parse_map_include(Map & map, xml_node const& include)
|
|
{
|
|
try
|
|
{
|
|
xml_node::const_iterator itr = include.begin();
|
|
xml_node::const_iterator end = include.end();
|
|
|
|
for (; itr != end; ++itr)
|
|
{
|
|
if (itr->is_text()) continue;
|
|
if (itr->is("Include"))
|
|
{
|
|
parse_map_include(map, *itr);
|
|
}
|
|
else if (itr->is("Style"))
|
|
{
|
|
parse_style(map, *itr);
|
|
}
|
|
else if (itr->is("Layer"))
|
|
{
|
|
parse_layer(map, *itr);
|
|
}
|
|
else if (itr->is("FontSet"))
|
|
{
|
|
parse_fontset(map, *itr);
|
|
}
|
|
else if (itr->is("FileSource"))
|
|
{
|
|
std::string name = itr->get_attr<std::string>("name");
|
|
std::string value = itr->get_text();
|
|
file_sources_[name] = value;
|
|
}
|
|
else if (itr->is("Datasource"))
|
|
{
|
|
std::string name = itr->get_attr("name", std::string("Unnamed"));
|
|
parameters params;
|
|
xml_node::const_iterator paramIter = itr->begin();
|
|
xml_node::const_iterator endParam = itr->end();
|
|
for (; paramIter != endParam; ++paramIter)
|
|
{
|
|
if (paramIter->is("Parameter"))
|
|
{
|
|
std::string param_name = paramIter->get_attr<std::string>("name");
|
|
std::string value = paramIter->get_text();
|
|
params[param_name] = value;
|
|
}
|
|
}
|
|
datasource_templates_[name] = params;
|
|
}
|
|
else if (itr->is("Parameters"))
|
|
{
|
|
parameters & params = map.get_extra_parameters();
|
|
xml_node::const_iterator paramIter = itr->begin();
|
|
xml_node::const_iterator endParam = itr->end();
|
|
for (; paramIter != endParam; ++paramIter)
|
|
{
|
|
if (paramIter->is("Parameter"))
|
|
{
|
|
std::string name = paramIter->get_attr<std::string>("name");
|
|
bool is_string = true;
|
|
boost::optional<std::string> type = paramIter->get_opt_attr<std::string>("type");
|
|
if (type)
|
|
{
|
|
if (*type == "int")
|
|
{
|
|
is_string = false;
|
|
mapnik::value_integer value = paramIter->get_value<mapnik::value_integer>();
|
|
params[name] = value;
|
|
}
|
|
else if (*type == "float")
|
|
{
|
|
is_string = false;
|
|
double value = paramIter->get_value<mapnik::value_double>();
|
|
params[name] = value;
|
|
}
|
|
}
|
|
|
|
if (is_string)
|
|
{
|
|
std::string value = paramIter->get_text();
|
|
params[name] = value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (const config_error & ex) {
|
|
ex.append_context(include);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
void map_parser::parse_style(Map & map, xml_node const& sty)
|
|
{
|
|
std::string name("<missing name>");
|
|
try
|
|
{
|
|
name = sty.get_attr<std::string>("name");
|
|
feature_type_style style;
|
|
|
|
filter_mode_e filter_mode = sty.get_attr<filter_mode_e>("filter-mode", FILTER_ALL);
|
|
style.set_filter_mode(filter_mode);
|
|
|
|
// compositing
|
|
optional<std::string> comp_op_name = sty.get_opt_attr<std::string>("comp-op");
|
|
if (comp_op_name)
|
|
{
|
|
optional<composite_mode_e> comp_op = comp_op_from_string(*comp_op_name);
|
|
if (comp_op)
|
|
{
|
|
style.set_comp_op(*comp_op);
|
|
}
|
|
else
|
|
{
|
|
throw config_error("failed to parse comp-op: '" + *comp_op_name + "'");
|
|
}
|
|
}
|
|
|
|
optional<float> opacity = sty.get_opt_attr<float>("opacity");
|
|
if (opacity)
|
|
{
|
|
style.set_opacity(*opacity);
|
|
}
|
|
|
|
// image filters
|
|
optional<std::string> filters = sty.get_opt_attr<std::string>("image-filters");
|
|
if (filters)
|
|
{
|
|
std::string filter_str = *filters;
|
|
std::string::const_iterator itr = filter_str.begin();
|
|
std::string::const_iterator end = filter_str.end();
|
|
bool result = boost::spirit::qi::phrase_parse(itr,end,
|
|
sty.get_tree().image_filters_grammar,
|
|
boost::spirit::qi::ascii::space,
|
|
style.image_filters());
|
|
if (!result || itr!=end)
|
|
{
|
|
throw config_error("failed to parse image-filters: '" + std::string(itr,end) + "'");
|
|
}
|
|
}
|
|
|
|
// direct image filters (applied directly on main image buffer
|
|
// TODO : consider creating a separate XML node e.g
|
|
// <ImageFilter name="myfilter" op="blur emboss"/>
|
|
//
|
|
optional<std::string> direct_filters = sty.get_opt_attr<std::string>("direct-image-filters");
|
|
if (direct_filters)
|
|
{
|
|
std::string filter_str = *direct_filters;
|
|
std::string::const_iterator itr = filter_str.begin();
|
|
std::string::const_iterator end = filter_str.end();
|
|
bool result = boost::spirit::qi::phrase_parse(itr,end,
|
|
sty.get_tree().image_filters_grammar,
|
|
boost::spirit::qi::ascii::space,
|
|
style.direct_image_filters());
|
|
if (!result || itr!=end)
|
|
{
|
|
throw config_error("failed to parse direct-image-filters: '" + std::string(itr,end) + "'");
|
|
}
|
|
}
|
|
|
|
// rules
|
|
xml_node::const_iterator ruleIter = sty.begin();
|
|
xml_node::const_iterator endRule = sty.end();
|
|
|
|
for (; ruleIter!=endRule; ++ruleIter)
|
|
{
|
|
if (ruleIter->is("Rule"))
|
|
{
|
|
parse_rule(style, *ruleIter);
|
|
}
|
|
}
|
|
|
|
map.insert_style(name, style);
|
|
} catch (const config_error & ex) {
|
|
ex.append_context(std::string("in style '") + name + "'", sty);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
void map_parser::parse_fontset(Map & map, xml_node const& fset)
|
|
{
|
|
std::string name("<missing name>");
|
|
try
|
|
{
|
|
name = fset.get_attr<std::string>("name");
|
|
font_set fontset(name);
|
|
xml_node::const_iterator itr = fset.begin();
|
|
xml_node::const_iterator end = fset.end();
|
|
|
|
bool success = false;
|
|
for (; itr != end; ++itr)
|
|
{
|
|
if (itr->is("Font"))
|
|
{
|
|
if (parse_font(fontset, *itr))
|
|
{
|
|
success = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if not at least one face-name is valid
|
|
if (!success)
|
|
{
|
|
throw mapnik::config_error("no valid fonts could be loaded");
|
|
}
|
|
|
|
map.insert_fontset(name, fontset);
|
|
|
|
// XXX Hack because map object isn't accessible by text_symbolizer
|
|
// when it's parsed
|
|
fontsets_.insert(std::pair<std::string, font_set>(name, fontset));
|
|
}
|
|
catch (const config_error & ex)
|
|
{
|
|
ex.append_context(std::string("in FontSet '") + name + "'", fset);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
bool map_parser::parse_font(font_set &fset, xml_node const& f)
|
|
{
|
|
optional<std::string> face_name = f.get_opt_attr<std::string>("face-name");
|
|
if (face_name)
|
|
{
|
|
face_ptr face = font_manager_.get_face(*face_name);
|
|
if (face)
|
|
{
|
|
fset.add_face_name(*face_name);
|
|
return true;
|
|
}
|
|
else if (strict_)
|
|
{
|
|
throw config_error("Failed to find font face '" +
|
|
*face_name + "'");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw config_error("Must have 'face-name' set", f);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void map_parser::parse_layer(Map & map, xml_node const& node)
|
|
{
|
|
std::string name;
|
|
try
|
|
{
|
|
name = node.get_attr("name", std::string("Unnamed"));
|
|
|
|
// XXX if no projection is given inherit from map? [DS]
|
|
std::string srs = node.get_attr("srs", map.srs());
|
|
|
|
layer lyr(name, srs);
|
|
|
|
optional<boolean> status = node.get_opt_attr<boolean>("status");
|
|
if (status)
|
|
{
|
|
lyr.set_active(* status);
|
|
}
|
|
|
|
optional<double> min_zoom = node.get_opt_attr<double>("minzoom");
|
|
if (min_zoom)
|
|
{
|
|
lyr.set_min_zoom(* min_zoom);
|
|
}
|
|
|
|
|
|
optional<double> max_zoom = node.get_opt_attr<double>("maxzoom");
|
|
if (max_zoom)
|
|
{
|
|
lyr.set_max_zoom(* max_zoom);
|
|
}
|
|
|
|
optional<boolean> queryable = node.get_opt_attr<boolean>("queryable");
|
|
if (queryable)
|
|
{
|
|
lyr.set_queryable(* queryable);
|
|
}
|
|
|
|
optional<boolean> clear_cache =
|
|
node.get_opt_attr<boolean>("clear-label-cache");
|
|
if (clear_cache)
|
|
{
|
|
lyr.set_clear_label_cache(* clear_cache);
|
|
}
|
|
|
|
optional<boolean> cache_features =
|
|
node.get_opt_attr<boolean>("cache-features");
|
|
if (cache_features)
|
|
{
|
|
lyr.set_cache_features(* cache_features);
|
|
}
|
|
|
|
optional<std::string> group_by =
|
|
node.get_opt_attr<std::string>("group-by");
|
|
if (group_by)
|
|
{
|
|
lyr.set_group_by(* group_by);
|
|
}
|
|
|
|
optional<unsigned> buffer_size = node.get_opt_attr<unsigned>("buffer-size");
|
|
if (buffer_size)
|
|
{
|
|
lyr.set_buffer_size(*buffer_size);
|
|
}
|
|
|
|
optional<std::string> maximum_extent = node.get_opt_attr<std::string>("maximum-extent");
|
|
if (maximum_extent)
|
|
{
|
|
box2d<double> box;
|
|
if (box.from_string(*maximum_extent))
|
|
{
|
|
lyr.set_maximum_extent(box);
|
|
}
|
|
else
|
|
{
|
|
std::string s_err("failed to parse Layer maximum-extent '");
|
|
s_err += *maximum_extent + "' for '" + name + "'";
|
|
if (strict_)
|
|
{
|
|
throw config_error(s_err);
|
|
}
|
|
else
|
|
{
|
|
MAPNIK_LOG_ERROR(load_map) << "map_parser: " << s_err;
|
|
}
|
|
}
|
|
}
|
|
|
|
xml_node::const_iterator child = node.begin();
|
|
xml_node::const_iterator end = node.end();
|
|
|
|
for(; child != end; ++child)
|
|
{
|
|
|
|
if (child->is("StyleName"))
|
|
{
|
|
std::string style_name = child->get_text();
|
|
if (style_name.empty())
|
|
{
|
|
std::string ss("StyleName is empty in Layer: '");
|
|
ss += lyr.name() + "'";
|
|
if (strict_)
|
|
{
|
|
throw config_error(ss);
|
|
}
|
|
else
|
|
{
|
|
MAPNIK_LOG_WARN(load_map) << "map_parser: " << ss;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lyr.add_style(style_name);
|
|
}
|
|
}
|
|
else if (child->is("Datasource"))
|
|
{
|
|
parameters params;
|
|
optional<std::string> base = child->get_opt_attr<std::string>("base");
|
|
if(base)
|
|
{
|
|
std::map<std::string,parameters>::const_iterator base_itr = datasource_templates_.find(*base);
|
|
if (base_itr!=datasource_templates_.end())
|
|
{
|
|
params = base_itr->second;
|
|
}
|
|
else
|
|
{
|
|
MAPNIK_LOG_ERROR(datasource) << "Datasource template '" << *base
|
|
<< "' not found for layer '" << name << "'";
|
|
}
|
|
}
|
|
|
|
xml_node::const_iterator paramIter = child->begin();
|
|
xml_node::const_iterator endParam = child->end();
|
|
for (; paramIter != endParam; ++paramIter)
|
|
{
|
|
if (paramIter->is("Parameter"))
|
|
{
|
|
std::string param_name = paramIter->get_attr<std::string>("name");
|
|
std::string value = paramIter->get_text();
|
|
params[param_name] = value;
|
|
}
|
|
}
|
|
|
|
boost::optional<std::string> base_param = params.get<std::string>("base");
|
|
boost::optional<std::string> file_param = params.get<std::string>("file");
|
|
|
|
if (base_param){
|
|
params["base"] = ensure_relative_to_xml(base_param);
|
|
}
|
|
|
|
else if (file_param){
|
|
params["file"] = ensure_relative_to_xml(file_param);
|
|
}
|
|
|
|
//now we are ready to create datasource
|
|
try
|
|
{
|
|
boost::shared_ptr<datasource> ds =
|
|
datasource_cache::instance().create(params);
|
|
lyr.set_datasource(ds);
|
|
}
|
|
catch (std::exception const& ex)
|
|
{
|
|
throw config_error(ex.what());
|
|
}
|
|
catch (...)
|
|
{
|
|
throw config_error("Unknown exception occured attempting to create datasoure for layer '" + lyr.name() + "'");
|
|
}
|
|
}
|
|
}
|
|
map.addLayer(lyr);
|
|
}
|
|
catch (const config_error & ex)
|
|
{
|
|
if (!name.empty())
|
|
{
|
|
ex.append_context(std::string(" encountered during parsing of layer '") + name + "'", node);
|
|
}
|
|
throw;
|
|
}
|
|
}
|
|
|
|
void map_parser::parse_rule(feature_type_style & style, xml_node const& r)
|
|
{
|
|
std::string name;
|
|
try
|
|
{
|
|
name = r.get_attr("name", std::string());
|
|
rule rule(name);
|
|
|
|
xml_node const* child = r.get_opt_child("Filter");
|
|
if (child)
|
|
{
|
|
rule.set_filter(child->get_value<expression_ptr>());
|
|
}
|
|
|
|
if (r.has_child("ElseFilter"))
|
|
{
|
|
rule.set_else(true);
|
|
}
|
|
|
|
if (r.has_child("AlsoFilter"))
|
|
{
|
|
rule.set_also(true);
|
|
}
|
|
|
|
child = r.get_opt_child("MinScaleDenominator");
|
|
if (child)
|
|
{
|
|
rule.set_min_scale(child->get_value<double>());
|
|
}
|
|
|
|
child = r.get_opt_child("MaxScaleDenominator");
|
|
if (child)
|
|
{
|
|
rule.set_max_scale(child->get_value<double>());
|
|
}
|
|
|
|
xml_node::const_iterator symIter = r.begin();
|
|
xml_node::const_iterator endSym = r.end();
|
|
|
|
for(;symIter != endSym; ++symIter)
|
|
{
|
|
|
|
if (symIter->is("PointSymbolizer"))
|
|
{
|
|
parse_point_symbolizer(rule, *symIter);
|
|
}
|
|
else if (symIter->is("LinePatternSymbolizer"))
|
|
{
|
|
parse_line_pattern_symbolizer(rule, *symIter);
|
|
}
|
|
else if (symIter->is("PolygonPatternSymbolizer"))
|
|
{
|
|
parse_polygon_pattern_symbolizer(rule, *symIter);
|
|
}
|
|
else if (symIter->is("TextSymbolizer"))
|
|
{
|
|
parse_text_symbolizer(rule, *symIter);
|
|
}
|
|
else if (symIter->is("ShieldSymbolizer"))
|
|
{
|
|
parse_shield_symbolizer(rule, *symIter);
|
|
}
|
|
else if (symIter->is("LineSymbolizer"))
|
|
{
|
|
parse_line_symbolizer(rule, *symIter);
|
|
}
|
|
else if (symIter->is("PolygonSymbolizer"))
|
|
{
|
|
parse_polygon_symbolizer(rule, *symIter);
|
|
}
|
|
else if (symIter->is("BuildingSymbolizer"))
|
|
{
|
|
parse_building_symbolizer(rule, *symIter);
|
|
}
|
|
else if (symIter->is("RasterSymbolizer"))
|
|
{
|
|
parse_raster_symbolizer(rule, *symIter);
|
|
}
|
|
else if (symIter->is("MarkersSymbolizer"))
|
|
{
|
|
parse_markers_symbolizer(rule, *symIter);
|
|
}
|
|
else if (symIter->is("DebugSymbolizer"))
|
|
{
|
|
parse_debug_symbolizer(rule, *symIter);
|
|
}
|
|
}
|
|
style.add_rule(rule);
|
|
|
|
}
|
|
catch (const config_error & ex)
|
|
{
|
|
if (!name.empty())
|
|
{
|
|
ex.append_context(std::string("in rule '") + name + "'", r);
|
|
}
|
|
throw;
|
|
}
|
|
}
|
|
|
|
void map_parser::parse_symbolizer_base(symbolizer_base &sym, xml_node const &pt)
|
|
{
|
|
optional<std::string> comp_op_name = pt.get_opt_attr<std::string>("comp-op");
|
|
if (comp_op_name)
|
|
{
|
|
optional<composite_mode_e> comp_op = comp_op_from_string(*comp_op_name);
|
|
if (comp_op)
|
|
{
|
|
sym.set_comp_op(*comp_op);
|
|
}
|
|
else
|
|
{
|
|
throw config_error("failed to parse comp-op: '" + *comp_op_name + "'");
|
|
}
|
|
}
|
|
|
|
optional<std::string> geometry_transform_wkt = pt.get_opt_attr<std::string>("geometry-transform");
|
|
if (geometry_transform_wkt)
|
|
{
|
|
mapnik::transform_list_ptr tl = boost::make_shared<mapnik::transform_list>();
|
|
if (!mapnik::parse_transform(*tl, *geometry_transform_wkt, pt.get_tree().transform_expr_grammar))
|
|
{
|
|
std::string ss("Could not parse transform from '");
|
|
ss += *geometry_transform_wkt + "', expected transform attribute";
|
|
throw config_error(ss);
|
|
}
|
|
sym.set_transform(tl);
|
|
}
|
|
|
|
optional<boolean> clip = pt.get_opt_attr<boolean>("clip");
|
|
if (clip) sym.set_clip(*clip);
|
|
|
|
// simplify algorithm
|
|
optional<std::string> simplify_algorithm_name = pt.get_opt_attr<std::string>("simplify-algorithm");
|
|
if (simplify_algorithm_name)
|
|
{
|
|
optional<simplify_algorithm_e> simplify_algorithm = simplify_algorithm_from_string(*simplify_algorithm_name);
|
|
if (simplify_algorithm)
|
|
{
|
|
sym.set_simplify_algorithm(*simplify_algorithm);
|
|
}
|
|
else
|
|
{
|
|
throw config_error("failed to parse simplify-algorithm: '" + *simplify_algorithm_name + "'");
|
|
}
|
|
}
|
|
|
|
// simplify value
|
|
optional<double> simplify_tolerance = pt.get_opt_attr<double>("simplify");
|
|
if (simplify_tolerance) sym.set_simplify_tolerance(*simplify_tolerance);
|
|
|
|
// smooth value
|
|
optional<double> smooth = pt.get_opt_attr<double>("smooth");
|
|
if (smooth) sym.set_smooth(*smooth);
|
|
}
|
|
|
|
void map_parser::parse_point_symbolizer(rule & rule, xml_node const & sym)
|
|
{
|
|
try
|
|
{
|
|
optional<std::string> file = sym.get_opt_attr<std::string>("file");
|
|
optional<std::string> base = sym.get_opt_attr<std::string>("base");
|
|
optional<boolean> allow_overlap = sym.get_opt_attr<boolean>("allow-overlap");
|
|
optional<boolean> ignore_placement = sym.get_opt_attr<boolean>("ignore-placement");
|
|
optional<float> opacity = sym.get_opt_attr<float>("opacity");
|
|
|
|
point_symbolizer symbol;
|
|
if (allow_overlap)
|
|
{
|
|
symbol.set_allow_overlap(* allow_overlap);
|
|
}
|
|
if (opacity)
|
|
{
|
|
symbol.set_opacity(* opacity);
|
|
}
|
|
if (ignore_placement)
|
|
{
|
|
symbol.set_ignore_placement(* ignore_placement);
|
|
}
|
|
point_placement_e placement =
|
|
sym.get_attr<point_placement_e>("placement", symbol.get_point_placement());
|
|
symbol.set_point_placement(placement);
|
|
|
|
if (file && !file->empty())
|
|
{
|
|
if(base)
|
|
{
|
|
std::map<std::string,std::string>::const_iterator itr = file_sources_.find(*base);
|
|
if (itr!=file_sources_.end())
|
|
{
|
|
*file = itr->second + "/" + *file;
|
|
}
|
|
}
|
|
|
|
*file = ensure_relative_to_xml(file);
|
|
std::string filename = *file;
|
|
ensure_exists(filename);
|
|
symbol.set_filename( parse_path(filename, sym.get_tree().path_expr_grammar) );
|
|
|
|
optional<std::string> image_transform_wkt = sym.get_opt_attr<std::string>("transform");
|
|
if (image_transform_wkt)
|
|
{
|
|
mapnik::transform_list_ptr tl = boost::make_shared<mapnik::transform_list>();
|
|
if (!mapnik::parse_transform(*tl, *image_transform_wkt, sym.get_tree().transform_expr_grammar))
|
|
{
|
|
throw mapnik::config_error("Failed to parse transform: '" + *image_transform_wkt + "'");
|
|
}
|
|
symbol.set_image_transform(tl);
|
|
}
|
|
}
|
|
parse_symbolizer_base(symbol, sym);
|
|
rule.append(symbol);
|
|
}
|
|
catch (const config_error & ex)
|
|
{
|
|
ex.append_context(sym);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
void map_parser::parse_markers_symbolizer(rule & rule, xml_node const& sym)
|
|
{
|
|
try
|
|
{
|
|
std::string filename("");
|
|
optional<std::string> file = sym.get_opt_attr<std::string>("file");
|
|
optional<std::string> base = sym.get_opt_attr<std::string>("base");
|
|
|
|
if (file && !file->empty())
|
|
{
|
|
if (base)
|
|
{
|
|
std::map<std::string,std::string>::const_iterator itr = file_sources_.find(*base);
|
|
if (itr!=file_sources_.end())
|
|
{
|
|
*file = itr->second + "/" + *file;
|
|
}
|
|
}
|
|
|
|
filename = ensure_relative_to_xml(file);
|
|
}
|
|
|
|
optional<std::string> marker_type = sym.get_opt_attr<std::string>("marker-type");
|
|
if (marker_type)
|
|
{
|
|
// TODO - revisit whether to officially deprecate marker-type
|
|
// https://github.com/mapnik/mapnik/issues/1427
|
|
//MAPNIK_LOG_WARN(markers_symbolizer) << "'marker-type' is deprecated and will be removed in Mapnik 3.x, use file='shape://<type>' to specify known svg shapes";
|
|
// back compatibility with Mapnik 2.0.0
|
|
if (!marker_type->empty() && filename.empty())
|
|
{
|
|
if (*marker_type == "ellipse")
|
|
{
|
|
filename = marker_cache::instance().known_svg_prefix_ + "ellipse";
|
|
}
|
|
else if (*marker_type == "arrow")
|
|
{
|
|
filename = marker_cache::instance().known_svg_prefix_ + "arrow";
|
|
}
|
|
}
|
|
}
|
|
|
|
markers_symbolizer symbol;
|
|
|
|
if (!filename.empty())
|
|
{
|
|
ensure_exists(filename);
|
|
symbol.set_filename( parse_path(filename, sym.get_tree().path_expr_grammar) );
|
|
}
|
|
|
|
// overall opacity to be applied to all paths
|
|
optional<float> opacity = sym.get_opt_attr<float>("opacity");
|
|
if (opacity) symbol.set_opacity(*opacity);
|
|
|
|
optional<float> fill_opacity = sym.get_opt_attr<float>("fill-opacity");
|
|
if (fill_opacity) symbol.set_fill_opacity(*fill_opacity);
|
|
|
|
optional<std::string> image_transform_wkt = sym.get_opt_attr<std::string>("transform");
|
|
if (image_transform_wkt)
|
|
{
|
|
mapnik::transform_list_ptr tl = boost::make_shared<mapnik::transform_list>();
|
|
if (!mapnik::parse_transform(*tl, *image_transform_wkt, sym.get_tree().transform_expr_grammar))
|
|
{
|
|
throw mapnik::config_error("Failed to parse transform: '" + *image_transform_wkt + "'");
|
|
}
|
|
symbol.set_image_transform(tl);
|
|
}
|
|
|
|
optional<color> c = sym.get_opt_attr<color>("fill");
|
|
if (c) symbol.set_fill(*c);
|
|
|
|
optional<double> spacing = sym.get_opt_attr<double>("spacing");
|
|
if (spacing) symbol.set_spacing(*spacing);
|
|
|
|
optional<double> max_error = sym.get_opt_attr<double>("max-error");
|
|
if (max_error) symbol.set_max_error(*max_error);
|
|
|
|
optional<boolean> allow_overlap = sym.get_opt_attr<boolean>("allow-overlap");
|
|
if (allow_overlap) symbol.set_allow_overlap(*allow_overlap);
|
|
|
|
optional<boolean> ignore_placement = sym.get_opt_attr<boolean>("ignore-placement");
|
|
if (ignore_placement) symbol.set_ignore_placement(*ignore_placement);
|
|
|
|
optional<expression_ptr> width = sym.get_opt_attr<expression_ptr>("width");
|
|
if (width) symbol.set_width(*width);
|
|
|
|
optional<expression_ptr> height = sym.get_opt_attr<expression_ptr>("height");
|
|
if (height) symbol.set_height(*height);
|
|
|
|
stroke strk;
|
|
if (parse_stroke(strk,sym))
|
|
{
|
|
symbol.set_stroke(strk);
|
|
}
|
|
|
|
marker_placement_e placement = sym.get_attr<marker_placement_e>("placement",symbol.get_marker_placement());
|
|
symbol.set_marker_placement(placement);
|
|
|
|
marker_multi_policy_e mpolicy = sym.get_attr<marker_multi_policy_e>("multi-policy",symbol.get_marker_multi_policy());
|
|
symbol.set_marker_multi_policy(mpolicy);
|
|
|
|
parse_symbolizer_base(symbol, sym);
|
|
rule.append(symbol);
|
|
}
|
|
catch (config_error const& ex)
|
|
{
|
|
ex.append_context(sym);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
void map_parser::parse_line_pattern_symbolizer(rule & rule, xml_node const & sym)
|
|
{
|
|
try
|
|
{
|
|
std::string file = sym.get_attr<std::string>("file");
|
|
if (file.empty())
|
|
{
|
|
throw config_error("empty file attribute");
|
|
}
|
|
|
|
optional<std::string> base = sym.get_opt_attr<std::string>("base");
|
|
|
|
if(base)
|
|
{
|
|
std::map<std::string,std::string>::const_iterator itr = file_sources_.find(*base);
|
|
if (itr!=file_sources_.end())
|
|
{
|
|
file = itr->second + "/" + file;
|
|
}
|
|
}
|
|
|
|
file = ensure_relative_to_xml(file);
|
|
ensure_exists(file);
|
|
line_pattern_symbolizer symbol( parse_path(file, sym.get_tree().path_expr_grammar) );
|
|
|
|
parse_symbolizer_base(symbol, sym);
|
|
rule.append(symbol);
|
|
}
|
|
catch (const config_error & ex)
|
|
{
|
|
ex.append_context(sym);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
void map_parser::parse_polygon_pattern_symbolizer(rule & rule,
|
|
xml_node const & sym)
|
|
{
|
|
try
|
|
{
|
|
std::string file = sym.get_attr<std::string>("file");
|
|
|
|
if (file.empty())
|
|
{
|
|
throw config_error("empty file attribute");
|
|
}
|
|
|
|
optional<std::string> base = sym.get_opt_attr<std::string>("base");
|
|
|
|
if(base)
|
|
{
|
|
std::map<std::string,std::string>::iterator itr = file_sources_.find(*base);
|
|
if (itr!=file_sources_.end())
|
|
{
|
|
file = itr->second + "/" + file;
|
|
}
|
|
}
|
|
|
|
file = ensure_relative_to_xml(file);
|
|
ensure_exists(file);
|
|
polygon_pattern_symbolizer symbol( parse_path(file, sym.get_tree().path_expr_grammar) );
|
|
|
|
// pattern alignment
|
|
pattern_alignment_e p_alignment = sym.get_attr<pattern_alignment_e>("alignment",LOCAL_ALIGNMENT);
|
|
symbol.set_alignment(p_alignment);
|
|
|
|
// opacity
|
|
optional<float> opacity = sym.get_opt_attr<float>("opacity");
|
|
if (opacity) symbol.set_opacity(*opacity);
|
|
|
|
// gamma
|
|
optional<double> gamma = sym.get_opt_attr<double>("gamma");
|
|
if (gamma) symbol.set_gamma(*gamma);
|
|
|
|
// gamma method
|
|
optional<gamma_method_e> gamma_method = sym.get_opt_attr<gamma_method_e>("gamma-method");
|
|
if (gamma_method) symbol.set_gamma_method(*gamma_method);
|
|
|
|
parse_symbolizer_base(symbol, sym);
|
|
rule.append(symbol);
|
|
}
|
|
catch (const config_error & ex)
|
|
{
|
|
ex.append_context(sym);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
void map_parser::parse_text_symbolizer(rule & rule, xml_node const& sym)
|
|
{
|
|
try
|
|
{
|
|
text_placements_ptr placement_finder;
|
|
optional<std::string> placement_type = sym.get_opt_attr<std::string>("placement-type");
|
|
if (placement_type) {
|
|
placement_finder = placements::registry::instance().from_xml(*placement_type, sym, fontsets_);
|
|
} else {
|
|
placement_finder = boost::make_shared<text_placements_dummy>();
|
|
placement_finder->defaults.from_xml(sym, fontsets_);
|
|
}
|
|
if (strict_ &&
|
|
!placement_finder->defaults.format.fontset)
|
|
{
|
|
ensure_font_face(placement_finder->defaults.format.face_name);
|
|
}
|
|
|
|
text_symbolizer text_symbol = text_symbolizer(placement_finder);
|
|
parse_symbolizer_base(text_symbol, sym);
|
|
rule.append(text_symbol);
|
|
}
|
|
catch (const config_error & ex)
|
|
{
|
|
ex.append_context(sym);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
void map_parser::parse_shield_symbolizer(rule & rule, xml_node const& sym)
|
|
{
|
|
try
|
|
{
|
|
text_placements_ptr placement_finder;
|
|
optional<std::string> placement_type = sym.get_opt_attr<std::string>("placement-type");
|
|
if (placement_type) {
|
|
placement_finder = placements::registry::instance().from_xml(*placement_type, sym, fontsets_);
|
|
} else {
|
|
placement_finder = boost::make_shared<text_placements_dummy>();
|
|
}
|
|
placement_finder->defaults.from_xml(sym, fontsets_);
|
|
if (strict_ &&
|
|
!placement_finder->defaults.format.fontset)
|
|
{
|
|
ensure_font_face(placement_finder->defaults.format.face_name);
|
|
}
|
|
|
|
shield_symbolizer shield_symbol = shield_symbolizer(placement_finder);
|
|
|
|
optional<std::string> image_transform_wkt = sym.get_opt_attr<std::string>("transform");
|
|
if (image_transform_wkt)
|
|
{
|
|
mapnik::transform_list_ptr tl = boost::make_shared<mapnik::transform_list>();
|
|
if (!mapnik::parse_transform(*tl, *image_transform_wkt, sym.get_tree().transform_expr_grammar))
|
|
{
|
|
throw mapnik::config_error("Failed to parse transform: '" + *image_transform_wkt + "'");
|
|
}
|
|
shield_symbol.set_image_transform(tl);
|
|
}
|
|
|
|
// shield displacement
|
|
double shield_dx = sym.get_attr("shield-dx", 0.0);
|
|
double shield_dy = sym.get_attr("shield-dy", 0.0);
|
|
shield_symbol.set_shield_displacement(shield_dx,shield_dy);
|
|
|
|
// opacity
|
|
optional<float> opacity = sym.get_opt_attr<float>("opacity");
|
|
if (opacity)
|
|
{
|
|
shield_symbol.set_opacity(*opacity);
|
|
}
|
|
|
|
// text-opacity
|
|
// TODO: Could be problematic because it is named opacity in TextSymbolizer but opacity has a diffrent meaning here.
|
|
optional<double> text_opacity =
|
|
sym.get_opt_attr<double>("text-opacity");
|
|
if (text_opacity)
|
|
{
|
|
shield_symbol.set_text_opacity(* text_opacity);
|
|
}
|
|
|
|
// unlock_image
|
|
optional<boolean> unlock_image =
|
|
sym.get_opt_attr<boolean>("unlock-image");
|
|
if (unlock_image)
|
|
{
|
|
shield_symbol.set_unlock_image(* unlock_image);
|
|
}
|
|
|
|
std::string file = sym.get_attr<std::string>("file");
|
|
if (file.empty())
|
|
{
|
|
throw config_error("empty file attribute");
|
|
}
|
|
|
|
optional<std::string> base = sym.get_opt_attr<std::string>("base");
|
|
|
|
if(base)
|
|
{
|
|
std::map<std::string,std::string>::const_iterator itr = file_sources_.find(*base);
|
|
if (itr!=file_sources_.end())
|
|
{
|
|
file = itr->second + "/" + file;
|
|
}
|
|
}
|
|
|
|
// no_text - removed property in 2.1.x that used to have a purpose
|
|
// before you could provide an expression with an empty string
|
|
optional<boolean> no_text =
|
|
sym.get_opt_attr<boolean>("no-text");
|
|
if (no_text)
|
|
{
|
|
MAPNIK_LOG_ERROR(shield_symbolizer) << "'no-text' is deprecated and will be removed in Mapnik 3.x, to create a ShieldSymbolizer without text just provide an element like: \"<ShieldSymbolizer ... />' '</>\"";
|
|
if (*no_text)
|
|
shield_symbol.set_name(parse_expression("' '"));
|
|
}
|
|
|
|
file = ensure_relative_to_xml(file);
|
|
ensure_exists(file);
|
|
shield_symbol.set_filename( parse_path(file, sym.get_tree().path_expr_grammar) );
|
|
parse_symbolizer_base(shield_symbol, sym);
|
|
rule.append(shield_symbol);
|
|
}
|
|
catch (const config_error & ex)
|
|
{
|
|
ex.append_context(sym);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
bool map_parser::parse_stroke(stroke & strk, xml_node const & sym)
|
|
{
|
|
bool result = false;
|
|
|
|
// stroke color
|
|
optional<color> c = sym.get_opt_attr<color>("stroke");
|
|
if (c)
|
|
{
|
|
result = true;
|
|
strk.set_color(*c);
|
|
}
|
|
|
|
// stroke-width
|
|
optional<double> width = sym.get_opt_attr<double>("stroke-width");
|
|
if (width)
|
|
{
|
|
result = true;
|
|
strk.set_width(*width);
|
|
}
|
|
|
|
// stroke-opacity
|
|
optional<double> opacity = sym.get_opt_attr<double>("stroke-opacity");
|
|
if (opacity)
|
|
{
|
|
result = true;
|
|
strk.set_opacity(*opacity);
|
|
}
|
|
|
|
// stroke-linejoin
|
|
optional<line_join_e> line_join = sym.get_opt_attr<line_join_e>("stroke-linejoin");
|
|
if (line_join) strk.set_line_join(*line_join);
|
|
|
|
// stroke-linecap
|
|
optional<line_cap_e> line_cap = sym.get_opt_attr<line_cap_e>("stroke-linecap");
|
|
if (line_cap) strk.set_line_cap(*line_cap);
|
|
|
|
// stroke-gamma
|
|
optional<double> gamma = sym.get_opt_attr<double>("stroke-gamma");
|
|
if (gamma) strk.set_gamma(*gamma);
|
|
|
|
// stroke-gamma-method
|
|
optional<gamma_method_e> gamma_method = sym.get_opt_attr<gamma_method_e>("stroke-gamma-method");
|
|
if (gamma_method) strk.set_gamma_method(*gamma_method);
|
|
|
|
// stroke-dashoffset
|
|
optional<double> dash_offset = sym.get_opt_attr<double>("stroke-dashoffset");
|
|
if (dash_offset) strk.set_dash_offset(*dash_offset);
|
|
|
|
// stroke-dasharray
|
|
optional<std::string> str = sym.get_opt_attr<std::string>("stroke-dasharray");
|
|
if (str)
|
|
{
|
|
std::vector<double> dash_array;
|
|
if (util::parse_dasharray((*str).begin(),(*str).end(),dash_array))
|
|
{
|
|
if (!dash_array.empty())
|
|
{
|
|
size_t size = dash_array.size();
|
|
if (size % 2 == 1)
|
|
dash_array.insert(dash_array.end(),dash_array.begin(),dash_array.end());
|
|
|
|
std::vector<double>::const_iterator pos = dash_array.begin();
|
|
while (pos != dash_array.end())
|
|
{
|
|
if (*pos > 0.0 || *(pos+1) > 0.0) // avoid both dash and gap eq 0.0
|
|
strk.add_dash(*pos,*(pos + 1));
|
|
pos +=2;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw config_error(std::string("Failed to parse dasharray ") +
|
|
"'. Expected a " +
|
|
"list of floats or 'none' but got '" + (*str) + "'");
|
|
}
|
|
}
|
|
|
|
// stroke-miterlimit
|
|
optional<double> miterlimit = sym.get_opt_attr<double>("stroke-miterlimit");
|
|
if (miterlimit) strk.set_miterlimit(*miterlimit);
|
|
return result;
|
|
}
|
|
|
|
void map_parser::parse_line_symbolizer(rule & rule, xml_node const & sym)
|
|
{
|
|
try
|
|
{
|
|
stroke strk;
|
|
parse_stroke(strk,sym);
|
|
line_symbolizer symbol = line_symbolizer(strk);
|
|
|
|
// offset value
|
|
optional<double> offset = sym.get_opt_attr<double>("offset");
|
|
if (offset) symbol.set_offset(*offset);
|
|
|
|
line_rasterizer_e rasterizer = sym.get_attr<line_rasterizer_e>("rasterizer", RASTERIZER_FULL);
|
|
symbol.set_rasterizer(rasterizer);
|
|
|
|
parse_symbolizer_base(symbol, sym);
|
|
rule.append(symbol);
|
|
}
|
|
catch (const config_error & ex)
|
|
{
|
|
ex.append_context(sym);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
|
|
void map_parser::parse_polygon_symbolizer(rule & rule, xml_node const & sym)
|
|
{
|
|
try
|
|
{
|
|
polygon_symbolizer poly_sym;
|
|
// fill
|
|
optional<color> fill = sym.get_opt_attr<color>("fill");
|
|
if (fill) poly_sym.set_fill(*fill);
|
|
// fill-opacity
|
|
optional<double> opacity = sym.get_opt_attr<double>("fill-opacity");
|
|
if (opacity) poly_sym.set_opacity(*opacity);
|
|
// gamma
|
|
optional<double> gamma = sym.get_opt_attr<double>("gamma");
|
|
if (gamma) poly_sym.set_gamma(*gamma);
|
|
// gamma method
|
|
optional<gamma_method_e> gamma_method = sym.get_opt_attr<gamma_method_e>("gamma-method");
|
|
if (gamma_method) poly_sym.set_gamma_method(*gamma_method);
|
|
|
|
parse_symbolizer_base(poly_sym, sym);
|
|
rule.append(poly_sym);
|
|
}
|
|
catch (const config_error & ex)
|
|
{
|
|
ex.append_context(sym);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
void map_parser::parse_building_symbolizer(rule & rule, xml_node const & sym)
|
|
{
|
|
try
|
|
{
|
|
building_symbolizer building_sym;
|
|
|
|
// fill
|
|
optional<color> fill = sym.get_opt_attr<color>("fill");
|
|
if (fill) building_sym.set_fill(*fill);
|
|
// fill-opacity
|
|
optional<double> opacity = sym.get_opt_attr<double>("fill-opacity");
|
|
if (opacity) building_sym.set_opacity(*opacity);
|
|
// height
|
|
optional<expression_ptr> height = sym.get_opt_attr<expression_ptr>("height");
|
|
if (height) building_sym.set_height(*height);
|
|
|
|
parse_symbolizer_base(building_sym, sym);
|
|
rule.append(building_sym);
|
|
}
|
|
catch (const config_error & ex)
|
|
{
|
|
ex.append_context(sym);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
void map_parser::parse_raster_symbolizer(rule & rule, xml_node const & sym)
|
|
{
|
|
try
|
|
{
|
|
raster_symbolizer raster_sym;
|
|
|
|
// mode
|
|
optional<std::string> mode = sym.get_opt_attr<std::string>("mode");
|
|
if (mode)
|
|
{
|
|
std::string mode_string = *mode;
|
|
if (boost::algorithm::find_first(mode_string,"_"))
|
|
{
|
|
MAPNIK_LOG_ERROR(raster_symbolizer) << "'mode' values using \"_\" are deprecated and will be removed in Mapnik 3.x, use \"-\"instead";
|
|
boost::algorithm::replace_all(mode_string,"_","-");
|
|
}
|
|
raster_sym.set_mode(mode_string);
|
|
}
|
|
|
|
// scaling
|
|
optional<std::string> scaling = sym.get_opt_attr<std::string>("scaling");
|
|
if (scaling)
|
|
{
|
|
std::string scaling_method = *scaling;
|
|
if (scaling_method == "fast")
|
|
{
|
|
MAPNIK_LOG_ERROR(raster_symbolizer) << "'scaling' value of 'fast' is deprecated and will be removed in Mapnik 3.x, use 'near' with Mapnik >= 2.1.x";
|
|
raster_sym.set_scaling_method(SCALING_NEAR);
|
|
}
|
|
else
|
|
{
|
|
boost::optional<scaling_method_e> method = scaling_method_from_string(scaling_method);
|
|
if (method)
|
|
{
|
|
raster_sym.set_scaling_method(*method);
|
|
}
|
|
else
|
|
{
|
|
throw config_error("failed to parse 'scaling': '" + *scaling + "'");
|
|
}
|
|
}
|
|
}
|
|
|
|
// opacity
|
|
optional<float> opacity = sym.get_opt_attr<float>("opacity");
|
|
if (opacity) raster_sym.set_opacity(*opacity);
|
|
|
|
// filter factor
|
|
optional<double> filter_factor = sym.get_opt_attr<double>("filter-factor");
|
|
if (filter_factor) raster_sym.set_filter_factor(*filter_factor);
|
|
|
|
// mesh-size
|
|
optional<unsigned> mesh_size = sym.get_opt_attr<unsigned>("mesh-size");
|
|
if (mesh_size) raster_sym.set_mesh_size(*mesh_size);
|
|
|
|
// premultiplied status of image
|
|
optional<boolean> premultiplied = sym.get_opt_attr<boolean>("premultiplied");
|
|
if (premultiplied) raster_sym.set_premultiplied(*premultiplied);
|
|
|
|
xml_node::const_iterator cssIter = sym.begin();
|
|
xml_node::const_iterator endCss = sym.end();
|
|
|
|
bool found_colorizer = false;
|
|
for(; cssIter != endCss; ++cssIter)
|
|
{
|
|
if (cssIter->is("RasterColorizer"))
|
|
{
|
|
found_colorizer = true;
|
|
raster_colorizer_ptr colorizer = boost::make_shared<raster_colorizer>();
|
|
raster_sym.set_colorizer(colorizer);
|
|
if (parse_raster_colorizer(colorizer, *cssIter))
|
|
raster_sym.set_colorizer(colorizer);
|
|
|
|
}
|
|
}
|
|
// look for properties one level up
|
|
if (!found_colorizer)
|
|
{
|
|
raster_colorizer_ptr colorizer = boost::make_shared<raster_colorizer>();
|
|
if (parse_raster_colorizer(colorizer, sym))
|
|
raster_sym.set_colorizer(colorizer);
|
|
}
|
|
parse_symbolizer_base(raster_sym, sym);
|
|
rule.append(raster_sym);
|
|
}
|
|
catch (const config_error & ex)
|
|
{
|
|
ex.append_context(sym);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
void map_parser::parse_debug_symbolizer(rule & rule, xml_node const & sym)
|
|
{
|
|
debug_symbolizer symbol;
|
|
|
|
parse_symbolizer_base(symbol, sym);
|
|
rule.append(symbol);
|
|
}
|
|
|
|
bool map_parser::parse_raster_colorizer(raster_colorizer_ptr const& rc,
|
|
xml_node const& node)
|
|
{
|
|
bool found_stops = false;
|
|
try
|
|
{
|
|
// mode
|
|
colorizer_mode default_mode =
|
|
node.get_attr<colorizer_mode>("default-mode", COLORIZER_LINEAR);
|
|
|
|
if(default_mode == COLORIZER_INHERIT) {
|
|
throw config_error("RasterColorizer mode must not be INHERIT. ");
|
|
}
|
|
rc->set_default_mode(default_mode);
|
|
|
|
// default colour
|
|
optional<color> default_color = node.get_opt_attr<color>("default-color");
|
|
if (default_color)
|
|
{
|
|
rc->set_default_color(*default_color);
|
|
}
|
|
|
|
|
|
// epsilon
|
|
optional<float> eps = node.get_opt_attr<float>("epsilon");
|
|
if (eps)
|
|
{
|
|
if(*eps < 0) {
|
|
throw config_error("RasterColorizer epsilon must be > 0. ");
|
|
}
|
|
rc->set_epsilon(*eps);
|
|
}
|
|
|
|
|
|
xml_node::const_iterator stopIter = node.begin();
|
|
xml_node::const_iterator endStop = node.end();
|
|
float maximumValue = -std::numeric_limits<float>::max();
|
|
|
|
for(; stopIter != endStop; ++stopIter)
|
|
{
|
|
if (stopIter->is("stop"))
|
|
{
|
|
found_stops = true;
|
|
// colour is optional.
|
|
optional<color> stopcolor = stopIter->get_opt_attr<color>("color");
|
|
if (!stopcolor) {
|
|
*stopcolor = *default_color;
|
|
}
|
|
|
|
// mode default to INHERIT
|
|
colorizer_mode mode =
|
|
stopIter->get_attr<colorizer_mode>("mode", COLORIZER_INHERIT);
|
|
|
|
// value is required, and it must be bigger than the previous
|
|
optional<float> value =
|
|
stopIter->get_opt_attr<float>("value");
|
|
|
|
if(!value) {
|
|
throw config_error("stop tag missing value");
|
|
}
|
|
|
|
if(value < maximumValue) {
|
|
throw config_error("stop tag values must be in ascending order");
|
|
}
|
|
maximumValue = *value;
|
|
|
|
optional<std::string> label =
|
|
stopIter->get_opt_attr<std::string>("label");
|
|
|
|
//append the stop
|
|
colorizer_stop tmpStop;
|
|
tmpStop.set_color(*stopcolor);
|
|
tmpStop.set_mode(mode);
|
|
tmpStop.set_value(*value);
|
|
if (label)
|
|
tmpStop.set_label(*label);
|
|
|
|
rc->add_stop(tmpStop);
|
|
}
|
|
}
|
|
}
|
|
catch (const config_error & ex)
|
|
{
|
|
ex.append_context(node);
|
|
throw;
|
|
}
|
|
return found_stops;
|
|
}
|
|
|
|
void map_parser::ensure_font_face(std::string const& face_name)
|
|
{
|
|
if (! font_manager_.get_face(face_name))
|
|
{
|
|
throw config_error("Failed to find font face '" +
|
|
face_name + "'");
|
|
}
|
|
}
|
|
|
|
std::string map_parser::ensure_relative_to_xml(boost::optional<std::string> opt_path)
|
|
{
|
|
if (marker_cache::instance().is_uri(*opt_path))
|
|
return *opt_path;
|
|
|
|
if (relative_to_xml_)
|
|
{
|
|
boost::filesystem::path xml_path = filename_;
|
|
boost::filesystem::path rel_path = *opt_path;
|
|
if (!rel_path.has_root_path())
|
|
{
|
|
#if (BOOST_FILESYSTEM_VERSION == 3)
|
|
// TODO - normalize is now deprecated, use make_preferred?
|
|
boost::filesystem::path full = boost::filesystem::absolute(xml_path.parent_path()/rel_path);
|
|
#else // v2
|
|
boost::filesystem::path full = boost::filesystem::complete(xml_path.branch_path()/rel_path).normalize();
|
|
#endif
|
|
|
|
MAPNIK_LOG_DEBUG(load_map) << "map_parser: Modifying relative paths to be relative to xml...";
|
|
MAPNIK_LOG_DEBUG(load_map) << "map_parser: -- Original base path=" << *opt_path;
|
|
MAPNIK_LOG_DEBUG(load_map) << "map_parser: -- Relative base path=" << full.string();
|
|
|
|
return full.string();
|
|
}
|
|
}
|
|
return *opt_path;
|
|
}
|
|
|
|
void map_parser::ensure_exists(std::string const& file_path)
|
|
{
|
|
if (marker_cache::instance().is_uri(file_path))
|
|
return;
|
|
// validate that the filename exists if it is not a dynamic PathExpression
|
|
if (!boost::algorithm::find_first(file_path,"[") && !boost::algorithm::find_first(file_path,"]"))
|
|
{
|
|
if (!boost::filesystem::exists(file_path))
|
|
{
|
|
throw mapnik::config_error("file could not be found: '" + file_path + "'");
|
|
}
|
|
}
|
|
}
|
|
|
|
void map_parser::find_unused_nodes(xml_node const& root)
|
|
{
|
|
std::string error_message;
|
|
find_unused_nodes_recursive(root, error_message);
|
|
if (!error_message.empty())
|
|
{
|
|
std::string msg("Unable to process some data while parsing '" + filename_ + "':" + error_message);
|
|
if (strict_)
|
|
{
|
|
throw config_error(msg);
|
|
}
|
|
else
|
|
{
|
|
MAPNIK_LOG_ERROR(load_map) << msg;
|
|
}
|
|
}
|
|
}
|
|
|
|
void map_parser::find_unused_nodes_recursive(xml_node const& node, std::string & error_message)
|
|
{
|
|
if (!node.processed())
|
|
{
|
|
if (node.is_text()) {
|
|
error_message += "\n* text '" + node.text() + "'";
|
|
} else {
|
|
error_message += "\n* node '" + node.name() + "' at line " + node.line_to_string();
|
|
}
|
|
return; //All attributes and children are automatically unprocessed, too.
|
|
}
|
|
xml_node::attribute_map const& attr = node.get_attributes();
|
|
xml_node::attribute_map::const_iterator aitr = attr.begin();
|
|
xml_node::attribute_map::const_iterator aend = attr.end();
|
|
for (;aitr!=aend; aitr++)
|
|
{
|
|
if (!aitr->second.processed)
|
|
{
|
|
error_message += "\n* attribute '" + aitr->first +
|
|
"' with value '" + aitr->second.value +
|
|
"' at line " + node.line_to_string();
|
|
}
|
|
}
|
|
xml_node::const_iterator itr = node.begin();
|
|
xml_node::const_iterator end = node.end();
|
|
for (; itr!=end; itr++)
|
|
{
|
|
find_unused_nodes_recursive(*itr, error_message);
|
|
}
|
|
}
|
|
|
|
} // end of namespace mapnik
|