ign-pdal-tools 1.7.4__py3-none-any.whl → 1.7.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ign-pdal-tools
3
- Version: 1.7.4
3
+ Version: 1.7.5
4
4
  Summary: Library for common LAS files manipulation with PDAL
5
5
  Author-email: Guillaume Liegard <guillaume.liegard@ign.fr>
6
6
  Description-Content-Type: text/markdown
@@ -87,6 +87,11 @@ By default, `xcoord` and `ycoord` are given in kilometers and the shape of the t
87
87
  `readers.las: Global encoding WKT flag not set for point format 6 - 10.` which is due to TerraSolid
88
88
  malformed LAS output for LAS1.4 files with point format 6 to 10.
89
89
 
90
+ ## Add points in Las
91
+
92
+ [add_points_in_las.py](pdaltools/add_points_in_las.py): add points from some vector files (ex: shp, geojson, ...) inside Las. New points will have X,Y and Z coordinates. Other attributes values given by the initial las file are null (ex: classification at 0). These others attributes could be forced by using the '--dimensions/-d' option in the command line (ex : 'add_points_in_las.py -i myLas.las -g myPoints.json -d classification=64' - points will have their classification set to 64). The dimension should be present in the initial las ; this is not allowed to add new dimension.
93
+
94
+
90
95
  # Dev / Build
91
96
 
92
97
  ## Contribute
@@ -0,0 +1,17 @@
1
+ pdaltools/_version.py,sha256=Jr3d2rnNzq-ZLSjZiEMdSSozTrU6AhKJLKOvXK3-0H0,74
2
+ pdaltools/add_points_in_las.py,sha256=TGbt5JUkszjmbQiA2LCUntsjz6A8DHb7QPIXGDuEgWA,3643
3
+ pdaltools/color.py,sha256=PSdtMMdsapOtgzojdnaKVx6IxbKOaN2xP9mScAbCGm0,8629
4
+ pdaltools/las_add_buffer.py,sha256=sBpTywlfsHHS8KuCUa-eydB2hylshEvjrMQt5TrqXb8,11275
5
+ pdaltools/las_clip.py,sha256=GvEOYu8RXV68e35kU8i42GwSkbo4P9TvmS6rkrdPmFM,1034
6
+ pdaltools/las_info.py,sha256=cVVsOrtwgyOpI093PuXNhs2N7BJIdfW0BgxxRb-XqyQ,8096
7
+ pdaltools/las_merge.py,sha256=tcFVueV9X9nNEaoAl5zCduY5DETlBg63MAgP2SuKiNo,4121
8
+ pdaltools/las_remove_dimensions.py,sha256=u_3VfkabkN_Y3eDLdJwCLVGpondvIx0f0v0RdFDoAFw,1792
9
+ pdaltools/pcd_info.py,sha256=NIAH5KGikVDQLlbCcw9FuaPqe20UZvRfkHsDZd5kmZA,3210
10
+ pdaltools/replace_attribute_in_las.py,sha256=po1F-fi8s7iilqKWaryW4JRbsmdMOUe0yGvG3AEKxtk,4771
11
+ pdaltools/standardize_format.py,sha256=gqm2GJbtDkT4k4oC_NX2LIPh9R2BLh4sMHLKYgfKrMc,3973
12
+ pdaltools/unlock_file.py,sha256=pIThdWMNkTph0xgJVVRaM1o9aUMQhM6804PscScB3JI,1963
13
+ ign_pdal_tools-1.7.5.dist-info/LICENSE.md,sha256=iVzCFZTUXeiqP8bP474iuWZiWO_kDCD4SPh1Wiw125Y,1120
14
+ ign_pdal_tools-1.7.5.dist-info/METADATA,sha256=EqDhgZ2NEpoOr7Whk2RefX9emt_M05l7MGotTplcgkE,5722
15
+ ign_pdal_tools-1.7.5.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
16
+ ign_pdal_tools-1.7.5.dist-info/top_level.txt,sha256=KvGW0ZzqQbhCKzB5_Tp_buWMZyIgiO2M2krWF_ecOZc,10
17
+ ign_pdal_tools-1.7.5.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.1.0)
2
+ Generator: setuptools (75.6.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
pdaltools/_version.py CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "1.7.4"
1
+ __version__ = "1.7.5"
2
2
 
3
3
 
4
4
  if __name__ == "__main__":
@@ -0,0 +1,104 @@
1
+ import argparse
2
+
3
+ import geopandas
4
+ import numpy as np
5
+ import pdal
6
+
7
+ from pdaltools.las_info import get_writer_parameters_from_reader_metadata, las_info_metadata, get_bounds_from_header_info
8
+
9
+
10
+ def extract_points_from_geo(input_geo: str):
11
+ file = open(input_geo)
12
+ df = geopandas.read_file(file)
13
+ return df.get_coordinates(ignore_index=True, include_z=True)
14
+
15
+ def point_in_bound(bound_minx, bound_maxx, bound_miny, bound_maxy, pt_x, pt_y):
16
+ return pt_x >= bound_minx and pt_x <= bound_maxx and pt_y >= bound_miny and pt_y <= bound_maxy
17
+
18
+ def add_points_in_las(input_las: str, input_geo: str, output_las: str, inside_las: bool, values_dimensions: {}):
19
+ points_geo = extract_points_from_geo(input_geo)
20
+ pipeline = pdal.Pipeline() | pdal.Reader.las(input_las)
21
+ pipeline.execute()
22
+ points_las = pipeline.arrays[0]
23
+ dimensions = list(points_las.dtype.fields.keys())
24
+
25
+ if inside_las:
26
+ mtd = las_info_metadata(input_las)
27
+ bound_minx, bound_maxx, bound_miny, bound_maxy = get_bounds_from_header_info(mtd)
28
+
29
+ for i in points_geo.index:
30
+ if inside_las :
31
+ if not point_in_bound(bound_minx, bound_maxx, bound_miny, bound_maxy, points_geo["x"][i], points_geo["y"][i]):
32
+ continue
33
+ pt_las = np.empty(1, dtype=points_las.dtype)
34
+ pt_las[0][dimensions.index("X")] = points_geo["x"][i]
35
+ pt_las[0][dimensions.index("Y")] = points_geo["y"][i]
36
+ pt_las[0][dimensions.index("Z")] = points_geo["z"][i]
37
+ for val in values_dimensions:
38
+ pt_las[0][dimensions.index(val)] = values_dimensions[val]
39
+ points_las = np.append(points_las, pt_las, axis=0)
40
+
41
+ params = get_writer_parameters_from_reader_metadata(pipeline.metadata)
42
+ pipeline_end = pdal.Pipeline(arrays=[points_las])
43
+ pipeline_end |= pdal.Writer.las(output_las, forward="all", **params)
44
+ pipeline_end.execute()
45
+
46
+
47
+ def parse_args():
48
+ parser = argparse.ArgumentParser("Add points from geometry file in a las/laz file.")
49
+ parser.add_argument("--input_file", "-i", type=str, help="Las/Laz input file")
50
+ parser.add_argument("--output_file", "-o", type=str, help="Las/Laz output file.")
51
+ parser.add_argument("--input_geo_file", "-g", type=str, help="Geometry input file.")
52
+ parser.add_argument("--inside_las", "-l", type=str, help="Keep points only inside the las boundary.")
53
+ parser.add_argument(
54
+ "--dimensions",
55
+ "-d",
56
+ metavar="KEY=VALUE",
57
+ nargs="+",
58
+ help="Set a number of key-value pairs corresponding to value "
59
+ "needed in points added in the output las; key should be included in the input las.",
60
+ )
61
+ return parser.parse_args()
62
+
63
+
64
+ def is_nature(value, nature):
65
+ if value is None:
66
+ return False
67
+ try:
68
+ nature(value)
69
+ return True
70
+ except:
71
+ return False
72
+
73
+
74
+ def parse_var(s):
75
+ items = s.split("=")
76
+ key = items[0].strip()
77
+ if len(items) > 1:
78
+ value = "=".join(items[1:])
79
+ if is_nature(value, int):
80
+ value = int(value)
81
+ elif is_nature(value, float):
82
+ value = float(value)
83
+ return (key, value)
84
+
85
+
86
+ def parse_vars(items):
87
+ d = {}
88
+ if items:
89
+ for item in items:
90
+ key, value = parse_var(item)
91
+ d[key] = value
92
+ return d
93
+
94
+
95
+ if __name__ == "__main__":
96
+ args = parse_args()
97
+ added_dimensions = parse_vars(args.dimensions)
98
+ add_points_in_las(
99
+ input_las=args.input_file,
100
+ input_geo=args.input_geo_file,
101
+ output_las=args.input_file if args.output_file is None else args.output_file,
102
+ inside_las=args.inside_las,
103
+ values_dimensions=added_dimensions,
104
+ )
pdaltools/las_info.py CHANGED
@@ -6,6 +6,8 @@ from typing import Dict, Tuple
6
6
  import osgeo.osr as osr
7
7
  import pdal
8
8
 
9
+ from pdaltools.pcd_info import infer_tile_origin
10
+
9
11
  osr.UseExceptions()
10
12
 
11
13
 
@@ -17,13 +19,37 @@ def las_info_metadata(filename: str):
17
19
  return metadata
18
20
 
19
21
 
20
- def get_bounds_from_header_info(metadata):
22
+ def get_bounds_from_header_info(metadata: Dict) -> Tuple[float, float, float, float]:
23
+ """Get bounds from metadata that has been extracted previously from the header of a las file
24
+
25
+ Args:
26
+ metadata (str): Dictonary containing metadata from a las file (as extracted with pipeline.quickinfo)
27
+
28
+ Returns:
29
+ Tuple[float, float, float, float]: minx, maxx, miny, maxy
30
+ """
21
31
  bounds = metadata["bounds"]
22
32
  minx, maxx, miny, maxy = bounds["minx"], bounds["maxx"], bounds["miny"], bounds["maxy"]
23
33
 
24
34
  return minx, maxx, miny, maxy
25
35
 
26
36
 
37
+ def get_tile_origin_using_header_info(filename: str, tile_width: int = 1000) -> Tuple[int, int]:
38
+ """ "Get las file theoretical origin (xmin, ymax) for a data that originates from a square tesselation/tiling
39
+ using the tesselation tile width only, directly from its path
40
+ Args:
41
+ filename (str): path to the las file
42
+ tile_width (int, optional): Tesselation tile width (in meters). Defaults to 1000.
43
+
44
+ Returns:
45
+ Tuple[int, int]: (origin_x, origin_y) tile origin coordinates = theoretical (xmin, ymax)
46
+ """
47
+ metadata = las_info_metadata(filename)
48
+ minx, maxx, miny, maxy = get_bounds_from_header_info(metadata)
49
+
50
+ return infer_tile_origin(minx, maxx, miny, maxy, tile_width)
51
+
52
+
27
53
  def get_epsg_from_header_info(metadata):
28
54
  if "srs" not in metadata.keys():
29
55
  raise RuntimeError("EPSG could not be inferred from metadata: No 'srs' key in metadata.")
@@ -1,5 +1,4 @@
1
1
  import argparse
2
- import os
3
2
 
4
3
  import pdal
5
4
  from pdaltools.las_info import get_writer_parameters_from_reader_metadata
pdaltools/pcd_info.py CHANGED
@@ -5,14 +5,52 @@ from typing import Tuple
5
5
  import numpy as np
6
6
 
7
7
 
8
+ def infer_tile_origin(minx: float, maxx: float, miny: float, maxy: float, tile_width: int) -> Tuple[int, int]:
9
+ """Get point cloud theoretical origin (xmin, ymax) for a data that originates from a square tesselation/tiling
10
+ using the tesselation tile width only, based on the min/max values
11
+
12
+ Edge values are supposed to be included in the tile
13
+
14
+ Args:
15
+ minx (float): point cloud min x value
16
+ maxx (float): point cloud max x value
17
+ miny (float): point cloud min y value
18
+ maxy (float): point cloud max y value
19
+ tile_width (int): tile width in meters
20
+
21
+ Raises:
22
+ ValueError: In case the min and max values do not belong to the same tile
23
+
24
+ Returns:
25
+ Tuple[int, int]: (origin_x, origin_y) tile origin coordinates = theoretical (xmin, ymax)
26
+ """
27
+
28
+ minx_tile_index = np.floor(minx / tile_width)
29
+ maxx_tile_index = np.floor(maxx / tile_width) if maxx % tile_width != 0 else np.floor(maxx / tile_width) - 1
30
+ miny_tile_index = np.ceil(miny / tile_width) if miny % tile_width != 0 else np.floor(miny / tile_width) + 1
31
+ maxy_tile_index = np.ceil(maxy / tile_width)
32
+
33
+ if maxx_tile_index == minx_tile_index and maxy_tile_index == miny_tile_index:
34
+ origin_x = minx_tile_index * tile_width
35
+ origin_y = maxy_tile_index * tile_width
36
+ return origin_x, origin_y
37
+ else:
38
+ raise ValueError(
39
+ f"Min values (x={minx} and y={miny}) do not belong to the same theoretical tile as"
40
+ f"max values (x={maxx} and y={maxy})."
41
+ )
42
+
43
+
8
44
  def get_pointcloud_origin_from_tile_width(
9
45
  points: np.ndarray, tile_width: int = 1000, buffer_size: float = 0
10
46
  ) -> Tuple[int, int]:
11
47
  """Get point cloud theoretical origin (xmin, ymax) for a data that originates from a square tesselation/tiling
12
- using the tesselation tile width only.
48
+ using the tesselation tile width only, based on the point cloud as a np.ndarray
13
49
 
14
50
  Edge values are supposed to be included in the tile
15
51
 
52
+ In case buffer_size is provided, the origin will be calculated on an "original" tile, supposing that
53
+ there has been a buffer added to the input tile.
16
54
 
17
55
  Args:
18
56
  points (np.ndarray): numpy array with the tile points
@@ -20,27 +58,19 @@ def get_pointcloud_origin_from_tile_width(
20
58
  buffer_size (float, optional): Optional buffer around the tile. Defaults to 0.
21
59
 
22
60
  Raises:
23
- ValueError: Raise an error when the bounding box of the tile is not included in a tile
61
+ ValueError: Raise an error when the initial tile is smaller than the buffer (in this case, we cannot find the
62
+ origin (it can be either in the buffer or in the tile))
24
63
 
25
64
  Returns:
26
65
  Tuple[int, int]: (origin_x, origin_y) origin coordinates
27
66
  """
28
67
  # Extract coordinates xmin, xmax, ymin and ymax of the original tile without buffer
29
- x_min, y_min = np.min(points[:, :2], axis=0) + buffer_size
30
- x_max, y_max = np.max(points[:, :2], axis=0) - buffer_size
31
-
32
- # Calculate the tiles to which x, y bounds belong
33
- tile_x_min = np.floor(x_min / tile_width)
34
- tile_x_max = np.floor(x_max / tile_width) if x_max % tile_width != 0 else np.floor(x_max / tile_width) - 1
35
- tile_y_min = np.ceil(y_min / tile_width) if y_min % tile_width != 0 else np.floor(y_min / tile_width) + 1
36
- tile_y_max = np.ceil(y_max / tile_width)
37
-
38
- if not (tile_x_max - tile_x_min) and not (tile_y_max - tile_y_min):
39
- origin_x = tile_x_min * tile_width
40
- origin_y = tile_y_max * tile_width
41
- return origin_x, origin_y
42
- else:
68
+ minx, miny = np.min(points[:, :2], axis=0) + buffer_size
69
+ maxx, maxy = np.max(points[:, :2], axis=0) - buffer_size
70
+
71
+ if maxx < minx or maxy < miny:
43
72
  raise ValueError(
44
- f"Min values (x={x_min} and y={y_min}) do not belong to the same theoretical tile as"
45
- f"max values (x={x_max} and y={y_max})."
73
+ "Cannot find pointcloud origin as the pointcloud width or height is smaller than buffer width"
46
74
  )
75
+
76
+ return infer_tile_origin(minx, maxx, miny, maxy, tile_width)
@@ -19,6 +19,7 @@ import pdal
19
19
 
20
20
  from pdaltools.unlock_file import copy_and_hack_decorator
21
21
 
22
+ # Standard parameters to pass to the pdal writer
22
23
  STANDARD_PARAMETERS = dict(
23
24
  major_version="1",
24
25
  minor_version="4", # Laz format version (pdal always write in 1.x format)
@@ -33,7 +34,6 @@ STANDARD_PARAMETERS = dict(
33
34
  offset_z=0,
34
35
  dataformat_id=6, # No color by default
35
36
  a_srs="EPSG:2154",
36
- class_points_removed=[], # remove points from class
37
37
  )
38
38
 
39
39
 
@@ -1,16 +0,0 @@
1
- pdaltools/_version.py,sha256=lNYVqr3yBgm8bym5YoCi_BcGRCVp-3qRpo5R-aiwjwQ,74
2
- pdaltools/color.py,sha256=PSdtMMdsapOtgzojdnaKVx6IxbKOaN2xP9mScAbCGm0,8629
3
- pdaltools/las_add_buffer.py,sha256=sBpTywlfsHHS8KuCUa-eydB2hylshEvjrMQt5TrqXb8,11275
4
- pdaltools/las_clip.py,sha256=GvEOYu8RXV68e35kU8i42GwSkbo4P9TvmS6rkrdPmFM,1034
5
- pdaltools/las_info.py,sha256=RE-UBdEUXqKvSrMV3mOlvE_16mhum7bw-p-ERu5bGOc,6979
6
- pdaltools/las_merge.py,sha256=tcFVueV9X9nNEaoAl5zCduY5DETlBg63MAgP2SuKiNo,4121
7
- pdaltools/las_remove_dimensions.py,sha256=J9-jBvT3APKUuPwuqAv_Sf5aW8lHxnpnFKZcxiC_Kno,1802
8
- pdaltools/pcd_info.py,sha256=d7ukCEdOFBQqaFp3HYM2MNbKDl1hdxUVHI9vSu9jN-M,1897
9
- pdaltools/replace_attribute_in_las.py,sha256=po1F-fi8s7iilqKWaryW4JRbsmdMOUe0yGvG3AEKxtk,4771
10
- pdaltools/standardize_format.py,sha256=xx3FHU8rqV-uLQgWGJtThn5G5hfCxkKag6fXGH4JOXs,3981
11
- pdaltools/unlock_file.py,sha256=pIThdWMNkTph0xgJVVRaM1o9aUMQhM6804PscScB3JI,1963
12
- ign_pdal_tools-1.7.4.dist-info/LICENSE.md,sha256=iVzCFZTUXeiqP8bP474iuWZiWO_kDCD4SPh1Wiw125Y,1120
13
- ign_pdal_tools-1.7.4.dist-info/METADATA,sha256=_naab5v10YXIqMh9bF7cUgKCUbJlfF5r4w6gOjKNc5s,5112
14
- ign_pdal_tools-1.7.4.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
15
- ign_pdal_tools-1.7.4.dist-info/top_level.txt,sha256=KvGW0ZzqQbhCKzB5_Tp_buWMZyIgiO2M2krWF_ecOZc,10
16
- ign_pdal_tools-1.7.4.dist-info/RECORD,,