From 7157b5252a26cf9042618d884c7670842fa11058 Mon Sep 17 00:00:00 2001 From: Oliver Tonnhofer Date: Wed, 10 May 2017 14:23:32 +0200 Subject: [PATCH] add relation_type --- mapping/config.go | 82 +++++--- mapping/filter.go | 26 ++- mapping/filter_test.go | 329 ++++++++++++++++++-------------- mapping/matcher.go | 34 +++- mapping/matcher_test.go | 10 +- test/complete_db.osm | 11 ++ test/completedb_test.go | 8 + test/route_relation.osm | 10 + test/route_relation_mapping.yml | 10 +- test/route_relation_test.go | 19 +- 10 files changed, 343 insertions(+), 196 deletions(-) diff --git a/mapping/config.go b/mapping/config.go index 932a11f..ffb5783 100644 --- a/mapping/config.go +++ b/mapping/config.go @@ -20,14 +20,15 @@ type Field struct { } type Table struct { - Name string - Type TableType `yaml:"type"` - Mapping KeyValues `yaml:"mapping"` - Mappings map[string]SubMapping `yaml:"mappings"` - TypeMappings TypeMappings `yaml:"type_mappings"` - Fields []*Field `yaml:"columns"` // TODO rename Fields internaly to Columns - OldFields []*Field `yaml:"fields"` - Filters *Filters `yaml:"filters"` + Name string + Type TableType `yaml:"type"` + Mapping KeyValues `yaml:"mapping"` + Mappings map[string]SubMapping `yaml:"mappings"` + TypeMappings TypeMappings `yaml:"type_mappings"` + Fields []*Field `yaml:"columns"` // TODO rename Fields internaly to Columns + OldFields []*Field `yaml:"fields"` + Filters *Filters `yaml:"filters"` + RelationTypes []string `yaml:"relation_types"` } type GeneralizedTable struct { @@ -179,19 +180,6 @@ func NewMapping(filename string) (*Mapping, error) { return &mapping, nil } -func (t *Table) ExtraTags() map[Key]bool { - tags := make(map[Key]bool) - for _, field := range t.Fields { - if field.Key != "" { - tags[field.Key] = true - } - for _, k := range field.Keys { - tags[k] = true - } - } - return tags -} - func (m *Mapping) prepare() error { for name, t := range m.Tables { t.Name = name @@ -247,7 +235,7 @@ func (m *Mapping) mappings(tableType TableType, mappings TagTables) { func (m *Mapping) tables(tableType TableType) map[string]*TableFields { result := make(map[string]*TableFields) for name, t := range m.Tables { - if t.Type == tableType || t.Type == "geometry" { + if t.Type == tableType || t.Type == GeometryTable { result[name] = t.TableFields() } } @@ -256,17 +244,30 @@ func (m *Mapping) tables(tableType TableType) map[string]*TableFields { func (m *Mapping) extraTags(tableType TableType, tags map[Key]bool) { for _, t := range m.Tables { - if t.Type != tableType && t.Type != "geometry" { + if t.Type != tableType && t.Type != GeometryTable { continue } - for key, _ := range t.ExtraTags() { - tags[key] = true + + for _, field := range t.Fields { + if field.Key != "" { + tags[field.Key] = true + } + for _, k := range field.Keys { + tags[k] = true + } } + if t.Filters != nil && t.Filters.ExcludeTags != nil { for _, keyVal := range *t.Filters.ExcludeTags { tags[Key(keyVal[0])] = true } } + + if tableType == PolygonTable || tableType == RelationTable || tableType == RelationMemberTable { + if t.RelationTypes != nil { + tags["type"] = true + } + } } for _, k := range m.Tags.Include { tags[k] = true @@ -331,6 +332,37 @@ func (m *Mapping) addTypedFilters(tableType TableType, filters tableFilters) { } } +func (m *Mapping) addRelationFilters(tableType TableType, filters tableFilters) { + for name, t := range m.Tables { + if t.RelationTypes != nil { + f := func(tags element.Tags, key Key, closed bool) bool { + if v, ok := tags["type"]; ok { + for _, rtype := range t.RelationTypes { + if v == rtype { + return true + } + } + } + return false + } + filters[name] = append(filters[name], f) + } else { + if t.Type == PolygonTable { + // standard mulipolygon handling (boundary and land_area are for backwards compatibility) + f := func(tags element.Tags, key Key, closed bool) bool { + if v, ok := tags["type"]; ok { + if v == "multipolygon" || v == "boundary" || v == "land_area" { + return true + } + } + return false + } + filters[name] = append(filters[name], f) + } + } + } +} + func (m *Mapping) addFilters(filters tableFilters) { for name, t := range m.Tables { if t.Filters == nil { diff --git a/mapping/filter.go b/mapping/filter.go index 0142869..9cc52e6 100644 --- a/mapping/filter.go +++ b/mapping/filter.go @@ -12,9 +12,10 @@ func (m *Mapping) NodeTagFilter() TagFilterer { return newExcludeFilter(m.Tags.Exclude) } mappings := make(map[Key]map[Value][]OrderedDestTable) - m.mappings("point", mappings) + m.mappings(PointTable, mappings) tags := make(map[Key]bool) - m.extraTags("point", tags) + m.extraTags(PointTable, tags) + m.extraTags(RelationMemberTable, tags) return &TagFilter{mappings, tags} } @@ -23,11 +24,12 @@ func (m *Mapping) WayTagFilter() TagFilterer { return newExcludeFilter(m.Tags.Exclude) } mappings := make(map[Key]map[Value][]OrderedDestTable) - m.mappings("linestring", mappings) - m.mappings("polygon", mappings) + m.mappings(LineStringTable, mappings) + m.mappings(PolygonTable, mappings) tags := make(map[Key]bool) - m.extraTags("linestring", tags) - m.extraTags("polygon", tags) + m.extraTags(LineStringTable, tags) + m.extraTags(PolygonTable, tags) + m.extraTags(RelationMemberTable, tags) return &TagFilter{mappings, tags} } @@ -42,11 +44,15 @@ func (m *Mapping) RelationTagFilter() TagFilterer { "boundary": []OrderedDestTable{}, "land_area": []OrderedDestTable{}, } - m.mappings("linestring", mappings) - m.mappings("polygon", mappings) + m.mappings(LineStringTable, mappings) + m.mappings(PolygonTable, mappings) + m.mappings(RelationTable, mappings) + m.mappings(RelationMemberTable, mappings) tags := make(map[Key]bool) - m.extraTags("linestring", tags) - m.extraTags("polygon", tags) + m.extraTags(LineStringTable, tags) + m.extraTags(PolygonTable, tags) + m.extraTags(RelationTable, tags) + m.extraTags(RelationMemberTable, tags) return &TagFilter{mappings, tags} } diff --git a/mapping/filter_test.go b/mapping/filter_test.go index 6869d65..7e55175 100644 --- a/mapping/filter_test.go +++ b/mapping/filter_test.go @@ -17,22 +17,6 @@ func init() { } } -func stringMapEquals(t *testing.T, expected, actual map[string]string) { - if len(expected) != len(actual) { - t.Errorf("different length in %v and %v\n", expected, actual) - } - - for k, v := range expected { - if actualV, ok := actual[k]; ok { - if actualV != v { - t.Errorf("%s != %s in %v and %v\n", v, actualV, expected, actual) - } - } else { - t.Errorf("%s not in %v\n", k, actual) - } - } -} - func stringMapEqual(expected, actual map[string]string) bool { if len(expected) != len(actual) { return false @@ -50,12 +34,12 @@ func stringMapEqual(expected, actual map[string]string) bool { return true } -func matchesEqual(t *testing.T, expected []Match, actual []Match) { +func matchesEqual(expected []Match, actual []Match) bool { expectedMatches := make(map[DestTable]Match) actualMatches := make(map[DestTable]Match) if len(expected) != len(actual) { - t.Fatalf("different length in %v and %v\n", expected, actual) + return false } for _, match := range expected { @@ -70,12 +54,13 @@ func matchesEqual(t *testing.T, expected []Match, actual []Match) { if expectedMatch.Table != actualMatch.Table || expectedMatch.Key != actualMatch.Key || expectedMatch.Value != actualMatch.Value { - t.Fatalf("match differ %v != %v", expectedMatch, actualMatch) + return false } } else { - t.Fatalf("%s not in %v", name, actualMatches) + return false } } + return true } func TestTagFilterNodes(t *testing.T) { @@ -154,151 +139,209 @@ func TestTagFilterRelations(t *testing.T) { } func TestPointMatcher(t *testing.T) { - elem := element.Node{} - points := mapping.PointMatcher() - - elem.Tags = element.Tags{"unknown": "baz"} - matchesEqual(t, []Match{}, points.MatchNode(&elem)) - - elem.Tags = element.Tags{"place": "unknown"} - matchesEqual(t, []Match{}, points.MatchNode(&elem)) - - elem.Tags = element.Tags{"place": "city"} - matchesEqual(t, []Match{{"place", "city", DestTable{Name: "places"}, nil}}, points.MatchNode(&elem)) - - elem.Tags = element.Tags{"place": "city", "highway": "unknown"} - matchesEqual(t, []Match{{"place", "city", DestTable{Name: "places"}, nil}}, points.MatchNode(&elem)) - - elem.Tags = element.Tags{"place": "city", "highway": "bus_stop"} - matchesEqual(t, - []Match{ + tests := []struct { + tags element.Tags + matches []Match + }{ + {element.Tags{"unknown": "baz"}, []Match{}}, + {element.Tags{"place": "unknown"}, []Match{}}, + {element.Tags{"place": "city"}, []Match{{"place", "city", DestTable{Name: "places"}, nil}}}, + {element.Tags{"place": "city", "highway": "unknown"}, []Match{{"place", "city", DestTable{Name: "places"}, nil}}}, + {element.Tags{"place": "city", "highway": "bus_stop"}, []Match{ {"place", "city", DestTable{Name: "places"}, nil}, {"highway", "bus_stop", DestTable{Name: "transport_points"}, nil}}, - points.MatchNode(&elem)) + }, + } + + elem := element.Node{} + m := mapping.PointMatcher() + for i, test := range tests { + elem.Tags = test.tags + actual := m.MatchNode(&elem) + if !matchesEqual(actual, test.matches) { + t.Errorf("unexpected result for case %d: %v != %v", i+1, actual, test.matches) + } + } } func TestLineStringMatcher(t *testing.T) { + tests := []struct { + tags element.Tags + matches []Match + }{ + {element.Tags{"unknown": "baz"}, []Match{}}, + {element.Tags{"highway": "unknown"}, []Match{}}, + {element.Tags{"highway": "pedestrian"}, + []Match{{"highway", "pedestrian", DestTable{Name: "roads", SubMapping: "roads"}, nil}}}, + + // exclude_tags area=yes + {element.Tags{"highway": "pedestrian", "area": "yes"}, []Match{}}, + + {element.Tags{"barrier": "hedge"}, + []Match{{"barrier", "hedge", DestTable{Name: "barrierways"}, nil}}}, + {element.Tags{"barrier": "hedge", "area": "yes"}, []Match{}}, + + {element.Tags{"aeroway": "runway"}, []Match{}}, + {element.Tags{"aeroway": "runway", "area": "no"}, + []Match{{"aeroway", "runway", DestTable{Name: "aeroways"}, nil}}}, + + {element.Tags{"highway": "secondary", "railway": "tram"}, + []Match{ + {"highway", "secondary", DestTable{Name: "roads", SubMapping: "roads"}, nil}, + {"railway", "tram", DestTable{Name: "roads", SubMapping: "railway"}, nil}}, + }, + {element.Tags{"highway": "footway", "landuse": "park", "barrier": "hedge"}, + // landusages not a linestring table + []Match{ + {"highway", "footway", DestTable{Name: "roads", SubMapping: "roads"}, nil}, + {"barrier", "hedge", DestTable{Name: "barrierways"}, nil}}, + }, + } + elem := element.Way{} // fake closed way for area matching elem.Refs = []int64{1, 2, 3, 4, 1} if !elem.IsClosed() { t.Fatal("way not closed") } - ls := mapping.LineStringMatcher() - - elem.Tags = element.Tags{"unknown": "baz"} - matchesEqual(t, []Match{}, ls.MatchWay(&elem)) - - elem.Tags = element.Tags{"highway": "unknown"} - matchesEqual(t, []Match{}, ls.MatchWay(&elem)) - - elem.Tags = element.Tags{"highway": "pedestrian"} - matchesEqual(t, []Match{{"highway", "pedestrian", DestTable{Name: "roads", SubMapping: "roads"}, nil}}, ls.MatchWay(&elem)) - - // exclude_tags area=yes - elem.Tags = element.Tags{"highway": "pedestrian", "area": "yes"} - matchesEqual(t, []Match{}, ls.MatchWay(&elem)) - - elem.Tags = element.Tags{"barrier": "hedge"} - matchesEqual(t, []Match{{"barrier", "hedge", DestTable{Name: "barrierways"}, nil}}, ls.MatchWay(&elem)) - - elem.Tags = element.Tags{"barrier": "hedge", "area": "yes"} - matchesEqual(t, []Match{}, ls.MatchWay(&elem)) - - elem.Tags = element.Tags{"aeroway": "runway", "area": "no"} - matchesEqual(t, []Match{{"aeroway", "runway", DestTable{Name: "aeroways"}, nil}}, ls.MatchWay(&elem)) - - elem.Tags = element.Tags{"aeroway": "runway"} - matchesEqual(t, []Match{}, ls.MatchWay(&elem)) - - elem.Tags = element.Tags{"highway": "secondary", "railway": "tram"} - matchesEqual(t, - []Match{ - {"highway", "secondary", DestTable{Name: "roads", SubMapping: "roads"}, nil}, - {"railway", "tram", DestTable{Name: "roads", SubMapping: "railway"}, nil}}, - ls.MatchWay(&elem)) - - elem.Tags = element.Tags{"highway": "footway", "landuse": "park"} - // landusages not a linestring table - matchesEqual(t, []Match{{"highway", "footway", DestTable{Name: "roads", SubMapping: "roads"}, nil}}, ls.MatchWay(&elem)) + m := mapping.LineStringMatcher() + for i, test := range tests { + elem.Tags = test.tags + actual := m.MatchWay(&elem) + if !matchesEqual(actual, test.matches) { + t.Errorf("unexpected result for case %d: %v != %v", i+1, actual, test.matches) + } + } } -func TestPolygonMatcher(t *testing.T) { - elem := element.Relation{} - polys := mapping.PolygonMatcher() +func TestPolygonMatcher_MatchWay(t *testing.T) { + tests := []struct { + tags element.Tags + matches []Match + }{ + {element.Tags{}, []Match{}}, + {element.Tags{"unknown": "baz"}, []Match{}}, + {element.Tags{"landuse": "unknown"}, []Match{}}, + {element.Tags{"landuse": "unknown", "type": "multipolygon"}, []Match{}}, + {element.Tags{"building": "yes"}, []Match{{"building", "yes", DestTable{Name: "buildings"}, nil}}}, + {element.Tags{"building": "residential"}, []Match{{"building", "residential", DestTable{Name: "buildings"}, nil}}}, + // line type requires area=yes + {element.Tags{"barrier": "hedge"}, []Match{}}, + {element.Tags{"barrier": "hedge", "area": "yes"}, []Match{{"barrier", "hedge", DestTable{Name: "landusages"}, nil}}}, - elem.Tags = element.Tags{"unknown": "baz"} - matchesEqual(t, []Match{}, polys.MatchRelation(&elem)) + {element.Tags{"building": "shop"}, []Match{ + {"building", "shop", DestTable{Name: "buildings"}, nil}, + {"building", "shop", DestTable{Name: "amenity_areas"}, nil}, + }}, - elem.Tags = element.Tags{"landuse": "unknowns"} - matchesEqual(t, []Match{}, polys.MatchRelation(&elem)) - - elem.Tags = element.Tags{"building": "yes"} - matchesEqual(t, []Match{{"building", "yes", DestTable{Name: "buildings"}, nil}}, polys.MatchRelation(&elem)) - elem.Tags = element.Tags{"building": "residential"} - matchesEqual(t, []Match{{"building", "residential", DestTable{Name: "buildings"}, nil}}, polys.MatchRelation(&elem)) - - elem.Tags = element.Tags{"barrier": "hedge"} - matchesEqual(t, []Match{}, polys.MatchRelation(&elem)) - - elem.Tags = element.Tags{"barrier": "hedge", "area": "yes"} - matchesEqual(t, []Match{{"barrier", "hedge", DestTable{Name: "landusages"}, nil}}, polys.MatchRelation(&elem)) - - elem.Tags = element.Tags{"building": "shop"} - matchesEqual(t, []Match{ - {"building", "shop", DestTable{Name: "buildings"}, nil}, - {"building", "shop", DestTable{Name: "amenity_areas"}, nil}}, - polys.MatchRelation(&elem)) - - elem.Tags = element.Tags{"landuse": "farm"} - matchesEqual(t, []Match{{"landuse", "farm", DestTable{Name: "landusages"}, nil}}, polys.MatchRelation(&elem)) - - elem.Tags = element.Tags{"landuse": "farm", "highway": "secondary"} - matchesEqual(t, []Match{{"landuse", "farm", DestTable{Name: "landusages"}, nil}}, polys.MatchRelation(&elem)) - - elem.Tags = element.Tags{"landuse": "farm", "aeroway": "apron"} - matchesEqual(t, - []Match{ + {element.Tags{"aeroway": "apron", "landuse": "farm"}, []Match{ {"aeroway", "apron", DestTable{Name: "transport_areas"}, nil}, - {"landuse", "farm", DestTable{Name: "landusages"}, nil}}, - polys.MatchRelation(&elem)) + {"landuse", "farm", DestTable{Name: "landusages"}, nil}, + }}, - elem.Tags = element.Tags{"highway": "footway"} // linear by default - matchesEqual(t, []Match{}, polys.MatchRelation(&elem)) + {element.Tags{"landuse": "farm", "highway": "secondary"}, []Match{ + {"landuse", "farm", DestTable{Name: "landusages"}, nil}, + }}, - elem.Tags = element.Tags{"highway": "footway", "area": "yes"} - matchesEqual(t, []Match{{"highway", "footway", DestTable{Name: "landusages"}, nil}}, polys.MatchRelation(&elem)) + {element.Tags{"highway": "footway"}, []Match{}}, + {element.Tags{"highway": "footway", "area": "yes"}, []Match{ + {"highway", "footway", DestTable{Name: "landusages"}, nil}, + }}, - elem.Tags = element.Tags{"boundary": "administrative", "admin_level": "8"} - matchesEqual(t, []Match{{"boundary", "administrative", DestTable{Name: "admin"}, nil}}, polys.MatchRelation(&elem)) + {element.Tags{"boundary": "administrative", "admin_level": "8"}, []Match{{"boundary", "administrative", DestTable{Name: "admin"}, nil}}}, + + /* + landusages mapping has the following order, + check that XxxMatcher always uses the first + + amenity: + - university + landuse: + - forest + leisure: + - park + landuse: + - park + */ + + {element.Tags{"landuse": "forest", "leisure": "park"}, []Match{{"landuse", "forest", DestTable{Name: "landusages"}, nil}}}, + {element.Tags{"landuse": "park", "leisure": "park"}, []Match{{"leisure", "park", DestTable{Name: "landusages"}, nil}}}, + {element.Tags{"landuse": "park", "leisure": "park", "amenity": "university"}, []Match{{"amenity", "university", DestTable{Name: "landusages"}, nil}}}, + } + + elem := element.Way{} + // fake closed way for area matching + elem.Refs = []int64{1, 2, 3, 4, 1} + if !elem.IsClosed() { + t.Fatal("way not closed") + } + m := mapping.PolygonMatcher() + for i, test := range tests { + elem.Tags = test.tags + actual := m.MatchWay(&elem) + if !matchesEqual(actual, test.matches) { + t.Errorf("unexpected result for case %d: %v != %v", i+1, actual, test.matches) + } + } + + elem.Refs = nil + elem.Tags = element.Tags{"building": "yes"} + actual := m.MatchWay(&elem) + if !matchesEqual([]Match{}, actual) { + t.Error("open way matched as polygon") + } } -func TestMatcherMappingOrder(t *testing.T) { +func TestPolygonMatcher_MatchRelation(t *testing.T) { + // check that only relations with type=multipolygon/boundary are matched as polygon + + tests := []struct { + tags element.Tags + matches []Match + }{ + {element.Tags{}, []Match{}}, + {element.Tags{"unknown": "baz"}, []Match{}}, + {element.Tags{"landuse": "unknown"}, []Match{}}, + {element.Tags{"landuse": "unknown", "type": "multipolygon"}, []Match{}}, + {element.Tags{"building": "yes"}, []Match{}}, + {element.Tags{"building": "yes", "type": "multipolygon"}, []Match{{"building", "yes", DestTable{Name: "buildings"}, nil}}}, + {element.Tags{"building": "residential", "type": "multipolygon"}, []Match{{"building", "residential", DestTable{Name: "buildings"}, nil}}}, + // line type requires area=yes + {element.Tags{"barrier": "hedge", "type": "multipolygon"}, []Match{}}, + {element.Tags{"barrier": "hedge", "area": "yes", "type": "multipolygon"}, []Match{{"barrier", "hedge", DestTable{Name: "landusages"}, nil}}}, + + {element.Tags{"building": "shop", "type": "multipolygon"}, []Match{ + {"building", "shop", DestTable{Name: "buildings"}, nil}, + {"building", "shop", DestTable{Name: "amenity_areas"}, nil}, + }}, + + {element.Tags{"aeroway": "apron", "landuse": "farm", "type": "multipolygon"}, []Match{ + {"aeroway", "apron", DestTable{Name: "transport_areas"}, nil}, + {"landuse", "farm", DestTable{Name: "landusages"}, nil}, + }}, + + {element.Tags{"landuse": "farm", "highway": "secondary", "type": "multipolygon"}, []Match{ + {"landuse", "farm", DestTable{Name: "landusages"}, nil}, + }}, + + {element.Tags{"highway": "footway", "type": "multipolygon"}, []Match{}}, + {element.Tags{"highway": "footway", "area": "yes", "type": "multipolygon"}, []Match{ + {"highway", "footway", DestTable{Name: "landusages"}, nil}, + }}, + + {element.Tags{"boundary": "administrative", "admin_level": "8"}, []Match{}}, + {element.Tags{"boundary": "administrative", "admin_level": "8", "type": "boundary"}, []Match{{"boundary", "administrative", DestTable{Name: "admin"}, nil}}}, + } + elem := element.Relation{} - polys := mapping.PolygonMatcher() - - /* - landusages mapping has the following order, - check that XxxMatcher always uses the first - - amenity: - - university - landuse: - - forest - leisure: - - park - landuse: - - park - */ - - elem.Tags = element.Tags{"landuse": "forest", "leisure": "park"} - matchesEqual(t, []Match{{"landuse", "forest", DestTable{Name: "landusages"}, nil}}, polys.MatchRelation(&elem)) - - elem.Tags = element.Tags{"landuse": "park", "leisure": "park"} - matchesEqual(t, []Match{{"leisure", "park", DestTable{Name: "landusages"}, nil}}, polys.MatchRelation(&elem)) - - elem.Tags = element.Tags{"landuse": "park", "leisure": "park", "amenity": "university"} - matchesEqual(t, []Match{{"amenity", "university", DestTable{Name: "landusages"}, nil}}, polys.MatchRelation(&elem)) + m := mapping.PolygonMatcher() + for i, test := range tests { + elem.Tags = test.tags + actual := m.MatchRelation(&elem) + if !matchesEqual(actual, test.matches) { + t.Errorf("unexpected result for case %d: %v != %v", i+1, actual, test.matches) + } + } } func TestExcludeFilter(t *testing.T) { diff --git a/mapping/matcher.go b/mapping/matcher.go index da69520..39014ca 100644 --- a/mapping/matcher.go +++ b/mapping/matcher.go @@ -39,10 +39,13 @@ func (m *Mapping) PolygonMatcher() RelWayMatcher { filters := make(tableFilters) m.addFilters(filters) m.addTypedFilters(PolygonTable, filters) + relFilters := make(tableFilters) + m.addRelationFilters(PolygonTable, relFilters) return &tagMatcher{ mappings: mappings, tables: m.tables(PolygonTable), filters: filters, + relFilters: relFilters, matchAreas: true, } } @@ -54,10 +57,13 @@ func (m *Mapping) RelationMatcher() RelationMatcher { m.addFilters(filters) m.addTypedFilters(PolygonTable, filters) m.addTypedFilters(RelationTable, filters) + relFilters := make(tableFilters) + m.addRelationFilters(RelationTable, relFilters) return &tagMatcher{ mappings: mappings, tables: m.tables(RelationTable), filters: filters, + relFilters: relFilters, matchAreas: true, } } @@ -68,10 +74,13 @@ func (m *Mapping) RelationMemberMatcher() RelationMatcher { filters := make(tableFilters) m.addFilters(filters) m.addTypedFilters(RelationMemberTable, filters) + relFilters := make(tableFilters) + m.addRelationFilters(RelationMemberTable, relFilters) return &tagMatcher{ mappings: mappings, tables: m.tables(RelationMemberTable), filters: filters, + relFilters: relFilters, matchAreas: true, } } @@ -104,6 +113,7 @@ type tagMatcher struct { mappings TagTables tables map[string]*TableFields filters map[string][]ElementFilter + relFilters map[string][]ElementFilter matchAreas bool } @@ -116,7 +126,7 @@ func (m *Match) MemberRow(rel *element.Relation, member *element.Member, geom *g } func (tm *tagMatcher) MatchNode(node *element.Node) []Match { - return tm.match(node.Tags, false) + return tm.match(node.Tags, false, false) } func (tm *tagMatcher) MatchWay(way *element.Way) []Match { @@ -125,22 +135,22 @@ func (tm *tagMatcher) MatchWay(way *element.Way) []Match { if way.Tags["area"] == "no" { return nil } - return tm.match(way.Tags, true) + return tm.match(way.Tags, true, false) } } else { // match way as linestring if way.IsClosed() { if way.Tags["area"] == "yes" { return nil } - return tm.match(way.Tags, true) + return tm.match(way.Tags, true, false) } - return tm.match(way.Tags, false) + return tm.match(way.Tags, false, false) } return nil } func (tm *tagMatcher) MatchRelation(rel *element.Relation) []Match { - return tm.match(rel.Tags, true) + return tm.match(rel.Tags, true, true) } type orderedMatch struct { @@ -148,7 +158,7 @@ type orderedMatch struct { order int } -func (tm *tagMatcher) match(tags element.Tags, closed bool) []Match { +func (tm *tagMatcher) match(tags element.Tags, closed bool, relation bool) []Match { tables := make(map[DestTable]orderedMatch) addTables := func(k, v string, tbls []OrderedDestTable) { @@ -198,6 +208,18 @@ func (tm *tagMatcher) match(tags element.Tags, closed bool) []Match { } } } + if relation && !filteredOut { + filters, ok := tm.relFilters[t.Name] + if ok { + for _, filter := range filters { + if !filter(tags, Key(match.Key), closed) { + filteredOut = true + break + } + } + } + } + if !filteredOut { matches = append(matches, match.Match) } diff --git a/mapping/matcher_test.go b/mapping/matcher_test.go index 8e5efa2..6435719 100644 --- a/mapping/matcher_test.go +++ b/mapping/matcher_test.go @@ -43,7 +43,7 @@ func TestSelectRelationPolygonsSimple(t *testing.T) { t.Fatal(err) } r := element.Relation{} - r.Tags = element.Tags{"landuse": "park"} + r.Tags = element.Tags{"landuse": "park", "type": "multipolygon"} r.Members = []element.Member{ makeMember(0, element.Tags{"landuse": "forest"}), makeMember(1, element.Tags{"landuse": "park"}), @@ -68,7 +68,7 @@ func TestSelectRelationPolygonsUnrelatedTags(t *testing.T) { t.Fatal(err) } r := element.Relation{} - r.Tags = element.Tags{"landuse": "park"} + r.Tags = element.Tags{"landuse": "park", "type": "multipolygon"} r.Members = []element.Member{ makeMember(0, element.Tags{"landuse": "park", "layer": "2", "name": "foo"}), makeMember(1, element.Tags{"landuse": "forest"}), @@ -91,7 +91,7 @@ func TestSelectRelationPolygonsMultiple(t *testing.T) { t.Fatal(err) } r := element.Relation{} - r.Tags = element.Tags{"landuse": "park"} + r.Tags = element.Tags{"landuse": "park", "type": "multipolygon"} r.Members = []element.Member{ makeMember(0, element.Tags{"landuse": "park"}), makeMember(1, element.Tags{"natural": "forest"}), @@ -117,7 +117,7 @@ func TestSelectRelationPolygonsMultipleTags(t *testing.T) { t.Fatal(err) } r := element.Relation{} - r.Tags = element.Tags{"landuse": "forest", "natural": "scrub"} + r.Tags = element.Tags{"landuse": "forest", "natural": "scrub", "type": "multipolygon"} r.Members = []element.Member{ makeMember(0, element.Tags{"natural": "scrub"}), makeMember(1, element.Tags{"landuse": "forest"}), @@ -139,7 +139,7 @@ func TestSelectRelationPolygonsMultipleTagsOnWay(t *testing.T) { t.Fatal(err) } r := element.Relation{} - r.Tags = element.Tags{"waterway": "riverbank"} + r.Tags = element.Tags{"waterway": "riverbank", "type": "multipolygon"} r.Members = []element.Member{ makeMemberRole(0, element.Tags{"waterway": "riverbank", "natural": "water"}, "outer"), makeMemberRole(1, element.Tags{"natural": "water"}, "inner"), diff --git a/test/complete_db.osm b/test/complete_db.osm index e417149..75f3f1e 100644 --- a/test/complete_db.osm +++ b/test/complete_db.osm @@ -94,6 +94,7 @@ + @@ -269,6 +270,16 @@ + + + + + + + + + + diff --git a/test/completedb_test.go b/test/completedb_test.go index 22720e7..4bf0773 100644 --- a/test/completedb_test.go +++ b/test/completedb_test.go @@ -61,6 +61,14 @@ func TestComplete_Deploy(t *testing.T) { } } +func TestComplete_OnlyNewStyleMultipolgon(t *testing.T) { + assertRecords(t, []checkElem{ + {"osm_landusages", -1001, "wood", nil}, + {"osm_landusages", -1011, Missing, nil}, + {"osm_landusages", -1021, Missing, nil}, + }) +} + func TestComplete_LandusageToWaterarea1(t *testing.T) { // Parks inserted into landusages cache := ts.cache(t) diff --git a/test/route_relation.osm b/test/route_relation.osm index 9050239..0a0366a 100644 --- a/test/route_relation.osm +++ b/test/route_relation.osm @@ -153,5 +153,15 @@ + + + + + + + + + + diff --git a/test/route_relation_mapping.yml b/test/route_relation_mapping.yml index aa17e6d..f3955e9 100644 --- a/test/route_relation_mapping.yml +++ b/test/route_relation_mapping.yml @@ -1,9 +1,3 @@ -tags: - load_all: true - exclude: - - created_by - - source - tables: master_routes: type: relation_member @@ -27,6 +21,7 @@ tables: - key: name name: name type: string + relation_types: [route_master] mapping: route_master: [bus] route_members: @@ -54,6 +49,7 @@ tables: key: name type: string from_member: true + relation_types: [route] mapping: route: [bus, tram, rail] routes: @@ -66,5 +62,7 @@ tables: type: string - name: tags type: hstore_tags + relation_types: [route, route_master] mapping: + route_master: [bus, tram, rail] route: [bus, tram, rail] diff --git a/test/route_relation_test.go b/test/route_relation_test.go index a3aec6d..88522c8 100644 --- a/test/route_relation_test.go +++ b/test/route_relation_test.go @@ -58,6 +58,23 @@ func TestRouteRelation_RelationData(t *testing.T) { if r.tags["name"] != "Bus 301: A => B" { t.Error(r) } + + // check tags of master relation + r = ts.queryTags(t, "osm_routes", -100911) + if r.tags["name"] != "Bus 301" { + t.Error(r) + } +} + +func TestRouteRelation_MemberUpdatedByNode1(t *testing.T) { + // check that member is updated after node was modified + rows := ts.queryDynamic(t, "osm_route_members", "osm_id = -110901 AND member = 110101") + if len(rows) != 1 { + t.Fatal(rows) + } + if rows[0]["name"] != "Stop" { + t.Error(rows[0]) + } } func TestRouteRelation_MemberGeomUpdated1(t *testing.T) { @@ -131,7 +148,7 @@ func TestRouteRelation_MemberGeomUpdated2(t *testing.T) { } -func TestRouteRelation_MemberUpdatedByNode(t *testing.T) { +func TestRouteRelation_MemberUpdatedByNode2(t *testing.T) { // check that member is updated after node was modified rows := ts.queryDynamic(t, "osm_route_members", "osm_id = -110901 AND member = 110101") if len(rows) != 1 {