2022/11,江端さんの技術メモ

楕円内の一様乱数

などと苦労しているのですが、結局のところ、私は地図のある領域を指定して、任意の緯度・軽度情報を出す乱数を作りたかったのです。

川の中から人が歩き始める」とか「山の中で人が消える」とか、シミュレーションと言えども、ちょっと許されない設定だと思いまして。

で、上記の記事を読まれた師匠の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 エリア 領域 範囲 指定 乱数 座標 囲む

2020/05,江端さんの技術メモ

嘘です。コンパネを真っ二つにする裁断を1回だけやりました(Unidyでやってもらった)。

使った材料は、1800x900のコンパネ3枚と、4X2の1800の角材5本、木ネジ60本、自宅の庭に放置されていたコンクリートの固定具です。制作時間は2時間強くらいでした。

 

 

2024,江端さんの忘備録,江端さんの技術メモ

『6ヶ月』 ―― これが、私が計算と経験則から導き出した結論です。

Six months" is the conclusion I have drawn from my calculations and rule of thumb.

上記の私のコラムの5ページ目に、以下の記載があります。

You will find the following statement on page 5 of my column above.

■「この私」が、3・11の震災(東日本大震災)をどのように忘れていったのかを、定量的に知りたいと思いました。

- I wanted to know quantitatively how "this I" had forgotten about the 3/11 disaster (the Great East Japan)

■そこで、私がここ何年間、1日も欠かさずに記録し続けているブログを使って、以下のような調査をやってみました。

- I did the following survey using my blog, which I have kept track of without missing a single day for the past years.

-----

上記の私のコラムの6ページ目に、以下の記載があります。

You will find the following statement on page 6 of my column above.

■ここから導かれる一つの仮説は、江戸時代以前の"寝たきり"とは、どんなに長くても半年程度であったということです。

- One hypothesis derived from this is that "bedridden" was only for about six months before the Edo period.

■当時の介護技術で、"寝たきり"を3年とか10年のオーダーで成立させるのは、無理だったはずです。

- With the care technology available at the time, it would have been impossible to keep a person "bedridden" for three or ten years.

-----

ウクライナ支援についても、各国の「支援疲れ」は、厳然たる事実です。

As for support for Ukraine, it is a stark fact that countries are "tired of supporting" Ukraine.

期限が定められていない支援に耐えられるほど、私たちは強くないのです。

We are not strong enough to withstand support without a set deadline.

-----

結論:

Conclusion:

(1)「他人への、条件のない愛情(無償の愛)のストックには上限がある」

(1) "There is an upper limit to the stock of unconditional love (free love) for others."

(2)「そのストックは、概ね6ヶ月で尽きる」

(2) "Its stock generally runs out in six months."

(3)「被災地支援、寝たきりの人への介護が「無償」で継続できる期間は、最長で"半年"である」

(3) "The maximum period during which support for disaster-affected areas and care for bedridden people can continue "free of charge" is "six months.

この現実をベースに、私たちは、被災者支援や高齢者介護を計画しなければならないと思います。

Based on this reality, we must plan to support the affected population and care for older people.

「無償の愛」を『無期限』とする計画は、必ず破綻します ―― "必ず"です。

The plan to make "free love" "indefinite" will surely fail -- "surely."

事業仕分けに関する一考察

 

2024,江端さんの技術メモ

User
You
fastapiでjson形式のメッセージを受けとった後、処理を行い、別のjsonメッセージを返信して、終了するAPIを考えます。
この場合、別のjsonのメッセージを送信して、それの受信が返ってこないと、APIが終了しません(デッドロックしてしまう)。この問題を解決するfastapiの簡単なプログラムを作成して下さい

■fastapi側プログラム dummy2.py

from fastapi import FastAPI
from pydantic import BaseModel
import asyncio  # asyncio モジュールをインポート

app = FastAPI()

class InputMessage(BaseModel):
    value: int

class OutputMessage(BaseModel):
    response: int  # 数値を含むレスポンスメッセージ

@app.post("/process")
async def process(input_message: InputMessage):

    # 5秒間の待機
    await asyncio.sleep(5)

    # サーバー側で数値を受け取り、同じ数値を含むレスポンスメッセージを返す
    response_value = input_message.value
    output_message = OutputMessage(response=response_value)
    return output_message

起動方法は、
uvicorn dummy2:app --host 0.0.0.0 --reload

■メッセージ送信側(クライアント)プログラム dummy3.py

import httpx
import sys
import asyncio  # asyncio モジュールをインポート

async def send_message_to_api(value: int):
    url = "http://localhost:8000/process"  # FastAPIサーバーのエンドポイントURLを指定

    input_message = {"value": value}  # 送信する数値をJSONメッセージに含める

    async with httpx.AsyncClient() as client:
        response = await client.post(url, json=input_message, timeout=10.0)

    if response.status_code == 200:
        data = response.json()
        print(f"APIからのレスポンス: {data['response']}")
    else:
        print(f"エラーが発生しました。ステータスコード: {response.status_code}")

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("使用法: python send.py <数値>")
        sys.exit(1)

    try:
        value_to_send = int(sys.argv[1])  # コマンドライン引数から数値を取得
    except ValueError:
        print("数値を指定してください。")
        sys.exit(1)

    asyncio.run(send_message_to_api(value_to_send))

起動方法は、
>python dummy3.py 10

平行に3つ起動しても、正確に非同期処理してくれるようです。

昨夜から、デッドロック問題で、困っていたので、原点に戻って考え中です。

2023,江端さんの技術メモ

Win32 Disk Imager

"Win32 Disk Imager"の使いかたが結構分けわからん

まず、SDカード→PCへのコピーは、

Deviceでドライブ"e.f.[G\]"を選んで→
Image Fileの方にテキトーな名前を(d:\testtest20231010とか、絶対に存在しないファイル名にして)つけて
"Read"ボタンを押す。絶対存在しないファイル名にすることによって、間違った読み込みをすることはなくなる(はず)

PC→SDカードへのコピーは、
上記の逆転をやって
"Write"をボタンを押す。

この"Read"と"Write"を間違えて、ハードディスク丸ごと破壊したことを、ゆめゆめ忘れるな

2024,江端さんの技術メモ

プロセスを強制終了しなければならないAPIを作っているのですが、「pythonからPIDをkillできない件」で困っていました。
で、以下の実験用プログラムを作成しました。

import sys
import os
import signal
def kill_process(pid):
    try:
        os.kill(pid, signal.SIGKILL)  # 指定したプロセスIDをSIGKILLシグナルで終了
        print(f"Process with PID {pid} has been killed.")
    except OSError:
        print(f"Failed to kill process with PID {pid}.")
if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("Usage: python kill_process.py <PID>")
        sys.exit(1)
    try:
        pid = int(sys.argv[1])
        kill_process(pid)
    except ValueError:
        print("Invalid PID. Please enter a valid integer PID.")

で、

python3 kill_process.py 7870

を連発したのですが、全くプロセスが消えません(ps -ef | grep xxxxxなどでレビュー)

どうやら、最初に"sudo"を付けて、

sudo python3 kill_process.py 7870

で、起動してくれることが分かりました。

で、本家の問題ですが、fastapiを使ったプログラムを試してみたのですが、

sudo uvicorn test:app --host 0.0.0.0 --reload

では、エラーになります。これは環境変数を引きついでいない、とのことで、"-E"を付与することで、動くことを確認しました。

sudo -E uvicorn test:app --host 0.0.0.0 --reload

持っていかれた時間は4時間くらいかなぁ。
それでも、とりあえず動いて、次の開発に進めるので、安堵しています。

(こういう案件を夜に残すと、夜の眠りが浅くなる)。

2024,江端さんの技術メモ

※fastapiの場合、スレッドでは上手くコンロールできない(らしい)ので、プロセス単位で管理します
(1)execute-commandというAPIから、pingを行う回数Xをjson形式入力して、"ping -n X kobore.net"を起動します。返り値はプロセス番号(pid)がjson形式で戻ります。
curl -X POST -H "Content-Type: application/json" -d "{\"count\": \"5\"}" http://localhost:8000/execute-command
(2)terminate-processというAPIから、上記のpidをjson形式で入力して、プロセスを強制終了します。返り値は、この成否(1/-1)がjson形式で戻ります。
curl -X POST -H "Content-Type: application/json" -d "{\"pid\": \"10164\"}" http://localhost:8000/terminate-process
(3)以下のファイルをtest.pyとして、uvicorn test:app --host 0.0.0.0 --reload を投入してfastapiサーバを起動します。
# curl -X POST -H "Content-Type: application/json" -d "{\"count\": \"5\"}" http://localhost:8000/execute-command
# curl -X POST -H "Content-Type: application/json" -d "{\"pid\": \"10164\"}" http://localhost:8000/terminate-process

import subprocess
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class ExecuteRequest(BaseModel):
    count: int  # pingコマンドの実行回数を指定

class TerminateRequest(BaseModel):
    pid: int  # 終了させるプロセスのPID

# 実行中のプロセスを格納する辞書
running_processes = {}

@app.post("/execute-command")
def execute_command(request: ExecuteRequest):
    count = request.count

    try:
        command = f"ping -n {count} kobore.net"  # pingコマンドの回数をcountに指定
        # コマンドを非同期で実行し、プロセスを取得
        process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        pid = process.pid  # プロセスのPIDを取得
        running_processes[pid] = process

        return {"pid": pid}  # PIDのみを返す
    except Exception as e:
        return {"message": f"コマンドの実行中にエラーが発生しました: {str(e)}"}


@app.post("/terminate-process")
def terminate_process(request: TerminateRequest):
    pid = request.pid
    print("pid in terminate-process", pid)

    try:
        # プロセスを取得し、終了させる
        process = running_processes.get(pid)

        process.terminate()  # プロセスを終了させる(SIGTERMを送信)
        process.wait()
        del running_processes[pid]  # プロセスを辞書から削除

        # 成功の場合は1を返す
        return {"status": 1}
    except Exception as e:
        return {"status": -1}  # 失敗の場合は-1を返す

if __name__ == "__main__":
    # FastAPIサーバーを開始
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

出力結果
C:\Users\ebata\fastapi7>curl -X POST -H "Content-Type: application/json" -d "{\"count\": \"100\"}" http://localhost:8000/execute-command
{"pid":1784}
C:\Users\ebata\fastapi7>curl -X POST -H "Content-Type: application/json" -d "{\"pid\": \"1784\"}" http://localhost:8000/terminate-process
{"status":1}
C:\Users\ebata\fastapi7>curl -X POST -H "Content-Type: application/json" -d "{\"pid\": \"1784\"}" http://localhost:8000/terminate-process
{"status":-1}


起動したプロセスを監視して、プロセスが予定通り/突然停止した場合、それを通知する仕組みを追加しました。

# curl -X POST -H "Content-Type: application/json" -d "{\"count\": \"5\"}" http://localhost:8000/execute-command
# curl -X POST -H "Content-Type: application/json" -d "{\"pid\": \"10164\"}" http://localhost:8000/terminate-process
# C:\Users\ebata\fastapi7>uvicorn test:app --host 0.0.0.0 --reload

import subprocess
import os
import time
import multiprocessing  # multiprocessingモジュールを追加
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class ExecuteRequest(BaseModel):
    count: int  # pingコマンドの実行回数を指定

class TerminateRequest(BaseModel):
    pid: int  # 終了させるプロセスのPID

# 実行中のプロセスを格納する辞書
running_processes = {}
process_monitor_processes = {}

@app.post("/execute-command")
def execute_command(request: ExecuteRequest):
    count = request.count

    try:
        command = f"ping -n {count} kobore.net"  # pingコマンドの回数をcountに指定
        # コマンドを非同期で実行し、プロセスを取得
        process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        pid = process.pid  # プロセスのPIDを取得
        running_processes[pid] = process

        # プロセスを監視するプロセスを起動
        monitor_process = multiprocessing.Process(target=monitor_process_status, args=(pid,))
        monitor_process.start()
        process_monitor_processes[pid] = monitor_process

        return {"pid": pid}  # PIDのみを返す
    except Exception as e:
        return {"message": f"コマンドの実行中にエラーが発生しました: {str(e)}"}


@app.post("/terminate-process")
def terminate_process(request: TerminateRequest):
    pid = request.pid
    print("pid in terminate-process", pid)

    try:
        # プロセスを取得し、終了させる
        process = running_processes.get(pid)

        process.terminate()  # プロセスを終了させる(SIGTERMを送信)
        process.wait()
        del running_processes[pid]  # プロセスを辞書から削除

        # 成功の場合は1を返す
        return {"status": 1}
    except Exception as e:
        return {"status": -1}  # 失敗の場合は-1を返す

def monitor_process_status(pid):
    while True:
        if not is_process_running(pid):
            # プロセスが存在しない場合
            # メッセージを生成して出力(または送信)
            message = {
                "status": "Process Disappeared",
                "pid": pid
            }
            print("Process Disappeared:", message)

            # プロセス監視プロセスを停止
            ### del process_monitor_processes[pid]
            break

        # 一定の待機時間を設定して監視を継続
        time.sleep(10)  # 10秒ごとに監視

def is_process_running(pid):
    #try:
    #    os.kill(pid, 0)  # PIDを使ってプロセスにシグナルを送信し、存在を確認
    #    return True
    #except OSError:
    #    return False
    try:
        # ここの部分Windows特有のやりかたなので、後で、例の、os.kill(pid,0)を試してみること
        # tasklistコマンドを実行してプロセス一覧を取得
        result = subprocess.check_output(["tasklist", "/fi", f"PID eq {pid}"], universal_newlines=True)

        # 結果から指定したPIDの行を検索
        lines = result.splitlines()
        for line in lines:
            if f"{pid}" in line:
                return True

        # 指定したPIDが見つからない場合
        # ここに、停止時のメッセージ送信を組み込めばO.K.のはず   

        return False
    except Exception as e:
        return False





if __name__ == "__main__":
    # FastAPIサーバーを開始
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

2024,江端さんの技術メモ

指定されたコマンド "ping -100 kobore.net" をFastAPIのエンドポイントから実行し、それが終了したらAPIを正常に終了させるコードは以下のようになります:

test.py

import subprocess
import threading
import os
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class CommandRequest(BaseModel):
    command: str

@app.post("/execute-command")
def execute_command(request: CommandRequest):
    command = request.command
    # コマンドを非同期で実行
    execution_thread = threading.Thread(target=execute_command_async, args=(command,))
    execution_thread.start()
    return {"message": f"コマンド '{command}' の実行を開始しました"}

def execute_command_async(command):
    process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    process.wait()  # コマンドの終了を待つ
    print(f"コマンド '{command}' の実行が終了しました")
    # os._exit(0)  # FastAPIサーバーを終了させる

if __name__ == "__main__":
    # FastAPIサーバーを開始
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

$ uvicorn test:app --host 0.0.0.0 --reload

 

C:\Users\ebata\fastapi7>curl -X POST -H "Content-Type: application/json" -d "{\"command\": \"ping -n 20 kobore.net\"}" http://localhost:8000/execute-command

2023,江端さんの技術メモ

以下のtest.csvファイルを、gunplotで3D表示させたくて、色々やっています。

test.csv
#lat,lng,datetime,distance,speed
35.681100,139.758600,2023-05-22 02:10:30,313.307785,37.596934
35.683300,139.759900,2023-05-22 02:11:00,271.347933,32.561752
35.685300,139.760900,2023-05-22 02:11:30,240.030143,28.803617
35.685400,139.761000,2023-05-22 02:12:00,14.325264,1.719032
35.685400,139.761000,2023-05-22 02:12:30,0.000000,0.000000
35.685400,139.761100,2023-05-22 02:13:00,9.031610,1.083793
35.685200,139.763500,2023-05-22 02:13:30,217.896760,26.147611
35.684700,139.765700,2023-05-22 02:14:00,206.328362,24.759403
35.684200,139.768000,2023-05-22 02:14:30,215.040983,25.804918
35.685400,139.768400,2023-05-22 02:15:00,138.238013,16.588562

で、まあ、こんなgpファイルを使って試していましたが、上手く動きませんでした。

# Function to parse datetime string into a numerical value
strptime_datetime(x) = strptime('%Y-%m-%d %H:%M:%S', x)

# Set the output terminal to a 3D plot (you can change the output format if needed)
set terminal pngcairo enhanced size 800,600

# Set the data file separator to a comma
set datafile separator ','

# Set the axis labels
set xlabel 'Longitude (lng)'
set ylabel 'Latitude (lat)'
set zlabel 'Date and Time (datetime)'

# Set the view to a 3D perspective
set view 50,30,1,1


# Get the minimum and maximum datetime values from the data
stats 'test.csv' using (strptime_datetime(stringcolumn(3))) nooutput
min_datetime = STATS_min
max_datetime = STATS_max

# Set the range for the Z axis (datetime)
set zrange [min_datetime:max_datetime]

# Plot the data using the specified columns
#splot 'test.csv' using 2:1:(timecolumn(stringcolumn(3))) with points pointtype 7 pointsize 1 title 'Data Points'
#splot 'test.csv' using 2:1:(timecolumn(stringcolumn(3))) with points pointtype 7 pointsize 1 title 'Data Points'
#splot 'test.csv' using 2:1:(timecolumn(stringcolumn(3))):(0) with points pointtype 7 pointsize 1 title 'Data Points'

splot 'test.csv' using 2:1:(timecolumn(stringcolumn(3)))

時間のパース(strptime_datetime(x) = strptime('%Y-%m-%d %H:%M:%S', x))がやっぱり上手く動きませんでした。

でまあ、しょうがないので、"2023-05-22 02:10:30"を秒数に変換するプログラムを作成しました。

/* "2023-05-22 02:10:30"を秒数に変換するプログラムを作成しました
   c:\Users\ebata\gnuplot\convert_datetime.go
*/
package main

import (
	"encoding/csv"
	"fmt"
	"os"
	"time"
)

func main() {
	// 入力ファイル名と出力ファイル名を指定
	inputFileName := "test.csv"
	outputFileName := "formatted_test.csv"

	// 入力ファイルを開く
	inputFile, err := os.Open(inputFileName)
	if err != nil {
		fmt.Println("ファイルを開けませんでした:", err)
		return
	}
	defer inputFile.Close()

	// 出力ファイルを作成または上書き
	outputFile, err := os.Create(outputFileName)
	if err != nil {
		fmt.Println("ファイルを作成できませんでした:", err)
		return
	}
	defer outputFile.Close()

	// CSVリーダーとライターを作成
	reader := csv.NewReader(inputFile)
	writer := csv.NewWriter(outputFile)

	// ヘッダーを読み込み、書き込み
	header, err := reader.Read()
	if err != nil {
		fmt.Println("ヘッダーを読み込めませんでした:", err)
		return
	}
	writer.Write(header)

	// データを読み込んで秒数に変換して書き込み
	for {
		record, err := reader.Read()
		if err != nil {
			break
		}

		// datetime列をパースして秒数に変換
		datetime := record[2] // datetime列のインデックスを確認してください
		parsedTime, err := time.Parse("2006-01-02 15:04:05", datetime)
		if err != nil {
			fmt.Println("日時をパースできませんでした:", err)
			return
		}
		seconds := parsedTime.Unix()

		// 秒数に変換した値を新しい列として書き込み
		record = append(record, fmt.Sprintf("%d", seconds))
		writer.Write(record)
	}

	// ライターをフラッシュしてクローズ
	writer.Flush()

	if err := writer.Error(); err != nil {
		fmt.Println("書き込みエラー:", err)
		return
	}

	fmt.Println("変換が完了しました。出力ファイル:", outputFileName)
}

で、こんなのができました。
formatted_test.csv

#lat,lng,datetime,distance,speed
35.681100,139.758600,2023-05-22 02:10:30,313.307785,37.596934,1684721430
35.683300,139.759900,2023-05-22 02:11:00,271.347933,32.561752,1684721460
35.685300,139.760900,2023-05-22 02:11:30,240.030143,28.803617,1684721490
35.685400,139.761000,2023-05-22 02:12:00,14.325264,1.719032,1684721520
35.685400,139.761000,2023-05-22 02:12:30,0.000000,0.000000,1684721550
35.685400,139.761100,2023-05-22 02:13:00,9.031610,1.083793,1684721580
35.685200,139.763500,2023-05-22 02:13:30,217.896760,26.147611,1684721610
35.684700,139.765700,2023-05-22 02:14:00,206.328362,24.759403,1684721640

スクリプトではなく、コマンドで一つづつ入れていきました。

gnuplot> set datafile separator ','   ← これ凄く重要
gnuplot> splot "formatted_test.csv" using 2:1:6
の結果は以下の通りでした。

エリア限定
# x軸の範囲を指定
set xrange [139.6119000:139.6312000]

# y軸の範囲を指定
set yrange [35.3627000:35.3737000] ←これだと、地図のイメージと逆転するので
set yrange [35.3737000:35.3627000]  ← y軸の範囲を逆転させる

上記のxrangeとyrangeの範囲を地図で示すと、

の範囲となる。
領域の範囲内でのトラッキングデータの様子

gnuplotでx軸とy軸の範囲を指定解除するには、以下のコマンドを使用する。

gnuplot> set xrange [*:*]
gnuplot> set yrange [*:*]

gnuplot> splot "2023-topLatLngCounts4-1.csv" using 2:1:3
gnuplot> set yrange [35.3737000:35.3627000]    ← 軸の大小を引っくり返す
gnuplot> splot "2023-topLatLngCounts4-1.csv" using 2:1:3 ← 点を表示
gnuplot> replot "2023-topLatLngCounts4-1.csv" using 2:1:3 with impulses ← 縦軸表示

ちなみに、文字列が入っているcsvだと表示されないことが多いようだ。

2024,江端さんの技術メモ

運行情報(ダイヤグラム)を反映した最短時間で到着する計算方法は、通常のダイクストラ法を拡張して実行することができます。以下は、ダイヤ情報を考慮する最短到着時間を計算する一般的なアプローチです:

  1. グラフの作成:
    • ダイヤグラムに基づいて、駅間の接続を表すグラフを作成します。各エッジ(駅間の接続)には、所要時間が含まれます。
    • グラフのノードは駅を表し、エッジの重みは駅間の移動にかかる時間です。
    • 通常のダイクストラ法と同様に、出発駅を始点としてグラフを探索します。
  2. ノードの拡張と更新:
    • 通常のダイクストラ法と同様に、出発駅から各駅への最短到着時間を記録するデータ構造を使用します。初期状態では、出発駅の最短到着時間を0に設定し、他の駅は無限大(無効値)とします。
    • プライオリティキューを使用して、最短到着時間が最小の駅を選択します。
  3. ダイヤ情報の適用:
    • 選択した駅から出発するエッジを調べ、ダイヤ情報を考慮して最短到着時間を計算します。
    • ダイヤ情報には、駅への到着時間や運行間隔などが含まれます。現在の到着時間とダイヤ情報を使用して、次の駅への最短到着時間を計算します。
    • 新しい到着時間が現在の最短到着時間よりも短い場合、その駅の最短到着時間を更新します。
  4. プライオリティキューから次の最短到着時間の駅を選択し、ステップ3を繰り返します。目的の駅に到達した場合、計算を終了します。
  5. 最短到着時間を使用して、目的の駅への最短経路を復元します。

このアプローチを使用すると、ダイヤ情報を反映した最短到着時間を計算できます。ダイヤ情報は、駅への到着時間や運行間隔を正確に取得し、計算に組み込む必要があります。また、プライオリティキューの実装や適切なデータ構造の設計も重要です。