from .laspy_config import *
[docs]class LasPoint:
"""Las file container to convert datasets to ESRI point shapefiles and/or GeoTIFFs.
Args:
las_file_name (str): Directory to and name of a las file.
epsg (int): Authority Code - Geodetic Parameter Dataset ID (default: ``3857``).
overwrite (bool): Overwrite existing shapefiles and/or GeoTIFFs (default: ``True``).
use_attributes (str): Attributes (properties) to use from the las-file available in pattr (config.py).
(default: ``use_attributes="aciw"``).
Attributes:
las_file (laspy.file.File): A laspy file object
attributes (str): Defined with ``use_attributes``
epsg (int): Authority code
gdf (geopandas.GeoDataFrame): geopandas data frame containing all points of the las file with the properties (columns) defined by ``use_attributes``
offset (laspy.file.File().header.offset): Offset of las points (auto-read)
overwrite (bool): Enable or disable overwriting existing files (default: ``True``)
scale (laspy.file.File().header.scale): Scale of las points relative to the offset (auto-read)
shapefile_name (str): The name and dicrectorty of a point shapefile where all las-file data is stored
srs (osr.SpatialReference): The geo-spatial reference imported from ``epsg``
"""
def __init__(self, las_file_name, epsg=3857, use_attributes="aciw", overwrite=True):
self.las_file = laspy.file.File(las_file_name, mode="r")
self.attributes = use_attributes
self.epsg = epsg
self.gdf = geopandas.GeoDataFrame() # void initialization
self.offset = np.array(self.las_file.header.offset, dtype=np.float64)
self.overwrite = overwrite
self.scale = np.array(self.las_file.header.scale, dtype=np.float64)
self.shapefile_name = ""
self.srs = osr.SpatialReference()
self.srs.ImportFromEPSG(epsg)
logging.info("Using EPSG = %04i" % epsg)
def __del__(self):
self.las_file.close()
del self.las_file
def __repr__(self):
return "%s" % self.__class__.__name__
[docs] def create_dem(self, target_file_name="", pixel_size=1.0, **kwargs):
"""Creates a digital elevation model (DEM) in GeoTIFF format from the *las* file points.
Args:
target_file_name (str): A file name including an existing directory where the dem will be created< must end on ``.tif``.
pixel_size (float): The size of one pixel relative to the spatial reference system
Keyword Args:
src_shp_file_name (str): Name of a shapefile from which elevation information is to be extracted (default: name of the las-point shapefile)
elevation_field_name (str): Name of the field from which elevation data is to be extracted (default: ``"elevation"``)
interpolate_gap_pixels (bool): Fill empty pixels that are not touched by a shapefile point with interpolated values (default: ``False``)
radius1 (float): Define the x-radius for interpolating pixels (default: ``-1``, corresponding to infinity). Only applicable ``with interpolate_gap_pixels``.
radius2 (float): Define the y-radius for interpolating pixels (default: ``-1``, corresponding to infinity). Only applicable ``with interpolate_gap_pixels``.
power (float): Power of the function for interpolating pixel values (default: ``1.0``, corresponding to linear).
smoothing (float): Smoothing parameter for interpolating pixel values (default: ``0.0``).
min_points (int): Minimum number of points to use for interpolation. If the interpolator cannot find at least ``min_points`` for a pixel, it assigns a ``no_data`` value to that pixel (default: ``0``).
max_points (int): Maximum number of points to use for interpolation. The interpolator will not use more than ``max_points`` closest points to interpolate a pixel value (default: ``0``).
Hint:
This function works independently and does not require the prior creation of a shapefile.
Returns:
int: ``0`` if successful, otherwise ``-1``
"""
logging.info(" * Creating GeoTIFF DEM %s ..." % target_file_name)
default_keys = {"src_shp_file_name": self.shapefile_name,
"elevation_field_name": "Elevation",
"interpolate_gap_pixels": False,
"radius1": -1,
"radius2": -1,
"power": 1.0,
"smoothing": 0.0,
"min_points": 0,
"max_points": 0,
}
for k in default_keys.keys():
if kwargs.get(k):
default_keys[k] = str(kwargs.get(k))
if not os.path.isfile(default_keys["src_shp_file_name"]):
logging.info(" * Need to create a point shapefile first (%s does not exist) ..." % default_keys["src_shp_file_name"])
self.export2shp(shapefile_name=default_keys["src_shp_file_name"])
if os.path.isfile(target_file_name) and self.overwrite:
logging.info(" -- Overwriting %s ..." % target_file_name)
geo_utils.remove_tif(target_file_name)
geo_utils.rasterize(default_keys["src_shp_file_name"], target_file_name, pixel_size=pixel_size,
field_name=default_keys["elevation_field_name"],
interpolate_gap_pixels=default_keys["interpolate_gap_pixels"],
power=default_keys["power"],
radius1=default_keys["radius1"],
radius2=default_keys["radius2"],
smoothing=default_keys["smoothing"],
min_points=default_keys["min_points"],
max_points=default_keys["max_points"])
logging.info(" -- Done.")
return 0
[docs] def export2shp(self, **kwargs):
"""Converts las file points to a point shapefile.
Keyword Args:
shapefile_name (`str`): Optional shapefile name (must end on .shp).
(default: ``'/this/dir/las_file_name.shp'``).
Returns:
str: ``/path/to/shapefile.shp``, which is a point shapefile created by the function.
"""
if kwargs.get("shapefile_name"):
self.shapefile_name = kwargs.get("shapefile_name")
else:
self.shapefile_name = os.path.abspath("") + "/{0}.shp".format(self.las_file.filename)
if os.path.isfile(self.shapefile_name) and self.overwrite is False:
logging.info(" * Using existing shapefile %s." % self.shapefile_name)
return self.shapefile_name
self._build_data_frame()
logging.info(" * Writing geopandas.GeoDataFrame to shapefile (%s) ..." % self.shapefile_name)
logging.info(" *** this action may take a while (0.5h per 1 million points)***")
self.gdf.to_file(filename=self.shapefile_name, driver="ESRI Shapefile")
logging.info(" -- Done.")
return self.shapefile_name
[docs] def get_file_info(self):
""" Prints las file information to console."""
print("Point data formats in file:")
for f in self.las_file.point_format:
print(" -- %s" % f.name)
print("File header info:")
headers = [str(spec.name) for spec in self.las_file.header.header_format]
print(" -- " + ", ".join(headers))
def _build_data_frame(self):
""" Builds the geopandas GeoDataFrame - auto-runs ``self._parse_attributes``."""
point_dict = self._parse_attributes()
# for attr in self.pts_description:
# if not re.search("[x-z]", attr):
# point_dict.update({attr: pts_df[attr]})
logging.info(" * Building geopandas.GeoDataFrame ...")
self.gdf = geopandas.GeoDataFrame(pd.DataFrame(point_dict),
crs="EPSG:%04i" % self.epsg)
logging.info(" -- Done.")
def _get_xyz_array(self):
"""Extract x-y-z data from las records in a faster way than using ``las_file.x``, ``y``, or ``z``.
Returns:
ndarray: The DEM information extracted from the las file.
"""
pts = self.las_file.points['point'].copy().view(np.recarray)
# read and transform data (from raw - fast than las_file.x)
dem_array = np.empty((3, len(pts)), dtype=np.float64)
dem_array[0, :] = pts.X * self.scale[0] + self.offset[0]
dem_array[1, :] = pts.Y * self.scale[1] + self.offset[1]
dem_array[2, :] = pts.Z * self.scale[2] + self.offset[2]
return dem_array
def _parse_attributes(self):
"""Parses attributes and append entries to point list."""
logging.info(" * Extracting transformed point coordinates ...")
dem = self._get_xyz_array()
point_dict = {"geometry": geopandas.points_from_xy(x=dem[0], y=dem[1], z=dem[2])}
# add elevation field to facilitate DEM export
point_dict.update({"elevation": self.las_file.z})
logging.info(" * Parsing and extracting user attributes of points ...")
for attr in self.attributes:
try:
point_dict.update({wattr[attr]: self.las_file.__getattribute__(pattr[attr])})
logging.info(" -- added %s" % wattr[attr])
except AttributeError:
logging.error("Non-existing attribute %s. Valid attributes are: %s" % (str(attr), dict2str(wattr)))
except KeyError:
logging.error("Non-existing las-file key %s - valid are: " % str(attr) + ", ".join(dir(self.las_file)))
return point_dict