allow custom ranking for wayzorder

master
Oliver Tonnhofer 2016-11-10 13:54:28 +01:00
parent b6b3d262d4
commit 4d90e55953
4 changed files with 220 additions and 67 deletions

View File

@ -186,6 +186,30 @@ The following `enum` column will contain ``1`` for ``landuse=forest``, ``4`` for
``mapping_value`` will be used when ``key`` is not set or ``null``.
``wayzorder``
^^^^^^^^^^^^^
Calculate the z-order of an OSM highway or railway. Returns a numeric value that represents the importance of a way where ``motorway`` is the most important (9), and ``path`` or ``track`` are least important (0). ``bridge`` and ``tunnel`` will modify the value by -10/+10. ``layer`` will be multiplied by ten and added to the value. E.g. ``highway=motorway``, ``bridge=yes`` and ``layer=2`` will return 39 (9+10+2*10).
You can define your own ordering by adding a list of ``ranks``. The z-order value will be the index in the list (starting with 1). ``bridge``, ``tunnel``, and ``layer`` will modify the value by the number of items in the ``ranks`` list, instead of 10.
Use ``default`` to set the default rank.
::
columns:
- name: zorder
type: wayzorder
args:
default: 5
ranks:
- footway
- path
- residential
- primary
- motorway
A ``motorway`` will have a ``zorder`` value of 5, a ``residential`` with ``bridge=yes`` will be 8 (3+5).
Element types
~~~~~~~~~~~~~
@ -230,12 +254,6 @@ Like `geometry`, but the geometries will be validated and repaired when this tab
Area of polygon geometries in square meters. This area is calculated in the webmercator projection, so it is only accurate at the equator and gets off the more the geometry moves to the poles. It's still good enough to sort features by area for rendering purposes.
``wayzorder``
^^^^^^^^^^^^^
Calculate the z-order of an OSM highway or railway. Returns a numeric value that represents the importance of a way where ``motorway`` is the most important (9), and ``path`` or ``track`` are least important (0). ``bridge`` and ``tunnel`` will modify the value by -10/+10. ``layer`` will be multiplied by ten and added to the value. E.g. ``highway=motorway``, ``bridge=yes`` and ``layer=2`` will return 39 (9+10+2*10).
``hstore_tags``
^^^^^^^^^^^^^^^

View File

@ -2,6 +2,7 @@ package mapping
import (
"errors"
"fmt"
"regexp"
"strconv"
"strings"
@ -32,7 +33,7 @@ func init() {
"geometry": {"geometry", "geometry", Geometry, nil, nil, false},
"validated_geometry": {"validated_geometry", "validated_geometry", Geometry, nil, nil, false},
"hstore_tags": {"hstore_tags", "hstore_string", HstoreString, nil, nil, false},
"wayzorder": {"wayzorder", "int32", WayZOrder, nil, nil, false},
"wayzorder": {"wayzorder", "int32", nil, MakeWayZOrder, nil, false},
"pseudoarea": {"pseudoarea", "float32", PseudoArea, nil, nil, false},
"zorder": {"zorder", "int32", nil, MakeZOrder, nil, false},
"enumerate": {"enumerate", "int32", nil, MakeEnumerate, nil, false},
@ -230,10 +231,51 @@ func HstoreString(val string, elem *element.OSMElem, geom *geom.Geometry, match
return strings.Join(tags, ", ")
}
var wayRanks map[string]int
func MakeWayZOrder(fieldName string, fieldType FieldType, field Field) (MakeValue, error) {
if _, ok := field.Args["ranks"]; !ok {
return DefaultWayZOrder, nil
}
ranks, err := decodeEnumArg(field, "ranks")
if err != nil {
return nil, err
}
levelOffset := len(ranks)
defaultRank := 0
if val, ok := field.Args["default"].(float64); ok {
defaultRank = int(val)
}
wayZOrder := func(val string, elem *element.OSMElem, geom *geom.Geometry, match Match) interface{} {
var z int
layer, _ := strconv.ParseInt(elem.Tags["layer"], 10, 64)
z += int(layer) * levelOffset
rank, ok := ranks[match.Value]
if !ok {
z += defaultRank
}
z += rank
tunnel := elem.Tags["tunnel"]
if tunnel == "true" || tunnel == "yes" || tunnel == "1" {
z -= levelOffset
}
bridge := elem.Tags["bridge"]
if bridge == "true" || bridge == "yes" || bridge == "1" {
z += levelOffset
}
return z
}
return wayZOrder, nil
}
var defaultRanks map[string]int
func init() {
wayRanks = map[string]int{
defaultRanks = map[string]int{
"minor": 3,
"road": 3,
"unclassified": 3,
@ -251,19 +293,19 @@ func init() {
}
}
func WayZOrder(val string, elem *element.OSMElem, geom *geom.Geometry, match Match) interface{} {
var z int32
func DefaultWayZOrder(val string, elem *element.OSMElem, geom *geom.Geometry, match Match) interface{} {
var z int
layer, _ := strconv.ParseInt(elem.Tags["layer"], 10, 64)
z += int32(layer) * 10
z += int(layer) * 10
rank := wayRanks[match.Value]
rank := defaultRanks[match.Value]
if rank == 0 {
if _, ok := elem.Tags["railway"]; ok {
rank = 7
}
}
z += int32(rank)
z += rank
tunnel := elem.Tags["tunnel"]
if tunnel == "true" || tunnel == "yes" || tunnel == "1" {
@ -325,24 +367,9 @@ func MakeZOrder(fieldName string, fieldType FieldType, field Field) (MakeValue,
}
func MakeEnumerate(fieldName string, fieldType FieldType, field Field) (MakeValue, error) {
_valuesList, ok := field.Args["values"]
if !ok {
return nil, errors.New("missing values in args for enumerate")
}
valuesList, ok := _valuesList.([]interface{})
if !ok {
return nil, errors.New("values in args for enumerate not a list")
}
values := make(map[string]int)
for i, value := range valuesList {
valueName, ok := value.(string)
if !ok {
return nil, errors.New("value in values not a string")
}
values[valueName] = i + 1
values, err := decodeEnumArg(field, "values")
if err != nil {
return nil, err
}
enumerate := func(val string, elem *element.OSMElem, geom *geom.Geometry, match Match) interface{} {
if field.Key != "" {
@ -360,6 +387,29 @@ func MakeEnumerate(fieldName string, fieldType FieldType, field Field) (MakeValu
return enumerate, nil
}
func decodeEnumArg(field Field, key string) (map[string]int, error) {
_valuesList, ok := field.Args[key]
if !ok {
return nil, fmt.Errorf("missing '%v' in args for %s", key, field.Type)
}
valuesList, ok := _valuesList.([]interface{})
if !ok {
return nil, fmt.Errorf("'%v' in args for %s not a list", key, field.Type)
}
values := make(map[string]int)
for i, value := range valuesList {
valueName, ok := value.(string)
if !ok {
return nil, fmt.Errorf("value in '%v' not a string", key)
}
values[valueName] = i + 1
}
return values, nil
}
func MakeSuffixReplace(fieldName string, fieldType FieldType, field Field) (MakeValue, error) {
_changes, ok := field.Args["suffixes"]
if !ok {

View File

@ -109,7 +109,6 @@ func TestZOrder(t *testing.T) {
func TestEnumerate_Match(t *testing.T) {
// test enumerate by matched mapping key
match := Match{}
zOrder, err := MakeEnumerate("enumerate",
AvailableFieldTypes["enumerate"],
@ -123,33 +122,28 @@ func TestEnumerate_Match(t *testing.T) {
if err != nil {
t.Fatal(err)
}
elem := &element.OSMElem{}
elem.Tags = element.Tags{} // missing
if v := zOrder("", elem, nil, match); v != 0 {
t.Errorf(" -> %v", v)
tests := []struct {
key string
tags element.Tags
expected int
}{
{"", nil, 0},
{"ABCD", nil, 0},
{"AA", nil, 1},
{"CC", nil, 2},
{"ZZ", nil, 4},
}
match.Value = "ABCD" // unknown
if v := zOrder("", elem, nil, match); v != 0 {
t.Errorf(" -> %v", v)
}
match.Value = "AA"
if v := zOrder("", elem, nil, match); v != 1 {
t.Errorf(" -> %v", v)
}
match.Value = "CC"
if v := zOrder("", elem, nil, match); v != 2 {
t.Errorf(" -> %v", v)
}
match.Value = "ZZ"
if v := zOrder("", elem, nil, match); v != 4 {
t.Errorf(" -> %v", v)
for _, test := range tests {
elem := &element.OSMElem{Tags: test.tags}
match := Match{Value: test.key}
if v := zOrder("", elem, nil, match); v.(int) != test.expected {
t.Errorf("%v %v %d != %d", test.key, test.tags, v, test.expected)
}
}
}
func TestEnumerate_Key(t *testing.T) {
// test enumerate by key
match := Match{}
zOrder, err := MakeEnumerate("enumerate",
AvailableFieldTypes["enumerate"],
@ -163,21 +157,77 @@ func TestEnumerate_Key(t *testing.T) {
if err != nil {
t.Fatal(err)
}
elem := &element.OSMElem{}
if v := zOrder("", elem, nil, match); v != 0 {
t.Errorf(" -> %v", v)
tests := []struct {
key string
tags element.Tags
expected int
}{
{"", nil, 0},
{"ABCD", nil, 0},
{"AA", nil, 1},
{"CC", nil, 2},
{"ZZ", nil, 4},
}
if v := zOrder("ABCD", elem, nil, match); v != 0 {
t.Errorf(" -> %v", v)
for _, test := range tests {
elem := &element.OSMElem{Tags: test.tags}
match := Match{}
if v := zOrder(test.key, elem, nil, match); v.(int) != test.expected {
t.Errorf("%v %v %d != %d", test.key, test.tags, v, test.expected)
}
}
if v := zOrder("AA", elem, nil, match); v != 1 {
t.Errorf(" -> %v", v)
}
func TestWayZOrder(t *testing.T) {
zOrder, err := MakeWayZOrder("z_order",
AvailableFieldTypes["wayzorder"],
Field{
Name: "zorder",
Type: "wayzorder",
Args: map[string]interface{}{
"default": float64(5),
"ranks": []interface{}{
"path",
"footway",
"pedestrian",
"residential",
"light_rail",
"primary",
"tram",
"rail",
"trunk",
"motorway_link",
"motorway",
}},
},
)
if err != nil {
t.Fatal(err)
}
if v := zOrder("CC", elem, nil, match); v != 2 {
t.Errorf(" -> %v", v)
tests := []struct {
key string
tags element.Tags
expected int
}{
{"unknown", nil, 5},
{"path", nil, 1},
{"residential", nil, 4},
{"residential", nil, 4},
{"motorway", nil, 11},
{"path", element.Tags{"bridge": "yes"}, 12},
{"path", element.Tags{"layer": "1"}, 12},
{"path", element.Tags{"tunnel": "yes"}, -10},
{"unknown", element.Tags{"tunnel": "yes"}, -6},
{"unknown", element.Tags{"tunnel": "yes", "layer": "1"}, 5},
}
if v := zOrder("ZZ", elem, nil, match); v != 4 {
t.Errorf(" -> %v", v)
for _, test := range tests {
elem := &element.OSMElem{Tags: test.tags}
match := Match{Value: test.key}
if v := zOrder("", elem, nil, match); v.(int) != test.expected {
t.Errorf("%v %v %d != %d", test.key, test.tags, v, test.expected)
}
}
}

View File

@ -431,9 +431,44 @@ tables:
- key: ref
name: ref
type: string
- key: layer
name: z_order
- name: z_order
type: wayzorder
args:
ranks:
- disused
- raceway
- pedestrian
- pier
- groyne
- steps
- cycleway
- bridleway
- footway
- service
- track
- path
- monorail
- funicular
- preserved
- tertiary_link
- living_street
- unclassified
- residential
- road
- secondary_link
- tertiary
- narrow_gauge
- subway
- light_rail
- tram
- rail
- secondary
- primary_link
- primary
- trunk_link
- trunk
- motorway_link
- motorway
- key: access
name: access
type: string