diff --git a/docs/mapping.rst b/docs/mapping.rst index 8d2e2da..82aef9f 100644 --- a/docs/mapping.rst +++ b/docs/mapping.rst @@ -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`` ^^^^^^^^^^^^^^^ diff --git a/mapping/fields.go b/mapping/fields.go index b2ca2a6..fbb0958 100644 --- a/mapping/fields.go +++ b/mapping/fields.go @@ -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 { diff --git a/mapping/fields_test.go b/mapping/fields_test.go index 1135040..f920526 100644 --- a/mapping/fields_test.go +++ b/mapping/fields_test.go @@ -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) + } } } diff --git a/mapping/test_mapping.yml b/mapping/test_mapping.yml index 2ffed32..747fc2b 100644 --- a/mapping/test_mapping.yml +++ b/mapping/test_mapping.yml @@ -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