Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add Custom Colormap querystring option #262

Merged
merged 5 commits into from
Mar 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Release Notes

## 0.2.0 (TBD)
## 0.2.0 (2021-03-09)

* adapt for cogeo-mosaic `3.0.0rc2` and add `backend_options` attribute in MosaicTilerFactory (https://github.com/developmentseed/titiler/pull/247)
* update FastAPI requirements
Expand All @@ -15,6 +15,21 @@
* renamed `MimeType` to `MediaType` (https://github.com/developmentseed/titiler/pull/258)
* add `ColorMapParams` dependency to ease the creation of custom colormap dependency (https://github.com/developmentseed/titiler/pull/252)
* renamed `PathParams` to `DatasetPathParams` and also made it a simple callable (https://github.com/developmentseed/titiler/pull/260)
* renamed `colormap` query-parameter to `colormap_name` (https://github.com/developmentseed/titiler/pull/262)
```
# before
/cog/preview.png?colormap=viridis

# now
/cog/preview.png?colormap_name=viridis
```

* use `colormap` query-parameter to pass custom colormap (https://github.com/developmentseed/titiler/pull/262)
```
/cog/preview.png?colormap={"0": "#FFFF00FF", ...}
```



## 0.1.0 (2021-02-17)

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"python-dotenv",
"rasterio",
"rio-cogeo>=2.1,<2.2",
"rio-tiler>=2.0.1,<2.1",
"rio-tiler>=2.0.4,<2.1",
"uvicorn[standard]>=0.12.0,<0.14.0",
# Additional requirements for python 3.6
"dataclasses;python_version<'3.7'",
Expand Down
46 changes: 43 additions & 3 deletions tests/routes/test_cog.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
"""test /COG endpoints."""

import json
import os
from io import BytesIO
from unittest.mock import patch
from urllib.parse import parse_qsl, urlencode, urlparse

import numpy
import pytest
Expand Down Expand Up @@ -202,19 +205,37 @@ def test_tile(rio, app):
assert response.headers["content-type"] == "image/png"

response = app.get(
"/cog/tiles/8/84/47?url=https://myurl.com/cog.tif&nodata=0&rescale=0,1000&color_map=viridis"
"/cog/tiles/8/84/47?url=https://myurl.com/cog.tif&nodata=0&rescale=0,1000&colormap_name=viridis"
)
assert response.status_code == 200
assert response.headers["content-type"] == "image/png"

response = app.get(
"/cog/tiles/8/53/50.png?url=https://myurl.com/above_cog.tif&bidx=1&colormap_name=above"
)
assert response.status_code == 200
assert response.headers["content-type"] == "image/png"

cmap = urlencode(
{
"colormap": json.dumps(
{
"1": [58, 102, 24, 255],
"2": [100, 177, 41],
"3": "#b1b129",
"4": "#ddcb9aFF",
}
)
}
)
response = app.get(
"/cog/tiles/8/53/50.png?url=https://myurl.com/above_cog.tif&bidx=1&color_map=above"
f"/cog/tiles/8/53/50.png?url=https://myurl.com/above_cog.tif&bidx=1&{cmap}"
)
assert response.status_code == 200
assert response.headers["content-type"] == "image/png"

response = app.get(
"/cog/tiles/8/53/50.png?url=https://myurl.com/above_cog.tif&bidx=1&color_map=above&resampling_method=somethingwrong"
"/cog/tiles/8/53/50.png?url=https://myurl.com/above_cog.tif&bidx=1&colormap_name=above&resampling_method=somethingwrong"
)
assert response.status_code == 422

Expand All @@ -241,6 +262,7 @@ def test_tilejson(rio, app):
assert body["tilejson"] == "2.2.0"
assert body["version"] == "1.0.0"
assert body["scheme"] == "xyz"
assert body["name"] == "cog.tif"
assert len(body["tiles"]) == 1
assert body["tiles"][0].startswith(
"http://testserver/cog/tiles/WebMercatorQuad/{z}/{x}/{y}@1x?url=https"
Expand All @@ -259,6 +281,24 @@ def test_tilejson(rio, app):
"http://testserver/cog/tiles/WebMercatorQuad/{z}/{x}/{y}@2x.png?url=https"
)

cmap_dict = {
"1": [58, 102, 24, 255],
"2": [100, 177, 41],
"3": "#b1b129",
"4": "#ddcb9aFF",
}
cmap = urlencode({"colormap": json.dumps(cmap_dict)})
response = app.get(
f"/cog/tilejson.json?url=https://myurl.com/above_cog.tif&bidx=1&{cmap}"
)
assert response.status_code == 200
body = response.json()
assert body["tiles"][0].startswith(
"http://testserver/cog/tiles/WebMercatorQuad/{z}/{x}/{y}@1x?url=https"
)
query = dict(parse_qsl(urlparse(body["tiles"][0]).query))
assert json.loads(query["colormap"]) == cmap_dict


@patch("rio_tiler.io.cogeo.rasterio")
def test_preview(rio, app):
Expand Down
2 changes: 1 addition & 1 deletion tests/routes/test_mosaic.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def test_tile(app):
params={
"url": MOSAICJSON_FILE,
"rescale": "0,1000",
"color_map": "viridis",
"colormap_name": "viridis",
"bidx": 1,
},
)
Expand Down
15 changes: 8 additions & 7 deletions tests/test_CustomCmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,18 @@
"cmap1": {6: (4, 5, 6, 255)},
}
cmap = ColorMaps(data=cmap_values)
ColorMapNames = Enum( # type: ignore
"ColorMapNames", [(a, a) for a in sorted(cmap.list())]
ColorMapName = Enum( # type: ignore
"ColorMapName", [(a, a) for a in sorted(cmap.list())]
)


def ColorMapParams(
color_map: ColorMapNames = Query(None, description="Colormap name",)
colormap_name: ColorMapName = Query(None, description="Colormap name"),
) -> Optional[Dict]:
"""Colormap Dependency."""
if color_map:
return cmap.get(color_map.value)
if colormap_name:
return cmap.get(colormap_name.value)

return None


Expand All @@ -41,7 +42,7 @@ def test_CustomCmap():
client = TestClient(app)

response = client.get(
f"/preview.npy?url={DATA_DIR}/above_cog.tif&bidx=1&color_map=cmap1"
f"/preview.npy?url={DATA_DIR}/above_cog.tif&bidx=1&colormap_name=cmap1"
)
assert response.status_code == 200
assert response.headers["content-type"] == "application/x-binary"
Expand All @@ -51,6 +52,6 @@ def test_CustomCmap():
assert 6 in data[2]

response = client.get(
f"/preview.npy?url={DATA_DIR}/above_cog.tif&bidx=1&color_map=another_cmap"
f"/preview.npy?url={DATA_DIR}/above_cog.tif&bidx=1&colormap_name=another_cmap"
)
assert response.status_code == 422
17 changes: 13 additions & 4 deletions titiler/dependencies.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Common dependency."""

import json
import re
from dataclasses import dataclass, field
from enum import Enum
Expand All @@ -9,7 +10,7 @@
from morecantile import tms
from morecantile.models import TileMatrixSet
from rasterio.enums import Resampling
from rio_tiler.colormap import cmap
from rio_tiler.colormap import cmap, parse_color
from rio_tiler.errors import MissingAssets, MissingBands

from .custom import cmap as custom_colormap
Expand Down Expand Up @@ -69,11 +70,19 @@ def TMSParams(


def ColorMapParams(
color_map: ColorMapName = Query(None, description="Colormap name",)
colormap_name: ColorMapName = Query(None, description="Colormap name"),
colormap: str = Query(None, description="JSON encoded custom Colormap"),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

breaking change

) -> Optional[Dict]:
"""Colormap Dependency."""
if color_map:
return cmap.get(color_map.value)
if colormap_name:
return cmap.get(colormap_name.value)

if colormap:
return json.loads(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we also accept base64 encoded string?

Copy link
Contributor

@geospatial-jeff geospatial-jeff Mar 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

neither are great, but I think base64 is less weird than JSON for get requests

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another way maybe would be to pass a list of value like

colormap=1,#FFFF00&colormap=2,#FF00FF

#equivalent of

colormap={"1": "#FFFF00", "2":"#FF00FF"}

colormap,
object_hook=lambda x: {int(k): parse_color(v) for k, v in x.items()},
)

return None


Expand Down
15 changes: 7 additions & 8 deletions titiler/endpoints/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import os
from dataclasses import dataclass, field
from typing import Any, Callable, Dict, List, Optional, Type
from urllib.parse import urlencode
from urllib.parse import urlencode, urlparse

import rasterio
from cogeo_mosaic.backends import BaseBackend, MosaicBackend
Expand Down Expand Up @@ -325,7 +325,6 @@ def tile(
**kwargs,
)
dst_colormap = getattr(src_dst, "colormap", None)

timings.append(("dataread", round(t.elapsed * 1000, 2)))

if not format:
Expand Down Expand Up @@ -423,7 +422,7 @@ def tilejson(
"center": tuple(center),
"minzoom": minzoom if minzoom is not None else src_dst.minzoom,
"maxzoom": maxzoom if maxzoom is not None else src_dst.maxzoom,
"name": os.path.basename(src_path),
"name": urlparse(src_path).path.lstrip("/") or "cogeotif",
"tiles": [tiles_url],
}

Expand Down Expand Up @@ -590,7 +589,7 @@ def preview(
**dataset_params.kwargs,
**kwargs,
)
colormap = colormap or getattr(src_dst, "colormap", None)
dst_colormap = getattr(src_dst, "colormap", None)
timings.append(("dataread", round(t.elapsed * 1000, 2)))

if not format:
Expand All @@ -607,7 +606,7 @@ def preview(
content = image.render(
add_mask=render_params.return_mask,
img_format=format.driver,
colormap=colormap,
colormap=colormap or dst_colormap,
**format.profile,
**render_params.kwargs,
)
Expand Down Expand Up @@ -661,7 +660,7 @@ def part(
**dataset_params.kwargs,
**kwargs,
)
colormap = colormap or getattr(src_dst, "colormap", None)
dst_colormap = getattr(src_dst, "colormap", None)
timings.append(("dataread", round(t.elapsed * 1000, 2)))

with utils.Timer() as t:
Expand All @@ -675,7 +674,7 @@ def part(
content = image.render(
add_mask=render_params.return_mask,
img_format=format.driver,
colormap=colormap,
colormap=colormap or dst_colormap,
**format.profile,
**render_params.kwargs,
)
Expand Down Expand Up @@ -1219,7 +1218,7 @@ def tilejson(
"center": tuple(center),
"minzoom": minzoom if minzoom is not None else src_dst.minzoom,
"maxzoom": maxzoom if maxzoom is not None else src_dst.maxzoom,
"name": os.path.basename(src_path),
"name": urlparse(src_path).path.lstrip("/") or "mosaic",
"tiles": [tiles_url],
}

Expand Down
2 changes: 1 addition & 1 deletion titiler/templates/cog_index.html
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@
params.rescale = `${minV},${maxV}`
}
const cmap = document.getElementById('colormap-selector')[document.getElementById('colormap-selector').selectedIndex]
if (cmap.value !== 'b&w') params.color_map = cmap.value
if (cmap.value !== 'b&w') params.colormap_name = cmap.value

const url_params = Object.keys(params).map(i => `${i}=${params[i]}`).join('&')
let url = `${tilejson_endpoint}?${url_params}`
Expand Down
2 changes: 1 addition & 1 deletion titiler/templates/stac_index.html
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@
}

const cmap = document.getElementById('colormap-selector')[document.getElementById('colormap-selector').selectedIndex]
if (cmap.value !== 'b&w') params.color_map = cmap.value
if (cmap.value !== 'b&w') params.colormap_name = cmap.value

const url_params = Object.keys(params).map(i => `${i}=${params[i]}`).join('&')
let url = `${tilejson_endpoint}?${url_params}`
Expand Down