Skip to content

Commit cc21047

Browse files
committed
postprocess: collision_ranker
1 parent a542dd2 commit cc21047

File tree

10 files changed

+1564
-7
lines changed

10 files changed

+1564
-7
lines changed

config/spreadsheets/collision_rank.yaml

+1,024
Large diffs are not rendered by default.

embeddedconfig/config.go

+23
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package integrationtests
2+
3+
import (
4+
"testing"
5+
6+
"github.com/paulmach/osm"
7+
)
8+
9+
func TestCollisionRank(t *testing.T) {
10+
cases := []struct {
11+
name string
12+
tags osm.Tags
13+
rank int
14+
}{
15+
{
16+
name: "beach",
17+
tags: osm.Tags{
18+
{Key: "natural", Value: "beach"},
19+
{Key: "name", Value: "Stinson Beach"},
20+
},
21+
rank: 534,
22+
},
23+
{
24+
name: "population",
25+
tags: osm.Tags{
26+
{Key: "name", Value: "Berkeley"},
27+
{Key: "population", Value: "120000"},
28+
{Key: "place", Value: "city"},
29+
},
30+
rank: 350,
31+
},
32+
{
33+
name: "population",
34+
tags: osm.Tags{
35+
{Key: "name", Value: "Berkeley"},
36+
{Key: "population", Value: "210000"},
37+
{Key: "place", Value: "city"},
38+
},
39+
rank: 347,
40+
},
41+
{
42+
name: "building exit",
43+
tags: osm.Tags{
44+
{Key: "name", Value: "exit"},
45+
{Key: "entrance", Value: "fire_exit"},
46+
},
47+
rank: 4303,
48+
},
49+
{
50+
name: "no rank if no name",
51+
tags: osm.Tags{
52+
{Key: "building", Value: "true"},
53+
},
54+
rank: 0,
55+
},
56+
}
57+
58+
for _, tc := range cases {
59+
t.Run(tc.name, func(t *testing.T) {
60+
data := &osm.OSM{
61+
Nodes: osm.Nodes{{ID: 1, Visible: true, Version: 1, Tags: tc.tags}},
62+
}
63+
64+
// run the request
65+
tile := processOSM(t, data)
66+
67+
for name, layer := range tile {
68+
for _, feature := range layer.Features {
69+
r := feature.Properties.MustInt("collision_rank", 0)
70+
if r != tc.rank {
71+
t.Errorf("layer %v: incorrect rank: %v != %v", name, r, tc.rank)
72+
}
73+
}
74+
}
75+
})
76+
}
77+
}

load.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
)
1515

1616
// to copy the yaml data into the binary for easier loading.
17-
//go:generate go-bindata -pkg embeddedconfig -o embeddedconfig/config.go -prefix=config config/queries.yaml config/yaml/ config/spreadsheets/scale_rank/ config/spreadsheets/sort_rank/
17+
//go:generate go-bindata -pkg embeddedconfig -o embeddedconfig/config.go -prefix=config config/queries.yaml config/yaml/ config/spreadsheets/ config/spreadsheets/scale_rank/ config/spreadsheets/sort_rank/
1818
//go:generate gofmt -w embeddedconfig/config.go
1919

2020
// Config is the full queries.yaml config file for this library.

postprocess/functions.go

+75-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/paulmach/osmzen/filter"
99
"github.com/paulmach/osmzen/matcher"
10+
"github.com/paulmach/osmzen/ranker"
1011

1112
"github.com/paulmach/orb"
1213
"github.com/paulmach/orb/clip/smartclip"
@@ -63,7 +64,7 @@ var functions = map[string]func(*CompileContext, *Config) (Function, error){
6364
"whitelist": compileWhitelist,
6465
"quantize_height": compileQuantizeHeight,
6566
"clamp_min_zoom": compileClampMinZoom,
66-
"add_collision_rank": nil, // TODO, yes
67+
"add_collision_rank": compileAddCollisionRank,
6768
}
6869

6970
var (
@@ -206,6 +207,67 @@ func compileCSVMatchProperties(ctx *CompileContext, c *Config) (Function, error)
206207
}, nil
207208
}
208209

210+
type addCollisionRank struct {
211+
Ranker *ranker.Ranker
212+
}
213+
214+
func (f *addCollisionRank) Eval(ctx *Context, layers map[string]*geojson.FeatureCollection) {
215+
for name, layer := range layers {
216+
for _, feature := range layer.Features {
217+
218+
// hard coded version of the where clause in queries.yaml
219+
add := false
220+
if name == "pois" || hasName(feature) {
221+
add = true
222+
} else if _, ok := feature.Properties["ref"]; ok {
223+
add = true
224+
} else if _, ok := feature.Properties["shield_text"]; ok {
225+
add = true
226+
} else if _, ok := feature.Properties["bicycle_shield_text"]; ok {
227+
add = true
228+
} else if _, ok := feature.Properties["bus_shield_text"]; ok {
229+
add = true
230+
} else if _, ok := feature.Properties["walking_shield_text"]; ok {
231+
add = true
232+
} else if _, ok := feature.Properties["bicycle_shield_text"]; ok {
233+
add = true
234+
}
235+
236+
if add {
237+
rank := f.Ranker.Rank(name, feature.Properties)
238+
feature.Properties["collision_rank"] = rank
239+
}
240+
}
241+
}
242+
}
243+
244+
func compileAddCollisionRank(ctx *CompileContext, c *Config) (Function, error) {
245+
data, err := ctx.Asset(c.Resources.Ranker.Path)
246+
if err != nil {
247+
return nil, err
248+
}
249+
250+
r, err := ranker.Load(data)
251+
if err != nil {
252+
return nil, err
253+
}
254+
255+
where := "layer_name == 'pois' or " +
256+
"_has_name or " +
257+
"ref is not None or " +
258+
"shield_text is not None or " +
259+
"bicycle_shield_text is not None or " +
260+
"bus_shield_text is not None or " +
261+
"walking_shield_text is not None"
262+
if where != c.Params["where"] {
263+
return nil, errors.Errorf("add_collision_rank: where has changed, it's hard coded")
264+
}
265+
266+
return &addCollisionRank{
267+
Ranker: r,
268+
}, nil
269+
}
270+
209271
type handleLabelPlacement struct {
210272
Layers []string
211273
ClipFactors map[string]float64
@@ -579,7 +641,7 @@ func keyIsName(key string) bool {
579641
}
580642

581643
// then any of the alternative forms of name
582-
tagTameAlternates := []string{
644+
tagNameAlternates := []string{
583645
"int_name",
584646
"loc_name",
585647
"nat_name",
@@ -592,7 +654,7 @@ func keyIsName(key string) bool {
592654
"name:short",
593655
}
594656

595-
for _, alt := range tagTameAlternates {
657+
for _, alt := range tagNameAlternates {
596658
if strings.HasPrefix(key, alt) {
597659
return true
598660
}
@@ -601,6 +663,16 @@ func keyIsName(key string) bool {
601663
return false
602664
}
603665

666+
func hasName(feature *geojson.Feature) bool {
667+
for k := range feature.Properties {
668+
if keyIsName(k) {
669+
return true
670+
}
671+
}
672+
673+
return false
674+
}
675+
604676
type dropNames struct {
605677
Layer string
606678
StartZoom float64

postprocess/postprocess.go

+5
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ type Config struct {
4141
InitFunc string `yaml:"init_fn"`
4242
Path string `yaml:"path"`
4343
} `yaml:"matcher"`
44+
Ranker struct {
45+
Type string `yaml:"type"`
46+
InitFunc string `yaml:"init_fn"`
47+
Path string `yaml:"path"`
48+
} `yaml:"ranker"`
4449
} `yaml:"resources"`
4550
Params map[interface{}]interface{} `yaml:"params"`
4651
}

ranker/condition.go

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package ranker
2+
3+
import "github.com/pkg/errors"
4+
5+
// Condition is a matcher in the YAML file that can be evaluated.
6+
// It is not as full featured as filter.Condition but differs in that
7+
// it takes a string->interface{} map vs. a string->string.
8+
type Condition interface {
9+
Eval(map[string]interface{}) bool
10+
}
11+
12+
// CompileCondition compiles the matcher in the collision_rank YAML file so
13+
// it can be evaluated. YAML will decompile hashes into this map with
14+
// interface{} keys. We assume they are always strings.
15+
func CompileCondition(cond map[interface{}]interface{}) (Condition, error) {
16+
conds := []Condition{}
17+
for key, val := range cond {
18+
switch key {
19+
case "not":
20+
c, err := CompileCondition(val.(map[interface{}]interface{}))
21+
if err != nil {
22+
return nil, err
23+
}
24+
25+
conds = append(conds, &notCond{cond: c})
26+
default:
27+
// we want to only support comparable types. If the file adds
28+
// nested hashes we need to do more work.
29+
if _, ok := val.(map[interface{}]interface{}); ok {
30+
return nil, errors.Errorf("compile: key %v is a hash", key)
31+
}
32+
33+
conds = append(conds, &eqCond{
34+
Key: key.(string),
35+
Val: val,
36+
})
37+
}
38+
}
39+
40+
if len(conds) == 1 {
41+
return conds[0], nil
42+
}
43+
44+
return &allCond{conds: conds}, nil
45+
}
46+
47+
type eqCond struct {
48+
Key string
49+
Val interface{}
50+
}
51+
52+
func (c *eqCond) Eval(vals map[string]interface{}) bool {
53+
return vals[c.Key] == c.Val
54+
}
55+
56+
type notCond struct {
57+
cond Condition
58+
}
59+
60+
func (c *notCond) Eval(vals map[string]interface{}) bool {
61+
return !c.cond.Eval(vals)
62+
}
63+
64+
type allCond struct {
65+
conds []Condition
66+
}
67+
68+
func (c *allCond) Eval(vals map[string]interface{}) bool {
69+
for _, c := range c.conds {
70+
if !c.Eval(vals) {
71+
return false
72+
}
73+
}
74+
75+
return true
76+
}

0 commit comments

Comments
 (0)