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

最近、スペルチェックをvscodeにやらせていて、emacsでのリロードを何度もやっているのだけど、これが中々に面倒で、これを一発でできないものか、色々調べた結果です。

方針: 面倒なので、".emacs" だけの記載で終了したい
操作:<F5>押すだけ
判ったこと: ".emacs"の最後に追記すると動かない(私の環境だけかもしれないが) → ".emacs"の真ん中あたりに突っ込んだ。

;;;; バッファを一発でリロードする (.emacsの最後に記載したら動かなかったので、真ん中くらいに移動)

(defun revert-buffer-no-confirm (&optional force-reverting)
  (interactive "P")
  ;;(message "force-reverting value is %s" force-reverting)
  (if (or force-reverting (not (buffer-modified-p)))
      (revert-buffer :ignore-auto :noconfirm)
    (error "The buffer has been modified")))

;; reload buffer
(global-set-key (kbd "<f5>") 'revert-buffer-no-confirm)
;;(global-set-key "\C-c\C-r" 'revert-buffer-no-confirm)
;;(global-set-key "\M-r" 'revert-buffer-no-confirm)
;(global-set-key "\C-c\C-m" 'revert-buffer-no-confirm)

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

こっちはPythonで書いたファジィ(Fuzzy)推論コードです。



# -*- coding: utf-8 -*-
# 上記の一行がないと、コメント分の和文も全部エラーになる 

# シンプルファジィ推論プログラム ver0.0 2015年12月15日
# シンプルファジィ推論プログラム ver0.1 2023年04月20日 print文の仕様変更対応

# メンバーシップ関数は、前件部、後件部ともに3つに固定した単純なもの
# 推論方式は、単純なmin-max重心法のみ



class condition_MF:
    
    center = 0
    width =  100
    PV ="LESS"

    def __init__(self, center, width, PV):
        self.center = center
        self.width = width
        self.PV = PV

    # x,yは、メンバーシップ関数上の座標を示す
    def func(self, x):
        if (self.PV == "LESS"):
            if (x <= self.center - self.width):
                y = 1.0
            elif (x <= self.center):
                y = - 1.0 / self.width * (x - self.center)
            else:
                y = 0.0        
        elif (self.PV == "ZERO"):
            if (x <= self.center - self.width):
                y = 0.0
            elif (x <= self.center):
                y = 1.0 / self.width * (x - self.center) + 1.0
            elif (x <= self.center + self.width):
                y = -1.0 / self.width * (x - self.center) + 1.0
            else:
                y = 0.0       
        elif (self.PV == "MORE"):
            if (x <= self.center):
                y = 0.0
            elif (x <= self.center + self.width):
                y = 1.0 / self.width * (x - self.center)
            else:
                y = 1.0
        
        return y

class action_MF:
    center = 0 # 値はダミー
    width =  100 # 値はダミー
    PV ="ZERO" # 値はダミー
    y = 0.0 # 最大値で更新されていく
    x = 0.0 # 値はダミー

    def __init__(self, center, width, PV):
        self.center = center
        self.width = width
        self.PV = PV

        if (self.PV == "LESS"):
            self.x = self.center - self.width
        elif (self.PV == "ZERO"):
            self.x = self.center
        elif (self.PV == "MORE"):
            self.x = self.center + self.width
        else:
            print ("error")

    # yを最大値で更新していく
    def func_Max(self,b):
        self.y = max(b, self.y)

    # X座標を返す
    def func_X(self):
        return self.x

    # (最大値で更新された、最後の)Y座標を返す
    def func_Y(self):
        return self.y

temp_High = condition_MF(20.0, 10.0, "MORE") # 温度が高い
temp_Middle = condition_MF(20.0, 10.0, "ZERO") #温度が普通
temp_Low = condition_MF(20.0, 10.0, "LESS")# 温度が低い

humi_High = condition_MF(50.0, 20.0, "MORE") # 湿度が高い
humi_Middle = condition_MF(50.0, 20.0, "ZERO") #湿度が普通
humi_Low = condition_MF(50.0, 20.0, "LESS")# 湿度が低い

switch_High = action_MF(0.0, 1.0, "MORE") # エアコンの温度設定を上げる("+1"にする)
switch_Middle = action_MF(0.0, 1.0, "ZERO")# エアコンの温度設定に何もしない("0"にする)
switch_Low = action_MF(0.0, 1.0, "LESS") # エアコンの温度設定を下げる("-1"にする)


# 入力値(温度27度、湿度57%)
t = 35.0
h = 80.0

# (1)「もし、温度が高くて、湿度が高ければ、エアコンの温度設定を下げる」
a1 = min(temp_High.func(t),humi_High.func(h))
print (a1)
switch_Low.func_Max(a1) 

print ("Low") 
print (switch_Low.func_Y()) 

# (2)「もし、温度が普通で、湿度が高ければ、何もしない」
a2 = min(temp_Middle.func(t),humi_High.func(h))
switch_Middle.func_Max(a2) 

print ("Middle") 
print (switch_Middle.func_Y()) 

# (3)「もし、温度が高くて、湿度が普通なら、エアコンの温度設定を下げる」

a3 = min(temp_High.func(t),humi_Middle.func(h))
switch_Low.func_Max(a3) 
print ("Low") 
print (switch_Low.func_Y()) 

# (4)「もし、温度が低くて、湿度が低ければ、エアコンの温度設定を上げる」
a4 = min(temp_Middle.func(t),humi_Low.func(h))
switch_High.func_Max(a4) 
print ("High") 
print (switch_High.func_Y()) 

# (5)(追加)もし温度が普通で、湿度が普通なら、何もしない
a5 = min(temp_Middle.func(t),humi_Middle.func(h))
switch_Middle.func_Max(a5) 
print ("Middle") 
print (switch_Middle.func_Y()) 

# 重心値を求める
# (ルールが推論空間を網羅していないと、ゼロ割が発生することがあるので注意)

reasoning =  (switch_High.func_X() * switch_High.func_Y()  + switch_Middle.func_X() * 
switch_Middle.func_Y()  + switch_Low.func_X() * switch_Low.func_Y()) / (switch_High.func_Y()  + switch_Middle.func_Y()  + switch_Low.func_Y())

print ("\n reasoning")
print (reasoning)

元ネタはこちら(クリックするとページに飛びます)。

ファジィ推論

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

Go言語を使った自作のファジィ(Fuzzy)推論コードを探していたのですが、見つけることができずにちょっとショックを受けております。

というか、Go言語どころか、C言語でも見つけられなかった(いや、あるはずだ)ので、今、PCの中探して見つかったものをアップしておきます。

毎回スクラッチから作るのも迂遠なので。

↓のコードですが、こちらのコラムで使ったものだと思います。

/*
  このプログラムをfuzzy.cpp"という名前で保存して、
  gcc -g fuzzy.cpp -o fuzzy
  でコンパイルして下さい。
*/



#include <stdio.h>
#include <stdlib.h>


// 共通関数
double max(double a){
    return a;
}

double max(double a, double b){
    if (a > b) 
        return a;
    else 
        return b;
};

double max(double a, double b, double c ){
    return max(max(a,b), max(b,c)); 
};

double max(double a, double b, double c, double d ){
    return max(max(a,b,c), d); 
};

double min(double a){
    return a;
}

double min(double a, double b){
    if (b > a) 
        return a;
    else 
        return b;
};

double min(double a, double b, double c ){
    return min(min(a,b), min(b,c)); 
};

double min(double a, double b, double c, double d ){
    return min(min(a,b,c), d); 
};


// ファジィ表現
typedef enum scale {LESSLESS, LESS, ZERO, MORE, MOREMORE} SCALE;

// 前件部メンバーシップ関数(山3つ)クラス
class condition_MF3
{
private:
    double center;
    double width;
    SCALE express;
   
public:
    condition_MF3(double _center, double _witdth, SCALE _express){
        center = _center;
        width = _witdth;
        express = _express;

        // 使用できないファジィ表現を使った場合は止める        
        if ((express == LESSLESS) || (express == MOREMORE)){
            printf("wrong expression used \n");
            exit(0);
        }

    };
    double func(double _x);
};

double condition_MF3::func(double _x)
{
// x,yは、メンバーシップ関数上の座標を示す
    double x = _x;
    double y = 0.0; // yの値は、必ず0以上1以下になる

    if (express == LESS){
        if (x <= center - width){
            y = 1.0;
        }
        else if (x <= center){
            y = - 1.0 / width * (x - center);
        }
        else{
            y = 0.0;
        }
    }
    else if (express == ZERO){
        if (x <= center - width){
            y = 0.0;
        }
        else if (x <= center){
            y = 1.0 / width * (x - center) + 1.0;
        }
        else if (x <= center + width){
            y = -1.0 / width * (x - center) + 1.0;
        }
        else{
            y = 0.0;
        }
    }
    else if (express == MORE){
        if (x <= center){
            y = 0.0;
        }
        else if (x <= center + width){
            y = 1.0 / width * (x - center);
        }
        else{
            y = 1.0;
        }
    }
    else {
        printf("wrong expression\n");
        exit(1);
    }

    return y;
};

// 前件部メンバーシップ関数(山5つ)クラス
class condition_MF5
{
private:
    double center;
    double width;
    SCALE express;
   
public:
    condition_MF5(double _center, double _witdth, SCALE _express){
        center = _center;
        width = _witdth;
        express = _express;
    };
    double func(double _x);
};


double condition_MF5::func(double _x)
{
// x,yは、メンバーシップ関数上の座標を示す
    double x = _x;
    double y = 0.0; // yの値は、必ず0以上1以下になる

    if (express == LESSLESS){
        if (x <= center - 2.0 * width){
            y = 1.0;
        }
        else if (x <= center - width){
            y = - 1.0 / width * (x - (center - 2.0 * width)) + 1.0;
        }
        else{
            y = 0.0;
        }
    }
    else if (express == LESS){
        if (x <= center - 2.0 * width){
            y = 0.0;
        }
        else if (x <= center - width){
            y = 1.0 / width * (x - (center - width)) + 1.0;
        }
        else if (x <= center){
            y = -1.0 / width * (x - (center - width)) + 1.0; 
        }
        else{
            y = 0.0;
        }
    }
    else if (express == ZERO){
        if (x <= center - width){
            y = 0.0;
        }
        else if (x <= center){
            y = 1.0 / width * (x - center) + 1.0;
        }
        else if (x <= center + width){
            y = -1.0 / width * (x - center) + 1.0;
        }
        else{
            y = 0.0;
        }
    }
    else if (express == MORE){
        if (x <= center){
            y = 0.0;
        }
        else if (x <= center + width){
            y = 1.0 / width * (x - (center + width)) + 1.0;
        }
        else if (x <= center + 2.0 * width){
            y = -1.0 / width * (x - (center + width)) + 1.0; 
        }
        else{
            y = 0.0;
        }
    }
    else if (express == MOREMORE){
        if (x <= center + width){
            y = 0.0;
        }
        else if (x <= center + 2.0 * width){
            y = 1.0 / width * (x - (center + 2.0 * width)) + 1.0;
        }
        else{
            y = 1.0;
        }
    }

    return y;
};

// 後件部メンバーシップ関数(山3つ)クラス  
class action_MF3
{
private:
    double center;
    double width;
    SCALE express;

    double x;
    double y;

public:
    action_MF3(double _center, double _witdth, SCALE _express){

        y = 0.0; // yの値は、必ず0以上1以下になる
       
        center = _center;
        width = _witdth;
        express = _express;
        
        if (express == LESS){
            x = center - width;
        }
        else if (express == ZERO){
            x = center;
        }
        else if (express == MORE){
            x = center + width;
        }
        else{
            printf("wrong scale expression\n");
            exit(0);
        }
    };
    
    void func_Max(double b){
        y = max(b, y);
    };
    
// X座標を返す
    double func_X(void){
        return x;
    };
    
    // (最大値で更新された、最後の)Y座標を返す
    double func_Y(){
        return y;
    };

};

// 後件部メンバーシップ関数(山5つ)クラス  
class action_MF5
{
private:
    double center;
    double width;
    SCALE express;

    double x;
    double y;

public:
    action_MF5(double _center, double _witdth, SCALE _express){
        y = 0.0; // yの値は、必ず0以上1以下になる
       
        center = _center;
        width = _witdth;
        express = _express;
        
        if (express == LESSLESS){
            x = center - 2.0 * width;
        }
        else if (express == LESS){
            x = center - width;
        }
        else if (express == ZERO){
            x = center;
        }
        else if (express == MORE){
            x = center + width;
        }
        else if (express == MOREMORE){
            x = center + 2.0 * width;
        }
        else{
            printf("wrong scale expression\n");
        }
    };
    
    void func_Max(double b){
        y = max(b, y);
    };
    
// X座標を返す
    double func_X(void){
        return x;
    };
    
    // (最大値で更新された、最後の)Y座標を返す
    double func_Y(){
        return y;
    };

};

int main()
{
    // ダイエット経過期間
    condition_MF3 Period_Short(14, 14, LESS); // 0日
    condition_MF3 Period_Middle(14, 14, ZERO); // 14日
    condition_MF3 Period_Long(14, 14, MORE); // 28日

    // いわゆる「停滞期」(14日間あたりの平均減重変化量)
    condition_MF3 WeighChange_Small(1.0, 1.0, LESS); // 0kg
    condition_MF3 WeighChange_Middle(1.0, 1.0, ZERO); // 1kg
    condition_MF3 WeighChange_High(1.0, 1.0, MORE); // 2kg

    // 苦痛度
    condition_MF3 Suffer_Zerol(400.0, 400.0, LESS); // 0
    condition_MF3 Suffer_Middle(400.0, 400.0, ZERO); // 400
    condition_MF3 Suffer_Hight(400.0, 400.0, MORE); // 800

    // BMI
    condition_MF5 BMI_lowlow(22.0, 3.0, LESSLESS); // 痩せすぎ(15.0)
    condition_MF5 BMI_low(22.0, 3.0, LESS);// 痩せぎみ(19.0)
    condition_MF5 BMI_Normal(22.0, 3.0, ZERO);// 普通(22.0)
    condition_MF5 BMI_High(22.0, 3.0, MORE); // 太りぎみ(25.0)
    condition_MF5 BMI_HighHigh(22.0, 3.0, MOREMORE);// 太りすぎ(28.0)

    // 一日の摂取カロリー
    action_MF5 Eat_LittleLittle(2500, 1000, LESSLESS); // 500 Kcal
    action_MF5 Eat_Little(2500, 1000, LESS);// 1500 Kcal
    action_MF5 Eat_Normal(2500, 1000, ZERO);// 2500 Kcal
    action_MF5 Eat_Lot(2500, 1000, MORE);// 3500 Kcal
    action_MF5 Eat_LotLot(2500, 1000, MOREMORE);// 4500 Kcal



    
    
    // 超シンプルシミュレータ投入
    // 体重変動シミュレーションプログラム
    //基礎代謝量と求め方	?ハリス・ベネディクト方程式(日本人版) 計算式
    //http://www.kintore.info/kisotaisya_mass/index.cgi
    //【計算式】
    // 男性 66.5+(体重kg×13.8)+(身長cm×5.0)-(年齢×6.8)
    // 女性 665.1+(体重kg×9.6)+(身長cm×1.9)-(年齢×7.0)							
    // 江端智一 2014日2月4日現在のデータを使って計算
    double weight = 78.0;  // 開始時の体重 78.0kg
    double lengthw = 172.0;  // 身長 172cm
    double age = 49.0 + 94/365;  // 開始時の年齢 49歳と94日 (2014年2月4日現在)

    // 最初の基礎代謝カロリー 男性  66.5+(体重kg×13.8)+(身長cm×5.0)-(年齢×6.8)
    double consumption_calorie =  
        66.5 + weight * 13.8 + 172.0 * 5.0 - age * 6.8;
    // このカロリーに運動消費カロリーを加えて、
    // トータルの一日の消費カロリーを算出する

    // (現在の体重 66.5kgにおいて)一日運動消費カロリー 708kcalと推定し、
    // このカロリーは、体重に比例するものと仮定する。
    consumption_calorie += weight/66.5 * 708.0;
    printf("consumption_calorie = %f\n",  consumption_calorie);

    double last_weight[14]; // 過去14日分の体重
    
    // 最初は全部同じ体重とする 14日経過までは同じ体重とする
    for (int i = 0; i++; i < 14){
        last_weight[i] = weight;
    }

    // ここからループ開始
    for (int day=0; day < 1460; day++){ // 1460日は、ちょうど4年分の日数
        // 7kcal = 1g = 0.001kg とする    

        // 体重データを一日分ずらす
        for (int i = 0; i++; i < 14-1 ){
            last_weight[i+1] = last_weight[i];
        }
        last_weight[0] = weight;
        
        // 線形補完の傾きだけを求める(14日分)
        // http://d.hatena.ne.jp/rainlib/20090112/1231735459
        double k1=0,k2=0,k3=0,k4=0;
        for (int i = 0; i++; i < 14 ){
            k1 += (double)i * (double)i;
            k2 += (double)i * last_weight[i];
            k3 += (double)i;
            k4 += last_weight[i] *last_weight[i];
        }
        double dW = (14.0 * k2 - k3 * k4) / (14.0 * k4 - k3*k3); // 14

        // BMIの計算式 体重(Kg)÷身長(m)÷身長(m) 江端の身長172cm
        double bmi = weight / 1.72 / 1.72;
        
        // [ルール1] ダイエット開始直後は、「無条件にがんばれる」
        double r1 = min(Period_Short.func(day));
        Eat_Little.func_Max(r1);
        //printf("r1: %f\n",r1);
        
        //printf("Eat_Little.func_X() = %f\n",Eat_Little.func_X());
        //printf("Eat_Little.func_Y() = %f\n",Eat_Little.func_Y());
        
        // [ルール2] ダイエット一ヶ月を過ぎても、停滞し続けると「切れる」
        double r2 = min(Period_Long.func(day),WeighChange_Small.func(dW));
        Eat_LotLot.func_Max(r2);
        //printf("r2: %f\n",r2);
        
        // [ルール3] ダイエット2週間を過ぎて、停滞し続けると「緩んでくる」
        double r3 = min(Period_Middle.func(day),WeighChange_Small.func(dW));
        Eat_Normal.func_Max(r3);
        //printf("r3: %f\n",r3);
        
        // [ルール4] 停滞期がなく順調に体重が落ちている場合は「がんばれる」
        double r4 = min(WeighChange_High.func(dW));
        Eat_Little.func_Max(r4);
		//        printf("r4: %f\n",r4);
        
        // [ルール5] ダイエット一ヶ月を過ぎて、再び太り出してくると、またダイエットを再開してしまう
        double r5 = min(Period_Long.func(day),BMI_High.func(bmi));
        Eat_Little.func_Max(r5);
        //printf("r5: %f\n",r5);
        
        // [ルール6] ダイエット一ヶ月を過ぎて、太り過ぎると、滅茶苦茶なダイエットを始めてしまう
        double r6 = min(Period_Long.func(day),BMI_HighHigh.func(bmi));
        Eat_LittleLittle.func_Max(r6);

        //printf("r6: %f\n",r6);
		/*        
        printf("Eat_LittleLittle.func_X() = %f\n",Eat_LittleLittle.func_X());
        printf("Eat_LittleLittle.func_Y() = %f\n",Eat_LittleLittle.func_Y());
        printf("Eat_Little.func_X() = %f\n",Eat_Little.func_X());
        printf("Eat_Little.func_Y() = %f\n",Eat_Little.func_Y());
        printf("Eat_Normal.func_X() = %f\n",Eat_Normal.func_X());
        printf("Eat_Normal.func_Y() = %f\n",Eat_Normal.func_Y());
        printf("Eat_Lot.func_X() = %f\n",Eat_Lot.func_X());
        printf("Eat_Lot.func_Y() = %f\n",Eat_Lot.func_Y());
        printf("Eat_LotLot.func_X() = %f\n",Eat_LotLot.func_X());
        printf("Eat_LotLot.func_Y() = %f\n",Eat_LotLot.func_Y());
        */
        
        double source_calorie =  
            (Eat_LittleLittle.func_X() * Eat_LittleLittle.func_Y() + 
             Eat_Little.func_X() * Eat_Little.func_Y() +
             Eat_Normal.func_X() * Eat_Normal.func_Y() +
             Eat_Lot.func_X() * Eat_Lot.func_Y() +
             Eat_LotLot.func_X() * Eat_LotLot.func_Y() )  
            /
            (Eat_LittleLittle.func_Y() + 
             Eat_Little.func_Y() +
             Eat_Normal.func_Y() +
             Eat_Lot.func_Y() +
             Eat_LotLot.func_Y() ) ;
        
        printf("\n source_calorie = %f\n",source_calorie);

        double diff_weight = (source_calorie - consumption_calorie)/7.0 / 1000.0;
        weight += diff_weight;
        age += 1.0/365.0;
        consumption_calorie =  66.5 + weight * 13.8 + 172.0 * 5.0 - age * 6.8;
        consumption_calorie += weight/66.5 * 708.0;
        //       printf("day:%d\tweight = %f\tconsumption_calorie =%f\n", day,weight, consumption_calorie);
    }



}

ファジィ推論

 

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

長女が125CCのバイクに乗り替えてから、放置し続けてきたバイクが動かなくなった、というので、廃棄処分にするか相談を受けていたのですが ―― 私には、どうにも故障に見えない。

 

バイク屋で見て貰った時間は、3秒くらい。

簡単に言えば、「スピードメータに、オイル交換やらバッテリーやらの電飾表示が出ないので、充電してもダメ」ということらしい。ただし「スターターが壊れている可能性もあるが、まずはバッテリーを交換してみないと分からん」と言われました。

で、早速Amazonで購入。もっとも安いもので2000円、リチウムは16000円もするのでスコープ外として、今回はこれにしました。

購入の判断は、評価者数の多さと(1040個の評価)と、

評価の分布でした。私は、こういう正規分布に近い評価を見ると、安心できます。

さて、これが届くまでの間、バイクの空気圧の補充をしに、ガススタンドにいっていました。150~200PKは必要なのに、90くらいしか入っていませんでした。


本日の昼前にバッテリーが届いたので、さきほど5分くらいで、バッテリー交換しました(一度やっているので、コツは掴んでいる)。

ちゃんと充電されて配送されてきた。感心、感心。

バッテリーボックスの回りにガムテープを貼っているのは・・・以前、きちんとバッテリー収納ボックスを開けてみたはいいが、閉めれなかったんだろうな~、と推測。 → 保険証を貼りつけていたことを思い出した。

古いバッテリーを取り出す。

新しいバッテリーに交換。

無事、インジケータの表示とセル起動を確認して、バイク復活を確認しました。

バッテリー不良ごときで、バイクが廃棄されたかもしれないかも、と考えると、さすがの私も「もったいない」と思います。

敢えていうのであれば、「面倒くさい」あたりがしっくりきます。

3000円程度であれば、今後もバッテリー交換を繰替えせば良いと思うけど、もしかしたら、バッテリー液交換(で遊ぶ)かもしれないので、自分の為に、マニュアルを添付しておきます。

しかし、我が家には、当然のように、テスターや、電圧可変ができる安定化電源(ジャンク屋で拾ってきた)があるのだけど、一般家庭にはないかもしれません。

こういうDIYをサクっとできるのは、そういうことが好きな人間だけの特権かもしれません。

以上

江端さんの技術メモ

Golangは「一つのディレクトリの中に、一つの実行形式を作る」というスタイルようです。

で、そのディレクトリから、"go run ." とやるだけで、動き出すので、直感的に取扱いが良いです(まあ、好みの問題とは思います)。

├── RASSimulator
│   ├── bike.go
│   ├── cert.pem
│   ├── channel.go
│   ├── key.pem
│   ├── person.go
│   ├── real_person.go
│   ├── routing
│   │   └── librouting.go
│   ├── simulator.go
│   ├── util.go
│   └── virtual_person.go
├── REALSimulator
│   ├── bike.go
│   ├── cert.pem
│   ├── channel.go
│   ├── key.pem
│   ├── person.go
│   ├── real_person.go
│   ├── routing
│   │   └── librouting.go
│   ├── simulator.go
│   ├── util.go
│   └── virtual_person.go

で、現在、RASSimulator REALSimulator  の中身は、simulator.go 以外は、全く同じ となっていまして、simulator.go以外のファイルを変更する時は、その変更が同時に反映する必要があります(ライブラリ化するのが面倒くさい、という理由もある)。

一方のディレクトリのファイルを変更すれば、他方のディレクトリに直ちに反映されれば、良いのです。

これは、Unix系では"ln"コマンドを使えば、一発で解決する内容なのですが、これがWindows10でもできるかどうかかが分かりませんでしたが、どうやら、その方法はあるようです。

https://www.wannko.net/windows10/etc/sinlink.html

に記載がありました。

(というか、Microsoftも意地はらんと、Command.comのコマンド、全部UNIX系に全部合わせていけばいいのに、と思うんだが・・権利関係かなぁ? しかし、コマンドなんぞに権利が発生するのかなぁ?)

管理者モードでコマンドプロンプトを開いて、以下のようにフルパスで記載すると作成できるようです(私の場合フルパスで書かなかった場合失敗しました)

c:\Users\ebata\20220830rev196kai>mklink /D c:\users\ebata\20220830rev196kai\REALSimulator c:\users\ebata\20220830rev196kai\RASSimulator
c:\users\ebata\20220830rev196kai\REALSimulator <<===>> c:\users\ebata\20220830rev196kai\RASSimulator のシンボリック リンクが作成されました
ただ、このようにディレクトリを丸ごとリンクしてしまうと、ディレクトリにファイルを追加しても、他方のディレクトリにも反映されてしまって、困ったことになりました。
という訳で、面倒ですが、simulator.go以外のファイルは全て手動でリンクするようにします。
まずリンクを解除するために、REALSimulatorを削除します。
現在、こんな感じです。
c:\Users\ebata\20220830rev196kai>ls -la REALSimulator
lrwxrwxrwx 1 ebata ebata 45 Sep 5 15:53 REALSimulator -> /c/users/ebata/20220830rev196kai/RASSimulator
で、
c:\Users\ebata\20220830rev196kai>rm REALSimulator
c:\Users\ebata\20220830rev196kai>ls -la REALSimulator
ls: cannot access 'REALSimulator': No such file or directory
c:\Users\ebata\20220830rev196kai>ls -la RASSimulator
total 112
drwxr-xr-x 1 ebata ebata     0 Sep  5 15:59 .
drwxr-xr-x 1 ebata ebata     0 Sep  5 16:05 ..
-rw-r--r-- 1 ebata ebata 11551 Sep  3 20:41 bike.go
-rw-r--r-- 1 ebata ebata  1643 Jul 19 19:45 cert.pem
-rw-r--r-- 1 ebata ebata  5952 Aug 17 18:31 channel.go
drwxr-xr-x 1 ebata ebata     0 Sep  2 01:32 data
-rw-r--r-- 1 ebata ebata  1704 Jul 19 19:45 key.pem
-rw-r--r-- 1 ebata ebata 13029 Aug 28 19:50 person.go
-rw-r--r-- 1 ebata ebata  4259 Sep  3 16:33 real_person.go
drwxr-xr-x 1 ebata ebata     0 Sep  2 01:32 routing
-rw-r--r-- 1 ebata ebata 13164 Sep  5 15:58 simulator.go
-rw-r--r-- 1 ebata ebata 13163 Sep  5 15:57 simulator.go~
-rw-r--r-- 1 ebata ebata  8633 Sep  2 14:43 util.go
-rw-r--r-- 1 ebata ebata  3164 Sep  3 20:39 virtual_person.go
c:\Users\ebata\20220830rev196kai>
OK。これで、元の状態に戻っています。
では、迂遠ではありますが、simulator.go を除いたリンクを一斉に貼ります。
mklink c:\users\ebata\20220830rev196kai\REALSimulator\bike.go c:\users\ebata\20220830rev196kai\RASSimulator\bike.go
mklink c:\users\ebata\20220830rev196kai\REALSimulator\cert.pem c:\users\ebata\20220830rev196kai\RASSimulator\cert.pem
mklink c:\users\ebata\20220830rev196kai\REALSimulator\channel.go c:\users\ebata\20220830rev196kai\RASSimulator\channel.go
mklink c:\users\ebata\20220830rev196kai\REALSimulator\key.pem c:\users\ebata\20220830rev196kai\RASSimulator\key.pem
mklink c:\users\ebata\20220830rev196kai\REALSimulator\person.go c:\users\ebata\20220830rev196kai\RASSimulator\person.go
mklink c:\users\ebata\20220830rev196kai\REALSimulator\real_person.go c:\users\ebata\20220830rev196kai\RASSimulator\real_person.go
mklink c:\users\ebata\20220830rev196kai\REALSimulator\util.go c:\users\ebata\20220830rev196kai\RASSimulator\util.go
mklink c:\users\ebata\20220830rev196kai\REALSimulator\virtual_person.go c:\users\ebata\20220830rev196kai\RASSimulator\virtual_person.go
mklink /D c:\users\ebata\20220830rev196kai\REALSimulator\data c:\users\ebata\20220830rev196kai\RASSimulator\data
mklink /D c:\users\ebata\20220830rev196kai\REALSimulator\routing c:\users\ebata\20220830rev196kai\RASSimulator\routing

で、simulator.goだけは本体をコピペして、こんな感じになりました。

以上

 

 

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

redisのブロードキャストで構造体データを運ぶ時の注意(というか、golangのキャストがC/C++みたいに単純でない件)

で記載していますが、Redisのブロードキャストを大変「ありがたく」使っているのですが、

/switch v := psc.Receive().(type) {

では、受信でロックしてしまうので、これを破る(Break)する方法を探して、以下の方法を見つけたのですが

switch v := psc.ReceiveWithTimeout(5 * time.Second).(type) {

これが、上手く動きません。

// go run sub.go
// goga\1-9-6\others\sub2.go

package main

import (
	"encoding/json"
	"fmt"
	"time"

	"github.com/gomodule/redigo/redis"
)

type Ch5_info struct {
	Bus_num int     // バスの番号
	CC      int     //  1: 座標情報 2: 停車位置情報
	Present int     // 停車位置番号
	Lat     float64 //
	Lon     float64 //
}

func main() {
	// 接続
	conn, err := redis.Dial("tcp", "localhost:6379")
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	psc := redis.PubSubConn{Conn: conn}
	psc.Subscribe("channel_1")

	for {
		c5i := new(Ch5_info)

		//switch v := psc.Receive().(type) {
		switch v := psc.ReceiveWithTimeout(5 * time.Second).(type) {
		case redis.Message:
			fmt.Printf("%s: message: %s\n", v.Channel, v.Data)

			_ = json.Unmarshal(v.Data, &c5i)

			// 試しに2つほど出力してみる
			fmt.Println(c5i.Bus_num)
			fmt.Println(c5i.Lon)

		case redis.Subscription:
			fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count)
		case error:
			fmt.Println("After 10 seconds. out of switch")
			time.Sleep(time.Millisecond * 1000)
			//psc.Conn.Flush()
			//psc.Subscribe("channel_1")
		}

		//		fmt.Println("out of switch")

		//if c5i.CC == -1 {
		//	break
		//}

	}

	fmt.Println("Catch break")
}

"case error"
これで、5秒を経過すると"case error"に飛んでしまい、その後、forループで戻しても、常に"case error"に行ってしまいます。

どなたか、5後にswitchを抜けた後、また同じようにswitchで5秒を待つようにする方法をご存知の方、御一報下されば大変助かります。

ちなみに、参考までに、データ配送側のプログラムはこんな感じです(1回配送、走り切りです)。

// go run pub.go
// goga\1-9-6\others\pub.go
package main

import (
	"encoding/json"
	"fmt"

	"github.com/gomodule/redigo/redis"
)

type Ch5_info struct {
	Bus_num int     // バスの番号
	CC      int     //  1: 座標情報 2: 停車位置情報
	Present int     // 停車位置番号
	Lat     float64 //
	Lon     float64 //
}

func main() {
	// 接続
	conn, err := redis.Dial("tcp", "localhost:6379")
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	c5i := new(Ch5_info)

	c5i.Bus_num = 1
	c5i.CC = -1
	c5i.Present = 23
	c5i.Lat = 12.34
	c5i.Lon = 56.78

	json_c5i, _ := json.Marshal(c5i)

	// パブリッシュ
	r, err := redis.Int(conn.Do("PUBLISH", "channel_1", json_c5i))
	if err != nil {
		panic(err)
	}
	fmt.Println(r)
}

 

 

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

以下の記述は間違っているようですので、参照にしないで下さい(現在検証中)。
(昨日、ソフト外注会社の方に教えて貰いました)

 

Golangで、http.HandleFunc と http.Handleについて、ずっと混乱しつづけています。

というか、私は、使い方が分かればよくて、その理屈なんぞ1mmも興味がないので、コードを書きながら理解しています(結局、遅くなっているような気がしますが)。

1. http.Handle()は、index.htmlをベースとしたサーバを立てるもの

// main16.go 現在の居場所は、c:\Users\ebata\hirohakama\199A2\others
/*
.
├── main16.go
├── index.html (A)
└── chart            # chartフォルダに静的ファイルがある
    └── index.html (B)
*/

package main

import (
	"net/http"
)

func main() {

	// 静的ファイル配信.
	// ディレクトリ名をURLパスに使う場合
	// 例:http://localhost:8080/chart/で index.html (B) の方を表示
	http.Handle("/chart/", http.FileServer(http.Dir("./")))

	// 例:http://localhost:8080/で index.html (A) の方を表示
	http.Handle("/", http.FileServer(http.Dir("./")))

	// ディレクトリ名とURLパスを変える場合
	// 例:http://localhost:8080/mysecret/sample1.txt
	// http.Handle("/mysecret/", http.StripPrefix("/mysecret/", http.FileServer(http.Dir("./contents"))))

// 例:http://localhost:8080/で index.html (A) の方を表示
	http.Handle("/", http.FileServer(http.Dir("./")))
	// 8080ポートで起動
	http.ListenAndServe(":8080", nil)
}

これで、main16.goが置いている場所が、基準点となります(それだけです)。

で、色々考えずに、基本は、

http.Handle("/", http.FileServer(http.Dir("./")))

としておきましょう(というか、これがデフォルトなら、記載すらしなくてもいい)

2. http.HandleFunc()は、ソースコードで書いたものをサーバとするもの

// main15.go 現在の場所はc:\Users\ebata\hirohakama\199A2\others

/*
.
└── main15.go

*/

package main

import (
	"io"
	"log"
	"net/http"
)

func h1(w http.ResponseWriter, _ *http.Request) {
	io.WriteString(w, "Hello from a HandleFunc #1!\n")
}

func h2(w http.ResponseWriter, _ *http.Request) {
	io.WriteString(w, "Hello from a HandleFunc #2!\n")
}

func main() {

	// http://localhost:8080/ で h1の内容を表示 (プログラムの内容を)
	http.HandleFunc("/", h1)

	// http://localhost:8080/endpoint で h2の内容を表示
	http.HandleFunc("/endpoint", h2)

	log.Fatal(http.ListenAndServe(":8080", nil))
}

3. http.Handle()1つとhttp.handleFunc()1つが混在しているものは、それぞれサーバが2つある、ということ

// main13.go

package main

import (
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"time"

	"github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{}

type GetLoc struct {
	ID    int     `json:"id"`
	Lat   float64 `json:"lat"`
	Lng   float64 `json:"lng"`
	TYPE  string  `json:"type"` // "PERSON","BUS","CONTROL
	POPUP int     `json:"popup"`
	//Address string  `json:"address"`
}

func echo3(w http.ResponseWriter, r *http.Request) {
	upgrader.CheckOrigin = func(r *http.Request) bool { return true } // おまじない
	conn2, err := upgrader.Upgrade(w, r, nil) //conn2でwebsocketを作成
	if err != nil {
		log.Println("websocket connection err:", err)
		return
	}
	defer conn2.Close()

	for {
		gl2 := new(GetLoc)
		gl2.ID = rand.Intn(20) // ここで乱数を発生されて、javascriptで受信させる
		gl2.Lat = 181.0
		gl2.Lng = 181.0
		gl2.TYPE = "BUS"
		gl2.POPUP = 101

		err := conn2.WriteJSON(&gl2)
		if err != nil {
			log.Println("ReadJSON:", err)
			break
		}
		fmt.Println("echo3:", gl2)
		time.Sleep(time.Second * 1)
	}

}

//var addr = flag.String("addr", "0.0.0.0:5000", "http service address") // テスト

func main() {

	http.Handle("/chart/", http.FileServer(http.Dir("./")))

	http.HandleFunc("/echo3", echo3)

	//log.Println("server starting...", "http://localhost:8080")
	//log.Fatal(http.ListenAndServe("localhost:8080", nil))
	log.Fatal(http.ListenAndServe(":8080", nil))
}

index.html

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>test</title>

</head>

<script type="text/javascript" src="moment.js"></script>
<script type="text/javascript" src="Chart.js"></script>
<script type="text/javascript" src="chartjs-plugin-streaming.js"></script> 



<script>  
    var ws;

    // websocketのオープン(この段階で接続完了)
    ws = new WebSocket('ws://localhost:8080/echo3')  // ユーザ登録画面

    ws.onopen = function (event) {
    }

    ws.onmessage = function (event) {
        // 送られてきたデータを受信して、JSON形式に変更
        var obj = JSON.parse(event.data);
        console.log("obj:",obj);
        console.log("obj.id:",obj.id);
        aa = obj.id;
    }
</script>  

<body BGCOLOR="black" text="white"  STYLE="overflow: hidden;">

	<center>
	  <font size="5">Waking Time(min.) <br></font> <!--- 意味のない表示 -->
	  <font size="5"> 歩行時間(分)</font> <!--- 意味のない表示 -->
	</center>
	
    <canvas id="myChart" width="100" height="85"></canvas>


<script>  
    var ctx = document.getElementById('myChart').getContext('2d');
			var chart = new Chart(ctx, {
				type: 'line',
				data: {
					datasets: [{
                        data: [],  // 1つめ
                        borderColor: "rgba(255,0,0,1)",
                        backgroundColor: "rgba(0,0,0,0)",  
                        lineTension: 0,
                        label: 'Time',
					}]
				},
				options: {
					scales: {
						xAxes: [{
                            type: 'realtime',
                            realtime: {
                                duration: 30000, // 300000ミリ秒(5分)のデータを表示 (コメントアウトすると早く動く)
                                onRefresh: function(chart) {
                                    chart.data.datasets.forEach(function(dataset) {
                                        dataset.data.push({
                                            x: Date.now(),
                                            //y: (Math.floor(Math.random()*16)+30) //30-45の乱数(整数)
                                            y: aa, // この"aa"が、送られてきたデータ
                                        });
                                    });
                                }
                            }

                        }],
                        yAxes: [{
					        ticks: {
					        	max: 20,
					        	min: 0
        					}
                        }]
					}
				}
			});

</script>

</body>
</html>

 

この場合、

  • /chart/index.htmlをベースとするサーバと、
  • echo3()が作るサーバ

の2つがある、ということ。

実際のところ、echo3は、/chart/index.html のクライアント(データの送信元)でもあるんだけど、要求があれば、ポコポコ作り出される、という点ではサーバでもある、という形になっています。

―― という説明を、次に私が頭を抱えた時に、私が思い出せるのかが、不安です

 

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

csvファイル形式でデータを送るのではなく、プログラムから直接データを放り込むプログラム

 

// main13.go

package main

import (
	"flag"
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{}

type GetLoc struct {
	ID    int     `json:"id"`
	Lat   float64 `json:"lat"`
	Lng   float64 `json:"lng"`
	TYPE  string  `json:"type"` // "PERSON","BUS","CONTROL
	POPUP int     `json:"popup"`
	//Address string  `json:"address"`
}

func echo2(w http.ResponseWriter, r *http.Request) {
	conn2, err := upgrader.Upgrade(w, r, nil) //conn2でwebsocketを作成
	if err != nil {
		log.Println("websocket connection err:", err)
		return
	}
	defer conn2.Close()

	for {
		gl2 := new(GetLoc)
		gl2.ID = 101
		gl2.Lat = 181.0
		gl2.Lng = 181.0
		gl2.TYPE = "BUS"
		gl2.POPUP = 101

		err := conn2.WriteJSON(&gl2)
		if err != nil {
			log.Println("ReadJSON:", err)
			break
		}
		fmt.Println("echo2:", gl2)
		time.Sleep(time.Second * 1)
	}

}

var addr = flag.String("addr", "0.0.0.0:5000", "http service address") // テスト

func main() {
	http.Handle("/", http.FileServer(http.Dir(".")))

	http.HandleFunc("/echo2", echo2)

	log.Println("server starting...", "http://localhost:5000")
	log.Fatal(http.ListenAndServe("localhost:5000", nil))
}

index.htmlはこちら

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>test</title>

</head>
<body>
    <form>
    </form>

    <script>  

        var ws;

        // websocketのオープン(この段階で接続完了)
        ws = new WebSocket('ws://localhost:5000/echo2')  // ユーザ登録画面

        ws.onopen = function (event) {
		    }

        ws.onmessage = function (event) {

          // データをJSON形式に変更
          var obj = JSON.parse(event.data);
          console.log("obj:",obj);
        }
   
    </script>
</body>
</html>

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

Keyword マイク スピーカー 音 声 音声 office teams 設定 失敗

最近、リモート会議で結構スピーカーとかマイクの設定でコケるので、設定画面を書き出しておく。

だいたい、このスピーカーとマイクの設定でコケている

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

OpenStreetMapを使って、シミュレータを作成していますが、昨今のセキュリティ事故などを鑑みて、インターネットへのアクセスが一切禁じられた状態での使用を想定しておかなければなりません。

という訳で、OpenStreetMapから地図をバラバラにした画像ファイル(タイル)のダウンロードを試みているのですが、適当にダウンロードすると、不要なタイルまでダウンロードして、

(1)OpenStreetMapのサーバに負荷を与える(という公的な理由)こと

(2)ローカルに不要なファイルが大量に残る(という私的な理由)こと

が、なんとも気にいりません。

ならば「キャッシュに残っているタイルだけをダウロードすればよくね?」と思い(足りなくい部分が見つかったら、その部分だけ再度ダウンロードすればいい)、検討を開始しました。

先ず、キャッシュされている情報を確認してみました。

展開すると、こんな感じ

さて、キャッシュされている、タイルのダンロード先を見つける方法が必要でした。

これは、chrome_cache_view というツールを使うことで解決できそうであることが分かりました。

↓をクリックすると、画面に飛びます。

で、ここからダウンロードします。

解凍して、exeファイルをクリックします。

こんな感じでタイルサーバの位置とファイル名が分かります。

で、tile.osm.org の部分のファイルだけを取り出せればよいのですが、(今のところ、私には、見つけられていません)ので、"Edit" → "Select All" で全部のテキストを取り出して、適当にファイルをぶった切って、取り出すことにしました (良いやり方があったら、私に教えて下さい)。

# ちなみに、この取り出し方は、皆さんの方で好きなようにやって下さい(私は、grepと、emacsを使って手動で切り出すことにしました)。

私の場合、まず、"Edit" → "Select All" で全部のテキストをdummy.txtという名前で保存して、

grep tile.osm.org dummy.txt > dummy2.txt

として

になっている状態で、emacsのkill-rectangle で両端を切り落しました。

さて、ここから問題なのですが、OpenStreetMapの格納方法は、なんでもダウンロードすれば良い、というものではなく、ルールがあります。

/* map の表示準備 */
const map = L.map("map", {
    attributionControl: false,
    zoomControl: false
}).setView(CENTER_LATLNG, 14);
if (USE_OFFLINE_MAP) {
    L.tileLayer('images/map-yamaguchi/{z}/{x}/{y}.png', {
        detectRetina: true,
        minZoom: 13,
        maxZoom: 15,
    }).addTo(map);
}
else {
    L.tileLayer('https://{s}.tile.osm.org/{z}/{x}/{y}.png', {
        detectRetina: true,
        maxNativeZoom: 18
    }).addTo(map);
}
つまり

https://c.tile.osm.org/18/232959/102413.png
の場合、

"18"というディレクトリを作成し、さらにその中に"232959"というサブディレクトリを作成し、その中に"102313.png"というファイルを配置させる必要があるのです。

これを実施するプログラムをGolangで作りました。

/*
	main13.go

	キャッシュで取り込まれているOpenStreetMapのタイル画像(png)を、ローカルに取り込んで、
	ネットに繋がれていない状況でも、OpenStreetMapを使えるようにする

	前提
	http://www.nirsoft.net/utils/chrome_cache_view.html からChromeCacheView をダウンロードして、
	"https://b.tile.osm.org/13/7284/3196.png"などを取得しておくこと
*/

package main

import (
	"io"
	"net/http"
	"os"
	"strings"

	_ "github.com/lib/pq"
)

func main() {

	var urls = [...]string{
		"https://b.tile.osm.org/13/7284/3196.png",
		"https://c.tile.osm.org/18/232959/102413.png"} // ここに取得したいURLを記載する

	for _, url := range urls {

		arr1 := strings.Split(url, "/")

		//fmt.Println(arr1[3]) // 確認用
		//fmt.Println(arr1[4]) // 確認用
		//fmt.Println(arr1[5]) // 確認用

		os.Mkdir(arr1[3], 0777) // ディレクトリを掘る(すでに掘っていてもOKみたい)
		os.Chdir(arr1[3])       // カレントディレクトリを移動する
		os.Mkdir(arr1[4], 0777) // ディレクトリを掘る(すでに掘っていてもOKみたい)
		os.Chdir(arr1[4])

		response, err := http.Get(url)
		if err != nil { // カレントディレクトリを移動する
			panic(err)
		}
		defer response.Body.Close()

		file, err := os.Create(arr1[5])
		if err != nil {
			panic(err)
		}
		defer file.Close()

		io.Copy(file, response.Body) // ここでダウンロードしたファイルをセーブ

		err = os.Chdir("../..") // ディレクトリを元の位置に戻す(2つ上がる)
		if err != nil {
			panic(err)
		}
	}
}

こんな感じで、上手く動いているようです。

上手く動いていません。

 

ダウンロードした全部のファイルに、

Access denied. See https://operations.osmfoundation.org/policies/tiles/

というテキストが書かてているファイルがダウンロードされています。

どうも、以下の問題みたいです。

Technical Usage Requirements

  • Valid HTTP User-Agent identifying application. Faking another app’s User-Agent WILL get you blocked. Using a library’s default User-Agent is NOT recommended. If a device automatically sends an X-Requested-With header with an application specific Application ID, this will be considered an acceptable substitute for the HTTP User-Agent, although we still recommend setting a valid HTTP User-Agent for the application.
  • When coming from a web page, a valid HTTP Referer. Apps generally do not have a HTTP referer.
  • DO NOT send no-cache headers. (“Cache-Control: no-cache”, “Pragma: no-cache” etc.)
  • Cache Tile downloads locally according to HTTP Expiry Header, alternatively a minimum of 7 days.
  • Maximum of 2 download connections. (Unmodified web browsers’ download limits are acceptable.)

技術的な使用条件
アプリケーションを識別する有効なHTTP User-Agent。他のアプリケーションのUser-Agentを偽装すると、ブロックされる可能性があります。ライブラリのデフォルトのUser-Agentを使用することは推奨されません。デバイスがアプリケーション固有の Application ID を持つ X-Requested-With ヘッダを自動的に送信する場合、これは HTTP User-Agent の代用として認められますが、アプリケーションに対して有効な HTTP User-Agent を設定することを推奨します。
ウェブページからアクセスする場合は、有効なHTTP Refererを指定します。アプリは一般的にHTTP Refererを持ちません。
no-cacheヘッダを送信しないでください。("Cache-Control: no-cache", "Pragma: no-cache" など)
HTTP Expiry Headerに従ってTileダウンロードをローカルにキャッシュします。
ダウンロード接続は最大2回まで。(修正されていないウェブブラウザのダウンロード制限も許容されます)。
注:標準的な設定の最近のウェブブラウザは、一般に上記の技術的要件をすべてクリアしています。

今、対策中です。暫くお待ち下さい。

以下のように変更したら動きました。


/*
	main14.go

	キャッシュで取り込まれているOpenStreetMapのタイル画像(png)を、ローカルに取り込んで、
	ネットに繋がれていない状況でも、OpenStreetMapを使えるようにする

	前提
	http://www.nirsoft.net/utils/chrome_cache_view.html からChromeCacheView をダウンロードして、
	"https://b.tile.osm.org/13/7284/3196.png"などを取得しておくこと
*/

package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"

	_ "github.com/lib/pq"
)

func main() {

	var urls = [...]string{
		"https://c.tile.osm.org/18/232959/102413.png",
		"https://b.tile.osm.org/18/232955/102413.png",
		"https://c.tile.osm.org/18/232959/102413.png"} // ここに取得したいURLを記載する

	for _, url := range urls {

		arr1 := strings.Split(url, "/")

		//fmt.Println(arr1[3]) // 確認用
		//fmt.Println(arr1[4]) // 確認用
		//fmt.Println(arr1[5]) // 確認用

		os.Mkdir(arr1[3], 0777) // ディレクトリを掘る(すでに掘っていてもOKみたい)
		os.Chdir(arr1[3])       // カレントディレクトリを移動する
		os.Mkdir(arr1[4], 0777) // ディレクトリを掘る(すでに掘っていてもOKみたい)
		os.Chdir(arr1[4])

		/*  削除
		response, err := http.Get(url)
		if err != nil { // カレントディレクトリを移動する
			panic(err)
		}
		defer response.Body.Close()
		*/

		// 追加(ここから)
		client := &http.Client{}

		req, err := http.NewRequest("GET", url, nil)
		if err != nil {
			fmt.Println(err)
			return
		}
		req.Header.Set("User-Agent", "super-go-client")
		// 追加(ここまで)

		file, err := os.Create(arr1[5])
		if err != nil {
			panic(err)
		}
		defer file.Close()

		// 追加(ここから)
		r, _ := client.Do(req)
		defer r.Body.Close()
		// 追加(ここまで)

		/*
			_, err = io.Copy(file, response.Body) // ここでダウンロードしたファイルをセーブ
		*/

		// 追加(ここから)
		_, err = io.Copy(file, r.Body)
		// 追加(ここまで)
		if err != nil {
			panic(err)
		}

		err = os.Chdir("../..") // ディレクトリを元の位置に戻す(2つ上がる)
		if err != nil {
			panic(err)
		}
	}
}

これで、画像ファイルとして取り出せることが確認できました。

req.Header.Set("User-Agent", "super-go-client")

がポイントだったようです。

予想通り、キャッシュのないところが、欠けています。これは、運用して直していけばいいので、そのうち直します。

ちなみに、index.htmlの方は、

var map = L.map("map", {
			attributionControl: false,
			zoomControl: false
		}).setView(new L.LatLng(36.56240644, 139.9501693), 14); // 宇都宮

		//L.tileLayer('http://localhost:8080/static/{z}/{x}/{y}.png',{
		L.tileLayer('static/{z}/{x}/{y}.png',{
        	detectRetina: true,
        	//minZoom: 13,
        	//maxZoom: 15,
			maxNativeZoom: 18
    	}).addTo(map);


		//L.tileLayer('https://{s}.tile.osm.org/{z}/{x}/{y}.png', {
		//	detectRetina: true,
		//	maxNativeZoom: 18
		//}).addTo(map);

のように、

L.tileLayer('http://localhost:8080/static/{z}/{x}/{y}.png',{

でも、

L.tileLayer('static/{z}/{x}/{y}.png',{

でも、動作するようです。