File: test_crs.py

package info (click to toggle)
python-laspy 2.5.4-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 3,928 kB
  • sloc: python: 9,065; makefile: 20
file content (213 lines) | stat: -rw-r--r-- 6,607 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
import json
import os
import subprocess
from pathlib import Path
from typing import Dict

import pytest

import laspy
from laspy.vlrs.geotiff import GeographicTypeGeoKey, ProjectedCSTypeGeoKey
from laspy.vlrs.known import GeoKeyDirectoryVlr
from tests.test_common import autzen_geo_proj_las, autzen_las, test1_4_las

try:
    import pyproj
except ModuleNotFoundError:
    pyproj = None


@pytest.fixture()
def file_wkt():
    return laspy.read(test1_4_las)


@pytest.fixture()
def file_geotiff():
    return laspy.read(autzen_las)


@pytest.fixture()
def file_geotiff_geo_proj():
    return laspy.read(autzen_geo_proj_las)


@pytest.fixture()
def file_with_both_wkt_and_geotiff_vlrs():
    path = Path(__file__).parent / "data" / "file_with_both_wkt_and_geotiff_vlrs.las"
    return laspy.read(path)


def has_pyproj() -> bool:
    return pyproj is not None


@pytest.mark.skipif(not has_pyproj(), reason="pyproj not installed")
def test_parse_crs_wkt(file_wkt):
    assert "Bound CRS" in file_wkt.header.parse_crs().type_name


@pytest.mark.skipif(not has_pyproj(), reason="pyproj not installed")
def test_parse_crs_geotiff(file_geotiff, file_geotiff_geo_proj):
    assert "Projected CRS" in file_geotiff.header.parse_crs().type_name
    assert (
        file_geotiff.header.parse_crs().to_epsg()
        == file_geotiff_geo_proj.header.parse_crs().to_epsg()
        == 2994
    )

    geokeys1 = get_geokeys(file_geotiff.header)
    geokeys2 = get_geokeys(file_geotiff_geo_proj.header)

    assert (
        geokeys1[ProjectedCSTypeGeoKey.id] == geokeys2[ProjectedCSTypeGeoKey.id] == 2994
    )
    assert GeographicTypeGeoKey.id not in geokeys1
    assert geokeys2[GeographicTypeGeoKey.id] == 4152


def get_geokeys(header: laspy.LasHeader) -> Dict:
    geo_vlr = header.vlrs.get_by_id("LASF_Projection")
    for rec in geo_vlr:
        if isinstance(rec, GeoKeyDirectoryVlr):
            return {k.id: k.value_offset for k in rec.geo_keys}

    return {}


@pytest.mark.skipif(not has_pyproj(), reason="pyproj not installed")
def test_add_crs_wkt():
    header = laspy.LasHeader(point_format=6, version="1.4")
    crs = pyproj.CRS.from_epsg(6432)
    header.add_crs(crs)

    lasf_proj = header.vlrs.get_by_id("LASF_Projection")

    assert len(lasf_proj) > 0
    assert hasattr(lasf_proj[0], "string")
    assert lasf_proj[0].string == crs.to_wkt()
    assert header.global_encoding.wkt


@pytest.mark.skipif(not has_pyproj(), reason="pyproj not installed")
def test_handle_empty_crs_wkt_string():
    header = laspy.LasHeader(point_format=6, version="1.4")
    empty_wkt_crs = laspy.vlrs.known.WktCoordinateSystemVlr(wkt_string="")
    header.vlrs.append(empty_wkt_crs)

    crs = header.parse_crs()
    assert crs is None


@pytest.mark.skipif(not has_pyproj(), reason="pyproj not installed")
def test_add_crs_geotiff():
    """
    Test adding geotiff crs seems ok
    """
    header = laspy.LasHeader(point_format=3, version="1.2")
    crs = pyproj.CRS.from_epsg(6432)
    header.add_crs(crs)

    lasf_proj = header.vlrs.get_by_id("LASF_Projection")

    assert len(lasf_proj) > 0
    assert hasattr(lasf_proj[0], "geo_keys")
    assert hasattr(lasf_proj[1], "strings")
    assert header.global_encoding.wkt == False

    las = laspy.LasData(header=header)
    las = laspy.lib.write_then_read_again(las)
    epsg = las.header.parse_crs().to_epsg()
    assert epsg == 6432, "epsg after read-write is not the same"


@pytest.mark.skipif(not has_pyproj(), reason="pyproj not installed")
def test_add_crs_compatibility():
    header = laspy.LasHeader(point_format=3, version="1.4")
    crs = pyproj.CRS.from_epsg(6432)
    header.add_crs(crs, keep_compatibility=False)

    lasf_proj = header.vlrs.get_by_id("LASF_Projection")

    assert len(lasf_proj) > 0
    assert hasattr(lasf_proj[0], "string")
    assert lasf_proj[0].string == crs.to_wkt()
    assert header.global_encoding.wkt


def has_pdal():
    try:
        subprocess.run(["pdal", "--version"], capture_output=True, check=True)
    except:
        return False
    else:
        return True


def get_pdal_srs(filename):
    process = subprocess.run(
        ["pdal", "info", "--metadata", filename], capture_output=True, check=True
    )
    json_output = json.loads(process.stdout.decode("utf-8"))["metadata"]["srs"]
    return json_output["wkt"], json_output["proj4"]


def geotiff_crs_pdal_test(crs):
    header = laspy.LasHeader(point_format=3, version="1.2")
    header.add_crs(crs)

    tmp_filename = f"delete_me_pdal_test_cs_{crs.to_epsg()}.las"

    las = laspy.LasData(header=header)
    las.write(tmp_filename)

    try:
        pdal_wkt, _ = get_pdal_srs(tmp_filename)

    finally:
        os.remove(tmp_filename)

    # We don't use pdal's proj4 as its seems not complete enough
    # to be 100% equal, while the wkt seems ok
    assert pyproj.CRS.from_wkt(pdal_wkt) == crs


@pytest.mark.skipif(
    not (has_pdal() and has_pyproj()), reason="PDAL and/or pyproj not found"
)
def test_pdal_understands_our_geotiff_derived_projected_cs():
    crs = pyproj.CRS.from_epsg(3945)
    geotiff_crs_pdal_test(crs)


@pytest.mark.skipif(
    not (has_pdal() and has_pyproj()), reason="PDAL and/or pyproj not found"
)
def test_pdal_understands_our_geotiff_geographic_cs():
    crs = pyproj.CRS.from_epsg(4326)
    geotiff_crs_pdal_test(crs)


@pytest.mark.skipif(
    not (has_pdal() and has_pyproj()), reason="PDAL and/or pyproj not found"
)
def test_pdal_understands_our_geotiff_projected_cs():
    crs = pyproj.CRS.from_epsg(6432)
    geotiff_crs_pdal_test(crs)


@pytest.mark.skipif(not has_pyproj(), reason="pyproj not installed")
def test_preference_option(file_with_both_wkt_and_geotiff_vlrs):
    header = file_with_both_wkt_and_geotiff_vlrs.header

    expected_wkt_crs = """PROJCS["NAD83_2011_Nebraska_ft",GEOGCS["GCS_NAD_1983_2011",DATUM["D_NAD83_NATIONAL_SPATIAL_REFERENCE_SYSTEM_2011",SPHEROID["GRS_1980",6378137,298.257222101]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]],PROJECTION["Lambert_Conformal_Conic"],PARAMETER["scale_factor",1],PARAMETER["standard_parallel_1",40],PARAMETER["standard_parallel_2",43],PARAMETER["central_meridian",-100],PARAMETER["latitude_of_origin",39.83333333333334],PARAMETER["false_easting",1640416.666666667],PARAMETER["false_northing",0],UNIT["Foot_US",0.30480060960121924]]"""
    expected_geotiff_crs = "epsg:32104"

    crs = header.parse_crs()
    assert str(crs) == expected_wkt_crs

    crs = header.parse_crs(prefer_wkt=True)
    assert str(crs) == expected_wkt_crs

    crs = header.parse_crs(prefer_wkt=False)
    assert str(crs).lower() == expected_geotiff_crs