writer: do not use GEOS for LineString geometries

master
Oliver Tonnhofer 2017-11-21 09:28:16 +01:00
parent a367850f6f
commit ae4197a147
4 changed files with 241 additions and 4 deletions

View File

@ -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)
}
}

65
geom/wkb.go Normal file
View File

@ -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
}

88
geom/wkb_test.go Normal file
View File

@ -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)
}
}

View File

@ -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 {