-
Notifications
You must be signed in to change notification settings - Fork 3
/
shxheader.go
130 lines (118 loc) · 3.5 KB
/
shxheader.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
package shapefile
import (
"encoding/binary"
"errors"
"io"
"math"
"github.com/twpayne/go-geom"
)
// A SHxHeader is header of a .shp or .shx file.
type SHxHeader struct {
ShapeType ShapeType
Bounds *geom.Bounds
}
// readSHxHeader reads a SHxHeader from an io.Reader.
func readSHxHeader(r io.Reader, fileLength int64) (*SHxHeader, error) {
if fileLength < headerSize {
return nil, errors.New("file too short")
}
data := make([]byte, headerSize)
if err := readFull(r, data); err != nil {
return nil, err
}
return parseSHxHeader(data, fileLength)
}
// parseSHxHeader parses a SHxHeader from data.
func parseSHxHeader(data []byte, fileLength int64) (*SHxHeader, error) {
if len(data) != headerSize {
return nil, errors.New("invalid header length")
}
if headerFileCode := binary.BigEndian.Uint32(data[:4]); headerFileCode != fileCode {
return nil, errors.New("invalid file code")
}
if headerFileLength := 2 * int64(binary.BigEndian.Uint32(data[24:28])); headerFileLength != fileLength {
return nil, errors.New("invalid file length")
}
if headerVersion := binary.LittleEndian.Uint32(data[28:32]); headerVersion != version {
return nil, errors.New("invalid header version")
}
shapeType := ShapeType(binary.LittleEndian.Uint32(data[32:36]))
if _, validShapeType := validShapeTypes[shapeType]; !validShapeType {
return nil, errors.New("invalid shape type")
}
if _, unsupportedShapeType := unsupportedShapeTypes[shapeType]; unsupportedShapeType {
return nil, errors.New("unsupported shape type")
}
minX := math.Float64frombits(binary.LittleEndian.Uint64(data[36:44]))
minY := math.Float64frombits(binary.LittleEndian.Uint64(data[44:52]))
maxX := math.Float64frombits(binary.LittleEndian.Uint64(data[52:60]))
maxY := math.Float64frombits(binary.LittleEndian.Uint64(data[60:68]))
minZ := math.Float64frombits(binary.LittleEndian.Uint64(data[68:76]))
maxZ := math.Float64frombits(binary.LittleEndian.Uint64(data[76:84]))
minM := math.Float64frombits(binary.LittleEndian.Uint64(data[84:92]))
maxM := math.Float64frombits(binary.LittleEndian.Uint64(data[92:100]))
if NoData(minX) {
minX = math.Inf(1)
}
if NoData(minY) {
minY = math.Inf(1)
}
if NoData(maxX) {
maxX = math.Inf(-1)
}
if NoData(maxY) {
maxY = math.Inf(-1)
}
var bounds *geom.Bounds
switch shapeType {
case ShapeTypeNull:
case ShapeTypePoint, ShapeTypeMultiPoint, ShapeTypePolyLine, ShapeTypePolygon:
bounds = geom.NewBounds(geom.XY).Set(minX, minY, maxX, maxY)
case ShapeTypePointM, ShapeTypeMultiPointM, ShapeTypePolyLineM, ShapeTypePolygonM:
if NoData(minM) {
minM = math.Inf(1)
}
if NoData(maxM) {
maxM = math.Inf(-1)
}
bounds = geom.NewBounds(geom.XYM).Set(minX, minY, minM, maxX, maxY, maxM)
case ShapeTypePointZ, ShapeTypeMultiPointZ, ShapeTypePolyLineZ, ShapeTypePolygonZ:
if NoData(minM) {
minM = math.Inf(1)
}
if NoData(maxM) {
maxM = math.Inf(-1)
}
if NoData(minZ) {
minZ = math.Inf(1)
}
if NoData(maxZ) {
maxZ = math.Inf(-1)
}
bounds = geom.NewBounds(geom.XYZM).Set(minX, minY, minZ, minM, maxX, maxY, maxZ, maxM)
}
return &SHxHeader{
ShapeType: shapeType,
Bounds: bounds,
}, nil
}
// NoData returns if x represents no data.
func NoData(x float64) bool {
return x <= -1e38
}
func readFull(r io.Reader, data []byte) error {
for {
switch n, err := r.Read(data); {
case errors.Is(err, io.EOF) && n == len(data):
return nil
case err != nil:
return err
case n == 0:
return io.ErrUnexpectedEOF
case n < len(data):
data = data[n:]
default:
return nil
}
}
}