Skip to content

Instantly share code, notes, and snippets.

@BibMartin
Created September 1, 2021 09:41
Show Gist options
  • Save BibMartin/f538d656d44ee6c938ccde3af936b809 to your computer and use it in GitHub Desktop.
Save BibMartin/f538d656d44ee6c938ccde3af936b809 to your computer and use it in GitHub Desktop.
A folium element transform the tiles into grayscale (based on https://github.com/Zverik/leaflet-grayscale/)
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"code_folding": []
},
"outputs": [
{
"data": {
"text/html": [
"<div style=\"width:100%;\"><div style=\"position:relative;width:100%;height:0;padding-bottom:60%;\"><span style=\"color:#565656\">Make this Notebook Trusted to load map: File -> Trust Notebook</span><iframe src=\"about:blank\" style=\"position:absolute;width:100%;height:100%;left:0;top:0;border:none !important;\" data-html=PCFET0NUWVBFIGh0bWw+CjxoZWFkPiAgICAKICAgIDxtZXRhIGh0dHAtZXF1aXY9ImNvbnRlbnQtdHlwZSIgY29udGVudD0idGV4dC9odG1sOyBjaGFyc2V0PVVURi04IiAvPgogICAgCiAgICAgICAgPHNjcmlwdD4KICAgICAgICAgICAgTF9OT19UT1VDSCA9IGZhbHNlOwogICAgICAgICAgICBMX0RJU0FCTEVfM0QgPSBmYWxzZTsKICAgICAgICA8L3NjcmlwdD4KICAgIAogICAgPHNjcmlwdCBzcmM9Imh0dHBzOi8vY2RuLmpzZGVsaXZyLm5ldC9ucG0vbGVhZmxldEAxLjYuMC9kaXN0L2xlYWZsZXQuanMiPjwvc2NyaXB0PgogICAgPHNjcmlwdCBzcmM9Imh0dHBzOi8vY29kZS5qcXVlcnkuY29tL2pxdWVyeS0xLjEyLjQubWluLmpzIj48L3NjcmlwdD4KICAgIDxzY3JpcHQgc3JjPSJodHRwczovL21heGNkbi5ib290c3RyYXBjZG4uY29tL2Jvb3RzdHJhcC8zLjIuMC9qcy9ib290c3RyYXAubWluLmpzIj48L3NjcmlwdD4KICAgIDxzY3JpcHQgc3JjPSJodHRwczovL2NkbmpzLmNsb3VkZmxhcmUuY29tL2FqYXgvbGlicy9MZWFmbGV0LmF3ZXNvbWUtbWFya2Vycy8yLjAuMi9sZWFmbGV0LmF3ZXNvbWUtbWFya2Vycy5qcyI+PC9zY3JpcHQ+CiAgICA8bGluayByZWw9InN0eWxlc2hlZXQiIGhyZWY9Imh0dHBzOi8vY2RuLmpzZGVsaXZyLm5ldC9ucG0vbGVhZmxldEAxLjYuMC9kaXN0L2xlYWZsZXQuY3NzIi8+CiAgICA8bGluayByZWw9InN0eWxlc2hlZXQiIGhyZWY9Imh0dHBzOi8vbWF4Y2RuLmJvb3RzdHJhcGNkbi5jb20vYm9vdHN0cmFwLzMuMi4wL2Nzcy9ib290c3RyYXAubWluLmNzcyIvPgogICAgPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiBocmVmPSJodHRwczovL21heGNkbi5ib290c3RyYXBjZG4uY29tL2Jvb3RzdHJhcC8zLjIuMC9jc3MvYm9vdHN0cmFwLXRoZW1lLm1pbi5jc3MiLz4KICAgIDxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iaHR0cHM6Ly9tYXhjZG4uYm9vdHN0cmFwY2RuLmNvbS9mb250LWF3ZXNvbWUvNC42LjMvY3NzL2ZvbnQtYXdlc29tZS5taW4uY3NzIi8+CiAgICA8bGluayByZWw9InN0eWxlc2hlZXQiIGhyZWY9Imh0dHBzOi8vY2RuanMuY2xvdWRmbGFyZS5jb20vYWpheC9saWJzL0xlYWZsZXQuYXdlc29tZS1tYXJrZXJzLzIuMC4yL2xlYWZsZXQuYXdlc29tZS1tYXJrZXJzLmNzcyIvPgogICAgPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiBocmVmPSJodHRwczovL3Jhd2Nkbi5naXRoYWNrLmNvbS9weXRob24tdmlzdWFsaXphdGlvbi9mb2xpdW0vbWFzdGVyL2ZvbGl1bS90ZW1wbGF0ZXMvbGVhZmxldC5hd2Vzb21lLnJvdGF0ZS5jc3MiLz4KICAgIDxzdHlsZT5odG1sLCBib2R5IHt3aWR0aDogMTAwJTtoZWlnaHQ6IDEwMCU7bWFyZ2luOiAwO3BhZGRpbmc6IDA7fTwvc3R5bGU+CiAgICA8c3R5bGU+I21hcCB7cG9zaXRpb246YWJzb2x1dGU7dG9wOjA7Ym90dG9tOjA7cmlnaHQ6MDtsZWZ0OjA7fTwvc3R5bGU+CiAgICAKICAgICAgICAgICAgPG1ldGEgbmFtZT0idmlld3BvcnQiIGNvbnRlbnQ9IndpZHRoPWRldmljZS13aWR0aCwKICAgICAgICAgICAgICAgIGluaXRpYWwtc2NhbGU9MS4wLCBtYXhpbXVtLXNjYWxlPTEuMCwgdXNlci1zY2FsYWJsZT1ubyIgLz4KICAgICAgICAgICAgPHN0eWxlPgogICAgICAgICAgICAgICAgI21hcF80YzZmODJkNDVhZDQ0ZWY3YjZhZjAxOTk4NWVkN2U5MSB7CiAgICAgICAgICAgICAgICAgICAgcG9zaXRpb246IHJlbGF0aXZlOwogICAgICAgICAgICAgICAgICAgIHdpZHRoOiAxMDAuMCU7CiAgICAgICAgICAgICAgICAgICAgaGVpZ2h0OiAxMDAuMCU7CiAgICAgICAgICAgICAgICAgICAgbGVmdDogMC4wJTsKICAgICAgICAgICAgICAgICAgICB0b3A6IDAuMCU7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIDwvc3R5bGU+CiAgICAgICAgCiAgICAKICAgICAgPHNjcmlwdD4KICAgICAgICAvKgogICAgICAgICAqIEwuVGlsZUxheWVyLkdyYXlzY2FsZSBpcyBhIHJlZ3VsYXIgdGlsZWxheWVyIHdpdGggZ3JheXNjYWxlIG1ha2VvdmVyLgogICAgICAgICAqLwoKICAgICAgICBMLlRpbGVMYXllci5HcmF5c2NhbGUgPSBMLlRpbGVMYXllci5leHRlbmQoewogICAgICAgICAgICBvcHRpb25zOiB7CiAgICAgICAgICAgICAgICBxdW90YVJlZDogMjEsCiAgICAgICAgICAgICAgICBxdW90YUdyZWVuOiA3MSwKICAgICAgICAgICAgICAgIHF1b3RhQmx1ZTogOCwKICAgICAgICAgICAgICAgIHF1b3RhRGl2aWRlclR1bmU6IDAsCiAgICAgICAgICAgICAgICBxdW90YURpdmlkZXI6IGZ1bmN0aW9uKCkgewogICAgICAgICAgICAgICAgICAgIHJldHVybiB0aGlzLnF1b3RhUmVkICsgdGhpcy5xdW90YUdyZWVuICsgdGhpcy5xdW90YUJsdWUgKyB0aGlzLnF1b3RhRGl2aWRlclR1bmU7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0sCgogICAgICAgICAgICBpbml0aWFsaXplOiBmdW5jdGlvbiAodXJsLCBvcHRpb25zKSB7CiAgICAgICAgICAgICAgICBvcHRpb25zID0gb3B0aW9ucyB8fCB7fQogICAgICAgICAgICAgICAgb3B0aW9ucy5jcm9zc09yaWdpbiA9IHRydWU7CiAgICAgICAgICAgICAgICBMLlRpbGVMYXllci5wcm90b3R5cGUuaW5pdGlhbGl6ZS5jYWxsKHRoaXMsIHVybCwgb3B0aW9ucyk7CgogICAgICAgICAgICAgICAgdGhpcy5vbigndGlsZWxvYWQnLCBmdW5jdGlvbihlKSB7CiAgICAgICAgICAgICAgICAgICAgdGhpcy5fbWFrZUdyYXlzY2FsZShlLnRpbGUpOwogICAgICAgICAgICAgICAgfSk7CiAgICAgICAgICAgIH0sCgogICAgICAgICAgICBfY3JlYXRlVGlsZTogZnVuY3Rpb24gKCkgewogICAgICAgICAgICAgICAgdmFyIHRpbGUgPSBMLlRpbGVMYXllci5wcm90b3R5cGUuX2NyZWF0ZVRpbGUuY2FsbCh0aGlzKTsKICAgICAgICAgICAgICAgIHRpbGUuY3Jvc3NPcmlnaW4gPSAiQW5vbnltb3VzIjsKICAgICAgICAgICAgICAgIHJldHVybiB0aWxlOwogICAgICAgICAgICB9LAoKICAgICAgICAgICAgX21ha2VHcmF5c2NhbGU6IGZ1bmN0aW9uIChpbWcpIHsKICAgICAgICAgICAgICAgIGlmIChpbWcuZ2V0QXR0cmlidXRlKCdkYXRhLWdyYXlzY2FsZWQnKSkKICAgICAgICAgICAgICAgICAgICByZXR1cm47CgogICAgICAgICAgICAgICAgaW1nLmNyb3NzT3JpZ2luID0gJyc7CiAgICAgICAgICAgICAgICB2YXIgY2FudmFzID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiY2FudmFzIik7CiAgICAgICAgICAgICAgICBjYW52YXMud2lkdGggPSBpbWcud2lkdGg7CiAgICAgICAgICAgICAgICBjYW52YXMuaGVpZ2h0ID0gaW1nLmhlaWdodDsKICAgICAgICAgICAgICAgIHZhciBjdHggPSBjYW52YXMuZ2V0Q29udGV4dCgiMmQiKTsKICAgICAgICAgICAgICAgIGN0eC5kcmF3SW1hZ2UoaW1nLCAwLCAwKTsKCiAgICAgICAgICAgICAgICB2YXIgaW1nZCA9IGN0eC5nZXRJbWFnZURhdGEoMCwgMCwgY2FudmFzLndpZHRoLCBjYW52YXMuaGVpZ2h0KTsKICAgICAgICAgICAgICAgIHZhciBwaXggPSBpbWdkLmRhdGE7CiAgICAgICAgICAgICAgICBmb3IgKHZhciBpID0gMCwgbiA9IHBpeC5sZW5ndGg7IGkgPCBuOyBpICs9IDQpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwaXhbaV0gPSBwaXhbaSArIDFdID0gcGl4W2kgKyAyXSA9ICh0aGlzLm9wdGlvbnMucXVvdGFSZWQgKiBwaXhbaV0gKyB0aGlzLm9wdGlvbnMucXVvdGFHcmVlbiAqIHBpeFtpICsgMV0gKyB0aGlzLm9wdGlvbnMucXVvdGFCbHVlICogcGl4W2kgKyAyXSkgLyB0aGlzLm9wdGlvbnMucXVvdGFEaXZpZGVyKCk7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICBjdHgucHV0SW1hZ2VEYXRhKGltZ2QsIDAsIDApOwogICAgICAgICAgICAgICAgaW1nLnNldEF0dHJpYnV0ZSgnZGF0YS1ncmF5c2NhbGVkJywgdHJ1ZSk7CiAgICAgICAgICAgICAgICBpbWcuc3JjID0gY2FudmFzLnRvRGF0YVVSTCgpOwogICAgICAgICAgICB9CiAgICAgICAgfSk7CgogICAgICAgIEwudGlsZUxheWVyID0gZnVuY3Rpb24gKHVybCwgb3B0aW9ucykgewogICAgICAgICAgICByZXR1cm4gbmV3IEwuVGlsZUxheWVyLkdyYXlzY2FsZSh1cmwsIG9wdGlvbnMpOwogICAgICAgIH07CiAgICAgICAgPC9zY3JpcHQ+CiAgICAgICAgCjwvaGVhZD4KPGJvZHk+ICAgIAogICAgCiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImZvbGl1bS1tYXAiIGlkPSJtYXBfNGM2ZjgyZDQ1YWQ0NGVmN2I2YWYwMTk5ODVlZDdlOTEiID48L2Rpdj4KICAgICAgICAKPC9ib2R5Pgo8c2NyaXB0PiAgICAKICAgIAogICAgICAgICAgICB2YXIgbWFwXzRjNmY4MmQ0NWFkNDRlZjdiNmFmMDE5OTg1ZWQ3ZTkxID0gTC5tYXAoCiAgICAgICAgICAgICAgICAibWFwXzRjNmY4MmQ0NWFkNDRlZjdiNmFmMDE5OTg1ZWQ3ZTkxIiwKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICBjZW50ZXI6IFs0NS4wLCAwLjBdLAogICAgICAgICAgICAgICAgICAgIGNyczogTC5DUlMuRVBTRzM4NTcsCiAgICAgICAgICAgICAgICAgICAgem9vbTogNCwKICAgICAgICAgICAgICAgICAgICB6b29tQ29udHJvbDogdHJ1ZSwKICAgICAgICAgICAgICAgICAgICBwcmVmZXJDYW52YXM6IGZhbHNlLAogICAgICAgICAgICAgICAgfQogICAgICAgICAgICApOwoKICAgICAgICAgICAgCgogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciB0aWxlX2xheWVyXzQwZGUyMDU1NDBkZDQwNGViYWYxNGNiMzBjN2I0YWE1ID0gTC50aWxlTGF5ZXIoCiAgICAgICAgICAgICAgICAiaHR0cHM6Ly97c30udGlsZS5vcGVuc3RyZWV0bWFwLm9yZy97en0ve3h9L3t5fS5wbmciLAogICAgICAgICAgICAgICAgeyJhdHRyaWJ1dGlvbiI6ICJEYXRhIGJ5IFx1MDAyNmNvcHk7IFx1MDAzY2EgaHJlZj1cImh0dHA6Ly9vcGVuc3RyZWV0bWFwLm9yZ1wiXHUwMDNlT3BlblN0cmVldE1hcFx1MDAzYy9hXHUwMDNlLCB1bmRlciBcdTAwM2NhIGhyZWY9XCJodHRwOi8vd3d3Lm9wZW5zdHJlZXRtYXAub3JnL2NvcHlyaWdodFwiXHUwMDNlT0RiTFx1MDAzYy9hXHUwMDNlLiIsICJkZXRlY3RSZXRpbmEiOiBmYWxzZSwgIm1heE5hdGl2ZVpvb20iOiAxOCwgIm1heFpvb20iOiAxOCwgIm1pblpvb20iOiAwLCAibm9XcmFwIjogZmFsc2UsICJvcGFjaXR5IjogMSwgInN1YmRvbWFpbnMiOiAiYWJjIiwgInRtcyI6IGZhbHNlfQogICAgICAgICAgICApLmFkZFRvKG1hcF80YzZmODJkNDVhZDQ0ZWY3YjZhZjAxOTk4NWVkN2U5MSk7CiAgICAgICAgCjwvc2NyaXB0Pg== onload=\"this.contentDocument.open();this.contentDocument.write(atob(this.getAttribute('data-html')));this.contentDocument.close();\" allowfullscreen webkitallowfullscreen mozallowfullscreen></iframe></div></div>"
],
"text/plain": [
"<folium.folium.Map at 0x23ac9ee3ac8>"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import folium\n",
"from jinja2 import Template\n",
"\n",
"class GrayScaleTiles(folium.MacroElement):\n",
" \"\"\"Add this element to your map in order to have grayscale tiles.\n",
" \n",
" Based on https://github.com/Zverik/leaflet-grayscale/\n",
" \"\"\"\n",
" _name = \"GrayScaleTiles\"\n",
" _template = Template(\"\"\"\n",
" {% macro header(this, kwargs) %}\n",
" <script>\n",
" /*\n",
" * L.TileLayer.Grayscale is a regular tilelayer with grayscale makeover.\n",
" */\n",
"\n",
" L.TileLayer.Grayscale = L.TileLayer.extend({\n",
" options: {\n",
" quotaRed: 21,\n",
" quotaGreen: 71,\n",
" quotaBlue: 8,\n",
" quotaDividerTune: 0,\n",
" quotaDivider: function() {\n",
" return this.quotaRed + this.quotaGreen + this.quotaBlue + this.quotaDividerTune;\n",
" }\n",
" },\n",
"\n",
" initialize: function (url, options) {\n",
" options = options || {}\n",
" options.crossOrigin = true;\n",
" L.TileLayer.prototype.initialize.call(this, url, options);\n",
"\n",
" this.on('tileload', function(e) {\n",
" this._makeGrayscale(e.tile);\n",
" });\n",
" },\n",
"\n",
" _createTile: function () {\n",
" var tile = L.TileLayer.prototype._createTile.call(this);\n",
" tile.crossOrigin = \"Anonymous\";\n",
" return tile;\n",
" },\n",
"\n",
" _makeGrayscale: function (img) {\n",
" if (img.getAttribute('data-grayscaled'))\n",
" return;\n",
"\n",
" img.crossOrigin = '';\n",
" var canvas = document.createElement(\"canvas\");\n",
" canvas.width = img.width;\n",
" canvas.height = img.height;\n",
" var ctx = canvas.getContext(\"2d\");\n",
" ctx.drawImage(img, 0, 0);\n",
"\n",
" var imgd = ctx.getImageData(0, 0, canvas.width, canvas.height);\n",
" var pix = imgd.data;\n",
" for (var i = 0, n = pix.length; i < n; i += 4) {\n",
" pix[i] = pix[i + 1] = pix[i + 2] = (this.options.quotaRed * pix[i] + this.options.quotaGreen * pix[i + 1] + this.options.quotaBlue * pix[i + 2]) / this.options.quotaDivider();\n",
" }\n",
" ctx.putImageData(imgd, 0, 0);\n",
" img.setAttribute('data-grayscaled', true);\n",
" img.src = canvas.toDataURL();\n",
" }\n",
" });\n",
"\n",
" L.tileLayer = function (url, options) {\n",
" return new L.TileLayer.Grayscale(url, options);\n",
" };\n",
" </script>\n",
" {% endmacro %}\n",
" \"\"\")\n",
"\n",
"m = folium.Map([45,0], tiles='OpenStreetMap', zoom_start=4).add_child(GrayScaleTiles())\n",
"m"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.7"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
import folium
from jinja2 import Template
class GrayScaleTiles(folium.MacroElement):
"""Add this element to your map in order to have grayscale tiles.
Based on https://github.com/Zverik/leaflet-grayscale/
"""
_name = "GrayScaleTiles"
_template = Template("""
{% macro header(this, kwargs) %}
<script>
/*
* L.TileLayer.Grayscale is a regular tilelayer with grayscale makeover.
*/
L.TileLayer.Grayscale = L.TileLayer.extend({
options: {
quotaRed: 21,
quotaGreen: 71,
quotaBlue: 8,
quotaDividerTune: 0,
quotaDivider: function() {
return this.quotaRed + this.quotaGreen + this.quotaBlue + this.quotaDividerTune;
}
},
initialize: function (url, options) {
options = options || {}
options.crossOrigin = true;
L.TileLayer.prototype.initialize.call(this, url, options);
this.on('tileload', function(e) {
this._makeGrayscale(e.tile);
});
},
_createTile: function () {
var tile = L.TileLayer.prototype._createTile.call(this);
tile.crossOrigin = "Anonymous";
return tile;
},
_makeGrayscale: function (img) {
if (img.getAttribute('data-grayscaled'))
return;
img.crossOrigin = '';
var canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
var imgd = ctx.getImageData(0, 0, canvas.width, canvas.height);
var pix = imgd.data;
for (var i = 0, n = pix.length; i < n; i += 4) {
pix[i] = pix[i + 1] = pix[i + 2] = (this.options.quotaRed * pix[i] + this.options.quotaGreen * pix[i + 1] + this.options.quotaBlue * pix[i + 2]) / this.options.quotaDivider();
}
ctx.putImageData(imgd, 0, 0);
img.setAttribute('data-grayscaled', true);
img.src = canvas.toDataURL();
}
});
L.tileLayer = function (url, options) {
return new L.TileLayer.Grayscale(url, options);
};
</script>
{% endmacro %}
""")
m = folium.Map([45,0], tiles='OpenStreetMap', zoom_start=4).add_child(GrayScaleTiles())
m
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment