Repository: rasterio/rasterio

Issue:
VRT doesn't overwrite nodata
Rasterio `vrt` code says that we can set `dst_nodata` to be different from the `src_nodata`:
https://github.com/mapbox/rasterio/blob/40afba58bb0a3b3d92639e4d44b50c06c394bd32/rasterio/vrt.py#L29-L33

but it don't seem to work 

```python
import rasterio as rio
from rasterio.vrt import WarpedVRT
​
filename = 's3://landsat-pds/c1/L8/014/033/LC08_L1TP_014033_20170730_20170811_01_T1/LC08_L1TP_014033_20170730_20170811_01_T1_B4.TIF'
with rio.open(filename) as src:
    print(f'Source nodata is {src.nodata}')
​
Source nodata is None

with rio.open(filename) as src:
    with WarpedVRT(src, dst_crs='EPSG:3857',
        src_nodata=0, dst_nodata=0) as vrt:
        print(f'Expecting nodata to be 0 but is set to {vrt.nodata}')

Expecting nodata to be 0 but is set to None
```


Discussion:
Interesting, `vrt.nodata` is not returning the expected value, but when reading the vrt dataset with decimation, the resampling algorithm seems the catch the `src_nodata` and `dst_nodata`. 

my Issue was originally opened because I was trying to use `vrt.read` with `masked=True` option which seemed not to behave as if `src_nodata` or `dst_nodata` where set.

@vincentsarago confirmed. I think the proper behavior would be for `vrt.nodata` to reflect the `dst_nodata` value you pass to the constructor. Yes?
I mean if that fixes `vrt.read(masked=True)` [here](https://github.com/mapbox/rasterio/blob/master/rasterio/_io.pyx#L182)  then yes ;-) 
@sgillies here is a notebook with more details (logging) : https://gist.github.com/vincentsarago/b278a5b230ec3b01cf33633831f6aec0

Interesting things from logs is: 
`DEBUG:rasterio._base:Nodata success: 0, Nodata value: -10000.000000`
I believe this confirms that nodata value is not set 

Resolving patch:
diff --git a/rasterio/vrt.py b/rasterio/vrt.py
--- a/rasterio/vrt.py
+++ b/rasterio/vrt.py
@@ -26,15 +26,20 @@ class WarpedVRT(WarpedVRTReaderBase, WindowMethodsMixin,
     tolerance : float
         The maximum error tolerance in input pixels when approximating
         the warp transformation. The default is 0.125.
-    src_nodata : float
-        A nodata value for the source data. It may be a value other
-        than the nodata value of src_dataset.
-    dst_nodata : float, int
-        The nodata value for the virtually warped dataset.
+    src_nodata: int or float, optional
+        The source nodata value.  Pixels with this value will not be
+        used for interpolation. If not set, it will be default to the
+        nodata value of the source image, if available.
+    dst_nodata: int or float, optional
+        The nodata value used to initialize the destination; it will
+        remain in all areas not covered by the reprojected source.
+        Defaults to the value of src_nodata, or 0 (gdal default).
     dst_width : int, optional
-        Target width. dst_height and dst_transform must also be provided.
+        Target width in pixels. dst_height and dst_transform must also be
+        provided.
     dst_height : int, optional
-        Target height. dst_width and dst_transform must also be provided.
+        Target height in pixels. dst_width and dst_transform must also be
+        provided.
     dst_transform: affine.Affine(), optional
         Target affine transformation.  Required if width and height are
         provided.
diff --git a/rasterio/warp.py b/rasterio/warp.py
--- a/rasterio/warp.py
+++ b/rasterio/warp.py
@@ -70,7 +70,7 @@ def transform_geom(
         Target coordinate reference system.
     geom: GeoJSON like dict object
     antimeridian_cutting: bool, optional
-        If True, cut geometries at the antimeridian, otherwise geometries 
+        If True, cut geometries at the antimeridian, otherwise geometries
         will not be cut (default).  If False and GDAL is 2.2.0 or newer
         an exception is raised.  Antimeridian cutting is always on as of
         GDAL 2.2.0 but this could produce an unexpected geometry.
@@ -212,8 +212,7 @@ def reproject(source, destination, src_transform=None, gcps=None,
         The source nodata value.Pixels with this value will not be
         used for interpolation. If not set, it will be default to the
         nodata value of the source image if a masked ndarray or
-        rasterio band, if available. Must be provided if dst_nodata is
-        not None.
+        rasterio band, if available.
     dst_transform: affine.Affine(), optional
         Target affine transformation. Required if source and
         destination are ndarrays. Will be derived from target if it is


Test patch:
diff --git a/tests/test_warp.py b/tests/test_warp.py
--- a/tests/test_warp.py
+++ b/tests/test_warp.py
@@ -470,25 +470,6 @@ def test_reproject_invalid_dst_nodata():
         )
 
 
-def test_reproject_missing_src_nodata():
-    """src_nodata is required if dst_nodata is not None."""
-    params = default_reproject_params()
-
-    source = np.ones((params.width, params.height), dtype=np.uint8)
-    out = source.copy()
-
-    with pytest.raises(ValueError):
-        reproject(
-            source,
-            out,
-            src_transform=params.src_transform,
-            src_crs=params.src_crs,
-            dst_transform=params.dst_transform,
-            dst_crs=params.dst_crs,
-            dst_nodata=215
-        )
-
-
 def test_reproject_invalid_src_nodata():
     """src_nodata must be in range for data type."""
     params = default_reproject_params()
diff --git a/tests/test_warpedvrt.py b/tests/test_warpedvrt.py
--- a/tests/test_warpedvrt.py
+++ b/tests/test_warpedvrt.py
@@ -24,8 +24,8 @@ def test_warped_vrt(path_rgb_byte_tif):
     with rasterio.open(path_rgb_byte_tif) as src:
         vrt = WarpedVRT(src, dst_crs='EPSG:3857')
         assert vrt.dst_crs == 'EPSG:3857'
-        assert vrt.src_nodata is None
-        assert vrt.dst_nodata is None
+        assert vrt.src_nodata == 0.0
+        assert vrt.dst_nodata == 0.0
         assert vrt.tolerance == 0.125
         assert vrt.resampling == Resampling.nearest
         assert vrt.warp_extras == {'init_dest': 'NO_DATA'}
@@ -68,8 +68,8 @@ def test_warped_vrt_dimensions(path_rgb_byte_tif):
                         dst_width=size, dst_height=size,
                         dst_transform=dst_transform)
         assert vrt.dst_crs == 'EPSG:3857'
-        assert vrt.src_nodata is None
-        assert vrt.dst_nodata is None
+        assert vrt.src_nodata == 0.0
+        assert vrt.dst_nodata == 0.0
         assert vrt.resampling == Resampling.nearest
         assert vrt.width == size
         assert vrt.height == size
@@ -92,12 +92,22 @@ def test_wrap_s3():
     """A warp wrapper's dataset has the expected properties"""
     L8TIF = "s3://landsat-pds/L8/139/045/LC81390452014295LGN00/LC81390452014295LGN00_B1.TIF"
     with rasterio.open(L8TIF) as src:
-        with WarpedVRT(src, dst_crs='EPSG:3857') as vrt:
+        with WarpedVRT(src, dst_crs='EPSG:3857', src_nodata=0, dst_nodata=0) as vrt:
             assert vrt.crs == 'EPSG:3857'
             assert tuple(round(x, 1) for x in vrt.bounds) == (
                 9556764.6, 2345109.3, 9804595.9, 2598509.1)
             assert vrt.name == 'WarpedVRT(s3://landsat-pds/L8/139/045/LC81390452014295LGN00/LC81390452014295LGN00_B1.TIF)'
             assert vrt.indexes == (1,)
-            assert vrt.nodatavals == (None,)
+            assert vrt.nodatavals == (0,)
             assert vrt.dtypes == ('uint16',)
             assert vrt.shape == (7827, 7655)
+
+
+def test_warped_vrt_nodata_read(path_rgb_byte_tif):
+    """A read from a VirtualVRT respects dst_nodata."""
+    with rasterio.open(path_rgb_byte_tif) as src:
+        with WarpedVRT(src, dst_crs='EPSG:3857', src_nodata=0) as vrt:
+            data = vrt.read(1, masked=True)
+            assert data.mask.any()
+            mask = vrt.dataset_mask()
+            assert not mask.all()