support nodata for paletted images and allow user to set nodata on-the-fly - closes #1160 and #1161 - refs #688 and refs #730 and refs #50 and refs #1018

This commit is contained in:
Dane Springmeyer 2012-04-03 16:06:36 -07:00
parent a7e150a593
commit 46b16c917e
11 changed files with 196 additions and 16 deletions

View file

@ -9,6 +9,10 @@ For a complete change history, see the SVN log.
## Mapnik 2.1.0 ## 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) - 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) - Fix Markers rendering so that ellipse height/width units are pixels (previously were unintentially radii)

View file

@ -78,7 +78,8 @@ inline GDALDataset* gdal_datasource::open_dataset() const
gdal_datasource::gdal_datasource(parameters const& params, bool bind) gdal_datasource::gdal_datasource(parameters const& params, bool bind)
: datasource(params), : datasource(params),
desc_(*params.get<std::string>("type"), "utf-8"), desc_(*params.get<std::string>("type"), "utf-8"),
filter_factor_(*params_.get<double>("filter_factor", 0.0)) filter_factor_(*params_.get<double>("filter_factor", 0.0)),
nodata_value_(params_.get<double>("nodata"))
{ {
#ifdef MAPNIK_DEBUG #ifdef MAPNIK_DEBUG
std::clog << "GDAL Plugin: Initializing..." << std::endl; std::clog << "GDAL Plugin: Initializing..." << std::endl;
@ -242,7 +243,8 @@ featureset_ptr gdal_datasource::features(query const& q) const
nbands_, nbands_,
dx_, dx_,
dy_, dy_,
filter_factor_)); filter_factor_,
nodata_value_));
} }
featureset_ptr gdal_datasource::features_at_point(coord2d const& pt) const 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_, nbands_,
dx_, dx_,
dy_, dy_,
filter_factor_)); filter_factor_,
nodata_value_));
} }

View file

@ -57,6 +57,7 @@ private:
mutable int nbands_; mutable int nbands_;
mutable bool shared_dataset_; mutable bool shared_dataset_;
double filter_factor_; double filter_factor_;
boost::optional<double> nodata_value_;
inline GDALDataset* open_dataset() const; inline GDALDataset* open_dataset() const;
}; };

View file

@ -53,7 +53,8 @@ gdal_featureset::gdal_featureset(GDALDataset& dataset,
int nbands, int nbands,
double dx, double dx,
double dy, double dy,
double filter_factor) double filter_factor,
boost::optional<double> const& nodata)
: dataset_(dataset), : dataset_(dataset),
ctx_(boost::make_shared<mapnik::context_type>()), ctx_(boost::make_shared<mapnik::context_type>()),
band_(band), band_(band),
@ -65,6 +66,7 @@ gdal_featureset::gdal_featureset(GDALDataset& dataset,
dy_(dy), dy_(dy),
nbands_(nbands), nbands_(nbands),
filter_factor_(filter_factor), filter_factor_(filter_factor),
nodata_value_(nodata),
first_(true) first_(true)
{ {
ctx_->push("value"); ctx_->push("value");
@ -241,8 +243,17 @@ feature_ptr gdal_featureset::get_feature(mapnik::query const& q)
float* imageData = (float*)image.getBytes(); float* imageData = (float*)image.getBytes();
GDALRasterBand * band = dataset_.GetRasterBand(band_); GDALRasterBand * band = dataset_.GetRasterBand(band_);
int hasNoData; int hasNoData(0);
double nodata = band->GetNoDataValue(&hasNoData); 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, band->RasterIO(GF_Read, x_off, y_off, width, height,
imageData, image.width(), image.height(), imageData, image.width(), image.height(),
GDT_Float32, 0, 0); GDT_Float32, 0, 0);
@ -340,8 +351,21 @@ feature_ptr gdal_featureset::get_feature(mapnik::query const& q)
#ifdef MAPNIK_DEBUG #ifdef MAPNIK_DEBUG
std::clog << "GDAL Plugin: processing rgb bands..." << std::endl; std::clog << "GDAL Plugin: processing rgb bands..." << std::endl;
#endif #endif
int hasNoData; int hasNoData(0);
float nodata = red->GetNoDataValue(&hasNoData); 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(); GDALColorTable *color_table = red->GetColorTable();
if (! alpha && hasNoData && ! color_table) if (! alpha && hasNoData && ! color_table)
@ -380,12 +404,25 @@ feature_ptr gdal_featureset::get_feature(mapnik::query const& q)
#ifdef MAPNIK_DEBUG #ifdef MAPNIK_DEBUG
std::clog << "GDAL Plugin: processing gray band..." << std::endl; std::clog << "GDAL Plugin: processing gray band..." << std::endl;
#endif #endif
int hasNoData; int hasNoData(0);
float nodata = grey->GetNoDataValue(&hasNoData); double nodata(0);
if (nodata_value_)
{
hasNoData = 1;
nodata = *nodata_value_;
}
else
{
nodata = grey->GetNoDataValue(&hasNoData);
}
GDALColorTable* color_table = grey->GetColorTable(); GDALColorTable* color_table = grey->GetColorTable();
if (hasNoData && ! color_table) if (hasNoData && ! color_table)
{ {
if (hasNoData)
{
feature->put("NODATA",nodata);
}
#ifdef MAPNIK_DEBUG #ifdef MAPNIK_DEBUG
std::clog << "\tno data value for layer: " << nodata << std::endl; std::clog << "\tno data value for layer: " << nodata << std::endl;
#endif #endif
@ -422,23 +459,41 @@ feature_ptr gdal_featureset::get_feature(mapnik::query const& q)
#ifdef MAPNIK_DEBUG #ifdef MAPNIK_DEBUG
std::clog << "GDAL Plugin: Loading colour table..." << std::endl; std::clog << "GDAL Plugin: Loading colour table..." << std::endl;
#endif #endif
unsigned nodata_value = static_cast<unsigned>(nodata);
if (hasNoData)
{
feature->put("NODATA",static_cast<int>(nodata_value));
}
for (unsigned y = 0; y < image.height(); ++y) for (unsigned y = 0; y < image.height(); ++y)
{ {
unsigned int* row = image.getRow(y); unsigned int* row = image.getRow(y);
for (unsigned x = 0; x < image.width(); ++x) for (unsigned x = 0; x < image.width(); ++x)
{ {
unsigned value = row[x] & 0xff; unsigned value = row[x] & 0xff;
if (hasNoData && (value == nodata_value))
{
// make no data fully alpha
row[x] = 0;
}
else
{
const GDALColorEntry *ce = color_table->GetColorEntry(value); const GDALColorEntry *ce = color_table->GetColorEntry(value);
if (ce ) if (ce)
{ {
// TODO - big endian support // TODO - big endian support
row[x] = (ce->c4 << 24)| (ce->c3 << 16) | (ce->c2 << 8) | (ce->c1) ; 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) if (alpha)
{ {
#ifdef MAPNIK_DEBUG #ifdef MAPNIK_DEBUG

View file

@ -29,6 +29,7 @@
// boost // boost
#include <boost/variant.hpp> #include <boost/variant.hpp>
#include <boost/optional.hpp>
class GDALDataset; class GDALDataset;
class GDALRasterBand; class GDALRasterBand;
@ -47,7 +48,8 @@ public:
int nbands, int nbands,
double dx, double dx,
double dy, double dy,
double filter_factor); double filter_factor,
boost::optional<double> const& nodata);
virtual ~gdal_featureset(); virtual ~gdal_featureset();
mapnik::feature_ptr next(); mapnik::feature_ptr next();
private: private:
@ -67,6 +69,7 @@ private:
double dy_; double dy_;
int nbands_; int nbands_;
double filter_factor_; double filter_factor_;
boost::optional<double> nodata_value_;
bool first_; bool first_;
}; };

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<Map srs="+init=epsg:32630" background-color="rgb(255,255,255)">
<Style name="2011_5km_vrt_nodata_style">
<Rule>
<RasterSymbolizer/>
</Rule>
</Style>
<Layer name="2011_5km_vrt_nodata" srs="+init=epsg:32630">
<StyleName>2011_5km_vrt_nodata_style</StyleName>
<Datasource>
<Parameter name="file">../raster/dataraster.tif</Parameter>
<Parameter name="nodata">20</Parameter>
<Parameter name="type">gdal</Parameter>
</Datasource>
</Layer>
</Map>

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<Map srs="+init=epsg:32630" background-color="rgb(255,255,255)">
<Style name="2011_5km_vrt_nodata_style">
<Rule>
<RasterSymbolizer/>
</Rule>
</Style>
<Layer name="2011_5km_vrt_nodata" srs="+init=epsg:32630">
<StyleName>2011_5km_vrt_nodata_style</StyleName>
<Datasource>
<Parameter name="file">../raster/dataraster.vrt</Parameter>
<Parameter name="type">gdal</Parameter>
</Datasource>
</Layer>
</Map>

View file

@ -0,0 +1,45 @@
<VRTDataset rasterXSize="2283" rasterYSize="1913">
<SRS>PROJCS[&quot;WGS 84 / UTM zone 30N&quot;,GEOGCS[&quot;WGS 84&quot;,DATUM[&quot;WGS_1984&quot;,SPHEROID[&quot;WGS 84&quot;,6378137,298.257223563,AUTHORITY[&quot;EPSG&quot;,&quot;7030&quot;]],AUTHORITY[&quot;EPSG&quot;,&quot;6326&quot;]],PRIMEM[&quot;Greenwich&quot;,0],UNIT[&quot;degree&quot;,0.0174532925199433],AUTHORITY[&quot;EPSG&quot;,&quot;4326&quot;]],PROJECTION[&quot;Transverse_Mercator&quot;],PARAMETER[&quot;latitude_of_origin&quot;,0],PARAMETER[&quot;central_meridian&quot;,-3],PARAMETER[&quot;scale_factor&quot;,0.9996],PARAMETER[&quot;false_easting&quot;,500000],PARAMETER[&quot;false_northing&quot;,0],UNIT[&quot;metre&quot;,1,AUTHORITY[&quot;EPSG&quot;,&quot;9001&quot;]],AUTHORITY[&quot;EPSG&quot;,&quot;32630&quot;]]</SRS>
<GeoTransform> -1.4637000000000000e+04, 5.0000000000000000e+02, 0.0000000000000000e+00, 4.8596780000000000e+06, 0.0000000000000000e+00, -5.0000000000000000e+02</GeoTransform>
<VRTRasterBand dataType="Int16" band="1">
<NoDataValue>-9.99000000000000E+02</NoDataValue>
<HideNoDataValue>0</HideNoDataValue>
<ColorInterp>Palette</ColorInterp>
<ColorTable>
<Entry c1="0" c2="0" c3="0" c4="0"/>
<Entry c1="0" c2="0" c3="255"/>
<Entry c1="0" c2="255" c3="0"/>
<Entry c1="255" c2="0" c3="0"/>
<Entry c1="128" c2="128" c3="128"/>
<Entry c1="128" c2="128" c3="128"/>
<Entry c1="128" c2="128" c3="128"/>
<Entry c1="128" c2="128" c3="128"/>
<Entry c1="128" c2="128" c3="128"/>
<Entry c1="128" c2="128" c3="128"/>
<Entry c1="128" c2="128" c3="128"/>
<Entry c1="128" c2="128" c3="128"/>
<Entry c1="128" c2="128" c3="128"/>
<Entry c1="128" c2="128" c3="128"/>
<Entry c1="128" c2="128" c3="128"/>
<Entry c1="128" c2="128" c3="128"/>
<Entry c1="128" c2="128" c3="128"/>
<Entry c1="128" c2="128" c3="128"/>
<Entry c1="128" c2="128" c3="128"/>
<Entry c1="128" c2="128" c3="128"/>
<Entry c1="128" c2="128" c3="128"/>
<Entry c1="128" c2="128" c3="128"/>
<Entry c1="128" c2="128" c3="128"/>
<Entry c1="0" c2="0" c3="255"/>
<Entry c1="0" c2="255" c3="0"/>
<Entry c1="255" c2="0" c3="0"/>
</ColorTable>
<ComplexSource>
<SourceFilename relativeToVRT="1">dataraster.tif</SourceFilename>
<SourceBand>1</SourceBand>
<SourceProperties RasterXSize="2283" RasterYSize="1913" DataType="Int16" BlockXSize="256" BlockYSize="256" />
<SrcRect xOff="0" yOff="0" xSize="2283" ySize="1913" />
<DstRect xOff="0" yOff="0" xSize="2283" ySize="1913" />
<NODATA>-999</NODATA>
</ComplexSource>
</VRTRasterBand>
</VRTDataset>

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

View file

@ -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]