view pkg/octree/polygon.go @ 2490:c9164ff98871 octree-diff

Started with point in polygon check.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Sun, 03 Mar 2019 21:10:07 +0100
parents 4f292ff74d9e
children 10681749371d
line wrap: on
line source

// 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 octree

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"math"

	"gemma.intevation.de/gemma/pkg/wkb"
)

type ring []float64

type Polygon struct {
	// TODO: Implement me!
	rings []ring
}

type IntersectionType byte

const (
	IntersectionInside IntersectionType = iota
	IntersectionOutSide
	IntersectionOverlaps
)

func (p *Polygon) IntersectionBox2D(box Box2D) IntersectionType {
	// TODO: Implement me
	return IntersectionOutSide
}

func (p *Polygon) IntersectionWithTriangle(t *Triangle) IntersectionType {
	// TODO: Implement me
	return IntersectionOutSide
}

func (rng ring) isClosed() bool { return (len(rng) / 2) >= 3 }

func (rng ring) contains(point []float64) bool {
	if !rng.isClosed() {
		return false
	}

	contains := intersectsWithRaycast(point, rng[:2], rng[len(rng)-2:len(rng)])

	for i := 2; i < len(rng); i += 2 {
		if intersectsWithRaycast(point, rng[i-2:i], rng[i:i+2]) {
			contains = !contains
		}
	}

	return contains
}

// 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(point, start, end []float64) bool {

	// Always ensure that the the first point
	// has a y coordinate that is less than the second point
	if start[1] > end[1] {
		// Switch the points if otherwise.
		start, end = end, start
	}

	// Move the point's y coordinate
	// outside of the bounds of the testing region
	// so we can start drawing a ray
	for point[1] == start[1] || point[1] == end[1] {
		y := math.Nextafter(point[1], math.Inf(1))
		point = []float64{point[0], y}
	}

	// If we are outside of the polygon, indicate so.
	if point[1] < start[1] || point[1] > end[1] {
		return false
	}

	if start[0] > end[0] {
		if point[0] > start[0] {
			return false
		}
		if point[0] < end[0] {
			return true
		}
	} else {
		if point[0] > end[0] {
			return false
		}
		if point[0] < start[0] {
			return true
		}
	}

	raySlope := (point[1] - start[1]) / (point[0] - start[0])
	diagSlope := (end[1] - start[1]) / (end[0] - start[0])

	return raySlope >= diagSlope
}

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)

	for rng := uint32(0); rng < numRings; rng++ {
		var numVertices uint32
		if err = binary.Read(r, order, &numVertices); err != nil {
			return err
		}

		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
}