Skip to content

Commit eab7319

Browse files
committed
Support for importing non-area relations as MultiLineString
1 parent 576b4f3 commit eab7319

File tree

12 files changed

+376
-8
lines changed

12 files changed

+376
-8
lines changed

database/postgis/postgis.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ func addGeometryColumn(tx *sql.Tx, tableName string, spec TableSpec) error {
7272
}
7373

7474
geomType := strings.ToUpper(spec.GeometryType)
75-
if geomType == "POLYGON" {
76-
geomType = "GEOMETRY" // for multipolygon support
75+
if geomType == "POLYGON" || geomType == "LINESTRING" {
76+
geomType = "GEOMETRY" // for multigeometry support
7777
}
7878
sql := fmt.Sprintf("SELECT AddGeometryColumn('%s', '%s', '%s', '%d', '%s', 2);",
7979
spec.Schema, tableName, colName, spec.Srid, geomType)

geom/geom.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package geom
33
import (
44
"errors"
55
"math"
6+
"runtime"
67

78
osm "github.com/omniscale/go-osm"
89
"github.com/omniscale/imposm3/geom/geos"
@@ -136,6 +137,41 @@ func Polygon(g *geos.Geos, nodes []osm.Node) (*geos.Geom, error) {
136137
return geom, nil
137138
}
138139

140+
func MultiLinestring(rel *osm.Relation, srid int) (*geos.Geom, error) {
141+
g := geos.NewGeos()
142+
g.SetHandleSrid(srid)
143+
defer g.Finish()
144+
145+
var lines []*geos.Geom
146+
147+
for _, member := range rel.Members {
148+
if member.Way == nil {
149+
continue
150+
}
151+
152+
line, err := LineString(g, member.Way.Nodes)
153+
154+
// Clear the finalizer created in LineString()
155+
// as we want to make the object a part of MultiLineString.
156+
runtime.SetFinalizer(line, nil)
157+
158+
if err != nil {
159+
return nil, err
160+
}
161+
162+
lines = append(lines, line)
163+
}
164+
165+
result := g.MultiLineString(lines)
166+
if result == nil {
167+
return nil, errors.New("Error while building multi-linestring.")
168+
}
169+
170+
g.DestroyLater(result)
171+
172+
return result, nil
173+
}
174+
139175
func AsGeomElement(g *geos.Geos, geom *geos.Geom) (Geometry, error) {
140176
wkb := g.AsEwkbHex(geom)
141177
if wkb == nil {

geom/multipolygon_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,3 +659,38 @@ func TestClosedAndOpenRing(t *testing.T) {
659659
t.Fatal("geometry not valid", g.AsWkt(geom.Geom))
660660
}
661661
}
662+
663+
func TestSimpleMultiLineString(t *testing.T) {
664+
w1 := makeWay(1, osm.Tags{}, []coord{
665+
{1, 1, 0},
666+
{2, 2, 0},
667+
})
668+
w2 := makeWay(2, osm.Tags{}, []coord{
669+
{3, 2, 0},
670+
{4, 3, 0},
671+
})
672+
673+
rel := osm.Relation{
674+
Element: osm.Element{ID: 1, Tags: osm.Tags{}}}
675+
rel.Members = []osm.Member{
676+
{ID: 1, Type: osm.WayMember, Role: "", Way: &w1},
677+
{ID: 2, Type: osm.WayMember, Role: "", Way: &w2},
678+
}
679+
680+
geom, err := MultiLinestring(&rel, 3857)
681+
682+
if err != nil {
683+
t.Fatal(err)
684+
}
685+
686+
g := geos.NewGeos()
687+
defer g.Finish()
688+
689+
if !g.IsValid(geom) {
690+
t.Fatal("geometry not valid", g.AsWkt(geom))
691+
}
692+
693+
if length := geom.Length(); length != 2 {
694+
t.Fatal("length invalid", length)
695+
}
696+
}

import_/import.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ func Import(importOpts config.Import) {
188188
tagmapping.Conf.SingleIDSpace,
189189
relations,
190190
db, progress,
191+
tagmapping.LineStringMatcher,
191192
tagmapping.PolygonMatcher,
192193
tagmapping.RelationMatcher,
193194
tagmapping.RelationMemberMatcher,

mapping/mapping.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ const (
8484
type Mapping struct {
8585
Conf config.Mapping
8686
PointMatcher NodeMatcher
87-
LineStringMatcher WayMatcher
87+
LineStringMatcher RelWayMatcher
8888
PolygonMatcher RelWayMatcher
8989
RelationMatcher RelationMatcher
9090
RelationMemberMatcher RelationMatcher
@@ -356,6 +356,11 @@ func (m *Mapping) addRelationFilters(tableType TableType, filters tableElementFi
356356
return false
357357
}
358358
filters[name] = append(filters[name], f)
359+
} else if TableType(t.Type) == LineStringTable {
360+
f := func(tags osm.Tags, key Key, closed bool) bool {
361+
return false
362+
}
363+
filters[name] = append(filters[name], f)
359364
}
360365
}
361366
}

mapping/matcher.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,20 @@ func (m *Mapping) pointMatcher() (NodeMatcher, error) {
2020
}, err
2121
}
2222

23-
func (m *Mapping) lineStringMatcher() (WayMatcher, error) {
23+
func (m *Mapping) lineStringMatcher() (RelWayMatcher, error) {
2424
mappings := make(TagTableMapping)
2525
m.mappings(LineStringTable, mappings)
2626
filters := make(tableElementFilters)
2727
m.addFilters(filters)
2828
m.addTypedFilters(LineStringTable, filters)
29+
relFilters := make(tableElementFilters)
30+
m.addRelationFilters(LineStringTable, relFilters)
2931
tables, err := m.tables(LineStringTable)
3032
return &tagMatcher{
3133
mappings: mappings,
3234
filters: filters,
3335
tables: tables,
36+
relFilters: relFilters,
3437
matchAreas: false,
3538
}, err
3639
}

test/multilinestring.osc

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?xml version='1.0' encoding='UTF-8'?>
2+
<osmChange version="1">
3+
<create>
4+
<node id="10009" version="1" timestamp="2016-01-01T00:00:00Z" lat="51.5512379" lon="-0.0405630"/>
5+
<node id="10010" version="1" timestamp="2016-01-01T00:00:00Z" lat="51.5515113" lon="-0.0406333"/>
6+
<way id="1006" version="1" timestamp="2016-01-01T00:00:00Z">
7+
<nd ref="10009"/>
8+
<nd ref="10010"/>
9+
</way>
10+
11+
<node id="10008" version="1" timestamp="2016-01-01T00:00:00Z" lat="51.5515683" lon="-0.0406486"/>
12+
<node id="10007" version="1" timestamp="2016-01-01T00:00:00Z" lat="51.5515883" lon="-0.0406757"/>
13+
<way id="1007" version="1" timestamp="2016-01-01T00:00:00Z">
14+
<nd ref="10008"/>
15+
<nd ref="10007"/>
16+
</way>
17+
18+
<relation id="102" version="1" timestamp="2016-01-01T00:00:00Z">
19+
<member type="way" ref="1006" role=""/>
20+
<member type="way" ref="1007" role=""/>
21+
<tag k="network" v="lcn"/>
22+
<tag k="route" v="bicycle"/>
23+
<tag k="type" v="route"/>
24+
</relation>
25+
</create>
26+
27+
<modify>
28+
<way id="1004" version="2" timestamp="2016-01-01T00:00:00Z">
29+
<nd ref="10004"/>
30+
<nd ref="10005"/>
31+
<nd ref="10006"/>
32+
<nd ref="10004"/>
33+
<tag k="building" v="residential"/>
34+
<!-- tag with area=yes removed -->
35+
</way>
36+
</modify>
37+
</osmChange>

test/multilinestring.osm

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?xml version='1.0' encoding='UTF-8'?>
2+
<osm version="1">
3+
<node id="10002" version="1" timestamp="2015-12-31T23:59:99Z" lat="51.5107973" lon="-0.0930300" />
4+
<node id="10003" version="1" timestamp="2015-12-31T23:59:99Z" lat="51.5108542" lon="-0.0930091" />
5+
<way id="1000" version="1" timestamp="2015-12-31T23:59:99Z">
6+
<nd ref="10002"/>
7+
<nd ref="10003"/>
8+
<tag k="highway" v="trunk"/>
9+
</way>
10+
11+
<node id="10000" version="1" timestamp="2015-12-31T23:59:99Z" lat="51.5107776" lon="-0.0930375"/>
12+
<way id="1005" version="1" timestamp="2015-12-31T23:59:99Z">
13+
<nd ref="10000"/>
14+
<nd ref="10002"/>
15+
</way>
16+
17+
<node id="10001" version="1" timestamp="2015-12-31T23:59:99Z" lat="51.5106434" lon="-0.0930826" />
18+
<way id="1001" version="1" timestamp="2015-12-31T23:59:99Z">
19+
<nd ref="10001"/>
20+
<nd ref="10000"/>
21+
</way>
22+
<relation id="100" version="1" timestamp="2015-12-31T23:59:99Z">
23+
<member type="way" ref="1000" role=""/>
24+
<member type="way" ref="1005" role=""/>
25+
<member type="way" ref="1001" role=""/>
26+
<tag k="route" v="bicycle"/>
27+
<tag k="type" v="route"/>
28+
</relation>
29+
30+
<node id="10004" version="1" timestamp="2015-12-31T23:59:99Z" lat="51.4860918" lon="-0.0873508"/>
31+
<node id="10006" version="1" timestamp="2015-12-31T23:59:99Z" lat="51.4860020" lon="-0.0875372"/>
32+
<node id="10005" version="1" timestamp="2015-12-31T23:59:99Z" lat="51.4853370" lon="-0.0864133"/>
33+
<way id="1004" version="1" timestamp="2015-12-31T23:59:99Z">
34+
<nd ref="10004"/>
35+
<nd ref="10005"/>
36+
<nd ref="10006"/>
37+
<nd ref="10004"/>
38+
<tag k="building" v="residential"/>
39+
<tag k="area" v="yes"/>
40+
</way>
41+
42+
<node id="10011" version="1" timestamp="2015-12-31T23:59:99Z" lat="51.5004084" lon="-0.0824810"/>
43+
<node id="10012" version="1" timestamp="2015-12-31T23:59:99Z" lat="51.5003998" lon="-0.0833872"/>
44+
<node id="10013" version="1" timestamp="2015-12-31T23:59:99Z" lat="51.5003917" lon="-0.0836970"/>
45+
<way id="1008" version="1" timestamp="2015-12-31T23:59:99Z">
46+
<nd ref="10011"/>
47+
<nd ref="10012"/>
48+
<nd ref="10013"/>
49+
<nd ref="10011"/>
50+
<tag k="leisure" v="park"/>
51+
</way>
52+
</osm>

test/multilinestring_mapping.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
areas:
2+
area_tags:
3+
- leisure
4+
tables:
5+
multilinestring:
6+
type: linestring
7+
columns:
8+
- name: osm_id
9+
type: id
10+
- name: geometry
11+
type: geometry
12+
- name: name
13+
type: string
14+
key: name
15+
- name: type
16+
type: mapping_value
17+
relation_types:
18+
- route
19+
mapping:
20+
type:
21+
- route
22+
highway:
23+
- trunk
24+
building:
25+
- residential
26+
leisure:
27+
- park
28+
multilinestring_no_relations:
29+
type: linestring
30+
columns:
31+
- name: osm_id
32+
type: id
33+
- name: geometry
34+
type: geometry
35+
- name: name
36+
type: string
37+
key: name
38+
- name: type
39+
type: mapping_value
40+
mapping:
41+
type:
42+
- route

test/multilinestring_test.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package test
2+
3+
import (
4+
"database/sql"
5+
"io/ioutil"
6+
7+
"testing"
8+
9+
"github.com/omniscale/imposm3/geom/geos"
10+
)
11+
12+
func TestMultiLineString(t *testing.T) {
13+
if testing.Short() {
14+
t.Skip("system test skipped with -test.short")
15+
}
16+
t.Parallel()
17+
18+
ts := importTestSuite{
19+
name: "multilinestring",
20+
}
21+
22+
t.Run("Prepare", func(t *testing.T) {
23+
var err error
24+
25+
ts.dir, err = ioutil.TempDir("", "imposm_test")
26+
if err != nil {
27+
t.Fatal(err)
28+
}
29+
ts.config = importConfig{
30+
connection: "postgis://",
31+
cacheDir: ts.dir,
32+
osmFileName: "build/multilinestring.pbf",
33+
mappingFileName: "multilinestring_mapping.yml",
34+
}
35+
ts.g = geos.NewGeos()
36+
37+
ts.db, err = sql.Open("postgres", "sslmode=disable")
38+
if err != nil {
39+
t.Fatal(err)
40+
}
41+
ts.dropSchemas()
42+
})
43+
44+
const mlsTable = "osm_multilinestring"
45+
46+
t.Run("Import", func(t *testing.T) {
47+
if ts.tableExists(t, ts.dbschemaImport(), mlsTable) != false {
48+
t.Fatalf("table %s exists in schema %s", mlsTable, ts.dbschemaImport())
49+
}
50+
ts.importOsm(t)
51+
if ts.tableExists(t, ts.dbschemaImport(), mlsTable) != true {
52+
t.Fatalf("table %s does not exists in schema %s", mlsTable, ts.dbschemaImport())
53+
}
54+
})
55+
56+
t.Run("Deploy", func(t *testing.T) {
57+
ts.deployOsm(t)
58+
if ts.tableExists(t, ts.dbschemaImport(), mlsTable) != false {
59+
t.Fatalf("table %s exists in schema %s", mlsTable, ts.dbschemaImport())
60+
}
61+
if ts.tableExists(t, ts.dbschemaProduction(), mlsTable) != true {
62+
t.Fatalf("table %s does not exists in schema %s", mlsTable, ts.dbschemaProduction())
63+
}
64+
})
65+
66+
t.Run("CheckMultiLineStringGeometry", func(t *testing.T) {
67+
element := checkElem{mlsTable, -100, "*", nil}
68+
ts.assertGeomType(t, element, "MultiLineString")
69+
ts.assertGeomValid(t, element)
70+
ts.assertGeomLength(t, element, 38)
71+
})
72+
73+
t.Run("CheckLineStringGeometry", func(t *testing.T) {
74+
element := checkElem{mlsTable, 1000, "*", nil}
75+
ts.assertGeomType(t, element, "LineString")
76+
ts.assertGeomValid(t, element)
77+
ts.assertGeomLength(t, element, 10)
78+
})
79+
80+
t.Run("CheckFilters", func(t *testing.T) {
81+
if records := ts.queryRows(t, mlsTable, 1008); len(records) > 0 {
82+
t.Fatalf("The way 1008 should be filtered out by typed filter")
83+
}
84+
if records := ts.queryRows(t, mlsTable, 1004); len(records) > 0 {
85+
t.Fatalf("The way 1004 should be filtered out as it is closed path with area=yes")
86+
}
87+
})
88+
89+
t.Run("RelationTypesFilter", func(t *testing.T) {
90+
if records := ts.queryRows(t, "osm_multilinestring_no_relations", -100); len(records) > 0 {
91+
t.Fatalf("The relation -100 should not be imported due to empty relation_types")
92+
}
93+
})
94+
95+
t.Run("Update", func(t *testing.T) {
96+
ts.updateOsm(t, "build/multilinestring.osc.gz")
97+
})
98+
99+
t.Run("CheckFilters2", func(t *testing.T) {
100+
if records := ts.queryRows(t, mlsTable, 1004); len(records) == 0 {
101+
t.Fatalf("The way 1004 should now be there as we removed area=yes in the update")
102+
}
103+
})
104+
105+
t.Run("CheckNewRelation", func(t *testing.T) {
106+
if records := ts.queryRows(t, mlsTable, -102); len(records) == 0 {
107+
t.Fatalf("The relation -102 should be created")
108+
}
109+
})
110+
}

0 commit comments

Comments
 (0)