From 9548492036cad2ec0544472bc477e2941e032cc2 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Tue, 26 Apr 2011 23:51:29 +0000 Subject: [PATCH] add python binding to grid_renderer allowing conversion of grid buffer into json --- CHANGELOG | 3 + bindings/python/mapnik/__init__.py | 4 + bindings/python/mapnik_python.cpp | 6 +- bindings/python/python_grid_utils.hpp | 239 ++++++++++++++++++++++++++ 4 files changed, 251 insertions(+), 1 deletion(-) create mode 100644 bindings/python/python_grid_utils.hpp diff --git a/CHANGELOG b/CHANGELOG index 008cb6d5e..3014800cb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,9 @@ For a complete change history, see the SVN log. Mapnik Trunk ------------ +- Added python function 'render_grid' to allow conversion of grid buffer to python object containing list of grid + pixels, list of keys, and a and dictionary of feature attributes. + - Added new rendering backend, grid_renderer, that collects the attributes of rendered features and burns their ids into a grid buffer. diff --git a/bindings/python/mapnik/__init__.py b/bindings/python/mapnik/__init__.py index 6e309d0ae..c69977a3d 100644 --- a/bindings/python/mapnik/__init__.py +++ b/bindings/python/mapnik/__init__.py @@ -76,6 +76,9 @@ class _MapnikMetaclass(BoostPythonMetaclass): _injector = _MapnikMetaclass('_injector', (object, ), {}) +def render_grid(m,layer_idx,join_field,step=1,fields=[]): + return render_grid_(m,layer_idx,join_field,step,fields) + def Filter(*args,**kwargs): warnings.warn("'Filter' is deprecated and will be removed in Mapnik 2.0.1, use 'Expression' instead", DeprecationWarning, 2) @@ -739,6 +742,7 @@ __all__ = [ 'save_map', 'save_map_to_string', 'render', + 'render_grid', 'render_tile_to_file', 'render_to_file', # other diff --git a/bindings/python/mapnik_python.cpp b/bindings/python/mapnik_python.cpp index b1d8c7b31..bd7e314ca 100644 --- a/bindings/python/mapnik_python.cpp +++ b/bindings/python/mapnik_python.cpp @@ -65,11 +65,13 @@ void export_glyph_symbolizer(); void export_inmem_metawriter(); #include +#include #include #include #ifdef HAVE_CAIRO #include #endif +#include "python_grid_utils.hpp" #include #include #include @@ -242,7 +244,6 @@ void render_to_file3(const mapnik::Map& map, } } - double scale_denominator(mapnik::Map const &map, bool geographic) { return mapnik::scale_denominator(map, geographic); @@ -327,6 +328,7 @@ BOOST_PYTHON_MODULE(_mapnik2) using mapnik::load_map_string; using mapnik::save_map; using mapnik::save_map_to_string; + using mapnik::render_grid; register_exception_translator(&config_error_translator); register_exception_translator(&value_error_translator); @@ -367,6 +369,8 @@ BOOST_PYTHON_MODULE(_mapnik2) export_glyph_symbolizer(); export_inmem_metawriter(); + def("render_grid_",&render_grid); + def("render_to_file",&render_to_file1, "\n" "Render Map to file using explicit image type.\n" diff --git a/bindings/python/python_grid_utils.hpp b/bindings/python/python_grid_utils.hpp new file mode 100644 index 000000000..28c97a8e2 --- /dev/null +++ b/bindings/python/python_grid_utils.hpp @@ -0,0 +1,239 @@ +/***************************************************************************** + * + * 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 + * + *****************************************************************************/ +#ifndef MAPNIK_PYTHON_BINDING_GRID_UTILS_INCLUDED +#define MAPNIK_PYTHON_BINDING_GRID_UTILS_INCLUDED + +// boost +#include +#include +#include + +// mapnik +#include +#include +#include +#include "mapnik_value_converter.hpp" + +namespace mapnik { + + +void grid2utf(mapnik::grid const& grid, + boost::python::list& l, + std::vector& key_order) +{ + mapnik::grid::data_type const& data = grid.data(); + mapnik::grid::feature_key_type const& feature_keys = grid.get_feature_keys(); + mapnik::grid::key_type keys; + mapnik::grid::key_type::const_iterator key_pos; + mapnik::grid::feature_key_type::const_iterator feature_pos; + uint16_t codepoint = 31; + + for (unsigned y = 0; y < data.height(); ++y) + { + uint16_t idx = 0; + boost::scoped_array line(new Py_UNICODE[data.width()]); + mapnik::grid::value_type const* row = data.getRow(y); + for (unsigned x = 0; x < data.width(); ++x) + { + feature_pos = feature_keys.find(row[x]); + if (feature_pos != feature_keys.end()) + { + mapnik::grid::lookup_type val = feature_pos->second; + key_pos = keys.find(val); + if (key_pos == keys.end()) + { + // Create a new entry for this key. Skip the codepoints that + // can't be encoded directly in JSON. + ++codepoint; + if (codepoint == 34) ++codepoint; // Skip " + else if (codepoint == 92) ++codepoint; // Skip backslash + + keys[val] = codepoint; + key_order.push_back(val); + line[idx++] = static_cast(codepoint); + } + else + { + line[idx++] = static_cast(key_pos->second); + } + } + // else, shouldn't get here... + } + l.append(boost::python::object( + boost::python::handle<>( + PyUnicode_FromUnicode(line.get(), data.width())))); + } +} + + +void write_features(mapnik::grid::feature_type const& g_features, + boost::python::dict& feature_data, + std::vector const& key_order, + std::string const& join_field, + std::set const& attributes) +{ + mapnik::grid::feature_type::const_iterator feat_itr = g_features.begin(); + mapnik::grid::feature_type::const_iterator feat_end = g_features.end(); + bool include_join_field = (attributes.find(join_field) != attributes.end()); + for (; feat_itr != feat_end; ++feat_itr) + { + std::map const& props = feat_itr->second; + std::map::const_iterator const& itr = props.find(join_field); + if (itr != props.end()) + { + mapnik::grid::lookup_type const& join_value = itr->second.to_string(); + + // only serialize features visible in the grid + if(std::find(key_order.begin(), key_order.end(), join_value) != key_order.end()) { + boost::python::dict feat; + std::map::const_iterator it = props.begin(); + std::map::const_iterator end = props.end(); + bool found = false; + for (; it != end; ++it) + { + std::string const& key = it->first; + if (key == join_field) { + // drop join_field unless requested + if (include_join_field) { + found = true; + feat[it->first] = boost::python::object( + boost::python::handle<>( + boost::apply_visitor( + boost::python::value_converter(), + it->second.base()))); + } + } + else + { + found = true; + feat[it->first] = boost::python::object( + boost::python::handle<>( + boost::apply_visitor( + boost::python::value_converter(), + it->second.base()))); + } + } + if (found) + { + feature_data[feat_itr->first] = feat; + } + } + } + else + { + std::clog << "should not get here: join_field '" << join_field << "' not found in grid feature properties\n"; + } + } +} + +boost::python::dict render_grid(const mapnik::Map& map, + unsigned layer_idx, // layer + std::string const& join_field, // key_name + unsigned int step, // resolution + boost::python::list fields) +{ + std::vector const& layers = map.layers(); + std::size_t layer_num = layers.size(); + if (layer_idx >= layer_num) { + std::ostringstream s; + s << "Zero-based layer index '" << layer_idx << "' not valid, only '" + << layer_num << "' layers are in map\n"; + throw std::runtime_error(s.str()); + } + + unsigned int grid_width = map.width()/step; + unsigned int grid_height = map.height()/step; + + mapnik::grid grid(grid_width,grid_height,join_field,step); + + // convert python list to std::vector + boost::python::ssize_t num_fields = boost::python::len(fields); + for(boost::python::ssize_t i=0; i name(fields[i]); + if (name.check()) { + grid.add_property_name(name()); + } + else + { + std::stringstream s; + s << "list of field names must be strings"; + throw mapnik::value_error(s.str()); + } + } + + // copy property names + std::set attributes = grid.property_names(); + + if (join_field == grid.id_name_) + { + // TODO - should feature.id() be a first class attribute? + if (attributes.find(join_field) != attributes.end()) + { + attributes.erase(join_field); + } + } + else if (attributes.find(join_field) == attributes.end()) + { + attributes.insert(join_field); + } + + try + { + mapnik::grid_renderer ren(map,grid,1.0,0,0); + mapnik::layer const& layer = layers[layer_idx]; + ren.apply(layer,attributes); + } + catch (...) + { + throw; + } + + // convert buffer to utf and gather key order + boost::python::list l; + std::vector key_order; + mapnik::grid2utf(grid,l,key_order); + + // convert key order to proper python list + boost::python::list keys_a; + BOOST_FOREACH ( mapnik::grid::lookup_type const& key_id, key_order ) + { + keys_a.append(key_id); + } + + // gather feature data + boost::python::dict feature_data; + if (num_fields > 0) { + mapnik::grid::feature_type const& g_features = grid.get_grid_features(); + mapnik::write_features(g_features,feature_data,key_order,join_field,grid.property_names()); + } + + // build dictionary and return to python + boost::python::dict json; + json["grid"] = l; + json["keys"] = keys_a; + json["data"] = feature_data; + return json; +} + +} + +#endif // MAPNIK_PYTHON_BINDING_GRID_UTILS_INCLUDED