Skip to content

conversion module

Module for converting Google Earth Engine (GEE) JavaScripts to Python scripts and Jupyter notebooks. - To convert a GEE JavaScript to Python script: js_to_python(in_file out_file) - To convert all GEE JavaScripts in a folder recursively to Python scripts: js_to_python_dir(in_dir, out_dir) - To convert a GEE Python script to Jupyter notebook: py_to_ipynb(in_file, template_file, out_file) - To convert all GEE Python scripts in a folder recursively to Jupyter notebooks: py_to_ipynb_dir(in_dir, template_file, out_dir) - To execute a Jupyter notebook and save output cells: execute_notebook(in_file) - To execute all Jupyter notebooks in a folder recursively: execute_notebook_dir(in_dir)

check_map_functions(input_lines)

Extracts Earth Engine map function

Parameters:

Name Type Description Default
input_lines list

List of Earth Engine JavaScrips

required

Returns:

Type Description
list

Output JavaScript with map function

Source code in geemap/conversion.py
def check_map_functions(input_lines):
    """Extracts Earth Engine map function

    Args:
        input_lines (list): List of Earth Engine JavaScrips

    Returns:
        list: Output JavaScript with map function
    """
    output_lines = []
    currentNumOfNestedFuncs = 0
    for index, line in enumerate(input_lines):
        if (
            line.strip().endswith(".map(")
            and input_lines[index + 1].strip().replace(" ", "").startswith("function(")
        ) or (
            line.strip().endswith(".map(function(")
            and input_lines[index + 1].strip().replace(" ", "").endswith("{")
        ):
            input_lines[index + 1] = line + input_lines[index + 1]
            continue

        if (
            ".map(function" in line.replace(" ", "")
            or "returnfunction" in line.replace(" ", "")
            or "function(" in line.replace(" ", "")
        ):
            try:
                bracket_index = line.index("{")
                matching_line_index, matching_char_index = find_matching_bracket(
                    input_lines, index, bracket_index
                )

                func_start_index = line.index("function")
                func_name = "func_" + random_string()
                func_header = line[func_start_index:].replace(
                    "function", "function " + func_name
                )
                output_lines.append("\n")
                output_lines.append(func_header)

                currentNumOfNestedFuncs += 1

                new_lines = input_lines[index + 1 : matching_line_index]

                new_lines = check_map_functions(new_lines)

                for sub_index, tmp_line in enumerate(new_lines):
                    output_lines.append(("    " * currentNumOfNestedFuncs) + tmp_line)
                    if "{" in tmp_line:
                        currentNumOfNestedFuncs += 1
                    if "}" in tmp_line:
                        currentNumOfNestedFuncs -= 1
                    input_lines[index + 1 + sub_index] = ""

                currentNumOfNestedFuncs -= 1

                header_line = line[:func_start_index] + func_name
                header_line = header_line.rstrip()

                func_footer = input_lines[matching_line_index][
                    : matching_char_index + 1
                ]
                output_lines.append(func_footer)

                footer_line = input_lines[matching_line_index][
                    matching_char_index + 1 :
                ].strip()
                if footer_line == ")" or footer_line == ");":
                    header_line = header_line + footer_line
                    footer_line = ""

                input_lines[matching_line_index] = footer_line

                output_lines.append(header_line)
                # output_lines.append(footer_line)
            except Exception as e:
                print(
                    f"An error occurred: {e}. The closing curly bracket could not be found in Line {index+1}: {line}. Please reformat the function definition and make sure that both the opening and closing curly brackets appear on the same line as the function keyword. "
                )
        else:
            output_lines.append(line)

    return output_lines

convert_for_loop(line)

Converts JavaScript for loop to Python for loop.

Parameters:

Name Type Description Default
line str

Input JavaScript for loop

required

Returns:

Type Description
str

Converted Python for loop.

Source code in geemap/conversion.py
def convert_for_loop(line):
    """Converts JavaScript for loop to Python for loop.

    Args:
        line (str): Input JavaScript for loop

    Returns:
        str: Converted Python for loop.
    """
    new_line = ""
    if "var " in line:
        line = line.replace("var ", "")
    start_index = line.index("(")
    end_index = line.index(")")

    prefix = line[:(start_index)]
    suffix = line[(end_index + 1) :]

    params = line[(start_index + 1) : end_index]

    if " in " in params and params.count(";") == 0:
        new_line = prefix + "{}:".format(params) + suffix
        return new_line

    items = params.split("=")
    param_name = items[0].strip()
    items = params.split(";")

    subitems = []

    for item in items:
        subitems.append(item.split(" ")[-1])

    start = subitems[0]
    end = subitems[1]
    step = subitems[2]

    if "++" in step:
        step = 1
    elif "--" in step:
        step = -1

    prefix = line[:(start_index)]
    suffix = line[(end_index + 1) :]
    new_line = (
        prefix
        + "{} in range({}, {}, {}):".format(param_name, start, end, step)
        + suffix
    )

    return new_line

create_new_cell(contents, replace=False)

Create a new cell in Jupyter notebook based on the contents.

Parameters:

Name Type Description Default
contents str

A string of Python code.

required
Source code in geemap/conversion.py
def create_new_cell(contents, replace=False):
    """Create a new cell in Jupyter notebook based on the contents.

    Args:
        contents (str): A string of Python code.
    """
    from IPython.core.getipython import get_ipython

    shell = get_ipython()
    shell.set_next_input(contents, replace=replace)

download_gee_app(url, out_file=None)

Downloads JavaScript source code from a GEE App

Parameters:

Name Type Description Default
url str

The URL of the GEE App.

required
out_file str

The output file path for the downloaded JavaScript. Defaults to None.

None
Source code in geemap/conversion.py
def download_gee_app(url, out_file=None):
    """Downloads JavaScript source code from a GEE App

    Args:
        url (str): The URL of the GEE App.
        out_file (str, optional): The output file path for the downloaded JavaScript. Defaults to None.
    """
    cwd = os.getcwd()
    out_file_name = os.path.basename(url) + ".js"
    out_file_path = os.path.join(cwd, out_file_name)
    items = url.split("/")
    items[3] = "javascript"
    items[4] = items[4] + "-modules.json"
    json_url = "/".join(items)
    print(f"The json url: {json_url}")

    if out_file is not None:
        out_file_path = out_file
        if not out_file_path.endswith("js"):
            out_file_path += ".js"

    out_dir = os.path.dirname(out_file_path)
    if not os.path.exists(out_dir):
        os.makedirs(out_dir)

    json_path = out_file_path + "on"

    try:
        urllib.request.urlretrieve(json_url, json_path)
    except Exception:
        raise Exception("The URL is invalid. Please double check the URL.")

    with open(out_file_path, "w") as f1:
        with open(json_path, encoding="utf-8") as f2:
            lines = f2.readlines()
            for line in lines:
                # print(line)
                items = line.split("\\n")
                for index, item in enumerate(items):
                    if (index > 0) and (index < (len(items) - 1)):
                        item = item.replace('\\"', '"')
                        item = item.replace(r"\\", "\n")
                        item = item.replace("\\r", "")
                        f1.write(item + "\n")
    os.remove(json_path)
    print(f"The JavaScript is saved at: {out_file_path}")

execute_notebook(in_file)

Executes a Jupyter notebook and save output cells

Parameters:

Name Type Description Default
in_file str

Input Jupyter notebook.

required
Source code in geemap/conversion.py
def execute_notebook(in_file):
    """Executes a Jupyter notebook and save output cells

    Args:
        in_file (str): Input Jupyter notebook.
    """
    # command = 'jupyter nbconvert --to notebook --execute ' + in_file + ' --inplace'
    command = 'jupyter nbconvert --to notebook --execute "{}" --inplace'.format(in_file)
    print(os.popen(command).read().rstrip())
    # os.popen(command)

execute_notebook_dir(in_dir)

Executes all Jupyter notebooks in the given directory recursively and save output cells.

Parameters:

Name Type Description Default
in_dir str

Input folder containing notebooks.

required
Source code in geemap/conversion.py
def execute_notebook_dir(in_dir):
    """Executes all Jupyter notebooks in the given directory recursively and save output cells.

    Args:
        in_dir (str): Input folder containing notebooks.
    """
    print("Executing Earth Engine Jupyter notebooks ...\n")

    in_dir = os.path.abspath(in_dir)
    files = list(Path(in_dir).rglob("*.ipynb"))
    count = len(files)
    if files is not None:
        for index, file in enumerate(files):
            in_file = str(file)
            print(f"Processing {index + 1}/{count}: {file} ...")
            execute_notebook(in_file)

find_matching_bracket(lines, start_line_index, start_char_index, matching_char='{')

Finds the position of the matching closing bracket from a list of lines.

Parameters:

Name Type Description Default
lines list

The input list of lines.

required
start_line_index int

The line index where the starting bracket is located.

required
start_char_index int

The position index of the starting bracket.

required
matching_char str

The starting bracket to search for. Defaults to '{'.

'{'

Returns:

Type Description
matching_line_index (int)

The line index where the matching closing bracket is located. matching_char_index (int): The position index of the matching closing bracket.

Source code in geemap/conversion.py
def find_matching_bracket(lines, start_line_index, start_char_index, matching_char="{"):
    """Finds the position of the matching closing bracket from a list of lines.

    Args:
        lines (list): The input list of lines.
        start_line_index (int): The line index where the starting bracket is located.
        start_char_index (int): The position index of the starting bracket.
        matching_char (str, optional): The starting bracket to search for. Defaults to '{'.

    Returns:
        matching_line_index (int): The line index where the matching closing bracket is located.
        matching_char_index (int): The position index of the matching closing bracket.
    """
    matching_line_index = -1
    matching_char_index = -1

    matching_chars = {"{": "}", "(": ")", "[": "]"}
    if matching_char not in matching_chars.keys():
        print(
            "The matching character must be one of the following: {}".format(
                ", ".join(matching_chars.keys())
            )
        )
        return matching_line_index, matching_char_index

    # Create a deque to use it as a stack.
    d = deque()

    for line_index in range(start_line_index, len(lines)):
        line = lines[line_index]
        # deal with the line where the starting bracket is located.
        if line_index == start_line_index:
            line = lines[line_index][start_char_index:]

        for index, item in enumerate(line):
            # Pops a starting bracket for each closing bracket
            if item == matching_chars[matching_char]:
                d.popleft()
            # Push all starting brackets
            elif item == matching_char:
                d.append(matching_char)

            # If deque becomes empty
            if not d:
                matching_line_index = line_index
                if line_index == start_line_index:
                    matching_char_index = start_char_index + index
                else:
                    matching_char_index = index

                return matching_line_index, matching_char_index

    return matching_line_index, matching_char_index

format_params(line, sep=':')

Formats keys in a dictionary and adds quotes to the keys. For example, {min: 0, max: 10} will result in ('min': 0, 'max': 10)

Parameters:

Name Type Description Default
line str

A string.

required
sep str

Separator. Defaults to ':'.

':'

Returns:

Type Description
[str]

A string with keys quoted

Source code in geemap/conversion.py
def format_params(line, sep=":"):
    """Formats keys in a dictionary and adds quotes to the keys.
    For example, {min: 0, max: 10} will result in ('min': 0, 'max': 10)

    Args:
        line (str): A string.
        sep (str, optional): Separator. Defaults to ':'.

    Returns:
        [str]: A string with keys quoted
    """
    # print(line)
    new_line = line
    prefix = ""
    # suffix = ""

    if line.strip().startswith("for"):  # skip for loop
        return line

    # find all occurrences of a substring
    def find_all(a_str, sub):
        start = 0
        while True:
            start = a_str.find(sub, start)
            if start == -1:
                return
            yield start
            start += len(sub)  # use start += 1 to find overlapping matches

    indices = list(find_all(line, sep))
    count = len(indices)

    if "{" in line:
        bracket_index = line.index("{")
        if bracket_index < indices[0]:
            prefix = line[: bracket_index + 1]
            line = line[bracket_index + 1 :]

    if count > 0:
        items = line.split(sep)

        if count == 1:
            for i in range(0, count):
                item = items[i].strip()
                if ('"' not in item) and ("'" not in item):
                    new_item = "'" + item + "'"
                    items[i] = items[i].replace(item, new_item)
            new_line = ":".join(items)
        elif count > 1:
            for i in range(0, count):
                item = items[i]
                if "," in item:
                    subitems = item.split(",")
                    subitem = subitems[-1]
                    if ('"' not in subitem) and ("'" not in subitem):
                        new_subitem = "'" + subitem.strip() + "'"
                        subitems[-1] = subitems[-1].replace(subitem, new_subitem)
                        items[i] = ", ".join(subitems)
                else:
                    if ('"' not in item) and ("'" not in item):
                        new_item = "'" + item.strip() + "'"
                        padding = len(item) - len(item.strip())
                        items[i] = " " * padding + item.replace(item, new_item)

            new_line = ":".join(items)

    return prefix + new_line

get_js_examples(out_dir=None)

Gets Earth Engine JavaScript examples from the geemap package.

Parameters:

Name Type Description Default
out_dir str

The folder to copy the JavaScript examples to. Defaults to None.

None

Returns:

Type Description
str

The folder containing the JavaScript examples.

Source code in geemap/conversion.py
def get_js_examples(out_dir=None):
    """Gets Earth Engine JavaScript examples from the geemap package.

    Args:
        out_dir (str, optional): The folder to copy the JavaScript examples to. Defaults to None.

    Returns:
        str: The folder containing the JavaScript examples.
    """
    pkg_dir = os.path.dirname(pkg_resources.resource_filename("geemap", "geemap.py"))
    example_dir = os.path.join(pkg_dir, "data")
    js_dir = os.path.join(example_dir, "javascripts")

    files = list(Path(js_dir).rglob("*.js"))
    if out_dir is None:
        out_dir = js_dir
    else:
        if not os.path.exists(out_dir):
            os.makedirs(out_dir)

        for file in files:
            basename = os.path.basename(file)
            out_path = os.path.join(out_dir, basename)
            shutil.copyfile(file, out_path)

    return out_dir

get_nb_template(download_latest=False, out_file=None)

Get the Earth Engine Jupyter notebook template.

Parameters:

Name Type Description Default
download_latest bool

If True, downloads the latest notebook template from GitHub. Defaults to False.

False
out_file str

Set the output file path of the notebook template. Defaults to None.

None

Returns:

Type Description
str

The file path of the template.

Source code in geemap/conversion.py
def get_nb_template(download_latest=False, out_file=None):
    """Get the Earth Engine Jupyter notebook template.

    Args:
        download_latest (bool, optional): If True, downloads the latest notebook template from GitHub. Defaults to False.
        out_file (str, optional): Set the output file path of the notebook template. Defaults to None.

    Returns:
        str: The file path of the template.
    """
    pkg_dir = os.path.dirname(pkg_resources.resource_filename("geemap", "geemap.py"))
    example_dir = os.path.join(pkg_dir, "data")
    template_dir = os.path.join(example_dir, "template")
    template_file = os.path.join(template_dir, "template.py")

    if out_file is None:
        out_file = template_file
        return out_file

    if not out_file.endswith(".py"):
        out_file = out_file + ".py"

    if not os.path.exists(os.path.dirname(out_file)):
        os.makedirs(os.path.dirname(out_file))

    if download_latest:
        template_url = "https://raw.githubusercontent.com/gee-community/geemap/master/examples/template/template.py"
        print(f"Downloading the latest notebook template from {template_url}")
        urllib.request.urlretrieve(template_url, out_file)
    elif out_file is not None:
        shutil.copyfile(template_file, out_file)

    return out_file

js_snippet_to_py(in_js_snippet, add_new_cell=True, import_ee=True, import_geemap=False, show_map=True, Map='Map')

Converts an Earth Engine JavaScript snippet wrapped in triple quotes to Python directly on a Jupyter notebook.

Parameters:

Name Type Description Default
in_js_snippet str

Earth Engine JavaScript within triple quotes.

required
add_new_cell bool

Whether add the converted Python to a new cell.

True
import_ee bool

Whether to import ee. Defaults to True.

True
import_geemap bool

Whether to import geemap. Defaults to False.

False
show_map bool

Whether to show the map. Defaults to True.

True
Map str

The name of the map variable. Defaults to "m".

'Map'

Returns:

Type Description
list

A list of Python script.

Source code in geemap/conversion.py
def js_snippet_to_py(
    in_js_snippet,
    add_new_cell=True,
    import_ee=True,
    import_geemap=False,
    show_map=True,
    Map="Map",
):
    """Converts an Earth Engine JavaScript snippet wrapped in triple quotes to Python directly on a Jupyter notebook.

    Args:
        in_js_snippet (str): Earth Engine JavaScript within triple quotes.
        add_new_cell (bool, optional): Whether add the converted Python to a new cell.
        import_ee (bool, optional): Whether to import ee. Defaults to True.
        import_geemap (bool, optional): Whether to import geemap. Defaults to False.
        show_map (bool, optional): Whether to show the map. Defaults to True.
        Map (str, optional): The name of the map variable. Defaults to "m".

    Returns:
        list: A list of Python script.
    """

    in_js = temp_file_path(".js")
    out_py = temp_file_path(".py")

    try:
        with open(in_js, "w") as f:
            f.write(in_js_snippet)
        js_to_python(
            in_js,
            out_file=out_py,
            use_qgis=False,
            show_map=show_map,
            import_geemap=import_geemap,
            Map=Map,
        )

        out_lines = []
        if import_ee:
            out_lines.append("import ee\n")
        if import_geemap:
            out_lines.append("import geemap\n\n")
            out_lines.append(f"{Map} = geemap.Map()\n")

        with open(out_py, encoding="utf-8") as f:
            lines = f.readlines()
            for index, line in enumerate(lines):
                if index < (len(lines) - 1):
                    if line.strip() == "import ee":
                        continue

                    next_line = lines[index + 1]
                    if line.strip() == "" and next_line.strip() == "":
                        continue
                    elif ".style(" in line and (".style(**" not in line):
                        line = line.replace(".style(", ".style(**")
                        out_lines.append(line)
                    else:
                        out_lines.append(line)
                elif index == (len(lines) - 1) and lines[index].strip() != "":
                    out_lines.append(line)

        os.remove(in_js)
        os.remove(out_py)

        if add_new_cell:
            contents = "".join(out_lines).strip()
            create_new_cell(contents)
        else:
            return out_lines

    except Exception as e:
        print(e)

js_to_python(in_file, out_file=None, use_qgis=True, github_repo=None, show_map=True, import_geemap=False, Map='Map')

Converts an Earth Engine JavaScript to Python script.

1
2
3
4
!!! args
    in_file (str): File path of the input JavaScript.
    out_file (str, optional): File path of the output Python script. Defaults to None.
    use_qgis (bool, optional): Whether to add "from ee_plugin import Map

" to the output script. Defaults to True. github_repo (str, optional): GitHub repo url. Defaults to None. show_map (bool, optional): Whether to add "Map" to the output script. Defaults to True. import_geemap (bool, optional): Whether to add "import geemap" to the output script. Defaults to False. Map (str, optional): The name of the map variable. Defaults to "m".

1
2
!!! returns
    list: Python script
Source code in geemap/conversion.py
def js_to_python(
    in_file,
    out_file=None,
    use_qgis=True,
    github_repo=None,
    show_map=True,
    import_geemap=False,
    Map="Map",
):
    """Converts an Earth Engine JavaScript to Python script.

    Args:
        in_file (str): File path of the input JavaScript.
        out_file (str, optional): File path of the output Python script. Defaults to None.
        use_qgis (bool, optional): Whether to add "from ee_plugin import Map \n" to the output script. Defaults to True.
        github_repo (str, optional): GitHub repo url. Defaults to None.
        show_map (bool, optional): Whether to add "Map" to the output script. Defaults to True.
        import_geemap (bool, optional): Whether to add "import geemap" to the output script. Defaults to False.
        Map (str, optional): The name of the map variable. Defaults to "m".

    Returns:
        list: Python script
    """
    in_file = os.path.abspath(in_file)
    if out_file is None:
        out_file = in_file.replace(".js", ".py")

    root_dir = os.path.dirname(os.path.abspath(__file__))
    if not os.path.isfile(in_file):
        in_file = os.path.join(root_dir, in_file)
    if not os.path.isfile(out_file):
        out_file = os.path.join(root_dir, out_file)

    is_python = False
    # add_github_url = False

    if use_qgis and import_geemap:
        raise Exception(
            "use_qgis and import_geemap cannot be both True. Please set one of them to False."
        )

    import_str = ""
    if use_qgis:
        import_str = "from ee_plugin import Map\n"
    if import_geemap:
        import_str = f"import geemap\n\n{Map} = geemap.Map()\n"

    github_url = ""
    if github_repo is not None:
        github_url = "# GitHub URL: " + github_repo + in_file + "\n\n"

    math_import = False
    math_import_str = ""

    lines = []
    with open(in_file, encoding="utf-8") as f:
        lines = f.readlines()

        math_import = use_math(lines)

        for line in lines:
            line = line.strip()
            if line == "import ee":
                is_python = True

    if math_import:
        math_import_str = "import math\n"

    output = ""

    if is_python:  # only update the GitHub URL if it is already a GEE Python script
        output = github_url + "".join(map(str, lines))
    else:  # deal with JavaScript
        header = github_url + "import ee \n" + math_import_str + import_str
        # function_defs = []
        output = header + "\n"

        with open(in_file, encoding="utf-8") as f:
            lines = f.readlines()

            numIncorrectParameters = 0
            checkNextLineForPrint = False
            shouldCheckForEmptyLines = False
            currentDictionaryScopeDepth = 0
            currentNumOfNestedFuncs = 0

            # We need to remove all spaces from the beginning of each line to accurately format the indentation
            lines = remove_all_indentation(lines)

            # print('Processing {}'.format(in_file))
            lines = check_map_functions(lines)

            for index, line in enumerate(lines):
                if ("/* color" in line) and ("*/" in line):
                    line = (
                        line[: line.index("/*")].lstrip()
                        + line[(line.index("*/") + 2) :]
                    )

                if (
                    ("= function" in line)
                    or ("=function" in line)
                    or line.strip().startswith("function")
                ):
                    try:
                        bracket_index = line.index("{")

                        (
                            matching_line_index,
                            matching_char_index,
                        ) = find_matching_bracket(lines, index, bracket_index)

                        if "func_" not in line:
                            currentNumOfNestedFuncs += 1

                            for sub_index, tmp_line in enumerate(
                                lines[index + 1 : matching_line_index]
                            ):
                                # lines[sub_index] = ('    ' * currentNumOfNestedFuncs) + tmp_line
                                if "{" in tmp_line and "function" not in line:
                                    currentNumOfNestedFuncs += 1
                                if "}" in tmp_line and "function" not in line:
                                    currentNumOfNestedFuncs -= 1
                                lines[index + 1 + sub_index] = (
                                    "    " * currentNumOfNestedFuncs
                                ) + lines[index + 1 + sub_index]

                            currentNumOfNestedFuncs -= 1

                        line = line[:bracket_index] + line[bracket_index + 1 :]
                        if matching_line_index == index:
                            line = (
                                line[:matching_char_index]
                                + line[matching_char_index + 1 :]
                            )
                        else:
                            tmp_line = lines[matching_line_index]
                            lines[matching_line_index] = (
                                tmp_line[:matching_char_index]
                                + tmp_line[matching_char_index + 1 :]
                            )

                    except Exception as e:
                        print(
                            f"An error occurred when processing {in_file}. The closing curly bracket could not be found in Line {index+1}: {line}. Please reformat the function definition and make sure that both the opening and closing curly brackets appear on the same line as the function keyword. "
                        )
                        return

                    line = (
                        line.replace(" = function", "")
                        .replace("=function", "")
                        .replace("function ", "")
                    )
                    if line.lstrip().startswith("//"):
                        line = line.replace("//", "").lstrip()
                        line = (
                            " " * (len(line) - len(line.lstrip()))
                            + "# def "
                            + line.strip()
                            + ":"
                        )
                    else:
                        line = (
                            " " * (len(line) - len(line.lstrip()))
                            + "def "
                            + line.strip()
                            + ":"
                        )
                elif "{" in line and "({" not in line:
                    bracket_index = line.index("{")
                    (
                        matching_line_index,
                        matching_char_index,
                    ) = find_matching_bracket(lines, index, bracket_index)

                    currentNumOfNestedFuncs += 1

                    for sub_index, tmp_line in enumerate(
                        lines[index + 1 : matching_line_index]
                    ):
                        lines[index + 1 + sub_index] = (
                            "    " * currentNumOfNestedFuncs
                        ) + lines[index + 1 + sub_index]
                        if "{" in tmp_line and "if" not in line and "for" not in line:
                            currentNumOfNestedFuncs += 1
                        if "}" in tmp_line and "if" not in line and "for" not in line:
                            currentNumOfNestedFuncs -= 1

                    currentNumOfNestedFuncs -= 1

                    if (matching_line_index == index) and (":" in line):
                        pass
                    elif (
                        ("for (" in line)
                        or ("for(" in line)
                        or ("if (" in line)
                        or ("if(" in line)
                    ):
                        if "if" not in line:
                            line = convert_for_loop(line)
                        else:
                            start_index = line.index("(")
                            end_index = line.index(")")
                            line = "if " + line[start_index:end_index] + "):{"
                        lines[index] = line
                        bracket_index = line.index("{")
                        (
                            matching_line_index,
                            matching_char_index,
                        ) = find_matching_bracket(lines, index, bracket_index)
                        tmp_line = lines[matching_line_index]
                        lines[matching_line_index] = (
                            tmp_line[:matching_char_index]
                            + tmp_line[matching_char_index + 1 :]
                        )
                        line = line.replace("{", "")

                if line is None:
                    line = ""

                line = line.replace("//", "#")
                line = line.replace("var ", "", 1)
                line = line.replace("/*", "#")
                line = line.replace("*/", "#")
                line = line.replace("true", "True").replace("false", "False")
                line = line.replace("null", "None")
                line = line.replace(".or", ".Or")
                line = line.replace(".and", ".And")
                line = line.replace(".not", ".Not")
                line = line.replace("visualize({", "visualize(**{")
                line = line.replace("Math.PI", "math.pi")
                line = line.replace("Math.", "math.")
                line = line.replace("parseInt", "int")
                line = line.replace("NotNull", "notNull")
                line = line.replace("= new", "=")
                line = line.replace("exports.", "")
                line = line.replace("Map.", f"{Map}.")
                line = line.replace(
                    "Export.table.toDrive", "geemap.ee_export_vector_to_drive"
                )
                line = line.replace(
                    "Export.table.toAsset", "geemap.ee_export_vector_to_asset"
                )
                line = line.replace(
                    "Export.image.toAsset", "geemap.ee_export_image_to_asset"
                )
                line = line.replace(
                    "Export.video.toDrive", "geemap.ee_export_video_to_drive"
                )
                line = line.replace("||", "or")
                line = line.replace("\****", "#")
                line = line.replace("def =", "_def =")
                line = line.replace(", def, ", ", _def, ")
                line = line.replace("(def, ", "(_def, ")
                line = line.replace(", def)", ", _def)")
                line = line.replace("===", "==")

                # Replaces all javascript operators with python operators
                if "!" in line:
                    try:
                        if (line.replace(" ", ""))[line.find("!") + 1] != "=":
                            line = line.replace("!", "not ")
                    except:
                        print("continue...")

                line = line.rstrip()

                # If the function concat is used, replace it with python's concatenation
                if "concat" in line:
                    line = line.replace(".concat(", "+")
                    line = line.replace(",", "+")
                    line = line.replace(")", "")

                # Checks if an equal sign is at the end of a line. If so, add backslashes
                if shouldCheckForEmptyLines:
                    if line.strip() == "" or "#" in line:
                        if line.strip().endswith("["):
                            line = "["
                            shouldCheckForEmptyLines = False
                        else:
                            line = "\\"
                    else:
                        shouldCheckForEmptyLines = False

                if line.strip().endswith("="):
                    line = line + " \\"
                    shouldCheckForEmptyLines = True

                # Adds getInfo at the end of print statements involving maps
                endOfPrintReplaced = False

                if ("print(" in line and "=" not in line) or checkNextLineForPrint:
                    for i in range(len(line) - 1):
                        if line[len(line) - i - 1] == ")":
                            line = line[: len(line) - i - 1] + ".getInfo())"
                            print(line)
                            endOfPrintReplaced = True
                            break
                    if endOfPrintReplaced:
                        checkNextLineForPrint = False
                    else:
                        checkNextLineForPrint = True

                # Removes potential commas after imports. Causes tuple type errors
                if line.endswith(","):
                    if "=" in lines[index + 1] and not lines[
                        index + 1
                    ].strip().startswith("'"):
                        line = line[:-1]

                # Changes object argument to individual parameters
                if (
                    line.strip().endswith("({")
                    and not "ee.Dictionary" in line
                    and not ".set(" in line
                    and ".addLayer" not in line
                    and "cast" not in line
                ):
                    line = line.rstrip()[:-1]
                    numIncorrectParameters = numIncorrectParameters + 1

                if numIncorrectParameters > 0:
                    if line.strip().startswith("})"):
                        line = line.replace("})", ")")
                        numIncorrectParameters = numIncorrectParameters - 1
                    else:
                        if currentDictionaryScopeDepth < 1:
                            line = line.replace(":", " =")

                if "= {" in line and "({" not in line:
                    currentDictionaryScopeDepth += 1

                if "}" in line and currentDictionaryScopeDepth > 0:
                    currentDictionaryScopeDepth -= 1

                # if ".style(" in line and ".style(**" not in line:
                #     line = line.replace(".style(", ".style(**")

                if line.endswith("+"):
                    line = line + " \\"
                elif line.endswith(";"):
                    line = line[:-1]

                if line.lstrip().startswith("*"):
                    line = line.replace("*", "#")

                if (
                    (":" in line)
                    and (not line.strip().startswith("#"))
                    and (not line.strip().startswith("def"))
                    and (not line.strip().startswith("."))
                    and (not line.strip().startswith("if"))
                ):
                    line = format_params(line)

                if (
                    index < (len(lines) - 1)
                    and line.lstrip().startswith("#")
                    and lines[index + 1].lstrip().startswith(".")
                ):
                    line = ""

                if (
                    "#" in line
                    and not line.strip().startswith("#")
                    and not line[line.index("#") - 1] == "'"
                ):
                    line = line[: line.index("#")]

                if line.lstrip().startswith("."):
                    if lines[index - 1].strip().endswith("\\") and lines[
                        index - 1
                    ].strip().startswith("#"):
                        lines[index - 1] = "\\"
                    if "#" in line:
                        line = line[: line.index("#")]
                    output = output.rstrip() + " " + "\\" + "\n" + line + "\n"
                else:
                    output += line + "\n"

    if show_map:
        output += "Map"

    out_dir = os.path.dirname(out_file)
    if not os.path.exists(out_dir):
        os.makedirs(out_dir)

    with open(out_file, "w", encoding="utf-8") as f:
        f.write(output)

    return output

js_to_python_dir(in_dir, out_dir=None, use_qgis=True, github_repo=None, import_geemap=False, Map='Map')

Converts all Earth Engine JavaScripts in a folder recursively to Python scripts.

1
2
3
4
!!! args
    in_dir (str): The input folder containing Earth Engine JavaScripts.
    out_dir (str, optional): The output folder containing Earth Engine Python scripts. Defaults to None.
    use_qgis (bool, optional): Whether to add "from ee_plugin import Map

" to the output script. Defaults to True. github_repo (str, optional): GitHub repo url. Defaults to None. import_geemap (bool, optional): Whether to add "import geemap" to the output script. Defaults to False. Map (str, optional): The name of the map variable. Defaults to "m".

Source code in geemap/conversion.py
def js_to_python_dir(
    in_dir,
    out_dir=None,
    use_qgis=True,
    github_repo=None,
    import_geemap=False,
    Map="Map",
):
    """Converts all Earth Engine JavaScripts in a folder recursively to Python scripts.

    Args:
        in_dir (str): The input folder containing Earth Engine JavaScripts.
        out_dir (str, optional): The output folder containing Earth Engine Python scripts. Defaults to None.
        use_qgis (bool, optional): Whether to add "from ee_plugin import Map \n" to the output script. Defaults to True.
        github_repo (str, optional): GitHub repo url. Defaults to None.
        import_geemap (bool, optional): Whether to add "import geemap" to the output script. Defaults to False.
        Map (str, optional): The name of the map variable. Defaults to "m".
    """
    print("Converting Earth Engine JavaScripts to Python scripts...\n")
    in_dir = os.path.abspath(in_dir)
    if out_dir is None:
        out_dir = in_dir
    elif not os.path.exists(out_dir):
        out_dir = os.path.abspath(out_dir)
        os.makedirs(out_dir)
    else:
        out_dir = os.path.abspath(out_dir)

    files = list(Path(in_dir).rglob("*.js"))

    for index, in_file in enumerate(files):
        print(f"Processing {index + 1}/{len(files)}: {in_file}")
        # if use_qgis:
        #     out_file = os.path.splitext(in_file)[0] + "_qgis.py"
        # else:
        out_file = os.path.splitext(in_file)[0] + "_geemap.py"
        out_file = out_file.replace(in_dir, out_dir)
        js_to_python(
            in_file,
            out_file,
            use_qgis,
            github_repo,
            import_geemap=import_geemap,
            Map=Map,
        )
    # print("Output Python script folder: {}".format(out_dir))

py_to_ipynb(in_file, template_file=None, out_file=None, github_username=None, github_repo=None, Map='Map')

Converts Earth Engine Python script to Jupyter notebook.

Parameters:

Name Type Description Default
in_file str

Input Earth Engine Python script.

required
template_file str

Input Jupyter notebook template.

None
out_file str

Output Jupyter notebook.

None
github_username str

GitHub username. Defaults to None.

None
github_repo str

GitHub repo name. Defaults to None.

None
Map str

The name of the map variable. Defaults to "m".

'Map'
Source code in geemap/conversion.py
def py_to_ipynb(
    in_file,
    template_file=None,
    out_file=None,
    github_username=None,
    github_repo=None,
    Map="Map",
):
    """Converts Earth Engine Python script to Jupyter notebook.

    Args:
        in_file (str): Input Earth Engine Python script.
        template_file (str): Input Jupyter notebook template.
        out_file (str, optional)): Output Jupyter notebook.
        github_username (str, optional): GitHub username. Defaults to None.
        github_repo (str, optional): GitHub repo name. Defaults to None.
        Map (str, optional): The name of the map variable. Defaults to "m".
    """
    in_file = os.path.abspath(in_file)

    if template_file is None:
        template_file = get_nb_template()

    if out_file is None:
        out_file = os.path.splitext(in_file)[0] + ".ipynb"

    out_py_file = os.path.splitext(out_file)[0] + "_tmp.py"

    out_dir = os.path.dirname(out_file)
    if not os.path.exists(out_dir):
        os.makedirs(out_dir)
    if out_dir == os.path.dirname(in_file):
        out_py_file = os.path.splitext(out_file)[0] + "_tmp.py"

    content = remove_qgis_import(in_file, Map=Map)
    if content[-1].strip() == "Map":
        content = content[:-1]
    header = template_header(template_file)
    footer = template_footer(template_file)

    if (github_username is not None) and (github_repo is not None):
        out_py_path = str(out_file).split("/")
        index = out_py_path.index(github_repo)
        out_py_relative_path = "/".join(out_py_path[index + 1 :])
        out_ipynb_relative_path = out_py_relative_path.replace(".py", ".ipynb")

        new_header = []
        for index, line in enumerate(header):
            if index < 9:  # Change Google Colab and binder URLs
                line = line.replace("giswqs", github_username)
                line = line.replace("geemap", github_repo)
                line = line.replace(
                    "examples/template/template.ipynb", out_ipynb_relative_path
                )
            new_header.append(line)
        header = new_header

    if content is not None:
        out_text = header + content + footer
    else:
        out_text = header + footer

    out_text = out_text[:-1] + [out_text[-1].strip()]

    if not os.path.exists(os.path.dirname(out_py_file)):
        os.makedirs(os.path.dirname(out_py_file))

    with open(out_py_file, "w", encoding="utf-8") as f:
        f.writelines(out_text)

    try:
        # command = 'ipynb-py-convert ' + out_py_file + ' ' + out_file
        command = 'ipynb-py-convert "{}" "{}"'.format(out_py_file, out_file)
        print(os.popen(command).read().rstrip())
        # os.popen(command)
    except Exception as e:
        print("Please install ipynb-py-convert using the following command:\n")
        print("pip install ipynb-py-convert")
        raise Exception(e)

    try:
        os.remove(out_py_file)
    except Exception as e:
        print(e)

py_to_ipynb_dir(in_dir, template_file=None, out_dir=None, github_username=None, github_repo=None, Map='Map')

Converts Earth Engine Python scripts in a folder recursively to Jupyter notebooks.

Parameters:

Name Type Description Default
in_dir str

Input folder containing Earth Engine Python scripts.

required
out_dir str

Output folder. Defaults to None.

None
template_file str

Input jupyter notebook template file.

None
github_username str

GitHub username. Defaults to None.

None
github_repo str

GitHub repo name. Defaults to None.

None
Map str

The name of the map variable. Defaults to "m".

'Map'
Source code in geemap/conversion.py
def py_to_ipynb_dir(
    in_dir,
    template_file=None,
    out_dir=None,
    github_username=None,
    github_repo=None,
    Map="Map",
):
    """Converts Earth Engine Python scripts in a folder recursively to Jupyter notebooks.

    Args:
        in_dir (str): Input folder containing Earth Engine Python scripts.
        out_dir str, optional): Output folder. Defaults to None.
        template_file (str): Input jupyter notebook template file.
        github_username (str, optional): GitHub username. Defaults to None.
        github_repo (str, optional): GitHub repo name. Defaults to None.
        Map (str, optional): The name of the map variable. Defaults to "m".
    """
    print("Converting Earth Engine Python scripts to Jupyter notebooks ...\n")

    in_dir = os.path.abspath(in_dir)
    files = []
    qgis_files = list(Path(in_dir).rglob("*_geemap.py"))
    py_files = list(Path(in_dir).rglob("*.py"))

    files = qgis_files

    # if len(qgis_files) == len(py_files) / 2:
    #     files = qgis_files
    # else:
    #     files = py_files

    if out_dir is None:
        out_dir = in_dir
    elif not os.path.exists(out_dir):
        out_dir = os.path.abspath(out_dir)
        os.makedirs(out_dir)
    else:
        out_dir = os.path.abspath(out_dir)

    for index, file in enumerate(files):
        in_file = str(file)
        out_file = (
            in_file.replace(in_dir, out_dir)
            .replace("_qgis", "")
            .replace(".py", ".ipynb")
        )
        print(f"Processing {index + 1}/{len(files)}: {in_file}")
        py_to_ipynb(in_file, template_file, out_file, github_username, github_repo, Map)

remove_all_indentation(input_lines)

Removes all indentation for reformatting according to python's indentation rules

Parameters:

Name Type Description Default
input_lines list

List of Earth Engine JavaScrips

required

Returns:

Type Description
list

Output JavaScript with indentation removed

Source code in geemap/conversion.py
def remove_all_indentation(input_lines):
    """Removes all indentation for reformatting according to python's indentation rules

    Args:
        input_lines (list): List of Earth Engine JavaScrips

    Returns:
        list: Output JavaScript with indentation removed
    """
    output_lines = []
    for _, line in enumerate(input_lines):
        output_lines.append(line.lstrip())
    return output_lines

remove_qgis_import(in_file, Map='Map')

Removes 'from ee_plugin import Map' from an Earth Engine Python script.

Parameters:

Name Type Description Default
in_file str

Input file path of the Python script.

required
Map str

The name of the map variable. Defaults to "m".

'Map'

Returns:

Type Description
list

List of lines 'from ee_plugin import Map' removed.

Source code in geemap/conversion.py
def remove_qgis_import(in_file, Map="Map"):
    """Removes 'from ee_plugin import Map' from an Earth Engine Python script.

    Args:
        in_file (str): Input file path of the Python script.
        Map (str, optional): The name of the map variable. Defaults to "m".

    Returns:
        list: List of lines  'from ee_plugin import Map' removed.
    """
    in_file = os.path.abspath(in_file)
    start_index = 0
    with open(in_file, encoding="utf-8") as f:
        lines = f.readlines()
        for index, line in enumerate(lines):
            if "from ee_plugin import Map" in line:
                start_index = index

                i = 1
                while True:
                    line_tmp = lines[start_index + i].strip()
                    if line_tmp != "":
                        return lines[start_index + i :]
                    else:
                        i = i + 1
            elif f"{Map} = geemap.Map()" in line:
                return lines[index + 1 :]

Extracts footer from the notebook template.

Parameters:

Name Type Description Default
in_template str

Input notebook template file path.

required

Returns:

Type Description
list

List of lines.

Source code in geemap/conversion.py
def template_footer(in_template):
    """Extracts footer from the notebook template.

    Args:
        in_template (str): Input notebook template file path.

    Returns:
        list: List of lines.
    """
    footer = []
    template_lines = []
    footer_start_index = 0

    with open(in_template, encoding="utf-8") as f:
        template_lines = f.readlines()
        for index, line in enumerate(template_lines):
            if "## Display the interactive map" in line:
                footer_start_index = index - 2

    footer = ["\n"] + template_lines[footer_start_index:]

    return footer

template_header(in_template)

Extracts header from the notebook template.

Parameters:

Name Type Description Default
in_template str

Input notebook template file path.

required

Returns:

Type Description
list

List of lines.

Source code in geemap/conversion.py
def template_header(in_template):
    """Extracts header from the notebook template.

    Args:
        in_template (str): Input notebook template file path.

    Returns:
        list: List of lines.
    """
    header = []
    template_lines = []
    header_end_index = 0

    with open(in_template, encoding="utf-8") as f:
        template_lines = f.readlines()
        for index, line in enumerate(template_lines):
            if "## Add Earth Engine Python script" in line:
                header_end_index = index + 6

    header = template_lines[:header_end_index]

    return header

update_nb_header(in_file, github_username=None, github_repo=None)

Updates notebook header (binder and Google Colab URLs).

Parameters:

Name Type Description Default
in_file str

The input Jupyter notebook.

required
github_username str

GitHub username. Defaults to None.

None
github_repo str

GitHub repo name. Defaults to None.

None
Source code in geemap/conversion.py
def update_nb_header(in_file, github_username=None, github_repo=None):
    """Updates notebook header (binder and Google Colab URLs).

    Args:
        in_file (str): The input Jupyter notebook.
        github_username (str, optional): GitHub username. Defaults to None.
        github_repo (str, optional): GitHub repo name. Defaults to None.
    """
    if github_username is None:
        github_username = "giswqs"
    if github_repo is None:
        github_repo = "geemap"

    index = in_file.index(github_repo)
    file_relative_path = in_file[index + len(github_repo) + 1 :]

    output_lines = []

    with open(in_file, encoding="utf-8") as f:
        lines = f.readlines()
        start_line_index = 2
        start_char_index = lines[start_line_index].index("{")
        matching_line_index, _ = find_matching_bracket(
            lines, start_line_index, start_char_index
        )

        header = lines[:matching_line_index]
        content = lines[matching_line_index:]

        new_header = []
        search_string = ""
        for line in header:
            line = line.replace("giswqs", github_username)
            line = line.replace("geemap", github_repo)
            if "master?filepath=" in line:
                search_string = "master?filepath="
                start_index = line.index(search_string) + len(search_string)
                end_index = line.index(".ipynb") + 6
                relative_path = line[start_index:end_index]
                line = line.replace(relative_path, file_relative_path)
            elif "/master/" in line:
                search_string = "/master/"
                start_index = line.index(search_string) + len(search_string)
                end_index = line.index(".ipynb") + 6
                relative_path = line[start_index:end_index]
                line = line.replace(relative_path, file_relative_path)
            new_header.append(line)

        output_lines = new_header + content

        with open(in_file, "w") as f:
            f.writelines(output_lines)

update_nb_header_dir(in_dir, github_username=None, github_repo=None)

Updates header (binder and Google Colab URLs) of all notebooks in a folder .

Parameters:

Name Type Description Default
in_dir str

The input directory containing Jupyter notebooks.

required
github_username str

GitHub username. Defaults to None.

None
github_repo str

GitHub repo name. Defaults to None.

None
Source code in geemap/conversion.py
def update_nb_header_dir(in_dir, github_username=None, github_repo=None):
    """Updates header (binder and Google Colab URLs) of all notebooks in a folder .

    Args:
        in_dir (str): The input directory containing Jupyter notebooks.
        github_username (str, optional): GitHub username. Defaults to None.
        github_repo (str, optional): GitHub repo name. Defaults to None.
    """
    files = list(Path(in_dir).rglob("*.ipynb"))
    for index, file in enumerate(files):
        file = str(file)
        if ".ipynb_checkpoints" in file:
            del files[index]
    count = len(files)
    if files is not None:
        for index, file in enumerate(files):
            in_file = str(file)
            print(f"Processing {index + 1}/{count}: {file} ...")
            update_nb_header(in_file, github_username, github_repo)

use_math(lines)

Checks if an Earth Engine uses Math library

Parameters:

Name Type Description Default
lines list

An Earth Engine JavaScript.

required

Returns:

Type Description
[bool]

Returns True if the script contains 'Math.'. For example 'Math.PI', 'Math.pow'

Source code in geemap/conversion.py
def use_math(lines):
    """Checks if an Earth Engine uses Math library

    Args:
        lines (list): An Earth Engine JavaScript.

    Returns:
        [bool]: Returns True if the script contains 'Math.'. For example 'Math.PI', 'Math.pow'
    """
    math_import = False
    for line in lines:
        if "Math." in line:
            math_import = True

    return math_import