From ed32d9f316563b17b178fa99e1259e8cf9562d74 Mon Sep 17 00:00:00 2001 From: HideBa Date: Sat, 17 Feb 2024 21:18:35 +0100 Subject: [PATCH 01/15] wip: impl laspy --- CHANGELOG.md | 5 +++ poetry.lock | 87 +++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 3 +- 3 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..df23720 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog +## [0.0.0] - 2024-02-01 +### Changed + +### Added \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index be0987f..0eb3309 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,21 @@ # This file is automatically @generated by Poetry and should not be changed by hand. +[[package]] +name = "affine" +version = "2.4.0" +description = "Matrices describing affine transformation of the plane" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "affine-2.4.0-py3-none-any.whl", hash = "sha256:8a3df80e2b2378aef598a83c1392efd47967afec4242021a0b06b4c7cbc61a92"}, + {file = "affine-2.4.0.tar.gz", hash = "sha256:a24d818d6a836c131976d22f8c27b8d3ca32d0af64c1d8d29deb7bafa4da1eea"}, +] + +[package.extras] +dev = ["coveralls", "flake8", "pydocstyle"] +test = ["pytest (>=4.6)", "pytest-cov"] + [[package]] name = "attrs" version = "23.1.0" @@ -1316,6 +1332,56 @@ colormaps = ["cmocean", "colorcet"] io = ["imageio", "meshio (>=5.2)"] jupyter = ["ipywidgets", "jupyter-server-proxy", "nest-asyncio", "trame (>=2.5.2)", "trame-client (>=2.12.7)", "trame-server (>=2.11.7)", "trame-vtk (>=2.5.8)", "trame-vuetify (>=2.3.1)"] +[[package]] +name = "rasterio" +version = "1.3.9" +description = "Fast and direct raster I/O for use with Numpy and SciPy" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "rasterio-1.3.9-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:04247da9f4002587ac2bec967c3a72f63fc0e6654101c06850bae3d8131b700d"}, + {file = "rasterio-1.3.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c9edce37b70f4cd4be5d3f5d314877e3130aeebb612120405cd28f83fe200865"}, + {file = "rasterio-1.3.9-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:fd6a850a37840ba590ddcf7ff90ba007b1e231b04434d8b4ac5ce0f746ada91a"}, + {file = "rasterio-1.3.9-cp310-cp310-win_amd64.whl", hash = "sha256:0c83156a44f8fda11876ff9f2ff1b602d7e7434447f7d621353f2929cefb1bf1"}, + {file = "rasterio-1.3.9-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:0172dbd80bd9adc105ec2c9bd207dbd5519ea06b438a4d965c6290ae8ed6ff9f"}, + {file = "rasterio-1.3.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0ea5b42597d85868ee88c750cc33f2ae729e1b5e3fe28f99071f39e1417bf1c0"}, + {file = "rasterio-1.3.9-cp311-cp311-manylinux2014_x86_64.whl", hash = "sha256:be9b343bd08245df22115775dc9513c912afb4134d832662fa165d70cb805c34"}, + {file = "rasterio-1.3.9-cp311-cp311-win_amd64.whl", hash = "sha256:06d53e2e0885f039f960beb7c861400b92ea3e0e5abc2c67483fb56b1e5cbc13"}, + {file = "rasterio-1.3.9-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:a34bb9eef67b7896e2dfb39e10ba6372f9894226fb790bd7a46f5748f205b7d8"}, + {file = "rasterio-1.3.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:67b144b9678f9ad4cf5f2c3f455cbc6a7166c0523179249cee8f2e2c57d76c5b"}, + {file = "rasterio-1.3.9-cp312-cp312-manylinux2014_x86_64.whl", hash = "sha256:99b72fccb702a921f43e56a4507b4cafe2a9196b478b993b98e82ec6851916d7"}, + {file = "rasterio-1.3.9-cp312-cp312-win_amd64.whl", hash = "sha256:6777fad3c31eb3e5da0ccaa28a032ad07c20d003bcd14f8bc13e16ca2f62348c"}, + {file = "rasterio-1.3.9-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:55bb1a2701dd67c1952b261a2ffbabd947a435d4457f13c25092a32ab7a4b36e"}, + {file = "rasterio-1.3.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:726d8e8884359c34f672312171310052d5483af550ef00fb4f2562cc022a6f5a"}, + {file = "rasterio-1.3.9-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:f65879415df188fdc9388ccf2ee01e0659abae370d12518a17b60151e7d04efe"}, + {file = "rasterio-1.3.9-cp38-cp38-win_amd64.whl", hash = "sha256:89771b70ee722c4cc808e2a6139b367bef1a736ecd497b311b3515d78a5d16bc"}, + {file = "rasterio-1.3.9-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:14df8413c030b04e54d478d6ecec4e5958b46585c3cb970bf0dc19b4831146c8"}, + {file = "rasterio-1.3.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:911e54e0bb97c456a045f6d8e24b00aeb055a235d2aa7c2c1f9128f4c6c7a52d"}, + {file = "rasterio-1.3.9-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:01e428ee5ba8444f5cb4fff56225acb1ab9bc8b77209b6e4198e04565d8a8509"}, + {file = "rasterio-1.3.9-cp39-cp39-win_amd64.whl", hash = "sha256:26d9aea05b035927647bb32cc04fad0a68346a2f5186224dc1c2555c33515183"}, + {file = "rasterio-1.3.9.tar.gz", hash = "sha256:fc6d0d290492fa1a5068711cfebb21cc936968891b7ed9da0690c8a7388885c5"}, +] + +[package.dependencies] +affine = "*" +attrs = "*" +certifi = "*" +click = ">=4.0" +click-plugins = "*" +cligj = ">=0.5" +numpy = "*" +setuptools = "*" +snuggs = ">=1.4.1" + +[package.extras] +all = ["boto3 (>=1.2.4)", "ghp-import", "hypothesis", "ipython (>=2.0)", "matplotlib", "numpydoc", "packaging", "pytest (>=2.8.2)", "pytest-cov (>=2.2.0)", "shapely", "sphinx", "sphinx-rtd-theme"] +docs = ["ghp-import", "numpydoc", "sphinx", "sphinx-rtd-theme"] +ipython = ["ipython (>=2.0)"] +plot = ["matplotlib"] +s3 = ["boto3 (>=1.2.4)"] +test = ["boto3 (>=1.2.4)", "hypothesis", "packaging", "pytest (>=2.8.2)", "pytest-cov (>=2.2.0)", "shapely"] + [[package]] name = "requests" version = "2.31.0" @@ -1440,6 +1506,25 @@ files = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +[[package]] +name = "snuggs" +version = "1.4.7" +description = "Snuggs are s-expressions for Numpy" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "snuggs-1.4.7-py3-none-any.whl", hash = "sha256:988dde5d4db88e9d71c99457404773dabcc7a1c45971bfbe81900999942d9f07"}, + {file = "snuggs-1.4.7.tar.gz", hash = "sha256:501cf113fe3892e14e2fee76da5cd0606b7e149c411c271898e6259ebde2617b"}, +] + +[package.dependencies] +numpy = "*" +pyparsing = ">=2.1.6" + +[package.extras] +test = ["hypothesis", "pytest"] + [[package]] name = "tomli" version = "2.0.1" @@ -1569,4 +1654,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.9, <3.12" -content-hash = "a8401f8ed689a0f116626158eda6ce19cd73bbb99c374f11705a54fe57aac573" +content-hash = "2656664055db173dae9e59093c97651c79ec67cb388d79a41799528ab175e815" diff --git a/pyproject.toml b/pyproject.toml index a08f9ea..4c8f910 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,9 +23,10 @@ click = "^8.1.7" pdal = "^3.2.3" geopandas = "^0.14.1" shapely = "^2.0.2" -laspy = { extras = ["lazrs"], version = "^2.5.3" } +laspy = {extras = ["lazrs"], version = "^2.5.3"} pyvista = "^0.43.1" requests = "^2.31.0" +rasterio = "^1.3.9" [tool.poetry.group.dev.dependencies] From f4d71c19461089dd88d35c1823b556e19a34ecab Mon Sep 17 00:00:00 2001 From: HideBa Date: Sun, 18 Feb 2024 21:01:59 +0100 Subject: [PATCH 02/15] wip: impl manipulator --- ahn_cli/manipulator/pipeline2.py | 267 +++++++++++++++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 ahn_cli/manipulator/pipeline2.py diff --git a/ahn_cli/manipulator/pipeline2.py b/ahn_cli/manipulator/pipeline2.py new file mode 100644 index 0000000..712e8b6 --- /dev/null +++ b/ahn_cli/manipulator/pipeline2.py @@ -0,0 +1,267 @@ +import json +from typing import Any, Iterable, Self, Tuple +import numpy as np +from shapely import Point +import geopandas as gpd +import pdal +from shapely import Polygon +from ahn_cli.manipulator import rasterizer +import rasterio +from rasterio.io import BufferedDatasetWriterBase +import laspy + +from ahn_cli.manipulator.transformer import tranform_polygon + + +class PntCPipeline: + """ + A class representing a data processing pipeline. + + Args: + input_path (str): The path to the input data. + output_path (str): The path to save the output data. + city_filepath (str): The path to the city data file. + city_name (str): The name of the city. + + Attributes: + pipeline_setting (list): The configuration settings for the pipeline. + + """ + + las = laspy.LasData + city_df: gpd.GeoDataFrame + city_name: str + raster_res: float = 50.0 # default raster resolution + epsg: str | None = None + + def __init__( + self, + las: laspy.LasData, + city_filepath: str, + city_name: str, + epsg: int = 28992, + ) -> None: + self.las = las + self.city_df = gpd.read_file(city_filepath) + self.city_name = city_name + self.epsg = "EPSG:" + str(epsg) + + def decimate(self, step: int) -> Self: + """ + Decimate the point cloud by a given step. + + Args: + step (int): The step to decimate by. + + Returns: + Pipeline: The updated pipeline object. + + """ + self.las.points = self.las.points[::step] + return self + + def include(self, include_classes: list[int]) -> Self: + """ + Filters the point cloud to include only the specified classes. + + Args: + include_classes (list[int]): List of class labels to include. + + Returns: + Self: The modified pipeline object. + """ + mask = np.isin(self.las.classification, include_classes) + self.las.points = self.las.points[mask] + print( + "before", + len(self.las.classification), + "after", + len(self.las.points), + ) + return self + + def exclude(self, exclude_classes: list[int]) -> Self: + """ + Exclude points with specific classification values from the pipeline. + + Args: + exclude_classes (list[int]): List of classification values to exclude. + + Returns: + Self: The modified pipeline object. + """ + mask = np.isin(self.las.classification, exclude_classes, invert=True) + self.las.points = self.las.points[mask] + print( + "before", + len(self.las.classification), + "after", + len(self.las.points), + ) + return self + + # def clip(self) -> Self: + # """ + # Clip the point cloud by a polygon. + # """ + # rasterized_polygon, transform = rasterizer.polygon_to_raster( + # self._city_polygon(), self.raster_res + # ) + + # xyz = self.las.xyz + # points = self.las.points + # valid_points_mask = np.zeros((len(xyz),), dtype=np.uint8) + # transformer = rasterio.transform.AffineTransformer(transform) + # for i, point in enumerate(xyz): + # # row, col = self.map_to_grid(point[0], point[1], transform) + # row, col = transformer.rowcol(point[0], point[1]) + # if ( + # row < 0 + # or row >= rasterized_polygon.shape[0] + # or col < 0 + # or col >= rasterized_polygon.shape[1] + # ): + # valid_points_mask[i] = 0 + # else: + # if rasterized_polygon[row, col] != 1: + # valid_points_mask[i] = 0 + # else: + # valid_points_mask[i] = 1 + # self.las.raster = valid_points_mask + # print("valid points", len(valid_points_mask)) + # print("valid points", valid_points_mask) + # return self + + def clip(self) -> Self: + """ + Clip the point cloud by a polygon. + """ + rasterized_polygon, transform = rasterizer.polygon_to_raster( + self._city_polygon(), self.raster_res + ) + + xyz = self.las.xyz + + transformer = rasterio.transform.AffineTransformer(transform) + rows, cols = transformer.rowcol(xyz[:, 0], xyz[:, 1]) + valid_points_mask = rasterized_polygon[rows, cols] == 1 + self.las.points = self.las.points[valid_points_mask].copy() + + print("before", len(xyz), "after", len(self.las.points)) + return self + + # def clip2(self) -> Self: + # points = self.las.xyz + # polygon = self._city_polygon() + # for i, point in enumerate(points): + # if not polygon.contains([Point(point[0], point[1])]): + # point = None + # return self + + def clip_by_arbitrary_polygon(self, clip_file: str) -> Self: + """ + Clip the point cloud by a polygon. + + Args: + clip_file (str): The path to the polygon file. + + Returns: + Pipeline: The updated pipeline object. + + """ + polygon = self._arbitrary_polygon(clip_file) + rasterized_polygon, transform = rasterizer.polygon_to_raster( + polygon, self.raster_res + ) + + xyz = self.las.xyz + grid_coords = np.array(~transform * (xyz[:, :2].T)) + rows, cols = grid_coords.astype(int) + + rows = np.clip(rows, 0, rasterized_polygon.shape[0] - 1) + cols = np.clip(cols, 0, rasterized_polygon.shape[1] - 1) + + valid_points_mask = rasterized_polygon[rows, cols] == 1 + self.las.points = self.las.points[valid_points_mask] + + print("before", len(xyz), "after", len(self.las.points)) + return self + + def clip_by_bbox(self, bbox: list[float]) -> Self: + """ + Clips the point cloud by a bounding box. + + Args: + bbox (list[float]): The bounding box to clip the point cloud. [xmin, ymin, xmax, ymax] + + Returns: + Self: The updated pipeline object. + + """ + xyz = self.las.xyz + x_valid = (xyz[0] >= bbox[0]) & (xyz[0] <= bbox[2]) + y_valid = (xyz[1] >= bbox[1]) & (xyz[1] <= bbox[3]) + print("first point---------", xyz[0]) + valid_points_mask = np.where(x_valid & y_valid) + print("valid points", len(valid_points_mask)) + self.las.points = self.las.points[valid_points_mask] + return self + + def execute(self) -> laspy.LasData: + """ + Execute the pipeline. + + Returns: + laspy.LasData: The processed point cloud. + """ + + print("Pipeline executed successfully") + return self.las + + def _city_polygon(self) -> Polygon: + """ + Retrieves the polygon for a given city name. + + Args: + None + Returns: + str: The well-known text (WKT) representation of the city's polygon. + Raises: + ValueError: If the polygon fails to be reprojected. + """ + record = self.city_df[ + self.city_df["name"].str.lower() == self.city_name.lower() + ] + polygon = record.iloc[0].geometry + crs = self.city_df.crs + if crs is not None: + polygon = tranform_polygon(polygon, crs, "EPSG:28992") + if polygon is None: + raise ValueError("Failed to reproject polygon") + return polygon + + def _arbitrary_polygon(self, filepath: str) -> Polygon: + """ + Reads a file containing city data, extracts the first polygon geometry, + transforms it to a specific coordinate reference system (CRS), and returns + the well-known text (WKT) representation of the polygon. + + Args: + filepath (str): The path to the file containing city data. + + Returns: + str: The well-known text (WKT) representation of the polygon. + + Raises: + ValueError: If the polygon fails to be reprojected. + """ + gdf = gpd.read_file(filepath) + polygon = gdf[gdf.geometry.type == "Polygon"].iloc[0].geometry + crs = gdf.crs + if crs is not None: + polygon = tranform_polygon(polygon, crs, "EPSG:28992") + if self.epsg is not None: + polygon = tranform_polygon(polygon, self.epsg, "EPSG:28992") + if polygon is None: + raise ValueError("Failed to reproject polygon") + return polygon From 4a36c3bc1a1708204162dcd9cbe83e10b2e82489 Mon Sep 17 00:00:00 2001 From: HideBa Date: Mon, 19 Feb 2024 21:58:09 +0100 Subject: [PATCH 03/15] wip: impl merge las files --- ahn_cli/process.py | 103 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 78 insertions(+), 25 deletions(-) diff --git a/ahn_cli/process.py b/ahn_cli/process.py index 6844dba..1910b20 100644 --- a/ahn_cli/process.py +++ b/ahn_cli/process.py @@ -1,7 +1,11 @@ import os + +import numpy as np from ahn_cli.fetcher.request import Fetcher -from ahn_cli.manipulator.pipeline import PntCPipeline +from ahn_cli.manipulator.pipeline2 import PntCPipeline from ahn_cli.manipulator.preview import previewer +import laspy +from laspy.lasappender import LasAppender def process( @@ -13,37 +17,86 @@ def process( exclude_classes: list[int] | None = None, no_clip_city: bool | None = False, clip_file: str | None = None, + epsg: int | None = None, bbox: list[float] | None = None, radius: int | None = None, preview: bool | None = False, ) -> None: - ahn_fetcher = Fetcher(base_url, city_name) - fetched_files = ahn_fetcher.fetch() + # ahn_fetcher = Fetcher(base_url, city_name) + # fetched_files = ahn_fetcher.fetch() + fetched_files = { + # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ1_14.LAZ": "./testdata/3.LAZ", + # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ1_20.LAZ": "./testdata/5.LAZ", + # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ2_01.LAZ": "./testdata/6.LAZ", + # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ1_15.LAZ": "./testdata/4.LAZ", + # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ2_02.LAZ": "./testdata/7.LAZ", + # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ2_03.LAZ": "./testdata/8.LAZ", + # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ2_16.LAZ": "./testdata/13.LAZ", + # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ2_17.LAZ": "./testdata/14.LAZ", + # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ1_10.LAZ": "./testdata/2.LAZ", + # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ2_06.LAZ": "./testdata/9.LAZ", + "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ2_07.LAZ": "./testdata/10.LAZ", + "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ2_11.LAZ": "./testdata/11.LAZ", + } files = list(fetched_files.values()) + # maxs = np.array([0, 0, 0]) # [x, y, z] + # mins = np.array([np.inf, np.inf, np.inf]) # [x, y, z] + + # for file in files: + # with laspy.open(file) as las: + # header = las.header + # print("header", header) + # print("offsets", las.header.offsets) + # print("scale", las.header.scales) + # maxs = np.maximum(maxs, las.header.maxs) + # mins = np.minimum(mins, las.header.mins) + # print("maxs", maxs) + # print("mins", mins) + + # TODO: Problem is that because each file has its own origin, relative position will be different + for i, file in enumerate(files): + with laspy.open(file) as las: + if i == 0: + global_header = las.header + # update maxs and mins if necessary + maxs = np.maximum(global_header.maxs, las.header.max) + mins = np.minimum(global_header.mins, las.header.min) + pipeline = PntCPipeline(las.read(), city_polygon_path, city_name) + if bbox is not None: + pipeline.clip_by_bbox(bbox) + header = las.header + offset = global_header.offsets - header.offsets + print("offset", offset) + + if include_classes is not None and len(include_classes) > 0: + pipeline.include(include_classes) + if exclude_classes is not None and len(exclude_classes) > 0: + pipeline.exclude(exclude_classes) + if ( + not no_clip_city and radius is None + ): # if no radius is specified, clip by city + print("a") + # pipeline.clip() + + global_header.maxs = maxs + global_header.mins = mins + with laspy.open( + output_path, mode="w" if i == 0 else "a", header=header + ) as writer: + print("writing points", len(pipeline.execute().points)) + points = pipeline.execute().points + print("x shape", points.x.shape) + points.x = points.x - offset[0] + points.y = points.y - offset[1] + points.z = points.z - offset[2] + if isinstance(writer, laspy.LasWriter): + writer.write_points(points) + if isinstance(writer, LasAppender): + writer.append_points(points) - pipeline = PntCPipeline( - files[0], output_path, city_polygon_path, city_name - ) - pipeline.merge(files[1:]) - if bbox is not None: - pipeline.clip_by_bbox(bbox) - if radius is not None: - pipeline.clip_by_radius(radius) - if include_classes is not None and len(include_classes) > 0: - pipeline.include(include_classes) - if exclude_classes is not None and len(exclude_classes) > 0: - pipeline.exclude(exclude_classes) - if ( - not no_clip_city and radius is None - ): # if no radius is specified, clip by city - pipeline.clip() - if clip_file is not None: - pipeline.clip_by_arbitrary_polygon(clip_file) - pipeline.execute() - - for file in files: - os.remove(file) + # for file in files: + # os.remove(file) if preview: print("Previewing output file...") From b268eee6fe71ae25eda97085e22ad845243beb15 Mon Sep 17 00:00:00 2001 From: HideBa Date: Fri, 23 Feb 2024 12:39:30 +0100 Subject: [PATCH 04/15] feat: replace PDAL to laspy --- ahn_cli/kwargs.py | 3 +- ahn_cli/main.py | 21 +- ahn_cli/manipulator/pipeline.py | 326 ++++++++---------------------- ahn_cli/manipulator/pipeline2.py | 267 ------------------------ ahn_cli/manipulator/rasterizer.py | 38 ++++ ahn_cli/process.py | 91 ++++----- ahn_cli/validator.py | 22 +- dockerfile | 24 +++ tests/test_pipeline.py | 188 +++++------------ tests/test_rasterize.py | 34 ++++ 10 files changed, 299 insertions(+), 715 deletions(-) delete mode 100644 ahn_cli/manipulator/pipeline2.py create mode 100644 ahn_cli/manipulator/rasterizer.py create mode 100644 dockerfile create mode 100644 tests/test_rasterize.py diff --git a/ahn_cli/kwargs.py b/ahn_cli/kwargs.py index 568fd77..6d2abba 100644 --- a/ahn_cli/kwargs.py +++ b/ahn_cli/kwargs.py @@ -8,6 +8,7 @@ class CLIArgs(TypedDict): exclude_class: str | None no_clip_city: bool clip_file: str | None + epsg: int | None decimate: int | None - radius: int | None + bbox: list[float] | None preview: bool diff --git a/ahn_cli/main.py b/ahn_cli/main.py index 117f823..0ac54b1 100644 --- a/ahn_cli/main.py +++ b/ahn_cli/main.py @@ -19,8 +19,8 @@ -ncc --no-clip-city Do not clip the point cloud data to the city boundary. -cf, --clip-file Specify a file path to a clipping boundary file. The tool will use this file to clip the point cloud data to a specific area. + -e, --epsg Set the EPSG code for user's clip file. -b, --bbox Specify a bounding box to clip the point cloud data. It should be comma-separated list with minx,miny,maxx,maxy - -r, --radius Set the radius of the circle to clip the point cloud data to. Unit is in meter. -p, --preview Preview the point cloud data in a 3D viewer. -h, --help [category] Display help information. Optionally, specify a category to get more detailed help for a specific command. @@ -69,6 +69,12 @@ type=str, help="Specify a file path to a clipping boundary file. The tool will use this file to clip the point cloud data to a specific area.", ) +@click.option( + "-e", + "--epsg", + type=int, + help="Set the EPSG code for user's clip file.", +) @click.option( "-d", "--decimate", @@ -81,12 +87,6 @@ type=str, help="Specify a bounding box to clip the point cloud data. It should be comma-separated list with minx,miny,maxx,maxy", ) -@click.option( - "-r", - "--radius", - type=int, - help="Set the radius of the circle to clip the point cloud data to. Unit is in meter.", -) @click.option( "-p", "--preview", @@ -110,13 +110,13 @@ def main(**kwargs: Any) -> None: ) no_clip_city = params.get("no_clip_city") clip_file = params.get("clip_file") + epsg = params.get("epsg") decimate = params.get("decimate") bbox = ( [float(x) for x in str(params.get("bbox", "")).split(",")] if params.get("bbox", "") else None ) - radius = params.get("radius") preview = params.get("preview") if validate_all( cfg, @@ -126,9 +126,9 @@ def main(**kwargs: Any) -> None: exclude_classes, no_clip_city, clip_file, + epsg, decimate, bbox, - radius, ): process( cfg.geotiles_base_url, @@ -139,8 +139,9 @@ def main(**kwargs: Any) -> None: exclude_classes, no_clip_city, clip_file, + epsg, + decimate, bbox, - radius, preview, ) diff --git a/ahn_cli/manipulator/pipeline.py b/ahn_cli/manipulator/pipeline.py index 1c13b8e..a203671 100644 --- a/ahn_cli/manipulator/pipeline.py +++ b/ahn_cli/manipulator/pipeline.py @@ -1,10 +1,12 @@ -import json -from typing import Any, Self +from typing import Self import geopandas as gpd -import pdal +import laspy +import numpy as np +import rasterio from shapely import Polygon +from ahn_cli.manipulator import rasterizer from ahn_cli.manipulator.transformer import tranform_polygon @@ -17,69 +19,30 @@ class PntCPipeline: output_path (str): The path to save the output data. city_filepath (str): The path to the city data file. city_name (str): The name of the city. + epsg (int): The EPSG code for the coordinate reference system (CRS). Attributes: pipeline_setting (list): The configuration settings for the pipeline. """ + las = laspy.LasData + city_df: gpd.GeoDataFrame + city_name: str + raster_res: float = 10.0 # default raster resolution + epsg: str | None = None + def __init__( self, - input_path: str, - output_path: str, + las: laspy.LasData, city_filepath: str, city_name: str, + epsg: int = 4326, ) -> None: - self.pipeline_setting: list[Any] = [] - self._init_pipeline(input_path, output_path) + self.las = las self.city_df = gpd.read_file(city_filepath) self.city_name = city_name - - def _init_pipeline(self, input_path: str, output_path: str) -> Self: - """ - Initialize the pipeline with the input and output settings. - - Args: - input_path (str): The path to the input data. - output_path (str): The path to save the output data. - - Returns: - list: The initialized pipeline configuration. - - """ - self.pipeline_setting = [ - input_path, - { - "type": "writers.las", - **( - {"compression": "laszip"} - if output_path.endswith(".laz") - or output_path.endswith("LAZ") - else {} - ), - "filename": output_path, - }, - ] - return self - - def merge(self, merge_files: list[str]) -> Self: - """ - Merge the point cloud with another point cloud. - - Args: - merge_file (str): The path to the point cloud file to merge. - - Returns: - Pipeline: The updated pipeline object. - - """ - merge_pipe = [merge_file for merge_file in merge_files] + [ - {"type": "filters.merge"} - ] - self.pipeline_setting = ( - self.pipeline_setting[:1] + merge_pipe + self.pipeline_setting[1:] - ) - return self + self.epsg = "EPSG:" + str(epsg) def decimate(self, step: int) -> Self: """ @@ -92,17 +55,7 @@ def decimate(self, step: int) -> Self: Pipeline: The updated pipeline object. """ - decimate_pipe = [ - { - "type": "filters.decimation", - "step": str(step), - } - ] - self.pipeline_setting = ( - self.pipeline_setting[:-1] - + decimate_pipe - + self.pipeline_setting[-1:] - ) + self.las.points = self.las.points[::step] return self def include(self, include_classes: list[int]) -> Self: @@ -115,25 +68,8 @@ def include(self, include_classes: list[int]) -> Self: Returns: Self: The modified pipeline object. """ - include_pipe = [ - { - "type": "filters.range", - "limits": ",".join( - [ - "Classification[{}:{}]".format( - str(kclass), str(kclass) - ) - for kclass in include_classes - ] - ), - }, - ] - # append dbscan_pipe to pipeline_setting as -2 index. This is because the last index is the writer - self.pipeline_setting = ( - self.pipeline_setting[:-1] - + include_pipe - + self.pipeline_setting[-1:] - ) + mask = np.isin(self.las.classification, include_classes) + self.las.points = self.las.points[mask] return self def exclude(self, exclude_classes: list[int]) -> Self: @@ -146,51 +82,38 @@ def exclude(self, exclude_classes: list[int]) -> Self: Returns: Self: The modified pipeline object. """ - # The reason why not using "filters.range" is because it doesn't work with multiple conditions for exclusion - exclude_pipe = [ - { - "type": "filters.expression", - "expression": "&&".join( - [ - "Classification != {}".format(str(ex_class)) - for ex_class in exclude_classes - ] - ), - }, - ] - - # append dbscan_pipe to pipeline_setting as -2 index. This is because the last index is the writer - self.pipeline_setting = ( - self.pipeline_setting[:-1] - + exclude_pipe - + self.pipeline_setting[-1:] - ) + mask = np.isin(self.las.classification, exclude_classes, invert=True) + self.las.points = self.las.points[mask] return self def clip(self) -> Self: """ Clip the point cloud by a polygon. + """ + rasterized_polygon, transform = rasterizer.polygon_to_raster( + self._city_polygon(), self.raster_res + ) - Args: - cityname (str): The name of the city to clip the point cloud. - - Returns: - Self: The updated pipeline object. + xyz = self.las.xyz + transformer = rasterio.transform.AffineTransformer(transform) + rows, cols = transformer.rowcol(xyz[:, 0], xyz[:, 1]) + rows, cols = np.array(rows, dtype=int), np.array(cols, dtype=int) + # Store original bounds checks + original_rows_out_of_bounds = (rows < 0) | ( + rows >= rasterized_polygon.shape[0] + ) + original_cols_out_of_bounds = (cols < 0) | ( + cols >= rasterized_polygon.shape[1] + ) - """ - city_polygon = self._city_polygon() - clip_pipe = [ - { - "type": "filters.crop", - "polygon": city_polygon, - } - ] - # append clip_pipe to pipeline_setting as -2 index. This is because the last index is the writer - self.pipeline_setting = ( - self.pipeline_setting[:-1] - + clip_pipe - + self.pipeline_setting[-1:] + rows = np.clip(rows, 0, rasterized_polygon.shape[0] - 1) + cols = np.clip(cols, 0, rasterized_polygon.shape[1] - 1) + valid_points_mask = ( + (rasterized_polygon[rows, cols] == 1) + & (~original_rows_out_of_bounds) + & (~original_cols_out_of_bounds) ) + self.las.points = self.las.points[valid_points_mask] return self def clip_by_arbitrary_polygon(self, clip_file: str) -> Self: @@ -204,18 +127,31 @@ def clip_by_arbitrary_polygon(self, clip_file: str) -> Self: Pipeline: The updated pipeline object. """ - clip_pipe = [ - { - "type": "filters.crop", - "polygon": self._arbitrary_polygon(clip_file), - } - ] - # append clip_pipe to pipeline_setting as -2 index. This is because the last index is the writer - self.pipeline_setting = ( - self.pipeline_setting[:-1] - + clip_pipe - + self.pipeline_setting[-1:] + polygon = self._arbitrary_polygon(clip_file) + rasterized_polygon, transform = rasterizer.polygon_to_raster( + polygon, self.raster_res ) + + xyz = self.las.xyz + transformer = rasterio.transform.AffineTransformer(transform) + rows, cols = transformer.rowcol(xyz[:, 0], xyz[:, 1]) + rows, cols = np.array(rows, dtype=int), np.array(cols, dtype=int) + # Store original bounds checks + original_rows_out_of_bounds = (rows < 0) | ( + rows >= rasterized_polygon.shape[0] + ) + original_cols_out_of_bounds = (cols < 0) | ( + cols >= rasterized_polygon.shape[1] + ) + + rows = np.clip(rows, 0, rasterized_polygon.shape[0] - 1) + cols = np.clip(cols, 0, rasterized_polygon.shape[1] - 1) + valid_points_mask = ( + (rasterized_polygon[rows, cols] == 1) + & (~original_rows_out_of_bounds) + & (~original_cols_out_of_bounds) + ) + self.las.points = self.las.points[valid_points_mask] return self def clip_by_bbox(self, bbox: list[float]) -> Self: @@ -229,89 +165,21 @@ def clip_by_bbox(self, bbox: list[float]) -> Self: Self: The updated pipeline object. """ - clip_pipe = [ - { - "type": "filters.crop", - "bounds": f"([{bbox[0]},{bbox[2]} ],[{bbox[1]},{bbox[3]}])", - } - ] - - # append clip_pipe to pipeline_setting as -2 index. This is because the last index is the writer - self.pipeline_setting = ( - self.pipeline_setting[:-1] - + clip_pipe - + self.pipeline_setting[-1:] - ) - return self - - def clip_by_radius(self, radius: float) -> Self: - """ - Clips the point cloud by a given radius around a specified center. - - Args: - radius (float): The radius of the clipping area. - - Returns: - Self: The modified instance of the pipeline. - """ - record = self.city_df[ - self.city_df["name"].str.lower() == self.city_name.lower() - ] - polygon = record.iloc[0].geometry - crs = self.city_df.crs - if crs is not None: - polygon = tranform_polygon(polygon, crs, "EPSG:28992") - center = polygon.centroid.coords[0] - clip_pipe = [ - { - "type": "filters.crop", - "point": f"POINT({center[0]} {center[1]} 0)", - "distance": str(radius), - } - ] - - # append clip_pipe to pipeline_setting as -2 index. This is because the last index is the writer - self.pipeline_setting = ( - self.pipeline_setting[:-1] - + clip_pipe - + self.pipeline_setting[-1:] - ) - return self - - def info(self) -> Self: - """ - Print the pipeline configuration. - - Returns: - None - """ - info_pipe = [ - { - "type": "filters.info", - } - ] - # append info_pipe to pipeline_setting as -2 index. This is because the last index is the writer - self.pipeline_setting = ( - self.pipeline_setting[:1] + info_pipe + self.pipeline_setting[1:] - ) + xyz = self.las.xyz + x_valid = (xyz[:, 0] >= bbox[0]) & (xyz[:, 0] <= bbox[2]) + y_valid = (xyz[:, 1] >= bbox[1]) & (xyz[:, 1] <= bbox[3]) + valid_points_mask = x_valid & y_valid + self.las.points = self.las.points[valid_points_mask] return self - def execute(self) -> None: + def points(self) -> laspy.LasData: """ Execute the pipeline. Returns: - None + laspy.LasData: The processed point cloud. """ - print("Executing pipeline...") - self.info() - pipeline_json = json.dumps(self.pipeline_setting) - pipeline = pdal.Pipeline(pipeline_json) - pipeline.execute() - log = pipeline.log - print(log) - - print("Pipeline executed successfully") + return self.las def _city_polygon(self) -> Polygon: """ @@ -320,7 +188,7 @@ def _city_polygon(self) -> Polygon: Args: None Returns: - str: The well-known text (WKT) representation of the city's polygon. + Polygon: The polygon representing the city's boundary. Raises: ValueError: If the polygon fails to be reprojected. """ @@ -333,8 +201,7 @@ def _city_polygon(self) -> Polygon: polygon = tranform_polygon(polygon, crs, "EPSG:28992") if polygon is None: raise ValueError("Failed to reproject polygon") - wkt_polygon = polygon.wkt - return wkt_polygon + return polygon def _arbitrary_polygon(self, filepath: str) -> Polygon: """ @@ -351,38 +218,13 @@ def _arbitrary_polygon(self, filepath: str) -> Polygon: Raises: ValueError: If the polygon fails to be reprojected. """ - city_df = gpd.read_file(filepath) - polygon = city_df[city_df.geometry.type == "Polygon"].iloc[0].geometry - crs = city_df.crs - if crs is not None: + gdf = gpd.read_file(filepath) + polygon = gdf[gdf.geometry.type == "Polygon"].iloc[0].geometry + crs = gdf.crs + if self.epsg is not None: + polygon = tranform_polygon(polygon, self.epsg, "EPSG:28992") + elif crs is not None: polygon = tranform_polygon(polygon, crs, "EPSG:28992") if polygon is None: raise ValueError("Failed to reproject polygon") - wkt_polygon = polygon.wkt - return wkt_polygon - - def _clip_bbox(self, bbox: list[float]) -> Self: - """ - Clip the point cloud by a bounding box. - - Args: - bbox (list[float]): The bounding box to clip the point cloud. [xmin, ymin, xmax, ymax] - - Returns: - Self: The updated pipeline object. - - """ - clip_pipe = [ - { - "type": "filters.crop", - "bounds": f"([{bbox[0]},{bbox[2]} ],[{bbox[1]},{bbox[3]}])", - } - ] - - # append clip_pipe to pipeline_setting as -2 index. This is because the last index is the writer - self.pipeline_setting = ( - self.pipeline_setting[:-1] - + clip_pipe - + self.pipeline_setting[-1:] - ) - return self + return polygon diff --git a/ahn_cli/manipulator/pipeline2.py b/ahn_cli/manipulator/pipeline2.py deleted file mode 100644 index 712e8b6..0000000 --- a/ahn_cli/manipulator/pipeline2.py +++ /dev/null @@ -1,267 +0,0 @@ -import json -from typing import Any, Iterable, Self, Tuple -import numpy as np -from shapely import Point -import geopandas as gpd -import pdal -from shapely import Polygon -from ahn_cli.manipulator import rasterizer -import rasterio -from rasterio.io import BufferedDatasetWriterBase -import laspy - -from ahn_cli.manipulator.transformer import tranform_polygon - - -class PntCPipeline: - """ - A class representing a data processing pipeline. - - Args: - input_path (str): The path to the input data. - output_path (str): The path to save the output data. - city_filepath (str): The path to the city data file. - city_name (str): The name of the city. - - Attributes: - pipeline_setting (list): The configuration settings for the pipeline. - - """ - - las = laspy.LasData - city_df: gpd.GeoDataFrame - city_name: str - raster_res: float = 50.0 # default raster resolution - epsg: str | None = None - - def __init__( - self, - las: laspy.LasData, - city_filepath: str, - city_name: str, - epsg: int = 28992, - ) -> None: - self.las = las - self.city_df = gpd.read_file(city_filepath) - self.city_name = city_name - self.epsg = "EPSG:" + str(epsg) - - def decimate(self, step: int) -> Self: - """ - Decimate the point cloud by a given step. - - Args: - step (int): The step to decimate by. - - Returns: - Pipeline: The updated pipeline object. - - """ - self.las.points = self.las.points[::step] - return self - - def include(self, include_classes: list[int]) -> Self: - """ - Filters the point cloud to include only the specified classes. - - Args: - include_classes (list[int]): List of class labels to include. - - Returns: - Self: The modified pipeline object. - """ - mask = np.isin(self.las.classification, include_classes) - self.las.points = self.las.points[mask] - print( - "before", - len(self.las.classification), - "after", - len(self.las.points), - ) - return self - - def exclude(self, exclude_classes: list[int]) -> Self: - """ - Exclude points with specific classification values from the pipeline. - - Args: - exclude_classes (list[int]): List of classification values to exclude. - - Returns: - Self: The modified pipeline object. - """ - mask = np.isin(self.las.classification, exclude_classes, invert=True) - self.las.points = self.las.points[mask] - print( - "before", - len(self.las.classification), - "after", - len(self.las.points), - ) - return self - - # def clip(self) -> Self: - # """ - # Clip the point cloud by a polygon. - # """ - # rasterized_polygon, transform = rasterizer.polygon_to_raster( - # self._city_polygon(), self.raster_res - # ) - - # xyz = self.las.xyz - # points = self.las.points - # valid_points_mask = np.zeros((len(xyz),), dtype=np.uint8) - # transformer = rasterio.transform.AffineTransformer(transform) - # for i, point in enumerate(xyz): - # # row, col = self.map_to_grid(point[0], point[1], transform) - # row, col = transformer.rowcol(point[0], point[1]) - # if ( - # row < 0 - # or row >= rasterized_polygon.shape[0] - # or col < 0 - # or col >= rasterized_polygon.shape[1] - # ): - # valid_points_mask[i] = 0 - # else: - # if rasterized_polygon[row, col] != 1: - # valid_points_mask[i] = 0 - # else: - # valid_points_mask[i] = 1 - # self.las.raster = valid_points_mask - # print("valid points", len(valid_points_mask)) - # print("valid points", valid_points_mask) - # return self - - def clip(self) -> Self: - """ - Clip the point cloud by a polygon. - """ - rasterized_polygon, transform = rasterizer.polygon_to_raster( - self._city_polygon(), self.raster_res - ) - - xyz = self.las.xyz - - transformer = rasterio.transform.AffineTransformer(transform) - rows, cols = transformer.rowcol(xyz[:, 0], xyz[:, 1]) - valid_points_mask = rasterized_polygon[rows, cols] == 1 - self.las.points = self.las.points[valid_points_mask].copy() - - print("before", len(xyz), "after", len(self.las.points)) - return self - - # def clip2(self) -> Self: - # points = self.las.xyz - # polygon = self._city_polygon() - # for i, point in enumerate(points): - # if not polygon.contains([Point(point[0], point[1])]): - # point = None - # return self - - def clip_by_arbitrary_polygon(self, clip_file: str) -> Self: - """ - Clip the point cloud by a polygon. - - Args: - clip_file (str): The path to the polygon file. - - Returns: - Pipeline: The updated pipeline object. - - """ - polygon = self._arbitrary_polygon(clip_file) - rasterized_polygon, transform = rasterizer.polygon_to_raster( - polygon, self.raster_res - ) - - xyz = self.las.xyz - grid_coords = np.array(~transform * (xyz[:, :2].T)) - rows, cols = grid_coords.astype(int) - - rows = np.clip(rows, 0, rasterized_polygon.shape[0] - 1) - cols = np.clip(cols, 0, rasterized_polygon.shape[1] - 1) - - valid_points_mask = rasterized_polygon[rows, cols] == 1 - self.las.points = self.las.points[valid_points_mask] - - print("before", len(xyz), "after", len(self.las.points)) - return self - - def clip_by_bbox(self, bbox: list[float]) -> Self: - """ - Clips the point cloud by a bounding box. - - Args: - bbox (list[float]): The bounding box to clip the point cloud. [xmin, ymin, xmax, ymax] - - Returns: - Self: The updated pipeline object. - - """ - xyz = self.las.xyz - x_valid = (xyz[0] >= bbox[0]) & (xyz[0] <= bbox[2]) - y_valid = (xyz[1] >= bbox[1]) & (xyz[1] <= bbox[3]) - print("first point---------", xyz[0]) - valid_points_mask = np.where(x_valid & y_valid) - print("valid points", len(valid_points_mask)) - self.las.points = self.las.points[valid_points_mask] - return self - - def execute(self) -> laspy.LasData: - """ - Execute the pipeline. - - Returns: - laspy.LasData: The processed point cloud. - """ - - print("Pipeline executed successfully") - return self.las - - def _city_polygon(self) -> Polygon: - """ - Retrieves the polygon for a given city name. - - Args: - None - Returns: - str: The well-known text (WKT) representation of the city's polygon. - Raises: - ValueError: If the polygon fails to be reprojected. - """ - record = self.city_df[ - self.city_df["name"].str.lower() == self.city_name.lower() - ] - polygon = record.iloc[0].geometry - crs = self.city_df.crs - if crs is not None: - polygon = tranform_polygon(polygon, crs, "EPSG:28992") - if polygon is None: - raise ValueError("Failed to reproject polygon") - return polygon - - def _arbitrary_polygon(self, filepath: str) -> Polygon: - """ - Reads a file containing city data, extracts the first polygon geometry, - transforms it to a specific coordinate reference system (CRS), and returns - the well-known text (WKT) representation of the polygon. - - Args: - filepath (str): The path to the file containing city data. - - Returns: - str: The well-known text (WKT) representation of the polygon. - - Raises: - ValueError: If the polygon fails to be reprojected. - """ - gdf = gpd.read_file(filepath) - polygon = gdf[gdf.geometry.type == "Polygon"].iloc[0].geometry - crs = gdf.crs - if crs is not None: - polygon = tranform_polygon(polygon, crs, "EPSG:28992") - if self.epsg is not None: - polygon = tranform_polygon(polygon, self.epsg, "EPSG:28992") - if polygon is None: - raise ValueError("Failed to reproject polygon") - return polygon diff --git a/ahn_cli/manipulator/rasterizer.py b/ahn_cli/manipulator/rasterizer.py new file mode 100644 index 0000000..95862aa --- /dev/null +++ b/ahn_cli/manipulator/rasterizer.py @@ -0,0 +1,38 @@ +from typing import Tuple + +import numpy as np +from rasterio import features +from rasterio.transform import Affine, from_origin +from shapely import Polygon + + +def polygon_to_raster( + polygon: Polygon, + resolution: float, +) -> Tuple[np.ndarray, Affine]: + """ + Convert a polygon to a raster file. + + Args: + polygon (Polygon): The polygon to convert. + resolution (float): The resolution of the raster. + + Returns: + None + + """ + bbox = polygon.bounds + height = int((bbox[3] - bbox[1]) / resolution) + width = int((bbox[2] - bbox[0]) / resolution) + + transform = from_origin(bbox[0], bbox[3], resolution, resolution) + shape = (height, width) + rasterized = features.rasterize( + shapes=[polygon], + out_shape=shape, + transform=transform, + fill=0, + all_touched=True, + dtype="uint8", + ) + return rasterized, transform diff --git a/ahn_cli/process.py b/ahn_cli/process.py index 1910b20..d9526de 100644 --- a/ahn_cli/process.py +++ b/ahn_cli/process.py @@ -2,7 +2,7 @@ import numpy as np from ahn_cli.fetcher.request import Fetcher -from ahn_cli.manipulator.pipeline2 import PntCPipeline +from ahn_cli.manipulator.pipeline import PntCPipeline from ahn_cli.manipulator.preview import previewer import laspy from laspy.lasappender import LasAppender @@ -18,75 +18,64 @@ def process( no_clip_city: bool | None = False, clip_file: str | None = None, epsg: int | None = None, + decimate: int | None = None, bbox: list[float] | None = None, - radius: int | None = None, preview: bool | None = False, ) -> None: - # ahn_fetcher = Fetcher(base_url, city_name) - # fetched_files = ahn_fetcher.fetch() - fetched_files = { - # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ1_14.LAZ": "./testdata/3.LAZ", - # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ1_20.LAZ": "./testdata/5.LAZ", - # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ2_01.LAZ": "./testdata/6.LAZ", - # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ1_15.LAZ": "./testdata/4.LAZ", - # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ2_02.LAZ": "./testdata/7.LAZ", - # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ2_03.LAZ": "./testdata/8.LAZ", - # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ2_16.LAZ": "./testdata/13.LAZ", - # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ2_17.LAZ": "./testdata/14.LAZ", - # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ1_10.LAZ": "./testdata/2.LAZ", - # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ2_06.LAZ": "./testdata/9.LAZ", - "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ2_07.LAZ": "./testdata/10.LAZ", - "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ2_11.LAZ": "./testdata/11.LAZ", - } + ahn_fetcher = Fetcher(base_url, city_name) + fetched_files = ahn_fetcher.fetch() + # fetched_files = { + # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ1_14.LAZ": "./testdata/3.LAZ", + # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ1_20.LAZ": "./testdata/5.LAZ", + # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ2_01.LAZ": "./testdata/6.LAZ", + # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ1_15.LAZ": "./testdata/4.LAZ", + # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ2_02.LAZ": "./testdata/7.LAZ", + # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ2_03.LAZ": "./testdata/8.LAZ", + # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ2_16.LAZ": "./testdata/13.LAZ", + # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ2_17.LAZ": "./testdata/14.LAZ", + # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ1_10.LAZ": "./testdata/2.LAZ", + # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ2_06.LAZ": "./testdata/9.LAZ", + # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ2_07.LAZ": "./testdata/10.LAZ", + # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ2_11.LAZ": "./testdata/11.LAZ", + # } files = list(fetched_files.values()) - # maxs = np.array([0, 0, 0]) # [x, y, z] - # mins = np.array([np.inf, np.inf, np.inf]) # [x, y, z] - - # for file in files: - # with laspy.open(file) as las: - # header = las.header - # print("header", header) - # print("offsets", las.header.offsets) - # print("scale", las.header.scales) - # maxs = np.maximum(maxs, las.header.maxs) - # mins = np.minimum(mins, las.header.mins) - # print("maxs", maxs) - # print("mins", mins) - - # TODO: Problem is that because each file has its own origin, relative position will be different for i, file in enumerate(files): with laspy.open(file) as las: if i == 0: global_header = las.header # update maxs and mins if necessary + header = las.header + offset = global_header.offsets - header.offsets maxs = np.maximum(global_header.maxs, las.header.max) mins = np.minimum(global_header.mins, las.header.min) - pipeline = PntCPipeline(las.read(), city_polygon_path, city_name) + global_header.maxs = maxs + global_header.mins = mins + + pipeline = PntCPipeline( + las.read(), + city_polygon_path, + city_name, + epsg if epsg is not None else 4326, + ) + if bbox is not None: pipeline.clip_by_bbox(bbox) - header = las.header - offset = global_header.offsets - header.offsets - print("offset", offset) - + if decimate is not None: + pipeline.decimate(decimate) if include_classes is not None and len(include_classes) > 0: pipeline.include(include_classes) if exclude_classes is not None and len(exclude_classes) > 0: pipeline.exclude(exclude_classes) - if ( - not no_clip_city and radius is None - ): # if no radius is specified, clip by city - print("a") - # pipeline.clip() + if not no_clip_city: + pipeline.clip() + if clip_file is not None: + pipeline.clip_by_arbitrary_polygon(clip_file) - global_header.maxs = maxs - global_header.mins = mins with laspy.open( - output_path, mode="w" if i == 0 else "a", header=header + output_path, mode="w" if i == 0 else "a", header=global_header ) as writer: - print("writing points", len(pipeline.execute().points)) - points = pipeline.execute().points - print("x shape", points.x.shape) + points = pipeline.points().points points.x = points.x - offset[0] points.y = points.y - offset[1] points.z = points.z - offset[2] @@ -95,8 +84,8 @@ def process( if isinstance(writer, LasAppender): writer.append_points(points) - # for file in files: - # os.remove(file) + for file in files: + os.remove(file) if preview: print("Previewing output file...") diff --git a/ahn_cli/validator.py b/ahn_cli/validator.py index 5c6d184..a981dc1 100644 --- a/ahn_cli/validator.py +++ b/ahn_cli/validator.py @@ -11,8 +11,6 @@ def validate_output(output: str) -> str: raise ValueError("Output path is required.") if not os.path.exists(os.path.dirname(output)): raise ValueError("Output directory does not exist.") - if os.path.exists(output): - raise ValueError("Output file already exists.") return output @@ -63,6 +61,14 @@ def validate_clip_file(clip_file: str | None) -> str | None: return clip_file +def validate_epsg(epsg: int | None) -> int | None: + if epsg is None: + return None + if epsg < 1000 or epsg > 999999: + raise ValueError("EPSG code is not valid.") + return epsg + + def validate_decimate(decimate: int | None) -> int | None: if decimate is None: return None @@ -81,14 +87,6 @@ def validate_bbox(bbox: list[float] | None) -> list[float] | None: return bbox -def validate_radius(radius: int | None) -> int | None: - if radius is None: - return None - if radius < 1: - raise ValueError("Radius must be greater than 0.") - return radius - - def validate_all( cfg: config.Config, output_path: str, @@ -97,9 +95,9 @@ def validate_all( exclude_classes: list[int] | None = None, no_clip_city: bool | None = False, clip_file: str | None = None, + epsg: int | None = None, decimate: int | None = None, bbox: list[float] | None = None, - radius: int | None = None, ) -> bool: validate_output(output_path) validate_city(city_name, cfg.city_polygon_file) @@ -107,7 +105,7 @@ def validate_all( validate_exclude_classes(exclude_classes) validate_include_exclude(include_classes, exclude_classes) validate_clip_file(clip_file) + validate_epsg(epsg) validate_decimate(decimate) validate_bbox(bbox) - validate_radius(radius) return True diff --git a/dockerfile b/dockerfile new file mode 100644 index 0000000..554dc0c --- /dev/null +++ b/dockerfile @@ -0,0 +1,24 @@ +FROM python:3.11-slim + +WORKDIR /usr/src/app + +RUN apt-get update && apt-get install -y --no-install-recommends \ + git \ + gcc \ + g++ \ + libgdal-dev \ + pdal \ + && rm -rf /var/lib/apt/lists/* + +RUN pip install pdal + +COPY . /usr/src/app + +RUN pip install poetry + +COPY project.toml poetry.lock* /usr/src/app/ + +RUN poetry config virtualenvs.create false \ + && poetry install --no-interaction --no-ansi --no-dev + +CMD ["poetry", "run", "ahn_cli", "$(ARGS)"] diff --git a/tests/test_pipeline.py b/tests/test_pipeline.py index 00fd91b..c25924d 100644 --- a/tests/test_pipeline.py +++ b/tests/test_pipeline.py @@ -1,8 +1,8 @@ -import os -import tempfile +import time import unittest import laspy +import numpy as np from ahn_cli.manipulator.pipeline import PntCPipeline @@ -13,155 +13,79 @@ class TestPipeline(unittest.TestCase): - def test_laz(self) -> None: - with tempfile.NamedTemporaryFile(suffix=".laz", delete=True) as tmp: - # check if file exists - self.assertTrue(tmp.name) - self.assertTrue(os.path.exists(TEST_DATA0)) - - pipeline = PntCPipeline( - TEST_DATA0, tmp.name, CITY_FILE_PATH, "Westervoort" - ) - pipeline.execute() - - with laspy.open(TEST_DATA0) as las: - points_before = las.header.point_count - las = laspy.read(tmp.name) - points_after = las.header.point_count - self.assertEqual(points_after, points_before) - def test_decimate(self) -> None: - with tempfile.NamedTemporaryFile(suffix=".las", delete=True) as tmp: - pipeline = PntCPipeline( - TEST_DATA0, tmp.name, CITY_FILE_PATH, "Westervoort" - ) + with laspy.open(TEST_DATA0) as reader: + las = reader.read() + pipeline = PntCPipeline(las, CITY_FILE_PATH, "Westervoort") + points_before = len(pipeline.las.points) pipeline.decimate(10) - pipeline.execute() - - with laspy.open(TEST_DATA0) as las: - points_before = las.header.point_count - las = laspy.read(tmp.name) - points_after = las.header.point_count - self.assertTrue(points_after < points_before) + points_after = len(pipeline.las.points) + self.assertTrue(points_after < points_before) def test_include(self) -> None: - with tempfile.NamedTemporaryFile(suffix=".las", delete=True) as tmp: - pipeline = PntCPipeline( - TEST_DATA0, tmp.name, CITY_FILE_PATH, "Westervoort" - ) + with laspy.open(TEST_DATA0) as reader: + las = reader.read() + pipeline = PntCPipeline(las, CITY_FILE_PATH, "Westervoort") + points_before = len(pipeline.las.points) pipeline.include([2, 6]) - pipeline.execute() - - with laspy.open(TEST_DATA0) as las: - points_before = las.header.point_count - las = laspy.read(tmp.name) - points_after = las.header.point_count - self.assertTrue(points_after < points_before) - - classes2 = len(las.points[las.classification == 2]) - classes6 = len(las.points[las.classification == 6]) - classes0 = len(las.points[las.classification == 0]) - classes1 = len(las.points[las.classification == 1]) - classes9 = len(las.points[las.classification == 9]) - classes26 = len(las.points[las.classification == 26]) - - self.assertTrue(classes2 > 0) - self.assertTrue(classes6 > 0) - self.assertTrue(classes0 == 0) - self.assertTrue(classes1 == 0) - self.assertTrue(classes9 == 0) - self.assertTrue(classes26 == 0) + points_after = len(pipeline.las.points) + self.assertTrue(points_after < points_before) + classes2 = len(las.points[las.classification == 2]) + classes6 = len(las.points[las.classification == 6]) + classes0 = len(las.points[las.classification == 0]) + classes1 = len(las.points[las.classification == 1]) + classes9 = len(las.points[las.classification == 9]) + classes26 = len(las.points[las.classification == 26]) + self.assertTrue(classes2 > 0) + self.assertTrue(classes6 > 0) + self.assertTrue(classes0 == 0) + self.assertTrue(classes1 == 0) + self.assertTrue(classes9 == 0) + self.assertTrue(classes26 == 0) def test_exclude(self) -> None: - with tempfile.NamedTemporaryFile(suffix=".las", delete=True) as tmp: - pipeline = PntCPipeline( - TEST_DATA0, tmp.name, CITY_FILE_PATH, "Westervoort" - ) + with laspy.open(TEST_DATA0) as reader: + las = reader.read() + pipeline = PntCPipeline(las, CITY_FILE_PATH, "Westervoort") + points_before = len(pipeline.las.points) pipeline.exclude([2, 6]) - pipeline.execute() - - with laspy.open(TEST_DATA0) as las: - points_before = las.header.point_count - las = laspy.read(tmp.name) - points_after = las.header.point_count - self.assertTrue(points_after < points_before) - - classes2 = len(las.points[las.classification == 2]) - classes6 = len(las.points[las.classification == 6]) - self.assertTrue(classes2 == 0) - self.assertTrue(classes6 == 0) - - def test_merge(self) -> None: - with tempfile.NamedTemporaryFile(suffix=".las", delete=True) as tmp: - pipeline = PntCPipeline( - TEST_DATA0, tmp.name, CITY_FILE_PATH, "Westervoort" - ) - pipeline.merge([TEST_DATA1]) - pipeline.execute() - - with laspy.open(TEST_DATA0) as las: - points_before = las.header.point_count - las = laspy.read(tmp.name) - points_after = las.header.point_count - self.assertTrue(points_after > points_before) + points_after = len(pipeline.las.points) + self.assertTrue(points_after < points_before) + classes2 = len(las.points[las.classification == 2]) + classes6 = len(las.points[las.classification == 6]) + self.assertTrue(classes2 == 0) + self.assertTrue(classes6 == 0) def test_clip(self) -> None: - with tempfile.NamedTemporaryFile(suffix=".las", delete=True) as tmp: - pipeline = PntCPipeline( - TEST_DATA0, tmp.name, CITY_FILE_PATH, "Westervoort" - ) + with laspy.open(TEST_DATA1) as reader: + las = reader.read() + extra_dim = laspy.ExtraBytesParams(name="raster", type=np.uint8) + las.add_extra_dim(extra_dim) + pipeline = PntCPipeline(las, CITY_FILE_PATH, "Westervoort") + points_before = len(pipeline.las.points) pipeline.clip() - pipeline.execute() - - with laspy.open(TEST_DATA0) as las: - points_before = las.header.point_count - las = laspy.read(tmp.name) - points_after = las.header.point_count - self.assertTrue(points_after < points_before) + points_after = len(pipeline.las.points) + self.assertTrue(points_after < points_before) def test_clip_by_arbitrary_polygon(self) -> None: - with tempfile.NamedTemporaryFile(suffix=".las", delete=True) as tmp: - pipeline = PntCPipeline( - TEST_DATA0, tmp.name, CITY_FILE_PATH, "Westervoort" - ) + with laspy.open(TEST_DATA1) as reader: + las = reader.read() + pipeline = PntCPipeline(las, CITY_FILE_PATH, "Westervoort") + points_before = len(pipeline.las.points) pipeline.clip_by_arbitrary_polygon(WESTERVOORT_FILE_PATH) - pipeline.execute() - - with laspy.open(TEST_DATA0) as las: - points_before = las.header.point_count - las = laspy.read(tmp.name) - points_after = las.header.point_count - self.assertTrue(points_after < points_before) + points_after = len(pipeline.las.points) + self.assertTrue(points_after < points_before) def test_clip_by_bbox(self) -> None: - with tempfile.NamedTemporaryFile(suffix=".las", delete=True) as tmp: - pipeline = PntCPipeline( - TEST_DATA0, tmp.name, CITY_FILE_PATH, "Westervoort" - ) + with laspy.open(TEST_DATA0) as reader: + las = reader.read() + pipeline = PntCPipeline(las, CITY_FILE_PATH, "Westervoort") + points_before = len(pipeline.las.points) pipeline.clip_by_bbox( [194198.302994, 443461.343994, 194594.109009, 443694.838989] - ).execute() - self.assertTrue(True) - - with laspy.open(TEST_DATA0) as las: - points_before = las.header.point_count - las = laspy.read(tmp.name) - points_after = las.header.point_count - self.assertTrue(points_after < points_before) - - def test_clip_by_radius(self) -> None: - with tempfile.NamedTemporaryFile(suffix=".las", delete=True) as tmp: - pipeline = PntCPipeline( - TEST_DATA0, tmp.name, CITY_FILE_PATH, "Westervoort" ) - pipeline.clip_by_radius(1000) - pipeline.execute() - - with laspy.open(TEST_DATA0) as las: - points_before = las.header.point_count - las = laspy.read(tmp.name) - points_after = las.header.point_count - self.assertTrue(points_after < points_before) + points_after = len(pipeline.las.points) + self.assertTrue(points_after < points_before) if __name__ == "__main__": diff --git a/tests/test_rasterize.py b/tests/test_rasterize.py new file mode 100644 index 0000000..5cbe4e2 --- /dev/null +++ b/tests/test_rasterize.py @@ -0,0 +1,34 @@ +from importlib.resources import files +import geopandas as gpd +import unittest + +from ahn_cli.manipulator import rasterizer + +from ahn_cli.manipulator.transformer import tranform_polygon + + +TEST_DATA0 = "./tests/testdata/westervoort0_thinned.las" +TEST_DATA1 = "./tests/testdata/westervoort1_thinned.las" +CITY_FILE_PATH = "./ahn_cli/fetcher/data/municipality_simple.geojson" +WESTERVOORT_FILE_PATH = "./tests/testdata/westervoort.geojson" + + +class TestRasterize(unittest.TestCase): + def test_rasterize(self) -> None: + city_polygon_file = files("ahn_cli.fetcher.data").joinpath( + "municipality_simple.geojson" + ) + city_df = gpd.read_file(city_polygon_file) + city_name = "Westervoort" + record = city_df[city_df["name"].str.lower() == city_name.lower()] + polygon = record.iloc[0].geometry + crs = city_df.crs + if crs is not None: + polygon = tranform_polygon(polygon, crs, "EPSG:28992") + if polygon is None: + raise ValueError("Failed to reproject polygon") + rasterizer.polygon_to_raster(polygon, 50) + + +if __name__ == "__main__": + unittest.main() From a32468ca78e53137bd7ce397592b6250e01f4839 Mon Sep 17 00:00:00 2001 From: HideBa Date: Fri, 23 Feb 2024 12:41:08 +0100 Subject: [PATCH 05/15] feat: fix ci and remove pdal --- .github/workflows/ci.yml | 52 ---------------------------------------- dockerfile | 24 ------------------- poetry.lock | 16 +------------ pyproject.toml | 1 - 4 files changed, 1 insertion(+), 92 deletions(-) delete mode 100644 dockerfile diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fa957a9..91cea80 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,49 +24,6 @@ jobs: with: python-version: 3.11.1 - - name: Install PDAL - run: | - sudo apt-get update - sudo apt-get install -y \ - g++ \ - cmake \ - git \ - libgdal-dev \ - libgeotiff-dev \ - liblaszip-dev \ - libboost-all-dev \ - libboost-program-options-dev \ - libboost-filesystem-dev \ - libboost-system-dev \ - libsqlite3-dev \ - sqlite3 \ - libproj-dev \ - libcurl4-openssl-dev \ - libjsoncpp-dev \ - libxml2-dev \ - libflann-dev \ - libpcl-dev \ - libeigen3-dev \ - libnitro-dev \ - libqhull-dev \ - libtbb-dev - - name: Clone PDAL - run: | - git clone https://github.com/PDAL/PDAL.git - cd PDAL - git checkout 2.5-maintenance - mkdir build - cd build - cmake .. - make - sudo make install - sudo ldconfig - pdal --version - - name: Check pdal - run: | - echo "PDAL version -------------" - pdal --version - - name: Cache pip packages uses: actions/cache@v2 with: @@ -84,15 +41,6 @@ jobs: - name: Install dependencies run: | poetry install - # - name: Install dependencies excluding PDAL - # run: | - # poetry add pdal --optional - # poetry install --no-root - - # - name: Install PDAL Python package - # run: | - # source .venv/bin/activate - # pip install pdal==3.2.3 - name: Linting run: | diff --git a/dockerfile b/dockerfile deleted file mode 100644 index 554dc0c..0000000 --- a/dockerfile +++ /dev/null @@ -1,24 +0,0 @@ -FROM python:3.11-slim - -WORKDIR /usr/src/app - -RUN apt-get update && apt-get install -y --no-install-recommends \ - git \ - gcc \ - g++ \ - libgdal-dev \ - pdal \ - && rm -rf /var/lib/apt/lists/* - -RUN pip install pdal - -COPY . /usr/src/app - -RUN pip install poetry - -COPY project.toml poetry.lock* /usr/src/app/ - -RUN poetry config virtualenvs.create false \ - && poetry install --no-interaction --no-ansi --no-dev - -CMD ["poetry", "run", "ahn_cli", "$(ARGS)"] diff --git a/poetry.lock b/poetry.lock index 0eb3309..c3d588e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1023,20 +1023,6 @@ files = [ {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] -[[package]] -name = "pdal" -version = "3.2.3" -description = "Point cloud data processing" -category = "main" -optional = false -python-versions = "*" -files = [ - {file = "pdal-3.2.3.tar.gz", hash = "sha256:0bc2cb113f4df45b239dd54aedb8026478349db2daa5c98569994308d0563807"}, -] - -[package.dependencies] -numpy = "*" - [[package]] name = "pillow" version = "10.2.0" @@ -1654,4 +1640,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.9, <3.12" -content-hash = "2656664055db173dae9e59093c97651c79ec67cb388d79a41799528ab175e815" +content-hash = "3e02d350ce90c3b3f1e143f321ef08dfa5446f74666af206ceae7ba49806f6d2" diff --git a/pyproject.toml b/pyproject.toml index 4c8f910..b431fc2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,6 @@ ahn_cli = "ahn_cli.main:main" [tool.poetry.dependencies] python = ">=3.9, <3.12" click = "^8.1.7" -pdal = "^3.2.3" geopandas = "^0.14.1" shapely = "^2.0.2" laspy = {extras = ["lazrs"], version = "^2.5.3"} From 41553ce91f11a5533e4abae6bbcebe35d9d15940 Mon Sep 17 00:00:00 2001 From: HideBa Date: Fri, 23 Feb 2024 12:53:18 +0100 Subject: [PATCH 06/15] chore: update readme --- README.md | 11 ++++++++--- ahn_cli/process.py | 14 -------------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index d5f5e61..87fedd0 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,8 @@ Options: -ncc, --no-clip-city Avoid clipping the point cloud data to the city boundary. -cf, --clip-file Provide a file path for a clipping boundary file to clip the point cloud data to a specified area. - -r, --radius Define a radius (in meters) to clip the point cloud data, + -e, --epsg Set the EPSG code for user's clip file. + -b, --bbox Specify a bounding box to clip the point cloud data. It should be comma-separated list with minx,miny,maxx,maxy centered on the city polygon. -p, --preview Preview the point cloud data in a 3D viewer. -h, --help [category] Show help information. Optionally specify a category for @@ -70,9 +71,13 @@ ahn_cli -c delft -o ./delft.laz -i 1,2 -ncc ahn_cli -c delft -o ./delft.laz -i 1,2 -d 2 ``` -**Specify a Radius for Clipping:** +**Specify a Bounding box for clipping:** + +If you specify a `b`, it will clip the point cloud data with specified bounding box. +``` +ahn_cli -c delft -o ./delft.laz -i 1,2 -d 2 -b 194198.302994,443461.343994,194594.109009,443694.838989 +``` -If you specify a `radius` (in meters), it will clip the point cloud data from the center of the city polygon to the specified radius. ## Reporting Issues diff --git a/ahn_cli/process.py b/ahn_cli/process.py index d9526de..16e16f2 100644 --- a/ahn_cli/process.py +++ b/ahn_cli/process.py @@ -24,20 +24,6 @@ def process( ) -> None: ahn_fetcher = Fetcher(base_url, city_name) fetched_files = ahn_fetcher.fetch() - # fetched_files = { - # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ1_14.LAZ": "./testdata/3.LAZ", - # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ1_20.LAZ": "./testdata/5.LAZ", - # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ2_01.LAZ": "./testdata/6.LAZ", - # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ1_15.LAZ": "./testdata/4.LAZ", - # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ2_02.LAZ": "./testdata/7.LAZ", - # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ2_03.LAZ": "./testdata/8.LAZ", - # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ2_16.LAZ": "./testdata/13.LAZ", - # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ2_17.LAZ": "./testdata/14.LAZ", - # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ1_10.LAZ": "./testdata/2.LAZ", - # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ2_06.LAZ": "./testdata/9.LAZ", - # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ2_07.LAZ": "./testdata/10.LAZ", - # "https://geotiles.citg.tudelft.nl/AHN4_T/40BZ2_11.LAZ": "./testdata/11.LAZ", - # } files = list(fetched_files.values()) for i, file in enumerate(files): From 8655454448ae02439afe2665f91923c91d9b9f14 Mon Sep 17 00:00:00 2001 From: HideBa Date: Fri, 23 Feb 2024 14:39:54 +0100 Subject: [PATCH 07/15] feat: replace pyvista with polyscope --- .gitignore | 2 + ahn_cli/fetcher/request.py | 21 +- ahn_cli/manipulator/preview.py | 12 +- ahn_cli/process.py | 7 +- poetry.lock | 604 ++++-------------------- pyproject.toml | 3 +- tests/test_pipeline.py | 9 + tests/testdata/westervoort28992.geojson | 8 + 8 files changed, 136 insertions(+), 530 deletions(-) create mode 100644 tests/testdata/westervoort28992.geojson diff --git a/.gitignore b/.gitignore index 2c80b4f..3de9fa5 100644 --- a/.gitignore +++ b/.gitignore @@ -164,3 +164,5 @@ cython_debug/ *.qmd /testdata /out +.polyscope.ini +imgui.ini diff --git a/ahn_cli/fetcher/request.py b/ahn_cli/fetcher/request.py index 53d90e4..078d27c 100644 --- a/ahn_cli/fetcher/request.py +++ b/ahn_cli/fetcher/request.py @@ -5,6 +5,7 @@ from concurrent.futures import ThreadPoolExecutor from threading import Lock from urllib.parse import urlparse +from tqdm import tqdm import requests @@ -23,23 +24,29 @@ def fetch(self) -> dict: logging.info("Start fetching AHN data") logging.info(f"Fetching {len(self.urls)} tiles") - def req(url: str, nth: int, results: dict, lock: Lock) -> None: - logging.info(f"Fetching tile {nth + 1}/{len(self.urls)}") - print(f"Fetching tile {nth + 1}/{len(self.urls)}") + def req( + url: str, nth: int, results: dict, lock: Lock, pbar: tqdm + ) -> None: res = requests.get(url, stream=True) with tempfile.NamedTemporaryFile( delete=False, mode="w+b", suffix=".laz" ) as temp_file: - for chunk in res.iter_content(chunk_size=1024 * 1024): + for chunk in tqdm( + res.iter_content(chunk_size=1024 * 1024), + desc="writing a file", + ): temp_file.write(chunk) with lock: results[url] = temp_file.name + pbar.update(1) results: dict = {} lock = threading.Lock() - with ThreadPoolExecutor(max_workers=8) as executor: - for i, url in enumerate(self.urls): - executor.submit(req, url, i, results, lock) + with tqdm(total=len(self.urls)) as pbar: + pbar.set_description("Fetching AHN data") + with ThreadPoolExecutor(max_workers=8) as executor: + for i, url in enumerate(self.urls): + executor.submit(req, url, i, results, lock, pbar) return results def _check_valid_url(self, url: str) -> bool: diff --git a/ahn_cli/manipulator/preview.py b/ahn_cli/manipulator/preview.py index bd8d415..87a7c8c 100644 --- a/ahn_cli/manipulator/preview.py +++ b/ahn_cli/manipulator/preview.py @@ -1,15 +1,13 @@ import laspy -import pyvista as pv +import polyscope as ps def previewer(filepath: str) -> None: # Read LAS file las = laspy.read(filepath) + points = las.xyz - points = pv.PolyData(las.xyz) + ps.init() + _ps_cloud = ps.register_point_cloud("AHN Point Cloud", points) - # Plot using PyVista - plotter = pv.Plotter() - plotter.add_points(points, color="blue", point_size=3) - - plotter.show() + ps.show() diff --git a/ahn_cli/process.py b/ahn_cli/process.py index 16e16f2..08a7ce5 100644 --- a/ahn_cli/process.py +++ b/ahn_cli/process.py @@ -1,6 +1,8 @@ +import logging import os import numpy as np +from tqdm import tqdm from ahn_cli.fetcher.request import Fetcher from ahn_cli.manipulator.pipeline import PntCPipeline from ahn_cli.manipulator.preview import previewer @@ -26,7 +28,10 @@ def process( fetched_files = ahn_fetcher.fetch() files = list(fetched_files.values()) - for i, file in enumerate(files): + for i, file in enumerate( + tqdm(files, desc="Processing files", unit="file", total=len(files)) + ): + logging.info("Start processing downloaded files...") with laspy.open(file) as las: if i == 0: global_header = las.header diff --git a/poetry.lock b/poetry.lock index c3d588e..42d4a7f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -257,86 +257,6 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -[[package]] -name = "contourpy" -version = "1.2.0" -description = "Python library for calculating contours of 2D quadrilateral grids" -category = "main" -optional = false -python-versions = ">=3.9" -files = [ - {file = "contourpy-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0274c1cb63625972c0c007ab14dd9ba9e199c36ae1a231ce45d725cbcbfd10a8"}, - {file = "contourpy-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ab459a1cbbf18e8698399c595a01f6dcc5c138220ca3ea9e7e6126232d102bb4"}, - {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fdd887f17c2f4572ce548461e4f96396681212d858cae7bd52ba3310bc6f00f"}, - {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d16edfc3fc09968e09ddffada434b3bf989bf4911535e04eada58469873e28e"}, - {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c203f617abc0dde5792beb586f827021069fb6d403d7f4d5c2b543d87edceb9"}, - {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b69303ceb2e4d4f146bf82fda78891ef7bcd80c41bf16bfca3d0d7eb545448aa"}, - {file = "contourpy-1.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:884c3f9d42d7218304bc74a8a7693d172685c84bd7ab2bab1ee567b769696df9"}, - {file = "contourpy-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4a1b1208102be6e851f20066bf0e7a96b7d48a07c9b0cfe6d0d4545c2f6cadab"}, - {file = "contourpy-1.2.0-cp310-cp310-win32.whl", hash = "sha256:34b9071c040d6fe45d9826cbbe3727d20d83f1b6110d219b83eb0e2a01d79488"}, - {file = "contourpy-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:bd2f1ae63998da104f16a8b788f685e55d65760cd1929518fd94cd682bf03e41"}, - {file = "contourpy-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dd10c26b4eadae44783c45ad6655220426f971c61d9b239e6f7b16d5cdaaa727"}, - {file = "contourpy-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5c6b28956b7b232ae801406e529ad7b350d3f09a4fde958dfdf3c0520cdde0dd"}, - {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebeac59e9e1eb4b84940d076d9f9a6cec0064e241818bcb6e32124cc5c3e377a"}, - {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:139d8d2e1c1dd52d78682f505e980f592ba53c9f73bd6be102233e358b401063"}, - {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e9dc350fb4c58adc64df3e0703ab076f60aac06e67d48b3848c23647ae4310e"}, - {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18fc2b4ed8e4a8fe849d18dce4bd3c7ea637758c6343a1f2bae1e9bd4c9f4686"}, - {file = "contourpy-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:16a7380e943a6d52472096cb7ad5264ecee36ed60888e2a3d3814991a0107286"}, - {file = "contourpy-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8d8faf05be5ec8e02a4d86f616fc2a0322ff4a4ce26c0f09d9f7fb5330a35c95"}, - {file = "contourpy-1.2.0-cp311-cp311-win32.whl", hash = "sha256:67b7f17679fa62ec82b7e3e611c43a016b887bd64fb933b3ae8638583006c6d6"}, - {file = "contourpy-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:99ad97258985328b4f207a5e777c1b44a83bfe7cf1f87b99f9c11d4ee477c4de"}, - {file = "contourpy-1.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:575bcaf957a25d1194903a10bc9f316c136c19f24e0985a2b9b5608bdf5dbfe0"}, - {file = "contourpy-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9e6c93b5b2dbcedad20a2f18ec22cae47da0d705d454308063421a3b290d9ea4"}, - {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:464b423bc2a009088f19bdf1f232299e8b6917963e2b7e1d277da5041f33a779"}, - {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:68ce4788b7d93e47f84edd3f1f95acdcd142ae60bc0e5493bfd120683d2d4316"}, - {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d7d1f8871998cdff5d2ff6a087e5e1780139abe2838e85b0b46b7ae6cc25399"}, - {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e739530c662a8d6d42c37c2ed52a6f0932c2d4a3e8c1f90692ad0ce1274abe0"}, - {file = "contourpy-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:247b9d16535acaa766d03037d8e8fb20866d054d3c7fbf6fd1f993f11fc60ca0"}, - {file = "contourpy-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:461e3ae84cd90b30f8d533f07d87c00379644205b1d33a5ea03381edc4b69431"}, - {file = "contourpy-1.2.0-cp312-cp312-win32.whl", hash = "sha256:1c2559d6cffc94890b0529ea7eeecc20d6fadc1539273aa27faf503eb4656d8f"}, - {file = "contourpy-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:491b1917afdd8638a05b611a56d46587d5a632cabead889a5440f7c638bc6ed9"}, - {file = "contourpy-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5fd1810973a375ca0e097dee059c407913ba35723b111df75671a1976efa04bc"}, - {file = "contourpy-1.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:999c71939aad2780f003979b25ac5b8f2df651dac7b38fb8ce6c46ba5abe6ae9"}, - {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7caf9b241464c404613512d5594a6e2ff0cc9cb5615c9475cc1d9b514218ae8"}, - {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:266270c6f6608340f6c9836a0fb9b367be61dde0c9a9a18d5ece97774105ff3e"}, - {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbd50d0a0539ae2e96e537553aff6d02c10ed165ef40c65b0e27e744a0f10af8"}, - {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11f8d2554e52f459918f7b8e6aa20ec2a3bce35ce95c1f0ef4ba36fbda306df5"}, - {file = "contourpy-1.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ce96dd400486e80ac7d195b2d800b03e3e6a787e2a522bfb83755938465a819e"}, - {file = "contourpy-1.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6d3364b999c62f539cd403f8123ae426da946e142312a514162adb2addd8d808"}, - {file = "contourpy-1.2.0-cp39-cp39-win32.whl", hash = "sha256:1c88dfb9e0c77612febebb6ac69d44a8d81e3dc60f993215425b62c1161353f4"}, - {file = "contourpy-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:78e6ad33cf2e2e80c5dfaaa0beec3d61face0fb650557100ee36db808bfa6843"}, - {file = "contourpy-1.2.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:be16975d94c320432657ad2402f6760990cb640c161ae6da1363051805fa8108"}, - {file = "contourpy-1.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b95a225d4948b26a28c08307a60ac00fb8671b14f2047fc5476613252a129776"}, - {file = "contourpy-1.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d7e03c0f9a4f90dc18d4e77e9ef4ec7b7bbb437f7f675be8e530d65ae6ef956"}, - {file = "contourpy-1.2.0.tar.gz", hash = "sha256:171f311cb758de7da13fc53af221ae47a5877be5a0843a9fe150818c51ed276a"}, -] - -[package.dependencies] -numpy = ">=1.20,<2.0" - -[package.extras] -bokeh = ["bokeh", "selenium"] -docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] -mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.6.1)", "types-Pillow"] -test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] -test-no-images = ["pytest", "pytest-cov", "pytest-xdist", "wurlitzer"] - -[[package]] -name = "cycler" -version = "0.12.1" -description = "Composable style cycles" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, - {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, -] - -[package.extras] -docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] -tests = ["pytest", "pytest-cov", "pytest-xdist"] - [[package]] name = "exceptiongroup" version = "1.2.0" @@ -419,72 +339,6 @@ mccabe = ">=0.7.0,<0.8.0" pycodestyle = ">=2.11.0,<2.12.0" pyflakes = ">=3.1.0,<3.2.0" -[[package]] -name = "fonttools" -version = "4.47.0" -description = "Tools to manipulate font files" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "fonttools-4.47.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2d2404107626f97a221dc1a65b05396d2bb2ce38e435f64f26ed2369f68675d9"}, - {file = "fonttools-4.47.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c01f409be619a9a0f5590389e37ccb58b47264939f0e8d58bfa1f3ba07d22671"}, - {file = "fonttools-4.47.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d986b66ff722ef675b7ee22fbe5947a41f60a61a4da15579d5e276d897fbc7fa"}, - {file = "fonttools-4.47.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8acf6dd0434b211b3bd30d572d9e019831aae17a54016629fa8224783b22df8"}, - {file = "fonttools-4.47.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:495369c660e0c27233e3c572269cbe520f7f4978be675f990f4005937337d391"}, - {file = "fonttools-4.47.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c59227d7ba5b232281c26ae04fac2c73a79ad0e236bca5c44aae904a18f14faf"}, - {file = "fonttools-4.47.0-cp310-cp310-win32.whl", hash = "sha256:59a6c8b71a245800e923cb684a2dc0eac19c56493e2f896218fcf2571ed28984"}, - {file = "fonttools-4.47.0-cp310-cp310-win_amd64.whl", hash = "sha256:52c82df66201f3a90db438d9d7b337c7c98139de598d0728fb99dab9fd0495ca"}, - {file = "fonttools-4.47.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:854421e328d47d70aa5abceacbe8eef231961b162c71cbe7ff3f47e235e2e5c5"}, - {file = "fonttools-4.47.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:511482df31cfea9f697930f61520f6541185fa5eeba2fa760fe72e8eee5af88b"}, - {file = "fonttools-4.47.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce0e2c88c8c985b7b9a7efcd06511fb0a1fe3ddd9a6cd2895ef1dbf9059719d7"}, - {file = "fonttools-4.47.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7a0a8848726956e9d9fb18c977a279013daadf0cbb6725d2015a6dd57527992"}, - {file = "fonttools-4.47.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e869da810ae35afb3019baa0d0306cdbab4760a54909c89ad8904fa629991812"}, - {file = "fonttools-4.47.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dd23848f877c3754f53a4903fb7a593ed100924f9b4bff7d5a4e2e8a7001ae11"}, - {file = "fonttools-4.47.0-cp311-cp311-win32.whl", hash = "sha256:bf1810635c00f7c45d93085611c995fc130009cec5abdc35b327156aa191f982"}, - {file = "fonttools-4.47.0-cp311-cp311-win_amd64.whl", hash = "sha256:61df4dee5d38ab65b26da8efd62d859a1eef7a34dcbc331299a28e24d04c59a7"}, - {file = "fonttools-4.47.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e3f4d61f3a8195eac784f1d0c16c0a3105382c1b9a74d99ac4ba421da39a8826"}, - {file = "fonttools-4.47.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:174995f7b057e799355b393e97f4f93ef1f2197cbfa945e988d49b2a09ecbce8"}, - {file = "fonttools-4.47.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea592e6a09b71cb7a7661dd93ac0b877a6228e2d677ebacbad0a4d118494c86d"}, - {file = "fonttools-4.47.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40bdbe90b33897d9cc4a39f8e415b0fcdeae4c40a99374b8a4982f127ff5c767"}, - {file = "fonttools-4.47.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:843509ae9b93db5aaf1a6302085e30bddc1111d31e11d724584818f5b698f500"}, - {file = "fonttools-4.47.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9acfa1cdc479e0dde528b61423855913d949a7f7fe09e276228298fef4589540"}, - {file = "fonttools-4.47.0-cp312-cp312-win32.whl", hash = "sha256:66c92ec7f95fd9732550ebedefcd190a8d81beaa97e89d523a0d17198a8bda4d"}, - {file = "fonttools-4.47.0-cp312-cp312-win_amd64.whl", hash = "sha256:e8fa20748de55d0021f83754b371432dca0439e02847962fc4c42a0e444c2d78"}, - {file = "fonttools-4.47.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c75e19971209fbbce891ebfd1b10c37320a5a28e8d438861c21d35305aedb81c"}, - {file = "fonttools-4.47.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e79f1a3970d25f692bbb8c8c2637e621a66c0d60c109ab48d4a160f50856deff"}, - {file = "fonttools-4.47.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:562681188c62c024fe2c611b32e08b8de2afa00c0c4e72bed47c47c318e16d5c"}, - {file = "fonttools-4.47.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a77a60315c33393b2bd29d538d1ef026060a63d3a49a9233b779261bad9c3f71"}, - {file = "fonttools-4.47.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4fabb8cc9422efae1a925160083fdcbab8fdc96a8483441eb7457235df625bd"}, - {file = "fonttools-4.47.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2a78dba8c2a1e9d53a0fb5382979f024200dc86adc46a56cbb668a2249862fda"}, - {file = "fonttools-4.47.0-cp38-cp38-win32.whl", hash = "sha256:e6b968543fde4119231c12c2a953dcf83349590ca631ba8216a8edf9cd4d36a9"}, - {file = "fonttools-4.47.0-cp38-cp38-win_amd64.whl", hash = "sha256:4a9a51745c0439516d947480d4d884fa18bd1458e05b829e482b9269afa655bc"}, - {file = "fonttools-4.47.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:62d8ddb058b8e87018e5dc26f3258e2c30daad4c87262dfeb0e2617dd84750e6"}, - {file = "fonttools-4.47.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5dde0eab40faaa5476133123f6a622a1cc3ac9b7af45d65690870620323308b4"}, - {file = "fonttools-4.47.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4da089f6dfdb822293bde576916492cd708c37c2501c3651adde39804630538"}, - {file = "fonttools-4.47.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:253bb46bab970e8aae254cebf2ae3db98a4ef6bd034707aa68a239027d2b198d"}, - {file = "fonttools-4.47.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1193fb090061efa2f9e2d8d743ae9850c77b66746a3b32792324cdce65784154"}, - {file = "fonttools-4.47.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:084511482dd265bce6dca24c509894062f0117e4e6869384d853f46c0e6d43be"}, - {file = "fonttools-4.47.0-cp39-cp39-win32.whl", hash = "sha256:97620c4af36e4c849e52661492e31dc36916df12571cb900d16960ab8e92a980"}, - {file = "fonttools-4.47.0-cp39-cp39-win_amd64.whl", hash = "sha256:e77bdf52185bdaf63d39f3e1ac3212e6cfa3ab07d509b94557a8902ce9c13c82"}, - {file = "fonttools-4.47.0-py3-none-any.whl", hash = "sha256:d6477ba902dd2d7adda7f0fd3bfaeb92885d45993c9e1928c9f28fc3961415f7"}, - {file = "fonttools-4.47.0.tar.gz", hash = "sha256:ec13a10715eef0e031858c1c23bfaee6cba02b97558e4a7bfa089dba4a8c2ebf"}, -] - -[package.extras] -all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] -graphite = ["lz4 (>=1.7.4.2)"] -interpolatable = ["munkres", "pycairo", "scipy"] -lxml = ["lxml (>=4.0,<5)"] -pathops = ["skia-pathops (>=0.5.0)"] -plot = ["matplotlib"] -repacker = ["uharfbuzz (>=0.23.0)"] -symfont = ["sympy"] -type1 = ["xattr"] -ufo = ["fs (>=2.2.0,<3)"] -unicode = ["unicodedata2 (>=15.1.0)"] -woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] - [[package]] name = "geopandas" version = "0.14.1" @@ -536,25 +390,6 @@ docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.link perf = ["ipython"] testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] -[[package]] -name = "importlib-resources" -version = "6.1.1" -description = "Read resources from Python packages" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "importlib_resources-6.1.1-py3-none-any.whl", hash = "sha256:e8bf90d8213b486f428c9c39714b920041cb02c184686a3dee24905aaa8105d6"}, - {file = "importlib_resources-6.1.1.tar.gz", hash = "sha256:3893a00122eafde6894c59914446a512f728a0c1a45f9bb9b63721b6bacf0b4a"}, -] - -[package.dependencies] -zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff", "zipp (>=3.17)"] - [[package]] name = "iniconfig" version = "2.0.0" @@ -582,120 +417,6 @@ files = [ [package.extras] colors = ["colorama (>=0.4.6)"] -[[package]] -name = "kiwisolver" -version = "1.4.5" -description = "A fast implementation of the Cassowary constraint solver" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af"}, - {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3"}, - {file = "kiwisolver-1.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ef7afcd2d281494c0a9101d5c571970708ad911d028137cd558f02b851c08b4"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9eaa8b117dc8337728e834b9c6e2611f10c79e38f65157c4c38e9400286f5cb1"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec20916e7b4cbfb1f12380e46486ec4bcbaa91a9c448b97023fde0d5bbf9e4ff"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b42c68602539407884cf70d6a480a469b93b81b7701378ba5e2328660c847a"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa12042de0171fad672b6c59df69106d20d5596e4f87b5e8f76df757a7c399aa"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a40773c71d7ccdd3798f6489aaac9eee213d566850a9533f8d26332d626b82c"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:19df6e621f6d8b4b9c4d45f40a66839294ff2bb235e64d2178f7522d9170ac5b"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:83d78376d0d4fd884e2c114d0621624b73d2aba4e2788182d286309ebdeed770"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e391b1f0a8a5a10ab3b9bb6afcfd74f2175f24f8975fb87ecae700d1503cdee0"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:852542f9481f4a62dbb5dd99e8ab7aedfeb8fb6342349a181d4036877410f525"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59edc41b24031bc25108e210c0def6f6c2191210492a972d585a06ff246bb79b"}, - {file = "kiwisolver-1.4.5-cp310-cp310-win32.whl", hash = "sha256:a6aa6315319a052b4ee378aa171959c898a6183f15c1e541821c5c59beaa0238"}, - {file = "kiwisolver-1.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:d0ef46024e6a3d79c01ff13801cb19d0cad7fd859b15037aec74315540acc276"}, - {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5"}, - {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90"}, - {file = "kiwisolver-1.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f"}, - {file = "kiwisolver-1.4.5-cp311-cp311-win32.whl", hash = "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac"}, - {file = "kiwisolver-1.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355"}, - {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a"}, - {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192"}, - {file = "kiwisolver-1.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a"}, - {file = "kiwisolver-1.4.5-cp312-cp312-win32.whl", hash = "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20"}, - {file = "kiwisolver-1.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3a2b053a0ab7a3960c98725cfb0bf5b48ba82f64ec95fe06f1d06c99b552e130"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cd32d6c13807e5c66a7cbb79f90b553642f296ae4518a60d8d76243b0ad2898"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59ec7b7c7e1a61061850d53aaf8e93db63dce0c936db1fda2658b70e4a1be709"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da4cfb373035def307905d05041c1d06d8936452fe89d464743ae7fb8371078b"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2400873bccc260b6ae184b2b8a4fec0e4082d30648eadb7c3d9a13405d861e89"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1b04139c4236a0f3aff534479b58f6f849a8b351e1314826c2d230849ed48985"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4e66e81a5779b65ac21764c295087de82235597a2293d18d943f8e9e32746265"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7931d8f1f67c4be9ba1dd9c451fb0eeca1a25b89e4d3f89e828fe12a519b782a"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b3f7e75f3015df442238cca659f8baa5f42ce2a8582727981cbfa15fee0ee205"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:bbf1d63eef84b2e8c89011b7f2235b1e0bf7dacc11cac9431fc6468e99ac77fb"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4c380469bd3f970ef677bf2bcba2b6b0b4d5c75e7a020fb863ef75084efad66f"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-win32.whl", hash = "sha256:9408acf3270c4b6baad483865191e3e582b638b1654a007c62e3efe96f09a9a3"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-win_amd64.whl", hash = "sha256:5b94529f9b2591b7af5f3e0e730a4e0a41ea174af35a4fd067775f9bdfeee01a"}, - {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:11c7de8f692fc99816e8ac50d1d1aef4f75126eefc33ac79aac02c099fd3db71"}, - {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:53abb58632235cd154176ced1ae8f0d29a6657aa1aa9decf50b899b755bc2b93"}, - {file = "kiwisolver-1.4.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:88b9f257ca61b838b6f8094a62418421f87ac2a1069f7e896c36a7d86b5d4c29"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3195782b26fc03aa9c6913d5bad5aeb864bdc372924c093b0f1cebad603dd712"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc579bf0f502e54926519451b920e875f433aceb4624a3646b3252b5caa9e0b6"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a580c91d686376f0f7c295357595c5a026e6cbc3d77b7c36e290201e7c11ecb"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cfe6ab8da05c01ba6fbea630377b5da2cd9bcbc6338510116b01c1bc939a2c18"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d2e5a98f0ec99beb3c10e13b387f8db39106d53993f498b295f0c914328b1333"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a51a263952b1429e429ff236d2f5a21c5125437861baeed77f5e1cc2d2c7c6da"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3edd2fa14e68c9be82c5b16689e8d63d89fe927e56debd6e1dbce7a26a17f81b"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:74d1b44c6cfc897df648cc9fdaa09bc3e7679926e6f96df05775d4fb3946571c"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:76d9289ed3f7501012e05abb8358bbb129149dbd173f1f57a1bf1c22d19ab7cc"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:92dea1ffe3714fa8eb6a314d2b3c773208d865a0e0d35e713ec54eea08a66250"}, - {file = "kiwisolver-1.4.5-cp38-cp38-win32.whl", hash = "sha256:5c90ae8c8d32e472be041e76f9d2f2dbff4d0b0be8bd4041770eddb18cf49a4e"}, - {file = "kiwisolver-1.4.5-cp38-cp38-win_amd64.whl", hash = "sha256:c7940c1dc63eb37a67721b10d703247552416f719c4188c54e04334321351ced"}, - {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9407b6a5f0d675e8a827ad8742e1d6b49d9c1a1da5d952a67d50ef5f4170b18d"}, - {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15568384086b6df3c65353820a4473575dbad192e35010f622c6ce3eebd57af9"}, - {file = "kiwisolver-1.4.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0dc9db8e79f0036e8173c466d21ef18e1befc02de8bf8aa8dc0813a6dc8a7046"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cdc8a402aaee9a798b50d8b827d7ecf75edc5fb35ea0f91f213ff927c15f4ff0"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6c3bd3cde54cafb87d74d8db50b909705c62b17c2099b8f2e25b461882e544ff"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:955e8513d07a283056b1396e9a57ceddbd272d9252c14f154d450d227606eb54"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:346f5343b9e3f00b8db8ba359350eb124b98c99efd0b408728ac6ebf38173958"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9098e0049e88c6a24ff64545cdfc50807818ba6c1b739cae221bbbcbc58aad3"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:00bd361b903dc4bbf4eb165f24d1acbee754fce22ded24c3d56eec268658a5cf"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7b8b454bac16428b22560d0a1cf0a09875339cab69df61d7805bf48919415901"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f1d072c2eb0ad60d4c183f3fb44ac6f73fb7a8f16a2694a91f988275cbf352f9"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:31a82d498054cac9f6d0b53d02bb85811185bcb477d4b60144f915f3b3126342"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6512cb89e334e4700febbffaaa52761b65b4f5a3cf33f960213d5656cea36a77"}, - {file = "kiwisolver-1.4.5-cp39-cp39-win32.whl", hash = "sha256:9db8ea4c388fdb0f780fe91346fd438657ea602d58348753d9fb265ce1bca67f"}, - {file = "kiwisolver-1.4.5-cp39-cp39-win_amd64.whl", hash = "sha256:59415f46a37f7f2efeec758353dd2eae1b07640d8ca0f0c42548ec4125492635"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5c7b3b3a728dc6faf3fc372ef24f21d1e3cee2ac3e9596691d746e5a536de920"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:620ced262a86244e2be10a676b646f29c34537d0d9cc8eb26c08f53d98013390"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:378a214a1e3bbf5ac4a8708304318b4f890da88c9e6a07699c4ae7174c09a68d"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf7be1207676ac608a50cd08f102f6742dbfc70e8d60c4db1c6897f62f71523"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ba55dce0a9b8ff59495ddd050a0225d58bd0983d09f87cfe2b6aec4f2c1234e4"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fd32ea360bcbb92d28933fc05ed09bffcb1704ba3fc7942e81db0fd4f81a7892"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5e7139af55d1688f8b960ee9ad5adafc4ac17c1c473fe07133ac092310d76544"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dced8146011d2bc2e883f9bd68618b8247387f4bbec46d7392b3c3b032640126"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9bf3325c47b11b2e51bca0824ea217c7cd84491d8ac4eefd1e409705ef092bd"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5794cf59533bc3f1b1c821f7206a3617999db9fbefc345360aafe2e067514929"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e368f200bbc2e4f905b8e71eb38b3c04333bddaa6a2464a6355487b02bb7fb09"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5d706eba36b4c4d5bc6c6377bb6568098765e990cfc21ee16d13963fab7b3e7"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85267bd1aa8880a9c88a8cb71e18d3d64d2751a790e6ca6c27b8ccc724bcd5ad"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210ef2c3a1f03272649aff1ef992df2e724748918c4bc2d5a90352849eb40bea"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:11d011a7574eb3b82bcc9c1a1d35c1d7075677fdd15de527d91b46bd35e935ee"}, - {file = "kiwisolver-1.4.5.tar.gz", hash = "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec"}, -] - [[package]] name = "laspy" version = "2.5.3" @@ -763,56 +484,6 @@ files = [ {file = "lazrs-0.5.3-cp39-none-win_amd64.whl", hash = "sha256:6c9f38393ba382bb786bc0a8c4348f34a108113361af3bb7f55df76cb088b12b"}, ] -[[package]] -name = "matplotlib" -version = "3.8.2" -description = "Python plotting package" -category = "main" -optional = false -python-versions = ">=3.9" -files = [ - {file = "matplotlib-3.8.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:09796f89fb71a0c0e1e2f4bdaf63fb2cefc84446bb963ecdeb40dfee7dfa98c7"}, - {file = "matplotlib-3.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6f9c6976748a25e8b9be51ea028df49b8e561eed7809146da7a47dbecebab367"}, - {file = "matplotlib-3.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b78e4f2cedf303869b782071b55fdde5987fda3038e9d09e58c91cc261b5ad18"}, - {file = "matplotlib-3.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e208f46cf6576a7624195aa047cb344a7f802e113bb1a06cfd4bee431de5e31"}, - {file = "matplotlib-3.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:46a569130ff53798ea5f50afce7406e91fdc471ca1e0e26ba976a8c734c9427a"}, - {file = "matplotlib-3.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:830f00640c965c5b7f6bc32f0d4ce0c36dfe0379f7dd65b07a00c801713ec40a"}, - {file = "matplotlib-3.8.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d86593ccf546223eb75a39b44c32788e6f6440d13cfc4750c1c15d0fcb850b63"}, - {file = "matplotlib-3.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9a5430836811b7652991939012f43d2808a2db9b64ee240387e8c43e2e5578c8"}, - {file = "matplotlib-3.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9576723858a78751d5aacd2497b8aef29ffea6d1c95981505877f7ac28215c6"}, - {file = "matplotlib-3.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ba9cbd8ac6cf422f3102622b20f8552d601bf8837e49a3afed188d560152788"}, - {file = "matplotlib-3.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:03f9d160a29e0b65c0790bb07f4f45d6a181b1ac33eb1bb0dd225986450148f0"}, - {file = "matplotlib-3.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:3773002da767f0a9323ba1a9b9b5d00d6257dbd2a93107233167cfb581f64717"}, - {file = "matplotlib-3.8.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4c318c1e95e2f5926fba326f68177dee364aa791d6df022ceb91b8221bd0a627"}, - {file = "matplotlib-3.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:091275d18d942cf1ee9609c830a1bc36610607d8223b1b981c37d5c9fc3e46a4"}, - {file = "matplotlib-3.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b0f3b8ea0e99e233a4bcc44590f01604840d833c280ebb8fe5554fd3e6cfe8d"}, - {file = "matplotlib-3.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7b1704a530395aaf73912be741c04d181f82ca78084fbd80bc737be04848331"}, - {file = "matplotlib-3.8.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:533b0e3b0c6768eef8cbe4b583731ce25a91ab54a22f830db2b031e83cca9213"}, - {file = "matplotlib-3.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:0f4fc5d72b75e2c18e55eb32292659cf731d9d5b312a6eb036506304f4675630"}, - {file = "matplotlib-3.8.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:deaed9ad4da0b1aea77fe0aa0cebb9ef611c70b3177be936a95e5d01fa05094f"}, - {file = "matplotlib-3.8.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:172f4d0fbac3383d39164c6caafd3255ce6fa58f08fc392513a0b1d3b89c4f89"}, - {file = "matplotlib-3.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7d36c2209d9136cd8e02fab1c0ddc185ce79bc914c45054a9f514e44c787917"}, - {file = "matplotlib-3.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5864bdd7da445e4e5e011b199bb67168cdad10b501750367c496420f2ad00843"}, - {file = "matplotlib-3.8.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ef8345b48e95cee45ff25192ed1f4857273117917a4dcd48e3905619bcd9c9b8"}, - {file = "matplotlib-3.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:7c48d9e221b637c017232e3760ed30b4e8d5dfd081daf327e829bf2a72c731b4"}, - {file = "matplotlib-3.8.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:aa11b3c6928a1e496c1a79917d51d4cd5d04f8a2e75f21df4949eeefdf697f4b"}, - {file = "matplotlib-3.8.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1095fecf99eeb7384dabad4bf44b965f929a5f6079654b681193edf7169ec20"}, - {file = "matplotlib-3.8.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:bddfb1db89bfaa855912261c805bd0e10218923cc262b9159a49c29a7a1c1afa"}, - {file = "matplotlib-3.8.2.tar.gz", hash = "sha256:01a978b871b881ee76017152f1f1a0cbf6bd5f7b8ff8c96df0df1bd57d8755a1"}, -] - -[package.dependencies] -contourpy = ">=1.0.1" -cycler = ">=0.10" -fonttools = ">=4.22.0" -importlib-resources = {version = ">=3.2.0", markers = "python_version < \"3.10\""} -kiwisolver = ">=1.3.1" -numpy = ">=1.21,<2" -packaging = ">=20.0" -pillow = ">=8" -pyparsing = ">=2.3.1" -python-dateutil = ">=2.7" - [[package]] name = "mccabe" version = "0.7.0" @@ -1023,97 +694,11 @@ files = [ {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] -[[package]] -name = "pillow" -version = "10.2.0" -description = "Python Imaging Library (Fork)" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pillow-10.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:7823bdd049099efa16e4246bdf15e5a13dbb18a51b68fa06d6c1d4d8b99a796e"}, - {file = "pillow-10.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:83b2021f2ade7d1ed556bc50a399127d7fb245e725aa0113ebd05cfe88aaf588"}, - {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fad5ff2f13d69b7e74ce5b4ecd12cc0ec530fcee76356cac6742785ff71c452"}, - {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2b52b37dad6d9ec64e653637a096905b258d2fc2b984c41ae7d08b938a67e4"}, - {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:47c0995fc4e7f79b5cfcab1fc437ff2890b770440f7696a3ba065ee0fd496563"}, - {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:322bdf3c9b556e9ffb18f93462e5f749d3444ce081290352c6070d014c93feb2"}, - {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:51f1a1bffc50e2e9492e87d8e09a17c5eea8409cda8d3f277eb6edc82813c17c"}, - {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69ffdd6120a4737710a9eee73e1d2e37db89b620f702754b8f6e62594471dee0"}, - {file = "pillow-10.2.0-cp310-cp310-win32.whl", hash = "sha256:c6dafac9e0f2b3c78df97e79af707cdc5ef8e88208d686a4847bab8266870023"}, - {file = "pillow-10.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:aebb6044806f2e16ecc07b2a2637ee1ef67a11840a66752751714a0d924adf72"}, - {file = "pillow-10.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:7049e301399273a0136ff39b84c3678e314f2158f50f517bc50285fb5ec847ad"}, - {file = "pillow-10.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35bb52c37f256f662abdfa49d2dfa6ce5d93281d323a9af377a120e89a9eafb5"}, - {file = "pillow-10.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c23f307202661071d94b5e384e1e1dc7dfb972a28a2310e4ee16103e66ddb67"}, - {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:773efe0603db30c281521a7c0214cad7836c03b8ccff897beae9b47c0b657d61"}, - {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11fa2e5984b949b0dd6d7a94d967743d87c577ff0b83392f17cb3990d0d2fd6e"}, - {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:716d30ed977be8b37d3ef185fecb9e5a1d62d110dfbdcd1e2a122ab46fddb03f"}, - {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a086c2af425c5f62a65e12fbf385f7c9fcb8f107d0849dba5839461a129cf311"}, - {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c8de2789052ed501dd829e9cae8d3dcce7acb4777ea4a479c14521c942d395b1"}, - {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:609448742444d9290fd687940ac0b57fb35e6fd92bdb65386e08e99af60bf757"}, - {file = "pillow-10.2.0-cp311-cp311-win32.whl", hash = "sha256:823ef7a27cf86df6597fa0671066c1b596f69eba53efa3d1e1cb8b30f3533068"}, - {file = "pillow-10.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1da3b2703afd040cf65ec97efea81cfba59cdbed9c11d8efc5ab09df9509fc56"}, - {file = "pillow-10.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:edca80cbfb2b68d7b56930b84a0e45ae1694aeba0541f798e908a49d66b837f1"}, - {file = "pillow-10.2.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:1b5e1b74d1bd1b78bc3477528919414874748dd363e6272efd5abf7654e68bef"}, - {file = "pillow-10.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0eae2073305f451d8ecacb5474997c08569fb4eb4ac231ffa4ad7d342fdc25ac"}, - {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7c2286c23cd350b80d2fc9d424fc797575fb16f854b831d16fd47ceec078f2c"}, - {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e23412b5c41e58cec602f1135c57dfcf15482013ce6e5f093a86db69646a5aa"}, - {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:52a50aa3fb3acb9cf7213573ef55d31d6eca37f5709c69e6858fe3bc04a5c2a2"}, - {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:127cee571038f252a552760076407f9cff79761c3d436a12af6000cd182a9d04"}, - {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8d12251f02d69d8310b046e82572ed486685c38f02176bd08baf216746eb947f"}, - {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54f1852cd531aa981bc0965b7d609f5f6cc8ce8c41b1139f6ed6b3c54ab82bfb"}, - {file = "pillow-10.2.0-cp312-cp312-win32.whl", hash = "sha256:257d8788df5ca62c980314053197f4d46eefedf4e6175bc9412f14412ec4ea2f"}, - {file = "pillow-10.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:154e939c5f0053a383de4fd3d3da48d9427a7e985f58af8e94d0b3c9fcfcf4f9"}, - {file = "pillow-10.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:f379abd2f1e3dddb2b61bc67977a6b5a0a3f7485538bcc6f39ec76163891ee48"}, - {file = "pillow-10.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8373c6c251f7ef8bda6675dd6d2b3a0fcc31edf1201266b5cf608b62a37407f9"}, - {file = "pillow-10.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:870ea1ada0899fd0b79643990809323b389d4d1d46c192f97342eeb6ee0b8483"}, - {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4b6b1e20608493548b1f32bce8cca185bf0480983890403d3b8753e44077129"}, - {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3031709084b6e7852d00479fd1d310b07d0ba82765f973b543c8af5061cf990e"}, - {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:3ff074fc97dd4e80543a3e91f69d58889baf2002b6be64347ea8cf5533188213"}, - {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:cb4c38abeef13c61d6916f264d4845fab99d7b711be96c326b84df9e3e0ff62d"}, - {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b1b3020d90c2d8e1dae29cf3ce54f8094f7938460fb5ce8bc5c01450b01fbaf6"}, - {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:170aeb00224ab3dc54230c797f8404507240dd868cf52066f66a41b33169bdbe"}, - {file = "pillow-10.2.0-cp38-cp38-win32.whl", hash = "sha256:c4225f5220f46b2fde568c74fca27ae9771536c2e29d7c04f4fb62c83275ac4e"}, - {file = "pillow-10.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:0689b5a8c5288bc0504d9fcee48f61a6a586b9b98514d7d29b840143d6734f39"}, - {file = "pillow-10.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b792a349405fbc0163190fde0dc7b3fef3c9268292586cf5645598b48e63dc67"}, - {file = "pillow-10.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c570f24be1e468e3f0ce7ef56a89a60f0e05b30a3669a459e419c6eac2c35364"}, - {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8ecd059fdaf60c1963c58ceb8997b32e9dc1b911f5da5307aab614f1ce5c2fb"}, - {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c365fd1703040de1ec284b176d6af5abe21b427cb3a5ff68e0759e1e313a5e7e"}, - {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:70c61d4c475835a19b3a5aa42492409878bbca7438554a1f89d20d58a7c75c01"}, - {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b6f491cdf80ae540738859d9766783e3b3c8e5bd37f5dfa0b76abdecc5081f13"}, - {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d189550615b4948f45252d7f005e53c2040cea1af5b60d6f79491a6e147eef7"}, - {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:49d9ba1ed0ef3e061088cd1e7538a0759aab559e2e0a80a36f9fd9d8c0c21591"}, - {file = "pillow-10.2.0-cp39-cp39-win32.whl", hash = "sha256:babf5acfede515f176833ed6028754cbcd0d206f7f614ea3447d67c33be12516"}, - {file = "pillow-10.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:0304004f8067386b477d20a518b50f3fa658a28d44e4116970abfcd94fac34a8"}, - {file = "pillow-10.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:0fb3e7fc88a14eacd303e90481ad983fd5b69c761e9e6ef94c983f91025da869"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:322209c642aabdd6207517e9739c704dc9f9db943015535783239022002f054a"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3eedd52442c0a5ff4f887fab0c1c0bb164d8635b32c894bc1faf4c618dd89df2"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb28c753fd5eb3dd859b4ee95de66cc62af91bcff5db5f2571d32a520baf1f04"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:33870dc4653c5017bf4c8873e5488d8f8d5f8935e2f1fb9a2208c47cdd66efd2"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3c31822339516fb3c82d03f30e22b1d038da87ef27b6a78c9549888f8ceda39a"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a2b56ba36e05f973d450582fb015594aaa78834fefe8dfb8fcd79b93e64ba4c6"}, - {file = "pillow-10.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d8e6aeb9201e655354b3ad049cb77d19813ad4ece0df1249d3c793de3774f8c7"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:2247178effb34a77c11c0e8ac355c7a741ceca0a732b27bf11e747bbc950722f"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15587643b9e5eb26c48e49a7b33659790d28f190fc514a322d55da2fb5c2950e"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753cd8f2086b2b80180d9b3010dd4ed147efc167c90d3bf593fe2af21265e5a5"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7c8f97e8e7a9009bcacbe3766a36175056c12f9a44e6e6f2d5caad06dcfbf03b"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d1b35bcd6c5543b9cb547dee3150c93008f8dd0f1fef78fc0cd2b141c5baf58a"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe4c15f6c9285dc54ce6553a3ce908ed37c8f3825b5a51a15c91442bb955b868"}, - {file = "pillow-10.2.0.tar.gz", hash = "sha256:e87f0b2c78157e12d7686b27d63c070fd65d994e8ddae6f328e0dcf4a0cd007e"}, -] - -[package.extras] -docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] -fpx = ["olefile"] -mic = ["olefile"] -tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] -typing = ["typing-extensions"] -xmp = ["defusedxml"] - [[package]] name = "platformdirs" version = "4.1.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "main" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1142,26 +727,79 @@ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] [[package]] -name = "pooch" -version = "1.8.0" -description = "\"Pooch manages your Python library's sample data files: it automatically downloads and stores them in a local directory, with support for versioning and corruption checks.\"" +name = "polyscope" +version = "2.1.0" +description = "Polyscope: A viewer and user interface for 3D data." category = "main" optional = false -python-versions = ">=3.7" +python-versions = "*" files = [ - {file = "pooch-1.8.0-py3-none-any.whl", hash = "sha256:1bfba436d9e2ad5199ccad3583cca8c241b8736b5bb23fe67c213d52650dbb66"}, - {file = "pooch-1.8.0.tar.gz", hash = "sha256:f59981fd5b9b5d032dcde8f4a11eaa492c2ac6343fae3596a2fdae35fc54b0a0"}, + {file = "polyscope-2.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:446a644b1d30c7b4fa22a2d815c2ee844e04a5d929179b24905d2b55457a9eaa"}, + {file = "polyscope-2.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:420320279a5d94b5549354ab7f7afa2e3edb6d7e4c0a7da27a5defdb88d528f6"}, + {file = "polyscope-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e2e1c5d867b037ddf40cf6f59b3219507e50e3030d1750105ad10b5bd744e395"}, + {file = "polyscope-2.1.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:558629bb19aac09a01d84fb3e8ba073b8289f68539c734effcd089fa0823c1ed"}, + {file = "polyscope-2.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00801e02df2355b787aae05f4e40bc5c394be67869fc10f1209e76da652028cf"}, + {file = "polyscope-2.1.0-cp310-cp310-win32.whl", hash = "sha256:23a72d28b8eeb965c4a2665991fe89c843d0502056134322ba1bcf1fab120e85"}, + {file = "polyscope-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0ada82a3c57eeccca0f6a81ae27793a5a928bd015ae1ee5002caef906157e579"}, + {file = "polyscope-2.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0631c7bb718bbdb366103a0d783ae592887ef2b6f10cc745603622b206ad8eab"}, + {file = "polyscope-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d2adbc2d25fbbf668dcbc16a9484c793d46e9070e777af7edd3d10210f3e51aa"}, + {file = "polyscope-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:79fae4df9ad861f4998c49ac6f782a007d5b600d10295696a3bb3892fb127c20"}, + {file = "polyscope-2.1.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47233b657fe1ccb57bfbdf1eeec616ddf86219224f7dd93407178cc7fce69296"}, + {file = "polyscope-2.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b501b8dcb353f87c1c89fd3cb727580ada75b7cae359fc1acdd709204c17d28b"}, + {file = "polyscope-2.1.0-cp311-cp311-win32.whl", hash = "sha256:f51d37376e202cb138f412714d108dd6135650e313e68a046f86979347cd170e"}, + {file = "polyscope-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:ae99d67c1d491a8de3c37a492a53d0ca9648f637528a5ecd419c88ff2fe2d3d9"}, + {file = "polyscope-2.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0fccf2e911347b4f710ae71749e5545d0be63cba97be0bf29ea5423d663c1440"}, + {file = "polyscope-2.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9c1d6c756b2b17c9ff8f818e27f754af8e01fb5295263ed20682eb998f6c3bf2"}, + {file = "polyscope-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5d03813f9721325c294f33b8dcaf86787287c1b77e0fce5c56b9fe368df7abaf"}, + {file = "polyscope-2.1.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c98197295dc775d9df0ac87abde06a0bb196ae6e7a43f8356568c4ddf0620b33"}, + {file = "polyscope-2.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0502e90b4731b02bdf61011e30a64a1552c7afc2996b60ac350434337c8b2616"}, + {file = "polyscope-2.1.0-cp312-cp312-win32.whl", hash = "sha256:7e1a4734f3d648fd24a0e69dc62b3c658c6fd9e330bbe7d03fefe824403c6f1d"}, + {file = "polyscope-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:c1f185dc2f00301c2c27864cf99f58f2100dcca29b48c85dbe9e4f2e482baf3f"}, + {file = "polyscope-2.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:dd74ba21bccb706aeac5f6c2d2a89b003d6edb92dd3c875472d1b69c83c36f41"}, + {file = "polyscope-2.1.0-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76db41946bead005218e4a0613fbcc424be2c509ffb037df88b14ae8245df95e"}, + {file = "polyscope-2.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63370c28f8b31ab5201af00cb9cb2470c0239f3e0a5edacacf601c2f1b609154"}, + {file = "polyscope-2.1.0-cp36-cp36m-win32.whl", hash = "sha256:dff85ab21a04b099938e78c17fd2eee0ecbf7f8da96f3d419f6f81c3bdf312e0"}, + {file = "polyscope-2.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:9b9170c4bb00776c0426580674287cf90c64003f96988da8c9a8edd1a16b5b70"}, + {file = "polyscope-2.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:87c4064679c0d487df557d93a83d8806a270c933834f09c25498a8b1cb9d06c6"}, + {file = "polyscope-2.1.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6ba85dad8676701795d02c65e9b34364c4dcf4b20b8ba19d908a28bfb2fed02"}, + {file = "polyscope-2.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:110adddca2e02d8f1dab104045746a2b7a4ccedde5623f8259a5e785fe9f457e"}, + {file = "polyscope-2.1.0-cp37-cp37m-win32.whl", hash = "sha256:95d5622bf0328d3fea15a64e2089460bdf7aa2f4f204ebe886836a467126c745"}, + {file = "polyscope-2.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:45e405eb3239fec151f9e4deb28d84125a13212cd00ed9b76af3b50df6ffc017"}, + {file = "polyscope-2.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6ccf00a05cd6c6e3945b028de64b6d4765602968ecfe08118fcacbba7a2d6ecd"}, + {file = "polyscope-2.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54bd26f37d1dabdb10e2e17190ca8b4cc7c6ef69b92d216479d75ed68339811b"}, + {file = "polyscope-2.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a547b372a78f39b1df630d1f7432f3f0c5c34b020ca8a1dd112b6e6cf2d4f5f0"}, + {file = "polyscope-2.1.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7bbed8d7b69a65ef49d6b86bbcb3fb03adc19ba9228fed6d9d0a621ab6fbcc14"}, + {file = "polyscope-2.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa81b8c640c8bb870e6ac8e7b7fe3fd42d676f2f6062602b6843df93467c4a72"}, + {file = "polyscope-2.1.0-cp38-cp38-win32.whl", hash = "sha256:ca9c934833e5233cc6a45f2206ba9ebe83174f8d3579156d418b611ceb52d22f"}, + {file = "polyscope-2.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:f87b9f4e8e377e5f48d74b46a66fbc0b0dd83872eaa70f6d122bda41b104f8cf"}, + {file = "polyscope-2.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ccb4d6c79aceccbf74bf7c0f983d14913558b3758466d2f4f8dcc40dfd56f75a"}, + {file = "polyscope-2.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54fa277f1114f820d2f34fe511a5f5818b12172f801504e469c7f86848685818"}, + {file = "polyscope-2.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87c28a0076a52f7e412b997a82cf226e7c9cabeea300c96b52a4a84391236eec"}, + {file = "polyscope-2.1.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0aac08c5b62a2af9ffc79af54ee0396c6ef190ce4f890c161c4c3dff013eef39"}, + {file = "polyscope-2.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27eacc7c43eca6a8b698d448900e58eb592c6d833bb8c4cd7478f88a6c32fee9"}, + {file = "polyscope-2.1.0-cp39-cp39-win32.whl", hash = "sha256:f8242a76e64c64e4b0f07fa24691a002887b23c6a739f4731aabec116c035a20"}, + {file = "polyscope-2.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:3bd6cd6f4a6aef0981e72b1870faf49bb8f4eb82814f4410f6246312cc8d332d"}, + {file = "polyscope-2.1.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d6b49a84be257af3ef6199011b7571cb064558db394be6cdeecfdaa722a1f076"}, + {file = "polyscope-2.1.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c8d8330d4aa1643bc921856b787692e183a7e699f05cb844e73fe254e642fbbe"}, + {file = "polyscope-2.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6214e1909ba9e81d94c469d753bb4ef4aaa26ce89f8f92919cdd57d233661f03"}, + {file = "polyscope-2.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:90819db107739531c73141295b8528b1a34b8573e9e3ad65919ae212eab797ef"}, + {file = "polyscope-2.1.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:460da7bbbec90fd9aa72d93c26543d68a985f1eccdc14cfd72a7a4539ef5df90"}, + {file = "polyscope-2.1.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90323111a77b86d1ba94790ca13cd5c2ae1e08bfbc5c36ba1fb3419ae51eb28d"}, + {file = "polyscope-2.1.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:314b2ce2171c9e597958d3f259cadd8002c2210a440732bdb60e1186f54310b6"}, + {file = "polyscope-2.1.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:9c5188ce9046e07fd4c905f135d117388d09e99fcb9990d87b71d94bcf1217e6"}, + {file = "polyscope-2.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:695b1a30ef247cc63564e342a6c485bda6c83f754b751001520fc8d542c6a64f"}, + {file = "polyscope-2.1.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf12f296d181a7f730405e52921dc192cf9f871c6f5b2711d46e6e19d7b18b13"}, + {file = "polyscope-2.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a0625d43921820e6eb83ca42e0d38d8612b50495ef4c0727dc2af6cd6250a8d"}, + {file = "polyscope-2.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:22ae5e88057edfbfcf4541c4705510bc5ecc841ef2ad8156224b4b152f6b0494"}, + {file = "polyscope-2.1.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:dbef952f1bfab76b17bec4d3a4c9fe12d0f1a76e6788818957bd6083cfbb2221"}, + {file = "polyscope-2.1.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f960a75bcc4583280faa18757f4b36e8dec0fd179077318eb2e1a33b8064603"}, + {file = "polyscope-2.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23f13a3fe274585b238a8ad2586ac74f64f50dc54f740c4857a47e720dcc6f01"}, + {file = "polyscope-2.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:37b7e3897b1347f0878fe131529c6223b4fec9a4fbd84b329124075c3cd838f9"}, + {file = "polyscope-2.1.0.tar.gz", hash = "sha256:4ed6d92b44a11d2146cd67248343c314d9fe2792c316273643b1194b0d86f9f5"}, ] [package.dependencies] -packaging = ">=20.0" -platformdirs = ">=2.5.0" -requests = ">=2.19.0" - -[package.extras] -progress = ["tqdm (>=4.41.0,<5.0.0)"] -sftp = ["paramiko (>=2.7.0)"] -xxhash = ["xxhash (>=1.4.3)"] +numpy = "*" [[package]] name = "pycodestyle" @@ -1292,32 +930,6 @@ files = [ {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, ] -[[package]] -name = "pyvista" -version = "0.43.1" -description = "Easier Pythonic interface to VTK" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyvista-0.43.1-py3-none-any.whl", hash = "sha256:e7e7597c3938ad7e695a1de724ba4d5fcb05cdfa4ac81bb9bee7e75659c25f8a"}, - {file = "pyvista-0.43.1.tar.gz", hash = "sha256:dd2c484d85da2c677a4fd01e801cd8f49a311be602088b0e43cfe9746db0c552"}, -] - -[package.dependencies] -matplotlib = ">=3.0.1" -numpy = ">=1.21.0" -pillow = "*" -pooch = "*" -scooby = ">=0.5.1" -vtk = "*" - -[package.extras] -all = ["pyvista[colormaps,io,jupyter]"] -colormaps = ["cmocean", "colorcet"] -io = ["imageio", "meshio (>=5.2)"] -jupyter = ["ipywidgets", "jupyter-server-proxy", "nest-asyncio", "trame (>=2.5.2)", "trame-client (>=2.12.7)", "trame-server (>=2.11.7)", "trame-vtk (>=2.5.8)", "trame-vuetify (>=2.3.1)"] - [[package]] name = "rasterio" version = "1.3.9" @@ -1390,21 +1002,6 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] -[[package]] -name = "scooby" -version = "0.9.2" -description = "A Great Dane turned Python environment detective" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "scooby-0.9.2-py3-none-any.whl", hash = "sha256:3cbc59de9febf8c8ba1e01bc7d08b4eca18ece3212d38b08a6f45188f88c8ea9"}, - {file = "scooby-0.9.2.tar.gz", hash = "sha256:28df643bb7c2087547b2e2220070e2f89e815ddbc515fbc28dd5df2b0a14293e"}, -] - -[package.extras] -cpu = ["mkl", "psutil"] - [[package]] name = "setuptools" version = "69.0.2" @@ -1523,6 +1120,27 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] +[[package]] +name = "tqdm" +version = "4.66.2" +description = "Fast, Extensible Progress Meter" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tqdm-4.66.2-py3-none-any.whl", hash = "sha256:1ee4f8a893eb9bef51c6e35730cebf234d5d0b6bd112b0271e10ed7c24a02bd9"}, + {file = "tqdm-4.66.2.tar.gz", hash = "sha256:6cd52cdf0fef0e0f543299cfc96fec90d7b8a7e88745f411ec33eb44d5ed3531"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] + [[package]] name = "types-requests" version = "2.31.0.20240125" @@ -1579,48 +1197,6 @@ brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] -[[package]] -name = "vtk" -version = "9.3.0" -description = "VTK is an open-source toolkit for 3D computer graphics, image processing, and visualization" -category = "main" -optional = false -python-versions = "*" -files = [ - {file = "vtk-9.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:7d3492cb6c52b23dc0c6e664938b8119254a77b5e3099106e2567ed0b6473162"}, - {file = "vtk-9.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3f4e86bff7a4cd71bd6205bd18cf4b6ab70956ecf9cbd73e77a95b2210d98ef"}, - {file = "vtk-9.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a827fb5f05ab78b2cbad81f5d3a3d7065fa995cc907cecdfa7a7b76374130ef3"}, - {file = "vtk-9.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:2aae778297817335ddd6698b4c124c109d8ac476512691fe19446614ae43ba56"}, - {file = "vtk-9.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:a3cd59108b21f55b873a63878a0decec0a707bd960b59d5e15b37d1ad873590f"}, - {file = "vtk-9.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6d2bdd2c60f0fa5d1926c11b72d96dc23caf9ff41781bae76e48edd09fb8aa03"}, - {file = "vtk-9.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a02bf6067cae7abfd7f6b1330c69555b715be8ec71a3c8d6471af45a96e8e56"}, - {file = "vtk-9.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:ff0eedcde5821c023623f70951f2499e9d59e709e288b67a2e2334abafacc322"}, - {file = "vtk-9.3.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:94678fa0476e113500f3b99e9692b92b83a5b058caace7bac3b5f780b12b36ed"}, - {file = "vtk-9.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:371b96afca3ed41a0bf1cd80a42f4b906ca2f470a13df32f39b22a9169d996d7"}, - {file = "vtk-9.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cfa8d73acbab386b9d6ef8a1a01149fd096a21a23547f10bf0cf98d88300724"}, - {file = "vtk-9.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:75d27b73270a42923ebefd87a8522f7717618c36825b8058c4d3aa8e64d6145d"}, - {file = "vtk-9.3.0-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:6b4a6f9d4ae16a417edf3cd750da5cb87e9676d1db1da6a6772a9e492567a452"}, - {file = "vtk-9.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fbd3536979c177dd12f9365a1072e217d64503596add6986318d466aab565d51"}, - {file = "vtk-9.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:296f185482df591d7b2c2a734f3a68884352efd89cade37f3345ddc4dcb6e019"}, - {file = "vtk-9.3.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:9d5c837a4d865ec80752d8ca8ee719be8341af66601df0da94ee78ae0806bb4b"}, - {file = "vtk-9.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cdfb7e51a63ee2f06b1aa84e643f046b746116397a89cb50d20956731e88209"}, - {file = "vtk-9.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2902c8601ada0e653a4d34ebca0f17768fb559f05fe9f4502dcdda136d847a1e"}, - {file = "vtk-9.3.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:f0798c2ae607be930656347491c520945984ab657ab00804d159323962e97102"}, - {file = "vtk-9.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d92c9a70902da512dfbcd3f064f825b7b5b6d62edd197d3754549f7c0ff516"}, - {file = "vtk-9.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:17810f82aeee7143057fcb2d963245f57450800a7b913c5d66ed915f09740d3d"}, - {file = "vtk-9.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:7a564f31dbe514276abffb1d3204120ead15506a24ecaa2560009ba304896dae"}, - {file = "vtk-9.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3b22a0d03305160d6612da0a378759083ef7691d0f83f1b1496418777ee3a2a3"}, - {file = "vtk-9.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdd81c9904647ace8d13ad255d8e5293fb81be8125e1a139a707aaf9e6f0e9e2"}, - {file = "vtk-9.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:f0a44c926ba18fd9e2ad7c07ae0adabb4ca62af28c69c96bcbaa884e0b240249"}, -] - -[package.dependencies] -matplotlib = ">=2.0.0" - -[package.extras] -numpy = ["numpy (>=1.9)"] -web = ["wslink (>=1.0.4)"] - [[package]] name = "zipp" version = "3.17.0" @@ -1640,4 +1216,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.9, <3.12" -content-hash = "3e02d350ce90c3b3f1e143f321ef08dfa5446f74666af206ceae7ba49806f6d2" +content-hash = "c0c5e35f6be5311eed85c91ada4433cb03df707696286e8e928ac5230cfc46b0" diff --git a/pyproject.toml b/pyproject.toml index b431fc2..5b094c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,9 +23,10 @@ click = "^8.1.7" geopandas = "^0.14.1" shapely = "^2.0.2" laspy = {extras = ["lazrs"], version = "^2.5.3"} -pyvista = "^0.43.1" requests = "^2.31.0" rasterio = "^1.3.9" +tqdm = "^4.66.2" +polyscope = "^2.1.0" [tool.poetry.group.dev.dependencies] diff --git a/tests/test_pipeline.py b/tests/test_pipeline.py index c25924d..1ec7f82 100644 --- a/tests/test_pipeline.py +++ b/tests/test_pipeline.py @@ -10,6 +10,7 @@ TEST_DATA1 = "./tests/testdata/westervoort1_thinned.las" CITY_FILE_PATH = "./ahn_cli/fetcher/data/municipality_simple.geojson" WESTERVOORT_FILE_PATH = "./tests/testdata/westervoort.geojson" +WESTERVOORT28992_FILE_PATH = "./tests/testdata/westervoort28992.geojson" class TestPipeline(unittest.TestCase): @@ -76,6 +77,14 @@ def test_clip_by_arbitrary_polygon(self) -> None: points_after = len(pipeline.las.points) self.assertTrue(points_after < points_before) + with laspy.open(TEST_DATA1) as reader: + las = reader.read() + pipeline = PntCPipeline(las, CITY_FILE_PATH, "Westervoort", 28992) + points_before = len(pipeline.las.points) + pipeline.clip_by_arbitrary_polygon(WESTERVOORT28992_FILE_PATH) + points_after = len(pipeline.las.points) + self.assertTrue(points_after < points_before) + def test_clip_by_bbox(self) -> None: with laspy.open(TEST_DATA0) as reader: las = reader.read() diff --git a/tests/testdata/westervoort28992.geojson b/tests/testdata/westervoort28992.geojson new file mode 100644 index 0000000..dab0c32 --- /dev/null +++ b/tests/testdata/westervoort28992.geojson @@ -0,0 +1,8 @@ +{ +"type": "FeatureCollection", +"name": "westervoort28992", +"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:EPSG::28992" } }, +"features": [ +{ "type": "Feature", "properties": { "id": "GM0293", "name": "Westervoort", "code": "0293", "provinceCode": "25", "provinceName": "Gelderland", "fuuid": "gemeentegebied.d496c941-7ab5-4602-a485-bdec57e61799", "area": 0.0010243601228694388, "perimeter": 0.17031820071340625 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 196065.120155059790704, 439986.45043137925677 ], [ 196274.110155246948125, 440392.53043163131224 ], [ 196084.590155071753543, 440529.070431717264 ], [ 196083.780155070446199, 440579.580431752605364 ], [ 196091.480155075347284, 440767.620431869698223 ], [ 196181.181155153550208, 441130.743432094110176 ], [ 196233.00215519691119, 441496.688432326947805 ], [ 196212.66015517609776, 441707.917432455113158 ], [ 196261.241155219904613, 441760.866432491340674 ], [ 196275.576155232673045, 441796.907432509935461 ], [ 196102.771155073394766, 441895.204432574973907 ], [ 196192.898155150178354, 442401.137432890885975 ], [ 196023.963154991681222, 442753.69643311429536 ], [ 196008.178154976398218, 442833.477433161868248 ], [ 196185.59415513894055, 442834.491433162067551 ], [ 196299.966155243077083, 442880.341433190507814 ], [ 196399.644155334535753, 442854.125433174602222 ], [ 196439.582155371288536, 442824.33943315257784 ], [ 196714.787155622063437, 442947.67843322706176 ], [ 196948.104155833891127, 443081.687433308688924 ], [ 197092.98915596518782, 443209.025433386384975 ], [ 197208.494156069122255, 443363.70143348473357 ], [ 197226.275156084331684, 443444.272433531703427 ], [ 197305.613156154926401, 443631.520433648431208 ], [ 197319.231156166410074, 443715.061433703638613 ], [ 197298.224156146781752, 443740.754433719266672 ], [ 197234.832156088756165, 443761.675433731172234 ], [ 197186.385156045900658, 443636.537433655757923 ], [ 197122.537155988626182, 443516.5174335782649 ], [ 196961.734155843500048, 443340.500433467852417 ], [ 196763.095155663177138, 443209.978433392301667 ], [ 196482.626155407866463, 443074.393433309625834 ], [ 196265.562155209976481, 443021.560433283273596 ], [ 195732.742154722975101, 442959.291433242848143 ], [ 195311.804154338838998, 442851.44043318007607 ], [ 194937.395153996680165, 442789.554433150100522 ], [ 194678.320153760229005, 442714.194433105178177 ], [ 194524.439153620303841, 442634.205433050985448 ], [ 194289.228153406555066, 442463.890432949585374 ], [ 194178.14115330617642, 442359.175432886520866 ], [ 193881.369153037841897, 441999.113432664016727 ], [ 193765.267152933869511, 441794.668432536942419 ], [ 193714.41015288856579, 441663.694432456337381 ], [ 193670.94715285039274, 441505.197432361543179 ], [ 193641.710152825515252, 441324.152432241011411 ], [ 193633.224152819573646, 441151.381432135473005 ], [ 193654.118152840790572, 440952.811432008224074 ], [ 193695.124152879870962, 440793.314431905106176 ], [ 193780.520152960030828, 440597.552431788993999 ], [ 193979.012153145216871, 440285.803431588108651 ], [ 194213.110153362795245, 439994.215431399294175 ], [ 194325.194153467513388, 439814.980431287083775 ], [ 194404.34615354181733, 439636.607431174721569 ], [ 194455.043153590027941, 439467.03243106789887 ], [ 194511.927153646247461, 439082.251430824457202 ], [ 194890.413153993227752, 439078.670430820435286 ], [ 195038.983154128247406, 439175.895430880249478 ], [ 195042.745154132368043, 439123.940430843620561 ], [ 195061.156154149153735, 439126.892430849315133 ], [ 195215.724154289753642, 439226.65943090710789 ], [ 195353.760154414747376, 439363.348430993035436 ], [ 195433.952154487604275, 439415.622431027004495 ], [ 195682.130154714221135, 439504.590431083459407 ], [ 195781.250154804321937, 439560.660431112744845 ], [ 196060.040155059832614, 439565.180431113753002 ], [ 196085.950155082566198, 439656.770431169075891 ], [ 196166.670155155094108, 439798.530431260645855 ], [ 196181.030155166459735, 439938.120431349554565 ], [ 196065.120155059790704, 439986.45043137925677 ] ] ] } } +] +} From cc118729bae2db80f578db53401775861424a3b3 Mon Sep 17 00:00:00 2001 From: HideBa Date: Fri, 23 Feb 2024 14:44:56 +0100 Subject: [PATCH 08/15] ci: change ci --- .github/workflows/ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 91cea80..3a55236 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,8 +8,6 @@ on: - "requirements.txt" - "requirements.dev.txt" pull_request: - branches: - - main jobs: ci: From fc7d765e4200f1d1b5d6b583e274007a8f327996 Mon Sep 17 00:00:00 2001 From: HideBa Date: Fri, 23 Feb 2024 14:49:17 +0100 Subject: [PATCH 09/15] ci: fix --- .github/workflows/ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3a55236..dc241fe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,8 +2,6 @@ name: CI on: push: - branches: - - main paths-ignore: - "requirements.txt" - "requirements.dev.txt" From 34a0fcde3fe9b285f5231814d8f4a6623e731d7d Mon Sep 17 00:00:00 2001 From: HideBa Date: Fri, 23 Feb 2024 14:56:16 +0100 Subject: [PATCH 10/15] chore: add change log --- CHANGELOG.md | 10 ++++++++-- ahn_cli/manipulator/preview.py | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df23720..5ca2dcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog -## [0.0.0] - 2024-02-01 +## [0.1.0] - 2024-02-23 ### Changed -### Added \ No newline at end of file +### Added +This is the first release of AHN CLI. There are a couple of features which helps users to easily download AHN point cloud data they need. +* Validation of user input +* Multi-thread download to speed up downloading time +* Rasterization of city polygon to reduce time complexity +* Filter points out by parameters such as classification classes, decimate, bounding box, etc +* Preview of downloaded data \ No newline at end of file diff --git a/ahn_cli/manipulator/preview.py b/ahn_cli/manipulator/preview.py index 87a7c8c..43c35c1 100644 --- a/ahn_cli/manipulator/preview.py +++ b/ahn_cli/manipulator/preview.py @@ -8,6 +8,6 @@ def previewer(filepath: str) -> None: points = las.xyz ps.init() - _ps_cloud = ps.register_point_cloud("AHN Point Cloud", points) + ps.register_point_cloud("AHN Point Cloud", points) ps.show() From c1ddd36b0b672b861817bb774715a3f826d1dca8 Mon Sep 17 00:00:00 2001 From: HideBa Date: Fri, 23 Feb 2024 14:56:41 +0100 Subject: [PATCH 11/15] chore: sort --- ahn_cli/fetcher/request.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ahn_cli/fetcher/request.py b/ahn_cli/fetcher/request.py index 078d27c..ca3389c 100644 --- a/ahn_cli/fetcher/request.py +++ b/ahn_cli/fetcher/request.py @@ -5,9 +5,9 @@ from concurrent.futures import ThreadPoolExecutor from threading import Lock from urllib.parse import urlparse -from tqdm import tqdm import requests +from tqdm import tqdm from ahn_cli.fetcher.geotiles import ahn_subunit_indicies_of_city From 5b84a7a12d89eff1e0c18117aec58ce9491c7aab Mon Sep 17 00:00:00 2001 From: HideBa Date: Fri, 23 Feb 2024 14:59:50 +0100 Subject: [PATCH 12/15] chore: fix ci --- .github/workflows/ci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc241fe..2791638 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,6 +2,8 @@ name: CI on: push: + branches: + - main paths-ignore: - "requirements.txt" - "requirements.dev.txt" @@ -40,11 +42,11 @@ jobs: - name: Linting run: | - poetry run pylint ./ahn_cli/**/*.py + make lint - name: Type checking run: | - poetry run mypy ./ahn_cli/**/*.py + make type - name: Testing run: poetry run pytest ./ahn_cli/**/*.py --cov=./ahn_cli From 6dea9160789437128a046d855b69fb9571321137 Mon Sep 17 00:00:00 2001 From: HideBa Date: Fri, 23 Feb 2024 15:01:26 +0100 Subject: [PATCH 13/15] ci: fix --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2791638..4435f70 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,7 +49,7 @@ jobs: make type - name: Testing - run: poetry run pytest ./ahn_cli/**/*.py --cov=./ahn_cli + run: poetry run pytest ./ahn_cli/**/*.py - name: Export requirements run: | From 66800840794a827957aedf6a079574949aaca2d2 Mon Sep 17 00:00:00 2001 From: HideBa Date: Fri, 23 Feb 2024 15:20:03 +0100 Subject: [PATCH 14/15] ci: fix --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4435f70..0b16528 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,7 +49,7 @@ jobs: make type - name: Testing - run: poetry run pytest ./ahn_cli/**/*.py + run: make test - name: Export requirements run: | From 4893ca6a741f3e19a700a20cbda8141e9da2fa5b Mon Sep 17 00:00:00 2001 From: HideBa Date: Fri, 23 Feb 2024 15:26:11 +0100 Subject: [PATCH 15/15] ci: fix --- .github/workflows/ci.yml | 22 +++++++++++----------- CHANGELOG.md | 2 +- ahn_cli/main.py | 2 +- pyproject.toml | 4 ++-- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b16528..53ce4f6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,15 +51,15 @@ jobs: - name: Testing run: make test - - name: Export requirements - run: | - poetry export --dev --format requirements.txt --output requirements.txt - poetry export --dev --format requirements.txt --output requirements.dev.txt + # - name: Export requirements + # run: | + # poetry export --dev --format requirements.txt --output requirements.txt + # poetry export --dev --format requirements.txt --output requirements.dev.txt - - name: Commit exported requirements - run: | - git config --global user.name "${{ secrets.GH_USER_NAME }}" - git config --global user.email "${{ secrets.GH_USER_EMAIL }}" - git add requirements.txt requirements.dev.txt - git commit -m "Update requirements" - git push origin main + # - name: Commit exported requirements + # run: | + # git config --global user.name "${{ secrets.GH_USER_NAME }}" + # git config --global user.email "${{ secrets.GH_USER_EMAIL }}" + # git add requirements.txt requirements.dev.txt + # git commit -m "Update requirements" + # git push origin main diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ca2dcf..c833f4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ # Changelog -## [0.1.0] - 2024-02-23 +## [0.1.6] - 2024-02-23 ### Changed ### Added diff --git a/ahn_cli/main.py b/ahn_cli/main.py index 0ac54b1..b9506fb 100644 --- a/ahn_cli/main.py +++ b/ahn_cli/main.py @@ -29,7 +29,7 @@ @click.command() -@click.version_option(version="0.1.5", prog_name="ahn_cli") +@click.version_option(version="0.1.6", prog_name="ahn_cli") @click.option( "-o", "--output", diff --git a/pyproject.toml b/pyproject.toml index 5b094c3..68a2ce3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ahn_cli" -version = "0.1.5" +version = "0.1.6" description = "" authors = ["HideBa "] license = "MIT" @@ -22,7 +22,7 @@ python = ">=3.9, <3.12" click = "^8.1.7" geopandas = "^0.14.1" shapely = "^2.0.2" -laspy = {extras = ["lazrs"], version = "^2.5.3"} +laspy = { extras = ["lazrs"], version = "^2.5.3" } requests = "^2.31.0" rasterio = "^1.3.9" tqdm = "^4.66.2"