diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e42b0dda..e84eb42f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ For a complete change history, see the SVN log. ## Mapnik 2.1.0 +- GDAL: allow setting nodata value on the fly (will override value if nodata is set in data) (#1161) + +- GDAL: respect nodata for paletted/colormapped images (#1160) + - PostGIS: the primary key, for tables containing one, is now auto-detected allowing for globally unique feature id values (#804) - Fix Markers rendering so that ellipse height/width units are pixels (previously were unintentially radii) diff --git a/plugins/input/gdal/gdal_datasource.cpp b/plugins/input/gdal/gdal_datasource.cpp index 1bdf85ce1..29303bd5e 100644 --- a/plugins/input/gdal/gdal_datasource.cpp +++ b/plugins/input/gdal/gdal_datasource.cpp @@ -78,7 +78,8 @@ inline GDALDataset* gdal_datasource::open_dataset() const gdal_datasource::gdal_datasource(parameters const& params, bool bind) : datasource(params), desc_(*params.get("type"), "utf-8"), - filter_factor_(*params_.get("filter_factor", 0.0)) + filter_factor_(*params_.get("filter_factor", 0.0)), + nodata_value_(params_.get("nodata")) { #ifdef MAPNIK_DEBUG std::clog << "GDAL Plugin: Initializing..." << std::endl; @@ -242,7 +243,8 @@ featureset_ptr gdal_datasource::features(query const& q) const nbands_, dx_, dy_, - filter_factor_)); + filter_factor_, + nodata_value_)); } featureset_ptr gdal_datasource::features_at_point(coord2d const& pt) const @@ -261,5 +263,6 @@ featureset_ptr gdal_datasource::features_at_point(coord2d const& pt) const nbands_, dx_, dy_, - filter_factor_)); + filter_factor_, + nodata_value_)); } diff --git a/plugins/input/gdal/gdal_datasource.hpp b/plugins/input/gdal/gdal_datasource.hpp index 4aa841371..4b2d2ea9d 100644 --- a/plugins/input/gdal/gdal_datasource.hpp +++ b/plugins/input/gdal/gdal_datasource.hpp @@ -57,6 +57,7 @@ private: mutable int nbands_; mutable bool shared_dataset_; double filter_factor_; + boost::optional nodata_value_; inline GDALDataset* open_dataset() const; }; diff --git a/plugins/input/gdal/gdal_featureset.cpp b/plugins/input/gdal/gdal_featureset.cpp index 7546adf3f..14bf11ee5 100644 --- a/plugins/input/gdal/gdal_featureset.cpp +++ b/plugins/input/gdal/gdal_featureset.cpp @@ -53,7 +53,8 @@ gdal_featureset::gdal_featureset(GDALDataset& dataset, int nbands, double dx, double dy, - double filter_factor) + double filter_factor, + boost::optional const& nodata) : dataset_(dataset), ctx_(boost::make_shared()), band_(band), @@ -65,6 +66,7 @@ gdal_featureset::gdal_featureset(GDALDataset& dataset, dy_(dy), nbands_(nbands), filter_factor_(filter_factor), + nodata_value_(nodata), first_(true) { ctx_->push("value"); @@ -241,8 +243,17 @@ feature_ptr gdal_featureset::get_feature(mapnik::query const& q) float* imageData = (float*)image.getBytes(); GDALRasterBand * band = dataset_.GetRasterBand(band_); - int hasNoData; - double nodata = band->GetNoDataValue(&hasNoData); + int hasNoData(0); + double nodata(0); + if (nodata_value_) + { + hasNoData = 1; + nodata = *nodata_value_; + } + else + { + nodata = band->GetNoDataValue(&hasNoData); + } band->RasterIO(GF_Read, x_off, y_off, width, height, imageData, image.width(), image.height(), GDT_Float32, 0, 0); @@ -340,8 +351,21 @@ feature_ptr gdal_featureset::get_feature(mapnik::query const& q) #ifdef MAPNIK_DEBUG std::clog << "GDAL Plugin: processing rgb bands..." << std::endl; #endif - int hasNoData; - float nodata = red->GetNoDataValue(&hasNoData); + int hasNoData(0); + double nodata(0); + if (nodata_value_) + { + hasNoData = 1; + nodata = *nodata_value_; + } + else + { + nodata = red->GetNoDataValue(&hasNoData); + } + if (hasNoData) + { + feature->put("NODATA",nodata); + } GDALColorTable *color_table = red->GetColorTable(); if (! alpha && hasNoData && ! color_table) @@ -380,12 +404,25 @@ feature_ptr gdal_featureset::get_feature(mapnik::query const& q) #ifdef MAPNIK_DEBUG std::clog << "GDAL Plugin: processing gray band..." << std::endl; #endif - int hasNoData; - float nodata = grey->GetNoDataValue(&hasNoData); + int hasNoData(0); + double nodata(0); + if (nodata_value_) + { + hasNoData = 1; + nodata = *nodata_value_; + } + else + { + nodata = grey->GetNoDataValue(&hasNoData); + } GDALColorTable* color_table = grey->GetColorTable(); if (hasNoData && ! color_table) { + if (hasNoData) + { + feature->put("NODATA",nodata); + } #ifdef MAPNIK_DEBUG std::clog << "\tno data value for layer: " << nodata << std::endl; #endif @@ -422,23 +459,41 @@ feature_ptr gdal_featureset::get_feature(mapnik::query const& q) #ifdef MAPNIK_DEBUG std::clog << "GDAL Plugin: Loading colour table..." << std::endl; #endif + unsigned nodata_value = static_cast(nodata); + if (hasNoData) + { + feature->put("NODATA",static_cast(nodata_value)); + } for (unsigned y = 0; y < image.height(); ++y) { unsigned int* row = image.getRow(y); for (unsigned x = 0; x < image.width(); ++x) { unsigned value = row[x] & 0xff; - const GDALColorEntry *ce = color_table->GetColorEntry(value); - if (ce ) + if (hasNoData && (value == nodata_value)) { - // TODO - big endian support - row[x] = (ce->c4 << 24)| (ce->c3 << 16) | (ce->c2 << 8) | (ce->c1) ; + // make no data fully alpha + row[x] = 0; + } + else + { + const GDALColorEntry *ce = color_table->GetColorEntry(value); + if (ce) + { + // TODO - big endian support + row[x] = (ce->c4 << 24)| (ce->c3 << 16) | (ce->c2 << 8) | (ce->c1) ; + } + else + { + // make lacking color entry fully alpha + // note - gdal_translate makes black + row[x] = 0; + } } } } } } - if (alpha) { #ifdef MAPNIK_DEBUG diff --git a/plugins/input/gdal/gdal_featureset.hpp b/plugins/input/gdal/gdal_featureset.hpp index d4a2e9db8..b812da815 100644 --- a/plugins/input/gdal/gdal_featureset.hpp +++ b/plugins/input/gdal/gdal_featureset.hpp @@ -29,6 +29,7 @@ // boost #include +#include class GDALDataset; class GDALRasterBand; @@ -47,7 +48,8 @@ public: int nbands, double dx, double dy, - double filter_factor); + double filter_factor, + boost::optional const& nodata); virtual ~gdal_featureset(); mapnik::feature_ptr next(); private: @@ -67,6 +69,7 @@ private: double dy_; int nbands_; double filter_factor_; + boost::optional nodata_value_; bool first_; }; diff --git a/tests/data/good_maps/tiff_colortable.xml b/tests/data/good_maps/tiff_colortable.xml new file mode 100644 index 000000000..c102ff363 --- /dev/null +++ b/tests/data/good_maps/tiff_colortable.xml @@ -0,0 +1,16 @@ + + + + + 2011_5km_vrt_nodata_style + + ../raster/dataraster.tif + 20 + gdal + + + \ No newline at end of file diff --git a/tests/data/good_maps/vrt_colortable.xml b/tests/data/good_maps/vrt_colortable.xml new file mode 100644 index 000000000..d20367c72 --- /dev/null +++ b/tests/data/good_maps/vrt_colortable.xml @@ -0,0 +1,15 @@ + + + + + 2011_5km_vrt_nodata_style + + ../raster/dataraster.vrt + gdal + + + \ No newline at end of file diff --git a/tests/data/raster/dataraster.vrt b/tests/data/raster/dataraster.vrt new file mode 100644 index 000000000..26f6b5cfe --- /dev/null +++ b/tests/data/raster/dataraster.vrt @@ -0,0 +1,45 @@ + + PROJCS["WGS 84 / UTM zone 30N",GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433],AUTHORITY["EPSG","4326"]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",-3],PARAMETER["scale_factor",0.9996],PARAMETER["false_easting",500000],PARAMETER["false_northing",0],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AUTHORITY["EPSG","32630"]] + -1.4637000000000000e+04, 5.0000000000000000e+02, 0.0000000000000000e+00, 4.8596780000000000e+06, 0.0000000000000000e+00, -5.0000000000000000e+02 + + -9.99000000000000E+02 + 0 + Palette + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dataraster.tif + 1 + + + + -999 + + + diff --git a/tests/python_tests/images/support/tif_colortable.png b/tests/python_tests/images/support/tif_colortable.png new file mode 100644 index 000000000..aa0021b38 Binary files /dev/null and b/tests/python_tests/images/support/tif_colortable.png differ diff --git a/tests/python_tests/images/support/vrt_colortable.png b/tests/python_tests/images/support/vrt_colortable.png new file mode 100644 index 000000000..48afae7fb Binary files /dev/null and b/tests/python_tests/images/support/vrt_colortable.png differ diff --git a/tests/python_tests/raster_colormapped_test.py b/tests/python_tests/raster_colormapped_test.py new file mode 100644 index 000000000..c700cd5f4 --- /dev/null +++ b/tests/python_tests/raster_colormapped_test.py @@ -0,0 +1,38 @@ +#coding=utf8 +import os +import mapnik +from utilities import execution_path +from nose.tools import * + +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('.')) + +def test_vrt_rendering(): + m = mapnik.Map(512,512) + mapnik.load_map(m,'../data/good_maps/vrt_colortable.xml') + m.zoom_all() + im = mapnik.Image(512,512) + mapnik.render(m,im) + actual = '/tmp/vrt_colortable.png' + expected = 'images/support/vrt_colortable.png' + im.save(actual) + expected_im = mapnik.Image.open(expected) + eq_(im.tostring(),expected_im.tostring(), 'failed comparing actual (%s) and expected(%s)' % (actual,'tests/python_tests/'+ expected)) + +def test_tif_rendering_nodata(): + m = mapnik.Map(512,512) + mapnik.load_map(m,'../data/good_maps/tiff_colortable.xml') + m.zoom_all() + im = mapnik.Image(512,512) + mapnik.render(m,im) + actual = '/tmp/tif_colortable.png' + expected = 'images/support/tif_colortable.png' + im.save(actual) + expected_im = mapnik.Image.open(expected) + eq_(im.tostring(),expected_im.tostring(), 'failed comparing actual (%s) and expected(%s)' % (actual,'tests/python_tests/'+ expected)) + +if __name__ == "__main__": + setup() + [eval(run)() for run in dir() if 'test_' in run]