Mercurial > gemma
diff pkg/mesh/polygon.go @ 4827:f4abfd0ee8ad remove-octree-debris
Renamed octree package to mesh as there is no octree any more.
author | Sascha L. Teichmann <sascha.teichmann@intevation.de> |
---|---|
date | Tue, 05 Nov 2019 14:30:22 +0100 |
parents | pkg/octree/polygon.go@ea570f43d7a9 |
children | 866eae1bd888 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/mesh/polygon.go Tue Nov 05 14:30:22 2019 +0100 @@ -0,0 +1,505 @@ +// This is Free Software under GNU Affero General Public License v >= 3.0 +// without warranty, see README.md and license for details. +// +// SPDX-License-Identifier: AGPL-3.0-or-later +// License-Filename: LICENSES/AGPL-3.0.txt +// +// Copyright (C) 2018 by via donau +// – Österreichische Wasserstraßen-Gesellschaft mbH +// Software engineering by Intevation GmbH +// +// Author(s): +// * Sascha L. Teichmann <sascha.teichmann@intevation.de> + +package mesh + +import ( + "bytes" + "encoding/binary" + "fmt" + "log" + "math" + + "github.com/tidwall/rtree" + + "gemma.intevation.de/gemma/pkg/wkb" +) + +type ( + ring []float64 + + Polygon struct { + rings []ring + indices []*rtree.RTree + } + + IntersectionType byte + + lineSegment []float64 +) + +const ( + IntersectionInside IntersectionType = iota + IntersectionOutSide + IntersectionOverlaps +) + +func (ls lineSegment) Rect(interface{}) ([]float64, []float64) { + + var min, max [2]float64 + + if ls[0] < ls[2] { + min[0] = ls[0] + max[0] = ls[2] + } else { + min[0] = ls[2] + max[0] = ls[0] + } + + if ls[1] < ls[3] { + min[1] = ls[1] + max[1] = ls[3] + } else { + min[1] = ls[3] + max[1] = ls[1] + } + + return min[:], max[:] +} + +func (p *Polygon) Indexify() { + indices := make([]*rtree.RTree, len(p.rings)) + + for i := range indices { + index := rtree.New(nil) + indices[i] = index + + rng := p.rings[i] + + for i := 0; i < len(rng); i += 2 { + var ls lineSegment + if i+4 <= len(rng) { + ls = lineSegment(rng[i : i+4]) + } else { + ls = []float64{rng[i], rng[i+1], rng[0], rng[1]} + } + index.Insert(ls) + } + } + + p.indices = indices +} + +func (ls lineSegment) intersects(a Box2D) bool { + p1x := ls[0] + p1y := ls[1] + p2x := ls[2] + p2y := ls[3] + + left := a.X1 + right := a.X2 + top := a.Y1 + bottom := a.Y2 + + // The direction of the ray + dx := p2x - p1x + dy := p2y - p1y + + min, max := 0.0, 1.0 + + var t0, t1 float64 + + // Left and right sides. + // - If the line is parallel to the y axis. + if dx == 0 { + if p1x < left || p1x > right { + return false + } + } else { + // - Make sure t0 holds the smaller value by checking the direction of the line. + if dx > 0 { + t0 = (left - p1x) / dx + t1 = (right - p1x) / dx + } else { + t1 = (left - p1x) / dx + t0 = (right - p1x) / dx + } + + if t0 > min { + min = t0 + } + if t1 < max { + max = t1 + } + if min > max || max < 0 { + return false + } + } + + // The top and bottom side. + // - If the line is parallel to the x axis. + if dy == 0 { + if p1y < top || p1y > bottom { + return false + } + } else { + // - Make sure t0 holds the smaller value by checking the direction of the line. + if dy > 0 { + t0 = (top - p1y) / dy + t1 = (bottom - p1y) / dy + } else { + t1 = (top - p1y) / dy + t0 = (bottom - p1y) / dy + } + + if t0 > min { + min = t0 + } + if t1 < max { + max = t1 + } + if min > max || max < 0 { + return false + } + } + + // The point of intersection + // ix = p1x + dx*min + // iy = p1y + dy*min + return true +} + +func (ls lineSegment) intersectsLineSegment(o lineSegment) bool { + + p0 := ls[:2] + p1 := ls[2:4] + p2 := o[:2] + p3 := o[2:4] + + s10x := p1[0] - p0[0] + s10y := p1[1] - p0[1] + s32x := p3[0] - p2[0] + s32y := p3[1] - p2[1] + + den := s10x*s32y - s32x*s10y + + if den == 0 { + return false + } + + denPos := den > 0 + + s02x := p0[0] - p2[0] + s02y := p0[1] - p2[1] + + sNum := s10x*s02y - s10y*s02x + if sNum < 0 == denPos { + return false + } + + tNum := s32x*s02y - s32y*s02x + if tNum < 0 == denPos { + return false + } + + if sNum > den == denPos || tNum > den == denPos { + return false + } + + // t := tNum / den + // intersection at( p0[0] + (t * s10x), p0[1] + (t * s10y) ) + return true +} + +func (p *Polygon) IntersectionBox2D(box Box2D) IntersectionType { + + if len(p.rings) == 0 { + return IntersectionOutSide + } + + for _, index := range p.indices { + var intersects bool + index.Search(box, func(item rtree.Item) bool { + if item.(lineSegment).intersects(box) { + intersects = true + return false + } + return true + }) + if intersects { + return IntersectionOverlaps + } + } + + // No intersection -> check inside or outside + // if an abritrary point is inside or not. + + // Check holes first: inside a hole means outside. + if len(p.rings) > 1 { + for _, hole := range p.rings[1:] { + if contains(hole, box.X1, box.Y1) { + return IntersectionOutSide + } + } + } + + // Check shell + if contains(p.rings[0], box.X1, box.Y1) { + return IntersectionInside + } + return IntersectionOutSide +} + +func (p *Polygon) IntersectionWithTriangle(t *Triangle) IntersectionType { + box := t.BBox() + for _, index := range p.indices { + var intersects bool + index.Search(box, func(item rtree.Item) bool { + ls := item.(lineSegment) + other := make(lineSegment, 4) + for i := range t { + other[0] = t[i].X + other[1] = t[i].Y + other[2] = t[(i+1)%len(t)].X + other[3] = t[(i+1)%len(t)].Y + if ls.intersectsLineSegment(other) { + intersects = true + return false + } + } + return true + }) + if intersects { + return IntersectionOverlaps + } + } + // No intersection -> check inside or outside + // if an abritrary point is inside or not. + pX, pY := t[0].X, t[0].Y + + // Check holes first: inside a hole means outside. + if len(p.rings) > 1 { + for _, hole := range p.rings[1:] { + if contains(hole, pX, pY) { + return IntersectionOutSide + } + } + } + + // Check shell + if contains(p.rings[0], pX, pY) { + return IntersectionInside + } + return IntersectionOutSide +} + +func (rng ring) length() int { + return len(rng) / 2 +} + +func (rng ring) point(i int) (float64, float64) { + i *= 2 + return rng[i], rng[i+1] +} + +type segments interface { + length() int + point(int) (float64, float64) +} + +func contains(s segments, pX, pY float64) bool { + + n := s.length() + if n < 3 { + return false + } + + sX, sY := s.point(0) + eX, eY := s.point(n - 1) + + const eps = 0.0000001 + + if math.Abs(sX-eX) > eps || math.Abs(sY-eY) > eps { + // It's not closed! + return false + } + + var inside bool + + for i := 1; i < n; i++ { + eX, eY := s.point(i) + if intersectsWithRaycast(pX, pY, sX, sY, eX, eY) { + inside = !inside + } + sX, sY = eX, eY + } + + return inside +} + +// Using the raycast algorithm, this returns whether or not the passed in point +// Intersects with the edge drawn by the passed in start and end points. +// Original implementation: http://rosettacode.org/wiki/Ray-casting_algorithm#Go +func intersectsWithRaycast(pX, pY, sX, sY, eX, eY float64) bool { + + // Always ensure that the the first point + // has a y coordinate that is less than the second point + if sY > eY { + // Switch the points if otherwise. + sX, sY, eX, eY = eX, eY, sX, sY + } + + // Move the point's y coordinate + // outside of the bounds of the testing region + // so we can start drawing a ray + for pY == sY || pY == eY { + pY = math.Nextafter(pY, math.Inf(1)) + } + + // If we are outside of the polygon, indicate so. + if pY < sY || pY > eY { + return false + } + + if sX > eX { + if pX > sX { + return false + } + if pX < eX { + return true + } + } else { + if pX > eX { + return false + } + if pX < sX { + return true + } + } + + raySlope := (pY - sY) / (pX - sX) + diagSlope := (eY - sY) / (eX - sX) + + return raySlope >= diagSlope +} + +func (p *Polygon) NumVertices(ring int) int { + if ring < 0 || ring >= len(p.rings) { + return 0 + } + return len(p.rings[ring]) / 2 +} + +func (p *Polygon) Vertices(ring int, fn func(float64, float64)) { + if ring < 0 || ring >= len(p.rings) { + return + } + rng := p.rings[ring] + for i := 0; i < len(rng); i += 2 { + fn(rng[i+0], rng[i+1]) + } +} + +func (p *Polygon) AsWKB() []byte { + + size := 1 + 4 + 4 + for _, r := range p.rings { + size += 4 + len(r)*8 + } + + buf := bytes.NewBuffer(make([]byte, 0, size)) + + binary.Write(buf, binary.LittleEndian, wkb.NDR) + binary.Write(buf, binary.LittleEndian, wkb.Polygon) + binary.Write(buf, binary.LittleEndian, uint32(len(p.rings))) + + for _, r := range p.rings { + binary.Write(buf, binary.LittleEndian, uint32(len(r)/2)) + for i := 0; i < len(r); i += 2 { + binary.Write(buf, binary.LittleEndian, math.Float64bits(r[i+0])) + binary.Write(buf, binary.LittleEndian, math.Float64bits(r[i+1])) + } + } + + return buf.Bytes() +} + +func (p *Polygon) FromWKB(data []byte) error { + + r := bytes.NewReader(data) + + endian, err := r.ReadByte() + + var order binary.ByteOrder + + switch { + case err != nil: + return err + case endian == wkb.NDR: + order = binary.LittleEndian + case endian == wkb.XDR: + order = binary.BigEndian + default: + return fmt.Errorf("unknown byte order %x", endian) + } + + var geomType uint32 + err = binary.Read(r, order, &geomType) + + switch { + case err != nil: + return err + case geomType != wkb.Polygon: + return fmt.Errorf("unknown geometry type %x", geomType) + } + + var numRings uint32 + if err = binary.Read(r, order, &numRings); err != nil { + return err + } + + rngs := make([]ring, numRings) + + log.Printf("info: Number of rings: %d\n", len(rngs)) + + for rng := uint32(0); rng < numRings; rng++ { + var numVertices uint32 + if err = binary.Read(r, order, &numVertices); err != nil { + return err + } + + log.Printf("info: Number of vertices in ring %d: %d\n", rng, numVertices) + + numVertices *= 2 + vertices := make([]float64, numVertices) + + for v := uint32(0); v < numVertices; v += 2 { + var lat, lon uint64 + if err = binary.Read(r, order, &lat); err != nil { + return err + } + if err = binary.Read(r, order, &lon); err != nil { + return err + } + vertices[v] = math.Float64frombits(lat) + vertices[v+1] = math.Float64frombits(lon) + } + + rngs[rng] = vertices + } + + p.rings = rngs + + return nil +} + +func (p *Polygon) FromLineStringZ(ls LineStringZ) { + r := make([]float64, 2*len(ls)) + var pos int + for i := range ls { + r[pos+0] = ls[i].X + r[pos+1] = ls[i].Y + pos += 2 + } + p.rings = []ring{r} +}