diff --git a/docs/mapping.rst b/docs/mapping.rst index 01030e2..8d2e2da 100644 --- a/docs/mapping.rst +++ b/docs/mapping.rst @@ -316,3 +316,27 @@ To load all tags except ``created_by``, ``source``, and ``tiger:county``, ``tige load_all: true, exclude: [created_by, source, "tiger:*"] + + +.. _Areas: + +Areas +----- + +A closed way is way where the first and last nodes are identical. These closed ways are used to represent elements like building, forest or park polygons, but they can also represent linear (non-polygon) features, like a roundabout or a race track. + +OpenStreetMap uses the `area `_ tag to specify if a closed way is an area (polygon) or a linear feature (linestring). For example ``highway=pedestrian, area=yes`` is a polygon feature. + +By default, Imposm inserts all closed ways into polygon tables as long as ``area`` is not ``no`` and linestring tables will contain all closed ways as long as the ``area`` is not ``yes``. +However, the ``area`` tag is missing from most OSM elements, as buildings, landuse, etc. should be interpreted as ``area=yes`` by default and highways for example are ``area=no`` by default. + +You can configure these default interpretations with the ``areas`` option. + +.. code-block:: yaml + + areas: + area_tags: [buildings, landuse, leisure, natural, aeroway] + linear_tags: [highway, barrier] + + +With this ``areas`` configuration, ``highway`` elements are only inserted into polygon tables if there is an ``area=yes`` tag. ``aeroway`` elements are only inserted into linestring tables if there is an ``area=no`` tag. diff --git a/example-mapping.yml b/example-mapping.yml index d3d38d8..22a79ff 100644 --- a/example-mapping.yml +++ b/example-mapping.yml @@ -1,3 +1,6 @@ +areas: + area_tags: [buildings, landuse, leisure, natural, aeroway] + linear_tags: [highway, barrier] generalized_tables: landusages_gen0: source: landusages_gen1 diff --git a/mapping/config.go b/mapping/config.go index 0c84d8b..3c2918c 100644 --- a/mapping/config.go +++ b/mapping/config.go @@ -49,11 +49,17 @@ type Mapping struct { Tables Tables `yaml:"tables"` GeneralizedTables GeneralizedTables `yaml:"generalized_tables"` Tags Tags `yaml:"tags"` + Areas Areas `yaml:"areas"` // SingleIdSpace mangles the overlapping node/way/relation IDs // to be unique (nodes positive, ways negative, relations negative -1e17) SingleIdSpace bool `yaml:"use_single_id_space"` } +type Areas struct { + AreaTags []Key `yaml:"area_tags"` + LinearTags []Key `yaml:"linear_tags"` +} + type Tags struct { LoadAll bool `yaml:"load_all"` Exclude []Key `yaml:"exclude"` @@ -106,7 +112,7 @@ type TypeMappings struct { Polygons KeyValues `yaml:"polygons"` } -type ElementFilter func(tags *element.Tags) bool +type ElementFilter func(tags element.Tags, key Key, closed bool) bool type TagTables map[Key]map[Value][]OrderedDestTable @@ -261,18 +267,67 @@ func (m *Mapping) extraTags(tableType TableType, tags map[Key]bool) { } } } + // always include area tag for closed-way handling + tags["area"] = true } func (m *Mapping) ElementFilters() map[string][]ElementFilter { result := make(map[string][]ElementFilter) + + var areaTags map[Key]struct{} + var linearTags map[Key]struct{} + if m.Areas.AreaTags != nil { + areaTags = make(map[Key]struct{}) + for _, tag := range m.Areas.AreaTags { + areaTags[tag] = struct{}{} + } + } + if m.Areas.LinearTags != nil { + linearTags = make(map[Key]struct{}) + for _, tag := range m.Areas.LinearTags { + linearTags[tag] = struct{}{} + } + } + for name, t := range m.Tables { + if t.Type == LineStringTable && areaTags != nil { + f := func(tags element.Tags, key Key, closed bool) bool { + if closed { + if tags["area"] == "yes" { + return false + } + if tags["area"] != "no" { + if _, ok := areaTags[key]; ok { + return false + } + } + } + return true + } + result[name] = append(result[name], f) + } + if t.Type == PolygonTable && linearTags != nil { + f := func(tags element.Tags, key Key, closed bool) bool { + if closed && tags["area"] == "no" { + return false + } + if tags["area"] != "yes" { + if _, ok := linearTags[key]; ok { + return false + } + } + return true + } + result[name] = append(result[name], f) + } + if t.Filters == nil { continue } if t.Filters.ExcludeTags != nil { for _, filterKeyVal := range *t.Filters.ExcludeTags { - f := func(tags *element.Tags) bool { - if v, ok := (*tags)[filterKeyVal[0]]; ok { + f := func(tags element.Tags, key Key, closed bool) bool { + if v, ok := tags[filterKeyVal[0]]; ok { if filterKeyVal[1] == "__any__" || v == filterKeyVal[1] { return false } diff --git a/mapping/filter_test.go b/mapping/filter_test.go index 2597bfd..209b053 100644 --- a/mapping/filter_test.go +++ b/mapping/filter_test.go @@ -278,6 +278,11 @@ func TestPointMatcher(t *testing.T) { func TestLineStringMatcher(t *testing.T) { 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"} @@ -293,6 +298,18 @@ func TestLineStringMatcher(t *testing.T) { 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{ @@ -320,6 +337,12 @@ func TestPolygonMatcher(t *testing.T) { 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}, @@ -339,7 +362,10 @@ func TestPolygonMatcher(t *testing.T) { {"landuse", "farm", DestTable{Name: "landusages"}, nil}}, polys.MatchRelation(&elem)) - elem.Tags = element.Tags{"highway": "footway"} + elem.Tags = element.Tags{"highway": "footway"} // linear by default + matchesEqual(t, []Match{}, polys.MatchRelation(&elem)) + + elem.Tags = element.Tags{"highway": "footway", "area": "yes"} matchesEqual(t, []Match{{"highway", "footway", DestTable{Name: "landusages"}, nil}}, polys.MatchRelation(&elem)) elem.Tags = element.Tags{"boundary": "administrative", "admin_level": "8"} diff --git a/mapping/matcher.go b/mapping/matcher.go index 8aa7b02..3796eae 100644 --- a/mapping/matcher.go +++ b/mapping/matcher.go @@ -105,7 +105,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) + return tm.match(node.Tags, false) } func (tm *tagMatcher) MatchWay(way *element.Way) []Match { @@ -114,21 +114,22 @@ func (tm *tagMatcher) MatchWay(way *element.Way) []Match { if way.Tags["area"] == "no" { return nil } - return tm.match(&way.Tags) + return tm.match(way.Tags, true) } } 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) + return tm.match(way.Tags, false) } return nil } func (tm *tagMatcher) MatchRelation(rel *element.Relation) []Match { - return tm.match(&rel.Tags) + return tm.match(rel.Tags, true) } type orderedMatch struct { @@ -136,13 +137,18 @@ type orderedMatch struct { order int } -func (tm *tagMatcher) match(tags *element.Tags) []Match { +func (tm *tagMatcher) match(tags element.Tags, closed bool) []Match { tables := make(map[DestTable]orderedMatch) addTables := func(k, v string, tbls []OrderedDestTable) { for _, t := range tbls { this := orderedMatch{ - Match: Match{k, v, t.DestTable, tm.tables[t.Name]}, + Match: Match{ + Key: k, + Value: v, + Table: t.DestTable, + tableFields: tm.tables[t.Name], + }, order: t.order, } if other, ok := tables[t.DestTable]; ok { @@ -158,7 +164,7 @@ func (tm *tagMatcher) match(tags *element.Tags) []Match { addTables("__any__", "__any__", values["__any__"]) } - for k, v := range *tags { + for k, v := range tags { values, ok := tm.mappings[Key(k)] if ok { if tbls, ok := values["__any__"]; ok { @@ -175,7 +181,7 @@ func (tm *tagMatcher) match(tags *element.Tags) []Match { filteredOut := false if ok { for _, filter := range filters { - if !filter(tags) { + if !filter(tags, Key(match.Key), closed) { filteredOut = true break } diff --git a/mapping/test_mapping.yml b/mapping/test_mapping.yml index 04359f9..2ffed32 100644 --- a/mapping/test_mapping.yml +++ b/mapping/test_mapping.yml @@ -1,3 +1,6 @@ +areas: + area_tags: [buildings, landuse, leisure, natural, aeroway] + linear_tags: [highway, barrier] generalized_tables: landusages_gen0: source: landusages_gen1 diff --git a/test/complete_db.osm b/test/complete_db.osm index 5d4e2cd..a2f4882 100644 --- a/test/complete_db.osm +++ b/test/complete_db.osm @@ -1155,4 +1155,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/complete_db_mapping.json b/test/complete_db_mapping.json index 637b06c..c2ee9ae 100644 --- a/test/complete_db_mapping.json +++ b/test/complete_db_mapping.json @@ -1,4 +1,8 @@ { + "areas": { + "area_tags": ["leisure"], + "linear_tags": ["highway"], + }, "generalized_tables": { "waterareas_gen1": { "source": "waterareas", @@ -125,7 +129,8 @@ "pitch", "stadium", "common", - "nature_reserve" + "nature_reserve", + "track" ], "tourism": [ "zoo" @@ -202,6 +207,7 @@ } ], "type": "polygon", + "filters": {"areas": {}}, "mapping": { "building": [ "__any__" @@ -711,9 +717,7 @@ ], "type": "linestring", "filters": { - "exclude_tags": [ - ["area", "yes"] - ] + "areas": {}, }, "mappings": { "railway": { @@ -733,10 +737,6 @@ }, "roads": { "mapping": { - "man_made": [ - "pier", - "groyne" - ], "highway": [ "motorway", "motorway_link", @@ -761,6 +761,13 @@ "unclassified", "residential", "raceway" + ], + "man_made": [ + "pier", + "groyne" + ], + "leisure": [ + "track" ] } } diff --git a/test/completedb_test.go b/test/completedb_test.go index 53767aa..4adb8a0 100644 --- a/test/completedb_test.go +++ b/test/completedb_test.go @@ -364,6 +364,28 @@ func TestComplete_EnumerateKey(t *testing.T) { }) } +func TestComplete_AreaMapping(t *testing.T) { + // Mapping type dependent area-defaults. + + assertRecords(t, []checkElem{ + // highway=pedestrian + {"osm_roads", 301151, "pedestrian", nil}, + {"osm_landusages", 301151, Missing, nil}, + + // // highway=pedestrian, area=yes + {"osm_roads", 301152, Missing, nil}, + {"osm_landusages", 301152, "pedestrian", nil}, + + // // leisure=track + {"osm_roads", 301153, Missing, nil}, + {"osm_landusages", 301153, "track", nil}, + + // // leisure=track, area=no + {"osm_roads", 301154, "track", nil}, + {"osm_landusages", 301154, Missing, nil}, + }) +} + func TestComplete_Update(t *testing.T) { ts.updateOsm(t, "./build/complete_db.osc.gz") }