diff --git a/tests/cxx/agg_blend_src_over_test.cpp b/tests/cxx/agg_blend_src_over_test.cpp new file mode 100644 index 000000000..235a23047 --- /dev/null +++ b/tests/cxx/agg_blend_src_over_test.cpp @@ -0,0 +1,209 @@ +#include "catch.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include "agg_color_rgba.h" +#include "agg_pixfmt_rgba.h" +#include "agg_rendering_buffer.h" +#include "agg_renderer_base.h" + +using color = agg::rgba8; +using order = agg::order_rgba; + +std::string to_string(color const& c) +{ + std::ostringstream s; + s << "rgba(" << (unsigned)c.r << "," << (unsigned)c.g << "," << (unsigned)c.b << "," << (unsigned)c.a << ")"; + return s.str(); +} + +template +color blend(color const& source, color const& dest, unsigned cover=255) +{ + unsigned stride = 4; + unsigned size = 1; + + color source_pre = source; + source_pre.premultiply(); + color dest_pre = dest; + dest_pre.premultiply(); + + unsigned char* buffer = new unsigned char[size*size*stride]; + std::memset(buffer, 0, size*size*stride); + buffer[0] = dest_pre.r; + buffer[1] = dest_pre.g; + buffer[2] = dest_pre.b; + buffer[3] = dest_pre.a; + // http://www.antigrain.com/doc/basic_renderers/basic_renderers.agdoc.html + agg::rendering_buffer rbuf(buffer, + size, + size, + size * stride); + color::value_type* psource = (color::value_type*)rbuf.row_ptr(0,0,1); + blender::blend_pix(psource,source_pre.r,source_pre.g,source_pre.b,source_pre.a,cover); + color color_result(psource[0],psource[1],psource[2],psource[3]); + color_result.demultiply(); + delete [] buffer; + return color_result; +} + +// agg::pixfmt_alpha_blend_rgba +color normal_blend(color const& source, color const& dest, unsigned cover=255) +{ + using renderer_type = agg::renderer_base; + unsigned stride = 4; + unsigned size = 1; + color source_pre = source; + source_pre.premultiply(); + color dest_pre = dest; + dest_pre.premultiply(); + // source buffer + unsigned char* source_buffer = new unsigned char[size*size*stride]; + std::memset(source_buffer, 0, size*size*stride); + source_buffer[0] = source_pre.r; + source_buffer[1] = source_pre.g; + source_buffer[2] = source_pre.b; + source_buffer[3] = source_pre.a; + agg::rendering_buffer source_rbuffer(source_buffer,size,size,size * 4); + agg::pixfmt_rgba32_pre pixf_source(source_rbuffer); + + // destination buffer + unsigned char* dest_buffer = new unsigned char[size*size*stride]; + std::memset(dest_buffer, 0, size*size*stride); + dest_buffer[0] = dest_pre.r; + dest_buffer[1] = dest_pre.g; + dest_buffer[2] = dest_pre.b; + dest_buffer[3] = dest_pre.a; + agg::rendering_buffer dest_rbuffer(dest_buffer,size,size,size * 4); + agg::pixfmt_rgba32_pre pixf_dest(dest_rbuffer); + + // renderer: blends source into destination + renderer_type ren(pixf_dest); + ren.blend_from(pixf_source,0,0,0,cover); + color color_result(dest_buffer[0],dest_buffer[1],dest_buffer[2],dest_buffer[3]); + color_result.demultiply(); + delete [] source_buffer; + delete [] dest_buffer; + return color_result; +} + + + +namespace agg { + +// the original agg template code for src_over +// before we changed A as per https://github.com/mapnik/mapnik/issues/1452 +template struct comp_op_rgba_src_over2 +{ + using color_type = ColorT; + using order_type = Order; + using value_type = typename color_type::value_type; + using calc_type = typename color_type::calc_type; + enum base_scale_e + { + base_shift = color_type::base_shift, + base_mask = color_type::base_mask + }; + + // Dca' = Sca + Dca.(1 - Sa) + // Da' = Sa + Da - Sa.Da + static void blend_pix(value_type* p, + unsigned sr, unsigned sg, unsigned sb, + unsigned sa, unsigned cover) + { + if(cover < 255) + { + sr = (sr * cover + 255) >> 8; + sg = (sg * cover + 255) >> 8; + sb = (sb * cover + 255) >> 8; + sa = (sa * cover + 255) >> 8; + } + calc_type s1a = base_mask - sa; + p[Order::R] = (value_type)(sr + ((p[Order::R] * s1a + base_mask) >> base_shift)); + p[Order::G] = (value_type)(sg + ((p[Order::G] * s1a + base_mask) >> base_shift)); + p[Order::B] = (value_type)(sb + ((p[Order::B] * s1a + base_mask) >> base_shift)); + p[Order::A] = (value_type)(sa + p[Order::A] - ((sa * p[Order::A] + base_mask) >> base_shift)); + } +}; + +} + +TEST_CASE("blending") { + +SECTION("src over") { + + using source_over_old_agg = agg::comp_op_rgba_src_over2; + using source_over = agg::comp_op_rgba_src_over; + + try + { + color white(255,255,255,255); + color black(0,0,0,255); + + REQUIRE( to_string(blend(white,white)) == to_string(white) ); + REQUIRE( to_string(blend(white,black)) == to_string(white) ); + REQUIRE( to_string(blend(black,white)) == to_string(black) ); + + color near_white(254,254,254,254); // Source + color near_trans(1,1,1,1); // Dest + color expected_color(253,253,253,255); // expected result + REQUIRE( to_string(blend(near_white,near_trans)) == to_string(color(253,253,253,254)) ); + REQUIRE( to_string(blend(near_white,near_trans)) == to_string(expected_color) ); + REQUIRE( to_string(normal_blend(near_white,near_trans)) == to_string(expected_color) ); + + // using normal_blend as expected, compare a variety of other colors + + { + color source(128,128,128,255); + color dest(128,128,128,255); + unsigned cover = 128; + std::string expected_str = to_string(normal_blend(source,dest,cover)); + REQUIRE( to_string(blend(source,dest,cover)) == expected_str ); + REQUIRE( to_string(blend(source,dest,cover)) == expected_str ); + } + + { + color source(128,128,128,255); + color dest(128,128,128,255); + unsigned cover = 245; + std::string expected_str = to_string(normal_blend(source,dest,cover)); + REQUIRE( to_string(blend(source,dest,cover)) == expected_str ); + REQUIRE( to_string(blend(source,dest,cover)) == expected_str ); + } + + // commenting until I study these failures more (dane) + /* + { + // fails, why? + color source(127,127,127,127); + color dest(127,127,127,127); + unsigned cover = 255; + std::string expected_str = to_string(normal_blend(source,dest,cover)); + REQUIRE( to_string(blend(source,dest,cover)) == expected_str ); + REQUIRE( to_string(blend(source,dest,cover)) == expected_str ); + } + + { + // fails, why? + color source(128,128,128,128); + color dest(128,128,128,128); + unsigned cover = 128; + std::string expected_str = to_string(normal_blend(source,dest,cover)); + REQUIRE( to_string(blend(source,dest,cover)) == expected_str ); + REQUIRE( to_string(blend(source,dest,cover)) == expected_str ); + } + */ + } + catch (std::exception const & ex) + { + std::clog << ex.what() << "\n"; + REQUIRE(false); + } +} + +} diff --git a/tests/cxx/clipping_test.cpp b/tests/cxx/clipping_test.cpp new file mode 100644 index 000000000..11b469594 --- /dev/null +++ b/tests/cxx/clipping_test.cpp @@ -0,0 +1,120 @@ +#include "catch.hpp" + +// mapnik +#include +#include +#include +// boost +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wunused-local-typedef" +#include +#pragma GCC diagnostic pop + +// stl +#include +#include +#include +#include +#include +#include + +// agg +#include "agg_conv_clip_polygon.h" +#include "agg_conv_clip_polyline.h" + +template +std::string dump_path(T & path) +{ + unsigned cmd = 1; + double x = 0; + double y = 0; + unsigned idx = 0; + std::ostringstream s; + path.rewind(0); + while ((cmd = path.vertex(&x, &y)) != mapnik::SEG_END) + { + if (idx > 0) s << ","; + s << x << " " << y << " " << cmd; + idx++; + } + return s.str(); +} + +std::string clip_line(mapnik::box2d const& bbox, + mapnik::path_type const& path) +{ + using line_clipper = agg::conv_clip_polyline; + mapnik::vertex_adapter va(path); + line_clipper clipped(va); + clipped.clip_box(bbox.minx(),bbox.miny(),bbox.maxx(),bbox.maxy()); + return dump_path(clipped); +} + +void parse_geom(mapnik::path_type & path, + std::string const& geom_string) { + std::vector vertices; + boost::split(vertices, geom_string, boost::is_any_of(",")); + for (std::string const& vert : vertices) + { + std::vector commands; + boost::split(commands, vert, boost::is_any_of(" ")); + if (commands.size() != 3) + { + throw std::runtime_error(std::string("could not parse geometry '") + geom_string + "'"); + } + double x = 0; + double y = 0; + int c = 0; + if (mapnik::util::string2double(commands[0],x) + && mapnik::util::string2double(commands[1],y) + && mapnik::util::string2int(commands[2],c)) + { + path.push_vertex(x,y,(mapnik::CommandType)c); + } + else + { + throw std::runtime_error(std::string("could not parse geometry '") + geom_string + "'"); + } + } +} + +TEST_CASE("clipping") { + +SECTION("lines") { + + try { + + std::string filename("tests/cpp_tests/data/cases.txt"); + std::ifstream stream(filename.c_str(),std::ios_base::in | std::ios_base::binary); + if (!stream.is_open()) + throw std::runtime_error("could not open: '" + filename + "'"); + + std::string csv_line; + while(std::getline(stream,csv_line,'\n')) + { + if (csv_line.empty() || csv_line[0] == '#') continue; + std::vector parts; + boost::split(parts, csv_line, boost::is_any_of(";")); + // first part is clipping box + mapnik::box2d bbox; + if (!bbox.from_string(parts[0])) { + throw std::runtime_error(std::string("could not parse bbox '") + parts[0] + "'"); + } + // second part is input geometry + mapnik::path_type path; + parse_geom(path, parts[1]); + //std::clog << dump_path(path) << "\n"; + // third part is expected, clipped geometry + REQUIRE(clip_line(bbox, path) == mapnik::util::trim_copy(parts[2])); + } + stream.close(); + } + catch (std::exception const& ex) + { + std::cerr << ex.what() << "\n"; + } + +} + +} \ No newline at end of file diff --git a/tests/cxx/conversions_test.cpp b/tests/cxx/conversions_test.cpp new file mode 100644 index 000000000..08819b2d0 --- /dev/null +++ b/tests/cxx/conversions_test.cpp @@ -0,0 +1,300 @@ +#include "catch.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#if defined(_MSC_VER) && _MSC_VER < 1900 +#include +#endif + +TEST_CASE("conversions") { + +SECTION("to string") { + + #if defined(_MSC_VER) && _MSC_VER < 1900 + unsigned int old = _set_output_format(_TWO_DIGIT_EXPONENT); + #endif + + using mapnik::util::to_string; + using mapnik::util::string2bool; + + try + { + std::string out; + + // Test double + to_string(out, double(0)); + REQUIRE( out == "0" ); + out.clear(); + + to_string(out, double(1)); + REQUIRE( out == "1" ); + out.clear(); + + to_string(out, double(-1)); + REQUIRE( out == "-1" ); + out.clear(); + + to_string(out, double(0.1)); + REQUIRE( out == "0.1" ); + out.clear(); + + to_string(out, double(-0.1)); + REQUIRE( out == "-0.1" ); + out.clear(); + + to_string(out, double(0.123)); + REQUIRE( out == "0.123" ); + out.clear(); + + to_string(out, double(-0.123)); + REQUIRE( out == "-0.123" ); + out.clear(); + + to_string(out, double(1e-06)); + REQUIRE( out == "1e-06" ); + out.clear(); + + to_string(out, double(-1e-06)); + REQUIRE( out == "-1e-06" ); + out.clear(); + + to_string(out, double(1e-05)); + REQUIRE( out == "1e-05" ); + out.clear(); + + to_string(out, double(-1e-05)); + REQUIRE( out == "-1e-05" ); + out.clear(); + + to_string(out, double(0.0001)); + REQUIRE( out == "0.0001" ); + out.clear(); + + to_string(out, double(-0.0001)); + REQUIRE( out == "-0.0001" ); + out.clear(); + + to_string(out, double(0.0001)); + REQUIRE( out == "0.0001" ); + out.clear(); + + to_string(out, double(0.00001)); + REQUIRE( out == "1e-05" ); + out.clear(); + + to_string(out, double(0.000001)); + REQUIRE( out == "1e-06" ); + out.clear(); + + to_string(out, double(0.0000001)); + REQUIRE( out == "1e-07" ); + out.clear(); + + to_string(out, double(0.00000001)); + REQUIRE( out == "1e-08" ); + out.clear(); + + to_string(out, double(0.000000001)); + REQUIRE( out == "1e-09" ); + out.clear(); + + to_string(out, double(0.0000000001)); + REQUIRE( out == "1e-10" ); + out.clear(); + + to_string(out, double(-1.234e+16)); + REQUIRE( out == "-1.234e+16" ); + out.clear(); + + // critical failure when karam is used + // https://github.com/mapnik/mapnik/issues/1741 + // https://github.com/mapbox/tilemill/issues/1456 + to_string(out, double(8.3)); + REQUIRE( out == "8.3" ); + out.clear(); + + // non-critical failures if karma is used + to_string(out, double(0.0001234567890123456)); + // TODO: https://github.com/mapnik/mapnik/issues/1676 + REQUIRE( out == "0.000123457" ); + out.clear(); + + to_string(out, double(0.00000000001)); + REQUIRE( out == "1e-11" ); + out.clear(); + + to_string(out, double(0.000000000001)); + REQUIRE( out == "1e-12" ); + out.clear(); + + to_string(out, double(0.0000000000001)); + REQUIRE( out == "1e-13" ); + out.clear(); + + to_string(out, double(0.00000000000001)); + REQUIRE( out == "1e-14" ); + out.clear(); + + to_string(out, double(0.000000000000001)); + REQUIRE( out == "1e-15" ); + out.clear(); + + to_string(out, double(100000)); + REQUIRE( out == "100000" ); + out.clear(); + + to_string(out, double(1000000)); + REQUIRE( out == "1e+06" ); + out.clear(); + + to_string(out, double(10000000)); + REQUIRE( out == "1e+07" ); + out.clear(); + + to_string(out, double(100000000)); + REQUIRE( out == "1e+08" ); + out.clear(); + + to_string(out, double(1000000000)); + REQUIRE( out == "1e+09" ); + out.clear(); + + to_string(out, double(10000000000)); + REQUIRE( out == "1e+10" ); + out.clear(); + + to_string(out, double(100000000000)); + REQUIRE( out == "1e+11" ); + out.clear(); + + to_string(out, double(1000000000000)); + REQUIRE( out == "1e+12" ); + out.clear(); + + to_string(out, double(10000000000000)); + REQUIRE( out == "1e+13" ); + out.clear(); + + to_string(out, double(100000000000000)); + REQUIRE( out == "1e+14" ); + out.clear(); + + to_string(out, double(1000000000000005)); + REQUIRE( out == "1e+15" ); + out.clear(); + + to_string(out, double(-1000000000000000)); + REQUIRE( out == "-1e+15" ); + out.clear(); + + to_string(out, double(100000000000000.1)); + REQUIRE( out == "1e+14" ); + out.clear(); + + to_string(out, double(1.00001)); + REQUIRE( out == "1.00001" ); + out.clear(); + + to_string(out, double(67.65)); + REQUIRE( out == "67.65" ); + out.clear(); + + to_string(out, double(67.35)); + REQUIRE( out == "67.35" ); + out.clear(); + + to_string(out, double(1234000000000000)); + REQUIRE( out == "1.234e+15" ); + out.clear(); + + to_string(out, double(1e+16)); + REQUIRE( out == "1e+16" ); + out.clear(); + + to_string(out, double(1.234e+16)); + REQUIRE( out == "1.234e+16" ); + out.clear(); + + // int + to_string(out, int(2)); + REQUIRE( out == "2" ); + out.clear(); + + to_string(out, int(0)); + REQUIRE( out == "0" ); + out.clear(); + + to_string(out, int(-2)); + REQUIRE( out == "-2" ); + out.clear(); + + to_string(out, int(2147483647)); + REQUIRE( out == "2147483647" ); + out.clear(); + + to_string(out, int(-2147483648)); + REQUIRE( out == "-2147483648" ); + out.clear(); + + // unsigned + to_string(out, unsigned(4294967295)); + REQUIRE( out == "4294967295" ); + out.clear(); + +#ifdef BIGINT + // long long + to_string(out,mapnik::value_integer(-0)); + REQUIRE( out == "0" ); + out.clear(); + + to_string(out,mapnik::value_integer(-2)); + REQUIRE( out == "-2" ); + out.clear(); + + to_string(out,mapnik::value_integer(9223372036854775807)); + REQUIRE( out == "9223372036854775807" ); + out.clear(); +#else + #ifdef _MSC_VER + #pragma NOTE("BIGINT not defined so skipping large number conversion tests") + #else + #warning BIGINT not defined so skipping large number conversion tests + #endif +#endif + // bool + to_string(out, true); + REQUIRE( out == "true" ); + out.clear(); + + to_string(out, false); + REQUIRE( out == "false" ); + out.clear(); + + bool val = false; + REQUIRE( !string2bool("this is invalid",val) ); + REQUIRE( val == false ); + REQUIRE( string2bool("true",val) ); + REQUIRE( val == true ); + + // mapnik::value hashability + using values_container = boost::unordered_map; + values_container vc; + mapnik::value val2(1); + vc[val2] = 1; + REQUIRE( vc[1] == static_cast(1) ); + + } + catch (std::exception const & ex) + { + std::clog << ex.what() << "\n"; + REQUIRE(false); + } +} + +} \ No newline at end of file diff --git a/tests/cxx/copy_move_test.cpp b/tests/cxx/copy_move_test.cpp new file mode 100644 index 000000000..d3f2b3477 --- /dev/null +++ b/tests/cxx/copy_move_test.cpp @@ -0,0 +1,83 @@ + +#include +#include +#include +#include +#include + +#include +#include + +#include "catch.hpp" + + +TEST_CASE("copy") { + +SECTION("layers") { + + try + { + mapnik::Map m0(100,100); + mapnik::Map m2(200,100); + // FIXME: not compiling when ported to catch.hpp + // due to some conflict: 'include/mapnik/value.hpp:832:11: error: no matching constructor for initialization of 'value_base'' + /* + + // mapnik::datasource + mapnik::datasource_cache::instance().register_datasources("plugins/input/shape.input"); + mapnik::parameters p; + p["type"]="shape"; + p["file"]="demo/data/boundaries"; + p["encoding"]="latin1"; + auto ds0 = mapnik::datasource_cache::instance().create(p); + + auto ds1 = ds0; // shared ptr copy + REQUIRE(ds1 == ds0); + //REQUIRE(*ds1 == *ds0); + ds1 = mapnik::datasource_cache::instance().create(p); // new with the same parameters + REQUIRE(ds1 != ds0); + REQUIRE(*ds1 == *ds0); + auto ds2 = std::move(ds1); + REQUIRE(ds2 != ds0); + REQUIRE(*ds2 == *ds0); + + // mapnik::layer + mapnik::layer l0("test-layer"); + l0.set_datasource(ds0); + + mapnik::layer l1 = l0; // copy assignment + REQUIRE(l1 == l0); + mapnik::layer l2(l0); // copy ctor + REQUIRE(l2 == l0); + mapnik::layer l3(mapnik::layer("test-layer")); // move ctor + l3.set_datasource(ds2); + + REQUIRE(l3 == l0); + mapnik::layer l4 = std::move(l3); + REQUIRE(l4 == l0); // move assignment + + m0.add_layer(l4); + m0.set_background(mapnik::color("skyblue")); + m2.set_background(mapnik::color("skyblue")); + + auto m1 = m0; //copy + + REQUIRE(m0 == m1); + REQUIRE(m0 != m2); + + m2 = m1; // copy + REQUIRE(m2 == m1); + m2 = std::move(m1); + REQUIRE(m2 == m0); + REQUIRE(m1 != m0); + + REQUIRE(m0 == m2); + */ + } + catch (std::exception const & ex) + { + std::clog << ex.what() << "\n"; + REQUIRE(false); + } +} +} \ No newline at end of file diff --git a/tests/cxx/exceptions_test.cpp b/tests/cxx/exceptions_test.cpp new file mode 100644 index 000000000..b3492d195 --- /dev/null +++ b/tests/cxx/exceptions_test.cpp @@ -0,0 +1,103 @@ +#include "catch.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +TEST_CASE("exceptions") { + +SECTION("handling") { + try { + mapnik::projection srs("foo"); + // to avoid unused variable warning + srs.params(); + REQUIRE(false); + } catch (...) { + REQUIRE(true); + } + + // https://github.com/mapnik/mapnik/issues/2170 + try { + mapnik::projection srs("+proj=longlat foo",true); + REQUIRE(srs.is_geographic()); + REQUIRE(true); + srs.init_proj4(); + // oddly init_proj4 does not throw with old proj/ubuntu precise + //REQUIRE(false); + } catch (...) { + REQUIRE(true); + } + + try { + mapnik::transcoder tr("bogus encoding"); + REQUIRE(false); + } catch (...) { + REQUIRE(true); + } + + mapnik::Map map(256,256); + mapnik::rule r; + r.set_filter(mapnik::parse_expression("[foo]='bar'")); + r.append(std::move(mapnik::markers_symbolizer())); + mapnik::feature_type_style style; + style.add_rule(std::move(r)); + map.insert_style("style", std::move(style)); + + std::string csv_plugin("./plugins/input/csv.input"); + if (mapnik::util::exists(csv_plugin)) { + try { + mapnik::datasource_cache::instance().register_datasource(csv_plugin); + mapnik::parameters p; + p["type"]="csv"; + p["inline"]="x,y\n0,0"; + mapnik::datasource_ptr ds = mapnik::datasource_cache::instance().create(p); + mapnik::layer l("layer"); + l.set_datasource(ds); + l.add_style("style"); + mapnik::Map m = map; + m.add_layer(l); + m.zoom_all(); + mapnik::image_rgba8 im(m.width(),m.height()); + mapnik::agg_renderer ren(m,im); + //std::clog << mapnik::save_map_to_string(m) << "\n"; + REQUIRE(true); + // should throw here with "CSV Plugin: no attribute 'foo'. Valid attributes are: x,y." + ren.apply(); + REQUIRE(false); + } catch (...) { + REQUIRE(true); + } + } + + std::string shape_plugin("./plugins/input/shape.input"); + if (mapnik::util::exists(shape_plugin)) { + try { + mapnik::datasource_cache::instance().register_datasource(shape_plugin); + mapnik::parameters p2; + p2["type"]="shape"; + p2["file"]="foo"; + mapnik::datasource_cache::instance().create(p2); + REQUIRE(false); + } catch (...) { + REQUIRE(true); + } + } + +} +} diff --git a/tests/cxx/font_registration_test.cpp b/tests/cxx/font_registration_test.cpp new file mode 100644 index 000000000..d39837f77 --- /dev/null +++ b/tests/cxx/font_registration_test.cpp @@ -0,0 +1,192 @@ +#include "catch.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include + +TEST_CASE("font") { + +SECTION("registration") { + try + { + mapnik::logger logger; + mapnik::logger::severity_type original_severity = logger.get_severity(); + + + + // grab references to global statics of registered/cached fonts + auto const& global_mapping = mapnik::freetype_engine::get_mapping(); + auto const& global_cache = mapnik::freetype_engine::get_cache(); + + // mapnik.Map object has parallel structure for localized fonts + mapnik::Map m(1,1); + auto const& local_mapping = m.get_font_file_mapping(); + auto const& local_cache = m.get_font_memory_cache(); + + // should be empty to start + REQUIRE( global_mapping.empty() ); + REQUIRE( global_cache.empty() ); + REQUIRE( local_mapping.empty() ); + REQUIRE( local_cache.empty() ); + + std::string fontdir("fonts/"); + + REQUIRE( mapnik::util::exists( fontdir ) ); + REQUIRE( mapnik::util::is_directory( fontdir ) ); + + // test map cached fonts + REQUIRE( m.register_fonts(fontdir , false ) ); + REQUIRE( m.get_font_memory_cache().size() == 0 ); + REQUIRE( m.get_font_file_mapping().size() == 1 ); + REQUIRE( m.load_fonts() ); + REQUIRE( m.get_font_memory_cache().size() == 1 ); + REQUIRE( m.register_fonts(fontdir , true ) ); + REQUIRE( m.get_font_file_mapping().size() == 22 ); + REQUIRE( m.load_fonts() ); + REQUIRE( m.get_font_memory_cache().size() == 22 ); + + // copy discards memory cache but not file mapping + mapnik::Map m2(m); + REQUIRE( m2.get_font_memory_cache().size() == 0 ); + REQUIRE( m2.get_font_file_mapping().size() == 22 ); + REQUIRE( m2.load_fonts() ); + REQUIRE( m2.get_font_memory_cache().size() == 22 ); + + // test font-directory from XML + mapnik::Map m3(1,1); + mapnik::load_map_string(m3,""); + REQUIRE( m3.get_font_memory_cache().size() == 0 ); + REQUIRE( m3.load_fonts() ); + REQUIRE( m3.get_font_memory_cache().size() == 1 ); + + std::vector face_names; + std::string foo("foo"); + // fake directories + REQUIRE( !mapnik::freetype_engine::register_fonts(foo , true ) ); + face_names = mapnik::freetype_engine::face_names(); + REQUIRE( face_names.size() == 0 ); + REQUIRE( !mapnik::freetype_engine::register_fonts(foo) ); + face_names = mapnik::freetype_engine::face_names(); + REQUIRE( face_names.size() == 0 ); + + // directories without fonts + // silence warnings here by altering the logging severity + logger.set_severity(mapnik::logger::none); + std::string src("src"); + // an empty directory will not return true + // we need to register at least one font and not fail on any + // to return true + REQUIRE( mapnik::freetype_engine::register_font(src) == false ); + REQUIRE( mapnik::freetype_engine::register_fonts(src, true) == false ); + REQUIRE( mapnik::freetype_engine::face_names().size() == 0 ); + + // bogus, emtpy file that looks like font + REQUIRE( mapnik::freetype_engine::register_font("tests/data/fonts/fake.ttf") == false ); + REQUIRE( mapnik::freetype_engine::register_fonts("tests/data/fonts/fake.ttf") == false ); + REQUIRE( mapnik::freetype_engine::face_names().size() == 0 ); + + REQUIRE( mapnik::freetype_engine::register_font("tests/data/fonts/intentionally-broken.ttf") == false ); + REQUIRE( mapnik::freetype_engine::register_fonts("tests/data/fonts/intentionally-broken.ttf") == false ); + REQUIRE( mapnik::freetype_engine::face_names().size() == 0 ); + + // now restore the original severity + logger.set_severity(original_severity); + + // register unifont, since we know it sits in the root fonts/ dir + REQUIRE( mapnik::freetype_engine::register_fonts(fontdir) ); + face_names = mapnik::freetype_engine::face_names(); + REQUIRE( face_names.size() > 0 ); + REQUIRE( face_names.size() == 1 ); + + // re-register unifont, should not have any affect + REQUIRE( mapnik::freetype_engine::register_fonts(fontdir, false) ); + face_names = mapnik::freetype_engine::face_names(); + REQUIRE( face_names.size() == 1 ); + + // single dejavu font in separate location + std::string dejavu_bold_oblique("tests/data/fonts/DejaVuSansMono-BoldOblique.ttf"); + REQUIRE( mapnik::freetype_engine::register_font(dejavu_bold_oblique) ); + face_names = mapnik::freetype_engine::face_names(); + REQUIRE( face_names.size() == 2 ); + + // now, inspect font mapping and confirm the correct 'DejaVu Sans Mono Bold Oblique' is registered + using font_file_mapping = std::map >; + font_file_mapping const& name2file = mapnik::freetype_engine::get_mapping(); + bool found_dejavu = false; + for (auto const& item : name2file) + { + if (item.first == "DejaVu Sans Mono Bold Oblique") + { + found_dejavu = true; + REQUIRE( item.second.first == 0 ); + REQUIRE( item.second.second == dejavu_bold_oblique ); + } + } + REQUIRE( found_dejavu ); + + // recurse to find all dejavu fonts + REQUIRE( mapnik::freetype_engine::register_fonts(fontdir, true) ); + face_names = mapnik::freetype_engine::face_names(); + REQUIRE( face_names.size() == 22 ); + + // we should have re-registered 'DejaVu Sans Mono Bold Oblique' again, + // but now at a new path + bool found_dejavu2 = false; + for (auto const& item : name2file) + { + if (item.first == "DejaVu Sans Mono Bold Oblique") + { + found_dejavu2 = true; + REQUIRE( item.second.first == 0 ); + // path should be different + REQUIRE( item.second.second != dejavu_bold_oblique ); + } + } + REQUIRE( found_dejavu2 ); + + // now that global registry is populated + // now test that a map only loads new fonts + mapnik::Map m4(1,1); + REQUIRE( m4.register_fonts(fontdir , true ) ); + REQUIRE( m4.get_font_memory_cache().size() == 0 ); + REQUIRE( m4.get_font_file_mapping().size() == 22 ); + REQUIRE( !m4.load_fonts() ); + REQUIRE( m4.get_font_memory_cache().size() == 0 ); + REQUIRE( m4.register_fonts(dejavu_bold_oblique, false) ); + REQUIRE( m4.load_fonts() ); + REQUIRE( m4.get_font_memory_cache().size() == 1 ); + + // check that we can correctly read a .ttc containing + // multiple valid faces + // https://github.com/mapnik/mapnik/issues/2274 + REQUIRE( mapnik::freetype_engine::register_font("tests/data/fonts/NotoSans-Regular.ttc") ); + face_names = mapnik::freetype_engine::face_names(); + REQUIRE( face_names.size() == 24 ); + + // now blindly register as many system fonts as possible + // the goal here to make sure we don't crash + // linux + mapnik::freetype_engine::register_fonts("/usr/share/fonts/", true); + mapnik::freetype_engine::register_fonts("/usr/local/share/fonts/", true); + // osx + mapnik::freetype_engine::register_fonts("/Library/Fonts/", true); + mapnik::freetype_engine::register_fonts("/System/Library/Fonts/", true); + // windows + mapnik::freetype_engine::register_fonts("C:\\Windows\\Fonts", true); + face_names = mapnik::freetype_engine::face_names(); + REQUIRE( face_names.size() > 22 ); + } + catch (std::exception const & ex) + { + std::clog << ex.what() << "\n"; + REQUIRE(false); + } + +} +} \ No newline at end of file diff --git a/tests/cxx/fontset_runtime_test.cpp b/tests/cxx/fontset_runtime_test.cpp new file mode 100644 index 000000000..72305b160 --- /dev/null +++ b/tests/cxx/fontset_runtime_test.cpp @@ -0,0 +1,85 @@ +#include "catch.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// icu - for memory cleanup (to make valgrind happy) +#include "unicode/uclean.h" + +TEST_CASE("fontset") { + +SECTION("error") { + + try { + + // create a renderable map with a fontset and a text symbolizer + // and do not register any fonts, to ensure the error thrown is reasonable + mapnik::context_ptr ctx = std::make_shared(); + ctx->push("name"); + mapnik::feature_ptr feature(mapnik::feature_factory::create(ctx,1)); + mapnik::transcoder tr("utf-8"); + mapnik::value_unicode_string ustr = tr.transcode("hello world!"); + feature->put("name",ustr); + //auto pt = std::make_unique(mapnik::geometry::geometry_types::Point); + //pt->move_to(128,128); + mapnik::geometry::point pt(128,128); + feature->set_geometry(std::move(pt)); + + mapnik::parameters params; + params["type"]="memory"; + auto ds = std::make_shared(params); + ds->push(feature); + mapnik::Map m(256,256); + mapnik::font_set fontset("fontset"); + // NOTE: this is a valid font, but will fail because none are registered + fontset.add_face_name("DejaVu Sans Book"); + m.insert_fontset("fontset", fontset); + mapnik::layer lyr("layer"); + lyr.set_datasource(ds); + lyr.add_style("style"); + m.add_layer(lyr); + mapnik::feature_type_style the_style; + mapnik::rule r; + mapnik::text_symbolizer text_sym; + mapnik::text_placements_ptr placement_finder = std::make_shared(); + placement_finder->defaults.format_defaults.face_name = "DejaVu Sans Book"; + placement_finder->defaults.format_defaults.text_size = 10.0; + placement_finder->defaults.format_defaults.fill = mapnik::color(0,0,0); + placement_finder->defaults.format_defaults.fontset = fontset; + placement_finder->defaults.set_format_tree(std::make_shared(mapnik::parse_expression("[name]"))); + mapnik::put(text_sym, mapnik::keys::text_placements_, placement_finder); + r.append(std::move(text_sym)); + the_style.add_rule(std::move(r)); + m.insert_style("style", std::move(the_style) ); + m.zoom_to_box(mapnik::box2d(-256,-256, + 256,256)); + mapnik::image_rgba8 buf(m.width(),m.height()); + mapnik::agg_renderer ren(m,buf); + ren.apply(); + } catch (std::exception const& ex) { + REQUIRE(std::string(ex.what()) == std::string("Unable to find specified font face 'DejaVu Sans Book' in font set: 'fontset'")); + } + u_cleanup(); +} +} \ No newline at end of file diff --git a/tests/cxx/geometry_converters_test.cpp b/tests/cxx/geometry_converters_test.cpp new file mode 100644 index 000000000..3689051b5 --- /dev/null +++ b/tests/cxx/geometry_converters_test.cpp @@ -0,0 +1,192 @@ +#include "catch.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// stl +#include +#if 0 // FIXME +struct output_geometry_backend +{ + output_geometry_backend(mapnik::geometry_container & paths, mapnik::geometry_type::types type) + : paths_(paths), + type_(type) {} + + template + void add_path(T & path) + { + mapnik::vertex2d vtx(mapnik::vertex2d::no_init); + path.rewind(0); + std::unique_ptr geom_ptr(new mapnik::geometry_type(type_)); + + while ((vtx.cmd = path.vertex(&vtx.x, &vtx.y)) != mapnik::SEG_END) + { + //std::cerr << vtx.x << "," << vtx.y << " cmd=" << vtx.cmd << std::endl; + geom_ptr->push_vertex(vtx.x, vtx.y, (mapnik::CommandType)vtx.cmd); + } + paths_.push_back(geom_ptr.release()); + } + mapnik::geometry_container & paths_; + mapnik::geometry_type::types type_; +}; + +boost::optional linestring_bbox_clipping(mapnik::box2d bbox, + std::string wkt_in) +{ + using namespace mapnik; + agg::trans_affine tr; + projection src(MAPNIK_LONGLAT_PROJ); + projection dst(MAPNIK_LONGLAT_PROJ); + proj_transform prj_trans(src,dst); + line_symbolizer sym; + view_transform t(bbox.width(),bbox.height(), bbox); + mapnik::geometry_container output_paths; + output_geometry_backend backend(output_paths, mapnik::geometry::geometry_types::LineString); + + mapnik::context_ptr ctx = std::make_shared(); + mapnik::feature_impl f(ctx,0); + vertex_converter + converter(bbox, backend, sym, t, prj_trans, tr, f, attributes(), 1.0); + + converter.set(); + + mapnik::geometry_container p; + if (!mapnik::from_wkt(wkt_in , p)) + { + throw std::runtime_error("Failed to parse WKT"); + } + + for (geometry_type const& geom : p) + { + vertex_adapter va(geom); + converter.apply(va); + } + + using sink_type = std::back_insert_iterator; + std::string wkt; + sink_type sink(wkt); + static const mapnik::wkt::wkt_multi_generator generator; + if (boost::spirit::karma::generate(sink, generator, output_paths)) + { + return boost::optional(wkt); + } + return boost::optional(); +} + +boost::optional polygon_bbox_clipping(mapnik::box2d bbox, + std::string wkt_in) +{ + using namespace mapnik; + agg::trans_affine tr; + projection src(MAPNIK_LONGLAT_PROJ); + projection dst(MAPNIK_LONGLAT_PROJ); + proj_transform prj_trans(src,dst); + polygon_symbolizer sym; + view_transform t(bbox.width(),bbox.height(), bbox); + mapnik::geometry_container output_paths; + output_geometry_backend backend(output_paths, mapnik::geometry::geometry_types::Polygon); + + mapnik::context_ptr ctx = std::make_shared(); + mapnik::feature_impl f(ctx,0); + vertex_converter + converter(bbox, backend, sym, t, prj_trans, tr, f, attributes(), 1.0); + + converter.set(); + + mapnik::geometry_container p; + if (!mapnik::from_wkt(wkt_in , p)) + { + throw std::runtime_error("Failed to parse WKT"); + } + + for (geometry_type const& geom : p) + { + vertex_adapter va(geom); + converter.apply(va); + } + + using sink_type = std::back_insert_iterator; + std::string wkt; // Use Python String directly ? + sink_type sink(wkt); + static const mapnik::wkt::wkt_multi_generator generator; + if (boost::spirit::karma::generate(sink, generator, output_paths)) + { + return boost::optional(wkt); + } + + return boost::optional(); +} + +#endif + +TEST_CASE("geometry converters") { + +SECTION("TODO") { + + try + { +#if 0 + // LineString/bbox clipping + { + std::string wkt_in("LineString(0 0,200 200)"); + boost::optional result = linestring_bbox_clipping(mapnik::box2d(50,50,150,150),wkt_in); + REQUIRE(result); + REQUIRE(*result == std::string("LineString(50 50,150 150)")); + } + // Polygon/bbox clipping + { + std::string wkt_in("Polygon((50 50,150 50,150 150,50 150,50 50))"); + boost::optional result = polygon_bbox_clipping(mapnik::box2d(50,50,150,150),wkt_in); + REQUIRE(result); + // TODO - the extra 50 50 is not ideal, but we enforce this result for now to prevent + // regressions and because we don't have actionable solution to drop extra 50 50 + REQUIRE(*result == std::string("Polygon((50 50,150 50,150 150,50 150,50 50,50 50))")); + // below is ideal, but not current result + //REQUIRE(*result == std::string("Polygon((50 50,150 50,150 150,50 150,50 50))")); + } + + { + std::string wkt_in("Polygon((60 60,140 60,140 160,60 140,60 60))"); + boost::optional result = polygon_bbox_clipping(mapnik::box2d(50,50,150,150),wkt_in); + REQUIRE(result); + REQUIRE(*result == std::string("Polygon((60 60,140 60,140 150,100 150,60 140,60 60,60 60))")); + //REQUIRE(*result == std::string("Polygon((60 60,140 60,140 160,60 140,60 60))")); + } + + { + std::string wkt_in("Polygon((0 0,10 0,10 10,0 10,0 0))"); + boost::optional result = polygon_bbox_clipping(mapnik::box2d(50,50,150,150),wkt_in); + REQUIRE(result); + // TODO - this is completely wrong: should not have )) and ideally should be EMPTY + REQUIRE(*result == std::string("Polygon())")); + } + { + std::string wkt_in("Polygon((0 0,100 200,200 0,0 0 ))"); + boost::optional result = polygon_bbox_clipping(mapnik::box2d(50,50,150,150),wkt_in); + REQUIRE(result); + REQUIRE(*result == std::string("Polygon((50 50,50 100,75 150,125 150,150 100,150 50))")); + //REQUIRE(*result == std::string("Polygon((50 50,50 100,75 150,125 150,150 100,150 50,50 50))")); + } +#endif + } + catch (std::exception const & ex) + { + std::clog << ex.what() << "\n"; + REQUIRE(false); + } +} +} diff --git a/tests/cxx/image_io_test.cpp b/tests/cxx/image_io_test.cpp new file mode 100644 index 000000000..d94397086 --- /dev/null +++ b/tests/cxx/image_io_test.cpp @@ -0,0 +1,130 @@ +#include "catch.hpp" + +#include +#include +#include +#include +#include +#include +#include +#if defined(HAVE_CAIRO) +#include +#include +#endif + +TEST_CASE("image io") { + +SECTION("readers") { + + std::string should_throw; + boost::optional type; + try + { +#if defined(HAVE_JPEG) + should_throw = "./tests/cpp_tests/data/blank.jpg"; + REQUIRE( mapnik::util::exists( should_throw ) ); + type = mapnik::type_from_filename(should_throw); + REQUIRE( type ); + try + { + std::unique_ptr reader(mapnik::get_image_reader(should_throw,*type)); + REQUIRE( false ); + } + catch (std::exception const&) + { + REQUIRE( true ); + } +#endif + + try + { + mapnik::image_rgba8 im(-10,-10); // should throw rather than overflow + REQUIRE( im.width() < 10 ); // should not get here, but if we did this test should fail + } + catch (std::exception const& ex) + { + REQUIRE( true ); // should hit bad alloc here + } + +#if defined(HAVE_CAIRO) + mapnik::cairo_surface_ptr image_surface( + cairo_image_surface_create(CAIRO_FORMAT_ARGB32,256,257), + mapnik::cairo_surface_closer()); + mapnik::image_rgba8 im_data(cairo_image_surface_get_width(&*image_surface), cairo_image_surface_get_height(&*image_surface)); + im_data.set(1); + REQUIRE( (unsigned)im_data(0,0) == unsigned(1) ); + // Should set back to fully transparent + mapnik::cairo_image_to_rgba8(im_data, image_surface); + REQUIRE( (unsigned)im_data(0,0) == unsigned(0) ); +#endif + +#if defined(HAVE_PNG) + should_throw = "./tests/cpp_tests/data/blank.png"; + REQUIRE( mapnik::util::exists( should_throw ) ); + type = mapnik::type_from_filename(should_throw); + REQUIRE( type ); + try + { + std::unique_ptr reader(mapnik::get_image_reader(should_throw,*type)); + REQUIRE( false ); + } + catch (std::exception const&) + { + REQUIRE( true ); + } + + should_throw = "./tests/data/images/xcode-CgBI.png"; + REQUIRE( mapnik::util::exists( should_throw ) ); + type = mapnik::type_from_filename(should_throw); + REQUIRE( type ); + try + { + std::unique_ptr reader(mapnik::get_image_reader(should_throw,*type)); + REQUIRE( false ); + } + catch (std::exception const&) + { + REQUIRE( true ); + } +#endif + +#if defined(HAVE_TIFF) + should_throw = "./tests/cpp_tests/data/blank.tiff"; + REQUIRE( mapnik::util::exists( should_throw ) ); + type = mapnik::type_from_filename(should_throw); + REQUIRE( type ); + try + { + std::unique_ptr reader(mapnik::get_image_reader(should_throw,*type)); + REQUIRE( false ); + } + catch (std::exception const&) + { + REQUIRE( true ); + } +#endif + +#if defined(HAVE_WEBP) + should_throw = "./tests/cpp_tests/data/blank.webp"; + REQUIRE( mapnik::util::exists( should_throw ) ); + type = mapnik::type_from_filename(should_throw); + REQUIRE( type ); + try + { + std::unique_ptr reader(mapnik::get_image_reader(should_throw,*type)); + REQUIRE( false ); + } + catch (std::exception const&) + { + REQUIRE( true ); + } +#endif + } + catch (std::exception const & ex) + { + std::clog << ex.what() << "\n"; + REQUIRE(false); + } + +} +} \ No newline at end of file diff --git a/tests/cxx/image_painted_test.cpp b/tests/cxx/image_painted_test.cpp new file mode 100644 index 000000000..b567d5e6a --- /dev/null +++ b/tests/cxx/image_painted_test.cpp @@ -0,0 +1,69 @@ +#include "catch.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +TEST_CASE("image") { + +SECTION("painting") { + + using namespace mapnik; + + try + { + datasource_cache::instance().register_datasources("plugins/input/csv.input"); + + Map m(256, 256); + + feature_type_style lines_style; + { + rule r; + line_symbolizer line_sym; + r.append(std::move(line_sym)); + lines_style.add_rule(std::move(r)); + } + m.insert_style("lines", std::move(lines_style)); + + feature_type_style markers_style; + { + rule r; + r.set_filter(parse_expression("False")); + markers_symbolizer mark_sym; + r.append(std::move(mark_sym)); + markers_style.add_rule(std::move(r)); + } + m.insert_style("markers", std::move(markers_style)); + + parameters p; + p["type"] = "csv"; + p["separator"] = "|"; + p["inline"] = "wkt\nLINESTRING(-10 0, 0 20, 10 0, 15 5)"; + + layer lyr("layer"); + lyr.set_datasource(datasource_cache::instance().create(p)); + lyr.add_style("lines"); + lyr.add_style("markers"); + m.add_layer(lyr); + + m.zoom_all(); + + image_rgba8 image(m.width(), m.height()); + agg_renderer ren(m, image); + ren.apply(); + + REQUIRE(image.painted() == true); + } + catch (std::exception const & ex) + { + std::clog << ex.what() << std::endl; + REQUIRE(false); + } + +} +} \ No newline at end of file diff --git a/tests/cxx/label_algo_test.cpp b/tests/cxx/label_algo_test.cpp new file mode 100644 index 000000000..851db3352 --- /dev/null +++ b/tests/cxx/label_algo_test.cpp @@ -0,0 +1,65 @@ +#include "catch.hpp" + +#include +#include +#include +#include +#include +#include + +TEST_CASE("labeling") { + +SECTION("algorithms") { + + try + { + // reused these for simplicity + mapnik::geometry::point centroid; + { + // single point + mapnik::geometry::point pt(10,10); + REQUIRE( mapnik::geometry::centroid(pt, centroid)); + REQUIRE( pt.x == centroid.x); + REQUIRE( pt.y == centroid.y); + } + + // linestring with three consecutive verticies + { + mapnik::geometry::line_string line; + line.add_coord(0, 0); + line.add_coord(25, 25); + line.add_coord(50, 50); + REQUIRE(mapnik::geometry::centroid(line, centroid)); + REQUIRE( centroid.x == 25 ); + REQUIRE( centroid.y == 25 ); + } + // TODO - centroid and interior should be equal but they appear not to be (check largest) + // MULTIPOLYGON(((-52 40,-60 32,-68 40,-60 48,-52 40)),((-60 50,-80 30,-100 49.9999999999999,-80.0000000000001 70,-60 50)),((-52 60,-60 52,-68 60,-60 68,-52 60))) +#if 0 + // hit tests + { + mapnik::geometry_type pt_hit(mapnik::geometry::geometry_types::Point); + pt_hit.move_to(10,10); + mapnik::vertex_adapter va(pt_hit); + REQUIRE( mapnik::label::hit_test(va, 10, 10, 0.1) ); + REQUIRE( !mapnik::label::hit_test(va, 9, 9, 0) ); + REQUIRE( mapnik::label::hit_test(va, 9, 9, 1.5) ); + } + { + mapnik::geometry_type line_hit(mapnik::geometry::geometry_types::LineString); + line_hit.move_to(0,0); + line_hit.line_to(50,50); + mapnik::vertex_adapter va(line_hit); + REQUIRE( mapnik::label::hit_test(va, 0, 0, 0.001) ); + REQUIRE( !mapnik::label::hit_test(va, 1, 1, 0) ); + REQUIRE( mapnik::label::hit_test(va, 1, 1, 1.001) ); + } +#endif + } + catch (std::exception const & ex) + { + std::clog << ex.what() << "\n"; + REQUIRE(false); + } +} +} diff --git a/tests/cxx/line_offset_test.cpp b/tests/cxx/line_offset_test.cpp new file mode 100644 index 000000000..94fbff6d1 --- /dev/null +++ b/tests/cxx/line_offset_test.cpp @@ -0,0 +1,195 @@ +#include "catch.hpp" + +// mapnik +#include +#include + +// stl +#include +#include +#include +#include +#include +#include + +struct fake_path +{ + using coord_type = std::tuple; + using cont_type = std::vector; + cont_type vertices_; + cont_type::iterator itr_; + + fake_path(std::initializer_list l) + : fake_path(l.begin(), l.size()) { + } + + fake_path(std::vector const &v) + : fake_path(v.begin(), v.size()) { + } + + template + fake_path(Itr itr, size_t sz) { + size_t num_coords = sz >> 1; + vertices_.reserve(num_coords); + + for (size_t i = 0; i < num_coords; ++i) { + double x = *itr++; + double y = *itr++; + unsigned cmd = (i == 0) ? agg::path_cmd_move_to : agg::path_cmd_line_to; + vertices_.push_back(std::make_tuple(x, y, cmd)); + } + itr_ = vertices_.begin(); + } + + unsigned vertex(double *x, double *y) { + if (itr_ == vertices_.end()) { + return agg::path_cmd_stop; + } + *x = std::get<0>(*itr_); + *y = std::get<1>(*itr_); + unsigned cmd = std::get<2>(*itr_); + ++itr_; + return cmd; + } + + void rewind(unsigned) { + itr_ = vertices_.begin(); + } +}; + +double dist(mapnik::pixel_position const &a, + mapnik::pixel_position const &b) +{ + mapnik::pixel_position d = a - b; + return std::sqrt(d.x*d.x + d.y*d.y); +} + +void test_simple_segment(double const &offset) +{ + const double dx = 0.01; + fake_path path = {0, 0, 1, 0}, off_path = {0, offset, 1, offset}; + mapnik::vertex_cache vc(path), off_vc(off_path); + + vc.reset(); vc.next_subpath(); + off_vc.reset(); off_vc.next_subpath(); + + while (vc.move(dx)) { + double pos = vc.linear_position(); + double off_pos = off_vc.position_closest_to(vc.current_position()); + REQUIRE(std::abs(pos - off_pos) < 1.0e-6); + } +} + +void test_straight_line(double const &offset) { + const double dx = 0.01; + fake_path path = {0, 0, 0.1, 0, 0.9, 0, 1, 0}, + off_path = {0, offset, 0.4, offset, 0.6, offset, 1, offset}; + mapnik::vertex_cache vc(path), off_vc(off_path); + + vc.reset(); vc.next_subpath(); + off_vc.reset(); off_vc.next_subpath(); + + while (vc.move(dx)) { + double pos = vc.linear_position(); + double off_pos = off_vc.position_closest_to(vc.current_position()); + REQUIRE(std::abs(pos - off_pos) < 1.0e-6); + } +} + +void test_offset_curve(double const &offset) { + const double dx = 0.01; + const double r = (1.0 + offset); + + std::vector pos, off_pos; + const size_t max_i = 1000; + for (size_t i = 0; i <= max_i; ++i) { + double x = M_PI * double(i) / max_i; + pos.push_back(-std::cos(x)); pos.push_back(std::sin(x)); + off_pos.push_back(-r * std::cos(x)); off_pos.push_back(r * std::sin(x)); + } + + fake_path path(pos), off_path(off_pos); + mapnik::vertex_cache vc(path), off_vc(off_path); + + vc.reset(); vc.next_subpath(); + off_vc.reset(); off_vc.next_subpath(); + + while (vc.move(dx)) { + double pos = vc.linear_position(); + double off_pos = off_vc.position_closest_to(vc.current_position()); + { + mapnik::vertex_cache::scoped_state s(off_vc); + off_vc.move(off_pos); + auto eps = (1.001 * offset); + auto actual = dist(vc.current_position(), off_vc.current_position()); + REQUIRE(actual < eps); + } + REQUIRE(std::abs((pos / vc.length()) - (off_pos / off_vc.length())) < 1.0e-3); + } +} + +void test_s_shaped_curve(double const &offset) { + const double dx = 0.01; + const double r = (1.0 + offset); + const double r2 = (1.0 - offset); + + std::vector pos, off_pos; + const size_t max_i = 1000; + for (size_t i = 0; i <= max_i; ++i) { + double x = M_PI * double(i) / max_i; + pos.push_back(-std::cos(x) - 1); pos.push_back(std::sin(x)); + off_pos.push_back(-r * std::cos(x) - 1); off_pos.push_back(r * std::sin(x)); + } + for (size_t i = 0; i <= max_i; ++i) { + double x = M_PI * double(i) / max_i; + pos.push_back(-std::cos(x) + 1); pos.push_back(-std::sin(x)); + off_pos.push_back(-r2 * std::cos(x) + 1); off_pos.push_back(-r2 * std::sin(x)); + } + + fake_path path(pos), off_path(off_pos); + mapnik::vertex_cache vc(path), off_vc(off_path); + + vc.reset(); vc.next_subpath(); + off_vc.reset(); off_vc.next_subpath(); + + while (vc.move(dx)) { + double off_pos = off_vc.position_closest_to(vc.current_position()); + { + mapnik::vertex_cache::scoped_state s(off_vc); + off_vc.move(off_pos); + REQUIRE(dist(vc.current_position(), off_vc.current_position()) < (1.002 * offset)); + } + } +} + +TEST_CASE("offsets") { + +SECTION("line") { + try { + + std::vector offsets = { 0.01, 0.02, 0.1, 0.2 }; + for (double offset : offsets) { + // test simple straight line segment - should be easy to + // find the correspondance here. + test_simple_segment(offset); + + // test straight line consisting of more than one segment. + test_straight_line(offset); + + // test an offset outer curve + test_offset_curve(offset); + + // test an offset along an S-shaped curve, which is harder + // because the positions along the offset are no longer + // linearly related to the positions along the original + // curve. + test_s_shaped_curve(offset); + } + } + catch (std::exception const& ex) + { + std::cerr << ex.what() << "\n"; + REQUIRE(false); + } +} +} diff --git a/tests/cxx/map_request_test.cpp b/tests/cxx/map_request_test.cpp new file mode 100644 index 000000000..59b6033ac --- /dev/null +++ b/tests/cxx/map_request_test.cpp @@ -0,0 +1,158 @@ +#include "catch.hpp" + +#include +#include +#include +#include +#if defined(HAVE_CAIRO) +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +bool compare_images(std::string const& src_fn,std::string const& dest_fn) +{ + using namespace mapnik; + std::unique_ptr reader1(mapnik::get_image_reader(dest_fn,"png")); + if (!reader1.get()) + { + throw mapnik::image_reader_exception("Failed to load: " + dest_fn); + } + std::shared_ptr image_ptr1 = std::make_shared(reader1->width(),reader1->height()); + reader1->read(0,0,*image_ptr1); + + std::unique_ptr reader2(mapnik::get_image_reader(src_fn,"png")); + if (!reader2.get()) + { + throw mapnik::image_reader_exception("Failed to load: " + src_fn); + } + std::shared_ptr image_ptr2 = std::make_shared(reader2->width(),reader2->height()); + reader2->read(0,0,*image_ptr2); + + image_rgba8 const& dest = *image_ptr1; + image_rgba8 const& src = *image_ptr2; + + unsigned int width = src.width(); + unsigned int height = src.height(); + if ((width != dest.width()) || height != dest.height()) return false; + for (unsigned int y = 0; y < height; ++y) + { + const unsigned int* row_from = src.getRow(y); + const unsigned int* row_to = dest.getRow(y); + for (unsigned int x = 0; x < width; ++x) + { + if (row_from[x] != row_to[x]) return false; + } + } + return true; +} + +TEST_CASE("mapnik::request") { + +SECTION("rendering") { + + std::string expected("./tests/cpp_tests/support/map-request-marker-text-line-expected.png"); + std::string expected_cairo("./tests/cpp_tests/support/map-request-marker-text-line-expected-cairo.png"); + try { + + mapnik::datasource_cache::instance().register_datasources("plugins/input/csv.input"); + mapnik::freetype_engine::register_fonts("./fonts", true ); + mapnik::Map m(256,256); + mapnik::load_map(m,"./tests/data/good_maps/marker-text-line.xml",false); + m.zoom_all(); + mapnik::image_rgba8 im(m.width(),m.height()); + double scale_factor = 1.2; + + // render normally with apply() and just map and image + mapnik::agg_renderer renderer1(m,im,scale_factor); + renderer1.apply(); + std::string actual1("/tmp/map-request-marker-text-line-actual1.png"); + //mapnik::save_to_file(im,expected); + mapnik::save_to_file(im,actual1); + // TODO - re-enable if we can control the freetype/cairo versions used + // https://github.com/mapnik/mapnik/issues/1868 + //REQUIRE(compare_images(actual1,expected)); + + // reset image + mapnik::fill(im, 0); + + // set up a mapnik::request object + mapnik::request req(m.width(),m.height(),m.get_current_extent()); + req.set_buffer_size(m.buffer_size()); + + // render using apply() and mapnik::request + mapnik::attributes vars; + mapnik::agg_renderer renderer2(m,req,vars,im,scale_factor); + renderer2.apply(); + std::string actual2("/tmp/map-request-marker-text-line-actual2.png"); + mapnik::save_to_file(im,actual2); + // TODO - re-enable if we can control the freetype/cairo versions used + // https://github.com/mapnik/mapnik/issues/1868 + //REQUIRE(compare_images(actual2,expected)); + + // reset image + mapnik::fill(im, 0); + + // render with apply_to_layer api and mapnik::request params passed to apply_to_layer + mapnik::agg_renderer renderer3(m,req,vars,im,scale_factor); + renderer3.start_map_processing(m); + mapnik::projection map_proj(m.srs(),true); + double scale_denom = mapnik::scale_denominator(req.scale(),map_proj.is_geographic()); + scale_denom *= scale_factor; + for (mapnik::layer const& lyr : m.layers() ) + { + if (lyr.visible(scale_denom)) + { + std::set names; + renderer3.apply_to_layer(lyr, + renderer3, + map_proj, + req.scale(), + scale_denom, + req.width(), + req.height(), + req.extent(), + req.buffer_size(), + names); + + } + } + renderer3.end_map_processing(m); + std::string actual3("/tmp/map-request-marker-text-line-actual3.png"); + mapnik::save_to_file(im,actual3); + // TODO - re-enable if we can control the freetype/cairo versions used + // https://github.com/mapnik/mapnik/issues/1868 + //REQUIRE(compare_images(actual3,expected)); + + // also test cairo +#if defined(HAVE_CAIRO) + mapnik::cairo_surface_ptr image_surface( + cairo_image_surface_create(CAIRO_FORMAT_ARGB32,req.width(),req.height()), + mapnik::cairo_surface_closer()); + mapnik::cairo_ptr image_context = (mapnik::create_context(image_surface)); + mapnik::cairo_renderer png_render(m,req,vars,image_context,scale_factor); + png_render.apply(); + //cairo_surface_write_to_png(&*image_surface, expected_cairo.c_str()); + std::string actual_cairo("/tmp/map-request-marker-text-line-actual4.png"); + cairo_surface_write_to_png(&*image_surface, actual_cairo.c_str()); + // TODO - re-enable if we can control the freetype/cairo versions used + // https://github.com/mapnik/mapnik/issues/1868 + //REQUIRE(compare_images(actual_cairo,expected_cairo)); +#endif + // TODO - test grid_renderer + + } catch (std::exception const& ex) { + std::clog << ex.what() << "\n"; + } +} +} diff --git a/tests/cxx/params_test.cpp b/tests/cxx/params_test.cpp new file mode 100644 index 000000000..879b86177 --- /dev/null +++ b/tests/cxx/params_test.cpp @@ -0,0 +1,115 @@ +#include "catch.hpp" + +#include +#include +#include +#include +#include +#include + +#include + +namespace detail { + +class string_holder { + public: + string_holder() : + member_("member") {} + std::string const& get_string() const + { + return member_; + } + private: + std::string member_; +}; + +} + +TEST_CASE("parameters") { + +SECTION("get/set") { + + try + { + mapnik::parameters params; + + // true + params["bool"] = mapnik::value_integer(true); + REQUIRE( (params.get("bool") && *params.get("bool") == true)); + + params["bool"] = "true"; + REQUIRE( (params.get("bool") && *params.get("bool") == true)); + + params["bool"] = mapnik::value_integer(1); + REQUIRE( (params.get("bool") && *params.get("bool") == true)); + + params["bool"] = "1"; + REQUIRE( (params.get("bool") && *params.get("bool") == true)); + + params["bool"] = "True"; + REQUIRE( (params.get("bool") && *params.get("bool") == true)); + + params["bool"] = "on"; + REQUIRE( (params.get("bool") && *params.get("bool") == true)); + + params["bool"] = "yes"; + REQUIRE( (params.get("bool") && *params.get("bool") == true)); + + // false + params["bool"] = mapnik::value_integer(false); + REQUIRE( (params.get("bool") && *params.get("bool") == false) ); + + params["bool"] = "false"; + REQUIRE( (params.get("bool") && *params.get("bool") == false) ); + + params["bool"] = mapnik::value_integer(0); + REQUIRE( (params.get("bool") && *params.get("bool") == false)); + + params["bool"] = "0"; + REQUIRE( (params.get("bool") && *params.get("bool") == false)); + + params["bool"] = "False"; + REQUIRE( (params.get("bool") && *params.get("bool") == false)); + + params["bool"] = "off"; + REQUIRE( (params.get("bool") && *params.get("bool") == false)); + + params["bool"] = "no"; + REQUIRE( (params.get("bool") && *params.get("bool") == false)); + + // strings + params["string"] = "hello"; + REQUIRE( (params.get("string") && *params.get("string") == "hello") ); + + // int + params["int"] = mapnik::value_integer(1); + REQUIRE( (params.get("int") && *params.get("int") == 1) ); + + // double + params["double"] = 1.5; + REQUIRE( (params.get("double") && *params.get("double") == 1.5) ); + // value_null + params["null"] = mapnik::value_null(); + // https://github.com/mapnik/mapnik/issues/2471 + //REQUIRE( (params.get("null") && *params.get("null") == mapnik::value_null()) ); + + std::string value("value"); + params["value"] = value; + REQUIRE( (params.get("value") == std::string("value")) ) ; + REQUIRE(value == std::string("value")); + + // ensure that const member is not moved incorrectly when added to params + detail::string_holder holder; + std::string const& holder_member = holder.get_string(); + params["member"] = holder_member; + REQUIRE( (params.get("member") == std::string("member")) ); + REQUIRE( (holder_member == std::string("member")) ); + } + catch (std::exception const& ex) + { + std::cerr << ex.what() << "\n"; + REQUIRE(false); + } + +} +} diff --git a/tests/cxx/simplify_converters_test.cpp b/tests/cxx/simplify_converters_test.cpp new file mode 100644 index 000000000..a5f99b205 --- /dev/null +++ b/tests/cxx/simplify_converters_test.cpp @@ -0,0 +1,82 @@ +#include "catch.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +// stl +#include + +// Convenience method for test cases +void simplify(std::string const& wkt_in, double tolerance, std::string const& method, std::string const& expected) +{ +#if 0 // FIXME + //grab the geom + mapnik::geometry_container multi_input; + if (!mapnik::from_wkt(wkt_in , multi_input)) + { + throw std::runtime_error("Failed to parse WKT"); + } + //setup the generalization + mapnik::vertex_adapter va(multi_input.front()); + mapnik::simplify_converter generalizer(va); + generalizer.set_simplify_algorithm(mapnik::simplify_algorithm_from_string(method).get()); + generalizer.set_simplify_tolerance(tolerance); + //suck the vertices back out of it + mapnik::geometry_type* output = new mapnik::geometry_type(multi_input.front().type()); + mapnik::CommandType cmd; + double x, y; + while ((cmd = (mapnik::CommandType)generalizer.vertex(&x, &y)) != mapnik::SEG_END) + { + output->push_vertex(x, y, cmd); + } + //construct the answer + mapnik::geometry_container multi_out; + multi_out.push_back(output); + std::string wkt_out; + REQUIRE(mapnik::to_wkt(multi_out, wkt_out)); + REQUIRE(wkt_out == expected); +#endif +} + +TEST_CASE("converters") { + +SECTION("simplify") { + + simplify( std::string("LineString(0 0,2 2,3 5,4 1,5 0,6 7,7 0)"), + 4, "douglas-peucker", + std::string("LineString(0 0,6 7,7 0)")); + + simplify( std::string("LineString(0 0,2 2,3 5,4 1,5 0,6 7,7 0)"), + 2, "douglas-peucker", + std::string("LineString(0 0,3 5,5 0,6 7,7 0)")); + + simplify( std::string("LineString(10 0,9 -4,7 -7,4 -9,0 -10,-4 -9,-7 -7,-9 -4,-10 0,-9 4,-7 7,-4 9,0 10,4 9,7 7,9 4)"), + 4, "douglas-peucker", + std::string("LineString(10 0,0 -10,-10 0,0 10,9 4)")); + + simplify( std::string("LineString(0 0,1 1,2 2,0 10,0 0)"), + 10, "douglas-peucker", + std::string("LineString(0 0,0 0)")); + + simplify( std::string("LineString(0 0,1 1,2 2,0 10,0 0)"), + 8, "douglas-peucker", + std::string("LineString(0 0,0 10,0 0)")); + + simplify( std::string("LineString(0 0,1 1,2 2,0 10,0 0)"), + 1, "douglas-peucker", + std::string("LineString(0 0,2 2,0 10,0 0)")); + + simplify( std::string("LineString(0 0, 1 -1, 2 2, 0 -10, 0 0, -5 7, 4 6)"), + 3, "douglas-peucker", + std::string("LineString(0 0,0 -10,-5 7,4 6)")); + +} +} \ No newline at end of file diff --git a/tests/cxx/symbolizer_test.cpp b/tests/cxx/symbolizer_test.cpp new file mode 100644 index 000000000..582613ddf --- /dev/null +++ b/tests/cxx/symbolizer_test.cpp @@ -0,0 +1,30 @@ +#include "catch.hpp" + +#include +#include +#include +#include + +using namespace mapnik; + +TEST_CASE("symbolizer") { + +SECTION("enums") { + + try { + marker_multi_policy_enum policy_in = MARKER_WHOLE_MULTI; + REQUIRE(policy_in == MARKER_WHOLE_MULTI); + markers_symbolizer sym; + put(sym, keys::markers_multipolicy, policy_in); + REQUIRE(sym.properties.count(keys::markers_multipolicy) == static_cast(1)); + marker_multi_policy_enum policy_out = get(sym, keys::markers_multipolicy); + REQUIRE(policy_out == MARKER_WHOLE_MULTI); + } + catch (std::exception const & ex) + { + std::clog << ex.what() << std::endl; + REQUIRE(false); + } + +} +} diff --git a/tests/cxx/wkb_formats_test.cpp b/tests/cxx/wkb_formats_test.cpp new file mode 100644 index 000000000..9e18b287b --- /dev/null +++ b/tests/cxx/wkb_formats_test.cpp @@ -0,0 +1,121 @@ +#include "catch.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +TEST_CASE("geometry formats") { + +SECTION("wkb") { + + unsigned char sp_valid_blob[] = { + 0x0, 0x1, 0xBC, 0xB, 0x0, 0x0, 0x1F, 0x12, 0xDB, 0xCF, 0xC3, 0xA2, 0x41, 0x41, 0x9D, 0x74, 0xB0, 0x31, 0xE6, 0x34, 0x53, 0x41, 0xDB, + 0x1B, 0xB6, 0x7C, 0xD9, 0xA2, 0x41, 0x41, 0x67, 0xA7, 0xB6, 0xF, 0xF6, 0x34, 0x53, 0x41, 0x7C, 0x6, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, + 0x0, 0x69, 0x3, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x12, 0x0, 0x0, 0x0, 0xBB, 0x4B, 0x9C, 0x59, 0xD2, 0xA2, 0x41, 0x41, 0x3A, 0xAA, + 0x3F, 0xAE, 0xEB, 0x34, 0x53, 0x41, 0xA2, 0xC2, 0xE4, 0xC6, 0xD1, 0xA2, 0x41, 0x41, 0x4C, 0xFE, 0x6, 0x2B, 0xEC, 0x34, 0x53, 0x41, + 0xEC, 0x65, 0x5F, 0x6, 0xCE, 0xA2, 0x41, 0x41, 0xDD, 0x33, 0x7F, 0x24, 0xEF, 0x34, 0x53, 0x41, 0x2D, 0x35, 0x2D, 0x30, 0xCB, 0xA2, + 0x41, 0x41, 0x4E, 0xA7, 0x88, 0x9, 0xF1, 0x34, 0x53, 0x41, 0x58, 0x2F, 0x12, 0x96, 0xCA, 0xA2, 0x41, 0x41, 0x52, 0xD1, 0xBD, 0xDC, + 0xF0, 0x34, 0x53, 0x41, 0x1F, 0x12, 0xDB, 0xCF, 0xC3, 0xA2, 0x41, 0x41, 0xB9, 0x31, 0xA4, 0xE1, 0xF5, 0x34, 0x53, 0x41, 0x21, 0xBB, + 0x20, 0x6D, 0xC4, 0xA2, 0x41, 0x41, 0x67, 0xA7, 0xB6, 0xF, 0xF6, 0x34, 0x53, 0x41, 0x5A, 0x82, 0x4A, 0xD3, 0xCA, 0xA2, 0x41, 0x41, + 0xA7, 0x85, 0x3D, 0x58, 0xF1, 0x34, 0x53, 0x41, 0x22, 0xB8, 0x3A, 0x7D, 0xCB, 0xA2, 0x41, 0x41, 0x7D, 0x89, 0xA1, 0x8E, 0xF1, 0x34, + 0x53, 0x41, 0xD0, 0x77, 0x3F, 0x80, 0xCF, 0xA2, 0x41, 0x41, 0x57, 0x69, 0x83, 0xC4, 0xEE, 0x34, 0x53, 0x41, 0xA7, 0xF5, 0x8E, 0xF9, + 0xD1, 0xA2, 0x41, 0x41, 0x9A, 0xA2, 0x31, 0xEE, 0xEC, 0x34, 0x53, 0x41, 0x2A, 0xCD, 0xDE, 0x4C, 0xD4, 0xA2, 0x41, 0x41, 0x11, 0x43, + 0xE1, 0xF7, 0xEA, 0x34, 0x53, 0x41, 0xF, 0x89, 0xB1, 0x66, 0xD5, 0xA2, 0x41, 0x41, 0xC8, 0x5D, 0x86, 0xF1, 0xE9, 0x34, 0x53, 0x41, + 0x19, 0xF4, 0x73, 0x63, 0xD7, 0xA2, 0x41, 0x41, 0x7, 0xB1, 0x14, 0x36, 0xE8, 0x34, 0x53, 0x41, 0xDB, 0x1B, 0xB6, 0x7C, 0xD9, 0xA2, + 0x41, 0x41, 0x98, 0xB5, 0xE0, 0x74, 0xE6, 0x34, 0x53, 0x41, 0xC0, 0x3F, 0xC6, 0xAC, 0xD8, 0xA2, 0x41, 0x41, 0x9D, 0x74, 0xB0, 0x31, + 0xE6, 0x34, 0x53, 0x41, 0xF0, 0xB5, 0xB1, 0x53, 0xD5, 0xA2, 0x41, 0x41, 0x97, 0x47, 0xAD, 0x36, 0xE9, 0x34, 0x53, 0x41, 0xBB, 0x4B, + 0x9C, 0x59, 0xD2, 0xA2, 0x41, 0x41, 0x3A, 0xAA, 0x3F, 0xAE, 0xEB, 0x34, 0x53, 0x41, 0xFE }; + + unsigned char sp_invalid_blob[] = { + 0x0, 0x1, 0xBC, 0xB, 0x0, 0x0, 0x1F, 0x12, 0xDB, 0xCF, 0xC3, 0xA2, 0x41, 0x41, 0x9D, 0x74, 0xB0, 0x31, 0xE6, 0x34, 0x53, 0x41, 0xDB, + 0x1B, 0xB6, 0x7C, 0xD9, 0xA2, 0x41, 0x41, 0x67, 0xA7, 0xB6, 0xF, 0xF6, 0x34, 0x53, 0x41, 0x7C, 0x6, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, + 0x0, 0x69, 0x3, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x12, 0x0, 0x0, 0x0, 0xBB, 0x4B, 0x9C, 0x59, 0xD2, 0xA2, 0x41, 0x41, 0x3A, 0xAA, + 0x3F, 0xAE, 0xEB, 0x34, 0x53, 0x41, 0xA2, 0xC2, 0xE4, 0xC6, 0xD1, 0xA2, 0x41, 0x41, 0x4C, 0xFE, 0x6, 0x2B, 0xEC, 0x34, 0x53, 0x41, + 0xEC, 0x65, 0x5F, 0x6, 0xCE, 0xA2, 0x41, 0x41, 0xDD, 0x33, 0x7F, 0x24, 0xEF, 0x34, 0x53, 0x41, 0x2D, 0x35, 0x2D, 0x30, 0xCB, 0xA2, + 0x41, 0x41, 0x4E, 0xA7, 0x88, 0x9, 0xF1, 0x34, 0x53, 0x41, 0x58, 0x2F, 0x12, 0x96, 0xCA, 0xA2, 0x41, 0x41, 0x52, 0xD1, 0xBD, 0xDC, + 0xF0, 0x34, 0x53, 0x41, 0x1F, 0x12, 0xDB, 0xCF, 0xC3, 0xA2, 0x41, 0x41, 0xB9, 0x31, 0xA4, 0xE1, 0xF5, 0x34, 0x53, 0x41, 0x21, 0xBB, + 0x20, 0x6D, 0xC4, 0xA2, 0x41, 0x41, 0x67, 0xA7, 0xB6, 0xF, 0xF6, 0x34, 0x53, 0x41, 0x5A, 0x82, 0x4A, 0xD3, 0xCA, 0xA2, 0x41, 0x41, + 0xA7, 0x85, 0x3D, 0x58, 0xF1, 0x34, 0x53, 0x41, 0x22, 0xB8, 0x3A, 0x7D, 0xCB, 0xA2, 0x41, 0x41, 0x7D, 0x89, 0xA1, 0x8E, 0xF1, 0x34, + 0x53, 0x41, 0xD0, 0x77, 0x3F, 0x80, 0xCF, 0xA2, 0x41, 0x41, 0x57, 0x69, 0x83, 0xC4, 0xEE, 0x34, 0x53, 0x41, 0xA7, 0xF5, 0x8E, 0xF9, + 0xD1, 0xA2, 0x41, 0x41, 0x9A, 0xA2, 0x31, 0xEE, 0xEC, 0x34, 0x53, 0x41, 0x2A, 0xCD, 0xDE, 0x4C, 0xD4, 0xA2, 0x41, 0x41, 0x11, 0x43, + 0xE1, 0xF7, 0xEA, 0x34, 0x53, 0x41, 0xF, 0x89, 0xB1, 0x66, 0xD5, 0xA2, 0x41, 0x41, 0xC8, 0x5D, 0x86, 0xF1, 0xE9, 0x34, 0x53, 0x41, + 0x19, 0xF4, 0x73, 0x63, 0xD7, 0xA2, 0x41, 0x41, 0x7, 0xB1, 0x14, 0x36, 0xE8, 0x34, 0x53, 0x41, 0xDB, 0x1B, 0xB6, 0x7C, 0xD9, 0xA2, + 0x41, 0x41, 0x98, 0xB5, 0xE0, 0x74, 0xE6, 0x34, 0x53, 0x41, 0xC0, 0x3F, 0xC6, 0xAC, 0xD8, 0xA2, 0x41, 0x41, 0x9D, 0x74, 0xB0, 0x31, + 0xE6, 0x34, 0x53, 0x41, 0xF0, 0xB5, 0xB1, 0x53, 0xD5, 0xA2, 0x41, 0x41, 0x97, 0x47, 0xAD, 0x36, 0xE9, 0x34, 0x53, 0x41, 0xBB, 0x4B, + 0x9C, 0x59, 0xD2, 0xA2, 0x41, 0x41, 0x3A, 0xAA, 0x3F, 0xAE, 0xEB, 0x34, 0x53, 0x41 }; + + unsigned char sq_valid_blob[] = { + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x40 }; + + unsigned char sq_invalid_blob[] = { + 0x23, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x40, 0x23 }; + + mapnik::context_ptr ctx(new mapnik::context_type); + mapnik::feature_ptr feature = mapnik::feature_factory::create(ctx, 1); + + // test of parsing wkb geometries + try { + + // spatialite blob + mapnik::geometry::geometry geom = mapnik::geometry_utils::from_wkb((const char*)sp_valid_blob, + sizeof(sp_valid_blob) / sizeof(sp_valid_blob[0]), + mapnik::wkbSpatiaLite); + // winding order is not correct per OGC so we'll fix it + mapnik::geometry::correct(geom); +#if BOOST_VERSION >= 105600 + REQUIRE(mapnik::geometry::is_valid(geom)); + REQUIRE(mapnik::geometry::is_simple(geom)); +#endif + + geom = mapnik::geometry_utils::from_wkb((const char*)sp_valid_blob, + sizeof(sp_valid_blob) / sizeof(sp_valid_blob[0]), + mapnik::wkbAuto); + mapnik::geometry::correct(geom); +#if BOOST_VERSION >= 105600 + REQUIRE(mapnik::geometry::is_valid(geom)); + REQUIRE(mapnik::geometry::is_simple(geom)); +#endif + + geom = mapnik::geometry_utils::from_wkb((const char*)sp_invalid_blob, + sizeof(sp_invalid_blob) / sizeof(sp_invalid_blob[0]), + mapnik::wkbAuto); + REQUIRE(geom.is()); // returns geometry_empty + + // sqlite generic wkb blob + + geom = mapnik::geometry_utils::from_wkb((const char*)sq_valid_blob, + sizeof(sq_valid_blob) / sizeof(sq_valid_blob[0]), + mapnik::wkbGeneric); +#if BOOST_VERSION >= 105600 + REQUIRE(mapnik::geometry::is_valid(geom)); + REQUIRE(mapnik::geometry::is_simple(geom)); +#endif + + geom = mapnik::geometry_utils::from_wkb( (const char*)sq_valid_blob, + sizeof(sq_valid_blob) / sizeof(sq_valid_blob[0]), + mapnik::wkbAuto); + +#if BOOST_VERSION >= 105600 + REQUIRE(mapnik::geometry::is_valid(geom)); + REQUIRE(mapnik::geometry::is_simple(geom)); +#endif + + geom = mapnik::geometry_utils::from_wkb((const char*)sq_invalid_blob, + sizeof(sq_invalid_blob) / sizeof(sq_invalid_blob[0]), + mapnik::wkbGeneric); + REQUIRE(geom.is()); // returns geometry_empty + + } catch (std::exception const& ex) { + REQUIRE(false); + std::clog << "threw: " << ex.what() << "\n"; + } +} +}