or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

compression.mdcopc.mdcore-io.mddata-containers.mdindex.mdio-handlers.mdpoint-data.mdvlr.md

vlr.mddocs/

0

# VLR Management

1

2

Variable Length Record (VLR) handling for storing metadata, coordinate reference systems, and custom application data within LAS files. VLRs provide a standardized way to embed additional information beyond the basic LAS specification.

3

4

## Capabilities

5

6

### Variable Length Records

7

8

Core VLR implementation for storing arbitrary data with standardized identification.

9

10

```python { .api }

11

class VLR:

12

def __init__(self, user_id, record_id, description="", record_data=b""):

13

"""

14

Create Variable Length Record.

15

16

Parameters:

17

- user_id: str - User/organization identifier (up to 16 chars)

18

- record_id: int - Record type identifier (0-65535)

19

- description: str - Human-readable description (up to 32 chars)

20

- record_data: bytes - Binary record data

21

"""

22

23

@property

24

def user_id(self) -> str:

25

"""User/organization identifier."""

26

27

@property

28

def record_id(self) -> int:

29

"""Record type identifier."""

30

31

@property

32

def description(self) -> str:

33

"""Human-readable description."""

34

35

@property

36

def record_data(self) -> bytes:

37

"""Binary record data payload."""

38

39

def record_data_bytes(self) -> bytes:

40

"""Get binary representation of record data."""

41

```

42

43

**Usage Examples:**

44

45

```python

46

import laspy

47

48

# Create custom VLR for application metadata

49

metadata_vlr = laspy.VLR(

50

user_id="MyCompany",

51

record_id=1001,

52

description="Processing metadata",

53

record_data=b'{"version": "1.0", "algorithm": "ground_filter_v2"}'

54

)

55

56

# Create VLR with structured data

57

import json

58

import struct

59

60

processing_params = {

61

"threshold": 2.5,

62

"iterations": 3,

63

"algorithm": "progressive_morphology"

64

}

65

66

# Encode as JSON bytes

67

json_data = json.dumps(processing_params).encode('utf-8')

68

69

params_vlr = laspy.VLR(

70

user_id="ProcessingApp",

71

record_id=2001,

72

description="Algorithm parameters",

73

record_data=json_data

74

)

75

76

# Add VLRs to LAS file

77

las = laspy.read('input.las')

78

las.vlrs.append(metadata_vlr)

79

las.vlrs.append(params_vlr)

80

las.write('output_with_metadata.las')

81

82

print(f"Added {len(las.vlrs)} VLRs to file")

83

```

84

85

### VLR Base Classes

86

87

Abstract base classes for creating custom VLR types with standardized interfaces.

88

89

```python { .api }

90

class IVLR:

91

"""Abstract interface for Variable Length Records."""

92

93

@property

94

def user_id(self) -> str: ...

95

96

@property

97

def record_id(self) -> int: ...

98

99

@property

100

def description(self) -> Union[str, bytes]: ...

101

102

def record_data_bytes(self) -> bytes:

103

"""Get binary representation of record data."""

104

105

class BaseVLR(IVLR):

106

"""Abstract base implementation for VLRs."""

107

108

def __init__(self, user_id, record_id, description=""):

109

"""

110

Initialize base VLR.

111

112

Parameters:

113

- user_id: str - User/organization identifier

114

- record_id: int - Record type identifier

115

- description: str - Human-readable description

116

"""

117

118

@property

119

def user_id(self) -> str: ...

120

121

@property

122

def record_id(self) -> int: ...

123

124

@property

125

def description(self) -> Union[str, bytes]: ...

126

```

127

128

### GeoTIFF VLR Support

129

130

Specialized support for GeoTIFF coordinate reference system information.

131

132

```python { .api }

133

# GeoTIFF VLR utilities available in laspy.vlrs.geotiff module

134

from laspy.vlrs import geotiff

135

136

# Standard GeoTIFF VLR user IDs and record IDs are predefined

137

# LASF_Projection user_id with various record_id values:

138

# 34735 - GeoKeyDirectoryTag

139

# 34736 - GeoDoubleParamsTag

140

# 34737 - GeoAsciiParamsTag

141

```

142

143

**Usage Examples:**

144

145

```python

146

import laspy

147

from laspy.vlrs import geotiff

148

149

# Read and inspect GeoTIFF VLRs

150

las = laspy.read('georeferenced.las')

151

152

for vlr in las.vlrs:

153

if vlr.user_id == "LASF_Projection":

154

print(f"Found GeoTIFF VLR: ID={vlr.record_id}, Desc='{vlr.description}'")

155

156

if vlr.record_id == 34735: # GeoKeyDirectoryTag

157

print("This VLR contains GeoTIFF key directory")

158

elif vlr.record_id == 34736: # GeoDoubleParamsTag

159

print("This VLR contains GeoTIFF double parameters")

160

elif vlr.record_id == 34737: # GeoAsciiParamsTag

161

print("This VLR contains GeoTIFF ASCII parameters")

162

163

# Parse coordinate reference system (requires pyproj)

164

try:

165

crs = las.header.parse_crs()

166

if crs:

167

print(f"Parsed CRS: {crs}")

168

print(f"CRS Authority: {crs.to_authority()}")

169

else:

170

print("No CRS information found")

171

except ImportError:

172

print("pyproj not available for CRS parsing")

173

```

174

175

## Standard VLR Types

176

177

### Well-Known VLR Categories

178

179

LAS files commonly use these standardized VLR types:

180

181

```python

182

# Coordinate Reference System VLRs

183

CRS_USER_ID = "LASF_Projection"

184

GEO_KEY_DIRECTORY = 34735

185

GEO_DOUBLE_PARAMS = 34736

186

GEO_ASCII_PARAMS = 34737

187

WKT_COORDINATE_SYSTEM = 2112

188

189

# Extra Bytes VLRs (for custom point dimensions)

190

EXTRA_BYTES_USER_ID = "LASF_Projection"

191

EXTRA_BYTES_RECORD_ID = 4

192

193

# Classification Lookup VLRs

194

CLASSIFICATION_USER_ID = "LASF_Projection"

195

CLASSIFICATION_RECORD_ID = 0

196

197

# Flight Line VLRs

198

FLIGHT_LINE_USER_ID = "LASF_Projection"

199

FLIGHT_LINE_RECORD_ID = 1

200

201

# Histogram VLRs

202

HISTOGRAM_USER_ID = "LASF_Projection"

203

HISTOGRAM_RECORD_ID = 2

204

```

205

206

### Working with Standard VLRs

207

208

```python

209

import laspy

210

import struct

211

212

def add_flight_line_info(las_data, flight_lines):

213

"""Add flight line information as VLR."""

214

215

# Pack flight line data

216

# Format: count (4 bytes) + flight_line_ids (4 bytes each)

217

data = struct.pack('<I', len(flight_lines)) # Count

218

for flight_id in flight_lines:

219

data += struct.pack('<I', flight_id)

220

221

flight_vlr = laspy.VLR(

222

user_id="LASF_Projection",

223

record_id=1,

224

description="Flight line information",

225

record_data=data

226

)

227

228

las_data.vlrs.append(flight_vlr)

229

return las_data

230

231

def add_classification_lookup(las_data, class_lookup):

232

"""Add classification lookup table as VLR."""

233

234

# Create lookup table data

235

# Format: class_id (1 byte) + description_length (1 byte) + description (variable)

236

data = b""

237

for class_id, description in class_lookup.items():

238

desc_bytes = description.encode('utf-8')

239

data += struct.pack('<BB', class_id, len(desc_bytes))

240

data += desc_bytes

241

242

class_vlr = laspy.VLR(

243

user_id="LASF_Projection",

244

record_id=0,

245

description="Classification lookup",

246

record_data=data

247

)

248

249

las_data.vlrs.append(class_vlr)

250

return las_data

251

252

# Usage

253

las = laspy.read('input.las')

254

255

# Add flight line info

256

flight_lines = [101, 102, 103, 104]

257

las = add_flight_line_info(las, flight_lines)

258

259

# Add classification lookup

260

classifications = {

261

0: "Never classified",

262

1: "Unclassified",

263

2: "Ground",

264

3: "Low vegetation",

265

4: "Medium vegetation",

266

5: "High vegetation",

267

6: "Building"

268

}

269

las = add_classification_lookup(las, classifications)

270

271

las.write('output_with_vlrs.las')

272

```

273

274

## Advanced VLR Usage

275

276

### Custom VLR Classes

277

278

```python

279

import laspy

280

import json

281

import struct

282

from typing import Dict, Any

283

284

class JsonVLR(laspy.vlrs.BaseVLR):

285

"""VLR that stores JSON data."""

286

287

def __init__(self, user_id: str, record_id: int, data: Dict[str, Any], description: str = ""):

288

super().__init__(user_id, record_id, description)

289

self._data = data

290

291

@property

292

def data(self) -> Dict[str, Any]:

293

"""Get JSON data."""

294

return self._data

295

296

@data.setter

297

def data(self, value: Dict[str, Any]):

298

"""Set JSON data."""

299

self._data = value

300

301

def record_data_bytes(self) -> bytes:

302

"""Serialize JSON data to bytes."""

303

return json.dumps(self._data).encode('utf-8')

304

305

@classmethod

306

def from_vlr(cls, vlr: laspy.VLR) -> 'JsonVLR':

307

"""Create JsonVLR from standard VLR."""

308

data = json.loads(vlr.record_data.decode('utf-8'))

309

return cls(vlr.user_id, vlr.record_id, data, vlr.description)

310

311

class BinaryStructVLR(laspy.vlrs.BaseVLR):

312

"""VLR that stores structured binary data."""

313

314

def __init__(self, user_id: str, record_id: int, format_string: str, values: tuple, description: str = ""):

315

super().__init__(user_id, record_id, description)

316

self.format_string = format_string

317

self.values = values

318

319

def record_data_bytes(self) -> bytes:

320

"""Pack structured data to bytes."""

321

return struct.pack(self.format_string, *self.values)

322

323

@classmethod

324

def from_vlr(cls, vlr: laspy.VLR, format_string: str) -> 'BinaryStructVLR':

325

"""Create BinaryStructVLR from standard VLR."""

326

values = struct.unpack(format_string, vlr.record_data)

327

return cls(vlr.user_id, vlr.record_id, format_string, values, vlr.description)

328

329

# Usage examples

330

las = laspy.read('input.las')

331

332

# Add JSON metadata

333

metadata = {

334

"processing_date": "2024-01-15",

335

"software_version": "2.1.0",

336

"quality_metrics": {

337

"ground_points_percent": 65.2,

338

"outliers_removed": 1250

339

}

340

}

341

342

json_vlr = JsonVLR("MyCompany", 1001, metadata, "Processing metadata")

343

las.vlrs.append(json_vlr)

344

345

# Add structured binary data

346

survey_params = (

347

12.5, # flight_height (float)

348

45.0, # scan_angle_max (float)

349

500000, # pulse_frequency (uint32)

350

2 # returns_per_pulse (uint16)

351

)

352

353

binary_vlr = BinaryStructVLR(

354

"SurveyApp", 2001,

355

'<ffIH', # Little-endian: 2 floats, 1 uint32, 1 uint16

356

survey_params,

357

"Survey parameters"

358

)

359

las.vlrs.append(binary_vlr)

360

361

las.write('output_custom_vlrs.las')

362

```

363

364

### VLR Inspection and Validation

365

366

```python

367

import laspy

368

369

def inspect_vlrs(las_file):

370

"""Inspect all VLRs in a LAS file."""

371

372

las = laspy.read(las_file)

373

374

print(f"File: {las_file}")

375

print(f"Total VLRs: {len(las.vlrs)}")

376

377

if hasattr(las, 'evlrs') and las.evlrs:

378

print(f"Extended VLRs: {len(las.evlrs)}")

379

380

print("\nVLR Details:")

381

print("-" * 80)

382

383

for i, vlr in enumerate(las.vlrs):

384

print(f"VLR {i+1}:")

385

print(f" User ID: '{vlr.user_id}'")

386

print(f" Record ID: {vlr.record_id}")

387

print(f" Description: '{vlr.description}'")

388

print(f" Data size: {len(vlr.record_data)} bytes")

389

390

# Try to identify known VLR types

391

if vlr.user_id == "LASF_Projection":

392

if vlr.record_id == 34735:

393

print(" Type: GeoTIFF Key Directory")

394

elif vlr.record_id == 34736:

395

print(" Type: GeoTIFF Double Parameters")

396

elif vlr.record_id == 34737:

397

print(" Type: GeoTIFF ASCII Parameters")

398

elif vlr.record_id == 2112:

399

print(" Type: WKT Coordinate System")

400

elif vlr.record_id == 4:

401

print(" Type: Extra Bytes Description")

402

403

# Show first few bytes of data

404

if len(vlr.record_data) > 0:

405

preview = vlr.record_data[:20]

406

print(f" Data preview: {preview}")

407

408

print()

409

410

def validate_vlrs(las_file):

411

"""Validate VLR integrity and standards compliance."""

412

413

las = laspy.read(las_file)

414

issues = []

415

416

for i, vlr in enumerate(las.vlrs):

417

# Check user ID length (16 chars max)

418

if len(vlr.user_id) > 16:

419

issues.append(f"VLR {i+1}: User ID too long ({len(vlr.user_id)} > 16)")

420

421

# Check description length (32 chars max)

422

if len(vlr.description) > 32:

423

issues.append(f"VLR {i+1}: Description too long ({len(vlr.description)} > 32)")

424

425

# Check record ID range (0-65535)

426

if not (0 <= vlr.record_id <= 65535):

427

issues.append(f"VLR {i+1}: Record ID out of range ({vlr.record_id})")

428

429

# Check for reserved record IDs

430

if vlr.user_id == "LASF_Projection":

431

reserved_ids = {34735, 34736, 34737, 2112, 4, 0, 1, 2}

432

if vlr.record_id not in reserved_ids:

433

issues.append(f"VLR {i+1}: Non-standard record ID for LASF_Projection ({vlr.record_id})")

434

435

if issues:

436

print("VLR Validation Issues:")

437

for issue in issues:

438

print(f" - {issue}")

439

else:

440

print("All VLRs passed validation")

441

442

return len(issues) == 0

443

444

# Usage

445

inspect_vlrs('sample.las')

446

is_valid = validate_vlrs('sample.las')

447

```

448

449

### VLR Migration and Conversion

450

451

```python

452

import laspy

453

454

def copy_vlrs_between_files(source_file, target_file, vlr_filter=None):

455

"""Copy VLRs from source to target file."""

456

457

source = laspy.read(source_file)

458

target = laspy.read(target_file)

459

460

copied_count = 0

461

462

for vlr in source.vlrs:

463

# Apply filter if provided

464

if vlr_filter and not vlr_filter(vlr):

465

continue

466

467

# Check if VLR already exists in target

468

exists = any(

469

(existing.user_id == vlr.user_id and existing.record_id == vlr.record_id)

470

for existing in target.vlrs

471

)

472

473

if not exists:

474

target.vlrs.append(vlr)

475

copied_count += 1

476

477

target.write(target_file)

478

print(f"Copied {copied_count} VLRs to {target_file}")

479

480

def crs_vlr_filter(vlr):

481

"""Filter for CRS-related VLRs only."""

482

return (vlr.user_id == "LASF_Projection" and

483

vlr.record_id in {34735, 34736, 34737, 2112})

484

485

def custom_vlr_filter(vlr):

486

"""Filter for custom application VLRs only."""

487

return vlr.user_id not in {"LASF_Projection"}

488

489

# Copy only CRS information

490

copy_vlrs_between_files('source.las', 'target.las', crs_vlr_filter)

491

492

# Copy only custom VLRs

493

copy_vlrs_between_files('source.las', 'target.las', custom_vlr_filter)

494

```