/*
c:\users\ebata\dscan\dscan3.go
DBSCANアルゴリズムを実装し、3次元空間のデータ(x、y、t)をクラスタリングするシンプルなプログラムです。
このプログラムでは、各データポイントは3次元座標(x、y、t)で表され、距離の計算にはユークリッド距離を使用します。
DBSCANをk-meansの違いで説明します。
DBSCAN(Density-Based Spatial Clustering of Applications with Noise)とk-meansは、クラスタリングアルゴリズムですが、そのアプローチや動作原理にはいくつかの違いがあります。
(1)クラスタリング方法:
- DBSCAN: 密度ベースのクラスタリングアルゴリズムであり、データポイントの密度に基づいてクラスタを形成します。各点は、一定の距離(ε、epsilon)内に最小限の近傍点数(minPts)が存在する場合、その点を中心としたクラスタが形成されます。
- k-means: 距離ベースのクラスタリングアルゴリズムであり、データポイントの距離に基づいてクラスタを形成します。クラスタの数(k)を事前に指定し、各点を最も近いセントロイド(クラスタの中心)に割り当てます。
(2)クラスタの形状:
- DBSCAN: クラスタの形状は任意であり、密度の高い領域に基づいて形成されます。したがって、DBSCANは非凸形状のクラスタを処理できます。
- k-means: クラスタの形状は円形(球形)であり、各クラスタのセントロイドからの距離に基づいて決定されます。したがって、k-meansは凸形状のクラスタを前提としています。
(3)ハイパーパラメータ:
- DBSCAN: ε(epsilon)とminPtsの2つのハイパーパラメータを必要とします。εは近傍点の距離の閾値を定義し、minPtsはクラスタと見なすための最小の近傍点数を指定します
- k-means: クラスタの数(k)を指定する必要があります。
(4)ノイズの処理:
- DBSCAN: ノイズポイントを自動的に検出し、外れ値として扱います。密度が低い領域に存在するポイントは、任意のクラスタに割り当てられず、ノイズとして扱われます。
-k-means: 外れ値やノイズの処理を明示的に行いません。各点は必ずどれかのクラスタに割り当てられます。
要するに、DBSCANは密度ベースのアルゴリズムであり、任意の形状のクラスタを検出し、ノイズを処理する能力があります。一方、k-meansは距離ベースのアルゴリズムであり、クラスタの形状が円形であることを前提としています。
*/
package main
import (
"fmt"
"math"
)
// Point represents a 3D point with coordinates x, y, and t
type Point struct {
X, Y, T float64
}
// DistanceTo calculates the Euclidean distance between two 3D points
func (p Point) DistanceTo(other Point) float64 {
dx := p.X - other.X
dy := p.Y - other.Y
dt := p.T - other.T
return math.Sqrt(dx*dx + dy*dy + dt*dt)
}
// Cluster represents a cluster of points
type Cluster struct {
Points []Point
}
// DBSCAN performs density-based clustering of 3D points
func DBSCAN(points []Point, epsilon float64, minPts int) []Cluster {
var clusters []Cluster
var visited = make(map[Point]bool)
for _, point := range points {
if visited[point] {
continue
}
visited[point] = true
neighbours := getNeighbours(points, point, epsilon)
if len(neighbours) < minPts {
continue
}
var clusterPoints []Point
expandCluster(&clusterPoints, points, visited, point, neighbours, epsilon, minPts)
clusters = append(clusters, Cluster{Points: clusterPoints})
}
return clusters
}
// getNeighbours returns all points within distance epsilon of the given point
func getNeighbours(points []Point, point Point, epsilon float64) []Point {
var neighbours []Point
for _, other := range points {
if point.DistanceTo(other) <= epsilon {
neighbours = append(neighbours, other)
}
}
return neighbours
}
// expandCluster expands the cluster from the given point
func expandCluster(cluster *[]Point, points []Point, visited map[Point]bool, point Point, neighbours []Point, epsilon float64, minPts int) {
*cluster = append(*cluster, point)
for _, neighbour := range neighbours {
if !visited[neighbour] {
visited[neighbour] = true
neighbourNeighbours := getNeighbours(points, neighbour, epsilon)
if len(neighbourNeighbours) >= minPts {
expandCluster(cluster, points, visited, neighbour, neighbourNeighbours, epsilon, minPts)
}
}
// Add neighbour to cluster if not already in another cluster
var isInCluster bool
for _, c := range *cluster {
if c == neighbour {
isInCluster = true
break
}
}
if !isInCluster {
*cluster = append(*cluster, neighbour)
}
}
}
func main() {
// Example usage
points := []Point{
{X: 1, Y: 2, T: 0},
{X: 1.5, Y: 1.8, T: 1},
{X: 5, Y: 8, T: 2},
{X: 8, Y: 8, T: 3},
{X: 1, Y: 0.6, T: 4},
{X: 9, Y: 11, T: 5},
{X: 8, Y: 2, T: 6},
{X: 10, Y: 2, T: 7},
{X: 9, Y: 3, T: 8},
}
epsilon := 3.0
minPts := 2
clusters := DBSCAN(points, epsilon, minPts)
for i, cluster := range clusters {
fmt.Printf("Cluster %d:\n", i+1)
for _, point := range cluster.Points {
fmt.Printf(" (%.2f, %.2f, %.2f)\n", point.X, point.Y, point.T)
}
}
}
3次元を取り扱うDBSCANプログラム
Webサーバに繋っているブラウザが、全部いなくなったと確認できた場合(30秒後)に、url = "http://localhost:8000/cancelAllVideoStreamGround"へメッセージを渡すプログラム
本プログラムは、
を拡張したものです。index.htmlは、このページに記載されているものを、そのまま、使って下さい。
こちらのプログラムでは、Webサーバに繋っているブラウザが、全部いなくなったと確認できた場合(30秒後)に、url = "http://localhost:8000/cancelAllVideoStreamGround"へメッセージを渡すプログラムです。
デバッグの為に使っていたコメントが残っていますので、適当に削除して下さい。
# c:\users\ebata\webMonitor.py
# https://wp.kobore.net/%e6%b1%9f%e7%ab%af%e3%81%95%e3%82%93%e3%81%ae%e6%8a%80%e8%a1%93%e3%83%a1%e3%83%a2/post-12475/を参照のこと
import threading
import time
import requests
import sys
from flask import Flask, request, jsonify
from requests.exceptions import Timeout
from flask_cors import CORS
app = Flask(__name__)
CORS(app) # すべてのリクエストに対してCORSを有効にする
last_heartbeat_time = 0
lock = threading.Lock()
def send_notification():
url = "http://localhost:8000/cancelAllVideoStreamGround"
try:
response = requests.post(url, timeout=3)
response.raise_for_status()
print("Notification sent successfully.")
sys.stdout.flush() # 標準出力をフラッシュする
except Timeout:
print("Error: Timeout while sending notification")
except requests.exceptions.RequestException as e:
print(f"Error sending notification: {e}")
def check_heartbeat():
global last_heartbeat_time
while True:
current_time = time.time()
print("ct:",current_time)
print("lht:",last_heartbeat_time)
diff = current_time - last_heartbeat_time
print("diff:",diff)
with lock:
if current_time - last_heartbeat_time > 30:
send_notification()
last_heartbeat_time = current_time
time.sleep(1)
@app.route('/heartbeat', methods=['POST'])
def receive_heartbeat():
print("pass receive_heartbeat()")
global last_heartbeat_time
data = request.get_json()
print(f"Received heartbeat: {data}")
sys.stdout.flush() # 標準出力をフラッシュする
with lock:
last_heartbeat_time = time.time()
print("lht_2:",last_heartbeat_time)
# data = request.get_json()
# print(f"Received heartbeat: {data}")
# sys.stdout.flush() # 標準出力をフラッシュする
return jsonify({"status": "OK"})
if __name__ == "__main__":
heartbeat_thread = threading.Thread(target=check_heartbeat)
heartbeat_thread.start()
app.run(host='0.0.0.0', port=3000)
私用のコメント
(1)receive_heartbeat():と receive_heartbeat()を同時に走らせる為に、スレッド化しなければならなくなった → で Ctrl-Cで止められなくなったので、ちょっと困っている(が、シェル画面ごと落せばいいので、現在はそうしている)
(2)url = "http://localhost:8000/cancelAllVideoStreamGround" からの時間がもたつく場合には、Timeoutを設定すると良いかもしれない。send_notification()を丸ごと差し替えると、こんな感じ。
def send_notification():
url = "http://localhost:8000/cancelAllVideoStreamGround"
try:
response = requests.post(url, timeout=3)
response.raise_for_status()
print("Notification sent successfully.")
sys.stdout.flush() # 標準出力をフラッシュする
except Timeout:
print("Error: Timeout while sending notification")
except requests.exceptions.RequestException as e:
print(f"Error sending notification: {e}")
(3)Webブラウザは、バックエンドに置いておくと、Heatbeatを出力しないことがあるので、常にアクティブにしておく必要があります(バックエンドのままにしておくと、cancelAllVideoStreamGroundが動き出します)
以上
Web版 MindManagerが「ファイルを開けませんでした」と言ってきた時の対応
を起動したら、
となることがあるので、この対応方法をメモしておきます。
https://app.mindmanager.com/ と入力(#my-filesはつけない)
ファイルが表示されるので、これを選択。
表示されるようになる。
のプロパティを開いて新しいURLをコピペする。
以上
Webサーバに繋っているブラウザが、全部いなくなったことを確認するプログラム
Webサーバに繋っているブラウザが、全部いなくなったことを確認する為に、ブラウザのJavaScriptからハートビートを飛ばして、ハートビートがこなくなったことを確認する簡易プログラムを作成しました。
■ブラウザの生存監視サーバ
$ pip install Flask
をしてから、以下のプログラムをhearbeat.pyという名前でサーバを立ち上げるフォルダ(例 c:\users\ebata)に放り込んでおく。
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/heartbeat', methods=['POST'])
def receive_heartbeat():
data = request.get_json()
print(f"Received heartbeat: {data}")
return jsonify({"status": "OK"})
if __name__ == "__main__":
app.run(host='0.0.0.0', port=3000)
■index.htmlの内容
これも、サーバを立ち上げるフォルダ(例 c:\users\ebata)に放り込んでおく。
<!DOCTYPE html>
<html>
<head>
<title>Heartbeat Example</title>
</head>
<body>
<h1>Heartbeat Sender</h1>
<script>
function sendHeartbeat() {
fetch('http://localhost:3000/heartbeat', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ timestamp: new Date() })
})
.then(response => response.json())
.then(data => console.log('Heartbeat sent:', data))
.catch(error => console.error('Error sending heartbeat:', error));
}
// 10秒ごとにハートビートを送信
setInterval(sendHeartbeat, 10000);
</script>
</body>
</html>
■起動方法
(Step 1)サーバを立ち上げるフォルダ(例 c:\users\ebata)で、
C:\Users\ebata>python heartbeat.py
で、「ブラウザの生存監視するサーバ」を起動
(Step 2)次に、以下のコマンドで、1行Webサーバを起動
C:\Users\ebata>python -m http.server 8000
(Step 3) ブラウザに、
http://localhost:8000/
を投入。複数のブラウザを立ち上げて、ブラウザの生存監視サーバから、
の表示が出てくれば成功 → ウソです。
ブラウザを全部落せば、動かなくなるはずです。→ これは本当ですが、def receive_heartbeat()の処理は全くされていません
Choromeの開発者の表示は、こんなのが出ていました。
Access to fetch at 'http://localhost:3000/heartbeat' from origin 'http://localhost:8000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled. (index):19 Error sending heartbeat: TypeError: Failed to fetch at sendHeartbeat ((index):10:13)
で、
このエラーメッセージは、CORS(Cross-Origin Resource Sharing)ポリシーに違反していることを示していて、これは、異なるオリジン(localhost:8000とlocalhost:3000)間でのリクエストがブラウザによってブロックされていることを意味します。
この問題を解決するために、FlaskアプリケーションでCORSポリシーを設定する必要があり、Flask-CORSという拡張機能を使用してこれを行うことができます。
pip install flask-cors
で、
from flask import Flask, request, jsonify
from flask_cors import CORSapp = Flask(__name__)
CORS(app) # すべてのリクエストに対してCORSを有効にする
を処理すれば、receive_heartbeat():
が実施されます。
で、修正後のコードは以下の通りです。
from flask import Flask, request, jsonify
from flask_cors import CORS
app = Flask(__name__)
CORS(app) # すべてのリクエストに対してCORSを有効にする
@app.route('/heartbeat', methods=['POST'])
def receive_heartbeat():
data = request.get_json()
print(f"Received heartbeat: {data}")
return jsonify({"status": "OK"})
if __name__ == "__main__":
app.run(host='0.0.0.0', port=3000)
出力結果
ちょっとまだ疑問はあるけど、とりあえず、メソッドの中には入ったようです。
GitHub Copilotをvscodeにアドインしたが、tabキーを押しても提案を採用できない
「まだChatGPTを使ってない人は『人生を悔い改めた方がいい』」 ―― と言った、ソフトバンクの孫社長に申し上げます。『いらんこと言うな』と。
にも書いていますが、
『当初、私は、ChatGPT(対話型AIアシスタント))、Grammerly(英語文章構成サービス), Deepl(翻訳サービス)を、無料で使ってきたのですが、私のそのサービスの利用頻度は、無料の範囲を越えてしまい、全て有料の会員となっています』
で、ここにAmazon Lightsailがあって、さらに、さくらインターネットのサービス(kobore.net)が入って、当然、ドメイン名の使用料も含まれていて、もう、これは、IT/AI搾取と称呼してもしても良いのではないかと思います。
で、先程、GitHub Copilot(10ドル/月)にも入りました ―― 貢いでいる対象を考えると「ホスト/ホステスに貢いだ方が楽しそう」です。金額の規模感は違いますが。
ジュニアに質問しにくいシニアにとっては、十分ペイする「お助けサービス」ではあるのですが ―― 私の人生、ハレがない とは思います。
それはさておき
先程まで、GitHub Copilotをvscodeにアドインしたが、tabキーを押しても提案を採用できないという問題に悩まされていました。
参考にさせて頂いたのは、こちら(https://mindtech.jp/?p=2330)のページです。
上記の"承諾する Tab"の部分をマウスでクリックすれば、確定はできるのですが、そんなコーディング作業、かったるくてやっていられません。
私の方も、やはり、Awesome Emacs Keymap と、vscode-emacs-indent が悪さをしていたようです。
これらを無効にした後、vscodeを再立ち上げしたら、コード確定ができるようにはなりましたが、当然ですが、emacsのキーバインドが使えなくなりました。しかし、私にはEmacsの環境が使えないコーディング環境は耐えられそうにありません(体が矯正不能)。
で、ちょっと試しに、Awesome Emacs Keymap を無効して、vscodeを再立ち上げして、GitHub Copilotを起動している状態で、Awesome Emacs Keymap を再び有効にしてみたら、両立に成功しているようです(すぐにボロが出るかもしれませんが)。
とりあえず、メモとして残しおきます。
またコケたら、こちらのメモに追記します。
fastapiの起動時にコマンドを起動して、そのままほったらかしにするコード
ChatGPTと相談しながら、fastapiの起動時にコマンドを起動する方法を試していたのですが、上手く動かない(最初のコマンドでロックしてしまう)ので、起動したまま、そのまま放置するコード(プロセスを落したければ、コマンドから自力でkillコマンドで落す必要ある)にしました。
import subprocess
from fastapi import FastAPI
app = FastAPI()
# 各コマンドを実行する関数
def run_command(command):
try:
# バックグラウンドでプロセスを実行
subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True)
except Exception as e:
print(f"コマンド '{command}' の実行中にエラーが発生しました: {str(e)}")
# 各コマンドを実行
commands = [
"sudo socat udp-listen:38089,reuseaddr,fork udp:192.168.3.153:38089",
"sudo socat udp-listen:38090,reuseaddr,fork udp:192.168.3.153:38090",
"sudo socat udp-listen:38091,reuseaddr,fork udp:192.168.3.153:38091"
]
for command in commands:
run_command(command)
@app.on_event("startup")
async def startup_event():
print("Application startup complete.")
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
fastapiのmain.pyを起動すると自動的に10秒おきに"hello"を自動的に表示するコードを作って下さい。
import asyncio
from fastapi import FastAPI
app = FastAPI()
async def hello_timer():
while True:
print("hello")
await asyncio.sleep(10) # 10秒待機
@app.on_event("startup")
async def startup_event():
# FastAPIアプリケーションの起動時にタイマーを開始
asyncio.create_task(hello_timer())
@app.get("/")
async def get_hello():
return {"message": "hello"}
座標を入力して一番近いpostGISのノードIDを検知して、ダイクストラ計算を行い、ユーザ心理を計算するファジィ推論を行うサンプルプログラム
/*
C:\Users\ebata\yamaguchi\src_light
■このサンプルプログラムのキモ
(1)座標を入力して一番近いpostGISのノードIDを検知して、ダイクストラ計算を行うこと
(2)ユーザ心理を計算するファジィ推論を行うこと
が入っているプログラムである。
■自動計算を実施するバッチ、dummy.batの内容は以下の通り
go run light-sim.go data/mod_20220522holyday_visitor.csv data/new_20220522holyday_visitor.csv
go run light-sim.go data/mod_20220521holyday_visitor.csv data/new_20220521holyday_visitor.csv
go run light-sim.go data/mod_20220522holyday.csv data/new_20220522holyday.csv
go run light-sim.go data/mod_20220518weekday.csv data/new_20220518weekday.csv
■その入力用のcsvファイルの一つである、"data/mod_20220522holyday_visitor.csv"の内容は以下の通り
34.172891,131.456346,34.182418,131.474987,21
34.163801,131.444142,34.164454,131.449142,62
34.158856,131.435881,34.164727,131.431189,52
(中略)
34.146351,131.461154,34.167045,131.448468,20
34.145639,131.449237,34.149603,131.432828,29
■"data/bike_stations.csv"の中身
index,station,address,lat,lon,initial_stock,max_stock
1,null,null,34.102543,131.392639,20,20
2,null,null,34.102543,131.392639,20,20
3,null,null,34.102543,131.392639,20,20
4,山口県庁前バス停,山口市春日町2086-4,34.183621,131.471688,20,20
5,null,null,34.102543,131.392639,20,20
6,山口市役所 駐輪場,山口市亀山町2-1,34.178056,131.474204,20,20
7,一の坂川交通交流広場,山口市中河原7-1,34.17960577,131.4783006,20,20
8,null,null,34.102543,131.392639,20,20
9,コープやまぐちこことどうもん店 駐輪場,山口市道場門前1-1-18,34.174234,131.474885,20,20
10,山口駅 駐輪場,山口市惣太夫町288-9,34.172219,131.480013,20,20
11,山口市教育委員会 駐輪場,山口市中央5-14-22,34.170346,131.46915,20,20
12,null,null,34.102543,131.392639,20,20
13,ファミリーマート山口泉都町店,山口市泉都町9-2,34.167346,131.462048,20,20
14,防長苑,山口市熊野町4-29,34.167204,131.45973,20,20
15,null,null,34.102543,131.392639,20,20
16,ホテルニュータナカ,山口市湯田温泉2-6-24,34.163937,131.456115,20,20
17,null,null,34.102543,131.392639,20,20
18,湯田温泉駅 駐輪場,山口市今井町146-6,34.159954,131.459967,20,20
19,アルク平川店,山口市平井724-1,34.152742,131.464199,20,20
20,山口大学(正門),山口市吉田1677-1,34.149852,131.466214,20,20
21,小郡総合支所 駐輪場,山口市小郡下郷609番地1,34.102543,131.392639,20,20
22,KDDI維新ホール 駐輪場,山口市小郡令和1丁目1番地,34.09368,131.394,20,20
23,風の並木通り(新山口駅南口側),山口市小郡金町1-1付近,34.092322,131.397667,20,20
24,平成公園 駐車場内,山口市小郡平成町3-1,34.088168,131.401698,20,20
25,null,null,34.102543,131.392639,20,20
26,アルク小郡店,山口市小郡下郷2273番地1,34.097135,131.391295,20,20
27,null,null,34.102543,131.392639,20,20
*/
package main
import (
"database/sql"
"encoding/csv"
"fmt"
"log"
"os"
"strconv"
_ "github.com/lib/pq"
)
// 自転車の型
type BikeParam struct {
id int // 自転車の識別番号
destinationId int // 出発座標番号(*1)
arrivalId int // 到着座標番号
// (*1)
// 名前がdestination(目的地)となっているのは、バスがユーザを迎えに行き、載せた時点が「出発」扱いであった名残。
// 自転車の場合は迎えに行く動作が無いので、名称変更が望ましい。
}
// 座標情報
type LocInfo struct {
Lng float64 // 経度
Lat float64 // 緯度
Source int // 地図DBのID
}
// 自転車の拠点
type BikeStation struct {
location LocInfo // 拠点の位置
initialStock int // 最初に配置する自転車の台数
stationName string // 拠点の名称
}
// ステーションからの自転車の出入り
type StationStock struct {
outgoing int
incoming int
}
var stationstock [40]StationStock
const STATIONS_PATH string = "data/bike_stations.csv"
// 拠点情報を読み込み、BikeStation型の配列を返す
func getStationInfo(dbMap *sql.DB) []BikeStation {
// ファイルをオープン
csvFile, err := os.Open(STATIONS_PATH)
if err != nil {
log.Fatal(err)
}
defer csvFile.Close()
// CSVファイルの中身を読み込み
r := csv.NewReader(csvFile)
rows, err := r.ReadAll()
if err != nil {
log.Fatal(err)
}
stationInfo := []BikeStation{} // 出力変数
// 行ごとに
for i, row := range rows {
if i == 0 {
continue // CSVのヘッダー行を無視
}
lat, err := strconv.ParseFloat(row[3], 64)
if err != nil {
log.Fatal(err)
}
lng, err := strconv.ParseFloat(row[4], 64)
if err != nil {
log.Fatal(err)
}
initialStockVal, err := strconv.Atoi(row[5])
if err != nil {
log.Fatal(err)
}
stationNameVal := row[1]
// CSV読み込み時点の座標のログ(地図DBによって補正する前の座標)
//log.Println("csv read result:", lng, lat, initialStock)
// 地図DBを参照することによって、ステーションの位置をノードの位置にする
source, lngModified, latModified := fixPosition(dbMap, lng, lat)
loc := LocInfo{
Lng: lngModified,
Lat: latModified,
Source: source,
}
// 読み込めていることの確認ログ
//log.Println("Station", (i - 1), lngModified, latModified, initialStock, source)
bikeStation := BikeStation{
location: loc,
initialStock: initialStockVal,
stationName: stationNameVal,
}
stationInfo = append(stationInfo, bikeStation)
}
return stationInfo
}
// Scan用の仮変数
var source int
var longitude float64
var latitude float64
var dist float64
// 指定した座標に近いDB上の座標を取得
func fixPosition(db *sql.DB, _x1, _y1 float64) (int, float64, float64) {
upperLimitMeter := 1500.0 // 近傍ノードの上限を1500 mに設定
str := fmt.Sprintf(
// 修正前: ways (道) の中から最近傍を取得
// "SELECT source, x1 AS longitude, y1 AS latitude, ST_Distance('SRID=4326;POINT(%v %v)'::GEOGRAPHY, the_geom) AS dist FROM ways WHERE ST_DWithin(the_geom, ST_GeographyFromText('SRID=4326;POINT(%v %v)'), %.1f) ORDER BY dist LIMIT 1",
// 修正後: ways_vertices_pgr (点座標) の中から最近傍を取得
"SELECT id AS source, lon AS longitude, lat AS latitude, ST_Distance('SRID=4326;POINT(%v %v)'::GEOGRAPHY, the_geom) AS dist FROM ways_vertices_pgr WHERE ST_DWithin(the_geom, ST_GeographyFromText('SRID=4326;POINT(%v %v)'), %.1f) ORDER BY dist LIMIT 1",
_x1, _y1, _x1, _y1, upperLimitMeter,
)
//fmt.Println(str)
rows, err := db.Query(str)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
foundGoodMapNode := false
for rows.Next() {
foundGoodMapNode = true
if err := rows.Scan(&source, &longitude, &latitude, &dist); err != nil {
fmt.Println(err)
}
//fmt.Println(source, longitude, latitude, dist)
}
if !foundGoodMapNode {
log.Println("Warning: in func fixPosition: Good Map Node not found for query point (",
_x1, ",", _y1, ")")
}
return source, longitude, latitude
}
/*
func getShortestDistanceToBikeStation(dbMap *sql.DB, node int) int {
StationId := -1
distance := 1000000.0
for i := 0; i < 2; i++ {
rowsDijkstra, errDijkstra := dbMap.Query(
"SELECT seq,source, target, x1, y1, x2, y2, agg_cost FROM pgr_dijkstra('SELECT gid as id, source, target, length_m as cost FROM ways', $1::bigint , $2::bigint , directed:=false) a INNER JOIN ways b ON (a.edge = b.gid) ORDER BY seq",
node,
Station[i].Node)
if errDijkstra != nil {
log.Fatal(errDijkstra)
}
defer rowsDijkstra.Close()
var agg_cost float64
for rowsDijkstra.Next() {
var x1, y1, x2, y2 float64
var seq, source, target int
err := rowsDijkstra.Scan(&seq, &source, &target, &x1, &y1, &x2, &y2, &agg_cost)
if err != nil {
fmt.Println(err)
}
}
if distance > agg_cost {
distance = agg_cost
StationId = i
}
}
return StationId
}
*/
// 江端修正版
func getDijkstraPath(dbMap *sql.DB, locInfoStart, locInfoGoal LocInfo) ([]LocInfo, float64) {
//log.Println("getDijkstraPath", locInfoStart, locInfoGoal)
var path []LocInfo // 経路 (返り値の一つ目)
var totalDistanceKm float64
rowsDijkstra, errDijkstra := dbMap.Query(
"SELECT seq,source, target, x1, y1, x2, y2, agg_cost FROM pgr_dijkstra('SELECT gid as id, source, target, length_m as cost FROM ways', $1::bigint , $2::bigint , directed:=false) a INNER JOIN ways b ON (a.edge = b.gid) ORDER BY seq",
locInfoStart.Source,
locInfoGoal.Source)
if errDijkstra != nil {
log.Fatal(errDijkstra)
}
defer rowsDijkstra.Close()
var agg_cost float64
isFirstCheck := true
isSourceCheck := true
for rowsDijkstra.Next() {
var x1, y1, x2, y2 float64
var seq int
var target int
// まずnodeを読む
if err := rowsDijkstra.Scan(&seq, &source, &target, &x1, &y1, &x2, &y2, &agg_cost); err != nil {
fmt.Println(err)
}
// 最初の1回だけ入る
if isFirstCheck {
if source == locInfoStart.Source {
isSourceCheck = true
} else {
isSourceCheck = false
}
isFirstCheck = false
}
var loc LocInfo
if isSourceCheck {
loc.Source = source
loc.Lng = x1
loc.Lat = y1
} else {
loc.Source = target
loc.Lng = x2
loc.Lat = y2
}
loc.Source = target
path = append(path, loc)
}
// ラストノードだけは手入力
path = append(path, locInfoGoal)
totalDistanceKm = agg_cost / 1000.0
return path, totalDistanceKm
}
// 一番近いステーションのIDを取得
func getNearestStation(dbMap *sql.DB, stationInfo []BikeStation, queryLocation LocInfo) (int, float64) {
bestDistanceKm := 1000.0 // 十分に大きい数
var bestStationId int
for i := 0; i < len(stationInfo); i++ {
// ダイクストラ法による経路で決定する距離
// SQLクエリを繰り返し実行するため、処理が遅くなる可能性がある
_, distKm := getDijkstraPath(dbMap, queryLocation, stationInfo[i].location)
// 直線距離の概算値(代替の計算方法)
//distKm, _ := distanceKm(queryLocation.Lng, queryLocation.Lat,
// stationInfo[i].location.Lng, stationInfo[i].location.Lat)
//log.Println("to station", i, "distanceKm=", distKm)
if distKm < bestDistanceKm {
bestDistanceKm = distKm
bestStationId = i
}
}
return bestStationId, bestDistanceKm
}
func main() {
dbMap, err := sql.Open("postgres",
"user=postgres password=password host=192.168.0.23 port=15432 dbname=yama_db sslmode=disable")
log.Println("------------------ map db open ------------------")
if err != nil {
log.Fatal("OpenError: ", err)
}
defer dbMap.Close()
// バイクステーション情報の読み込み
stationInfo := getStationInfo(dbMap)
//fmt.Println(stationInfo)
//file2, err2 := os.Open("data/new_20220518weekday.csv")
file2, err2 := os.Open(os.Args[1])
if err2 != nil {
log.Fatal(err2)
}
defer file2.Close()
r2 := csv.NewReader(file2)
rows2, err2 := r2.ReadAll() // csvを一度に全て読み込む
if err != nil {
log.Fatal(err2)
}
//file3, err3 := os.Create("data/calc2_new_20220518weekday.csv") // 第2パラメータ
file3, err3 := os.Create(os.Args[2]) // 第2パラメータ
if err3 != nil {
panic(err)
}
w := csv.NewWriter(file3)
output := []string{"id", "age", "origin_loc_Lng", "origin_loc_Lat", "dest_loc_Lng", "dest_loc_Lat", "distance_from_origin", "distance_between_stations", "distance_to_dest", "complain"}
if err = w.Write(output); err != nil {
log.Fatal(err)
}
for id, row := range rows2 {
/*
origin_lng := 131.4686813247102
origin_lat := 34.17901518198008
dest_lng := 131.45836175237153
dest_lat := 34.160484344205294
*/
origin_lng, err := strconv.ParseFloat(row[1], 64)
if err != nil {
log.Fatal(err)
}
origin_lat, err := strconv.ParseFloat(row[0], 64)
if err != nil {
log.Fatal(err)
}
dest_lng, err := strconv.ParseFloat(row[3], 64)
if err != nil {
log.Fatal(err)
}
dest_lat, err := strconv.ParseFloat(row[2], 64)
if err != nil {
log.Fatal(err)
}
age, err := strconv.ParseFloat(row[4], 64) // 年齢も実数扱いする
if err != nil {
log.Fatal(err)
}
// 最接近のステーションを選ぶ
var origin_loc, dest_loc LocInfo
origin_loc.Source, origin_loc.Lng, origin_loc.Lat = fixPosition(dbMap, origin_lng, origin_lat)
dest_loc.Source, dest_loc.Lng, dest_loc.Lat = fixPosition(dbMap, dest_lng, dest_lat)
stationId_from_origin, distance_from_origin := getNearestStation(dbMap, stationInfo, origin_loc) // Originから最初のステーション
stationId_to_dest, distance_to_dest := getNearestStation(dbMap, stationInfo, dest_loc) // 最後のステーションからDest
fmt.Println(stationInfo[stationId_from_origin])
stationstock[stationId_from_origin].outgoing++
fmt.Println(stationInfo[stationId_to_dest])
stationstock[stationId_to_dest].incoming++
_, distance_between_stations := getDijkstraPath(dbMap, stationInfo[stationId_from_origin].location, stationInfo[stationId_to_dest].location)
_, shortest_distance_total := getDijkstraPath(dbMap, origin_loc, dest_loc)
fmt.Println("stationId_from_origin, distance_from_origin", stationId_from_origin, distance_from_origin)
fmt.Println("stationId_to_dest, distance_to_dest", stationId_to_dest, distance_to_dest)
fmt.Println("distance_between_stations", distance_between_stations)
fmt.Println("shortest_distance_total", shortest_distance_total)
// 出発 ― _x[km]の歩行 ― 最初のステーション ― _y[km]の自転車走行 ― 最後のステーション ― _[km]の歩行 ― 到着
complain := fuzzy_reasoning(distance_from_origin, distance_between_stations, distance_to_dest, age)
fmt.Println("complain", complain)
output := []string{
fmt.Sprint(id),
fmt.Sprint(age),
fmt.Sprint(origin_loc.Lng),
fmt.Sprint(origin_loc.Lat),
fmt.Sprint(dest_loc.Lng),
fmt.Sprint(dest_loc.Lat),
fmt.Sprint(distance_from_origin),
fmt.Sprint(distance_between_stations),
fmt.Sprint(distance_to_dest),
fmt.Sprint(complain)}
fmt.Println(output)
if err = w.Write(output); err != nil {
log.Fatal(err)
}
}
// test
output = []string{"stationid", "outgoing", "incoming"}
if err = w.Write(output); err != nil {
log.Fatal(err)
}
for i := 0; i < 40; i++ {
output = []string{fmt.Sprint(i + 1), fmt.Sprint(stationstock[i].outgoing), fmt.Sprint(stationstock[i].incoming)}
// i+1しているのは、1からスタートする為
if err = w.Write(output); err != nil {
log.Fatal(err)
}
}
defer w.Flush()
if err := w.Error(); err != nil {
log.Fatal(err)
}
}
func fuzzy_reasoning(distance_from_origin, distance_between_stations, distance_to_dest, age float64) float64 {
////// パラメータの作成
// 絶対的歩行距離
walk := min_2(distance_from_origin, distance_to_dest)
// 総体的自転車移動距離
ratio := distance_between_stations / (distance_from_origin + distance_between_stations + distance_to_dest)
// 絶対的自転車移動距離
bike := distance_between_stations
// Age(前件部)
Age_Less := new_condition_MF3(45, 20, "LESS")
Age_Common := new_condition_MF3(45, 20, "COMMON") // 中央が 42歳
Age_More := new_condition_MF3(45, 20, "MORE")
// 絶対的歩行距離(前件部)
Walk_Less := new_condition_MF3(0.4, 0.1, "LESS")
Walk_Common := new_condition_MF3(0.4, 0.1, "COMMON")
Walk_More := new_condition_MF3(0.4, 0.1, "MORE")
// 相対的自転車移動比率(former)
Bike_Ratio_Less := new_condition_MF3(0.7, 0.1, "LESS")
Bike_Ratio_Common := new_condition_MF3(0.7, 0.1, "COMMON")
Bike_Ratio_More := new_condition_MF3(0.7, 0.1, "MORE")
// 絶対的自転車距離(前件部)
Bike_Less := new_condition_MF3(1.5, 1.0, "LESS")
Bike_Common := new_condition_MF3(1.5, 1.0, "COMMON")
Bike_More := new_condition_MF3(1.5, 1.0, "MORE")
// Complain(後件部)
Complain_LessLess := new_action_MF5(0.5, 0.25, "LESSLESS")
Complain_Less := new_action_MF5(0.5, 0.25, "LESS")
Complain_Common := new_action_MF5(0.5, 0.25, "COMMON")
Complain_More := new_action_MF5(0.5, 0.25, "MORE")
Complain_MoreMore := new_action_MF5(0.5, 0.25, "MOREMORE")
// [Rule A00]
Rule_A00 := min_2(Age_Less.func_X(age), Walk_Less.func_X(walk))
Complain_LessLess.func_Max(Rule_A00)
//fmt.Println("Rule_A00", Rule_A00)
// [Rule A01]
Rule_A01 := min_2(Age_Less.func_X(age), Walk_Common.func_X(walk))
Complain_Less.func_Max(Rule_A01)
//fmt.Println("Rule_A01", Rule_A01)
// [Rule A02]
Rule_A02 := min_2(Age_Less.func_X(age), Walk_More.func_X(walk))
Complain_Common.func_Max(Rule_A02)
//fmt.Println("Rule_A02", Rule_A02)
// [Rule A10]
Rule_A10 := min_2(Age_Common.func_X(age), Walk_Less.func_X(walk))
Complain_Common.func_Max(Rule_A10)
//fmt.Println("Rule_A10", Rule_A10)
// [Rule A11]
Rule_A11 := min_2(Age_Common.func_X(age), Walk_Common.func_X(walk))
Complain_Common.func_Max(Rule_A11)
//fmt.Println("Rule_A11", Rule_A11)
// [Rule A12]
Rule_A12 := min_2(Age_Common.func_X(age), Walk_More.func_X(walk))
Complain_More.func_Max(Rule_A12)
//fmt.Println("Rule_A12", Rule_A12)
// [Rule A20]
Rule_A20 := min_2(Age_More.func_X(age), Walk_Less.func_X(walk))
Complain_Common.func_Max(Rule_A20)
//fmt.Println("Rule_A20", Rule_A20)
// [Rule A21]
Rule_A21 := min_2(Age_More.func_X(age), Walk_Common.func_X(walk))
Complain_More.func_Max(Rule_A21)
//fmt.Println("Rule_A21", Rule_A21)
// [Rule A22]
Rule_A22 := min_2(Age_More.func_X(age), Walk_More.func_X(walk))
Complain_MoreMore.func_Max(Rule_A22)
//fmt.Println("Rule_A22", Rule_A22)
// [Rule B00]
Rule_B00 := Bike_Ratio_Less.func_X(ratio)
Complain_MoreMore.func_Max(Rule_B00)
//fmt.Println("Rule_B00", Rule_B00)
// [Rule B01]
Rule_B01 := Bike_Ratio_Common.func_X(ratio)
Complain_Common.func_Max(Rule_B01)
//fmt.Println("Rule_B01", Rule_B01)
// [Rule B02]
Rule_B02 := Bike_Ratio_More.func_X(ratio)
Complain_LessLess.func_Max(Rule_B02)
//fmt.Println("Rule_B02", Rule_B02)
// [Rule C00]
Rule_C00 := min_2(Age_Less.func_X(age), Bike_Less.func_X(bike))
Complain_LessLess.func_Max(Rule_C00)
//fmt.Println("Rule_C00", Rule_C00)
// [Rule C01]
Rule_C01 := min_2(Age_Less.func_X(age), Bike_Common.func_X(bike))
Complain_LessLess.func_Max(Rule_C01)
//fmt.Println("Rule_C01", Rule_C01)
// [Rule C02]
Rule_C02 := min_2(Age_Less.func_X(age), Bike_More.func_X(bike))
Complain_LessLess.func_Max(Rule_C02)
//fmt.Println("Rule_C02", Rule_C02)
// [Rule C10]
Rule_C10 := min_2(Age_Common.func_X(age), Bike_Less.func_X(bike))
Complain_Less.func_Max(Rule_C10)
//fmt.Println("Rule_C10", Rule_C10)
// [Rule C11]
Rule_C11 := min_2(Age_Common.func_X(age), Bike_Common.func_X(bike))
Complain_Common.func_Max(Rule_C11)
//fmt.Println("Rule_C11", Rule_C11)
// [Rule C12]
Rule_C12 := min_2(Age_Common.func_X(age), Bike_More.func_X(bike))
Complain_More.func_Max(Rule_C12)
//fmt.Println("Rule_C12", Rule_C12)
// [Rule C20]
Rule_C20 := min_2(Age_More.func_X(age), Bike_Less.func_X(bike))
Complain_Common.func_Max(Rule_C20)
//fmt.Println("Rule_C20", Rule_C20)
// [Rule C21]
Rule_C21 := min_2(Age_More.func_X(age), Bike_Common.func_X(bike))
Complain_More.func_Max(Rule_C21)
//fmt.Println("Rule_C21", Rule_C21)
// [Rule C22]
Rule_C22 := min_2(Age_More.func_X(age), Bike_More.func_X(bike))
Complain_MoreMore.func_Max(Rule_C22)
//fmt.Println("Rule_C22", Rule_C22)
// Reasoning calculations
numerator :=
Complain_LessLess.func_X()*Complain_LessLess.func_Y() +
Complain_Less.func_X()*Complain_Less.func_Y() +
Complain_Common.func_X()*Complain_Common.func_Y() +
Complain_More.func_X()*Complain_More.func_Y() +
Complain_MoreMore.func_X()*Complain_MoreMore.func_Y()
denominator :=
Complain_LessLess.func_Y() +
Complain_Less.func_Y() +
Complain_Common.func_Y() +
Complain_More.func_Y() +
Complain_MoreMore.func_Y()
reasoning := numerator / denominator
return reasoning
}
func max_2(a, b float64) float64 {
if a > b {
return a
} else {
return b
}
}
func min_2(a, b float64) float64 {
if a > b {
return b
} else {
return a
}
}
type condition_MF3 struct { // Base class for condition_MF3
center float64
width float64
express string
}
func new_condition_MF3(_center, _width float64, _express string) *condition_MF3 {
c3 := new(condition_MF3)
c3.center = _center
c3.width = _width
c3.express = _express
return c3
}
// Class for the membership function (3 mountains) of the former case
func (c3 *condition_MF3) func_X(_x float64) float64 {
// x,y denote coordinates on the membership function
x := _x
y := 0.0 // The value of y is always greater than or equal to 0 and less than or equal to 1
if c3.express == "LESS" {
if x <= c3.center-c3.width {
y = 1.0
} else if x <= c3.center {
y = -1.0 / c3.width * (x - c3.center)
} else {
y = 0.0
}
} else if c3.express == "COMMON" {
if x <= c3.center-c3.width {
y = 0.0
} else if x <= c3.center {
y = 1.0/c3.width*(x-c3.center) + 1.0
} else if x <= c3.center+c3.width {
y = -1.0/c3.width*(x-c3.center) + 1.0
} else {
y = 0.0
}
} else if c3.express == "MORE" {
if x <= c3.center {
y = 0.0
} else if x <= c3.center+c3.width {
y = 1.0 / c3.width * (x - c3.center)
} else {
y = 1.0
}
} else {
fmt.Println("MF3: wrong expression")
os.Exit(1)
}
return y
}
type condition_MF5 struct { // Base class for condition_MF5
center float64
width float64
express string
}
func new_condition_MF5(_center, _width float64, _express string) *condition_MF5 {
c5 := new(condition_MF5)
c5.center = _center
c5.width = _width
c5.express = _express
return c5
}
func (c5 *condition_MF5) func_X(_x float64) float64 {
// Class for the former membership function (5 mountains)
// x,y are the coordinates on the membership function
x := _x
y := 0.0 // The value of y is always greater than or equal to 0 and less than or equal to 1
if c5.express == "LESSLESS" {
if x <= c5.center-2.0*c5.width {
y = 1.0
} else if x <= c5.center-c5.width {
y = -1.0/c5.width*(x-(c5.center-2.0*c5.width)) + 1.0
} else {
y = 0.0
}
} else if c5.express == "LESS" {
if x <= c5.center-2.0*c5.width {
y = 0.0
} else if x <= c5.center-c5.width {
y = 1.0/c5.width*(x-(c5.center-c5.width)) + 1.0
} else if x <= c5.center {
y = -1.0/c5.width*(x-(c5.center-c5.width)) + 1.0
} else {
y = 0.0
}
} else if c5.express == "COMMON" {
if x <= c5.center-c5.width {
y = 0.0
} else if x <= c5.center {
y = 1.0/c5.width*(x-c5.center) + 1.0
} else if x <= c5.center+c5.width {
y = -1.0/c5.width*(x-c5.center) + 1.0
} else {
y = 0.0
}
} else if c5.express == "MORE" {
if x <= c5.center {
y = 0.0
} else if x <= c5.center+c5.width {
y = 1.0/c5.width*(x-(c5.center+c5.width)) + 1.0
} else if x <= c5.center+2.0*c5.width {
y = -1.0/c5.width*(x-(c5.center+c5.width)) + 1.0
} else {
y = 0.0
}
} else if c5.express == "MOREMORE" {
if x <= c5.center+c5.width {
y = 0.0
} else if x <= c5.center+2.0*c5.width {
y = 1.0/c5.width*(x-(c5.center+2.0*c5.width)) + 1.0
} else {
y = 1.0
}
} else {
fmt.Println("MF5 func_X(): wrong expression")
os.Exit(1)
}
return y
}
/////////////////////////////
type action_MF5 struct { // Base class for action_MF5
center float64
width float64
express string
x float64
y float64
}
type action_MF3 struct { // Base class for action_MF3
center float64
width float64
express string
x float64
y float64
}
func new_action_MF5(_center, _width float64, _express string) *action_MF5 {
a5 := new(action_MF5)
a5.center = _center
a5.width = _width
a5.express = _express
if a5.express == "LESSLESS" {
a5.x = a5.center - 2.0*a5.width
} else if a5.express == "LESS" {
a5.x = a5.center - a5.width
} else if a5.express == "COMMON" {
a5.x = a5.center
} else if a5.express == "MORE" {
a5.x = a5.center + a5.width
} else if a5.express == "MOREMORE" {
a5.x = a5.center + 2.0*a5.width
} else {
fmt.Println("new_action_MF5: wrong scale expression")
os.Exit(-1)
}
a5.y = 0.0
return a5
}
func new_action_MF3(_center, _width float64, _express string) *action_MF3 {
a3 := new(action_MF3)
a3.center = _center
a3.width = _width
a3.express = _express
if a3.express == "LESS" {
a3.x = a3.center - a3.width
} else if a3.express == "COMMON" {
a3.x = a3.center
} else if a3.express == "MORE" {
a3.x = a3.center + a3.width
} else {
fmt.Println("new_action_MF3: wrong scale expression")
os.Exit(-1)
}
a3.y = 0.0
return a3
}
// The latter membership function (5 mountains) class
func (a5 *action_MF5) func_Y() float64 {
return a5.y
}
// The latter membership function (3 mountains) class
func (a3 *action_MF3) func_Y() float64 {
return a3.y
}
func (a5 *action_MF5) func_Max(b float64) {
a5.y = max_2(b, a5.y)
}
func (a3 *action_MF3) func_Max(b float64) {
a3.y = max_2(b, a3.y)
}
func (a5 *action_MF5) func_X() float64 {
return a5.x
}
func (a3 *action_MF3) func_X() float64 {
return a3.x
}
ある領域の中に任意の数の乱数座標を作る方法 + golang で文字列分割 (regexp.MustCompile("[() ,]").Split(msg, -1))
などと苦労しているのですが、結局のところ、私は地図のある領域を指定して、任意の緯度・軽度情報を出す乱数を作りたかったのです。
「川の中から人が歩き始める」とか「山の中で人が消える」とか、シミュレーションと言えども、ちょっと許されない設定だと思いまして。
で、上記の記事を読まれた師匠のSさんから「こんなのがあるよ」と教え貰いました。
https://aginfo.cgk.affrc.go.jp/docs/pgisman/2.3.0/ST_GeneratePoints.html
適当なdbに接続して、例題を試してみました。
yama_db=# SELECT ST_GeneratePoints(ST_Buffer(ST_GeomFromText('LINESTRING(50 50,150 150,150 50)'), 10, 'endcap=round join=round'), 12);
st_generatepoints
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
01040000000C0000000101000000BBA9D0DF47496240F87F69424E0E5F400101000000B7F51F68D5646240F55A898F1F17504001010000003572FBDA8A5F5F400F87342089BE5E400101000000D4A38750931D5C40ED72799B370B5E4001010000006667943B23885B4066E8F671D97C5B400101000000EBA3152848526340221E78F6C3965D400101000000113BA23198BF6240C7234B9EF33D5840010100000069499F8062745540FF88E6BF407355400101000000A581F43AC300624042D083BD5A2262400101000000636CD355C19E62404AD3293D4D904B40010100000045F7B8041DAA4E405A849AF069A34F400101000000DE1370CF02E5564050D2988109CE5940
(1 row)
geom形式で出されても全然分からんので、st_asTextでラッピングしてみました。
yama_db=# SELECT st_asText(ST_GeneratePoints(ST_Buffer(ST_GeomFromText('LINESTRING(50 50,150 150,150 50)'), 10, 'endcap=round join=round'), 12));
st_astext
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
MULTIPOINT(147.766265850576 102.733187047914,84.0318650671786 83.712367636874,149.077046025334 68.9777848138994,107.54047530747 106.78013766944,121.059921872846 120.108716631886,137.475992983887 141.067784163524,145.074876095804 96.5277972374404,92.7965422941866 103.656943557244,66.0805226475207 56.7924152255781,72.3801321221102 71.3671567226799,145.087956158666 41.5121740980702,151.631108302923 156.218459065337)
なるほど、乱数が出力されているようです。
'LINESTRING(50 50,150 150,150 50)'), 10 → 座標 (50,50)(150,150)(150,50)で繋がれた幅10の直線上に
'endcap=round join=round'), 12)); → 12個の座標乱数を作れ
という意味のようです。
さて、実際の地図で試してみました。
kitaya_db=# SELECT st_asText(ST_GeneratePoints(ST_GeomFromText('POLYGON((35.66463989558893 139.69827111644202,35.663009879798764 139.6983247606236,35.663436999453225 139.7011571734108,35.665398233838545 139.7012966482829,35.66463989558893 139.69827111644202))'),12));
これは、以下の地図の4点で取り囲まれた地区で、任意の12点を抽出しろというSQL文になっています。
POLYGON((35.66463989558893 139.69827111644202,35.663009879798764 139.6983247606236,35.663436999453225 139.7011571734108,35.665398233838545 139.7012966482829,35.66463989558893 139.69827111644202))
ポリゴン(POLYGON)は、始点と終点(上の赤字)を同じ座標として閉じなればならないようなので、注意して下さい。
このSQL文のアウトプットは、以下のようになりました。
st_astext
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
MULTIPOINT(35.6638134136244 139.699085401991,35.6638440750173 139.700762425247,35.6634366319309 139.699705025931,35.6644917235626 ,35.66424835050 139.69868073913379 139.70025902483, 35.664689711471 139.700525986493,35.6635000403398 139.700601350665,35.6637472356065 139.698748086462,35.6641512918098 139.699288949827,35.6643791061995 139.701118277182,35.6636240715869 139.699272976596,35.6645803781279 139.699116246391)
エクセルで座標描いて、当ててみました。
全て領域の中に入っているようです。
さて、今度は、プログラム(go言語)でのこの座標の取り出し方です。
package main
import (
"database/sql"
"fmt"
"log"
"regexp"
// "os"
_ "github.com/lib/pq"
)
func main() {
db, err := sql.Open("postgres",
"user=postgres password=password host=192.168.0.23 port=15432 dbname=kitaya_db sslmode=disable")
if err != nil {
log.Fatal("OpenError: ", err)
}
defer db.Close()
//rows, err := db.Query("select id, age, type, departure_name, departure_number, departure_lat, departure_lng, arrival_name, arrival_number, arrival_lat, arrival_lng from user_list")
rows, err := db.Query("SELECT st_asText(ST_GeneratePoints(ST_GeomFromText('POLYGON((35.66404878 139.6931452,35.66051393 139.6943828,35.65878732 139.6973512,35.658431 139.6997472,35.66067562 139.705346,35.66404467 139.706768,35.66790807 139.7049654,35.66945399 139.702109,35.66672151 139.7018011,35.66475716 139.6987517,35.66362838 139.6955941,35.66641828 139.6934209,35.66404878 139.6931452))'),12))")
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) // '('か、')'か、' 'か、","で分割する → "[中身]" という構造でまぎらわしい
for _, s := range arr1 {
fmt.Printf("%s\n", s)
}
}
}
出力結果
> go run main3.go
MULTIPOINT
35.6601418389163
139.702654481044
35.661087233013
139.694572689447
35.6617615089132
Keyword: postgis postgres エリア 領域 範囲 指定 乱数 座標 囲む
購入した木材を切断せずに、バイク用屋根付き車庫を作ってみました
嘘です。コンパネを真っ二つにする裁断を1回だけやりました(Unidyでやってもらった)。
使った材料は、1800x900のコンパネ3枚と、4X2の1800の角材5本、木ネジ60本、自宅の庭に放置されていたコンクリートの固定具です。制作時間は2時間強くらいでした。

