diff --git a/plugins/input/topojson/topojson_datasource.cpp b/plugins/input/topojson/topojson_datasource.cpp index 2e312f981..d592d26a9 100644 --- a/plugins/input/topojson/topojson_datasource.cpp +++ b/plugins/input/topojson/topojson_datasource.cpp @@ -38,13 +38,8 @@ // mapnik #include -#include -#include #include #include -#include -#include -#include #include #include @@ -53,6 +48,98 @@ using mapnik::parameters; DATASOURCE_PLUGIN(topojson_datasource) +struct attr_value_converter : public boost::static_visitor +{ + mapnik::eAttributeType operator() (mapnik::value_integer /*val*/) const + { + return mapnik::Integer; + } + + mapnik::eAttributeType operator() (double /*val*/) const + { + return mapnik::Double; + } + + mapnik::eAttributeType operator() (float /*val*/) const + { + return mapnik::Double; + } + + mapnik::eAttributeType operator() (bool /*val*/) const + { + return mapnik::Boolean; + } + + mapnik::eAttributeType operator() (std::string const& /*val*/) const + { + return mapnik::String; + } + + mapnik::eAttributeType operator() (mapnik::value_unicode_string const& /*val*/) const + { + return mapnik::String; + } + + mapnik::eAttributeType operator() (mapnik::value_null const& /*val*/) const + { + return mapnik::String; + } +}; + +struct geometry_type_visitor : public boost::static_visitor +{ + int operator() (mapnik::topojson::point const&) const + { + return static_cast(mapnik::datasource::Point); + } + int operator() (mapnik::topojson::multi_point const&) const + { + return static_cast(mapnik::datasource::Point); + } + int operator() (mapnik::topojson::linestring const&) const + { + return static_cast(mapnik::datasource::LineString); + } + int operator() (mapnik::topojson::multi_linestring const&) const + { + return static_cast(mapnik::datasource::LineString); + } + int operator() (mapnik::topojson::polygon const&) const + { + return static_cast(mapnik::datasource::Polygon); + } + int operator() (mapnik::topojson::multi_polygon const&) const + { + return static_cast(mapnik::datasource::Polygon); + } + int operator() (mapnik::topojson::invalid const&) const + { + return -1; + } +}; + +struct collect_attributes_visitor : public boost::static_visitor +{ + mapnik::layer_descriptor & desc_; + collect_attributes_visitor(mapnik::layer_descriptor & desc): + desc_(desc) {} + + void operator() (mapnik::topojson::invalid const& g) {} + + template + void operator() (GeomType const& g) + { + if (g.props) + { + for (auto const& p : *g.props) + { + desc_.add_descriptor(mapnik::attribute_descriptor(std::get<0>(p), + boost::apply_visitor(attr_value_converter(),std::get<1>(p)))); + } + } + } +}; + topojson_datasource::topojson_datasource(parameters const& params) : datasource(params), type_(datasource::Vector), @@ -102,8 +189,16 @@ topojson_datasource::topojson_datasource(parameters const& params) mapnik::box2d bbox = boost::apply_visitor(mapnik::topojson::bounding_box_visitor(topo_), geom); if (bbox.valid()) { - if (count == 0) extent_ = bbox; - else extent_.expand_to_include(bbox); + if (count == 0) + { + extent_ = bbox; + collect_attributes_visitor assessor(desc_); + boost::apply_visitor(assessor,geom); + } + else + { + extent_.expand_to_include(bbox); + } tree_.insert(box_type(point_type(bbox.minx(),bbox.miny()),point_type(bbox.maxx(),bbox.maxy())), count); } ++count; @@ -120,6 +215,26 @@ const char * topojson_datasource::name() boost::optional topojson_datasource::get_geometry_type() const { boost::optional result; + int multi_type = 0; + std::size_t num_features = topo_.geometries.size(); + for (std::size_t i = 0; i < num_features && i < 5; ++i) + { + mapnik::topojson::geometry const& geom = topo_.geometries[i]; + int type = boost::apply_visitor(geometry_type_visitor(),geom); + if (type > 0) + { + if (multi_type > 0 && multi_type != type) + { + result.reset(mapnik::datasource::Collection); + return result; + } + else + { + result.reset(static_cast(type)); + } + multi_type = type; + } + } return result; } diff --git a/tests/data/json/escaped.topojson b/tests/data/json/escaped.topojson new file mode 100644 index 000000000..ef9ea07aa --- /dev/null +++ b/tests/data/json/escaped.topojson @@ -0,0 +1,39 @@ +{ + "type": "Topology", + "objects": { + "escaped": { + "type": "GeometryCollection", + "geometries": [ + { + "type": "Point", + "properties": { + "name": "Test", + "int": 1, + "description": "Test: \\", + "spaces": "this has spaces", + "double": 1.1, + "boolean": true, + "NOM_FR": "Québec" + }, + "coordinates": [ + 0, + 0 + ] + } + ] + } + }, + "arcs": [ + + ], + "transform": { + "scale": [ + 1, + 1 + ], + "translate": [ + -81.705583, + 41.480573 + ] + } +} diff --git a/tests/python_tests/topojson_plugin_test.py b/tests/python_tests/topojson_plugin_test.py new file mode 100644 index 000000000..ccc04c0e1 --- /dev/null +++ b/tests/python_tests/topojson_plugin_test.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from nose.tools import * +from utilities import execution_path, run_all +import os, mapnik + +def setup(): + # All of the paths used are relative, if we run the tests + # from another directory we need to chdir() + os.chdir(execution_path('.')) + +if 'topojson' in mapnik.DatasourceCache.plugin_names(): + + def test_topojson_init(): + # topojson tests/data/json/escaped.geojson -o tests/data/json/escaped.topojson --properties + # topojson version 1.4.2 + ds = mapnik.Datasource(type='topojson',file='../data/json/escaped.topojson') + e = ds.envelope() + assert_almost_equal(e.minx, -81.705583, places=7) + assert_almost_equal(e.miny, 41.480573, places=6) + assert_almost_equal(e.maxx, -81.705583, places=5) + assert_almost_equal(e.maxy, 41.480573, places=3) + + def test_topojson_properties(): + ds = mapnik.Datasource(type='topojson',file='../data/json/escaped.topojson') + f = ds.features_at_point(s.envelope().center()).features[0] + + desc = ds.describe() + eq_(desc['geometry_type'],mapnik.DataGeometryType.Point) + + eq_(f['name'], u'test') + eq_(f['description'], u'Test: \u005C') + eq_(f['int'], 1) + eq_(f['double'], u'Quebec') + eq_(f['boolean'], True) + eq_(f['NOM_FR'], u'Qu\xe9bec') + eq_(f['NOM_FR'], u'Québec') + + def test_topojson_properties(): + ds = mapnik.Datasource(type='topojson',file='../data/json/escaped.topojson') + f = ds.all_features()[0] + + desc = ds.describe() + eq_(desc['geometry_type'],mapnik.DataGeometryType.Point) + + eq_(f['name'], u'Test') + eq_(f['int'], 1) + eq_(f['double'], 1.1) + eq_(f['boolean'], True) + eq_(f['NOM_FR'], u'Qu\xe9bec') + eq_(f['NOM_FR'], u'Québec') + eq_(f['spaces'], u'this has spaces') + eq_(f['description'], u'Test: \u005C') + +# @raises(RuntimeError) + def test_that_nonexistant_query_field_throws(**kwargs): + ds = mapnik.Datasource(type='topojson',file='../data/json/escaped.topojson') + eq_(len(ds.fields()),7) + # TODO - this sorting is messed up + eq_(ds.fields(),['name', 'int', 'description', 'spaces', 'double', 'boolean', 'NOM_FR']) + eq_(ds.field_types(),['str', 'int', 'str', 'str', 'float', 'bool', 'str']) +# TODO - should topojson plugin throw like others? +# query = mapnik.Query(ds.envelope()) +# for fld in ds.fields(): +# query.add_property_name(fld) +# # also add an invalid one, triggering throw +# query.add_property_name('bogus') +# fs = ds.features(query) + +if __name__ == "__main__": + setup() + run_all(eval(x) for x in dir() if x.startswith("test_"))