Merge branch 'master' into mapnik-geometry

This commit is contained in:
artemp 2015-03-09 14:59:45 +01:00
commit 317e9c497f
21 changed files with 251 additions and 77 deletions

View file

@ -52,6 +52,7 @@ Mapnik is written by Artem Pavlenko with contributions from:
* Igor Podolskiy
* Reid Priedhorsky
* Brian Quinion
* Even Rouault
* Marcin Rudowski
* Sandro Santilli
* Christopher Schmidt

View file

@ -1283,6 +1283,7 @@ if not preconfigured:
else:
env['MISSING_DEPS'].append('libxml2')
if not env['HOST']:
if conf.CheckHasDlfcn():
env.Append(CPPDEFINES = '-DMAPNIK_HAS_DLCFN')
else:
@ -1340,7 +1341,7 @@ if not preconfigured:
conf.prioritize_paths(silent=True)
# test for C++11 support, which is required
if not conf.supports_cxx11():
if not env['HOST'] and not conf.supports_cxx11():
color_print(1,"C++ compiler does not support C++11 standard (-std=c++11), which is required. Please upgrade your compiler to at least g++ 4.7 (ideally 4.8)")
Exit(1)
@ -1361,7 +1362,7 @@ if not preconfigured:
env['MISSING_DEPS'].append(env['ICU_LIB_NAME'])
elif libname == 'harfbuzz':
if not conf.harfbuzz_version():
env['MISSING_DEPS'].append('harfbuzz-min-version')
env['SKIPPED_DEPS'].append('harfbuzz-min-version')
if env['BIGINT']:
env.Append(CPPDEFINES = '-DBIGINT')

View file

@ -222,6 +222,11 @@ void set_pixel_int(mapnik::image_any & im, unsigned x, unsigned y, int val)
mapnik::set_pixel(im, x, y, val);
}
unsigned get_type(mapnik::image_any & im)
{
return im.get_dtype();
}
std::shared_ptr<image_any> open_from_file(std::string const& filename)
{
boost::optional<std::string> type = type_from_filename(filename);
@ -441,6 +446,7 @@ void export_image()
arg("y"),
arg("get_color")=false
))
.def("get_type",&get_type)
.def("clear",&clear)
//TODO(haoyu) The method name 'tostring' might be confusing since they actually return bytes in Python 3

View file

@ -124,6 +124,7 @@ class image
public:
using pixel = T;
using pixel_type = typename T::type;
static const image_dtype dtype = T::id;
static constexpr std::size_t pixel_size = sizeof(pixel_type);
private:
detail::image_dimensions<max_size> dimensions_;
@ -313,6 +314,11 @@ public:
{
return painted_;
}
inline image_dtype get_dtype() const
{
return dtype;
}
};
using image_rgba8 = image<rgba8_t>;
@ -327,22 +333,6 @@ using image_gray64 = image<gray64_t>;
using image_gray64s = image<gray64s_t>;
using image_gray64f = image<gray64f_t>;
enum image_dtype : std::uint8_t
{
image_dtype_rgba8 = 0,
image_dtype_gray8,
image_dtype_gray8s,
image_dtype_gray16,
image_dtype_gray16s,
image_dtype_gray32,
image_dtype_gray32s,
image_dtype_gray32f,
image_dtype_gray64,
image_dtype_gray64s,
image_dtype_gray64f,
image_dtype_null
};
} // end ns
#endif // MAPNIK_IMAGE_DATA_HPP

View file

@ -31,6 +31,7 @@ namespace mapnik {
struct image_null
{
using pixel_type = uint8_t;
static const image_dtype dtype = image_dtype_null;
unsigned char const* getBytes() const { return nullptr; }
unsigned char* getBytes() { return nullptr;}
unsigned getSize() const { return 0; }
@ -40,6 +41,7 @@ struct image_null
bool painted() const { return false; }
double get_offset() const { return 0.0; }
void set_offset(double) {}
image_dtype get_dtype() const { return dtype; }
double get_scaling() const { return 1.0; }
void set_scaling(double) {}
bool get_premultiplied() const { return false; }
@ -88,6 +90,15 @@ struct get_bytes_visitor
}
};
struct get_dtype_visitor
{
template <typename T>
image_dtype operator()(T & data)
{
return data.get_dtype();
}
};
struct get_bytes_visitor_const
{
template <typename T>
@ -263,6 +274,11 @@ struct image_any : image_base
return util::apply_visitor(detail::get_scaling_visitor(),*this);
}
image_dtype get_dtype() const
{
return util::apply_visitor(detail::get_dtype_visitor(),*this);
}
void set_offset(double val)
{
util::apply_visitor(detail::set_offset_visitor(val),*this);
@ -306,6 +322,7 @@ inline image_any create_image_any(int width,
case image_dtype_null:
return image_any(std::move(image_null()));
case image_dtype_rgba8:
case IMAGE_DTYPE_MAX:
default:
return image_any(std::move(image_rgba8(width, height, initialize, premultiplied, painted)));
}

View file

@ -33,6 +33,7 @@ class image_view
public:
using pixel = typename T::pixel;
using pixel_type = typename T::pixel_type;
static const image_dtype dtype = T::dtype;
static constexpr std::size_t pixel_size = sizeof(pixel_type);
image_view(unsigned x, unsigned y, unsigned width, unsigned height, T const& data)
@ -42,10 +43,10 @@ public:
height_(height),
data_(data)
{
if (x_ >= data_.width()) x_=data_.width()-1;
if (y_ >= data_.height()) y_=data_.height()-1;
if (x_ + width_ > data_.width()) width_= data_.width() - x_;
if (y_ + height_ > data_.height()) height_= data_.height() - y_;
if (x_ >= data_.width() && data_.width() > 0) x_ = data_.width() - 1;
if (y_ >= data_.height() && data.height() > 0) y_ = data_.height() - 1;
if (x_ + width_ > data_.width()) width_ = data_.width() - x_;
if (y_ + height_ > data_.height()) height_ = data_.height() - y_;
}
~image_view() {}
@ -132,6 +133,11 @@ public:
return data_.get_scaling();
}
inline image_dtype get_dtype() const
{
return dtype;
}
private:
unsigned x_;
unsigned y_;

View file

@ -69,6 +69,15 @@ struct get_view_size_visitor
}
};
struct get_view_dtype_visitor
{
template <typename T>
image_dtype operator()(T const& data) const
{
return data.get_dtype();
}
};
struct get_view_row_size_visitor
{
template <typename T>
@ -148,6 +157,11 @@ struct image_view_any : image_view_base
{
return util::apply_visitor(detail::get_view_scaling_visitor(),*this);
}
image_dtype get_dtype() const
{
return util::apply_visitor(detail::get_view_dtype_visitor(),*this);
}
};
}

View file

@ -25,16 +25,36 @@
#include <mapnik/global.hpp>
struct rgba8_t { using type = std::uint32_t; };
struct gray8_t { using type = std::uint8_t; };
struct gray8s_t { using type = std::int8_t; };
struct gray16_t { using type = std::uint16_t; };
struct gray16s_t { using type = std::int16_t; };
struct gray32_t { using type = std::uint32_t; };
struct gray32s_t { using type = std::int32_t; };
struct gray32f_t { using type = float; };
struct gray64_t { using type = std::uint64_t; };
struct gray64s_t { using type = std::int64_t; };
struct gray64f_t { using type = double; };
namespace mapnik {
enum image_dtype : std::uint8_t
{
image_dtype_rgba8 = 0,
image_dtype_gray8,
image_dtype_gray8s,
image_dtype_gray16,
image_dtype_gray16s,
image_dtype_gray32,
image_dtype_gray32s,
image_dtype_gray32f,
image_dtype_gray64,
image_dtype_gray64s,
image_dtype_gray64f,
image_dtype_null,
IMAGE_DTYPE_MAX
};
struct rgba8_t { using type = std::uint32_t; static const image_dtype id = image_dtype_rgba8; };
struct gray8_t { using type = std::uint8_t; static const image_dtype id = image_dtype_gray8; };
struct gray8s_t { using type = std::int8_t; static const image_dtype id = image_dtype_gray8s; };
struct gray16_t { using type = std::uint16_t; static const image_dtype id = image_dtype_gray16; };
struct gray16s_t { using type = std::int16_t; static const image_dtype id = image_dtype_gray16s; };
struct gray32_t { using type = std::uint32_t; static const image_dtype id = image_dtype_gray32; };
struct gray32s_t { using type = std::int32_t; static const image_dtype id = image_dtype_gray32s; };
struct gray32f_t { using type = float; static const image_dtype id = image_dtype_gray32f; };
struct gray64_t { using type = std::uint64_t; static const image_dtype id = image_dtype_gray64; };
struct gray64s_t { using type = std::int64_t; static const image_dtype id = image_dtype_gray64s; };
struct gray64f_t { using type = double; static const image_dtype id = image_dtype_gray64f; };
} // end ns
#endif // MAPNIK_PIXEL_TYPES_HPP

View file

@ -176,18 +176,19 @@ geojson_datasource::geojson_datasource(parameters const& params)
namespace {
using base_iterator_type = char const*;
const mapnik::transcoder tr("utf8");
const mapnik::json::feature_collection_grammar<base_iterator_type,mapnik::feature_impl> fc_grammar(tr);
const mapnik::transcoder geojson_datasource_static_tr("utf8");
const mapnik::json::feature_collection_grammar<base_iterator_type,mapnik::feature_impl> geojson_datasource_static_fc_grammar(geojson_datasource_static_tr);
const mapnik::json::feature_grammar<base_iterator_type, mapnik::feature_impl> geojson_datasource_static_feature_grammar(geojson_datasource_static_tr);
const mapnik::json::extract_bounding_box_grammar<base_iterator_type> geojson_datasource_static_bbox_grammar;
}
template <typename Iterator>
void geojson_datasource::initialise_index(Iterator start, Iterator end)
{
mapnik::json::boxes boxes;
mapnik::json::extract_bounding_box_grammar<Iterator> bbox_grammar;
boost::spirit::ascii::space_type space;
Iterator itr = start;
if (!boost::spirit::qi::phrase_parse(itr, end, (bbox_grammar)(boost::phoenix::ref(boxes)) , space))
if (!boost::spirit::qi::phrase_parse(itr, end, (geojson_datasource_static_bbox_grammar)(boost::phoenix::ref(boxes)) , space))
{
throw mapnik::datasource_exception("GeoJSON Plugin: could not parse: '" + filename_ + "'");
}
@ -207,10 +208,8 @@ void geojson_datasource::initialise_index(Iterator start, Iterator end)
Iterator end = itr + geometry_index.second;
mapnik::context_ptr ctx = std::make_shared<mapnik::context_type>();
mapnik::feature_ptr feature(mapnik::feature_factory::create(ctx,1));
static const mapnik::transcoder tr("utf8");
static const mapnik::json::feature_grammar<Iterator, mapnik::feature_impl> grammar(tr);
boost::spirit::ascii::space_type space;
if (!boost::spirit::qi::phrase_parse(itr, end, (grammar)(boost::phoenix::ref(*feature)), space))
if (!boost::spirit::qi::phrase_parse(itr, end, (geojson_datasource_static_feature_grammar)(boost::phoenix::ref(*feature)), space))
{
throw std::runtime_error("Failed to parse geojson feature");
}
@ -237,7 +236,7 @@ void geojson_datasource::parse_geojson(Iterator start, Iterator end)
mapnik::json::default_feature_callback callback(features_);
bool result = boost::spirit::qi::phrase_parse(start, end, (fc_grammar)
bool result = boost::spirit::qi::phrase_parse(start, end, (geojson_datasource_static_fc_grammar)
(boost::phoenix::ref(ctx),boost::phoenix::ref(start_id), boost::phoenix::ref(callback)),
space);
if (!result)

View file

@ -58,7 +58,7 @@ public:
{
std::string err_msg = "Postgis Plugin: ";
err_msg += status();
err_msg += "\nConnection string: '";
err_msg += "Connection string: '";
err_msg += connection_str;
err_msg += "'\n";
MAPNIK_LOG_DEBUG(postgis) << "postgis_connection: creation failed, closing connection - " << this;
@ -71,7 +71,7 @@ public:
if ( ! ok ) {
std::string err_msg = "Postgis Plugin: ";
err_msg += status();
err_msg += "\nConnection string: '";
err_msg += "Connection string: '";
err_msg += connection_str;
err_msg += "'\n";
close();
@ -127,7 +127,7 @@ public:
{
std::string err_msg = "Postgis Plugin: ";
err_msg += status();
err_msg += "\nin executeQuery Full sql was: '";
err_msg += "in executeQuery Full sql was: '";
err_msg += sql;
err_msg += "'\n";
if ( result ) PQclear(result);
@ -142,12 +142,19 @@ public:
std::string status;
if (conn_)
{
if ( isOK() ) return PQerrorMessage(conn_);
else return "Bad connection";
char * err_msg = PQerrorMessage(conn_);
if (err_msg == nullptr)
{
status = "Bad connection\n";
}
else
{
status = "Uninitialized connection";
status = std::string(err_msg);
}
}
else
{
status = "Uninitialized connection\n";
}
return status;
}
@ -167,7 +174,7 @@ public:
{
std::string err_msg = "Postgis Plugin: ";
err_msg += status();
err_msg += "\nin executeAsyncQuery Full sql was: '";
err_msg += "in executeAsyncQuery Full sql was: '";
err_msg += sql;
err_msg += "'\n";
clearAsyncResult(PQgetResult(conn_));
@ -191,7 +198,7 @@ public:
{
std::string err_msg = "Postgis Plugin: ";
err_msg += status();
err_msg += "\nin getNextAsyncResult";
err_msg += "in getNextAsyncResult";
clearAsyncResult(result);
// We need to guarde against losing the connection
// (i.e db restart) so here we invalidate the full connection
@ -208,7 +215,7 @@ public:
{
std::string err_msg = "Postgis Plugin: ";
err_msg += status();
err_msg += "\nin getAsyncResult";
err_msg += "in getAsyncResult";
clearAsyncResult(result);
// We need to be guarded against losing the connection
// (i.e db restart), we invalidate the full connection

View file

@ -159,6 +159,10 @@ postgis_datasource::postgis_datasource(parameters const& params)
geometry_table_ = geometry_table_.substr(0);
}
// NOTE: geometry_table_ how should ideally be a table name, but
// there are known edge cases where this will break down and
// geometry_table_ may even be empty: https://github.com/mapnik/mapnik/issues/2718
// If we do not know both the geometry_field and the srid
// then first attempt to fetch the geometry name from a geometry_columns entry.
// This will return no records if we are querying a bogus table returned
@ -166,7 +170,7 @@ postgis_datasource::postgis_datasource(parameters const& params)
// the table parameter references a table, view, or subselect not
// registered in the geometry columns.
geometryColumn_ = geometry_field_;
if (geometryColumn_.empty() || srid_ == 0)
if (!geometry_table_.empty() && (geometryColumn_.empty() || srid_ == 0))
{
#ifdef MAPNIK_STATS
mapnik::progress_timer __stats2__(std::clog, "postgis_datasource::init(get_srid_and_geometry_column)");
@ -223,12 +227,20 @@ postgis_datasource::postgis_datasource(parameters const& params)
// If we still do not know the srid then we can try to fetch
// it from the 'geometry_table_' parameter, which should work even if it is
// a subselect as long as we know the geometry_field to query
if (! geometryColumn_.empty() && srid_ <= 0)
if (!geometryColumn_.empty() && srid_ <= 0)
{
std::ostringstream s;
s << "SELECT ST_SRID(\"" << geometryColumn_ << "\") AS srid FROM "
<< populate_tokens(geometry_table_) << " WHERE \"" << geometryColumn_ << "\" IS NOT NULL LIMIT 1;";
s << "SELECT ST_SRID(\"" << geometryColumn_ << "\") AS srid FROM ";
if (!geometry_table_.empty())
{
s << geometry_table_;
}
else
{
s << populate_tokens(table_);
}
s << " WHERE \"" << geometryColumn_ << "\" IS NOT NULL LIMIT 1;";
shared_ptr<ResultSet> rs = conn->executeQuery(s.str());
if (rs->next())

View file

@ -251,7 +251,7 @@ if env['PLUGIN_LINKING'] == 'static':
lib_env.AppendUnique(CPPPATH='../plugins/')
for plugin in env['REQUESTED_PLUGINS']:
details = env['PLUGINS'][plugin]
if details['lib'] in env['LIBS'] or not details['lib']:
if not details['lib'] or details['lib'] in env['LIBS']:
plugin_env = SConscript('../plugins/input/%s/build.py' % plugin)
if not plugin_env:
print("Notice: no 'plugin_env' variable found for plugin: '%s'" % plugin)

View file

@ -40,8 +40,24 @@ expression_ptr parse_expression(std::string const& str)
auto node = std::make_shared<expr_node>();
std::string::const_iterator itr = str.begin();
std::string::const_iterator end = str.end();
try {
bool r = boost::spirit::qi::phrase_parse(itr, end, g, space, *node);
bool r = false;
try
{
r = boost::spirit::qi::phrase_parse(itr, end, g, space, *node);
}
catch (std::exception const& ex)
{
if (std::string("boost::spirit::qi::expectation_failure") == std::string(ex.what()))
{
// no need to show "boost::spirit::qi::expectation_failure" which is a std::runtime_error
throw config_error("Failed to parse expression: \"" + str + "\"");
}
else
{
// show "Could not initialize ICU resources" from boost::regex which is a std::runtime_error
throw config_error(std::string(ex.what()) + " for expression: \"" + str + "\"");
}
}
if (r && itr == end)
{
return node;
@ -50,11 +66,6 @@ expression_ptr parse_expression(std::string const& str)
{
throw config_error("Failed to parse expression: \"" + str + "\"");
}
}
catch (std::exception const&) // boost::spirit::qi::expectation_failure
{
throw config_error("Failed to parse expression: \"" + str + "\"");
}
}

View file

@ -93,9 +93,19 @@ struct visitor_image_copy_so
}
T0 operator() (T0 const& src)
{
if (offset_ == src.get_offset() && scaling_ == src.get_scaling())
{
return T0(src);
}
else
{
T0 dst(src);
dst.set_scaling(scaling_);
dst.set_offset(offset_);
return T0(std::move(dst));
}
}
template <typename T1>
T0 operator() (T1 const& src)
@ -340,6 +350,10 @@ MAPNIK_DECL image_any image_copy(image_any const& data, image_dtype type, double
return image_any(std::move(image_copy<image_gray64f>(data, offset, scaling)));
case image_dtype_null:
throw std::runtime_error("Can not cast a null image");
case IMAGE_DTYPE_MAX:
default:
throw std::runtime_error("Can not cast unknown type");
}
throw std::runtime_error("Unknown image type passed");
}

View file

@ -46,11 +46,24 @@ jpeg_saver::jpeg_saver(std::ostream & stream, std::string const& t):
stream_(stream), t_(t) {}
template <typename T>
void process_rgba8_jpeg(T const& image, std::string const& t, std::ostream & stream)
void process_rgba8_jpeg(T const& image, std::string const& type, std::ostream & stream)
{
#if defined(HAVE_JPEG)
int quality = 85;
std::string val = t.substr(4);
//std::string val = type.substr(4);
if (type != "jpeg")
{
boost::char_separator<char> sep(":");
boost::tokenizer< boost::char_separator<char> > tokens(type, sep);
for (auto const& t : tokens)
{
if (t == "jpeg")
{
continue;
}
else if (boost::algorithm::starts_with(t, "quality="))
{
std::string val = t.substr(8);
if (!val.empty())
{
if (!mapnik::util::string2int(val,quality) || quality < 0 || quality > 100)
@ -58,6 +71,9 @@ void process_rgba8_jpeg(T const& image, std::string const& t, std::ostream & str
throw ImageWriterException("invalid jpeg quality: '" + val + "'");
}
}
}
}
}
save_as_jpeg(stream, quality, image);
#else
throw ImageWriterException("jpeg output is not enabled in your build of Mapnik");

View file

@ -3,6 +3,12 @@
"objects": {
"escaped": {
"type": "GeometryCollection",
"bbox": [
-180,
-90,
180,
90
],
"geometries": [
{
"type": "Point",

View file

@ -10,6 +10,12 @@ def setup():
# from another directory we need to chdir()
os.chdir(execution_path('.'))
def test_type():
im = mapnik.Image(256, 256)
eq_(im.get_type(), mapnik.ImageType.rgba8)
im = mapnik.Image(256, 256, mapnik.ImageType.gray8)
eq_(im.get_type(), mapnik.ImageType.gray8)
def test_image_premultiply():
im = mapnik.Image(256,256)
eq_(im.premultiplied(),False)

View file

@ -53,7 +53,7 @@ def test_can_parse_xml_with_deprecated_properties():
except RuntimeError, e:
# only test datasources that we have installed
if not 'Could not create datasource' in str(e) \
and not 'Bad connection' in str(e):
and not 'could not connect' in str(e):
failures.append('Failed to load valid map %s (%s)' % (filename,e))
eq_(len(failures),0,'\n'+'\n'.join(failures))
mapnik.logger.set_severity(default_logging_severity)
@ -73,7 +73,7 @@ def test_good_files():
except RuntimeError, e:
# only test datasources that we have installed
if not 'Could not create datasource' in str(e) \
and not 'Bad connection' in str(e):
and not 'could not connect' in str(e):
failures.append('Failed to load valid map %s (%s)' % (filename,e))
eq_(len(failures),0,'\n'+'\n'.join(failures))

View file

@ -283,6 +283,16 @@ if 'postgis' in mapnik.DatasourceCache.plugin_names() \
eq_(meta['encoding'],u'UTF8')
eq_(meta['geometry_type'],mapnik.DataGeometryType.Polygon)
def test_bad_connection():
try:
ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,
table='test',
max_size=20,
geometry_field='geom',
user="rolethatdoesnotexist")
except Exception, e:
assert 'role "rolethatdoesnotexist" does not exist' in str(e)
def test_empty_db():
ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,table='empty')
fs = ds.featureset()
@ -844,7 +854,8 @@ if 'postgis' in mapnik.DatasourceCache.plugin_names() \
fs = ds_bad.featureset()
for feature in fs:
pass
except RuntimeError:
except RuntimeError, e:
assert 'invalid input syntax for integer' in str(e)
failed = True
eq_(failed,True)
@ -907,7 +918,8 @@ if 'postgis' in mapnik.DatasourceCache.plugin_names() \
mapnik.render_to_file(map1,'/tmp/mapnik-postgis-test-map1.png', 'png')
# Test must fail if error was not raised just above
eq_(False,True)
except RuntimeError:
except RuntimeError, e:
assert 'invalid input syntax for integer' in str(e)
pass
# This used to raise an exception before correction of issue 2042
mapnik.render_to_file(map2,'/tmp/mapnik-postgis-test-map2.png', 'png')
@ -1158,6 +1170,42 @@ if 'postgis' in mapnik.DatasourceCache.plugin_names() \
eq_(meta.get('key_field'),"gid")
eq_(meta['geometry_type'],None)
# currently needs manual `geometry_table` passed
# to avoid misparse of `geometry_table`
# in the future ideally this would not need manual `geometry_table`
# https://github.com/mapnik/mapnik/issues/2718
# currently `bogus` would be picked automatically for geometry_table
def test_broken_parsing_of_comments():
ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,table='''
(select * FROM test) AS data
-- select this from bogus''',
geometry_table='test')
fs = ds.featureset()
for id in range(1,5):
eq_(fs.next().id(),id)
meta = ds.describe()
eq_(meta['srid'],4326)
eq_(meta['geometry_type'],mapnik.DataGeometryType.Collection)
# same
# to avoid misparse of `geometry_table`
# in the future ideally this would not need manual `geometry_table`
# https://github.com/mapnik/mapnik/issues/2718
# currently nothing would be picked automatically for geometry_table
def test_broken_parsing_of_comments():
ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,table='''
(select * FROM test) AS data
-- select this from bogus.''',
geometry_table='test')
fs = ds.featureset()
for id in range(1,5):
eq_(fs.next().id(),id)
meta = ds.describe()
eq_(meta['srid'],4326)
eq_(meta['geometry_type'],mapnik.DataGeometryType.Collection)
atexit.register(postgis_takedown)

View file

@ -27,7 +27,7 @@ def compare_map(xml):
except RuntimeError, e:
# only test datasources that we have installed
if not 'Could not create datasource' in str(e) \
and not 'Bad connection' in str(e):
and not 'could not connect' in str(e):
raise RuntimeError(str(e))
return
(handle, test_map) = tempfile.mkstemp(suffix='.xml', prefix='mapnik-temp-map1-')

View file

@ -214,7 +214,7 @@ def render(filename, config, scale_factor, reporting):
return
except Exception, e:
if 'Could not create datasource' in str(e) \
or 'Bad connection' in str(e):
or 'could not connect' in str(e):
return m
reporting.other_error(filename, repr(e))
return m