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}