From 5203225bcdb90c0182acd9de67bbda598b943e82 Mon Sep 17 00:00:00 2001 From: Oliver Tonnhofer Date: Thu, 31 Oct 2013 15:40:42 +0100 Subject: [PATCH] improve limiter - check complete contains first - intersect with original geometry if it intersects to many parts --- geom/geojson/geojson.go | 1 - geom/limit/limit.go | 78 ++++++++++++++++++++++++++++++----------- 2 files changed, 58 insertions(+), 21 deletions(-) diff --git a/geom/geojson/geojson.go b/geom/geojson/geojson.go index c764fa6..3a38e6d 100644 --- a/geom/geojson/geojson.go +++ b/geom/geojson/geojson.go @@ -248,6 +248,5 @@ func geosPolygon(g *geos.Geos, polygon polygon) (*geos.Geom, error) { } return nil, errors.New("unable to create polygon") } - g.DestroyLater(geom) return geom, nil } diff --git a/geom/limit/limit.go b/geom/limit/limit.go index 1514ec4..416c3d2 100644 --- a/geom/limit/limit.go +++ b/geom/limit/limit.go @@ -107,10 +107,23 @@ func SplitPolygonAtGrid(g *geos.Geos, geom *geos.Geom, gridWidth, currentGridWid } type Limiter struct { - index *geos.Index + // for quick intersections of small geometries + index *geos.Index + // for direct intersections of large geometries + geom *geos.Geom + // for quick contains checks + geomPrep *geos.PreparedGeom + geomPrepMu *sync.Mutex + + // bufferedXxx for diff elements + // (we keep more data at the boundary to be able to build + // geometries that intersects the limitto geometry) + + // for quick coarse contains checks + bufferedBbox geos.Bounds + // for quick contains checks bufferedPrep *geos.PreparedGeom bufferedPrepMu *sync.Mutex - bufferedBbox geos.Bounds } func NewFromGeoJson(source string) (*Limiter, error) { @@ -138,6 +151,7 @@ func NewFromGeoJsonWithBuffered(source string, buffer float64) (*Limiter, error) index := g.CreateIndex() + var bufferedPolygons []*geos.Geom var polygons []*geos.Geom withBuffer := false @@ -157,8 +171,9 @@ func NewFromGeoJsonWithBuffered(source string, buffer float64) (*Limiter, error) return nil, errors.New("couldn't buffer limitto") } g.DestroyLater(buffered) - polygons = append(polygons, buffered) + bufferedPolygons = append(bufferedPolygons, buffered) } + polygons = append(polygons, geom) parts, err := SplitPolygonAtAutoGrid(g, geom) @@ -170,10 +185,10 @@ func NewFromGeoJsonWithBuffered(source string, buffer float64) (*Limiter, error) } } - var bbox geos.Bounds - var prep *geos.PreparedGeom - if len(polygons) > 0 { - union := g.UnionPolygons(polygons) + var bufferedBbox geos.Bounds + var bufferedPrep *geos.PreparedGeom + if len(bufferedPolygons) > 0 { + union := g.UnionPolygons(bufferedPolygons) if union == nil { return nil, errors.New("unable to union limitto polygons") } @@ -182,14 +197,24 @@ func NewFromGeoJsonWithBuffered(source string, buffer float64) (*Limiter, error) return nil, errors.New("unable to simplify limitto polygons") } g.Destroy(union) - bbox = simplified.Bounds() - prep = g.Prepare(simplified) + bufferedBbox = simplified.Bounds() + bufferedPrep = g.Prepare(simplified) // keep simplified around for prepared geometry - if prep == nil { + if bufferedPrep == nil { return nil, errors.New("unable to prepare limitto polygons") } } - return &Limiter{index, prep, &sync.Mutex{}, bbox}, nil + var geomPrep *geos.PreparedGeom + union := g.UnionPolygons(polygons) + if union == nil { + return nil, errors.New("unable to union limitto polygons") + } + geomPrep = g.Prepare(union) + if geomPrep == nil { + return nil, errors.New("unable to prepare limitto polygons") + } + + return &Limiter{index, union, geomPrep, &sync.Mutex{}, bufferedBbox, bufferedPrep, &sync.Mutex{}}, nil } func filterGeometryByType(g *geos.Geos, geom *geos.Geom, targetType string) []*geos.Geom { @@ -234,22 +259,34 @@ func (l *Limiter) Clip(geom *geos.Geom) ([]*geos.Geom, error) { g := geos.NewGeos() defer g.Finish() + // check if geom is completely contained + l.geomPrepMu.Lock() + if g.PreparedContains(l.geomPrep, geom) { + l.geomPrepMu.Unlock() + return []*geos.Geom{geom}, nil + } + l.geomPrepMu.Unlock() + + // we have intersections, query index to get intersecting parts hits := g.IndexQuery(l.index, geom) - if len(hits) == 0 { - return nil, nil - } geomType := g.Type(geom) - var intersections []*geos.Geom + // too many intersecting parts, it probably faster to + // intersect with the original geometry + if len(hits) > 25 { + newPart := g.Intersection(l.geom, geom) + if newPart == nil { + return nil, nil + } + newParts := filterGeometryByType(g, newPart, geomType) + return mergeGeometries(g, newParts, geomType), nil + } + var intersections []*geos.Geom + // intersect with each part... for _, hit := range hits { hit.Lock() - if g.PreparedContains(hit.Prepared, geom) { - hit.Unlock() - return []*geos.Geom{geom}, nil - } - if g.PreparedIntersects(hit.Prepared, geom) { hit.Unlock() newPart := g.Intersection(hit.Geom, geom) @@ -264,6 +301,7 @@ func (l *Limiter) Clip(geom *geos.Geom) ([]*geos.Geom, error) { hit.Unlock() } } + // and merge parts back to our clipped intersection return mergeGeometries(g, intersections, geomType), nil }