// file plugin #include "python_datasource.hpp" #include "python_featureset.hpp" // stl #include #include // boost #include #include #include #include #include #include "python_utils.hpp" using mapnik::datasource; using mapnik::parameters; DATASOURCE_PLUGIN(python_datasource) python_datasource::python_datasource(parameters const& params, bool bind) : datasource(params), desc_(*params_.get("type"), *params_.get("encoding","utf-8")), factory_(*params_.get("factory", "")) { // extract any remaining parameters as keyword args for the factory BOOST_FOREACH(const mapnik::parameters::value_type& kv, params_) { if((kv.first != "type") && (kv.first != "factory")) { kwargs_.insert(std::make_pair(kv.first, *params_.get(kv.first))); } } if (bind) { this->bind(); } } python_datasource::~python_datasource() { } // This name must match the plugin filename, eg 'python.input' const char* python_datasource::name_="python"; const char* python_datasource::name() { return name_; } mapnik::layer_descriptor python_datasource::get_descriptor() const { if (!is_bound_) bind(); return desc_; } // The following methods call into the Python interpreter and hence require, unfortunately, that the GIL be held. void python_datasource::bind() const { using namespace boost; using namespace boost::python; if (is_bound_) return; // if no factory callable is defined, bind is a nop if (factory_.empty()) return; // split factory at ':' to parse out module and callable std::vector factory_split; split(factory_split, factory_, is_any_of(":")); if ((factory_split.size() < 1) || (factory_split.size() > 2)) { // FIMXE: is this appropriate error reporting? std::cerr << "python: factory string must be of the form '[module:]callable' when parsing \"" << factory_ << '"' << std::endl; return; } // extract the module and the callable str module_name("__main__"), callable_name; if (factory_split.size() == 1) { callable_name = str(factory_split[0]); } else { module_name = str(factory_split[0]); callable_name = str(factory_split[1]); } { ensure_gil lock; // import the main module from Python (in case we're embedding the // interpreter directly) and also import the callable. object main_module = import("__main__"); object callable_module = import(module_name); object callable = callable_module.attr(callable_name); // prepare the arguments dict kwargs; typedef std::map::value_type kv_type; BOOST_FOREACH(const kv_type& kv, kwargs_) { kwargs[str(kv.first)] = str(kv.second); } // get our wrapped data source datasource_ = callable(*boost::python::make_tuple(), **kwargs); } is_bound_ = true; } mapnik::datasource::datasource_t python_datasource::type() const { using namespace boost::python; typedef boost::optional return_type; if (!is_bound_) bind(); ensure_gil lock; object data_type = datasource_.attr("data_type"); long data_type_integer = extract(data_type); return mapnik::datasource::datasource_t(data_type_integer); } mapnik::box2d python_datasource::envelope() const { using namespace boost::python; if (!is_bound_) bind(); ensure_gil lock; return extract >(datasource_.attr("envelope")); } boost::optional python_datasource::get_geometry_type() const { using namespace boost::python; typedef boost::optional return_type; if (!is_bound_) bind(); ensure_gil lock; // if the datasource object has no geometry_type attribute, return a 'none' value if (!PyObject_HasAttrString(datasource_.ptr(), "geometry_type")) return return_type(); object py_geometry_type = datasource_.attr("geometry_type"); // if the attribute value is 'None', return a 'none' value if (py_geometry_type.ptr() == object().ptr()) return return_type(); long geom_type_integer = extract(py_geometry_type); return mapnik::datasource::geometry_t(geom_type_integer); } mapnik::featureset_ptr python_datasource::features(mapnik::query const& q) const { using namespace boost::python; if (!is_bound_) bind(); // if the query box intersects our world extent then query for features if (envelope().intersects(q.get_bbox())) { ensure_gil lock; object features(datasource_.attr("features")(q)); // if 'None' was returned, return an empty feature set if(features.ptr() == object().ptr()) return mapnik::featureset_ptr(); return boost::make_shared(features); } // otherwise return an empty featureset pointer return mapnik::featureset_ptr(); } mapnik::featureset_ptr python_datasource::features_at_point(mapnik::coord2d const& pt) const { using namespace boost::python; if (!is_bound_) bind(); ensure_gil lock; object features(datasource_.attr("features_at_point")(pt)); // if we returned none, return an empty set if(features.ptr() == object().ptr()) return mapnik::featureset_ptr(); // otherwise, return a feature set which can iterate over the iterator return boost::make_shared(features); }