Skip to content

Commit d98aadc

Browse files
committed
encoding/mvt: use protoscan for unmarshalling
benchmark old ns/op new ns/op delta BenchmarkUnmarshal-12 367590 246198 -33.02% benchmark old allocs new allocs delta BenchmarkUnmarshal-12 6629 2476 -62.65% benchmark old bytes new bytes delta BenchmarkUnmarshal-12 372954 211681 -43.24%
1 parent bc42fc7 commit d98aadc

File tree

7 files changed

+547
-284
lines changed

7 files changed

+547
-284
lines changed

encoding/mvt/geometry.go

Lines changed: 0 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -140,170 +140,6 @@ type keyValueEncoder struct {
140140
valueMap map[interface{}]uint32
141141
}
142142

143-
// A geomDecoder holds state for geometry decoding.
144-
type geomDecoder struct {
145-
geom []uint32
146-
i int
147-
148-
prev orb.Point
149-
}
150-
151-
func decodeGeometry(geomType vectortile.Tile_GeomType, geom []uint32) (orb.Geometry, error) {
152-
if len(geom) < 2 {
153-
return nil, errors.Errorf("geom is not long enough: %v", geom)
154-
}
155-
156-
gd := &geomDecoder{geom: geom}
157-
158-
switch geomType {
159-
case vectortile.Tile_POINT:
160-
return gd.decodePoint()
161-
case vectortile.Tile_LINESTRING:
162-
return gd.decodeLineString()
163-
case vectortile.Tile_POLYGON:
164-
return gd.decodePolygon()
165-
}
166-
167-
return nil, errors.Errorf("unknown geometry type: %v", geomType)
168-
}
169-
170-
func (gd *geomDecoder) decodePoint() (orb.Geometry, error) {
171-
_, count, err := gd.cmdAndCount()
172-
if err != nil {
173-
return nil, err
174-
}
175-
176-
if count == 1 {
177-
return gd.NextPoint(), nil
178-
}
179-
180-
mp := make(orb.MultiPoint, 0, count)
181-
for i := uint32(0); i < count; i++ {
182-
mp = append(mp, gd.NextPoint())
183-
}
184-
185-
return mp, nil
186-
}
187-
188-
func (gd *geomDecoder) decodeLine() (orb.LineString, error) {
189-
cmd, count, err := gd.cmdAndCount()
190-
if err != nil {
191-
return nil, err
192-
}
193-
194-
if cmd != moveTo || count != 1 {
195-
return nil, errors.New("first command not one moveTo")
196-
}
197-
198-
first := gd.NextPoint()
199-
cmd, count, err = gd.cmdAndCount()
200-
if err != nil {
201-
return nil, err
202-
}
203-
204-
if cmd != lineTo {
205-
return nil, errors.New("second command not a lineTo")
206-
}
207-
208-
ls := make(orb.LineString, 0, count+1)
209-
ls = append(ls, first)
210-
211-
for i := uint32(0); i < count; i++ {
212-
ls = append(ls, gd.NextPoint())
213-
}
214-
215-
return ls, nil
216-
}
217-
218-
func (gd *geomDecoder) decodeLineString() (orb.Geometry, error) {
219-
var mls orb.MultiLineString
220-
for !gd.done() {
221-
ls, err := gd.decodeLine()
222-
if err != nil {
223-
return nil, err
224-
}
225-
226-
if gd.done() && len(mls) == 0 {
227-
return ls, nil
228-
}
229-
230-
mls = append(mls, ls)
231-
}
232-
233-
return mls, nil
234-
}
235-
236-
func (gd *geomDecoder) decodePolygon() (orb.Geometry, error) {
237-
var mp orb.MultiPolygon
238-
var p orb.Polygon
239-
for !gd.done() {
240-
ls, err := gd.decodeLine()
241-
if err != nil {
242-
return nil, err
243-
}
244-
245-
r := orb.Ring(ls)
246-
247-
cmd, _, err := gd.cmdAndCount()
248-
if err != nil {
249-
return nil, err
250-
}
251-
252-
if cmd == closePath && !r.Closed() {
253-
r = append(r, r[0])
254-
}
255-
256-
// figure out if new polygon
257-
if len(mp) == 0 && len(p) == 0 {
258-
p = append(p, r)
259-
} else {
260-
if r.Orientation() == orb.CCW {
261-
mp = append(mp, p)
262-
p = orb.Polygon{r}
263-
} else {
264-
p = append(p, r)
265-
}
266-
}
267-
}
268-
269-
if len(mp) == 0 {
270-
return p, nil
271-
}
272-
273-
return append(mp, p), nil
274-
}
275-
276-
func (gd *geomDecoder) cmdAndCount() (uint32, uint32, error) {
277-
if gd.i >= len(gd.geom) {
278-
return 0, 0, errors.New("no more data")
279-
}
280-
281-
v := gd.geom[gd.i]
282-
283-
cmd := v & 0x07
284-
count := v >> 3
285-
gd.i++
286-
287-
if cmd != closePath {
288-
if v := gd.i + int(2*count); len(gd.geom) < v {
289-
return 0, 0, errors.Errorf("data cut short: needed %d, have %d", v, len(gd.geom))
290-
}
291-
}
292-
293-
return cmd, count, nil
294-
}
295-
296-
func (gd *geomDecoder) NextPoint() orb.Point {
297-
gd.i += 2
298-
gd.prev[0] += unzigzag(gd.geom[gd.i-2])
299-
gd.prev[1] += unzigzag(gd.geom[gd.i-1])
300-
return gd.prev
301-
}
302-
303-
func (gd *geomDecoder) done() bool {
304-
return gd.i >= len(gd.geom)
305-
}
306-
307143
func newKeyValueEncoder() *keyValueEncoder {
308144
return &keyValueEncoder{
309145
keyMap: make(map[string]uint32),

encoding/mvt/geometry_test.go

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/paulmach/orb"
99
"github.com/paulmach/orb/encoding/mvt/vectortile"
10+
"github.com/paulmach/protoscan"
1011
)
1112

1213
func TestGeometry_Point(t *testing.T) {
@@ -322,7 +323,8 @@ func compareGeometry(
322323
t.Errorf("different encoding")
323324
}
324325

325-
result, err := decodeGeometry(geomType, input)
326+
d := &decoder{geom: sliceToIterator(input)}
327+
result, err := d.Geometry(geomType)
326328
if err != nil {
327329
t.Fatalf("decode error: %v", err)
328330
}
@@ -337,3 +339,31 @@ func compareGeometry(
337339
t.Errorf("geometry not equal")
338340
}
339341
}
342+
343+
func sliceToIterator(vals []uint32) *protoscan.Iterator {
344+
feature := &vectortile.Tile_Feature{
345+
Geometry: vals,
346+
}
347+
348+
data, err := feature.Marshal()
349+
if err != nil {
350+
panic(err)
351+
}
352+
353+
msg := protoscan.New(data)
354+
for msg.Next() {
355+
switch msg.FieldNumber() {
356+
case 4:
357+
iter, err := msg.Iterator(nil)
358+
if err != nil {
359+
panic(err)
360+
}
361+
362+
return iter
363+
default:
364+
msg.Skip()
365+
}
366+
}
367+
368+
panic("unreachable")
369+
}

encoding/mvt/marshal.go

Lines changed: 0 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"bytes"
55
"compress/gzip"
66
"fmt"
7-
"io/ioutil"
87
"strconv"
98

109
"github.com/paulmach/orb/encoding/mvt/vectortile"
@@ -84,72 +83,6 @@ func Marshal(layers Layers) ([]byte, error) {
8483
return proto.Marshal(vt)
8584
}
8685

87-
// UnmarshalGzipped takes gzipped Mapbox Vector Tile (MVT) data and unzips it
88-
// before decoding it into a set of layers, It does not project the coordinates.
89-
func UnmarshalGzipped(data []byte) (Layers, error) {
90-
gzreader, err := gzip.NewReader(bytes.NewBuffer(data))
91-
if err != nil {
92-
return nil, errors.WithMessage(err, "failed to create gzreader")
93-
}
94-
95-
decoded, err := ioutil.ReadAll(gzreader)
96-
if err != nil {
97-
return nil, errors.WithMessage(err, "failed to unzip")
98-
}
99-
100-
return Unmarshal(decoded)
101-
}
102-
103-
// Unmarshal takes Mapbox Vector Tile (MVT) data and converts into a
104-
// set of layers, It does not project the coordinates.
105-
func Unmarshal(data []byte) (Layers, error) {
106-
vt := &vectortile.Tile{}
107-
err := vt.Unmarshal(data)
108-
if err != nil {
109-
return nil, err
110-
}
111-
112-
return decode(vt)
113-
}
114-
115-
func decode(vt *vectortile.Tile) (Layers, error) {
116-
result := make(Layers, 0, len(vt.Layers))
117-
for i, l := range vt.Layers {
118-
layer := &Layer{
119-
Name: l.GetName(),
120-
Version: l.GetVersion(),
121-
Extent: l.GetExtent(),
122-
Features: make([]*geojson.Feature, 0, len(l.Features)),
123-
}
124-
125-
for j, f := range l.Features {
126-
geom, err := decodeGeometry(f.GetType(), f.Geometry)
127-
if err != nil {
128-
return nil, errors.WithMessage(err, fmt.Sprintf("layer %d: feature %d", i, j))
129-
}
130-
131-
properties := decodeFeatureProperties(l.Keys, l.Values, f.Tags)
132-
133-
if geom != nil {
134-
gjf := &geojson.Feature{
135-
Geometry: geom,
136-
Properties: properties,
137-
}
138-
139-
if f.Id != nil {
140-
gjf.ID = float64(*f.Id)
141-
}
142-
143-
layer.Features = append(layer.Features, gjf)
144-
}
145-
}
146-
147-
result = append(result, layer)
148-
}
149-
150-
return result, nil
151-
}
152-
15386
func encodeProperties(kve *keyValueEncoder, properties geojson.Properties) ([]uint32, error) {
15487
tags := make([]uint32, 0, 2*len(properties))
15588
for k, v := range properties {
@@ -165,36 +98,6 @@ func encodeProperties(kve *keyValueEncoder, properties geojson.Properties) ([]ui
16598
return tags, nil
16699
}
167100

168-
func decodeFeatureProperties(
169-
keys []string,
170-
values []*vectortile.Tile_Value,
171-
tags []uint32,
172-
) geojson.Properties {
173-
result := make(geojson.Properties, len(tags)/2)
174-
if len(tags) == 0 {
175-
return result
176-
}
177-
178-
for i := 2; i <= len(tags); i += 2 {
179-
vi := tags[i-1]
180-
if int(vi) >= len(values) {
181-
continue
182-
}
183-
184-
v := decodeValue(values[vi])
185-
if v != nil {
186-
ti := tags[i-2]
187-
if int(ti) >= len(keys) {
188-
continue
189-
}
190-
191-
result[keys[ti]] = v
192-
}
193-
}
194-
195-
return result
196-
}
197-
198101
func convertID(id interface{}) *uint64 {
199102
if id == nil {
200103
return nil

encoding/mvt/marshal_test.go

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"testing"
1010

1111
"github.com/paulmach/orb"
12-
"github.com/paulmach/orb/encoding/mvt/vectortile"
1312
"github.com/paulmach/orb/geojson"
1413
"github.com/paulmach/orb/maptile"
1514
)
@@ -484,27 +483,6 @@ func BenchmarkUnmarshal(b *testing.B) {
484483
}
485484
}
486485

487-
func BenchmarkDecode(b *testing.B) {
488-
layers := NewLayers(loadGeoJSON(b, maptile.New(17896, 24449, 16)))
489-
data, err := Marshal(layers)
490-
if err != nil {
491-
b.Fatalf("marshal error: %v", err)
492-
}
493-
494-
vt := &vectortile.Tile{}
495-
err = vt.Unmarshal(data)
496-
if err != nil {
497-
b.Fatalf("unmarshal error: %v", err)
498-
}
499-
500-
b.ReportAllocs()
501-
b.ResetTimer()
502-
503-
for i := 0; i < b.N; i++ {
504-
decode(vt)
505-
}
506-
}
507-
508486
func BenchmarkProjectToTile(b *testing.B) {
509487
tile := maptile.New(17896, 24449, 16)
510488
layers := NewLayers(loadGeoJSON(b, maptile.New(17896, 24449, 16)))

0 commit comments

Comments
 (0)