view cmd/soundingresults/points.go @ 960:e23ae2c83427

Load boundary polygon of sounding result from ZIP file.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Wed, 17 Oct 2018 12:23:39 +0200
parents bc2b7da07d60
children
line wrap: on
line source

package main

import (
	"bufio"
	"bytes"
	"compress/bzip2"
	"compress/gzip"
	"encoding/binary"
	"io"
	"log"
	"math"
	"os"
	"path/filepath"
	"strconv"
	"strings"
)

type point3d struct {
	x float64
	y float64
	z float64
}

type points3d []*point3d

func parseXYZ(fname string) (points3d, error) {
	f, err := os.Open(fname)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	r, err := wrap(fname, f)
	if err != nil {
		return nil, err
	}
	return readXYZ(r)
}

func wrap(fname string, f io.Reader) (io.Reader, error) {

	switch strings.ToLower(filepath.Ext(fname)) {
	case ".gz":
		return gzip.NewReader(f)
	case ".bz2":
		return bzip2.NewReader(f), nil
	}

	return bufio.NewReader(f), nil
}

func readXYZ(r io.Reader) (points3d, error) {

	// Alloc in larger chunks to reduce pressure on memory management.
	var chunk []point3d
	alloc := func() *point3d {
		if len(chunk) == 0 {
			chunk = make([]point3d, 8*1024)
		}
		p := &chunk[0]
		chunk = chunk[1:]
		return p
	}

	var points points3d

	s := bufio.NewScanner(r)
	if s.Scan() { // Skip header line.
		for line := 2; s.Scan(); line++ {
			p := alloc()
			text := s.Text()
			// fmt.Sscanf(text, "%f,%f,%f") is 4 times slower.
			idx := strings.IndexByte(text, ',')
			if idx == -1 {
				log.Printf("format error in line %d\n", line)
				continue
			}
			var err error
			if p.x, err = strconv.ParseFloat(text[:idx], 64); err != nil {
				log.Printf("format error in line %d: %v\n", line, err)
				continue
			}
			text = text[idx+1:]
			if idx = strings.IndexByte(text, ','); idx == -1 {
				log.Printf("format error in line %d\n", line)
				continue
			}
			if p.y, err = strconv.ParseFloat(text[:idx], 64); err != nil {
				log.Printf("format error in line %d: %v\n", line, err)
				continue
			}
			text = text[idx+1:]
			if p.z, err = strconv.ParseFloat(text, 64); err != nil {
				log.Printf("format error in line %d: %v\n", line, err)
				continue
			}
			points = append(points, p)
		}
	}

	return points, s.Err()
}

const (
	wkbNDR         byte   = 1
	wkbPointZ      uint32 = 1 + 1000
	wkbMultiPointZ uint32 = 4 + 1000
)

func (ps points3d) asWKB() string {

	size := 1 + 4 + 4 + len(ps)*(1+4+3*8)

	buf := bytes.NewBuffer(make([]byte, 0, size))

	binary.Write(buf, binary.LittleEndian, wkbNDR)
	binary.Write(buf, binary.LittleEndian, wkbMultiPointZ)
	binary.Write(buf, binary.LittleEndian, uint32(len(ps)))

	for _, p := range ps {
		binary.Write(buf, binary.LittleEndian, wkbNDR)
		binary.Write(buf, binary.LittleEndian, wkbPointZ)
		binary.Write(buf, binary.LittleEndian, math.Float64bits(p.x))
		binary.Write(buf, binary.LittleEndian, math.Float64bits(p.y))
		binary.Write(buf, binary.LittleEndian, math.Float64bits(p.z))
	}

	return buf.String()
}