diff --git a/.circleci/config.yml b/.circleci/config.yml
index 89e3236..0056c8d 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -12,7 +12,7 @@ common: &common
     - run:
         name: run pre-commit
         command: |
-          if [[ "$CIRCLE_JOB" == "python-3.6" ]]; then
+          if [[ "$CIRCLE_JOB" == "python-3.7" ]]; then
              ~/.local/bin/pre-commit run --all-files
           fi
     - run:
@@ -26,13 +26,6 @@ common: &common
 
 
 jobs:
-  "python-2.7":
-    <<: *common
-    docker:
-      - image: circleci/python:2.7
-        environment:
-          - TOXENV=py27
-
   "python-3.6":
     <<: *common
     docker:
@@ -78,7 +71,6 @@ workflows:
   version: 2
   build_and_deploy:
     jobs:
-      - "python-2.7"
       - "python-3.6"
       - "python-3.7":
           filters:  # required since `deploy` has tag filters AND requires `build`
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 140fd1c..cc13e4a 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,20 +1,23 @@
-
 repos:
+    -   
+        repo: https://github.com/pre-commit/mirrors-isort
+        rev: v4.3.21
+        hooks:
+            - id: isort
+              language_version: python3.7
     -
-        repo: 'https://github.com/ambv/black'
-        # 18.6b1
-        rev: ed50737290662f6ef4016a7ea44da78ee1eff1e2
+        repo: 'https://github.com/psf/black'
+        rev: stable
         hooks:
             - id: black
               args: ['--safe']
-              language_version: python3.6
+              language_version: python3.7
     -
         repo: 'https://github.com/pre-commit/pre-commit-hooks'
-        # v1.3.0
-        rev: a6209d8d4f97a09b61855ea3f1fb250f55147b8b
+        rev: v2.4.0 
         hooks:
             - id: flake8
-              language_version: python3.6
+              language_version: python3.7
               args: [
                   # E501 let black handle all line length decisions
                   # W503 black conflicts with "line break before operator" rule
@@ -26,9 +29,16 @@ repos:
         rev: 22d3ccf6cf91ffce3b16caa946c155778f0cb20f
         hooks:
             - id: pydocstyle
-              language_version: python3.6
+              language_version: python3.7
               args: [
                  # Check for docstring presence only
                  '--select=D1',
                  # Don't require docstrings for tests
                  '--match=(?!test).*\.py']
+
+    -   
+        repo: https://github.com/pre-commit/mirrors-mypy
+        rev: 'v0.770'
+        hooks:
+            -   id: mypy
+                args: [--no-strict-optional, --ignore-missing-imports]
\ No newline at end of file
diff --git a/CHANGES.txt b/CHANGES.txt
index 03f049e..aeaadc8 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,3 +1,11 @@
+2.0a1 (TBD)
+------------------
+- Dropping python 2 (#128)
+- use new mercantile xy_bounds for better web-optimized file (#126)
+- Allow temporary file on disk when using MemoryFile output
+- add `--blocksize` option in CLI (#131)
+- depreciate `rio_cogeo.utils.get_maximum_overview_level` and use rasterio.rio.overview.get_maximum_overview_level (#132)
+
 1.1.10 (2020-02-21)
 ------------------
 - Transfer colormap (#121)
diff --git a/README.md b/README.md
index 522afb2..b6ec401 100644
--- a/README.md
+++ b/README.md
@@ -66,6 +66,7 @@ $ rio cogeo create --help
                                      CloudOptimized GeoTIFF profile (default: deflate).
     --nodata NUMBER|nan              Set nodata masking values for input dataset.
     --add-mask                       Force output dataset creation with an internal mask (convert alpha band or nodata to mask).
+    --blocksize INTEGER             Overwrite profile's tile size.
     -t, --dtype [ubyte|uint8|uint16|int16|uint32|int32|float32|float64] Output data type.
     --overview-level INTEGER         Overview level (if not provided, appropriate overview level will be selected until the
                                      smallest overview is smaller than the value of the internal blocksize)
@@ -409,13 +410,21 @@ $ cd rio-cogeo
 $ pip install -e .[dev]
 ```
 
-**Python3.6 only**
+**Python >=3.7 only**
 
-This repo is set to use `pre-commit` to run *flake8*, *pydocstring* and *black*
-("uncompromising Python code formatter") when commiting new code.
+This repo is set to use `pre-commit` to run *isort*, *flake8*, *pydocstring*, *black* ("uncompromising Python code formatter") and mypy when committing new code.
 
-```bash
+```
 $ pre-commit install
+
+$ git add .
+
+$ git commit -m'my change'
+isort....................................................................Passed
+black....................................................................Passed
+Flake8...................................................................Passed
+Verifying PEP257 Compliance..............................................Passed
+mypy.....................................................................Passed
 ```
 
 ## Extras
diff --git a/rio_cogeo/cogeo.py b/rio_cogeo/cogeo.py
index 693febe..292341f 100644
--- a/rio_cogeo/cogeo.py
+++ b/rio_cogeo/cogeo.py
@@ -1,48 +1,40 @@
 """rio_cogeo.cogeo: translate a file to a cloud optimized geotiff."""
 
+import math
 import os
 import sys
-import math
-import warnings
 import tempfile
-from contextlib import contextmanager
+import warnings
+from contextlib import ExitStack, contextmanager
 
 import click
-
+import mercantile
 import rasterio
-from rasterio.io import DatasetReader, DatasetWriter, MemoryFile
+from rasterio.crs import CRS
+from rasterio.enums import ColorInterp
+from rasterio.enums import Resampling as ResamplingEnums
 from rasterio.env import GDALVersion
-from rasterio.vrt import WarpedVRT
-from rasterio.warp import transform_bounds
-from rasterio.enums import Resampling as ResamplingEnums, ColorInterp
+from rasterio.io import DatasetReader, DatasetWriter, MemoryFile
+from rasterio.rio.overview import get_maximum_overview_level
 from rasterio.shutil import copy
 from rasterio.transform import Affine
-
-import mercantile
+from rasterio.vrt import WarpedVRT
+from rasterio.warp import transform_bounds
 from supermercado.burntiles import tile_extrema
-from rio_cogeo.errors import LossyCompression, IncompatibleBlockRasterSize
-from rio_cogeo.utils import (
-    get_maximum_overview_level,
-    has_alpha_band,
-    has_mask_band,
-    get_max_zoom,
-    _meters_per_pixel,
-)
-
-try:
-    from contextlib import ExitStack
-except ImportError:
-    from contextlib2 import ExitStack
+
+from rio_cogeo import utils
+from rio_cogeo.errors import IncompatibleBlockRasterSize, LossyCompression
 
 IN_MEMORY_THRESHOLD = int(os.environ.get("IN_MEMORY_THRESHOLD", 10980 * 10980))
+WEB_MERCATOR_CRS = CRS.from_epsg(3857)
+WGS84_CRS = CRS.from_epsg(4326)
 
 
 @contextmanager
 def TemporaryRasterFile(dst_path, suffix=".tif"):
     """Create temporary file."""
-    fileobj = tempfile.NamedTemporaryFile(
-        dir=os.path.dirname(dst_path), suffix=suffix, delete=False
-    )
+    tmpdir = None if dst_path.startswith("/vsi") else os.path.dirname(dst_path)
+    fileobj = tempfile.NamedTemporaryFile(dir=tmpdir, suffix=suffix, delete=False)
     fileobj.close()
     try:
         yield fileobj
@@ -130,8 +122,8 @@ def cog_translate(
             indexes = indexes if indexes else src_dst.indexes
             nodata = nodata if nodata is not None else src_dst.nodata
             dtype = dtype if dtype else src_dst.dtypes[0]
-            alpha = has_alpha_band(src_dst)
-            mask = has_mask_band(src_dst)
+            alpha = utils.has_alpha_band(src_dst)
+            mask = utils.has_mask_band(src_dst)
 
             if not add_mask and (
                 (nodata is not None or alpha)
@@ -180,28 +172,28 @@ def cog_translate(
             if web_optimized:
                 bounds = list(
                     transform_bounds(
-                        *[src_dst.crs, "epsg:4326"] + list(src_dst.bounds),
-                        densify_pts=21
+                        src_dst.crs, WGS84_CRS, *src_dst.bounds, densify_pts=21
                     )
                 )
                 center = [(bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2]
 
                 lat = 0 if latitude_adjustment else center[1]
-                max_zoom = get_max_zoom(src_dst, lat=lat, tilesize=tilesize)
+                max_zoom = utils.get_max_zoom(src_dst, lat=lat, tilesize=tilesize)
 
                 extrema = tile_extrema(bounds, max_zoom)
-                w, n = mercantile.xy(
-                    *mercantile.ul(extrema["x"]["min"], extrema["y"]["min"], max_zoom)
+
+                left, _, _, top = mercantile.xy_bounds(
+                    extrema["x"]["min"], extrema["y"]["min"], max_zoom
                 )
-                vrt_res = _meters_per_pixel(max_zoom, 0, tilesize=tilesize)
-                vrt_transform = Affine(vrt_res, 0, w, 0, -vrt_res, n)
+                vrt_res = utils._meters_per_pixel(max_zoom, 0, tilesize=tilesize)
+                vrt_transform = Affine(vrt_res, 0, left, 0, -vrt_res, top)
 
                 vrt_width = (extrema["x"]["max"] - extrema["x"]["min"]) * tilesize
                 vrt_height = (extrema["y"]["max"] - extrema["y"]["min"]) * tilesize
 
                 vrt_params.update(
                     dict(
-                        crs="epsg:3857",
+                        crs=WEB_MERCATOR_CRS,
                         transform=vrt_transform,
                         width=vrt_width,
                         height=vrt_height,
@@ -266,10 +258,8 @@ def cog_translate(
                 if not quiet:
                     click.echo("Reading input: {}".format(source), err=True)
                 fout = os.devnull if quiet else sys.stderr
-                with click.progressbar(
-                    wind, length=len(wind), file=fout, show_percent=True
-                ) as windows:
-                    for ij, w in windows:
+                with click.progressbar(wind, file=fout, show_percent=True) as windows:
+                    for _, w in windows:
                         matrix = vrt_dst.read(window=w, indexes=indexes)
                         tmp_dst.write(matrix, window=w)
 
@@ -279,7 +269,9 @@ def cog_translate(
                             tmp_dst.write_mask(mask_value, window=w)
 
                 if overview_level is None:
-                    overview_level = get_maximum_overview_level(vrt_dst, tilesize)
+                    overview_level = get_maximum_overview_level(
+                        vrt_dst.width, vrt_dst.height, minsize=tilesize
+                    )
 
                 if not quiet and overview_level:
                     click.echo("Adding overviews...", err=True)
diff --git a/rio_cogeo/profiles.py b/rio_cogeo/profiles.py
index 9a91747..15e7c9f 100644
--- a/rio_cogeo/profiles.py
+++ b/rio_cogeo/profiles.py
@@ -174,6 +174,7 @@ class COGProfiles(dict):
 
     def get(self, key):
         """Like normal item access but error."""
+        key = key.lower()
         if key not in (self.keys()):
             raise KeyError("{} is not a valid COG profile name".format(key))
 
diff --git a/rio_cogeo/scripts/cli.py b/rio_cogeo/scripts/cli.py
index 7d9ac8d..b110136 100644
--- a/rio_cogeo/scripts/cli.py
+++ b/rio_cogeo/scripts/cli.py
@@ -5,9 +5,8 @@ import warnings
 
 import click
 import numpy
-
-from rasterio.rio import options
 from rasterio.enums import Resampling as ResamplingEnums
+from rasterio.rio import options
 
 from rio_cogeo import version as cogeo_version
 from rio_cogeo.cogeo import cog_translate, cog_validate
@@ -84,7 +83,7 @@ def cogeo():
     "--cog-profile",
     "-p",
     "cogeo_profile",
-    type=click.Choice(cog_profiles.keys()),
+    type=click.Choice(cog_profiles.keys(), case_sensitive=False),
     default="deflate",
     help="CloudOptimized GeoTIFF profile (default: deflate).",
 )
@@ -100,6 +99,7 @@ def cogeo():
     help="Force output dataset creation with an internal mask (convert alpha "
     "band or nodata to mask).",
 )
+@click.option("--blocksize", type=int, help="Overwrite profile's tile size.")
 @options.dtype_opt
 @click.option(
     "--overview-level",
@@ -177,6 +177,7 @@ def create(
     nodata,
     dtype,
     add_mask,
+    blocksize,
     overview_level,
     overview_resampling,
     overview_blocksize,
@@ -202,6 +203,10 @@ def create(
     if creation_options:
         output_profile.update(creation_options)
 
+    if blocksize:
+        output_profile["blockxsize"] = blocksize
+        output_profile["blockysize"] = blocksize
+
     config = dict(
         GDAL_NUM_THREADS=threads,
         GDAL_TIFF_INTERNAL_MASK=os.environ.get("GDAL_TIFF_INTERNAL_MASK", True),
diff --git a/rio_cogeo/utils.py b/rio_cogeo/utils.py
index 8c3aeb1..02f9a91 100644
--- a/rio_cogeo/utils.py
+++ b/rio_cogeo/utils.py
@@ -1,9 +1,12 @@
 """rio_cogeo.utils: Utility functions."""
 
 import math
+import warnings
 
+from rasterio.enums import ColorInterp, MaskFlags
 from rasterio.warp import calculate_default_transform
-from rasterio.enums import MaskFlags, ColorInterp
+
+from rio_cogeo.errors import DeprecationWarning
 
 
 def _meters_per_pixel(zoom, lat=0.0, tilesize=256):
@@ -109,6 +112,11 @@ def get_maximum_overview_level(src_dst, minsize=512):
         overview level.
 
     """
+    warnings.warn(
+        "rio_cogeo.utils.get_maximum_overview_level will be removed in version 2.0",
+        DeprecationWarning,
+    )
+
     nlevel = 0
     overview = 1
     while min(src_dst.width // overview, src_dst.height // overview) > minsize:
diff --git a/setup.py b/setup.py
index 988cc71..9ec577c 100644
--- a/setup.py
+++ b/setup.py
@@ -1,7 +1,8 @@
 """Setup."""
 
 import sys
-from setuptools import setup, find_packages
+
+from setuptools import find_packages, setup
 
 with open("README.md") as f:
     readme = f.read()
@@ -9,18 +10,15 @@ with open("README.md") as f:
 # Runtime requirements.
 inst_reqs = [
     "click",
-    "rasterio[s3]>=1.0.28",
+    "rasterio[s3]~=1.1",
     "numpy~=1.15",
     "supermercado",
-    "mercantile",
+    "mercantile~=1.1",
 ]
 
-if sys.version_info < (3, 3):
-    inst_reqs.append("contextlib2")
-
 extra_reqs = {
-    "test": ["pytest", "pytest-cov", "rio-tiler"],
-    "dev": ["pytest", "pytest-cov", "rio-tiler", "pre-commit"],
+    "test": ["pytest", "pytest-cov", "rio-tiler~=2.0a3"],
+    "dev": ["pytest", "pytest-cov", "rio-tiler~=2.0a3", "pre-commit"],
 }
 
 if sys.version_info >= (3, 6):
@@ -29,7 +27,8 @@ if sys.version_info >= (3, 6):
 
 setup(
     name="rio-cogeo",
-    version="1.1.10",
+    version="2.0a1",
+    python_requires=">=3",
     description=u"CloudOptimized GeoTIFF (COGEO) creation plugin for rasterio",
     long_description=readme,
     long_description_content_type="text/markdown",
diff --git a/tox.ini b/tox.ini
index 987a910..0daa909 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = py27,py37,py36
+envlist = py37,py36
 
 [testenv]
 extras = test
@@ -32,3 +32,11 @@ commands =
 [flake8]
 exclude = .git,__pycache__,docs/source/conf.py,old,build,dist
 max-line-length = 90
+
+[tool:isort]
+include_trailing_comma = True
+multi_line_output = 3
+line_length = 90
+known_first_party = rio_cogeo
+known_third_party = rasterio,rio_tiler,mercantile,supermercado
+default_section = THIRDPARTY

Dropping Python 2 support for rio-cogeo version 2.0.0
We haven't had any feedback when we did https://github.com/cogeotiff/rio-tiler/issues/139 so I guess (🤞) it will be the same here. 
diff --git a/tests/conftest.py b/tests/conftest.py
index e359dfe..484ef8f 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -4,7 +4,6 @@
 import pytest
 import rasterio
 
-
 with rasterio.Env() as env:
     drivers = env.drivers()
 
diff --git a/tests/test_cli.py b/tests/test_cli.py
index cfe9346..0528ed6 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -3,12 +3,11 @@
 import os
 
 import pytest
-
 import rasterio
+
+from rio_cogeo.errors import LossyCompression
 from rio_cogeo.scripts.cli import cogeo
 from rio_cogeo.utils import has_mask_band
-from rio_cogeo.errors import LossyCompression
-
 
 raster_path_rgb = os.path.join(os.path.dirname(__file__), "fixtures", "image_rgb.tif")
 raster_path_rgba = os.path.join(os.path.dirname(__file__), "fixtures", "image_rgba.tif")
@@ -232,6 +231,20 @@ def test_cogeo_overviewTilesize(monkeypatch, runner):
         with rasterio.open("output.tif", OVERVIEW_LEVEL=1) as src:
             assert src.block_shapes[0] == (128, 128)
 
+    with runner.isolated_filesystem():
+        result = runner.invoke(
+            cogeo,
+            ["create", raster_path_rgb, "output.tif", "--quiet", "--blocksize", "128"],
+        )
+        assert not result.exception
+        assert result.exit_code == 0
+        with rasterio.open("output.tif") as src:
+            assert src.is_tiled
+            assert src.overviews(1)
+
+        with rasterio.open("output.tif", OVERVIEW_LEVEL=1) as src:
+            assert src.block_shapes[0] == (128, 128)
+
     with runner.isolated_filesystem():
         result = runner.invoke(
             cogeo,
@@ -459,3 +472,16 @@ def test_cogeo_validate(runner):
         assert "is NOT a valid cloud optimized GeoTIFF" in result.output
         assert not result.exception
         assert result.exit_code == 0
+
+
+def test_cogeo_validUpercaseProfile(monkeypatch, runner):
+    """Should work as expected."""
+    with runner.isolated_filesystem():
+        result = runner.invoke(
+            cogeo,
+            ["create", raster_path_rgb, "output.tif", "--cog-profile", "DEFLATE"],
+        )
+        assert not result.exception
+        assert result.exit_code == 0
+        with rasterio.open("output.tif") as src:
+            assert src.compression.value == "DEFLATE"
diff --git a/tests/test_cogeo.py b/tests/test_cogeo.py
index 076e555..cd47cd7 100644
--- a/tests/test_cogeo.py
+++ b/tests/test_cogeo.py
@@ -3,14 +3,15 @@
 import os
 
 import numpy
-
 import pytest
-
 import rasterio
+from rasterio.io import MemoryFile
+from rasterio.vrt import WarpedVRT
+
 from rio_cogeo.cogeo import cog_translate, cog_validate
-from rio_cogeo.utils import has_mask_band, has_alpha_band
-from rio_cogeo.errors import LossyCompression, IncompatibleBlockRasterSize
+from rio_cogeo.errors import IncompatibleBlockRasterSize, LossyCompression
 from rio_cogeo.profiles import cog_profiles
+from rio_cogeo.utils import has_alpha_band, has_mask_band
 
 from .conftest import requires_webp
 
@@ -315,8 +316,6 @@ def test_cog_translate_dataset(runner):
 
 def test_cog_translate_memfile(runner):
     """Should work as expected (create cogeo from an open memfile)."""
-    from rasterio.io import MemoryFile
-
     with runner.isolated_filesystem():
         with rasterio.open(raster_path_rgb) as dataset:
             data = dataset.read()
@@ -329,10 +328,26 @@ def test_cog_translate_memfile(runner):
             _validate_translated_rgb_jpeg(src)
 
 
+def test_cog_translate_to_memfile(runner):
+    """Create COG in memory and using in memory or in disk temp files."""
+    with runner.isolated_filesystem():
+        with rasterio.open(raster_path_rgb) as dataset:
+            with MemoryFile() as memfile:
+                cog_translate(dataset, memfile.name, deflate_profile, quiet=True)
+                with memfile.open() as src:
+                    assert src.width == dataset.width
+
+        with rasterio.open(raster_path_rgb) as dataset:
+            with MemoryFile() as memfile:
+                cog_translate(
+                    dataset, memfile.name, deflate_profile, in_memory=False, quiet=True
+                )
+                with memfile.open() as src:
+                    assert src.width == dataset.width
+
+
 def test_cog_translate_warpedvrt(runner):
     """Should work as expected (create cogeo from an open memfile)."""
-    from rasterio.vrt import WarpedVRT
-
     with runner.isolated_filesystem():
         with rasterio.open(raster_path_rgb) as dataset:
             with WarpedVRT(dataset) as vrt:
@@ -344,8 +359,6 @@ def test_cog_translate_warpedvrt(runner):
 
 def test_cog_translate_forward_tags(runner):
     """Should work as expected (create cogeo from an open memfile)."""
-    from rasterio.io import MemoryFile
-
     with runner.isolated_filesystem():
         with rasterio.open(raster_path_rgb) as dataset:
             data = dataset.read()
diff --git a/tests/test_profile.py b/tests/test_profile.py
index f470442..a4c40ed 100644
--- a/tests/test_profile.py
+++ b/tests/test_profile.py
@@ -1,6 +1,7 @@
 """tests rio_cogeo.profiles."""
 
 import pytest
+
 from rio_cogeo.profiles import cog_profiles
 
 
@@ -14,6 +15,9 @@ def test_profiles_jpeg():
     assert profile["photometric"] == "YCbCr"
     assert profile["interleave"] == "pixel"
 
+    profile = cog_profiles.get("JPEG")
+    assert profile["compress"] == "JPEG"
+
 
 def test_profiles_webp():
     """Should work as expected (return webp profile)."""
diff --git a/tests/test_utils.py b/tests/test_utils.py
index d1b076b..1359f7c 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -2,7 +2,10 @@
 
 import os
 
+import pytest
 import rasterio
+
+from rio_cogeo.errors import DeprecationWarning
 from rio_cogeo.utils import get_maximum_overview_level
 
 raster_path_rgb = os.path.join(os.path.dirname(__file__), "fixtures", "image_rgb.tif")
@@ -10,6 +13,7 @@ raster_path_rgb = os.path.join(os.path.dirname(__file__), "fixtures", "image_rgb
 
 def test_overviewlevel_valid():
     """Should work as expected (return overview level)."""
-    with rasterio.open(raster_path_rgb) as src_dst:
-        assert get_maximum_overview_level(src_dst, 128) == 2
-        assert get_maximum_overview_level(src_dst) == 0
+    with pytest.warns(DeprecationWarning):
+        with rasterio.open(raster_path_rgb) as src_dst:
+            assert get_maximum_overview_level(src_dst, 128) == 2
+            assert get_maximum_overview_level(src_dst) == 0
diff --git a/tests/test_validate.py b/tests/test_validate.py
index 8b3ce3e..e1b1c7a 100644
--- a/tests/test_validate.py
+++ b/tests/test_validate.py
@@ -5,10 +5,9 @@ import os
 import pytest
 from click.testing import CliRunner
 
-from rio_cogeo.cogeo import cog_validate, cog_translate
+from rio_cogeo.cogeo import cog_translate, cog_validate
 from rio_cogeo.profiles import cog_profiles
 
-
 raster_rgb = os.path.join(os.path.dirname(__file__), "fixtures", "image_rgb.tif")
 raster_external = os.path.join(
     os.path.dirname(__file__), "fixtures", "validate", "image_external.tif"
diff --git a/tests/test_web.py b/tests/test_web.py
index 1a1f727..742b441 100644
--- a/tests/test_web.py
+++ b/tests/test_web.py
@@ -1,24 +1,20 @@
 """tests rio_cogeo web."""
 
 import os
-import sys
 import struct
-
-import pytest
-from click.testing import CliRunner
-
-import numpy
+import sys
 
 import mercantile
+import numpy
+import pytest
 import rasterio
+from click.testing import CliRunner
 from rasterio.warp import transform_bounds
+from rio_tiler import reader as COGreader
 
-from rio_cogeo.utils import get_max_zoom
 from rio_cogeo.cogeo import cog_translate
 from rio_cogeo.profiles import cog_profiles
-
-from rio_tiler.utils import tile_read
-
+from rio_cogeo.utils import get_max_zoom
 
 raster_path_web = os.path.join(os.path.dirname(__file__), "fixtures", "image_web.tif")
 raster_path_north = os.path.join(
@@ -101,29 +97,20 @@ def test_cog_translate_web():
 
                 bounds = list(
                     transform_bounds(
-                        *[src_dst.crs, "epsg:4326"] + list(src_dst.bounds),
-                        densify_pts=21
+                        src_dst.crs, "epsg:4326", *src_dst.bounds, densify_pts=21
                     )
                 )
-
-                leftTile = mercantile.tile(bounds[0], bounds[3], max_zoom)
-                tbounds = mercantile.xy_bounds(leftTile)
-                west, north = tbounds.left, tbounds.top
-                assert out_dst.transform.xoff == west
-                assert out_dst.transform.yoff == north
-
-                rightTile = mercantile.tile(bounds[2], bounds[1], max_zoom)
-                tbounds = mercantile.xy_bounds(rightTile)
-                east, south = tbounds.right, tbounds.bottom
-
-                lrx = round(
-                    out_dst.transform.xoff + out_dst.transform.a * out_dst.width, 6
+                ulTile = mercantile.xy_bounds(
+                    mercantile.tile(bounds[0], bounds[3], max_zoom)
                 )
-                lry = round(
-                    out_dst.transform.yoff + out_dst.transform.e * out_dst.height, 6
+                assert out_dst.bounds.left == ulTile.left
+                assert out_dst.bounds.top == ulTile.top
+
+                lrTile = mercantile.xy_bounds(
+                    mercantile.tile(bounds[2], bounds[1], max_zoom)
                 )
-                assert lrx == round(east, 6)
-                assert lry == round(south, 6)
+                assert out_dst.bounds.right == lrTile.right
+                assert round(out_dst.bounds.bottom, 6) == round(lrTile.bottom, 6)
 
 
 @pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher")
@@ -167,29 +154,12 @@ def test_cog_translate_Internal():
 
                 bounds = list(
                     transform_bounds(
-                        *[src_dst.crs, "epsg:4326"] + list(src_dst.bounds),
-                        densify_pts=21
+                        src_dst.crs, "epsg:4326", *src_dst.bounds, densify_pts=21
                     )
                 )
 
-                leftTile = mercantile.tile(bounds[0], bounds[3], max_zoom)
-                tbounds = mercantile.xy_bounds(leftTile)
-                west, north = tbounds.left, tbounds.top
-                assert out_dst.transform.xoff == west
-                assert out_dst.transform.yoff == north
-
-                rightTile = mercantile.tile(bounds[2], bounds[1], max_zoom)
-                tbounds = mercantile.xy_bounds(rightTile)
-                east, south = tbounds.right, tbounds.bottom
-
-                lrx = round(
-                    out_dst.transform.xoff + out_dst.transform.a * out_dst.width, 6
-                )
-                lry = round(
-                    out_dst.transform.yoff + out_dst.transform.e * out_dst.height, 6
-                )
-                assert lrx == round(east, 6)
-                assert lry == round(south, 6)
+                minimumTile = mercantile.tile(bounds[0], bounds[3], max_zoom)
+                maximumTile = mercantile.tile(bounds[2], bounds[1], max_zoom)
 
                 with open("cogeo.tif", "rb") as out_body:
                     reader = FileReader(out_body)
@@ -197,29 +167,29 @@ def test_cog_translate_Internal():
 
                     # High resolution
                     # Top Left tile
-                    mime_type, tile = cog.get_tile(0, 0, 0)
+                    _, tile = cog.get_tile(0, 0, 0)
                     tile_length = 256 * 256 * 3
                     t = struct.unpack_from("{}b".format(tile_length), tile)
                     arr = numpy.array(t).reshape(256, 256, 3).astype(numpy.uint8)
                     arr = numpy.transpose(arr, [2, 0, 1])
 
-                    tbounds = mercantile.xy_bounds(leftTile)
-                    data, mask = tile_read(
-                        "cogeo.tif", tbounds, 256, resampling_method="nearest"
-                    )
+                    with rasterio.open("cogeo.tif") as src_dst:
+                        data, _ = COGreader.tile(
+                            src_dst, *minimumTile, resampling_method="nearest"
+                        )
                     numpy.testing.assert_array_equal(data, arr)
 
                     # Bottom right tile
-                    mime_type, tile = cog.get_tile(4, 3, 0)
+                    _, tile = cog.get_tile(4, 3, 0)
                     tile_length = 256 * 256 * 3
                     t = struct.unpack_from("{}b".format(tile_length), tile)
                     arr = numpy.array(t).reshape(256, 256, 3).astype(numpy.uint8)
                     arr = numpy.transpose(arr, [2, 0, 1])
 
-                    tbounds = mercantile.xy_bounds(rightTile)
-                    data, mask = tile_read(
-                        "cogeo.tif", tbounds, 256, resampling_method="nearest"
-                    )
+                    with rasterio.open("cogeo.tif") as src_dst:
+                        data, _ = COGreader.tile(
+                            src_dst, *maximumTile, resampling_method="nearest"
+                        )
                     numpy.testing.assert_array_equal(data, arr)
 
                     # Low resolution (overview 1)
@@ -228,23 +198,23 @@ def test_cog_translate_Internal():
                     # We need to stack two internal tiles to compare with
                     # the 256px mercator tile fetched by rio-tiler
                     # ref: https://github.com/cogeotiff/rio-cogeo/issues/60
-                    mime_type, tile = cog.get_tile(1, 0, 1)
+                    _, tile = cog.get_tile(1, 0, 1)
                     tile_length = 128 * 128 * 3
                     t = struct.unpack_from("{}b".format(tile_length), tile)
                     arr1 = numpy.array(t).reshape(128, 128, 3).astype(numpy.uint8)
                     arr1 = numpy.transpose(arr1, [2, 0, 1])
 
-                    mime_type, tile = cog.get_tile(2, 0, 1)
+                    _, tile = cog.get_tile(2, 0, 1)
                     tile_length = 128 * 128 * 3
                     t = struct.unpack_from("{}b".format(tile_length), tile)
                     arr2 = numpy.array(t).reshape(128, 128, 3).astype(numpy.uint8)
                     arr2 = numpy.transpose(arr2, [2, 0, 1])
                     arr = numpy.dstack((arr1, arr2))
 
-                    lowTile = mercantile.Tile(118594, 60034, 17)
-                    tbounds = mercantile.xy_bounds(lowTile)
-                    data, mask = tile_read(
-                        "cogeo.tif", tbounds, 256, resampling_method="nearest"
-                    )
+                    with rasterio.open("cogeo.tif") as src_dst:
+                        data, _ = COGreader.tile(
+                            src_dst, 118594, 60034, 17, resampling_method="nearest"
+                        )
+
                     data = data[:, 128:, :]
                     numpy.testing.assert_array_equal(data, arr)

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-cov
3.6
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
affine==2.3.1
attrs==22.2.0
boto3==1.23.10
botocore==1.26.10
certifi==2021.5.30
cfgv==3.3.1
charset-normalizer==2.0.12
click==8.0.4
click-plugins==1.1.1
cligj==0.7.2
cogdumper==1.1.0
coverage==6.2
dataclasses==0.8
distlib==0.3.9
filelock==3.4.1
identify==2.4.4
idna==3.10
importlib-metadata==4.8.3
importlib-resources==5.2.3
iniconfig==1.1.1
jmespath==0.10.0
mercantile==1.2.1
morecantile==2.1.4
nodeenv==1.6.0
numexpr==2.8.1
numpy==1.19.5
packaging==21.3
platformdirs==2.4.0
pluggy==1.0.0
pre-commit==2.17.0
py==1.11.0
pydantic==1.9.2
pyparsing==3.1.4
pystac==0.5.6
pytest==7.0.1
pytest-cov==4.0.0
python-dateutil==2.9.0.post0
PyYAML==6.0.1
rasterio==1.2.10
requests==2.27.1
-e git+https://github.com/cogeotiff/rio-cogeo.git@452edc1a90f87fe1fa7bbe597c6bf346b3be7366#egg=rio_cogeo
rio-color==1.0.4
rio-mucho==1.0.0
rio-tiler==2.1.4
s3transfer==0.5.2
six==1.17.0
snuggs==1.4.7
supermercado==0.2.0
toml==0.10.2
tomli==1.2.3
typing_extensions==4.1.1
urllib3==1.26.20
virtualenv==20.16.2
zipp==3.6.0

name: rio-cogeo
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - affine==2.3.1
      - attrs==22.2.0
      - boto3==1.23.10
      - botocore==1.26.10
      - cfgv==3.3.1
      - charset-normalizer==2.0.12
      - click==8.0.4
      - click-plugins==1.1.1
      - cligj==0.7.2
      - cogdumper==1.1.0
      - coverage==6.2
      - dataclasses==0.8
      - distlib==0.3.9
      - filelock==3.4.1
      - identify==2.4.4
      - idna==3.10
      - importlib-metadata==4.8.3
      - importlib-resources==5.2.3
      - iniconfig==1.1.1
      - jmespath==0.10.0
      - mercantile==1.2.1
      - morecantile==2.1.4
      - nodeenv==1.6.0
      - numexpr==2.8.1
      - numpy==1.19.5
      - packaging==21.3
      - platformdirs==2.4.0
      - pluggy==1.0.0
      - pre-commit==2.17.0
      - py==1.11.0
      - pydantic==1.9.2
      - pyparsing==3.1.4
      - pystac==0.5.6
      - pytest==7.0.1
      - pytest-cov==4.0.0
      - python-dateutil==2.9.0.post0
      - pyyaml==6.0.1
      - rasterio==1.2.10
      - requests==2.27.1
      - rio-color==1.0.4
      - rio-mucho==1.0.0
      - rio-tiler==2.1.4
      - s3transfer==0.5.2
      - six==1.17.0
      - snuggs==1.4.7
      - supermercado==0.2.0
      - toml==0.10.2
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - urllib3==1.26.20
      - virtualenv==20.16.2
      - zipp==3.6.0
prefix: /opt/conda/envs/rio-cogeo

tests/test_cli.py::test_cogeo_validUpercaseProfile tests/test_profile.py::test_profiles_jpeg tests/test_utils.py::test_overviewlevel_valid
tests/test_cli.py::test_cogeo_valid tests/test_cli.py::test_cogeo_overviewTilesize tests/test_cli.py::test_cogeo_validate tests/test_cogeo.py::test_cog_translate_valid tests/test_cogeo.py::test_cog_translate_NodataLossyWarning tests/test_cogeo.py::test_cog_translate_optionWarnings tests/test_cogeo.py::test_cog_translate_NodataMask tests/test_cogeo.py::test_cog_translate_validRaw tests/test_cogeo.py::test_cog_translate_validIndexes tests/test_cogeo.py::test_cog_translate_validAlpha tests/test_cogeo.py::test_cog_translate_valiNodataNan tests/test_cogeo.py::test_cog_translate_validOverviews tests/test_cogeo.py::test_cog_translate_valiEnv tests/test_cogeo.py::test_cog_translate_validCustom tests/test_cogeo.py::test_cog_translate_mask tests/test_cogeo.py::test_cog_translate_tags tests/test_cogeo.py::test_cog_translate_valid_blocksize tests/test_cogeo.py::test_cog_translate_dataset tests/test_cogeo.py::test_cog_translate_memfile tests/test_cogeo.py::test_cog_translate_to_memfile tests/test_cogeo.py::test_cog_translate_warpedvrt tests/test_cogeo.py::test_cog_translate_forward_tags tests/test_cogeo.py::test_cog_translate_oneBandJpeg tests/test_cogeo.py::test_cog_translate_forward_scales tests/test_cogeo.py::test_cog_translate_forward_cmap tests/test_validate.py::test_cog_validate_validCreatioValid tests/test_web.py::test_cog_translate_webZooms tests/test_web.py::test_cog_translate_web tests/test_web.py::test_cog_translate_Internal
tests/test_cli.py::test_cogeo_valid_external_mask tests/test_cli.py::test_cogeo_validbidx tests/test_cli.py::test_cogeo_invalidbidx tests/test_cli.py::test_cogeo_invalidbidxString tests/test_cli.py::test_cogeo_invalidThread tests/test_cli.py::test_cogeo_validnodata tests/test_cli.py::test_cogeo_validGdalOptions tests/test_cli.py::test_cogeo_validOvrOption tests/test_cli.py::test_cogeo_web tests/test_cli.py::test_cogeo_validgdalBlockOption tests/test_cli.py::test_cogeo_validNodataCustom tests/test_cli.py::test_cogeo_validTempFile tests/test_cli.py::test_cogeo_validCompress tests/test_profile.py::test_profiles_webp tests/test_profile.py::test_profiles_lzw tests/test_profile.py::test_profiles_deflate tests/test_profile.py::test_profiles_packbits tests/test_profile.py::test_profiles_lzma tests/test_profile.py::test_profiles_lerc tests/test_profile.py::test_profiles_lerc_deflate tests/test_profile.py::test_profiles_lerc_zstd tests/test_profile.py::test_profiles_raw tests/test_profile.py::test_profiles_nonstandard tests/test_profile.py::test_profiles_copy tests/test_profile.py::test_profiles_error tests/test_validate.py::test_cog_validate_valid
CHANGES.txt setup.py rio_cogeo/cogeo.py rio_cogeo/scripts/cli.py .circleci/config.yml rio_cogeo/utils.py rio_cogeo/profiles.py README.md tox.ini .pre-commit-config.yaml
CHANGES.txt setup.py rio_cogeo/cogeo.py rio_cogeo/scripts/cli.py .circleci/config.yml rio_cogeo/utils.py rio_cogeo/profiles.py README.md tox.ini .pre-commit-config.yaml