/***************************************************************************** * * This file is part of Mapnik (c++ mapping toolkit) * * Copyright (C) 2010 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 #ifdef HAVE_LIBXML2 #include #endif #include #include #include #include #include #include // boost #include #include #include #include #include #include #include #include // agg #include "agg_trans_affine.h" // stl #include using boost::lexical_cast; using boost::bad_lexical_cast; using boost::tokenizer; using boost::property_tree::ptree; using std::cerr; using std::endl; namespace mapnik { using boost::optional; class map_parser : boost::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, ptree const & sty); private: void parse_map_include( Map & map, ptree const & include); void parse_style(Map & map, ptree const & sty); void parse_layer(Map & map, ptree const & lay); void parse_metawriter(Map & map, ptree const & lay); void parse_metawriter_in_symbolizer(symbolizer_base &sym, ptree const &pt); void parse_fontset(Map & map, ptree const & fset); void parse_font(font_set & fset, ptree const & f); void parse_rule(feature_type_style & style, ptree const & r); void parse_point_symbolizer(rule & rule, ptree const & sym); void parse_line_pattern_symbolizer(rule & rule, ptree const & sym); void parse_polygon_pattern_symbolizer(rule & rule, ptree const & sym); void parse_text_symbolizer(rule & rule, ptree const & sym); void parse_shield_symbolizer(rule & rule, ptree const & sym); void parse_line_symbolizer(rule & rule, ptree const & sym); void parse_polygon_symbolizer(rule & rule, ptree const & sym); void parse_building_symbolizer(rule & rule, ptree const & sym ); void parse_raster_symbolizer(rule & rule, ptree const & sym ); void parse_markers_symbolizer(rule & rule, ptree const & sym ); void parse_glyph_symbolizer(rule & rule, ptree const & sym ); void parse_raster_colorizer(raster_colorizer_ptr const& rc, ptree const& node ); void parse_stroke(stroke & strk, ptree const & sym); void ensure_font_face( const std::string & face_name ); std::string ensure_relative_to_xml( boost::optional opt_path ); void ensure_attrs( ptree const& sym, std::string name, std::string attrs); bool strict_; std::string filename_; bool relative_to_xml_; std::map datasource_templates_; freetype_engine font_engine_; face_manager font_manager_; std::map file_sources_; std::map fontsets_; }; void load_map(Map & map, std::string const& filename, bool strict) { ptree pt; #ifdef HAVE_LIBXML2 read_xml2(filename, pt); #else try { read_xml(filename, pt); } catch (const boost::property_tree::xml_parser_error & ex) { throw config_error( ex.what() ); } #endif map_parser parser( strict, filename); parser.parse_map(map, pt); } void load_map_string(Map & map, std::string const& str, bool strict, std::string const& base_url) { ptree pt; #ifdef HAVE_LIBXML2 read_xml2_string(str, pt, base_url); #else try { std::istringstream s(str); read_xml(s,pt); } catch (const boost::property_tree::xml_parser_error & ex) { throw config_error( ex.what() ) ; } #endif map_parser parser( strict, base_url); parser.parse_map(map, pt); } void map_parser::parse_map( Map & map, ptree const & pt ) { try { ptree const & map_node = pt.get_child("Map"); std::ostringstream s(""); s << "background-color," << "background-image," << "srs," << "buffer-size," << "paths-from-xml," << "minimum-version," << "font-directory"; ensure_attrs(map_node, "Map", s.str()); try { parameters extra_attr; // 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 paths_from_xml = get_opt_attr(map_node, "paths-from-xml"); if (paths_from_xml) { relative_to_xml_ = *paths_from_xml; } optional bgcolor = get_opt_attr(map_node, "background-color"); if (bgcolor) { map.set_background( * bgcolor ); } optional image_filename = get_opt_attr(map_node, "background-image"); if (image_filename) { map.set_background_image(ensure_relative_to_xml(image_filename)); } map.set_srs( get_attr(map_node, "srs", map.srs() )); optional buffer_size = get_opt_attr(map_node,"buffer-size"); if (buffer_size) { map.set_buffer_size(*buffer_size); } optional font_directory = get_opt_attr(map_node,"font-directory"); if (font_directory) { extra_attr["font-directory"] = *font_directory; freetype_engine::register_fonts( ensure_relative_to_xml(font_directory), false); } optional min_version_string = get_opt_attr(map_node, "minimum-version"); if (min_version_string) { extra_attr["minimum-version"] = *min_version_string; boost::char_separator sep("."); boost::tokenizer > tokens(*min_version_string,sep); unsigned i = 0; bool success = false; int n[3]; for (boost::tokenizer >::iterator beg=tokens.begin(); beg!=tokens.end();++beg) { try { n[i] = boost::lexical_cast(boost::trim_copy(*beg)); } catch (boost::bad_lexical_cast & ex) { std::clog << *beg << " : " << ex.what() << "\n"; 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"); } } } map.set_extra_attributes(extra_attr); } catch (const config_error & ex) { ex.append_context("(in node Map)"); throw; } parse_map_include( map, map_node ); } catch (const boost::property_tree::ptree_bad_path &) { throw config_error("Not a map file. Node 'Map' not found."); } } void map_parser::parse_map_include( Map & map, ptree const & include ) { ptree::const_iterator itr = include.begin(); ptree::const_iterator end = include.end(); for (; itr != end; ++itr) { ptree::value_type const& v = *itr; if (v.first == "Include") { parse_map_include( map, v.second ); } else if (v.first == "Style") { parse_style( map, v.second ); } else if (v.first == "Layer") { parse_layer(map, v.second ); } else if (v.first == "FontSet") { parse_fontset(map, v.second); } else if (v.first == "MetaWriter") { parse_metawriter(map, v.second); } else if (v.first == "FileSource") { std::string name = get_attr( v.second, "name"); std::string value = get_value( v.second, ""); file_sources_[name] = value; } else if (v.first == "Datasource") { std::string name = get_attr(v.second, "name", string("Unnamed")); parameters params; ptree::const_iterator paramIter = v.second.begin(); ptree::const_iterator endParam = v.second.end(); for (; paramIter != endParam; ++paramIter) { ptree const& param = paramIter->second; if (paramIter->first == "Parameter") { std::string name = get_attr(param, "name"); std::string value = get_value( param, "datasource parameter"); params[name] = value; } else if( paramIter->first != "" && paramIter->first != "" ) { throw config_error(std::string("Unknown child node in ") + "'Datasource'. Expected 'Parameter' but got '" + paramIter->first + "'"); } } datasource_templates_[name] = params; } else if (v.first != "" && v.first != "") { throw config_error(std::string("Unknown child node in 'Map': '") + v.first + "'"); } } map.init_metawriters(); } void map_parser::parse_style( Map & map, ptree const & sty ) { std::ostringstream s(""); s << "name," << "filter-mode"; ensure_attrs(sty, "Style", s.str()); string name(""); try { name = get_attr(sty, "name"); feature_type_style style; filter_mode_e filter_mode = get_attr(sty, "filter-mode", FILTER_ALL); style.set_filter_mode(filter_mode); ptree::const_iterator ruleIter = sty.begin(); ptree::const_iterator endRule = sty.end(); for (; ruleIter!=endRule; ++ruleIter) { ptree::value_type const& rule_tag = *ruleIter; if (rule_tag.first == "Rule") { parse_rule( style, rule_tag.second ); } else if (rule_tag.first != "" && rule_tag.first != "" ) { throw config_error(std::string("Unknown child node in 'Style'. ") + "Expected 'Rule' but got '" + rule_tag.first + "'"); } } map.insert_style(name, style); } catch (const config_error & ex) { if ( ! name.empty() ) { ex.append_context(string("in style '") + name + "'"); } ex.append_context(string("in map '") + filename_ + "'"); throw; } } void map_parser::parse_metawriter(Map & map, ptree const & pt) { ensure_attrs(pt, "MetaWriter", "name,type,file,default-output,output-empty"); string name(""); metawriter_ptr writer; try { name = get_attr(pt, "name"); string type = get_attr(pt, "type"); if (type == "json") { string file = get_attr(pt, "file"); optional properties = get_opt_attr(pt, "default-output"); metawriter_json_ptr json = metawriter_json_ptr(new metawriter_json(properties, parse_path(file))); optional output_empty = get_opt_attr(pt, "output-empty"); if (output_empty) { json->set_output_empty(*output_empty); } writer = json; } else { throw config_error(string("Unknown type '") + type + "'"); } map.insert_metawriter(name, writer); } catch (const config_error & ex) { if (!name.empty()) { ex.append_context(string("in meta writer '") + name + "'"); } ex.append_context(string("in map '") + filename_ + "'"); throw; } } void map_parser::parse_fontset( Map & map, ptree const & fset ) { ensure_attrs(fset, "FontSet", "name,Font"); string name(""); try { name = get_attr(fset, "name"); font_set fontset(name); ptree::const_iterator itr = fset.begin(); ptree::const_iterator end = fset.end(); for (; itr != end; ++itr) { ptree::value_type const& font_tag = *itr; if (font_tag.first == "Font") { parse_font(fontset, font_tag.second); } else if (font_tag.first != "" && font_tag.first != "" ) { throw config_error(std::string("Unknown child node in 'FontSet'. ") + "Expected 'Font' but got '" + font_tag.first + "'"); } } map.insert_fontset(name, fontset); // XXX Hack because map object isn't accessible by text_symbolizer // when it's parsed fontsets_.insert(pair(name, fontset)); } catch (const config_error & ex) { if ( ! name.empty() ) { ex.append_context(string("in FontSet '") + name + "'"); } ex.append_context(string("in map '") + filename_ + "'"); throw; } } void map_parser::parse_font(font_set & fset, ptree const & f) { ensure_attrs(f, "Font", "face-name"); std::string face_name = get_attr(f, "face-name", string()); if ( strict_ ) { ensure_font_face( face_name ); } fset.add_face_name(face_name); } void map_parser::parse_layer( Map & map, ptree const & lay ) { std::string name; std::ostringstream s(""); s << "name," << "srs," << "status," << "title," << "abstract," << "minzoom," << "maxzoom," << "queryable," << "clear-label-cache"; ensure_attrs(lay, "Layer", s.str()); try { name = get_attr(lay, "name", string("Unnamed")); // XXX if no projection is given inherit from map? [DS] std::string srs = get_attr(lay, "srs", map.srs()); layer lyr(name, srs); optional status = get_opt_attr(lay, "status"); if (status) { lyr.setActive( * status ); } optional title = get_opt_attr(lay, "title"); if (title) { lyr.set_title( * title ); } optional abstract = get_opt_attr(lay, "abstract"); if (abstract) { lyr.set_abstract( * abstract ); } optional minZoom = get_opt_attr(lay, "minzoom"); if (minZoom) { lyr.setMinZoom( * minZoom ); } optional maxZoom = get_opt_attr(lay, "maxzoom"); if (maxZoom) { lyr.setMaxZoom( * maxZoom ); } optional queryable = get_opt_attr(lay, "queryable"); if (queryable) { lyr.setQueryable( * queryable ); } optional clear_cache = get_opt_attr(lay, "clear-label-cache"); if (clear_cache) { lyr.set_clear_label_cache( * clear_cache ); } optional cache_features = get_opt_attr(lay, "cache-features"); if (cache_features) { lyr.set_cache_features( * cache_features ); } ptree::const_iterator itr2 = lay.begin(); ptree::const_iterator end2 = lay.end(); for(; itr2 != end2; ++itr2) { ptree::value_type const& child = *itr2; if (child.first == "StyleName") { ensure_attrs(child.second, "StyleName", "none"); std::string style_name = child.second.data(); if (style_name.empty()) { std::ostringstream ss; ss << "StyleName is empty in Layer: '" << lyr.name() << "'"; if (strict_) throw config_error(ss.str()); else std::clog << "### WARNING: " << ss.str() << std::endl; } else { lyr.add_style(style_name); } } else if (child.first == "Datasource") { ensure_attrs(child.second, "Datasource", "base"); parameters params; optional base = get_opt_attr( child.second, "base" ); if( base ) { std::map::const_iterator base_itr = datasource_templates_.find(*base); if (base_itr!=datasource_templates_.end()) params = base_itr->second; } ptree::const_iterator paramIter = child.second.begin(); ptree::const_iterator endParam = child.second.end(); for (; paramIter != endParam; ++paramIter) { ptree const& param = paramIter->second; if (paramIter->first == "Parameter") { ensure_attrs(param, "Parameter", "name"); std::string name = get_attr(param, "name"); std::string value = get_value( param, "datasource parameter"); params[name] = value; } else if( paramIter->first != "" && paramIter->first != "" ) { throw config_error(std::string("Unknown child node in ") + "'Datasource'. Expected 'Parameter' but got '" + paramIter->first + "'"); } } 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 { boost::shared_ptr ds = datasource_cache::instance()->create(params); lyr.set_datasource(ds); } // catch problem at datasource registration catch (const mapnik::config_error & ex ) { throw config_error( ex.what() ); } // catch problem at the datasource creation catch (const mapnik::datasource_exception & ex ) { throw config_error( ex.what() ); } catch (...) { //throw config_error("exception..."); } } else if (child.first != "" && child.first != "") { throw config_error(std::string("Unknown child node in 'Layer'. ") + "Expected 'StyleName' or 'Datasource' but got '" + child.first + "'"); } } map.addLayer(lyr); } catch (const config_error & ex) { if ( ! name.empty() ) { ex.append_context(std::string("(encountered during parsing of layer '") + name + "' in map '" + filename_ + "')"); } throw; } } void map_parser::parse_rule( feature_type_style & style, ptree const & r ) { ensure_attrs(r, "Rule", "name,title"); std::string name; try { name = get_attr( r, "name", string()); std::string title = get_attr( r, "title", string()); rule rule(name,title); optional filter_expr = get_opt_child( r, "Filter"); if (filter_expr) { // TODO - can we use encoding defined for XML document for filter expressions? rule.set_filter(parse_expression(*filter_expr,"utf8")); } optional else_filter = get_opt_child(r, "ElseFilter"); if (else_filter) { rule.set_else(true); } optional min_scale = get_opt_child(r, "MinScaleDenominator"); if (min_scale) { rule.set_min_scale(*min_scale); } optional max_scale = get_opt_child(r, "MaxScaleDenominator"); if (max_scale) { rule.set_max_scale(*max_scale); } ptree::const_iterator symIter = r.begin(); ptree::const_iterator endSym = r.end(); for( ;symIter != endSym; ++symIter) { ptree::value_type const& sym = *symIter; if ( sym.first == "PointSymbolizer") { parse_point_symbolizer( rule, sym.second ); } else if ( sym.first == "LinePatternSymbolizer") { parse_line_pattern_symbolizer( rule, sym.second ); } else if ( sym.first == "PolygonPatternSymbolizer") { parse_polygon_pattern_symbolizer( rule, sym.second ); } else if ( sym.first == "TextSymbolizer") { parse_text_symbolizer( rule, sym.second ); } else if ( sym.first == "ShieldSymbolizer") { parse_shield_symbolizer( rule, sym.second ); } else if ( sym.first == "LineSymbolizer") { parse_line_symbolizer( rule, sym.second ); } else if ( sym.first == "PolygonSymbolizer") { parse_polygon_symbolizer( rule, sym.second ); } else if ( sym.first == "BuildingSymbolizer") { parse_building_symbolizer( rule, sym.second ); } else if ( sym.first == "RasterSymbolizer") { parse_raster_symbolizer( rule, sym.second ); } else if ( sym.first == "MarkersSymbolizer") { parse_markers_symbolizer(rule, sym.second); } else if ( sym.first == "GlyphSymbolizer") { parse_glyph_symbolizer( rule, sym.second ); } else if ( sym.first != "MinScaleDenominator" && sym.first != "MaxScaleDenominator" && sym.first != "Filter" && sym.first != "ElseFilter" && sym.first != "" && sym.first != "" ) { throw config_error(std::string("Unknown symbolizer '") + sym.first + "'"); } } style.add_rule(rule); } catch (const config_error & ex) { if ( ! name.empty() ) { ex.append_context(string("in rule '") + name + "' in map '" + filename_ + "')"); } throw; } } void map_parser::parse_metawriter_in_symbolizer(symbolizer_base &sym, ptree const &pt) { optional writer = get_opt_attr(pt, "meta-writer"); if (!writer) return; optional output = get_opt_attr(pt, "meta-output"); sym.add_metawriter(*writer, output); } void map_parser::parse_point_symbolizer( rule & rule, ptree const & sym ) { try { std::stringstream s; s << "file,base,allow-overlap,ignore-placement,opacity,placement,meta-writer,meta-output"; optional file = get_opt_attr(sym, "file"); optional base = get_opt_attr(sym, "base"); optional allow_overlap = get_opt_attr(sym, "allow-overlap"); optional ignore_placement = get_opt_attr(sym, "ignore-placement"); optional opacity = get_opt_attr(sym, "opacity"); optional transform_wkt = get_opt_attr(sym, "transform"); if (file) { s << "base,transform"; ensure_attrs(sym, "PointSymbolizer", s.str()); try { 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); point_symbolizer symbol(parse_path(*file)); 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 = get_attr(sym, "placement", CENTROID_POINT_PLACEMENT); symbol.set_point_placement( placement ); if (transform_wkt) { agg::trans_affine tr; if (!mapnik::svg::parse_transform(*transform_wkt,tr)) { std::stringstream ss; ss << "Could not parse transform from '" << transform_wkt << "', expected string like: 'matrix(1, 0, 0, 1, 0, 0)'"; if (strict_) throw config_error(ss.str()); // value_error here? else clog << "### WARNING: " << ss << endl; } boost::array matrix; tr.store_to(&matrix[0]); symbol.set_transform(matrix); } parse_metawriter_in_symbolizer(symbol, sym); rule.append(symbol); } catch (image_reader_exception const & ex ) { string msg("Failed to load image file '" + * file + "': " + ex.what()); if (strict_) { throw config_error(msg); } else { clog << "### WARNING: " << msg << endl; } } } else { // transform not supported ensure_attrs(sym, "PointSymbolizer", s.str()); 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 = get_attr(sym, "placement", CENTROID_POINT_PLACEMENT); symbol.set_point_placement( placement ); parse_metawriter_in_symbolizer(symbol, sym); rule.append(symbol); } } catch (const config_error & ex) { ex.append_context("in PointSymbolizer"); throw; } } void map_parser::parse_markers_symbolizer( rule & rule, ptree const & sym ) { try { std::string filename(""); optional file = get_opt_attr(sym, "file"); optional base = get_opt_attr(sym, "base"); optional transform_wkt = get_opt_attr(sym, "transform"); std::stringstream s; //s << "file,opacity,spacing,max-error,allow-overlap,placement,"; s << "file,base,transform,fill,opacity," << "spacing,max-error,allow-overlap," << "width,height,placement,marker-type," << "meta-writer,meta-output," << "stroke,stroke-width,stroke-opacity,stroke-linejoin," << "stroke-linecap,stroke-gamma,stroke-dashoffet,stroke-dasharray"; ensure_attrs(sym, "MarkersSymbolizer", s.str()); if (file) { //s << "base,transform"; //ensure_attrs(sym, "MarkersSymbolizer", s.str()); try { 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); } catch (...) { string msg("Failed to load marker file '" + *file + "'!"); if (strict_) { throw config_error(msg); } else { clog << "### WARNING: " << msg << endl; } } } /*else { //s << "fill,marker-type,width,height"; //ensure_attrs(sym, "MarkersSymbolizer", s.str()); }*/ markers_symbolizer symbol(parse_path(filename)); optional opacity = get_opt_attr(sym, "opacity"); if (opacity) symbol.set_opacity( *opacity ); if (transform_wkt) { agg::trans_affine tr; if (!mapnik::svg::parse_transform(*transform_wkt,tr)) { std::stringstream ss; ss << "Could not parse transform from '" << transform_wkt << "', expected string like: 'matrix(1, 0, 0, 1, 0, 0)'"; if (strict_) throw config_error(ss.str()); // value_error here? else clog << "### WARNING: " << ss << endl; } boost::array matrix; tr.store_to(&matrix[0]); symbol.set_transform(matrix); } optional c = get_opt_attr(sym, "fill"); if (c) symbol.set_fill(*c); optional spacing = get_opt_attr(sym, "spacing"); if (spacing) symbol.set_spacing(*spacing); optional max_error = get_opt_attr(sym, "max-error"); if (max_error) symbol.set_max_error(*max_error); optional allow_overlap = get_opt_attr(sym, "allow-overlap"); if (allow_overlap) symbol.set_allow_overlap(*allow_overlap); optional w = get_opt_attr(sym, "width"); optional h = get_opt_attr(sym, "height"); if (w && h) { symbol.set_width(*w); symbol.set_height(*h); } else if (w) { symbol.set_width(*w); symbol.set_height(*w); } else if (h) { symbol.set_width(*h); symbol.set_height(*h); } stroke strk; parse_stroke(strk,sym); symbol.set_stroke(strk); marker_placement_e placement = get_attr(sym, "placement", MARKER_LINE_PLACEMENT); symbol.set_marker_placement( placement ); marker_type_e dfl_marker_type = ARROW; if (placement == MARKER_POINT_PLACEMENT) dfl_marker_type = ELLIPSE; marker_type_e marker_type = get_attr(sym, "marker-type", dfl_marker_type); symbol.set_marker_type( marker_type ); parse_metawriter_in_symbolizer(symbol, sym); rule.append(symbol); } catch (const config_error & ex) { ex.append_context("in MarkersSymbolizer"); throw; } } void map_parser::parse_line_pattern_symbolizer( rule & rule, ptree const & sym ) { ensure_attrs(sym, "LinePatternSymbolizer", "file,base,meta-writer,meta-output"); try { std::string file = get_attr(sym, "file"); optional base = get_opt_attr(sym, "base"); try { 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); line_pattern_symbolizer symbol(parse_path(file)); parse_metawriter_in_symbolizer(symbol, sym); rule.append(symbol); } catch (image_reader_exception const & ex ) { string msg("Failed to load image file '" + file + "': " + ex.what()); if (strict_) { throw config_error(msg); } else { clog << "### WARNING: " << msg << endl; } } } catch (const config_error & ex) { ex.append_context("in LinePatternSymbolizer"); throw; } } void map_parser::parse_polygon_pattern_symbolizer( rule & rule, ptree const & sym ) { ensure_attrs(sym, "PolygonPatternSymbolizer", "file,base,alignment,meta-writer,meta-output"); try { std::string file = get_attr(sym, "file"); optional base = get_opt_attr(sym, "base"); try { if( base ) { std::map::iterator itr = file_sources_.find(*base); if (itr!=file_sources_.end()) { file = itr->second + "/" + file; } } file = ensure_relative_to_xml(file); polygon_pattern_symbolizer symbol(parse_path(file)); // pattern alignment pattern_alignment_e p_alignment = get_attr(sym, "alignment",LOCAL_ALIGNMENT); symbol.set_alignment(p_alignment); parse_metawriter_in_symbolizer(symbol, sym); rule.append(symbol); } catch (image_reader_exception const & ex ) { string msg("Failed to load image file '" + file + "': " + ex.what()); if (strict_) { throw config_error(msg); } else { clog << "### WARNING: " << msg << endl; } } } catch (const config_error & ex) { ex.append_context("in PolygonPatternSymbolizer"); throw; } } void map_parser::parse_text_symbolizer( rule & rule, ptree const & sym ) { std::stringstream s; s << "name,face-name,fontset-name,size,fill,orientation," << "dx,dy,placement,vertical-alignment,halo-fill," << "halo-radius,text-ratio,wrap-width,wrap-before," << "wrap-character,text-transform,line-spacing," << "label-position-tolerance,character-spacing," << "spacing,minimum-distance,minimum-padding," << "avoid-edges,allow-overlap,opacity,max-char-angle-delta," << "horizontal-alignment,justify-alignment," << "meta-writer,meta-output," << "placements,placement-type"; ensure_attrs(sym, "TextSymbolizer", s.str()); try { text_placements_ptr placement_finder; optional placement_type = get_opt_attr(sym, "placement-type"); if (placement_type) { if (*placement_type == "simple") { placement_finder = text_placements_ptr( new text_placements_simple( get_attr(sym, "placements", "X"))); } } if (!placement_finder) { placement_finder = text_placements_ptr(new text_placements_dummy()); } std::string name = get_attr(sym, "name"); optional face_name = get_opt_attr(sym, "face-name"); optional fontset_name = get_opt_attr(sym, "fontset-name"); unsigned size = get_attr(sym, "size", 10U); color c = get_attr(sym, "fill", color(0,0,0)); text_symbolizer text_symbol = text_symbolizer(parse_expression(name, "utf8"), size, c, placement_finder); optional orientation = get_opt_attr(sym, "orientation"); if (orientation) { text_symbol.set_orientation(parse_expression(*orientation, "utf8")); } if (fontset_name && face_name) { throw config_error(std::string("Can't have both face-name and fontset-name")); } else if (fontset_name) { std::map::const_iterator itr = fontsets_.find(*fontset_name); if (itr != fontsets_.end()) { text_symbol.set_fontset(itr->second); } else { throw config_error("Unable to find any fontset named '" + *fontset_name + "'"); } } else if (face_name) { if ( strict_ ) { ensure_font_face(*face_name); } text_symbol.set_face_name(*face_name); } else { throw config_error(std::string("Must have face-name or fontset-name")); } double dx = get_attr(sym, "dx", 0.0); double dy = get_attr(sym, "dy", 0.0); text_symbol.set_displacement(dx,dy); label_placement_e placement = get_attr(sym, "placement", POINT_PLACEMENT); text_symbol.set_label_placement( placement ); // vertical alignment vertical_alignment_e default_vertical_alignment = V_AUTO; vertical_alignment_e valign = get_attr(sym, "vertical-alignment", default_vertical_alignment); text_symbol.set_vertical_alignment(valign); // halo fill and radius optional halo_fill = get_opt_attr(sym, "halo-fill"); if (halo_fill) { text_symbol.set_halo_fill( * halo_fill ); } optional halo_radius = get_opt_attr(sym, "halo-radius"); if (halo_radius) { text_symbol.set_halo_radius(*halo_radius); } // text ratio and wrap width optional text_ratio = get_opt_attr(sym, "text-ratio"); if (text_ratio) { text_symbol.set_text_ratio(*text_ratio); } optional wrap_width = get_opt_attr(sym, "wrap-width"); if (wrap_width) { text_symbol.set_wrap_width(*wrap_width); } optional wrap_before = get_opt_attr(sym, "wrap-before"); if (wrap_before) { text_symbol.set_wrap_before(*wrap_before); } // character used to break long strings optional wrap_char = get_opt_attr(sym, "wrap-character"); if (wrap_char && (*wrap_char).size() > 0) { text_symbol.set_wrap_char((*wrap_char)[0]); } // text conversion before rendering text_transform_e tconvert = get_attr(sym, "text-transform", NONE); text_symbol.set_text_transform(tconvert); // spacing between text lines optional line_spacing = get_opt_attr(sym, "line-spacing"); if (line_spacing) { text_symbol.set_line_spacing(*line_spacing); } // tolerance between label spacing along line optional label_position_tolerance = get_opt_attr(sym, "label-position-tolerance"); if (label_position_tolerance) { text_symbol.set_label_position_tolerance(*label_position_tolerance); } // spacing between characters in text optional character_spacing = get_opt_attr(sym, "character-spacing"); if (character_spacing) { text_symbol.set_character_spacing(*character_spacing); } // spacing between repeated labels on lines optional spacing = get_opt_attr(sym, "spacing"); if (spacing) { text_symbol.set_label_spacing(*spacing); } // minimum distance between labels optional min_distance = get_opt_attr(sym, "minimum-distance"); if (min_distance) { text_symbol.set_minimum_distance(*min_distance); } // minimum distance from edge of the map optional min_padding = get_opt_attr(sym, "minimum-padding"); if (min_padding) { text_symbol.set_minimum_padding(*min_padding); } // do not render labels around edges optional avoid_edges = get_opt_attr(sym, "avoid-edges"); if (avoid_edges) { text_symbol.set_avoid_edges( * avoid_edges); } // allow_overlap optional allow_overlap = get_opt_attr(sym, "allow-overlap"); if (allow_overlap) { text_symbol.set_allow_overlap( * allow_overlap ); } // opacity optional opacity = get_opt_attr(sym, "opacity"); if (opacity) { text_symbol.set_text_opacity( * opacity ); } // max_char_angle_delta optional max_char_angle_delta = get_opt_attr(sym, "max-char-angle-delta"); if (max_char_angle_delta) { text_symbol.set_max_char_angle_delta( (*max_char_angle_delta)*(M_PI/180)); } // horizontal alignment horizontal_alignment_e halign = get_attr(sym, "horizontal-alignment", H_AUTO); text_symbol.set_horizontal_alignment(halign); // justify alignment justify_alignment_e jalign = get_attr(sym, "justify-alignment", J_MIDDLE); text_symbol.set_justify_alignment(jalign); parse_metawriter_in_symbolizer(text_symbol, sym); rule.append(text_symbol); } catch (const config_error & ex) { ex.append_context("in TextSymbolizer"); throw; } } void map_parser::parse_shield_symbolizer( rule & rule, ptree const & sym ) { std::stringstream s; //std::string a[] = {"a","b"}; s << "name,face-name,fontset-name,size,fill," << "dx,dy,placement,vertical-alignment,halo-fill," << "halo-radius,text-ratio,wrap-width,wrap-before," << "wrap-character,text-transform,line-spacing," << "label-position-tolerance,character-spacing," << "spacing,minimum-distance,minimum-padding," << "avoid-edges,allow-overlap,opacity,max-char-angle-delta," << "horizontal-alignment,justify-alignment," // additional for shield /* transform instead of orientation */ << "file,transform,shield-dx,shield-dy," << "text-opacity,unlock-image,no-text," << "meta-writer,meta-output"; ensure_attrs(sym, "ShieldSymbolizer", s.str()); try { std::string name = get_attr(sym, "name"); optional face_name = get_opt_attr(sym, "face-name"); optional fontset_name = get_opt_attr(sym, "fontset-name"); unsigned size = get_attr(sym, "size", 10U); color fill = get_attr(sym, "fill", color(0,0,0)); std::string image_file = get_attr(sym, "file"); optional base = get_opt_attr(sym, "base"); optional transform_wkt = get_opt_attr(sym, "transform"); try { if( base ) { std::map::const_iterator itr = file_sources_.find(*base); if (itr!=file_sources_.end()) { image_file = itr->second + "/" + image_file; } } image_file = ensure_relative_to_xml(image_file); shield_symbolizer shield_symbol(parse_expression(name, "utf8"),size,fill,parse_path(image_file)); if (fontset_name && face_name) { throw config_error(std::string("Can't have both face-name and fontset-name")); } else if (fontset_name) { std::map::const_iterator itr = fontsets_.find(*fontset_name); if (itr != fontsets_.end()) { shield_symbol.set_fontset(itr->second); } else { throw config_error("Unable to find any fontset named '" + *fontset_name + "'"); } } else if (face_name) { if ( strict_ ) { ensure_font_face(*face_name); } shield_symbol.set_face_name(*face_name); } else { throw config_error(std::string("Must have face-name or fontset-name")); } // text displacement (relative to shield_displacement) double dx = get_attr(sym, "dx", 0.0); double dy = get_attr(sym, "dy", 0.0); shield_symbol.set_displacement(dx,dy); // shield displacement double shield_dx = get_attr(sym, "shield-dx", 0.0); double shield_dy = get_attr(sym, "shield-dy", 0.0); shield_symbol.set_shield_displacement(shield_dx,shield_dy); label_placement_e placement = get_attr(sym, "placement", POINT_PLACEMENT); shield_symbol.set_label_placement( placement ); // don't render shields around edges optional avoid_edges = get_opt_attr(sym, "avoid-edges"); if (avoid_edges) { shield_symbol.set_avoid_edges( *avoid_edges); } // halo fill and radius optional halo_fill = get_opt_attr(sym, "halo-fill"); if (halo_fill) { shield_symbol.set_halo_fill( * halo_fill ); } optional halo_radius = get_opt_attr(sym, "halo-radius"); if (halo_radius) { shield_symbol.set_halo_radius(*halo_radius); } // minimum distance between labels optional min_distance = get_opt_attr(sym, "minimum-distance"); if (min_distance) { shield_symbol.set_minimum_distance(*min_distance); } // minimum distance from edge of the map optional min_padding = get_opt_attr(sym, "minimum-padding"); if (min_padding) { shield_symbol.set_minimum_padding(*min_padding); } // spacing between repeated labels on lines optional spacing = get_opt_attr(sym, "spacing"); if (spacing) { shield_symbol.set_label_spacing(*spacing); } // allow_overlap optional allow_overlap = get_opt_attr(sym, "allow-overlap"); if (allow_overlap) { shield_symbol.set_allow_overlap( * allow_overlap ); } // vertical alignment vertical_alignment_e valign = get_attr(sym, "vertical-alignment", V_MIDDLE); shield_symbol.set_vertical_alignment(valign); // horizontal alignment horizontal_alignment_e halign = get_attr(sym, "horizontal-alignment", H_MIDDLE); shield_symbol.set_horizontal_alignment(halign); // justify alignment justify_alignment_e jalign = get_attr(sym, "justify-alignment", J_MIDDLE); shield_symbol.set_justify_alignment(jalign); optional wrap_width = get_opt_attr(sym, "wrap-width"); if (wrap_width) { shield_symbol.set_wrap_width(*wrap_width); } optional wrap_before = get_opt_attr(sym, "wrap-before"); if (wrap_before) { shield_symbol.set_wrap_before(*wrap_before); } // character used to break long strings optional wrap_char = get_opt_attr(sym, "wrap-character"); if (wrap_char && (*wrap_char).size() > 0) { shield_symbol.set_wrap_char((*wrap_char)[0]); } // text conversion before rendering text_transform_e tconvert = get_attr(sym, "text-transform", NONE); shield_symbol.set_text_transform(tconvert); // spacing between text lines optional line_spacing = get_opt_attr(sym, "line-spacing"); if (line_spacing) { shield_symbol.set_line_spacing(*line_spacing); } // spacing between characters in text optional character_spacing = get_opt_attr(sym, "character-spacing"); if (character_spacing) { shield_symbol.set_character_spacing(*character_spacing); } // opacity optional opacity = get_opt_attr(sym, "opacity"); if (opacity) { shield_symbol.set_opacity( * opacity ); } // text-opacity optional text_opacity = get_opt_attr(sym, "text-opacity"); if (text_opacity) { shield_symbol.set_text_opacity( * text_opacity ); } if (transform_wkt) { agg::trans_affine tr; if (!mapnik::svg::parse_transform(*transform_wkt,tr)) { std::stringstream ss; ss << "Could not parse transform from '" << transform_wkt << "', expected string like: 'matrix(1, 0, 0, 1, 0, 0)'"; if (strict_) throw config_error(ss.str()); // value_error here? else clog << "### WARNING: " << ss << endl; } boost::array matrix; tr.store_to(&matrix[0]); shield_symbol.set_transform(matrix); } // unlock_image optional unlock_image = get_opt_attr(sym, "unlock-image"); if (unlock_image) { shield_symbol.set_unlock_image( * unlock_image ); } // no text optional no_text = get_opt_attr(sym, "no-text"); if (no_text) { shield_symbol.set_no_text( * no_text ); } parse_metawriter_in_symbolizer(shield_symbol, sym); rule.append(shield_symbol); } catch (image_reader_exception const & ex ) { string msg("Failed to load image file '" + image_file + "': " + ex.what()); if (strict_) { throw config_error(msg); } else { clog << "### WARNING: " << msg << endl; } } } catch (const config_error & ex) { ex.append_context("in ShieldSymbolizer"); throw; } } void map_parser::parse_stroke(stroke & strk, ptree const & sym) { // stroke color optional c = get_opt_attr(sym, "stroke"); if (c) strk.set_color(*c); // stroke-width optional width = get_opt_attr(sym, "stroke-width"); if (width) strk.set_width(*width); // stroke-opacity optional opacity = get_opt_attr(sym, "stroke-opacity"); if (opacity) strk.set_opacity(*opacity); // stroke-linejoin optional line_join = get_opt_attr(sym, "stroke-linejoin"); if (line_join) strk.set_line_join(*line_join); // stroke-linecap optional line_cap = get_opt_attr(sym, "stroke-linecap"); if (line_cap) strk.set_line_cap(*line_cap); // stroke-gamma optional gamma = get_opt_attr(sym, "stroke-gamma"); if (gamma) strk.set_gamma(*gamma); // stroke-dashaffset optional offset = get_opt_attr(sym, "stroke-dashoffet"); if (offset) strk.set_dash_offset(*offset); // stroke-dasharray optional str = get_opt_attr(sym,"stroke-dasharray"); if (str) { tokenizer<> tok (*str); std::vector dash_array; tokenizer<>::iterator itr = tok.begin(); for (; itr != tok.end(); ++itr) { try { double f = boost::lexical_cast(*itr); dash_array.push_back(f); } catch ( boost::bad_lexical_cast &) { throw config_error(std::string("Failed to parse dasharray ") + "'. Expected a " + "list of floats but got '" + (*str) + "'"); } } if (dash_array.size()) { size_t size = dash_array.size(); if ( size % 2) { for (size_t i=0; i < size ;++i) { dash_array.push_back(dash_array[i]); } } std::vector::const_iterator pos = dash_array.begin(); while (pos != dash_array.end()) { strk.add_dash(*pos,*(pos + 1)); pos +=2; } } } } void map_parser::parse_line_symbolizer( rule & rule, ptree const & sym ) { std::stringstream s; s << "meta-writer,meta-output," << "stroke,stroke-width,stroke-opacity,stroke-linejoin," << "stroke-linecap,stroke-gamma,stroke-dashoffet,stroke-dasharray"; ensure_attrs(sym, "LineSymbolizer", s.str()); try { stroke strk; parse_stroke(strk,sym); line_symbolizer symbol = line_symbolizer(strk); parse_metawriter_in_symbolizer(symbol, sym); rule.append(symbol); } catch (const config_error & ex) { ex.append_context("in LineSymbolizer"); throw; } } void map_parser::parse_polygon_symbolizer( rule & rule, ptree const & sym ) { ensure_attrs(sym, "PolygonSymbolizer", "fill,fill-opacity,gamma,meta-writer,meta-output"); try { polygon_symbolizer poly_sym; // fill optional fill = get_opt_attr(sym, "fill"); if (fill) poly_sym.set_fill(*fill); // fill-opacity optional opacity = get_opt_attr(sym, "fill-opacity"); if (opacity) poly_sym.set_opacity(*opacity); // gamma optional gamma = get_opt_attr(sym, "gamma"); if (gamma) poly_sym.set_gamma(*gamma); parse_metawriter_in_symbolizer(poly_sym, sym); rule.append(poly_sym); } catch (const config_error & ex) { ex.append_context("in PolygonSymbolizer"); throw; } } void map_parser::parse_building_symbolizer( rule & rule, ptree const & sym ) { ensure_attrs(sym, "PolygonSymbolizer", "fill,fill-opacity,height,meta-writer,meta-output"); try { building_symbolizer building_sym; // fill optional fill = get_opt_attr(sym, "fill"); if (fill) building_sym.set_fill(*fill); // fill-opacity optional opacity = get_opt_attr(sym, "fill-opacity"); if (opacity) building_sym.set_opacity(*opacity); // height // TODO - expression optional height = get_opt_attr(sym, "height"); if (height) building_sym.set_height(*height); parse_metawriter_in_symbolizer(building_sym, sym); rule.append(building_sym); } catch (const config_error & ex) { ex.append_context("in BuildingSymbolizer"); throw; } } void map_parser::parse_raster_symbolizer( rule & rule, ptree const & sym ) { // no support for meta-writer,meta-output ensure_attrs(sym, "PolygonSymbolizer", "mode,scaling,opacity,filter-factory"); try { raster_symbolizer raster_sym; // mode optional mode = get_opt_attr(sym, "mode"); if (mode) raster_sym.set_mode(*mode); // scaling optional scaling = get_opt_attr(sym, "scaling"); if (scaling) raster_sym.set_scaling(*scaling); // opacity optional opacity = get_opt_attr(sym, "opacity"); if (opacity) raster_sym.set_opacity(*opacity); // filter factor optional filter_factor = get_opt_attr(sym, "filter-factor"); if (filter_factor) raster_sym.set_filter_factor(*filter_factor); ptree::const_iterator cssIter = sym.begin(); ptree::const_iterator endCss = sym.end(); for(; cssIter != endCss; ++cssIter) { ptree::value_type const& css_tag = *cssIter; if (css_tag.first == "RasterColorizer") { raster_colorizer_ptr colorizer(new raster_colorizer()); raster_sym.set_colorizer(colorizer); parse_raster_colorizer(colorizer, css_tag.second); } else if (css_tag.first != "" && css_tag.first != "" ) { throw config_error(std::string("Unknown child node. ") + "Expected 'RasterColorizer' but got '" + css_tag.first + "'"); } } //Note: raster_symbolizer doesn't support metawriters rule.append(raster_sym); } catch (const config_error & ex) { ex.append_context("in RasterSymbolizer"); throw; } } void map_parser::parse_glyph_symbolizer(rule & rule, ptree const & sym) { ensure_attrs(sym, "GlyphSymbolizer", "face-name,char,angle,angle-mode,value,size,color,halo-fill,halo-radius,allow-overlap,avoid-edges,dx,dy,meta-writer,meta-output"); try { // Parse required constructor args std::string face_name = get_attr(sym, "face-name"); std::string _char = get_attr(sym, "char"); glyph_symbolizer glyph_sym = glyph_symbolizer( face_name, parse_expression(_char, "utf8") ); // // parse and set optional attrs. // // angle optional angle = get_opt_attr(sym, "angle"); if (angle) glyph_sym.set_angle(parse_expression(*angle, "utf8")); angle_mode_e angle_mode = get_attr(sym, "angle-mode", TRIGONOMETRIC); glyph_sym.set_angle_mode(angle_mode); // value optional value = get_opt_attr(sym, "value"); if (value) glyph_sym.set_value(parse_expression(*value, "utf8")); // size std::string size = get_attr(sym, "size"); glyph_sym.set_size(parse_expression(size, "utf8")); // color optional _color = get_opt_attr(sym, "color"); if (_color) glyph_sym.set_color(parse_expression(*_color, "utf8")); // halo_fill optional halo_fill = get_opt_attr(sym, "halo-fill"); if (halo_fill) glyph_sym.set_halo_fill(*halo_fill); // halo_radius optional halo_radius = get_opt_attr( sym, "halo-radius"); if (halo_radius) glyph_sym.set_halo_radius(*halo_radius); // allow_overlap optional allow_overlap = get_opt_attr( sym, "allow-overlap" ); if (allow_overlap) glyph_sym.set_allow_overlap(*allow_overlap); // avoid_edges optional avoid_edges = get_opt_attr( sym, "avoid-edges" ); if (avoid_edges) glyph_sym.set_avoid_edges(*avoid_edges); // displacement optional dx = get_opt_attr(sym, "dx"); optional dy = get_opt_attr(sym, "dy"); if (dx && dy) glyph_sym.set_displacement(*dx, *dy); // colorizer ptree::const_iterator childIter = sym.begin(); ptree::const_iterator endChild = sym.end(); for (; childIter != endChild; ++childIter) { ptree::value_type const& tag = *childIter; if (tag.first == "RasterColorizer") { raster_colorizer_ptr colorizer(new raster_colorizer()); glyph_sym.set_colorizer(colorizer); parse_raster_colorizer(colorizer, tag.second); } else if (tag.first!="" && tag.first!="" ) { throw config_error(std::string("Unknown child node. ") + "Expected 'RasterColorizer' but got '" + tag.first + "'"); } } parse_metawriter_in_symbolizer(glyph_sym, sym); rule.append(glyph_sym); } catch (const config_error & ex) { ex.append_context("in GlyphSymbolizer"); throw; } } void map_parser::parse_raster_colorizer(raster_colorizer_ptr const& rc, ptree const& node ) { try { ptree::const_iterator cbIter = node.begin(); ptree::const_iterator endCb = node.end(); for(; cbIter != endCb; ++cbIter) { ptree::value_type const& cb_tag = *cbIter; ptree const & cb = cbIter->second; if (cb_tag.first == "ColorBand") { ensure_attrs(cb, "ColorBand", "value,color,midpoints,max-value,label"); std::string value_s = get_attr(cb, "value"); float value; std::stringstream(value_s) >> value; optional c = get_opt_attr(cb, "color"); if (!c) { throw config_error("missing color"); } unsigned midpoints = get_attr(cb, "midpoints", 0); optional max_value = get_opt_attr(cb, "max-value"); if (max_value) { rc->append_band(value, *max_value, *c, midpoints); } else { rc->append_band(value, *c, midpoints); } } else if (cb_tag.first != "" && cb_tag.first != "" ) { throw config_error(std::string("Unknown child node. ") + "Expected 'ColorBand' but got '" + cb_tag.first + "'"); } } } catch (const config_error & ex) { ex.append_context("in RasterColorizer"); throw; } } void map_parser::ensure_font_face( const std::string & 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 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) boost::filesystem::path full = boost::filesystem::absolute(xml_path.branch_path()/rel_path).normalize(); #else // v2 boost::filesystem::path full = boost::filesystem::complete(xml_path.branch_path()/rel_path).normalize(); #endif #ifdef MAPNIK_DEBUG std::clog << "\nModifying relative paths to be relative to xml...\n"; std::clog << "original base path: " << *opt_path << "\n"; std::clog << "relative base path: " << full.string() << "\n"; #endif return full.string(); } } return *opt_path; } void map_parser::ensure_attrs(ptree const& sym, std::string name, std::string attrs) { typedef ptree::key_type::value_type Ch; //typedef boost::property_tree::xml_parser::xmlattr x_att; std::set attr_set; boost::split(attr_set, attrs, boost::is_any_of(",")); for (ptree::const_iterator itr = sym.begin(); itr != sym.end(); ++itr) { //ptree::value_type const& v = *itr; if (itr->first == boost::property_tree::xml_parser::xmlattr()) { optional attribs = sym.get_child_optional( boost::property_tree::xml_parser::xmlattr() ); if (attribs) { std::ostringstream s(""); s << "### " << name << " properties warning: "; int missing = 0; for (ptree::const_iterator it = attribs.get().begin(); it != attribs.get().end(); ++it) { std::string name = it->first; bool found = (attr_set.find(name) != attr_set.end()); if (!found) { if (missing) s << ","; s << "'" << name << "'"; ++missing; } } if (missing) { if (missing > 1) s << " are"; else s << " is"; s << " invalid, acceptable values are:\n'" << attrs << "'\n"; std::clog << s.str(); } } } } } } // end of namespace mapnik