Last active
September 18, 2023 20:41
-
-
Save jdbcode/90021653ba0cac8a6eede87b8062f1a7 to your computer and use it in GitHub Desktop.
convert_js_to_py.ipynb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "view-in-github", | |
"colab_type": "text" | |
}, | |
"source": [ | |
"<a href=\"https://colab.research.google.com/gist/jdbcode/90021653ba0cac8a6eede87b8062f1a7/convert_js_to_py.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "bra_MEILR0OW" | |
}, | |
"source": [ | |
"This Colab notebook is for converting Earth Engine JavaScript code examples to Python (with geemap for UI).\n", | |
"See this doc for more resources on conversion: https://docs.google.com/document/d/1ih_VzZPSqI6fVUvvF6pAluyduHkI1B6ReRxQ-lLRo2o/edit?usp=sharing" | |
], | |
"id": "bra_MEILR0OW" | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"Install Python code style library [Black](https://black.readthedocs.io/en/stable/index.html)." | |
], | |
"metadata": { | |
"id": "-seP7sBBrKrL" | |
}, | |
"id": "-seP7sBBrKrL" | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"!pip install -q black" | |
], | |
"metadata": { | |
"id": "PQ306YSfrJod" | |
}, | |
"id": "PQ306YSfrJod", | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"Imports and Earth Engine setup." | |
], | |
"metadata": { | |
"id": "wbJsQOSVrWwL" | |
}, | |
"id": "wbJsQOSVrWwL" | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"import datetime\n", | |
"import re\n", | |
"import geemap\n", | |
"import ee\n", | |
"ee.Authenticate()\n", | |
"ee.Initialize()" | |
], | |
"metadata": { | |
"id": "IXpr-1Pd6iHG" | |
}, | |
"id": "IXpr-1Pd6iHG", | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"# @title ##### JavaScript to Python functions.\n", | |
"\n", | |
"def misc_replacement(input_string):\n", | |
" \"\"\"Makes misc replacements.\"\"\"\n", | |
"\n", | |
" replacements = [\n", | |
" (\"/**\", \"\"),\n", | |
" (\"\\n */\", \".\"),\n", | |
" (\"..\\n\", \".\\n\"),\n", | |
" (\" *\", \"#\"),\n", | |
" (\"//\", \"#\"),\n", | |
" (\"# https\", \"# https\"),\n", | |
" (\"@fileoverview\", \"\"),\n", | |
" (\"https:#\", \"https://\"),\n", | |
" (\"http:#\", \"https://\"),\n", | |
" (\"print(\", \"display(\"),\n", | |
" (\";\", \"\"),\n", | |
" (\"\\\"License\\\")\", \"\\\"License\\\");\"),\n", | |
" (\"var \", \"\"),\n", | |
" (\"Map.addLayer\", \"m.add_ee_layer\"),\n", | |
" (\"Map.setCenter\", \"m.set_center\"),\n", | |
" (\"Map.centerObject\", \"m.center_object\"),\n", | |
" (\"and(\", \"And(\"),\n", | |
" (\"or(\", \"Or(\"),\n", | |
" (\"not(\", \"Not(\"),\n", | |
" (\"false\", \"False\"),\n", | |
" (\"true\", \"True\"),\n", | |
" (\"null\", \"None\"),\n", | |
" (\"min:\", \"'min':\"),\n", | |
" (\"max:\", \"'max':\"),\n", | |
" (\"color:\", \"'color':\"),\n", | |
" (\"bands:\", \"'bands':\"),\n", | |
" (\"palette:\", \"'palette':\"),\n", | |
" (\"gamma:\", \"'gamma':\"),\n", | |
" ]\n", | |
"\n", | |
" for old_str, new_str in replacements:\n", | |
" input_string = input_string.replace(old_str, new_str)\n", | |
"\n", | |
" return input_string\n", | |
"\n", | |
"\n", | |
"def update_copyright_year(input_string):\n", | |
" current_year = datetime.datetime.now().year\n", | |
" updated_string = re.sub(r'Copyright \\d{4}', f'Copyright {current_year}', input_string)\n", | |
" return updated_string\n", | |
"\n", | |
"\n", | |
"def convert_var_snake_case(input_string):\n", | |
" \"\"\"Converts camelCase var names to snake case.\"\"\"\n", | |
"\n", | |
" lines = input_string.split('\\n')\n", | |
" variables = {}\n", | |
"\n", | |
" for i, line in enumerate(lines):\n", | |
" if line.startswith(\"var\"):\n", | |
" match = re.search(r'\\bvar\\s+(\\w+)\\s*=', line)\n", | |
" if match:\n", | |
" original_var_name = match.group(1)\n", | |
" snake_var_name = re.sub(r'(?<=[0-9])(?=[A-Za-z])|(?<=[A-Za-z])(?=[0-9])', r'_', original_var_name)\n", | |
" snake_var_name = re.sub(r'(?<=[a-z0-9])([A-Z])', r'_\\1', snake_var_name)\n", | |
" snake_var_name = snake_var_name.lower()\n", | |
" variables[original_var_name] = snake_var_name\n", | |
"\n", | |
" for orig_var, snake_var in variables.items():\n", | |
" input_string = input_string.replace(orig_var, snake_var)\n", | |
"\n", | |
" return input_string\n", | |
"\n", | |
"\n", | |
"def single_line_chain(input_string):\n", | |
" lines = input_string.split('\\n')\n", | |
" output_lines = []\n", | |
"\n", | |
" for i, line in enumerate(lines):\n", | |
" if i > 0 and line.strip().startswith('.'):\n", | |
" output_lines[-1] = output_lines[-1].rstrip() + ' \\\\'\n", | |
" output_lines.append(line)\n", | |
"\n", | |
" return '\\n'.join(output_lines)\n", | |
"\n", | |
"\n", | |
"def fix_map_function_lines(input_string):\n", | |
" lines = input_string.split('\\n')\n", | |
" output_lines = []\n", | |
"\n", | |
" for line in lines:\n", | |
" if \"map(function(\" in line:\n", | |
" line += \" <--- FIX THIS FUNCTION\"\n", | |
" output_lines.append(line)\n", | |
"\n", | |
" return '\\n'.join(output_lines)\n", | |
"\n", | |
"\n", | |
"def add_a_map(input_string):\n", | |
" lines = input_string.split('\\n')\n", | |
" output_lines = []\n", | |
" append_m = True\n", | |
"\n", | |
" for line in lines:\n", | |
" if line.strip().startswith(\"m.\") and append_m:\n", | |
" output_lines.append(\"m = geemap.Map()\")\n", | |
" append_m = False\n", | |
" output_lines.append(line)\n", | |
"\n", | |
"\n", | |
" string = '\\n'.join(output_lines)\n", | |
" # if not append_m:\n", | |
" # string = f'{string}m'\n", | |
"\n", | |
" return string\n", | |
"\n", | |
"def display_a_map(input_string):\n", | |
" lines = input_string.split('\\n')\n", | |
" last_m_index = -1\n", | |
"\n", | |
" for i, line in enumerate(lines):\n", | |
" if line.strip().startswith('m.'):\n", | |
" last_m_index = i\n", | |
"\n", | |
" if last_m_index != -1:\n", | |
" lines.insert(last_m_index + 1, 'm')\n", | |
"\n", | |
" updated_string = '\\n'.join(lines)\n", | |
" return updated_string\n", | |
"\n", | |
"\n", | |
"def replace_dict_with_params(input_string):\n", | |
" end_comma_pattern = r',(\\s*\\n\\s*})'\n", | |
" input_string = re.sub(end_comma_pattern, r'\\1', input_string)\n", | |
"\n", | |
"\n", | |
" dict_pattern = r'\\(\\n*\\s*\\{\\s*([^{}]+)\\s*\\}\\)'\n", | |
"\n", | |
" def replace(match):\n", | |
" key_value_pairs = match.group(1).split(',')\n", | |
" replaced_pairs = [f\"{pair.split(':')[0].strip()}={pair.split(':')[1].strip()}\" for pair in key_value_pairs]\n", | |
" return '(' + ', '.join(replaced_pairs) + ')'\n", | |
"\n", | |
" result_string = re.sub(dict_pattern, replace, input_string)\n", | |
" return result_string\n", | |
"\n", | |
"\n", | |
"def remove_newline(input_string):\n", | |
" pattern = r'=\\s*\\n+'\n", | |
" result = re.sub(pattern, '=', input_string)\n", | |
" return result\n", | |
"\n", | |
"\n", | |
"def convert_to_py(snippet, snake_case=True):\n", | |
" \"\"\"Converts common JS syntax to Py.\"\"\"\n", | |
"\n", | |
" if snake_case:\n", | |
" snippet = convert_var_snake_case(snippet)\n", | |
" snippet = convert_var_snake_case(snippet) # 2nd run is intended\n", | |
"\n", | |
" snippet = misc_replacement(snippet)\n", | |
" snippet = update_copyright_year(snippet)\n", | |
" snippet = single_line_chain(snippet)\n", | |
" snippet = fix_map_function_lines(snippet)\n", | |
" snippet = replace_dict_with_params(snippet)\n", | |
" snippet = snippet.replace(\"'color'=\", \"color=\")\n", | |
" snippet = remove_newline(snippet)\n", | |
"\n", | |
" if \"m.add_ee_layer\" in snippet:\n", | |
" snippet = add_a_map(snippet)\n", | |
" snippet = display_a_map(snippet)\n", | |
"\n", | |
" return '# Python code converted from JavaScript\\n\\n\\n' + snippet\n" | |
], | |
"metadata": { | |
"id": "FKsLZPWCj9-v", | |
"cellView": "form" | |
}, | |
"id": "FKsLZPWCj9-v", | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "rjIpILWBR0OY" | |
}, | |
"source": [ | |
"#### 1. Copy and paste JavaScript code into a code block wrapped with trip quotes; declare as a variable." | |
], | |
"id": "rjIpILWBR0OY" | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"id": "W5eqqkbwR0OY" | |
}, | |
"outputs": [], | |
"source": [ | |
"js_snippet = \"\"\"\n", | |
"var dataset = ee.ImageCollection('IDAHO_EPSCOR/TERRACLIMATE')\n", | |
" .filter(ee.Filter.date('2017-07-01', '2017-08-01'));\n", | |
"var maximumTemperature = dataset.select('tmmx');\n", | |
"var maximumTemperatureVis = {\n", | |
" min: -300.0,\n", | |
" max: 300.0,\n", | |
" palette: [\n", | |
" '1a3678', '2955bc', '5699ff', '8dbae9', 'acd1ff', 'caebff', 'e5f9ff',\n", | |
" 'fdffb4', 'ffe6a2', 'ffc969', 'ffa12d', 'ff7c1f', 'ca531a', 'ff0000',\n", | |
" 'ab0000'\n", | |
" ],\n", | |
"};\n", | |
"Map.setCenter(71.72, 52.48, 3);\n", | |
"Map.addLayer(maximumTemperature, maximumTemperatureVis, 'Maximum Temperature');\n", | |
"\"\"\"" | |
], | |
"id": "W5eqqkbwR0OY" | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"#### 2. Run the following cell to convert JavaScript to Python, copy the result into the next code cell." | |
], | |
"metadata": { | |
"id": "lWtpPvx0Stio" | |
}, | |
"id": "lWtpPvx0Stio" | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"py_snippet = convert_to_py(js_snippet, snake_case=True)\n", | |
"print(py_snippet)" | |
], | |
"metadata": { | |
"id": "IBc5pTIzkMe3" | |
}, | |
"id": "IBc5pTIzkMe3", | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"#### 3. Paste output from above into code cell below and attempt to run." | |
], | |
"metadata": { | |
"id": "3GPUPd1lTYS4" | |
}, | |
"id": "3GPUPd1lTYS4" | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"# @title ##### Things to check for (converter should catch most):\n", | |
"\n", | |
"\"\"\"\n", | |
"### Constructing a `geemap.Map` object ###\n", | |
"# If the code sample displays map layers, you'll need to define a map object (right before the first time it is used)\n", | |
"m = geemap.Map()\n", | |
"m.set_center(-114.2579, 38.9275, 8)\n", | |
"m.add_ee_layer(dataset, visualization, 'True Color (432)')\n", | |
"m\n", | |
"\n", | |
"\n", | |
"### Multiline method chaining ###\n", | |
"# You'll need to wrap these in parenthesis (don't use \\\\ line continuation):\n", | |
"obj = (my.really()\n", | |
" .long()\n", | |
" .method()\n", | |
" .chain())\n", | |
"\n", | |
"\n", | |
"### Dictionary arguments ###\n", | |
"# You'll maybe need to add quotes around key names:\n", | |
"obj = ee.Dictionary({\"key\": value})\n", | |
"\n", | |
"\n", | |
"### Named parameters ###\n", | |
"# You'll need to use parameter names instead of dictionary:\n", | |
"obj = ee.ReduceRegion(\n", | |
" reducer=ee.Reducer.max(),\n", | |
" geometry=ee.Geometry.Point(0, 0),\n", | |
" scale=1)\n", | |
"\n", | |
"\n", | |
"### Named function definition ###\n", | |
"# You'll need to use Python syntax for named functions:\n", | |
"def fun_name(foo, bar):\n", | |
" print(foo, bar)\n", | |
" return None\n", | |
"\n", | |
"\n", | |
"### Anonymous function ###\n", | |
"# You'll need to use Python syntax for anonymous functions (use named function if multiline):\n", | |
"foo = col.map(lambda arg: arg)\n", | |
"\"\"\"" | |
], | |
"metadata": { | |
"cellView": "form", | |
"id": "gMFKLokB_VK6" | |
}, | |
"id": "gMFKLokB_VK6", | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"# Python code converted from JavaScript\n", | |
"\n", | |
"\n", | |
"\n", | |
"dataset = ee.ImageCollection('IDAHO_EPSCOR/TERRACLIMATE') \\\n", | |
" .filter(ee.Filter.date('2017-07-01', '2017-08-01'))\n", | |
"maximum_temperature = dataset.select('tmmx')\n", | |
"maximum_temperature_vis = {\n", | |
" 'min': -300.0,\n", | |
" 'max': 300.0,\n", | |
" 'palette': [\n", | |
" '1a3678', '2955bc', '5699ff', '8dbae9', 'acd1ff', 'caebff', 'e5f9ff',\n", | |
" 'fdffb4', 'ffe6a2', 'ffc969', 'ffa12d', 'ff7c1f', 'ca531a', 'ff0000',\n", | |
" 'ab0000'\n", | |
" ]\n", | |
"}\n", | |
"m = geemap.Map()\n", | |
"m.set_center(71.72, 52.48, 3)\n", | |
"m.add_ee_layer(maximum_temperature, maximum_temperature_vis, 'Maximum Temperature')\n", | |
"m\n" | |
], | |
"metadata": { | |
"id": "BmVr7ITltKYr" | |
}, | |
"id": "BmVr7ITltKYr", | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"#### 4. Style the code according to [Black](https://black.readthedocs.io/en/stable/index.html).\n", | |
"\n" | |
], | |
"metadata": { | |
"id": "QHA5s83Bte0i" | |
}, | |
"id": "QHA5s83Bte0i" | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"converted_code = [\n", | |
" s for s in _ih if s.startswith(\"# Python code converted from JavaScript\")\n", | |
"][-1].replace(\"# Python code converted from JavaScript\", \"\")\n", | |
"\n", | |
"!black --code \"{converted_code}\"" | |
], | |
"metadata": { | |
"id": "SAD8QdYQvunM" | |
}, | |
"id": "SAD8QdYQvunM", | |
"execution_count": null, | |
"outputs": [] | |
} | |
], | |
"metadata": { | |
"kernelspec": { | |
"display_name": "Python 3", | |
"language": "python", | |
"name": "python3" | |
}, | |
"colab": { | |
"provenance": [], | |
"toc_visible": true, | |
"include_colab_link": true | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 5 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment