指定された領域内で、乱数座標を発生させる2つの方式に関する事項

特定領域内で、乱数的な緯度・経度の作成を行うコードをChatGPTに考えて貰いました。

package main

import (
	"fmt"
	"math"
	"math/rand"
)

// Polygon represents a polygon defined by its vertices
type Polygon [][]float64

// Point represents a point with x and y coordinates
type Point struct {
	X float64
	Y float64
}

// isInPolygon checks if a point is inside a polygon using the ray casting algorithm
func (p Polygon) isInPolygon(point Point) bool {
	intersections := 0
	for i := 0; i < len(p); i++ {
		p1 := p[i]
		p2 := p[(i+1)%len(p)]

		if point.Y > math.Min(p1[1], p2[1]) && point.Y <= math.Max(p1[1], p2[1]) &&
			point.X <= math.Max(p1[0], p2[0]) && p1[1] != p2[1] {
			xIntersection := (point.Y-p1[1])*(p2[0]-p1[0])/(p2[1]-p1[1]) + p1[0]
			if p1[0] == p2[0] || point.X <= xIntersection {
				intersections++
			}
		}
	}
	return intersections%2 != 0
}

// generateRandomPointInPolygon generates a random point inside the given polygon
func generateRandomPointInPolygon(p Polygon) Point {
	var minX, minY, maxX, maxY float64
	minX = math.MaxFloat64
	minY = math.MaxFloat64
	maxX = -math.MaxFloat64
	maxY = -math.MaxFloat64

	// Find bounding box of the polygon
	for _, vertex := range p {
		if vertex[0] < minX {
			minX = vertex[0]
		}
		if vertex[1] < minY {
			minY = vertex[1]
		}
		if vertex[0] > maxX {
			maxX = vertex[0]
		}
		if vertex[1] > maxY {
			maxY = vertex[1]
		}
	}

	// Generate random points until a point inside the polygon is found
	var randomPoint Point
	for {
		randomPoint.X = rand.Float64()*(maxX-minX) + minX
		randomPoint.Y = rand.Float64()*(maxY-minY) + minY

		if p.isInPolygon(randomPoint) {
			break
		}
	}
	return randomPoint
}

func main() {
	// Define the polygon
	polygon := Polygon{
		{35.36394967, 139.61846500},
		{35.36216810, 139.61890850},
		{35.36170465505306, 139.6220415552594},
		{35.36163108058289, 139.62334070015595},
		{35.363117271878345, 139.62314221968566},
		{35.36335187635167, 139.62481739887647},
		{35.36310056060587, 139.62485183402688},
		{35.36376860001917, 139.6288186562702},
		{35.36420166714637, 139.6297897196359},
		{35.36754482323006, 139.6296024603071},
		{35.37126945661188, 139.62886244945108},
		{35.37375189636854, 139.62615207124352},
		{35.37464657021711, 139.623189740366},
		{35.37574882601201, 139.6213765671167},
		{35.37527643251494, 139.6210117866997},
		{35.37306314467156, 139.6217413475337},
		{35.37268696718477, 139.62013202216457},
		{35.37018828750506, 139.61840497406456},
		{35.370782160278, 139.61705458898427},
		{35.36947068533102, 139.61641732865053},
		{35.370596575495014, 139.61476348635583},
		{35.37078029225879, 139.61403393574466},
		{35.36864433631068, 139.61438212951467},
		{35.36653571408147, 139.61453772192408},
		{35.36394967, 139.61846500},
	}

	// Generate a random point inside the polygon
	randomPoint := generateRandomPointInPolygon(polygon)
	fmt.Println("Random point inside polygon:", randomPoint)
}

なるほど、と思いながらコードを読んでいたのですが、『あれ? これって、領域に入った緯度・経度の乱数を採用するだけだから、発生頻度が均一にならないんじゃないかな?』と思い、ChatGPTにたずねてみたら『Yes』と言われました。

で、発生頻度を均一になるコードを再度要求したのですが ―― コードを一目見て、『このコードは間違っているな』と分かるようなものが出てきました。

『ChatGPTは、もの凄く真摯な言葉で、嘘をつく』

で、昔のコードを探していたら、PostGISを使うものが出てきました。

// C:\Users\ebata\tomioka3B\src\others\main35.go
/*
	実験用の座標を作る
	修正後 (富岡西5丁目を削除し、富岡6丁目の下1/4を削除)

	このプログラムは、Go言語を使用してPostgreSQLデータベースから空間データを取得し、指定された多角形内に均等に配置された乱数の座標を生成します。以下はプログラムの概要です:

	パッケージのインポート: 必要なパッケージをインポートします。database/sql はデータベースの操作に使用され、fmt と log は出力とログのために使用されます。また、PostgreSQLデータベースへの接続に使用するドライバもインポートされます。
	main() 関数: プログラムのエントリーポイントです。まず、PostgreSQLデータベースに接続します。接続情報は、ユーザー名、パスワード、ホスト、ポート、およびデータベース名で構成されます。
	SQLクエリの実行: query 変数にSQLクエリが格納されています。このクエリは、指定された多角形の領域内に均等に配置された15000個の点を生成するものです。db.Query() を使用してクエリを実行し、結果を rows に格納します。
	結果の処理: rows.Next() を使用して、結果セットの各行を処理します。各行は msg として取得されます。次に、regexp パッケージを使用して、取得した座標データを処理します。
	座標データの分解: 取得した座標データは、MULTIPOINT 形式の文字列として提供されます。この文字列をバラバラに分解し、各点の緯度と経度を取得します。regexp.MustCompile("[() ,]").Split(msg, -1) を使用して、文字列を分割します。
	座標の表示: 緯度と経度のペアを取得し、それぞれを浮動小数点数に変換して表示します。

	このプログラムは、指定された多角形内に均等に分布する乱数の座標を生成し、それを取得して表示します。


*/

package main

import (
	"database/sql"
	"fmt"
	"log"
	"regexp"
	"strconv"

	_ "github.com/lib/pq"
)

func main() {
	// 取り扱うDBによってノード番号が代わるので注意すること
	// 例えばtomioka_db_c とtomioka_dbは全く異なる
	db, err := sql.Open("postgres",
		"user=postgres password=password host=192.168.0.23 port=15432 dbname=tomioka_db_f sslmode=disable")
	if err != nil {
		log.Fatal("OpenError: ", err)
	}
	defer db.Close()

	// 修正後のターゲットの富岡地区 (富岡西5丁目を削除し、富岡6丁目の下1/4を削除)

	query := `
	SELECT st_asText(
		ST_GeneratePoints(
			ST_GeomFromText(
				'POLYGON((
					35.36394967 139.61846500,
					35.36216810 139.61890850,
					35.36170465505306 139.6220415552594,
					35.36163108058289 139.62334070015595,
					35.363117271878345 139.62314221968566,
					35.36335187635167 139.62481739887647,
					35.36310056060587 139.62485183402688,
					35.36376860001917 139.6288186562702,
					35.36420166714637 139.6297897196359,
					35.36754482323006 139.6296024603071,
					35.37126945661188 139.62886244945108,
					35.37375189636854 139.62615207124352,
					35.37464657021711 139.623189740366,
					35.37574882601201 139.6213765671167,
					35.37527643251494 139.6210117866997,
					35.37306314467156 139.6217413475337,
					35.37268696718477 139.62013202216457,
					35.37018828750506 139.61840497406456,
					35.370782160278 139.61705458898427,
					35.36947068533102 139.61641732865053,
					35.370596575495014 139.61476348635583,
					35.37078029225879 139.61403393574466,
					35.36864433631068 139.61438212951467,
					35.36653571408147 139.61453772192408,
					35.36394967 139.61846500
				))'
			),
		15000)
	)
	`
	rows, err := db.Query(query) // この25が生成する乱数座標

	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()

	var msg string

	for rows.Next() {
		if err := rows.Scan(&msg); err != nil {
			fmt.Println(err)
		}

		// まずはMULTIPOINTをバラバラに分解する
		arr1 := regexp.MustCompile("[() ,]").Split(msg, -1) // '('か、')'か、' 'か、","で分割する → "[中身]" という構造でまぎらわしい

		// 2つの値を一度に取得する (最初の値" MULTIPOIN"をスキップする為に、i:=1から始める)
		for i := 1; i < len(arr1); i += 2 {
			// arr1[i]とarr1[i+1]は2つの値を表します
			if i+1 < len(arr1) {
				value1, _ := strconv.ParseFloat(arr1[i], 64)   // 乱数の緯度
				value2, _ := strconv.ParseFloat(arr1[i+1], 64) // 乱数の経度
				fmt.Println(i, value1, value2)
			}
		}
	}
}

ポイントは、指定された多角形内に均等に分布する乱数の座標を生成 という点です。

どういう仕組みか分かりませんし、ChatGPTが真実を言っているのかも不明ですが、『postGISなら、その程度の仕組みは入れてくるだろう』と期待して、今回は、こちらを使うことにしました。

以上

2024,江端さんの技術メモ

Posted by ebata