├── requirements.txt ├── py3dtiles_batcher ├── __init__.py └── command_line.py ├── .gitignore ├── doc └── assets │ └── example_3dtiles_on_cesium.png ├── setup.cfg ├── setup.py └── README.rst /requirements.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /py3dtiles_batcher/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.0.3' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | **/__pycache__ 3 | py3dtiles_batcher.egg-info -------------------------------------------------------------------------------- /doc/assets/example_3dtiles_on_cesium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tofull/py3dtiles_batcher/HEAD/doc/assets/example_3dtiles_on_cesium.png -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.0.3 3 | commit = True 4 | tag = True 5 | tag_name = {new_version} 6 | 7 | [bumpversion:file:py3dtiles_batcher/__init__.py] 8 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from setuptools import setup, find_packages 5 | import py3dtiles_batcher 6 | 7 | with open('requirements.txt') as f: 8 | requirements = f.read().splitlines() 9 | 10 | with open('README.rst') as f: 11 | long_description = f.read() 12 | 13 | setup( 14 | name='py3dtiles_batcher', 15 | version=py3dtiles_batcher.__version__, 16 | description="Create 3dtiles tileset.json with py3dtiles in batch.", 17 | long_description=long_description, 18 | author="Loïc Messal", 19 | author_email='Tofull@users.noreply.github.com', 20 | url='https://github.com/tofull/py3dtiles_batcher', 21 | packages=find_packages(include=['py3dtiles_batcher']), 22 | include_package_data=True, 23 | install_requires=requirements, 24 | license="MIT", 25 | zip_safe=False, 26 | classifiers=[ 27 | 'Development Status :: 2 - Pre-Alpha', 28 | 'Intended Audience :: Developers', 29 | 'License :: OSI Approved :: MIT License', 30 | 'Natural Language :: English', 31 | 'Natural Language :: French', 32 | 'Programming Language :: Python :: 3', 33 | 'Programming Language :: Python :: 3.6', 34 | ], 35 | test_suite='tests', 36 | entry_points={ 37 | 'console_scripts': ['py3dtiles_batcher=py3dtiles_batcher.command_line:command_line'], 38 | } 39 | ) -------------------------------------------------------------------------------- /py3dtiles_batcher/command_line.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding=utf-8 -*- 3 | 4 | import argparse 5 | import base64 6 | import glob 7 | import itertools 8 | import os 9 | import pathlib 10 | import shlex 11 | import subprocess 12 | import sys 13 | 14 | 15 | def command_line(): 16 | parser = argparse.ArgumentParser( 17 | description='Convert .las file to 3dtiles in batch.', 18 | formatter_class=argparse.ArgumentDefaultsHelpFormatter, 19 | epilog='''Working example (remove --dryrun when you want to generate tiles) : py3dtiles_batcher.exe "D:\\data_py3dtiles\\output" "D:\\data_py3dtiles\\raw" --dryrun -v''') 20 | parser.add_argument('--dryrun', help="Active dryrun mode. No tile will be generated in this mode.", action='store_true') 21 | parser.add_argument('--incremental', help="Active incremental mode. Skip tile if //tileset.json exists.", action='store_true') 22 | parser.add_argument('--srs_in', help="Srs in.", type=int, default=2959) 23 | parser.add_argument('--srs_out', help="Srs out.", type=int, default=4978) 24 | parser.add_argument('--cache_size', help="Cache size in MB.", type=int, default=3135) 25 | parser.add_argument('--docker_image', help="py3dtiles docker image to use.", type=str, default="py3dtiles") 26 | parser.add_argument('--verbose', '-v', action='count', help="Verbosity (-v simple info, -vv more info, -vvv spawn info)", default=0) 27 | parser.add_argument('--norgb', dest="rgb", action='store_false', help="Do not export rgb attributes") 28 | parser.add_argument('output_folder', help='Directory to save tiles.') 29 | parser.add_argument('input_folder', nargs="*", default=".", help='Directory to watch.') 30 | args = parser.parse_args() 31 | 32 | if args.verbose > 2: 33 | print("Command line args : \n\t{}".format(vars(args))) 34 | parse_args(**vars(args)) 35 | 36 | 37 | def parse_args(dryrun=None, srs_in=None, srs_out=None, cache_size=None, docker_image=None, verbose=None, output_folder=None, input_folder=None, incremental=None, rgb=None, **kwargs): 38 | main(input_folder, output_folder, dryrun=dryrun, srs_in=srs_in, srs_out=srs_out, cache_size=cache_size, docker_image=docker_image, verbose=verbose, incremental=incremental, rgb=rgb, **kwargs) 39 | 40 | 41 | def get_las(folder_to_watch): 42 | iterators = [] 43 | for path in folder_to_watch: 44 | if os.path.exists(path): 45 | if os.path.isdir(path): 46 | iterator_to_add = glob.iglob(path + '/**/*.las', recursive=True) 47 | else: 48 | iterator_to_add = glob.iglob(path, recursive=True) 49 | 50 | iterators = itertools.chain(iterators, iterator_to_add) 51 | 52 | return iterators 53 | 54 | 55 | def main(input_folder, output_folder, dryrun=None, srs_in=None, srs_out=None, cache_size=None, docker_image=None, verbose=None, incremental=None, rgb=None, **kwargs): 56 | 57 | liste_las_to_process_iterator = set(get_las(input_folder)) 58 | detected_files = len(liste_las_to_process_iterator) 59 | 60 | folder_tiles_path = pathlib.PurePath(os.path.abspath(output_folder)).as_posix() 61 | 62 | for index, filename in enumerate(liste_las_to_process_iterator): 63 | path = pathlib.PurePath(os.path.abspath(os.path.dirname(filename))).as_posix() 64 | basename = os.path.basename(filename) 65 | name, extension = os.path.splitext(basename) 66 | name_base64 = base64.b64encode(name.encode()).decode('utf-8') 67 | 68 | print("\nProcessing file {}/{}".format(index + 1, detected_files)) 69 | if verbose > 1: 70 | print("File information : \ 71 | \n\t filename : {}\ 72 | \n\t path : {}\ 73 | \n\t basename : {}\ 74 | \n\t name : {}\ 75 | \n\t name (base64): {}\ 76 | \n\t extension : {}\ 77 | \n\t rgb : {}\ 78 | \n".format( 79 | filename, 80 | path, 81 | basename, 82 | name, 83 | name_base64, 84 | extension, 85 | rgb)) 86 | 87 | commandline = 'docker run --init --rm -v {}:/data_in -v {}:/data_out {} py3dtiles convert --overwrite True --srs_in {} --srs_out {} --out \"/data_out/{}\" --cache_size {} \"/data_in/{}\" --rgb {}'.format( 88 | path, 89 | folder_tiles_path, 90 | docker_image, 91 | srs_in, 92 | srs_out, 93 | name_base64, 94 | cache_size, 95 | basename, 96 | rgb) 97 | 98 | must_be_processed = True 99 | if incremental: 100 | if os.path.isfile(os.path.join(folder_tiles_path, name_base64, 'tileset.json')): 101 | must_be_processed = False 102 | 103 | if dryrun: 104 | print("DryRun : \n{}\n".format(commandline)) 105 | print("Nothing to do in dryRun mode{}".format(" (This file will be skipped because of incremental mode)." if not must_be_processed else '.')) 106 | print("Done") 107 | pass 108 | else: 109 | if not must_be_processed: 110 | if verbose > 0: 111 | print("Skipped because of incremental mode. File {} already exists".format(os.path.join(folder_tiles_path, name_base64, 'tileset.json'))) 112 | continue 113 | if verbose > 0: 114 | print("Executing : \n{}\n".format(commandline)) 115 | args = shlex.split(commandline) 116 | proc = subprocess.Popen(args, stdout=sys.stdout, stderr=sys.stderr, shell=True) 117 | proc.communicate() 118 | proc.wait() 119 | proc.kill() # Ensure the process is killed to avoid docker: Error response from daemon: error while creating mount source path '/host_mnt/...': mkdir /host_mnt/...: file exists. 120 | print("\nDone") 121 | print("\nFinish") 122 | 123 | 124 | if __name__ == "__main__": 125 | command_line() 126 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | py3dtiles_merger 2 | ================ 3 | 4 | **Disclaimer:** 5 | 6 | This project is under active development and has been created to generate data as fast as possible at Jakarto (rush time). It doesn't cover either unit test, well-written documentation, or a sufficient level of abstraction to be used in different contexts. However, I will be more than happy to remove this disclaimer when improvements will be done. Feel free to open an issue to help the project. 7 | 8 | Convert `.las` files to `3dtiles` in batch using `py3dtiles `_. 9 | 10 | You can then easily visualize your supersized `.las` files on the Internet thanks to 3d viewers like `Cesium `_ or `Itowns `_. 11 | 12 | 13 | Requirements 14 | ############# 15 | 16 | This project needs a docker image of py3dtiles. Please follow the instructions : 17 | 18 | .. code-block:: shell 19 | 20 | $ git clone https://github.com/Tofull/py3dtiles 21 | $ cd py3dtiles 22 | $ git checkout feature/Docker_support 23 | $ docker build -t py3dtiles . 24 | 25 | Installation 26 | ############# 27 | 28 | - Local installation *(recommanded until the project support pypi integration)* 29 | 30 | .. code-block:: shell 31 | 32 | $ git clone https://github.com/Tofull/py3dtiles_batcher 33 | $ cd py3dtiles_batcher 34 | $ pip install . 35 | 36 | 37 | Usage 38 | ########### 39 | 40 | .. code-block:: shell 41 | 42 | usage: py3dtiles_batcher [-h] [--dryrun] [--incremental] [--srs_in SRS_IN] 43 | [--srs_out SRS_OUT] [--cache_size CACHE_SIZE] 44 | [--docker_image DOCKER_IMAGE] [--verbose] [--norgb] 45 | output_folder [input_folder [input_folder ...]] 46 | 47 | Convert .las file to 3dtiles in batch. 48 | 49 | positional arguments: 50 | output_folder Directory to save tiles. 51 | input_folder Directory to watch. (default: .) 52 | 53 | optional arguments: 54 | -h, --help show this help message and exit 55 | --dryrun Active dryrun mode. No tile will be generated in this 56 | mode. (default: False) 57 | --incremental Active incremental mode. Skip tile if 58 | //tileset.json exists. (default: 59 | False) 60 | --srs_in SRS_IN Srs in. (default: 2959) 61 | --srs_out SRS_OUT Srs out. (default: 4978) 62 | --cache_size CACHE_SIZE 63 | Cache size in MB. (default: 3135) 64 | --docker_image DOCKER_IMAGE 65 | py3dtiles docker image to use. (default: py3dtiles) 66 | --verbose, -v Verbosity (-v simple info, -vv more info, -vvv spawn 67 | info) (default: 0) 68 | --norgb Do not export rgb attributes (default: True) 69 | 70 | Working example (remove --dryrun when you want to generate tiles) : 71 | py3dtiles_batcher.exe "D:\data_py3dtiles\output" "D:\data_py3dtiles\raw" --dryrun -v 72 | 73 | 74 | Examples 75 | ########## 76 | 77 | 78 | If you want to convert all `.las` from "D:\data_py3dtiles\raw" directory and save result into "D:\data_py3dtiles\output": 79 | 80 | .. code-block:: shell 81 | 82 | # On windows 83 | py3dtiles_batcher.exe -v "D:\data_py3dtiles\output" "D:\data_py3dtiles\raw" 84 | 85 | 86 | You can select specific files or folder you want to convert: 87 | 88 | .. code-block:: shell 89 | 90 | # On windows 91 | py3dtiles_batcher.exe -v "D:\data_py3dtiles\output" "D:\data_py3dtiles\raw" "D:\folder1\file1.las" "D:\folder2" 92 | 93 | 94 | Notes : 95 | ############# 96 | 97 | - Remember to specify the `srs_in` option if its differs from EPSG:2959 98 | 99 | - output path will be written in base64 encodage, to respect URL’s standard (which will be useful for 3d webviewer [Read What's next section]). Don't be surprised. 100 | 101 | 102 | What's next ? 103 | ############## 104 | 105 | * Visualize 3dtiles individually 106 | 107 | Once yours `.las` files have been converted into 3dtiles, you can expose them individually over the Internet with any http server, like : 108 | 109 | .. code-block:: shell 110 | 111 | # using https://www.npmjs.com/package/http-server 112 | npm install http-server -g 113 | http-server D:\data_py3dtiles\output --cors -p 8080 114 | 115 | Then, each tileset in subfolder is available over the Internet, and you can visualize it one by one using a 3d viewer, for example Cesium sandcastle : 116 | 117 | 1. Go to https://cesiumjs.org/Cesium/Build/Apps/Sandcastle/index.html 118 | 2. Insert the following code on Javascript Code section. Replace by the name of the directory of the tileset.json you want to visualize. 119 | 120 | .. code-block:: javascript 121 | 122 | var viewer = new Cesium.Viewer('cesiumContainer'); 123 | var tileset = viewer.scene.primitives.add(new Cesium.Cesium3DTileset({ 124 | url : 'http://127.0.0.1:8080//tileset.json' 125 | })); 126 | 127 | 3. Click Run (or F8) and enjoy. 128 | 129 | .. image:: doc/assets/example_3dtiles_on_cesium.png 130 | :width: 200px 131 | :align: center 132 | :height: 100px 133 | :alt: Example on cesium 134 | 135 | * Visualize merged 3dtiles 136 | 137 | If you want to visualize all your 3dtiles at the same time, some steps are required to merge them into one big tileset.json. 138 | Hopefully, I created the merger tool. Please refer to it by clicking on the following link : https://github.com/Tofull/py3dtiles_merger 139 | 140 | After some discussion with Oslandia' developers team, they have released a new version of py3dtiles with a "merge" command which is intended to do a better stuff than py3dtiles_merger. The previous command "py3dtiles" (renamed as "py3dtiles convert") - used to generate the individual 3dtiles - needed some changes (a well-done hierarchical 3d points structure from children, reconsidering a true computation of the geometricError attribute). 141 | 142 | Contribution 143 | ############# 144 | 145 | Contributions are welcome. Feel free to open an issue for a question, a remark, a typo, a bugfix or a wanted feature. 146 | 147 | 148 | 149 | Licence 150 | ########## 151 | 152 | Copyright © 2018 Loïc Messal (@Tofull) and contributors 153 | 154 | Distributed under the MIT Licence. --------------------------------------------------------------------------------