🌌

【Golang】.ptsファイルを読み込むpackageを公開してみた

2022/08/09に公開

はじめに

.ptsファイルという点群を扱っている形式のファイルを読み込むpackageを作成したのでその過程を記事にしました.

ちなみに,以下のレポジトリでpackageを公開しているので,ぜひ使ってみた感想を頂きたいです↓

Pythonでも下記のリポジトリで実装したのでぜひ見ていってください↓

.ptsファイル

みなさん,.ptsファイルってご存知ですか?

点群を扱うために Leica の定義した PTS (Point Cloud) ファイルフォーマット

らしいです.

ということで,本記事では以下の点群ファイルを扱います.

sample.pts
version: 1
n_points: 55
{
623.000 169.000
615.000 165.000
606.000 162.000
597.000 161.000
589.000 163.000
584.000 168.000
580.000 173.000
578.000 182.000
580.000 190.000
583.000 198.000
587.000 204.000
591.000 212.000
594.000 217.000
600.000 224.000
607.000 229.000
611.000 232.000
617.000 234.000
624.000 235.000
630.000 231.000
632.000 224.000
616.000 188.000
613.000 182.000
610.000 173.000
604.000 169.000
596.000 167.000
589.000 169.000
585.000 173.000
582.000 179.000
581.000 185.000
584.000 192.000
587.000 198.000
591.411 203.700
594.094 210.041
597.996 214.675
603.850 219.797
620.435 188.578
619.460 193.700
620.923 201.505
624.826 205.407
626.045 212.236
618.728 212.724
614.094 210.285
608.240 212.724
604.826 209.797
602.143 204.919
603.362 200.041
604.338 195.163
606.777 191.017
610.191 187.114
611.655 183.456
593.850 204.432
592.387 196.871
592.143 188.578
592.630 181.261
594.582 172.724
}

経緯

仕事で.ptsを扱うことがあった (Python) のですが,読み込むためのライブラリがなくて困ったので,腕試しを兼ねてGopackageを作成してみました.

コード

structの定義

ここでは,使用する構造体の定義をしていきます.

pts.go
type Points struct {
	Array    []Point
	Path     string
	N_points int
}

type Point struct {
	X, Y float64
}

.ptsファイルの読み込み

pts.go
import (
	"bufio"
	"fmt"
	"log"
	"math"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
)

func ReadPTS(path string) (points Points) {
	path, _ = filepath.Abs(path)
	file, err := os.Open(path)
	defer file.Close()
	if err != nil {
		log.Println(err)
	}

	var (
		n_points int
		arr      []Point
	)
	scanner := bufio.NewScanner(file)
	for i := 0; scanner.Scan(); i++ {
		text := scanner.Text()
		if i == 1 {
			n_points = findNumber(text)
		} else if 2 < i && i < n_points+3 {
			x, y := createPoint(text)
			arr = append(arr, Point{x, y})
		}
	}

	if err := scanner.Err(); err != nil {
		log.Println(err)
	}

	points = Points{
		Array:    arr,
		Path:     path,
		N_points: n_points,
	}

	return points
}

// 今回は外部 package からアクセスする必要がないので頭文字は小文字
func findNumber(text string) (n_points int) {
	num := regexp.MustCompile(`[0-9]+`)
	n := num.FindString(text)
	n_points, _ = strconv.Atoi(n)

	return n_points
}

// 今回は外部 package からアクセスする必要がないので頭文字は小文字
func createPoint(str string) (x, y float64) {
	array := strings.Split(str, " ")
	x, _ = strconv.ParseFloat(array[0], 64)
	y, _ = strconv.ParseFloat(array[1], 64)
	return x, y
}

コード全体

最後に今回作成したコードをまとめます.

pts.go
package pts

import (
	"bufio"
	"fmt"
	"log"
	"math"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
)

type PointsOperator interface {
	Mean() (float64 float64)
	Std() (float64 float64)
	String() string
	Var() (float64 float64)
}

type PointOperator interface {
	Norm() (float64, float64)
	String() string
}

type Points struct {
	Array    []Point
	Path     string
	N_points int
}

type Point struct {
	X, Y float64
}

func (point *Point) Norm() (x float64, y float64) {
	r := math.Sqrt(
		math.Pow(point.X, 2) + math.Pow(point.Y, 2),
	)
	x, y = point.X/r, point.Y/r

	return x, y
}

func (point *Point) String() (s string) {
	s = fmt.Sprintf(
		"x: %v, y: %v", point.X, point.Y,
	)

	return s
}

func (points *Points) Describe() (s string) {
	x_mean, y_mean := points.Mean()
	x_var, y_var := points.Var()
	x_std, y_std := points.Std()

	s = fmt.Sprintf(
		`
	x	y
Mean:	%v	%v
Var:	%v	%v
Std:	%v	%v
`, x_mean, y_mean, x_var, y_var, x_std, y_std,
	)

	return s
}

func (points *Points) Mean() (float64, float64) {
	var (
		x, y     float64
		n_points float64 = float64(points.N_points)
	)
	for _, point := range points.Array {
		x += point.X
		y += point.Y
	}

	return x / n_points, y / n_points
}

func (points *Points) Var() (float64, float64) {
	var (
		x, y float64
	)
	x_mean, y_mean := points.Mean()

	for _, point := range points.Array {
		x += math.Pow(point.X-x_mean, 2)
		y += math.Pow(point.Y-y_mean, 2)
	}

	return x / float64(points.N_points), y / float64(points.N_points)
}

func (points *Points) Std() (float64, float64) {
	x_var, y_var := points.Var()

	return math.Sqrt(x_var), math.Sqrt(y_var)
}

func (points *Points) String() (s string) {
	s = fmt.Sprintf(
		"The file named %v has %v points.",
		points.Path,
		points.N_points,
	)

	return s
}

func ReadPTS(path string) (points Points) {
	path, _ = filepath.Abs(path)
	file, err := os.Open(path)
	defer file.Close()
	if err != nil {
		log.Println(err)
	}

	var (
		n_points int
		arr      []Point
	)
	scanner := bufio.NewScanner(file)
	for i := 0; scanner.Scan(); i++ {
		text := scanner.Text()
		if i == 1 {
			n_points = findNumber(text)
		} else if 2 < i && i < n_points+3 {
			x, y := createPoint(text)
			arr = append(arr, Point{x, y})
		}
	}

	if err := scanner.Err(); err != nil {
		log.Println(err)
	}

	points = Points{
		Array:    arr,
		Path:     path,
		N_points: n_points,
	}

	return points
}

func findNumber(text string) (n_points int) {
	num := regexp.MustCompile(`[0-9]+`)
	n := num.FindString(text)
	n_points, _ = strconv.Atoi(n)

	return n_points
}

func createPoint(str string) (x, y float64) {
	array := strings.Split(str, " ")
	x, _ = strconv.ParseFloat(array[0], 64)
	y, _ = strconv.ParseFloat(array[1], 64)
	return x, y
}

実行サンプル

main.go
package main

import (
	"log"
	"pts"
)

func main() {
	path := "assets/sample.pts"
	points := pts.ReadPTS(path)

	log.Print(points.Describe())
}

おわりに

今回は,勉強し始めたGo.ptsの読み込みを行ってみました.
普段はPythonを使っているので Python-like になっているかもしれませんが,間違いや改善点などあったら優しく指摘して頂けると嬉しいです.

GitHubで編集を提案

Discussion