forked from mfbonfigli/gocesiumtiler
-
Notifications
You must be signed in to change notification settings - Fork 0
/
tiler.go
152 lines (138 loc) · 5.68 KB
/
tiler.go
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
package tiler
import (
"context"
"fmt"
"path/filepath"
"strings"
"time"
"github.com/mfbonfigli/gocesiumtiler/v2/internal/conv/coor"
"github.com/mfbonfigli/gocesiumtiler/v2/internal/conv/coor/proj4"
"github.com/mfbonfigli/gocesiumtiler/v2/internal/conv/elev"
"github.com/mfbonfigli/gocesiumtiler/v2/internal/conv/elev/geoid2ellipsoid"
"github.com/mfbonfigli/gocesiumtiler/v2/internal/las"
"github.com/mfbonfigli/gocesiumtiler/v2/internal/tree"
"github.com/mfbonfigli/gocesiumtiler/v2/internal/utils"
"github.com/mfbonfigli/gocesiumtiler/v2/internal/writer"
)
type Tiler interface {
ProcessFiles(inputLasFiles []string, outputFolder string, epsgCode int, opts *TilerOptions, ctx context.Context) error
ProcessFolder(inputFolder, outputFolder string, epsgCode int, opts *TilerOptions, ctx context.Context) error
}
// GoCesiumTiler wraps the logic required to convert
// LAS point clouds into Cesium 3D tiles
type GoCesiumTiler struct {
cconv coor.CoordinateConverter
treeProvider
writerProvider
lasReaderProvider
}
type treeProvider func(opts *TilerOptions) tree.Tree
type writerProvider func(folder string, c coor.CoordinateConverter, opts *TilerOptions) (writer.Writer, error)
type lasReaderProvider func(inputLasFiles []string, epsgCode int, eightbit bool) (las.LasReader, error)
// NewGoCesiumTiler returns a new tiler to be used to convert LAS files into Cesium 3D Tiles
func NewGoCesiumTiler() (*GoCesiumTiler, error) {
cconv, err := proj4.NewProj4CoordinateConverter()
if err != nil {
return nil, err
}
return &GoCesiumTiler{
cconv: cconv,
treeProvider: func(opts *TilerOptions) tree.Tree {
return tree.NewGridTree(
tree.WithGridSize(opts.gridSize),
tree.WithMaxDepth(opts.maxDepth),
tree.WithLoadWorkersNumber(opts.numWorkers),
tree.WithMinPointsPerChildren(opts.minPointsPerTile),
)
},
writerProvider: func(folder string, c coor.CoordinateConverter, opts *TilerOptions) (writer.Writer, error) {
return writer.NewWriter(folder, c,
writer.WithNumWorkers(opts.numWorkers),
writer.WithTilesetVersion(opts.version),
)
},
lasReaderProvider: func(inputLasFiles []string, epsgCode int, eightbit bool) (las.LasReader, error) {
return las.NewCombinedFileLasReader(inputLasFiles, epsgCode, eightbit)
},
}, nil
}
// ProcessFolder converts all LAS files found in the provided input folder converting them into separate tilesets
// each tileset is stored in a subdirectory in the outputFolder named after the filename
func (t *GoCesiumTiler) ProcessFolder(inputFolder, outputFolder string, epsgCode int, opts *TilerOptions, ctx context.Context) error {
files, err := utils.FindLasFilesInFolder(inputFolder)
if err != nil {
return err
}
for _, f := range files {
subfolderName := strings.TrimSuffix(filepath.Base(f), filepath.Ext(f))
err := t.ProcessFiles([]string{f}, filepath.Join(outputFolder, subfolderName), epsgCode, opts, ctx)
if err != nil {
return err
}
}
return nil
}
// ProcessFiles converts the specified LAS files as a single cesium tileset and stores them in the
func (t *GoCesiumTiler) ProcessFiles(inputLasFiles []string, outputFolder string, epsgCode int, opts *TilerOptions, ctx context.Context) error {
start := time.Now()
tr := t.treeProvider(opts)
inputDesc := fmt.Sprintf("%d files", len(inputLasFiles))
if len(inputLasFiles) == 1 {
inputDesc = inputLasFiles[0]
}
// PARSE LAS HEADER
emitEvent(EventReadLasHeaderStarted, opts, start, inputDesc, "start reading las")
lasFile, err := t.lasReaderProvider(inputLasFiles, epsgCode, opts.eightBitColors)
if err != nil {
emitEvent(EventReadLasHeaderError, opts, start, inputDesc, fmt.Sprintf("las read error: %v", err))
return err
}
emitEvent(EventReadLasHeaderCompleted, opts, start, inputDesc, fmt.Sprintf("las header read completed: found %d points", lasFile.NumberOfPoints()))
// LOAD POINTS
emitEvent(EventPointLoadingStarted, opts, start, inputDesc, "point loading started")
elevationConverters := []elev.ElevationConverter{
elev.NewOffsetElevationConverter(opts.elevationOffset),
}
if opts.geoidElevation {
egmCalc, err := geoid2ellipsoid.NewEGMCalculator(t.cconv)
if err != nil {
emitEvent(EventPointLoadingError, opts, start, inputDesc, fmt.Sprintf("converter init error: %v", err))
return err
}
elevationConverters = append(elevationConverters, elev.NewGeoidElevationConverter(epsgCode, egmCalc))
}
eConv := elev.NewPipelineElevationCorrector(elevationConverters...)
err = tr.Load(lasFile, t.cconv, eConv, ctx)
if err != nil {
emitEvent(EventPointLoadingError, opts, start, inputDesc, fmt.Sprintf("load error: %v", err))
return err
}
emitEvent(EventPointLoadingCompleted, opts, start, inputDesc, "point loading completed")
// BUILD TREE
emitEvent(EventBuildStarted, opts, start, inputDesc, "build started")
err = tr.Build()
if err != nil {
emitEvent(EventBuildError, opts, start, inputDesc, fmt.Sprintf("build error: %v", err))
return err
}
emitEvent(EventBuildCompleted, opts, start, inputDesc, "build completed")
// EXPORT
emitEvent(EventExportStarted, opts, start, inputDesc, "export started")
w, err := t.writerProvider(outputFolder, t.cconv, opts)
if err != nil {
emitEvent(EventBuildError, opts, start, inputDesc, fmt.Sprintf("export init error: %v", err))
return err
}
err = w.Write(tr, "", ctx)
if err != nil {
emitEvent(EventBuildError, opts, start, inputDesc, fmt.Sprintf("export error: %v", err))
return err
}
emitEvent(EventExportStarted, opts, start, inputDesc, fmt.Sprintf("export completed in %v seconds", time.Since(start).String()))
return nil
}
func emitEvent(e TilerEvent, opts *TilerOptions, start time.Time, inputDesc string, msg string) {
if opts.callback != nil {
opts.callback(e, inputDesc, time.Since(start).Milliseconds(), msg)
}
}