Repository: rasterio/rasterio

Issue:
rasterio merge giving incorrect result
I have 4 separate ASCII grids which all contain the same data:

    1 2 3 4
    3 4 5 6
    4 5 6 8
    7 9 5 4

The metadata, however, is different:

    ncols 4
    nrows 4
    xllcorner x
    yllcorner y
    cellsize 1
    nodata_value -9999

Where `x`  and `y` take on the values (0,0), (4,0), (4,4) and (0,4) repectively. I then Try to run a merge operation as follows using `rasterio`:

    from rasterio.merge import merge
    import rasterio
    import glob 

    def merge_datasets(out_filename, dataset_list, driver):
        print('running merge command and outputting mosaic')
        mosaic, out_trans = merge(dataset_list)
    
        print('updating metadata')
        out_meta = dataset_list[0].meta.copy()
        out_meta.update({"driver": driver, "height": mosaic.shape[1], "width": mosaic.shape[2], "transform": out_trans})
    
        print('writing file')
        with rasterio.open(out_filename, 'w', **out_meta) as dest: # YES, NO, IF_NEEDED, IF_SAFER
        dest.write(mosaic)

        out_dataset = rasterio.open(out_filename)
        return out_dataset

    if __name__=='__main__':
        tiles_filename_list = glob.glob(r'test_data/*.asc')
        tiles_dataset_list = open_dataset_list(tiles_filename_list)

        merged_dataset = merge_datasets(r'test_data/out.asc', tiles_dataset_list, driver='AAIGrid')


This creates the following incorrect grid:

    ncols        8
    nrows        8
    xllcorner    0.000000000000
    yllcorner    0.000000000000
    cellsize     1.000000000000
    NODATA_value -9999
     1 2 3 4 1 2 2 3
     3 4 5 6 3 4 4 5
     3 4 5 6 3 4 4 5
     4 5 6 8 4 5 5 6
     1 2 3 4 1 2 2 3
     3 4 5 6 3 4 4 5
     4 5 6 8 4 5 5 6
     7 9 5 4 7 9 9 5

Does anyone know why this occurs. I would have expected:

    ncols        8
    nrows        8
    xllcorner    0.000000000000
    yllcorner    0.000000000000
    cellsize     1.000000000000
    NODATA_value -9999
     1 2 3 4 1 2 3 4
     3 4 5 6 3 4 5 6
     3 4 5 6 3 4 5 6
     4 5 6 8 4 5 6 8
     1 2 3 4 1 2 3 4
     3 4 5 6 3 4 5 6
     4 5 6 8 4 5 6 8
     7 9 5 4 7 9 5 4

I tried looking into the different options in the [merge method in rasterio][1] but found nothing useful.


  [1]: https://rasterio.readthedocs.io/en/stable/api/rasterio.merge.html


Discussion:
I've realized that adding the argument `precision=50` to the merge call solves the problem. I don't fully understand why, though.
@awa5114 we've got a test of merging quadrants into a seamless tile in the project test suite https://github.com/mapbox/rasterio/blob/master/tests/test_rio_merge.py#L445-L458 and it has been passing. Can you double-check the metadata of your grids to be sure that they are correct? Also can you paste them in here so I can check?
There is no metadata. I just made the grids in a notepad exactly as shown in my previous posts. That's all you should need to replicate the results I think....
@awa5114 it would really help me out if you would paste the 4 files in here. I don't have time to type them down and want to be 100% sure I'm looking at the same ones as you (whitespace and everything).

Resolving patch:
diff --git a/rasterio/merge.py b/rasterio/merge.py
--- a/rasterio/merge.py
+++ b/rasterio/merge.py
@@ -16,7 +16,7 @@
 MERGE_METHODS = ('first', 'last', 'min', 'max')
 
 
-def merge(datasets, bounds=None, res=None, nodata=None, precision=7, indexes=None,
+def merge(datasets, bounds=None, res=None, nodata=None, precision=10, indexes=None,
           method='first'):
     """Copy valid pixels from input files to an output file.
 
diff --git a/rasterio/rio/merge.py b/rasterio/rio/merge.py
--- a/rasterio/rio/merge.py
+++ b/rasterio/rio/merge.py
@@ -18,7 +18,7 @@
 @options.nodata_opt
 @options.bidx_mult_opt
 @options.overwrite_opt
-@click.option('--precision', type=int, default=7,
+@click.option('--precision', type=int, default=10,
               help="Number of decimal places of precision in alignment of "
                    "pixels")
 @options.creation_options


Test patch:
diff --git a/tests/test_rio_merge.py b/tests/test_rio_merge.py
--- a/tests/test_rio_merge.py
+++ b/tests/test_rio_merge.py
@@ -1,9 +1,9 @@
 """Unittests for $ rio merge"""
 
 
-import sys
 import os
-import logging
+import sys
+import textwrap
 
 import affine
 from click.testing import CliRunner
@@ -18,6 +18,7 @@
 
 from .conftest import requires_gdal22
 
+
 # Fixture to create test datasets within temporary directory
 @fixture(scope='function')
 def test_data_dir_1(tmpdir):
@@ -455,7 +456,7 @@ def test_merge_rgb(tmpdir):
     assert result.exit_code == 0
 
     with rasterio.open(outputname) as src:
-        assert [src.checksum(i) for i in src.indexes] == [25420, 29131, 37860]
+        assert [src.checksum(i) for i in src.indexes] == [33219, 35315, 45188]
 
 
 def test_merge_tiny_intres(tiffs):
@@ -463,3 +464,52 @@ def test_merge_tiny_intres(tiffs):
     inputs.sort()
     datasets = [rasterio.open(x) for x in inputs]
     merge(datasets, res=2)
+
+
+@pytest.mark.parametrize("precision", [[], ["--precision", "9"]])
+def test_merge_precision(tmpdir, precision):
+    """See https://github.com/mapbox/rasterio/issues/1837"""
+    # TDOD move ascii grids to a fixture?
+
+    expected = """\
+        ncols        8
+        nrows        8
+        xllcorner    0.000000000000
+        yllcorner    0.000000000000
+        cellsize     1.000000000000
+         1 2 3 4 1 2 3 4
+         3 4 5 6 3 4 5 6
+         4 5 6 8 4 5 6 8
+         7 9 5 4 7 9 5 4
+         1 2 3 4 1 2 3 4
+         3 4 5 6 3 4 5 6
+         4 5 6 8 4 5 6 8
+         7 9 5 4 7 9 5 4
+         """
+
+    template = """\
+        ncols 4
+        nrows 4
+        xllcorner {:f}
+        yllcorner {:f}
+        cellsize 1.0
+        1 2 3 4
+        3 4 5 6
+        4 5 6 8
+        7 9 5 4
+        """
+
+    names = ["sw.asc", "se.asc", "nw.asc", "ne.asc"]
+    corners = [(0.0, 0.0), (4.0, 0.0), (0.0, 4.0), (4.0, 4.0)]
+
+    for name, (minx, miny) in zip(names, corners):
+        content = textwrap.dedent(template.format(minx, miny))
+        tmpdir.join(name).write(content)
+
+    inputs = [str(tmpdir.join(name)) for name in names]
+    outputname = str(tmpdir.join("merged.asc"))
+
+    runner = CliRunner()
+    result = runner.invoke(main_group, ["merge", "-f", "AAIGrid"] + precision + inputs + [outputname])
+    assert result.exit_code == 0
+    assert open(outputname).read() == textwrap.dedent(expected)