/***************************************************************************** * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // boost #include #include #include #include #include #include // agg #include "agg_trans_affine.h" using boost::tokenizer; namespace mapnik { using boost::optional; constexpr unsigned name2int(const char *str, int off = 0) { return !str[off] ? 5381 : (name2int(str, off+1)*33) ^ str[off]; } class map_parser : mapnik::noncopyable { public: map_parser(bool strict, std::string const& filename = "") : strict_(strict), filename_(filename), font_manager_(font_engine_), xml_base_path_() {} void parse_map(Map & map, xml_node const& node, std::string const& base_path); private: void parse_map_include(Map & map, xml_node const& node); void parse_style(Map & map, xml_node const& node); void parse_layer(Map & map, xml_node const& node); void parse_symbolizer_base(symbolizer_base &sym, xml_node const& node); void parse_fontset(Map & map, xml_node const & node); bool parse_font(font_set & fset, xml_node const& f); void parse_rule(feature_type_style & style, xml_node const & node); void parse_symbolizers(rule & rule, xml_node const & node); void parse_point_symbolizer(rule & rule, xml_node const& node); void parse_line_pattern_symbolizer(rule & rule, xml_node const& node); void parse_polygon_pattern_symbolizer(rule & rule, xml_node const& node); void parse_text_symbolizer(rule & rule, xml_node const& node); void parse_shield_symbolizer(rule & rule, xml_node const& node); void parse_line_symbolizer(rule & rule, xml_node const& node); void parse_polygon_symbolizer(rule & rule, xml_node const& node); void parse_building_symbolizer(rule & rule, xml_node const& node); void parse_raster_symbolizer(rule & rule, xml_node const& node); void parse_markers_symbolizer(rule & rule, xml_node const& node); void parse_group_symbolizer(rule &rule, xml_node const& node); void parse_debug_symbolizer(rule & rule, xml_node const& node); void parse_group_rule(group_symbolizer_properties &prop, xml_node const& node); void parse_simple_layout(group_symbolizer_properties &prop, xml_node const& node); void parse_pair_layout(group_symbolizer_properties &prop, xml_node const& node); bool parse_raster_colorizer(raster_colorizer_ptr const& rc, xml_node const& node); void parse_stroke(symbolizer_base & symbol, xml_node const& node); 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 const& opt_path); void ensure_exists(std::string const& file_path); boost::optional get_opt_color_attr(boost::property_tree::ptree const& node, std::string const& name); bool strict_; std::string filename_; std::map datasource_templates_; freetype_engine font_engine_; face_manager font_manager_; std::map file_sources_; std::map fontsets_; std::string xml_base_path_; }; //#include void load_map(Map & map, std::string const& filename, bool strict, std::string base_path) { // 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(), base_path); //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()); // FIXME - this value is not fully known yet } map_parser parser(strict, base_path); parser.parse_map(map, tree.root(), base_path); } void map_parser::parse_map(Map & map, xml_node const& node, std::string const& base_path) { try { xml_node const& map_node = node.get_child("Map"); try { optional base_path_from_xml = map_node.get_opt_attr("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 if (!filename_.empty()) { map.set_base_path(mapnik::util::dirname(filename_)); } xml_base_path_ = map.base_path(); optional bgcolor = map_node.get_opt_attr("background-color"); if (bgcolor) { map.set_background(*bgcolor); } optional image_filename = map_node.get_opt_attr("background-image"); if (image_filename) { map.set_background_image(ensure_relative_to_xml(image_filename)); } optional comp_op_name = map_node.get_opt_attr("background-image-comp-op"); if (comp_op_name) { optional comp_op = comp_op_from_string(*comp_op_name); if (comp_op) { map.set_background_image_comp_op(*comp_op); } else { throw config_error("failed to parse background-image-comp-op: '" + *comp_op_name + "'"); } } optional opacity = map_node.get_opt_attr("background-image-opacity"); if (opacity) { map.set_background_image_opacity(*opacity); } std::string srs = map_node.get_attr("srs", map.srs()); try { // create throwaway projection object here to ensure it is valid projection proj(srs,true); } catch (std::exception const& ex) { throw mapnik::config_error(ex.what()); } map.set_srs(srs); optional buffer_size = map_node.get_opt_attr("buffer-size"); if (buffer_size) { map.set_buffer_size(*buffer_size); } optional maximum_extent = map_node.get_opt_attr("maximum-extent"); if (maximum_extent) { box2d 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 font_directory = map_node.get_opt_attr("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 min_version_string = map_node.get_opt_attr("minimum-version"); if (min_version_string) { boost::char_separator sep("."); boost::tokenizer > tokens(*min_version_string, sep); unsigned i = 0; bool success = false; int n[3]; for (auto const& beg : tokens) { 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 + "'"); } 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 (config_error const& 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(node); } void map_parser::parse_map_include(Map & map, xml_node const& node) { try { for (auto const& n : node) { if (n.is_text()) continue; if (n.is("Include")) { parse_map_include(map, n); } else if (n.is("Style")) { parse_style(map, n); } else if (n.is("Layer")) { parse_layer(map, n); } else if (n.is("FontSet")) { parse_fontset(map, n); } else if (n.is("FileSource")) { file_sources_[n.get_attr("name")] = n.get_text(); } else if (n.is("Datasource")) { std::string name = n.get_attr("name", std::string("Unnamed")); parameters params; for (auto const& p: n) { if (p.is("Parameter")) { params[p.get_attr("name")] = p.get_text(); } } datasource_templates_[std::move(name)] = std::move(params); } else if (n.is("Parameters")) { parameters & params = map.get_extra_parameters(); for (auto const& p: n) { if (p.is("Parameter")) { bool is_string = true; boost::optional type = p.get_opt_attr("type"); if (type) { if (*type == "int") { is_string = false; params[p.get_attr("name")] = p.get_value(); } else if (*type == "float") { is_string = false; params[p.get_attr("name")] = p.get_value(); } } if (is_string) { params[p.get_attr("name")] = p.get_text(); } } } } } } catch (config_error const& ex) { ex.append_context(node); throw; } } void map_parser::parse_style(Map & map, xml_node const& node) { std::string name(""); try { name = node.get_attr("name"); feature_type_style style; filter_mode_e filter_mode = node.get_attr("filter-mode", FILTER_ALL); style.set_filter_mode(filter_mode); // compositing optional comp_op_name = node.get_opt_attr("comp-op"); if (comp_op_name) { optional 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 opacity = node.get_opt_attr("opacity"); if (opacity) style.set_opacity(*opacity); optional image_filters_inflate = node.get_opt_attr("image-filters-inflate"); if (image_filters_inflate) { style.set_image_filters_inflate(*image_filters_inflate); } // image filters optional filters = node.get_opt_attr("image-filters"); if (filters) { if (!parse_image_filters(*filters, style.image_filters())) { throw config_error("failed to parse image-filters: '" + *filters + "'"); } } // direct image filters (applied directly on main image buffer // TODO : consider creating a separate XML node e.g // // optional direct_filters = node.get_opt_attr("direct-image-filters"); if (direct_filters) { if (!parse_image_filters(*direct_filters, style.direct_image_filters())) { throw config_error("failed to parse direct-image-filters: '" + *direct_filters + "'"); } } style.reserve(node.size()); // rules for (auto const& rule_ : node) { if (rule_.is("Rule")) { parse_rule(style, rule_); } } map.insert_style(name, std::move(style)); } catch (config_error const& ex) { ex.append_context(std::string("in style '") + name + "'", node); throw; } } void map_parser::parse_fontset(Map & map, xml_node const& node) { std::string name(""); try { name = node.get_attr("name"); font_set fontset(name); bool success = false; for (auto const& n: node) { if (n.is("Font")) { if (parse_font(fontset, n)) { success = true; } } } // if not at least one face-name is valid if (!success) { throw mapnik::config_error("no valid fonts could be loaded"); } // XXX Hack because map object isn't accessible by text_symbolizer // when it's parsed fontsets_.insert(std::make_pair(name, fontset)); map.insert_fontset(name, std::move(fontset)); } catch (config_error const& ex) { ex.append_context(std::string("in FontSet '") + name + "'", node); throw; } } bool map_parser::parse_font(font_set &fset, xml_node const& f) { optional face_name = f.get_opt_attr("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")); // If no projection is given inherit from map std::string srs = node.get_attr("srs", map.srs()); try { // create throwaway projection object here to ensure it is valid projection proj(srs,true); } catch (std::exception const& ex) { throw mapnik::config_error(ex.what()); } layer lyr(name, srs); optional status = node.get_opt_attr("status"); if (status) { lyr.set_active(* status); } optional min_zoom = node.get_opt_attr("minzoom"); if (min_zoom) { lyr.set_min_zoom(* min_zoom); } optional max_zoom = node.get_opt_attr("maxzoom"); if (max_zoom) { lyr.set_max_zoom(* max_zoom); } optional queryable = node.get_opt_attr("queryable"); if (queryable) { lyr.set_queryable(* queryable); } optional clear_cache = node.get_opt_attr("clear-label-cache"); if (clear_cache) { lyr.set_clear_label_cache(* clear_cache); } optional cache_features = node.get_opt_attr("cache-features"); if (cache_features) { lyr.set_cache_features(* cache_features); } optional group_by = node.get_opt_attr("group-by"); if (group_by) { lyr.set_group_by(* group_by); } optional buffer_size = node.get_opt_attr("buffer-size"); if (buffer_size) { lyr.set_buffer_size(*buffer_size); } optional maximum_extent = node.get_opt_attr("maximum-extent"); if (maximum_extent) { box2d 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; } } } for (auto const& child: node) { if (child.is("StyleName")) { std::string const& 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 base = child.get_opt_attr("base"); if(base) { std::map::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 << "'"; } } for (auto const& n : child) { if (n.is("Parameter")) { params[n.get_attr("name")] = n.get_text(); } } boost::optional base_param = params.get("base"); boost::optional file_param = params.get("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 { std::shared_ptr 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.add_layer(std::move(lyr)); } catch (config_error const& 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& node) { std::string name; try { name = node.get_attr("name", std::string()); rule rule(name); xml_node const* child = node.get_opt_child("Filter"); if (child) { rule.set_filter(child->get_value()); } if (node.has_child("ElseFilter")) { rule.set_else(true); } if (node.has_child("AlsoFilter")) { rule.set_also(true); } child = node.get_opt_child("MinScaleDenominator"); if (child) { rule.set_min_scale(child->get_value()); } child = node.get_opt_child("MaxScaleDenominator"); if (child) { rule.set_max_scale(child->get_value()); } parse_symbolizers(rule, node); style.add_rule(std::move(rule)); } catch (config_error const& ex) { if (!name.empty()) { ex.append_context(std::string("in rule '") + name + "'", node); } throw; } } void map_parser::parse_symbolizers(rule & rule, xml_node const & node) { rule.reserve(node.size()); for (auto const& sym_node : node) { switch (name2int(sym_node.name().c_str())) { case name2int("PointSymbolizer"): parse_point_symbolizer(rule, sym_node); sym_node.set_processed(true); break; case name2int("LinePatternSymbolizer"): parse_line_pattern_symbolizer(rule, sym_node); sym_node.set_processed(true); break; case name2int("PolygonPatternSymbolizer"): parse_polygon_pattern_symbolizer(rule, sym_node); sym_node.set_processed(true); break; case name2int("TextSymbolizer"): parse_text_symbolizer(rule, sym_node); sym_node.set_processed(true); break; case name2int("ShieldSymbolizer"): parse_shield_symbolizer(rule, sym_node); sym_node.set_processed(true); break; case name2int("LineSymbolizer"): parse_line_symbolizer(rule, sym_node); sym_node.set_processed(true); break; case name2int("PolygonSymbolizer"): parse_polygon_symbolizer(rule, sym_node); sym_node.set_processed(true); break; case name2int("BuildingSymbolizer"): parse_building_symbolizer(rule, sym_node); sym_node.set_processed(true); break; case name2int("RasterSymbolizer"): parse_raster_symbolizer(rule, sym_node); sym_node.set_processed(true); break; case name2int("MarkersSymbolizer"): parse_markers_symbolizer(rule, sym_node); sym_node.set_processed(true); break; case name2int("GroupSymbolizer"): parse_group_symbolizer(rule, sym_node); sym_node.set_processed(true); break; case name2int("DebugSymbolizer"): parse_debug_symbolizer(rule, sym_node); sym_node.set_processed(true); break; default: break; } } } namespace detail { // helpers template struct set_symbolizer_property_impl { static void apply(Symbolizer & sym, keys key, xml_node const& node) { using value_type = T; std::string const& name = std::get<0>(get_meta(key)); try { optional val = node.get_opt_attr(name); if (val) put(sym, key, *val); } catch (config_error const& ex) { // try parsing as an expression optional val = node.get_opt_attr(name); if (val) { // first try pre-evaluate expressions which don't have dynamic properties auto result = pre_evaluate_expression(*val); if (std::get<1>(result)) { set_property_from_value(sym, key,std::get<0>(result)); } else { // expression_ptr put(sym, key, *val); } } else { ex.append_context(std::string("set_symbolizer_property '") + name + "'", node); throw; } } } }; template struct set_symbolizer_property_impl { static void apply(Symbolizer & sym, keys key, xml_node const & node) { std::string const& name = std::get<0>(get_meta(key)); optional transform = node.get_opt_attr(name); if (transform) put(sym, key, mapnik::parse_transform(*transform)); } }; template struct set_symbolizer_property_impl { static void apply(Symbolizer & sym, keys key, xml_node const & node) { using value_type = T; std::string const& name = std::get<0>(get_meta(key)); try { optional enum_str = node.get_opt_attr(name); if (enum_str) { optional enum_val = detail::enum_traits::from_string(*enum_str); if (enum_val) { put(sym, key, *enum_val); } else { optional val = node.get_opt_attr(name); if (val) { // first try pre-evaluating expression auto result = pre_evaluate_expression(*val); if (std::get<1>(result)) { optional enum_val = detail::enum_traits::from_string(std::get<0>(result).to_string()); if (enum_val) { put(sym, key, *enum_val); } else { // can't evaluate throw config_error("failed to parse symbolizer property: '" + name + "'"); } } else { // put expression_ptr put(sym, key, *val); } } else { throw config_error("failed to parse symbolizer property: '" + name + "'"); } } } } catch (config_error const& ex) { ex.append_context(std::string("set_symbolizer_property '") + name + "'", node); throw; } } }; } // namespace detail template void set_symbolizer_property(Symbolizer & sym, keys key, xml_node const& node) { detail::set_symbolizer_property_impl::value>::apply(sym,key,node); } void map_parser::parse_symbolizer_base(symbolizer_base &sym, xml_node const& node) { // comp-op set_symbolizer_property(sym, keys::comp_op, node); // geometry transform set_symbolizer_property(sym, keys::geometry_transform, node); // clip set_symbolizer_property(sym, keys::clip, node); // simplify algorithm set_symbolizer_property(sym, keys::simplify_algorithm, node); // simplify value set_symbolizer_property(sym, keys::simplify_tolerance, node); // smooth value set_symbolizer_property(sym, keys::smooth, node); } void map_parser::parse_point_symbolizer(rule & rule, xml_node const & node) { try { optional file = node.get_opt_attr("file"); optional base = node.get_opt_attr("base"); optional image_transform_wkt = node.get_opt_attr("transform"); point_symbolizer sym; parse_symbolizer_base(sym, node); // allow-overlap set_symbolizer_property(sym, keys::allow_overlap, node); // opacity set_symbolizer_property(sym, keys::opacity, node); // ignore-placement set_symbolizer_property(sym, keys::ignore_placement, node); // point placement set_symbolizer_property(sym, keys::point_placement_type, node); if (file && !file->empty()) { if(base) { std::map::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); put(sym, keys::file, parse_path(filename)); set_symbolizer_property(sym, keys::image_transform, node); } rule.append(std::move(sym)); } catch (config_error const& ex) { ex.append_context(node); throw; } } void map_parser::parse_markers_symbolizer(rule & rule, xml_node const& node) { try { std::string filename(""); optional file = node.get_opt_attr("file"); optional base = node.get_opt_attr("base"); if (file && !file->empty()) { if (base) { std::map::const_iterator itr = file_sources_.find(*base); if (itr!=file_sources_.end()) { *file = itr->second + "/" + *file; } } filename = ensure_relative_to_xml(file); } optional marker_type = node.get_opt_attr("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://' 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 sym; parse_symbolizer_base(sym, node); if (!filename.empty()) { ensure_exists(filename); put(sym,keys::file, parse_path(filename)); } // overall opacity to be applied to all paths set_symbolizer_property(sym, keys::opacity, node); // fill opacity set_symbolizer_property(sym, keys::fill_opacity, node); // transform set_symbolizer_property(sym, keys::image_transform, node); // fill set_symbolizer_property(sym, keys::fill, node); // spacing set_symbolizer_property(sym, keys::spacing, node); // max-error set_symbolizer_property(sym, keys::max_error, node); // allow-overlap set_symbolizer_property(sym, keys::allow_overlap, node); // ignore-placement set_symbolizer_property(sym, keys::ignore_placement, node); // width //set_symbolizer_property(sym, keys::width, node); // height //set_symbolizer_property(sym, keys::height, node); optional width = node.get_opt_attr("width"); if (width) put(sym, keys::width, *width ); optional height = node.get_opt_attr("height"); if (height) put(sym, keys::height, *height); // stroke parse_stroke(sym,node); // marker placement set_symbolizer_property(sym, keys::markers_placement_type, node); // multi-policy set_symbolizer_property(sym, keys::markers_multipolicy, node); rule.append(std::move(sym)); } catch (config_error const& ex) { ex.append_context(node); throw; } } void map_parser::parse_line_pattern_symbolizer(rule & rule, xml_node const & node) { try { std::string file = node.get_attr("file"); if (file.empty()) { throw config_error("empty file attribute"); } optional base = node.get_opt_attr("base"); if(base) { std::map::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_symbolizer_base(symbol, node); put(symbol, keys::file, parse_path(file)); // offset value optional offset = node.get_opt_attr("offset"); if (offset) put(symbol, keys::offset, *offset); rule.append(std::move(symbol)); } catch (config_error const& ex) { ex.append_context(node); throw; } } void map_parser::parse_polygon_pattern_symbolizer(rule & rule, xml_node const & node) { try { std::string file = node.get_attr("file"); if (file.empty()) { throw config_error("empty file attribute"); } optional base = node.get_opt_attr("base"); if(base) { std::map::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); polygon_pattern_symbolizer symbol; parse_symbolizer_base(symbol, node); put(symbol, keys::file, parse_path(file)); // pattern alignment optional p_alignment = node.get_opt_attr("alignment"); if (p_alignment) put(symbol, keys::alignment, pattern_alignment_enum(*p_alignment)); // opacity set_symbolizer_property(symbol, keys::opacity, node); // gamma optional gamma = node.get_opt_attr("gamma"); if (gamma) put(symbol, keys::gamma, *gamma); // gamma method optional gamma_method = node.get_opt_attr("gamma-method"); if (gamma_method) put(symbol, keys::gamma_method, gamma_method_enum(*gamma_method)); rule.append(std::move(symbol)); } catch (config_error const& ex) { ex.append_context(node); throw; } } void map_parser::parse_text_symbolizer(rule & rule, xml_node const& node) { try { text_placements_ptr placements; optional placement_type = node.get_opt_attr("placement-type"); if (placement_type) { placements = placements::registry::instance().from_xml(*placement_type, node, fontsets_); } else { placements = std::make_shared(); placements->defaults.from_xml(node, fontsets_); } if (strict_ && !placements->defaults.format_defaults.fontset) { ensure_font_face(placements->defaults.format_defaults.face_name); } text_symbolizer sym; parse_symbolizer_base(sym, node); // placement finder put(sym, keys::text_placements_, placements); // halo-comp-op set_symbolizer_property(sym, keys::halo_comp_op, node); // halo-rasterizer set_symbolizer_property(sym, keys::halo_rasterizer, node); // halo-transform set_symbolizer_property(sym, keys::halo_transform, node); rule.append(std::move(sym)); } catch (config_error const& ex) { ex.append_context(node); throw; } } void map_parser::parse_shield_symbolizer(rule & rule, xml_node const& node) { try { text_placements_ptr placements; optional placement_type = node.get_opt_attr("placement-type"); if (placement_type) { placements = placements::registry::instance().from_xml(*placement_type, node, fontsets_); } else { placements = std::make_shared(); } placements->defaults.from_xml(node, fontsets_); if (strict_ && !placements->defaults.format_defaults.fontset) { ensure_font_face(placements->defaults.format_defaults.face_name); } shield_symbolizer sym; parse_symbolizer_base(sym, node); put(sym, keys::text_placements_, placements); // transform set_symbolizer_property(sym, keys::image_transform, node); // shield displacements: shield-dx shield-dy set_symbolizer_property(sym, keys::shield_dx, node); set_symbolizer_property(sym, keys::shield_dy, node); //optional shield_dx = node.get_opt_attr("shield-dx"); //if (shield_dx) put(shield_symbol, keys::shield_dx, *shield_dx); //optional shield_dy = node.get_opt_attr("shield-dy"); //if (shield_dy) put(shield_symbol, keys::shield_dy, *shield_dy); // opacity set_symbolizer_property(sym, keys::opacity, node); // text-opacity set_symbolizer_property(sym, keys::text_opacity, node); // unlock_image optional unlock_image = node.get_opt_attr("unlock-image"); if (unlock_image) put(sym, keys::unlock_image, *unlock_image); std::string file = node.get_attr("file"); if (file.empty()) { throw config_error("empty file attribute"); } optional base = node.get_opt_attr("base"); if(base) { std::map::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 no_text = node.get_opt_attr("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: \"' '\""; // FIXME // if (*no_text) // put(shield_symbol, "no-text", set_name(parse_expression("' '")); } file = ensure_relative_to_xml(file); ensure_exists(file); put(sym, keys::file , parse_path(file)); optional halo_rasterizer_ = node.get_opt_attr("halo-rasterizer"); if (halo_rasterizer_) put(sym, keys::halo_rasterizer, halo_rasterizer_enum(*halo_rasterizer_)); rule.append(std::move(sym)); } catch (config_error const& ex) { ex.append_context(node); throw; } } void map_parser::parse_stroke(symbolizer_base & sym, xml_node const & node) { // stroke set_symbolizer_property(sym, keys::stroke, node); // stroke-width set_symbolizer_property(sym, keys::stroke_width, node); // stroke-opacity set_symbolizer_property(sym, keys::stroke_opacity, node); // stroke-linejoin set_symbolizer_property(sym, keys::stroke_linejoin, node); // stroke-linecap set_symbolizer_property(sym, keys::stroke_linecap, node); // stroke-gamma set_symbolizer_property(sym, keys::stroke_gamma, node); // stroke-gamma-method set_symbolizer_property(sym, keys::stroke_gamma_method, node); // stroke-dashoffset set_symbolizer_property(sym, keys::stroke_dashoffset, node); // stroke-miterlimit set_symbolizer_property(sym, keys::stroke_miterlimit, node); // stroke-dasharray optional str = node.get_opt_attr("stroke-dasharray"); if (str) { std::vector buf; if (util::parse_dasharray((*str).begin(),(*str).end(),buf)) { if (!buf.empty()) { size_t size = buf.size(); if (size % 2 == 1) { buf.insert(buf.end(),buf.begin(),buf.end()); } dash_array dash; std::vector::const_iterator pos = buf.begin(); while (pos != buf.end()) { if (*pos > 0.0 || *(pos+1) > 0.0) // avoid both dash and gap eq 0.0 { dash.emplace_back(*pos,*(pos + 1)); } pos +=2; } if (dash.size() > 0) { put(sym,keys::stroke_dasharray,dash); } } } else { throw config_error(std::string("Failed to parse dasharray ") + "'. Expected a " + "list of floats or 'none' but got '" + (*str) + "'"); } } } void map_parser::parse_line_symbolizer(rule & rule, xml_node const & node) { try { line_symbolizer sym; parse_symbolizer_base(sym, node); // stroke parameters parse_stroke(sym, node); // offset set_symbolizer_property(sym, keys::offset, node); // rasterizer set_symbolizer_property(sym, keys::line_rasterizer, node); rule.append(std::move(sym)); } catch (config_error const& ex) { ex.append_context(node); throw; } } void map_parser::parse_polygon_symbolizer(rule & rule, xml_node const & node) { try { polygon_symbolizer sym; parse_symbolizer_base(sym, node); // fill set_symbolizer_property(sym, keys::fill, node); // fill-opacity set_symbolizer_property(sym, keys::fill_opacity, node); // gamma set_symbolizer_property(sym, keys::gamma, node); // gamma method set_symbolizer_property(sym, keys::gamma_method, node); rule.append(std::move(sym)); } catch (config_error const& ex) { ex.append_context(node); throw; } } void map_parser::parse_building_symbolizer(rule & rule, xml_node const & node) { try { building_symbolizer building_sym; parse_symbolizer_base(building_sym, node); // fill set_symbolizer_property(building_sym, keys::fill, node); // fill-opacity set_symbolizer_property(building_sym, keys::fill_opacity, node); // height optional height = node.get_opt_attr("height"); if (height) put(building_sym, keys::height, *height); rule.append(std::move(building_sym)); } catch (config_error const& ex) { ex.append_context(node); throw; } } void map_parser::parse_raster_symbolizer(rule & rule, xml_node const & node) { try { raster_symbolizer raster_sym; parse_symbolizer_base(raster_sym, node); // mode optional mode = node.get_opt_attr("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,"_","-"); } put(raster_sym, keys::mode, mode_string); } // scaling optional scaling = node.get_opt_attr("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"; put(raster_sym, keys::scaling, SCALING_NEAR); } else { boost::optional method = scaling_method_from_string(scaling_method); if (method) { put(raster_sym, keys::scaling, *method); } else { throw config_error("failed to parse 'scaling': '" + *scaling + "'"); } } } // opacity optional opacity = node.get_opt_attr("opacity"); if (opacity) put(raster_sym, keys::opacity, *opacity); // filter factor optional filter_factor = node.get_opt_attr("filter-factor"); if (filter_factor) put(raster_sym, keys::filter_factor, *filter_factor); // mesh-size optional mesh_size = node.get_opt_attr("mesh-size"); if (mesh_size) put(raster_sym, keys::mesh_size, *mesh_size); // premultiplied status of image optional premultiplied = node.get_opt_attr("premultiplied"); if (premultiplied) put(raster_sym, keys::premultiplied, *premultiplied); bool found_colorizer = false; for ( auto const& css : node) { if (css.is("RasterColorizer")) { found_colorizer = true; raster_colorizer_ptr colorizer = std::make_shared(); put(raster_sym, keys::colorizer, colorizer); if (parse_raster_colorizer(colorizer, css)) put(raster_sym, keys::colorizer, colorizer); } } //look for properties one level up if (!found_colorizer) { raster_colorizer_ptr colorizer = std::make_shared(); if (parse_raster_colorizer(colorizer, node)) put(raster_sym, keys::colorizer, colorizer); } rule.append(std::move(raster_sym)); } catch (config_error const& ex) { ex.append_context(node); throw; } } void map_parser::parse_group_symbolizer(rule &rule, xml_node const & node) { try { group_symbolizer symbol; parse_symbolizer_base(symbol, node); group_symbolizer_properties_ptr prop = std::make_shared(); set_symbolizer_property(symbol, keys::num_columns, node); set_symbolizer_property(symbol, keys::start_column, node); set_symbolizer_property(symbol, keys::repeat_key, node); text_placements_ptr placements = std::make_shared(); placements->defaults.placement_properties_from_xml(node); put(symbol, keys::text_placements_, placements); size_t layout_count = 0; for (auto const& child_node : node) { if (child_node.is("GroupRule")) { parse_group_rule(*prop, child_node); child_node.set_processed(true); } else if (child_node.is("SimpleLayout")) { parse_simple_layout(*prop, child_node); child_node.set_processed(true); ++layout_count; } else if (child_node.is("PairLayout")) { parse_pair_layout(*prop, child_node); child_node.set_processed(true); ++layout_count; } if (layout_count > 1) { throw config_error("Provide only one layout for a GroupSymbolizer."); } } put(symbol, keys::group_properties, prop); rule.append(std::move(symbol)); } catch (const config_error & ex) { ex.append_context(node); throw; } } void map_parser::parse_debug_symbolizer(rule & rule, xml_node const & node) { debug_symbolizer symbol; parse_symbolizer_base(symbol, node); optional mode = node.get_opt_attr("mode"); if (mode) put(symbol, keys::mode, debug_symbolizer_mode_enum(*mode)); rule.append(std::move(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("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 default_color = node.get_opt_attr("default-color"); if (default_color) { rc->set_default_color(*default_color); } // epsilon optional eps = node.get_opt_attr("epsilon"); if (eps) { if(*eps < 0) { throw config_error("RasterColorizer epsilon must be > 0. "); } rc->set_epsilon(*eps); } float maximumValue = -std::numeric_limits::max(); for (auto const& n : node) { if (n.is("stop")) { found_stops = true; // colour is optional. optional stopcolor = n.get_opt_attr("color"); if (!stopcolor) { *stopcolor = *default_color; } // mode default to INHERIT colorizer_mode mode = n.get_attr("mode", COLORIZER_INHERIT); // value is required, and it must be bigger than the previous optional value = n.get_opt_attr("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 label = n.get_opt_attr("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 (config_error const& ex) { ex.append_context(node); throw; } return found_stops; } void map_parser::parse_group_rule(group_symbolizer_properties & prop, xml_node const & node) { try { rule fake_rule; expression_ptr filter, repeat_key; xml_node const *filter_child = node.get_opt_child("Filter"), *rptkey_child = node.get_opt_child("RepeatKey"); if (filter_child) { filter = filter_child->get_value(); } else { filter = std::make_shared(true); } if (rptkey_child) { repeat_key = rptkey_child->get_value(); } group_rule_ptr rule = std::make_shared(filter, repeat_key); parse_symbolizers(fake_rule, node); for (auto const& sym : fake_rule) { rule->append(sym); } prop.add_rule(rule); } catch (const config_error & ex) { ex.append_context(node); throw; } } void map_parser::parse_simple_layout(group_symbolizer_properties & prop, xml_node const & node) { simple_row_layout layout; optional item_margin = node.get_opt_attr("item-margin"); if (item_margin) layout.set_item_margin(*item_margin); prop.set_layout(std::move(layout)); } void map_parser::parse_pair_layout(group_symbolizer_properties & prop, xml_node const & node) { pair_layout layout; optional item_margin = node.get_opt_attr("item-margin"); if (item_margin) layout.set_item_margin(*item_margin); optional max_difference = node.get_opt_attr("max-difference"); if (max_difference) layout.set_max_difference(*max_difference); prop.set_layout(std::move(layout)); } 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 const& opt_path) { if (marker_cache::instance().is_uri(*opt_path)) return *opt_path; if (!xml_base_path_.empty()) { std::string starting_path = *opt_path; if (mapnik::util::is_relative(starting_path)) { return mapnik::util::make_absolute(starting_path,xml_base_path_); } } 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 (!mapnik::util::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& attrs = node.get_attributes(); for (auto const& attr : attrs) { if (!attr.second.processed) { error_message += "\n* attribute '" + attr.first + "' with value '" + attr.second.value + "' at line " + node.line_to_string(); } } for (auto const& child_node : node) { find_unused_nodes_recursive(child_node, error_message); } } } // end of namespace mapnik