From ae4197a147de5619c29deadcc630a0e327032ba1 Mon Sep 17 00:00:00 2001 From: Oliver Tonnhofer Date: Tue, 21 Nov 2017 09:28:16 +0100 Subject: [PATCH] writer: do not use GEOS for LineString geometries --- geom/geom_test.go | 54 ++++++++++++++++++++++++++++- geom/wkb.go | 65 ++++++++++++++++++++++++++++++++++ geom/wkb_test.go | 88 +++++++++++++++++++++++++++++++++++++++++++++++ writer/ways.go | 38 ++++++++++++++++++-- 4 files changed, 241 insertions(+), 4 deletions(-) create mode 100644 geom/wkb.go create mode 100644 geom/wkb_test.go diff --git a/geom/geom_test.go b/geom/geom_test.go index 078e9d2..2ba3f6d 100644 --- a/geom/geom_test.go +++ b/geom/geom_test.go @@ -85,10 +85,62 @@ func BenchmarkLineString(b *testing.B) { nodes[i] = element.Node{Lat: 0, Long: float64(i)} } g := geos.NewGeos() + g.SetHandleSrid(4326) defer g.Finish() for i := 0; i < b.N; i++ { - LineString(g, nodes) + geom, err := LineString(g, nodes) + if err != nil { + b.Fatal(err) + } + g.AsEwkbHex(geom) + } +} + +func BenchmarkLineStringNoGeos(b *testing.B) { + size := 16 + nodes := make([]element.Node, size) + for i := 0; i < size; i++ { + nodes[i] = element.Node{Lat: 0, Long: float64(i)} + } + + for i := 0; i < b.N; i++ { + NodesAsEWKBHexLineString(nodes, 4326) + } +} + +func BenchmarkPolygon(b *testing.B) { + size := 16 + nodes := make([]element.Node, size) + for i := 1; i < size; i++ { + nodes[i] = element.Node{Lat: 1, Long: float64(i)} + } + nodes[0] = element.Node{Lat: 0, Long: 0} + nodes[len(nodes)-1] = element.Node{Lat: 0, Long: 0} + g := geos.NewGeos() + g.SetHandleSrid(4326) + defer g.Finish() + + for i := 0; i < b.N; i++ { + geom, err := Polygon(g, nodes) + if err != nil { + b.Fatal(err) + } + g.AsEwkbHex(geom) + } +} + +func BenchmarkPolygonNoGeos(b *testing.B) { + size := 16 + nodes := make([]element.Node, size) + for i := 1; i < size; i++ { + nodes[i] = element.Node{Lat: 1, Long: float64(i)} + } + nodes[0] = element.Node{Lat: 0, Long: 0} + nodes[len(nodes)-1] = element.Node{Lat: 0, Long: 0} + + for i := 0; i < b.N; i++ { + NodesAsEWKBHexPolygon(nodes, 4326) } } diff --git a/geom/wkb.go b/geom/wkb.go new file mode 100644 index 0000000..f564e2c --- /dev/null +++ b/geom/wkb.go @@ -0,0 +1,65 @@ +package geom + +import ( + "bytes" + "encoding/binary" + "encoding/hex" + + "github.com/omniscale/imposm3/element" +) + +const ( + wkbSridFlag = 0x20000000 + wkbLineStringType = 2 + wkbPolygonType = 3 +) + +func NodesAsEWKBHexLineString(nodes []element.Node, srid int) ([]byte, error) { + nodes = unduplicateNodes(nodes) + if len(nodes) < 2 { + return nil, ErrorOneNodeWay + } + buf := &bytes.Buffer{} + binary.Write(buf, binary.LittleEndian, uint8(1)) // little endian + if srid != 0 { + binary.Write(buf, binary.LittleEndian, uint32(wkbLineStringType|wkbSridFlag)) + binary.Write(buf, binary.LittleEndian, uint32(srid)) + } else { + binary.Write(buf, binary.LittleEndian, uint32(wkbLineStringType)) + } + binary.Write(buf, binary.LittleEndian, uint32(len(nodes))) + + for _, nd := range nodes { + binary.Write(buf, binary.LittleEndian, nd.Long) + binary.Write(buf, binary.LittleEndian, nd.Lat) + } + + src := buf.Bytes() + dst := make([]byte, hex.EncodedLen(len(src))) + hex.Encode(dst, src) + return dst, nil +} + +func NodesAsEWKBHexPolygon(nodes []element.Node, srid int) ([]byte, error) { + // TODO undup nodes and check if closed + buf := &bytes.Buffer{} + binary.Write(buf, binary.LittleEndian, uint8(1)) // little endian + if srid != 0 { + binary.Write(buf, binary.LittleEndian, uint32(wkbPolygonType|wkbSridFlag)) + binary.Write(buf, binary.LittleEndian, uint32(srid)) + } else { + binary.Write(buf, binary.LittleEndian, uint32(wkbPolygonType)) + } + binary.Write(buf, binary.LittleEndian, uint32(1)) // one ring + binary.Write(buf, binary.LittleEndian, uint32(len(nodes))) + + for _, nd := range nodes { + binary.Write(buf, binary.LittleEndian, nd.Long) + binary.Write(buf, binary.LittleEndian, nd.Lat) + } + + src := buf.Bytes() + dst := make([]byte, hex.EncodedLen(len(src))) + hex.Encode(dst, src) + return dst, nil +} diff --git a/geom/wkb_test.go b/geom/wkb_test.go new file mode 100644 index 0000000..85a47f6 --- /dev/null +++ b/geom/wkb_test.go @@ -0,0 +1,88 @@ +package geom + +import ( + "strings" + "testing" + + "github.com/omniscale/imposm3/element" + "github.com/omniscale/imposm3/geom/geos" +) + +func TestWkbLineString(t *testing.T) { + nodes := make([]element.Node, 5) + nodes[0] = element.Node{Lat: 0, Long: 0} + nodes[1] = element.Node{Lat: 1.123, Long: -0.2} + nodes[2] = element.Node{Lat: 1.99, Long: 1} + nodes[3] = element.Node{Lat: 0, Long: 1.1} + nodes[4] = element.Node{Lat: 0, Long: 0} + g := geos.NewGeos() + defer g.Finish() + + geom, err := LineString(g, nodes) + if err != nil { + t.Fatal(err) + } + geosWkb := string(g.AsEwkbHex(geom)) + wkbb, err := NodesAsEWKBHexLineString(nodes, 0) + if err != nil { + t.Fatal(err) + } + wkb := strings.ToUpper(string(wkbb)) + + if geosWkb != wkb { + t.Error("linestring wkb differs") + t.Error(string(geosWkb)) + t.Error(string(wkb)) + } +} + +func TestWkbPolygon(t *testing.T) { + nodes := make([]element.Node, 5) + nodes[0] = element.Node{Lat: 1.123, Long: -0.2} + nodes[1] = element.Node{Lat: 1.99, Long: 1} + nodes[2] = element.Node{Lat: 0, Long: 1.1} + nodes[3] = element.Node{Lat: 0, Long: 0} + nodes[4] = element.Node{Lat: 1.123, Long: -0.2} + g := geos.NewGeos() + defer g.Finish() + + geom, err := Polygon(g, nodes) + if err != nil { + t.Fatal(err) + } + geosWkb := string(g.AsEwkbHex(geom)) + wkbb, err := NodesAsEWKBHexPolygon(nodes, 0) + if err != nil { + t.Fatal(err) + } + wkb := strings.ToUpper(string(wkbb)) + + if geosWkb != wkb { + t.Error("polygon wkb differs") + t.Error(string(geosWkb)) + t.Error(string(wkb)) + } +} + +func BenchmarkAsWkb(b *testing.B) { + g := geos.NewGeos() + defer g.Finish() + + p := g.FromWkt("LINESTRING(0 0, 5 0, 10 0, 10 5, 10 10, 0 10, 0 0)") + + for i := 0; i < b.N; i++ { + g.AsEwkbHex(p) + } +} + +func BenchmarkAsWkbSrid(b *testing.B) { + g := geos.NewGeos() + g.SetHandleSrid(4326) + defer g.Finish() + + p := g.FromWkt("LINESTRING(0 0, 5 0, 10 0, 10 5, 10 10, 0 10, 0 0)") + + for i := 0; i < b.N; i++ { + g.AsEwkbHex(p) + } +} diff --git a/writer/ways.go b/writer/ways.go index 5ca184a..4cc7749 100644 --- a/writer/ways.go +++ b/writer/ways.go @@ -130,12 +130,44 @@ func (ww *WayWriter) loop() { ww.wg.Done() } -func (ww *WayWriter) buildAndInsert(g *geos.Geos, w *element.Way, matches []mapping.Match, isPolygon bool) (error, bool) { - var err error - var geosgeom *geos.Geom +func (ww *WayWriter) buildAndInsert( + g *geos.Geos, + w *element.Way, + matches []mapping.Match, + isPolygon bool, +) (error, bool) { + // make copy to avoid interference with polygon/linestring matches way := element.Way(*w) + // Shortcut for non-clipped LineStrings: + // We don't need any function from GEOS, so we can directly create the WKB hex string. + if ww.limiter == nil && !isPolygon { + wkb, err := geomp.NodesAsEWKBHexLineString(w.Nodes, ww.srid) + if err != nil { + return err, false + } + geom := geomp.Geometry{Wkb: wkb} + if err := ww.inserter.InsertLineString(w.OSMElem, geom, matches); err != nil { + return err, false + } + return nil, true + } + // TODO: We could also apply this shortcut for simple Polygons that we we don't + // need to make valid (e.g. no holes, only 4 points). + // However, this does not work with (Webmerc)Area columns, unless we have + // a non-GEOS implementation. + // if ww.limiter == nil && isPolygon && len(w.Nodes) <= 5 { + // geom := geomp.Geometry{Wkb: geomp.WayAsEWKBHexPolygon(w.Nodes, ww.srid)} + // if err := ww.inserter.InsertPolygon(w.OSMElem, geom, matches); err != nil { + // return err, false + // } + // return nil, true + // } + + var err error + var geosgeom *geos.Geom + if isPolygon { geosgeom, err = geomp.Polygon(g, way.Nodes) if err == nil {