model.go
1package graphhopper
2
3import (
4 "encoding/json"
5 "fmt"
6 "strconv"
7 "strings"
8)
9
10// Custom model structs do minimal error checking to help construct a valid model
11// profile. Prefer functional constructors.
12
13type Model struct {
14 Speed []Rule `json:"speed,omitempty"`
15 Priority []Rule `json:"priority,omitempty"`
16}
17
18type ModelOption func(m *Model)
19
20func WithSpeedRules(rules ...Rule) ModelOption {
21 return func(m *Model) {
22 m.Speed = rules
23 }
24}
25func WithPriorityRules(rules ...Rule) ModelOption {
26 return func(m *Model) {
27 m.Priority = rules
28 }
29}
30
31func NewModel(opts ...ModelOption) Model {
32 m := &Model{}
33 for _, opt := range opts {
34 opt(m)
35 }
36 return *m
37}
38
39type Rule interface {
40 rule() bool
41}
42
43type IfRule struct {
44 If string `json:"if"`
45 // only one of these
46 LimitTo string `json:"limit_to,omitempty"`
47 MultiplyBy string `json:"multiply_by,omitempty"`
48}
49
50func (r IfRule) rule() bool { return true }
51
52type ElseIfRule struct {
53 ElseIf string `json:"else_if"`
54 // only one of these
55 LimitTo string `json:"limit_to,omitempty"`
56 MultiplyBy string `json:"multiply_by,omitempty"`
57}
58
59func (r ElseIfRule) rule() bool { return true }
60
61type ElseRule struct {
62 // only one of these
63 LimitTo string `json:"limit_to,omitempty"`
64 MultiplyBy string `json:"multiply_by,omitempty"`
65}
66
67func (r ElseRule) rule() bool { return true }
68
69// constructs an else condition like {else: "", multiply_by: "0.1"}
70type _else struct {
71 Else string `json:"else"`
72 LimitTo string `json:"limit_to,omitempty"`
73 MultiplyBy string `json:"multiply_by,omitempty"`
74}
75
76func (e ElseRule) MarshalJSON() ([]byte, error) {
77 if !checkEx(e.LimitTo, e.MultiplyBy) {
78 return nil, fmt.Errorf("invalid rule: got limit_to=%s, multiply_by=%s", e.LimitTo, e.MultiplyBy)
79 }
80 return json.Marshal(_else{LimitTo: e.LimitTo, MultiplyBy: e.MultiplyBy})
81}
82
83func checkEx(a, b string) bool {
84 return (a == "" || b == "") && (a != "" || b != "")
85}
86
87// OSM Road classes from fastest to slowest
88// https://wiki.openstreetmap.org/wiki/Kaart:_Road_Classifications_Guide
89type RoadClass string
90
91const (
92 Motorway RoadClass = "MOTORWAY"
93 Trunk RoadClass = "TRUNK"
94 Prmary RoadClass = "PRIMARY"
95 Secondary RoadClass = "SECONDARY"
96 Tertiary RoadClass = "TERTIARY"
97 Unclassified RoadClass = "UNCLASSIFIED"
98 Residential RoadClass = "RESIDENTIAL"
99 LivingStreet RoadClass = "LIVING_STREET"
100 Service RoadClass = "SERVICE"
101 Track RoadClass = "TRACK"
102)
103
104// IsHighway includes Motorway and Trunk (two fastest roadways)
105func IsHighway() Condition {
106 return Condition(fmt.Sprintf("road_class == %s || road_class == %s", Motorway, Trunk))
107}
108
109// IsOffroad is a track or an unpaved road
110func IsOffroad() Condition {
111 return Condition("road_class == TRACK || " + IsUnpaved())
112}
113
114// OSM surface types
115// https://wiki.openstreetmap.org/wiki/Key:surface
116type Surface string
117
118const (
119 // paved types
120 Paved Surface = "PAVED"
121 Asphalt Surface = "ASPHALT"
122 Chipseal Surface = "CHIPSEAL"
123 Concrete Surface = "CONCRETE"
124 // unpaved types
125 Unpaved Surface = "UNPAVED"
126 Compacted Surface = "COMPACTED"
127 FineGravel Surface = "FINE_GRAVEL"
128 Gravel Surface = "GRAVEL"
129 Rock Surface = "ROCK"
130 Pebblestone Surface = "PEBBLESTONE"
131 Ground Surface = "GROUND"
132 Dirt Surface = "DIRT"
133 Earth Surface = "EARTH"
134 Grass Surface = "GRASS"
135 Mud Surface = "MUD"
136 Sand Surface = "SAND"
137 Woodchips Surface = "WOODCHIPS"
138)
139
140// Condition constructs if statements (e.g., surface == PAVED)
141type Condition string
142
143// IsPaved is a shortcut for all paved surface types
144func IsPaved() Condition {
145 out := []string{}
146 for _, s := range []Surface{Paved, Asphalt, Chipseal, Concrete} {
147 out = append(out, fmt.Sprintf("surface == %s", s))
148 }
149 return Condition(strings.Join(out, " || "))
150}
151
152// IsUnpaved is all unpaved surface types (incl. sand, mud)
153func IsUnpaved() Condition {
154 out := []string{}
155 for _, s := range []Surface{Unpaved, Compacted, FineGravel, Rock, Pebblestone, Ground, Dirt, Earth, Grass, Mud, Sand, Woodchips} {
156 out = append(out, fmt.Sprintf("surface == %s", s))
157 }
158 return Condition(strings.Join(out, " || "))
159}
160
161// Do helps construct valid multiply_by and limit_to values
162type Do struct {
163 field string
164 value string
165}
166
167// Multiply by value
168func MultiplyBy(value float64) Do {
169 return Do{
170 field: "multiply_by",
171 value: fmt.Sprintf("%.1f", value),
172 }
173}
174
175// Limit to integer value
176func LimitTo(value int) Do {
177 return Do{
178 field: "limit_to",
179 value: strconv.Itoa(value),
180 }
181}
182
183// If constructs valid model rules
184func If(c Condition, d Do) IfRule {
185 switch d.field {
186 case "multiply_by":
187 return IfRule{If: string(c), MultiplyBy: d.value}
188 default:
189 return IfRule{If: string(c), LimitTo: d.value}
190 }
191}
192
193// ElseIf constructs valid model rules
194func ElseIf(c Condition, d Do) ElseIfRule {
195 switch d.field {
196 case "multiply_by":
197 return ElseIfRule{ElseIf: string(c), MultiplyBy: d.value}
198 default:
199 return ElseIfRule{ElseIf: string(c), LimitTo: d.value}
200 }
201}
202
203// Else constructs valid model rules
204func Else(d Do) ElseRule {
205 switch d.field {
206 case "multiply_by":
207 return ElseRule{MultiplyBy: d.value}
208 default:
209 return ElseRule{LimitTo: d.value}
210 }
211}