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

ワーシャル-フロイド法
(ダイクストラは個別ルートでは早いが、先に全ルート計算しておくなら、
こっちの方法の法が速いこともある)

と、

STLのリストの使い方(ファンクションへのリストの渡し方とか、リストの複製の作り方とか)
などの、便利な技が仕込まれているので貼っておく

/*
  g++ -g wf.cpp -o wf
 
  ワーシャル-フロイド法
  (ダイクストラは個別ルートでは早いが、先に全ルート計算しておくなら、
  こっちの方法の法が速いこともある)
  
  と、

  STLのリストの使い方(ファンクションへのリストの渡し方とか、リストの複製の作り方とか)
  などの、便利な技が仕込まれているので貼っておく


 
*/
 
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <list>   // list 利用のため

using namespace std;

//int d[100][100];  // d[i][k]:ノードiからノードkへの距離 
//int via[100][100];  // d[i][k]の間にある(少くとも1つの)中継ノード

double d[100][100];  // d[i][k]:ノードiからノードkへの距離 
int via[100][100];  //  d[i][k]の間にある(少くとも1つの)中継ノード

list<int> path[100][100];   // int 型の list を宣言  
 

#if 1
// 中継パスの表示ルーチン(再帰呼出し用)
void printPath1_aux(int begin, int end) {
  if (via[begin][end] == begin) {
	if (begin != end)
	  printf("%02d -> ", begin);
	return;
  }
  
  printPath1_aux(begin, via[begin][end]);
  printPath1_aux(via[begin][end], end);
}
#endif

// 中継パスの表示ルーチン(再帰呼出し用)
void printPath1_aux(int begin, int end, list<int>* p) {
  if (via[begin][end] == begin) {
	if (begin != end){
	  // printf("%02d -> ", begin);
	  p->push_back(begin);
	}
	return;
  }
  
  printPath1_aux(begin, via[begin][end], p);
  printPath1_aux(via[begin][end], end, p);
}


 
// 中継パスの表示ルーチン
#if 1
void printPath1(int start, int goal) {
  printPath1_aux(start, via[start][goal]);
  printPath1_aux(via[start][goal], goal);
  printf("%02d\n", goal);
}
#endif 

void printPath1(int start, int goal, list<int> *p ) {
  printPath1_aux(start, via[start][goal], p);
  printPath1_aux(via[start][goal], goal, p);
  // printf("%02d\n", goal);
  p->push_back(goal);

}

 
int main(void)
{
  // 変数の初期化
  for(int i = 0; i < 100; i++){
	for(int j = 0; j < 100; j++){
	  d[i][j] = 999.9; // 距離の初期化(でっかい値を入力しておく(INT_MAXは足し算の時に桁上がりが起こるので使わない)
	  via[i][j] = i; // ノードiからノードkへの経由値の初期化 
	}
  }
 
 #if 0
  // 確認用の表示
  printf("\n[STEP1]\n");
 
  for(int i = 0; i < 100; i++){
	for(int k = 0; k < 100; k++){
	  printf("d[%d][%d]):%f\t",i,k,d[i][k]);
	  printf("via[%d][%d]):%d\n",i,k,via[i][k]);
	}
  }
#endif

  //// ここからは実際の距離を手書き
 
  for(int i = 0; i < 100; i++){
	d[i][i] = 0; //// 同じノードへの距離は0になるので、上書き
  }
 
  //ノード番号の通番を以下のようにする
  // [0][2] → "02", [4][9] → "49", [9][[9] → "99"
  // 座標は1ケタ内に留める

  for (int y = 0; y < 5; y++){
	for (int x = 0; x < 9; x++){

	  int n_num = x * 10 + y;

	  // + ( 1, 0)
	  int x_new = x + 1;
	  int y_new = y;

	  if (x_new < 9){
		int n_num_next = x_new * 10 + y_new;
		d[n_num][n_num_next] = 0.069;
		
		printf("1:d[%02d][%02d]=%f\n",n_num, n_num_next, d[n_num][n_num_next]);

	  }

	  // + (-1, 0)
	  x_new = x - 1;
	  y_new = y;

	  if (x_new > -1 ){
		int n_num_next = x_new * 10 + y_new;
		d[n_num][n_num_next] = 0.069;
		printf("2:d[%02d][%02d]=%f\n",n_num, n_num_next, d[n_num][n_num_next]);
	  }

	  // + ( 0, 1)
	  x_new = x;
	  y_new = y + 1;

	  if (y_new < 5 ){
		int n_num_next = x_new * 10 + y_new;
		d[n_num][n_num_next] = 0.069;
		printf("3:d[%02d][%02d]=%f\n",n_num, n_num_next, d[n_num][n_num_next]);
	  }

	  // + ( 0,-1)
	  x_new = x;
	  y_new = y - 1;

	  if (y_new > -1 ){
		int n_num_next = x_new * 10 + y_new;
		d[n_num][n_num_next] = 0.069;
		printf("4:d[%02d][%02d]=%f\n",n_num, n_num_next, d[n_num][n_num_next]);
	  }
	}
  }

  // 実験用上書き
  d[02][12] = 0.025;  
  d[12][22] = 0.025;  
  d[22][32] = 0.025;  
  d[32][42] = 0.025;  
  d[42][52] = 0.025;  
  d[52][62] = 0.025;  
  d[62][72] = 0.025;  
  d[72][82] = 0.025;  

  d[12][02] = 0.025;  
  d[22][12] = 0.025;  
  d[32][22] = 0.025;  
  d[42][32] = 0.025;  
  d[52][42] = 0.025;  
  d[62][52] = 0.025;  
  d[72][62] = 0.025;  
  d[82][72] = 0.025;  


#if 1
  // 確認用の表示
  printf("\n[STEP2]\n");
 
  for(int i = 0; i < 99; i++){
	for(int k = 0; k < 99; k++){
	  printf("d[%d][%d]):%f\t",i,k,d[i][k]);
	  printf("via[%d][%d]):%d\n",i,k,via[i][k]);
	}
  }
#endif
 

  // 経路長計算
  for (int k =0; k < 99; k++){  
	for (int i =0; i < 99; i++){
	  for(int j = 0; j < 99; j++){
		if(d[i][j] > d[i][k] + d[k][j]){
		  d[i][j] = d[i][k] + d[k][j];
		  via[i][j] = k; //更新処理
		}
	  }
	}
  }

 
#if 0
  // 計算結果
  printf("\n[STEP3]\n");
 
  for(int i = 0; i < 99; i++){
	for(int k = 0; k < 99; k++){
	  printf("d[%d][%d]):%f\t",i,k,d[i][k]);
	  printf("via[%d][%d]):%d\n",i,k,via[i][k]);
	}
  }
#endif 

#if 1
  // 経路パス表示
  printf("\n[Path]\n");
  for(int i = 0; i < 99; i++){
	for(int k = 0; k < 99; k++){
	  if (d[i][k] < 99.9){
		printf("d[%02d][%02d]:%f ",i,k,d[i][k]);
		printPath1(i, k);
		printPath1(i, k, &(path[i][k]));
	  }
	}
  }
#endif
  
  
  // イテレータ (反復子) の定義
  list<int>::iterator pos;

  list<int> l = path[83][04];
  // イテレータをずらしながら、全てのデータを取り出す。
  for(pos = l.begin(); pos!=l.end(); ++pos){
      cout << *pos << "\n";
  }

  printf("\n");


  // https://cpprefjp.github.io/reference/algorithm/copy.html
  // back_inserter を使って l2 へ設定。
  // back_inserter は要素をコピーするときに l2.push_back() するイテレータを作る関数。

  //std::list<int> l2;
  list<int> l2;  

  //std::copy(l.begin(), l.end(), back_inserter(l2));
  copy(l.begin(), l.end(), back_inserter(l2));

  // l2.erase(v.begin() + 2);       //  3番目の要素(9)を削除
  l2.erase(l2.begin());       // 先頭の要素を削除


  for(pos = l2.begin(); pos!=l2.end(); ++pos){
      cout << *pos << "\n";
  }


}

 

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

PruneClusterのマーカを一個づつ消すには、面倒でも以下のようにしなければならない

for (let i = 0; i < markers.length; ++i) {
				if (obj.id == markers[i].hashCode){
					console.log(i)
					console.log(obj.id)										
					console.log("obj.id == markers[i].hashCode")

					//leafletView.RemoveMarkers(markers[obj.id]);  // これでは消えてくれません
					// 1つのマーカーを消すのに、面倒でも以下の2行が必要
					var deleteList = markers.splice(i, 1);					
					leafletView.RemoveMarkers(deleteList);

					// 以下失敗例リスト
					//leafletView.RemoveMarkers(markers[i].hashCode);  //これはダメ
					//leafletView.RemoveMarkers(markers[obj.id],'item');
					//leafletView.ProcessView(); // 試しに入れてみる
					//leafletView.RemoveMarkers(markers[i-1]);
					//leafletView.RemoveMarkers(markers);					
					break;
				}
			}

ちなみに、インターバル関数で、定期的にProcessView()を叩いておかないと更新されないことにも注意のこと。

// 位置情報の更新
    window.setInterval(function () {
        leafletView.ProcessView();  // 変更が行われたときに呼び出されれなければならない (ここでは1000ミリ秒)
	}, 1000);

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

  • 背景
    • 事業部に散々脅かされた
    • 「もし、ハックされたら研開(というか江端一人)で責任取れよ」と念を押された
    • AWSコンソールは、江端のみにログイン権限があるので、ここからいじるのは難しいそう
  • 江端予測
    • EC2への直接の攻撃・または侵入
      • 一応SSHアクセスはできる状態
      • SSHによる攻撃はかなり難しいのではないか(秘密鍵がある限り)
    • EBLへの攻撃・または侵入
      • 無闇なアタックはできるだろうが、内部に入ることはできないのでは?
    • 取られる情報
      • 個人情報はないので、個人情報の流出は不可能だが、ユーザ利用情報やら運行情報が取られる可能性もある
  • 江端が調べた範囲のこと
    • SQLインジェクション
      • WebからSQLに触れないので意味なし
    • XSS(クロスサイトスクリプティング)
      • Webサイトのリンクは限定的であり、外部から改竄される余地がない
    • バッファオーバーフロー
      • ELBでEC2を保護しているから、サービスは落せない(?)
    • WebDAV
      • 本サービスでは使っていないので関係なし
    • FTPS(FTP over SSL)やSFTP(SSH File Transfer Protocol)等)を採用しているか
      • FTPサーバ上げる予定がないので、自動的にこれを採用することになる
    • メールサーバ
      • メールサーバがない
    • CMS
      • 使用しない
    • 意図しないサーバへのゾーン転送の防止
      • 発生しえない
    • 安全なHTTPS通信
      • TLSを利用したhttps通信を使用
    • 社外公開サーバ セキュリティ対策リスト_20191007.xlsxにて確認済み
  • 江端不明点
    • EC2からIAMを書き換えることって可能なのか?

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

キーワード: 切取り、切り取り、削除、圧縮、キャプチャ、画面、編集、

VideoSmaller動画のファイルサイズをオンラインで小さくする

■Avidemux/aviUtl→動画(mp4)のいらん部分を切りすてる

■amuseGraphics → これは使ったことないなぁ

■AG-デスクトップレコーダー → PC画面をキャプチャする(使い倒している)

■RealPlayer →wmvファイルをmp4に変換するのに使っている(ちなみにVLCでは、結構な音ずれが発生する)

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

goルーチン対応完了。通信情報を全部ルーチンの中に突っ込んで、さらにサーバの方を、リクエストを一回つだけしか受けつけないようにミューテックスでロックしてみた。ただし動作は、まだ不安定。

/*
// server12.go ペアはclient7.go

// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build ignore

// 使い方
// go run server9.go      (適当なシェルから)
// http://localhost:8080  (ブラウザ起動)
*/

package main

import (
	"flag"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"sync"

	"github.com/gorilla/websocket"
)

// GetLoc GetLoc
type GetLoc struct {
	ID  int     `json:"id"`
	Lat float64 `json:"lat"`
	Lng float64 `json:"lng"`
	//Address string  `json:"address"`
}

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

var upgrader = websocket.Upgrader{} // use default options

var chan2_1 = make(chan GetLoc)

var maxid = 0

var mutex sync.Mutex

func echo2(w http.ResponseWriter, r *http.Request) {
	c, err := upgrader.Upgrade(w, r, nil) // cはサーバのコネクション
	if err != nil {
		log.Print("upgrade:", err)
		return
	}
	defer c.Close()

	//mutex := new(sync.Mutex)

	for {
		//mt, message, err := c.ReadMessage() // クライアントからのメッセージの受信(mtはクライアント識別子)
		//_, _, err := c.ReadMessage() // クライアントからのメッセージの受信(mtはクライアント識別子)

		mutex.Lock()

		gl := new(GetLoc)

		err := c.ReadJSON(&gl) // クライアントからのメッセージの受信

		// 原因不明の対処処理
		if gl.ID == 0 && gl.Lat < 0.01 && gl.Lng < 0.01 {
			mutex.Unlock()
			break
		} else if gl.ID < -1 { // 受理できないメッセージとして返信する
			//条件分岐 (変なIDが付与されているメッセージは潰す)
			//if (gl.ID > maxid) || (gl.ID < -1) { // 受理できないメッセージとして返信する

			gl.ID = -1
			gl.Lat = -999
			gl.Lng = -999
			err2 := c.WriteJSON(gl)
			if err2 != nil {
				log.Println("write:", err2)
				break
			}
		} else { // それ以外は転送する
			log.Printf("echo2 after c.WriteJSON(gl) ID:%d", gl.ID)
			log.Printf("echo2 after c.WriteJSON(gl) Lat:%f", gl.Lat)
			log.Printf("echo2 after c.WriteJSON(gl) Lng:%f", gl.Lng)

			if err != nil {
				log.Println("read:", err)
				break
			}
			fmt.Printf("echo2 before chan2_1 <- *gl\n")
			chan2_1 <- *gl
			fmt.Printf("echo2 after chan2_1 <- *gl\n")

			//で、ここで受けとる
			//gl2 := new(GetLoc)
			fmt.Printf("echo2 before gl2 := <-chan2_1\n")
			gl2 := <-chan2_1
			maxid = gl2.ID // ID最大値の更新
			log.Printf("echo2 after gl2 := <-chan2_1 ID:%d", gl2.ID)
			log.Printf("echo2 after gl2 := <-chan2_1 Lat:%f", gl2.Lat)
			log.Printf("echo2 after gl2 := <-chan2_1 Lng:%f", gl2.Lng)

			fmt.Printf("echo2 before err2 := c.WriteJSON(gl2)\n")
			err2 := c.WriteJSON(gl2)
			fmt.Printf("echo2 after err2 := c.WriteJSON(gl2)\n")
			if err2 != nil {
				log.Println("write:", err2)
				break
			}
			fmt.Printf("end of echo2\n")

		}

		mutex.Unlock()
	}
}

func echo(w http.ResponseWriter, r *http.Request) {

	c, err := upgrader.Upgrade(w, r, nil) // cはサーバのコネクション
	if err != nil {
		log.Print("upgrade:", err)
		return
	}
	defer c.Close()

	/*	ここでロックして待つ */

	for {

		fmt.Printf("echo before gl := <-chan2_1\n")
		gl := <-chan2_1
		fmt.Printf("echo after gl := <-chan2_1\n")

		fmt.Printf("echo before err = c.WriteJSON(gl) gl2.id = %d\n", gl.ID)
		fmt.Printf("echo before err = c.WriteJSON(gl) gl2.lat = %f\n", gl.Lat)
		fmt.Printf("echo before err = c.WriteJSON(gl) gl2.lng= %f\n", gl.Lng)
		err = c.WriteJSON(gl)
		if err != nil {
			log.Println("WriteJSON:", err)
		}
		fmt.Printf("echo after err = c.WriteJSON(gl)\n")

		fmt.Printf("echo before err = c.RreadJSON(gl)\n")
		gl2 := new(GetLoc)
		err2 := c.ReadJSON(&gl2)
		fmt.Printf("echo after err = c.ReadJSON(&gl2) gl2.id = %d\n", gl2.ID)
		fmt.Printf("echo after err = c.ReadJSON(&gl2) gl2.lat = %f\n", gl2.Lat)
		fmt.Printf("echo after err = c.ReadJSON(&gl2) gl2.lng= %f\n", gl2.Lng)
		if err2 != nil {
			log.Println("ReadJSON:", err2)
		}
		// ここからチャネルで返す
		fmt.Printf("echo before chan2_1 <- *gl2 gl2.id = %d\n", gl2.ID)
		fmt.Printf("echo before chan2_1 <- *gl2 gl2.lat = %f\n", gl2.Lat)
		fmt.Printf("echo before chan2_1 <- *gl2 gl2.lng = %f\n", gl2.Lng)
		chan2_1 <- *gl2
		fmt.Printf("echo after chan2_1 <- *gl2\n")
		fmt.Printf("end of echo\n")
	}

}

func home(w http.ResponseWriter, r *http.Request) {
	homeTemplate.Execute(w, "ws://"+r.Host+"/echo")
}

func main() {
	flag.Parse()
	log.SetFlags(0)

	http.HandleFunc("/echo2", echo2)           // echo関数を登録 (サーバとして必要)
	http.HandleFunc("/echo", echo)             // echo関数を登録 (サーバとして必要)
	http.HandleFunc("/", home)                 // home関数を登録
	log.Fatal(http.ListenAndServe(*addr, nil)) // localhost:8080で起動をセット
}

var homeTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>PruneCluster - Realworld 50k</title>

	<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.0-beta.2.rc.2/leaflet.css"/>
	<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.0-beta.2.rc.2/leaflet.js"></script>

	<script src="http://kobore.net/PruneCluster.js"></script>           <!-- これ、いずれローカルホストから取れるように換える -->
	<link rel="stylesheet" href="http://kobore.net/examples.css"/>      <!-- これも、いずれローカルホストから取れるように換える -->

	<!-- goのテンプレートのローカルって、どこになるんだろう? -->

</head>
<body>
<div id="map"></div>

<script>

	ws = new WebSocket("{{.}}"); // websocketの確立

	/*
	var print = function(message) {
		var d = document.createElement("div");
		d.textContent = message;
		output.appendChild(d);
	};
	*/

	// 引数にはミリ秒を指定。(例:5秒の場合は5000)
	function sleep(a){
  		var dt1 = new Date().getTime();
  		var dt2 = new Date().getTime();
  		while (dt2 < dt1 + a){
			dt2 = new Date().getTime();
		}
  		return;
	}

    var map = L.map("map", {
        attributionControl: false,
        zoomControl: false
    }).setView(new L.LatLng(35.654543, 139.795534), 18);

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

    var leafletView = new PruneClusterForLeaflet(1,1);  // (120,20)がデフォルト

	ws.onopen = function (event) {
	}

	var markers = [];

	// 受信すると、勝手にここに飛んでくる
	ws.onmessage = function (event) {
		// データをJSON形式に変更
		var obj = JSON.parse(event.data);

		console.log("233");	
		console.log(obj.id);
		console.log(obj.lat);						
		console.log(obj.lng);	


		if (obj.id == 0){  // idが未登録の場合
			console.log("obj.id == 0")
			// データをマーカーとして登録
			var marker = new PruneCluster.Marker(obj.lat, obj.lng);
			console.log(marker.hashCode);		
			markers.push(marker);
	
			leafletView.RegisterMarker(marker);
	
			console.log(markers);
			console.log(markers.length)

			obj.id = marker.hashCode;
			//ws.send(marker.hashCode); // テキスト送信
			var json_obj = JSON.stringify(obj);
			ws.send(json_obj);			
		} else if ((Math.abs(obj.lat) > 90.0) || (Math.abs(obj.lng) > 180.0)){ // 異常な座標が入った場合は、マーカーを消去する
			console.log("Math.abs(obj.lat) > 180.0)")
			for (let i = 0; i < markers.length; ++i) {
				if (obj.id == markers[i].hashCode){
					console.log(i)
					console.log(obj.id)										
					console.log("obj.id == markers[i].hashCode")
					leafletView.RemoveMarkers(markers[obj.id]);
					//leafletView.RemoveMarkers(markers[i-1]);
					//leafletView.RemoveMarkers(markers);					
					break;
				}
			}
			obj.lat = 91.0;
			obj.lng = 181.0;
			var json_obj = JSON.stringify(obj);
			ws.send(json_obj);				
		} else {
			// 位置情報更新
			console.log("else")
			for (let i = 0; i < markers.length; ++i) {
				if (obj.id == markers[i].hashCode){
					var ll = markers[i].position;
					ll.lat = obj.lat;
					ll.lng = obj.lng;
					break;
				}
			}
			var json_obj = JSON.stringify(obj);
			ws.send(json_obj);	
		}
	}

	// 位置情報の更新
    window.setInterval(function () {
        leafletView.ProcessView();  // 変更が行われたときに呼び出されれなければならない
	}, 500);

	// サーバを止めると、ここに飛んでくる
	ws.onclose = function(event) {
		//print("CLOSE");
		ws = null;
	}


    map.addLayer(leafletView);
</script>



</body>
</html>
`))
// client7.go ペアは server12.go

package main

import (
	"flag"
	"log"
	"net/url"

	"github.com/gorilla/websocket"

	"math/rand"
	"time"
)

// GetLoc GetLoc
type GetLoc struct {
	ID  int     `json:"id"`
	Lat float64 `json:"lat"`
	Lng float64 `json:"lng"`
	//Address string  `json:"address"`
}

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

func random(min, max float64) float64 {
	return rand.Float64()*(max-min) + min
}

func main() {
	for i := 1; i < 100; i++ {
		go passenger(i)
	}

	time.Sleep(25 * time.Second)
}

func passenger(count int) {

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

	//var upgrader = websocket.Upgrader{} // use default options
	_ = websocket.Upgrader{} // use default options

	rand.Seed(time.Now().UnixNano())

	flag.Parse()
	log.SetFlags(0)
	u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo2"}
	log.Printf("connecting to %s", u.String())

	c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
	if err != nil {
		log.Fatal("dial:", err)
	}
	defer c.Close()

	gl := GetLoc{
		ID:  0,
		Lat: 35.653976,
		Lng: 139.796821,
	}

	log.Printf("count:%d before 1 ID:%d", count, gl.ID)
	log.Printf("count:%d before 1 Lat:%f", count, gl.Lat)
	log.Printf("count:%d before 1 Lng:%f", count, gl.Lng)
	//err = c.WriteJSON(mt, gl)
	err = c.WriteJSON(gl)
	if err != nil {
		log.Println("write:", err)
	}

	gl2 := new(GetLoc)
	err = c.ReadJSON(gl2)
	log.Printf("count:%d after1 ID:%d", count, gl2.ID)
	log.Printf("count:%d after1 Lat:%f", count, gl2.Lat)
	log.Printf("count:%d after1 Lng:%f", count, gl2.Lng)

	gl.ID = gl2.ID
	for i := 0; i < 20; i++ {
		gl.Lat += random(0.5, -0.5) * 0.00001 * 10 * 5
		gl.Lng += random(0.5, -0.5) * 0.00002 * 10 * 5

		log.Printf("count:%d-%d before 2 ID:%d", count, i, gl.ID)
		log.Printf("count:%d-%d before 2 Lat:%f", count, i, gl.Lat)
		log.Printf("count:%d-%d before 2 Lng:%f", count, i, gl.Lng)

		err = c.WriteJSON(gl)
		if err != nil {
			log.Println("write:", err)
		}
		gl2 := new(GetLoc)
		err = c.ReadJSON(gl2)
		log.Printf("count:%d-%d after 2 ID:%d", count, i, gl2.ID)
		log.Printf("count:%d-%d after 2 Lat:%f", count, i, gl2.Lat)
		log.Printf("count:%d-%d after 2 Lng:%f", count, i, gl2.Lng)

		time.Sleep(1 * time.Second) // 1秒休む
	}

	gl.ID = gl2.ID
	gl.Lat = 999.9
	gl.Lng = 999.9

	log.Printf("count:%d before 3 ID:%d", count, gl.ID)
	log.Printf("count:%d before 3 Lat:%f", count, gl.Lat)
	log.Printf("count:%d before 3 Lng:%f", count, gl.Lng)

	err = c.WriteJSON(gl)

	err = c.ReadJSON(gl2)
	log.Printf("count:%d after3 ID:%d", count, gl2.ID)
	log.Printf("count:%d after3 Lat:%f", count, gl2.Lat)
	log.Printf("count:%d after3 Lng:%f", count, gl2.Lng)
}

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

PruneMobile というソフトウェアを作っています。

クライアント側のプログラムで、Golang(Go言語)の 並列(通信)処理を始めて本格的に実装したのですが、サーバ側にエラーが乱発しています。

Golangの通信プログラムの仕様は今一つ分かなくて(C言語なら「私がマニュアルだ」と傲慢に言い放ちますが)、試行錯誤の最中です。

"Go"は"C"と、ポインタや配列も違って、勉強し直し状態です。

それはさておき、クライアント側のプログラムは、以下の通り。ポイントは、

go passenger(1)
go passenger(2)
go passenger(3)
go passenger(4)

でスレッド処理をしてみたところです。

// client6.go ペアは server12.go

package main

import (
	"flag"
	"log"
	"net/url"
	"os"

	"github.com/gorilla/websocket"

	"math/rand"
	"time"
)

// GetLoc GetLoc
type GetLoc struct {
	ID  int     `json:"id"`
	Lat float64 `json:"lat"`
	Lng float64 `json:"lng"`
	//Address string  `json:"address"`
}

var gl [100]GetLoc
var gl2 [100]GetLoc
var c [100](*websocket.Conn)

var addr = flag.String("addr", "0.0.0.0:8080", "http service address") // テスト
//var addr = flag.String("addr", "localhost:8080", "http service address") // テスト
var upgrader = websocket.Upgrader{} // use default options

func random(min, max float64) float64 {
	return rand.Float64()*(max-min) + min
}

func main() {
	rand.Seed(time.Now().UnixNano())

	flag.Parse()
	log.SetFlags(0)

	go passenger(1)
	go passenger(2)
	go passenger(3)
	go passenger(4)

	time.Sleep(25 * time.Second)
}

func passenger(i int) {

	/*
		gl[i] = GetLoc{
			ID:  0,
			Lat: 35.653976,
			Lng: 139.796821,
		}
	*/

	u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo2"}
	log.Printf("connecting to %s", u.String())

	var err error
	c[i], _, err = websocket.DefaultDialer.Dial(u.String(), nil)
	//c[i] = cc
	if err != nil {
		log.Fatal("dial:", err)
	}
	defer c[i].Close()

	gl[i].ID = 0
	gl[i].Lat = 35.653976
	gl[i].Lng = 139.796821

	if i != -100 {
		log.Printf("before1 %d ID:%d", i, gl[i].ID)
		log.Printf("before1 %d Lat:%f", i, gl[i].Lat)
		log.Printf("before1 %d Lng:%f", i, gl[i].Lng)
	}

	//err = c.WriteJSON(mt, gl)
	err = c[i].WriteJSON(gl[i])
	if err != nil {
		log.Println("write:", err)
	}

	//&(gl2[i]) = new(GetLoc)
	err = c[i].ReadJSON(&(gl2[i]))

	if i != -100 {
		log.Printf("after1 %d ID:%d", i, gl2[i].ID)
		log.Printf("after1 %d Lat:%f", i, gl2[i].Lat)
		log.Printf("after1 %d Lng:%f", i, gl2[i].Lng)
	}

	if gl2[i].ID == 0 {
		// 強制停止
		os.Exit(1)
	}

	gl[i].ID = gl2[i].ID

	for k := 0; k < 20; k++ {
		gl[i].Lat += random(0.5, -0.5) * 0.00001 * 10 * 5
		gl[i].Lng += random(0.5, -0.5) * 0.00002 * 10 * 5

		if i != -100 {
			log.Printf("before2 %d ID:%d", i, gl[i].ID)
			log.Printf("before2 %d Lat:%f", i, gl[i].Lat)
			log.Printf("before2 %d Lng:%f", i, gl[i].Lng)
		}

		err = c[i].WriteJSON(gl[i])
		if err != nil {
			log.Println("write:", err)
		}
		//gl2[i] := new(GetLoc)
		err = c[i].ReadJSON(&(gl2[i]))
		if i != -100 {
			log.Printf("after2 %d ID:%d", i, gl2[i].ID)
			log.Printf("after2 %d Lat:%f", i, gl2[i].Lat)
			log.Printf("after2 %d Lng:%f", i, gl2[i].Lng)
		}
		time.Sleep(1 * time.Second) // 1秒休む
	}

	gl[i].ID = gl2[i].ID
	gl[i].Lat = 999.9
	gl[i].Lng = 999.9

	log.Printf("before3 %d ID:%d", i, gl[i].ID)
	log.Printf("before3 %d Lat:%f", i, gl[i].Lat)
	log.Printf("before3 %d Lng:%f", i, gl[i].Lng)

	err = c[i].WriteJSON(gl[i])

	err = c[i].ReadJSON(&(gl2[i]))
	log.Printf("after3 %d ID:%d", i, gl2[i].ID)
	log.Printf("after3 %d Lat:%f", i, gl2[i].Lat)
	log.Printf("after3 %d Lng:%f", i, gl2[i].Lng)

}

でもって、サーバ側に、

wsarecv: An existing connection was forcibly closed by the remote host.

がでてくるのですが、これがどうにも分からない。

websocket.DefaultDialer.Dialもスレッド単位で分離しているから、良さそうなんだけどなー

サーバ側の出力コンソールは、こんな感じ

first ID:0
117 after gl = <-chan2_1
first Lat:0.000000
94
first Lng:0.000000
126
read: read tcp 127.0.0.1:8080->127.0.0.1:50368: wsarecv: An existing connection was forcibly closed by the remote host.
138
write: write tcp 127.0.0.1:8080->127.0.0.1:50366: wsasend: An existing connection was forcibly closed by the remote host.
after gl2.id = 3
after gl2.lat = 35.653909
after gl2.lng= 139.796333
145
148
113 before gl = <-chan2_1
86
final ID:3
final Lat:35.653909
final Lng:139.796333
94
write: write tcp 127.0.0.1:8080->127.0.0.1:50365: wsasend: An existing connection was forcibly closed by the remote host

とりあえず、忘れそうなので、自分の為にメモを残しておきます。

websocket.DefaultDialer.Dialもスレッド単位ごとに発行しているから分離しているから、良さそうなんだけどなぁ。Websocketの複数のエンドポイントを作って並列処理につっこむには、もう一頑張り必要、ということでしょう。

ちなみに「誰か、私を助けてくれたっていいんだからね!」と申し上げておきます。

 

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

MarkdownをWord形式(docx)に変換する を参考にして、Pandoc をインストールしましたが、とてもキレイに表示されて便利です。

# PruneMobileとは

複数の人間や自動車等の移動体のリアルタイムの位置情報を、地図上に表示する、PruneClusterのアプリーケーションです。

PruneMobileのサーバに対して、任意のタイミングで位置情報を送り込むだけで、地図上にマーカーが表示されます。

## 使用環境

- golang(Go言語)のインストールされていれば良いです。私(江端智一)の環境では以下のようになっています。
```
$ PruneMobile\server>go version
$ go version go1.14 windows/amd64
```

- 実際に動かせば、コンパイラから、あれこれ言われますので、それに対応して下さい。基本的には、
```
$ go get github.com/gorilla/websocket
```
は必要になると思います。

## サンプルプログラムの環境

- Webブラウザで表示される地図は、東京都江東区の豊洲駅を中心にして作ってあります。
  - PruneMobile\server\serverX.go (Xは数字) の中にある、
```
var map = L.map("map", {
   attributionControl: false,
   zoomControl: false
}).setView(new L.LatLng(35.654543, 139.795534), 18);
```
の"35.654543, 139.795534"の座標を変更すれば、地図の中心位置が変わります。

- クライアントプログラムでは、豊洲駅を中心にランダムウォークさせています
  - PruneMobile\server\clientX.go (Xは数字)を起動すると、10秒間程、マーカーが移動して、その後消滅します。

- クライアントプログラム(clientX.go)は複数同時に起動させることができます。

## 現時点で確認している問題点で、いずれ直すもの

- マーカーの消滅のタイミングが、同時になってしまう

- Webブラウザの表示が、最初の1つめしか、正常に動作しない

- ローカルのjs(javascript)のローディングに失敗した為、江端のプライベートサーバ(kobore.net)からローディングしている。PruneMobile\server\serverX.goの以下を参照
```
	<script src="http://kobore.net/PruneCluster.js"></script>
	<link rel="stylesheet" href="http://kobore.net/examples.css"/>
```

# サンプルプログラムの動作方法

## Step 1 サーバの起動

適当なシェルを立ち上げて
```
$ cd PruneMobile\server
$ go run serverX.go (Xは数字)
```
とすると、「Windowsセキュリティの重要な警告(windows10の場合)」が出てくるので、「アクセスを許可する」ボタンを押下して下さい。

## Step 2 ブラウザの起動
Chromoブラウザ(他のブラウザのことは知らん)から、
```
http://localhost:8080/
```
と入力して下さい。豊洲地区の地図が表示されます。

## Step 3 クライアントの起動
適当なシェルを立ち上げて
```
$ cd PruneMobile\client
$ go run clientX.go (Xは数字)
```
とすると、マーカが0.5秒単位でランダムに動きます。

# クライアントプログラムで使うI/F(データ形式)

## 前提

- サーバとwebsocketのコネクションを確立して下さい。方法は問いません。golangでの記述方法はclient/clientX.goを参考にして下さい。

- データ形式はJSONを用います。golangでの記載サンプルは以下の通りです。

```

// GetLoc GetLoc
type GetLoc struct {
	ID  int     `json:"id"`
	Lat float64 `json:"lat"`
	Lng float64 `json:"lng"`
	//Address string  `json:"address"`
}
```

## Step.1 マーカーの登録

IDを"0"にして、最初のマーカーの座標を入力したJSONを、サーバに送付して下さい。golangでの送信方法はは以下の通りです。
```
	gl := GetLoc{
		ID:  0,
		Lat: 35.653976,
		Lng: 139.796821,
	}

	err = c.WriteJSON(gl)
	if err != nil {
		log.Println("write:", err)
	}
```

返り値に入ってきたIDが、これからそのマーカで使うID番号となります。golangでの受信方法はは以下の通りです。

```
	gl2 := new(GetLoc)
	err = c.ReadJSON(gl2)
	log.Printf("after ID:%d", gl2.ID)
	log.Printf("after Lat:%f", gl2.Lat)
	log.Printf("after Lng:%f", gl2.Lng)
```

以後、このID番号(整数)を使用して下さい。この番号と異なる番号を使用した場合、動作は保証されません。

## Step.2 マーカーの移動

指定されたIDを使って、移動後の座標を送付して下さい。
```
	gl := GetLoc{
		ID:  5,         // IDが"5"の場合
		Lat: 35.653923,
		Lng: 139.796852,
	}

	err = c.WriteJSON(gl)
	if err != nil {
		log.Println("write:", err)
	}
```
返り値は、入力と同じJSONが戻ってきますが、必ず受信して下さい。
```
	gl2 := new(GetLoc)
	err = c.ReadJSON(gl2)
	log.Printf("after ID:%d", gl2.ID)
	log.Printf("after Lat:%f", gl2.Lat)
	log.Printf("after Lng:%f", gl2.Lng)
```

## Step.3 マーカーの抹消

指定されたIDを使って、地球上の緯度経度の数値で現わせない座標を入力して下さい。具体的に、latに90.0より大きな値、またはlngに180より大きな値を入力することで、マーカが消去されます。
```
	gl := GetLoc{
		ID:  5,         // IDが"5"の場合
		Lat: 999.9,
		Lng: 999.9,
	}

	err = c.WriteJSON(gl)
	if err != nil {
		log.Println("write:", err)
	}
```
返り値は、入力と同じJSONが戻ってきますが、必ず受信して下さい。
```
	gl2 := new(GetLoc)
	err = c.ReadJSON(gl2)
	log.Printf("after ID:%d", gl2.ID)
	log.Printf("after Lat:%f", gl2.Lat)
	log.Printf("after Lng:%f", gl2.Lng)
```

以上

 

README.docx

 

 

 

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

取り敢えず、週末の成果(ちゃんと動くものではないので、そのまま使うことはお勧めしません)

"client1.go"の内容

package main

import (
	"flag"
	"log"
	"net/url"

	"github.com/gorilla/websocket"
)

// GetLoc GetLoc
type GetLoc struct {
	ID  int     `json:"id"`
	Lat float64 `json:"lat"`
	Lng float64 `json:"lng"`
	//Address string  `json:"address"`
}

var addr = flag.String("addr", "0.0.0.0:8080", "http service address") // テスト
//var addr = flag.String("addr", "localhost:8080", "http service address") // テスト
var upgrader = websocket.Upgrader{} // use default options

func main() {
	flag.Parse()
	log.SetFlags(0)
	u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo2"}
	log.Printf("connecting to %s", u.String())

	c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
	if err != nil {
		log.Fatal("dial:", err)
	}
	defer c.Close()

	gl := GetLoc{
		ID:  1,
		Lat: 35.653976,
		Lng: 139.796841,
	}

	log.Printf("ID:%d", gl.ID)
	log.Printf("Lat:%f", gl.Lat)
	log.Printf("Lng:%f", gl.Lng)
	//err = c.WriteJSON(mt, gl)
	err = c.WriteJSON(gl)
	if err != nil {
		log.Println("write:", err)
	}
}

"server8.go"の内容

/*
// server7.go

// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build ignore

// 使い方
// go run server7.go      (適当なシェルから)
// http://localhost:8080  (ブラウザ起動)
*/

package main

import (
	"flag"
	"html/template"
	"log"
	"net/http"

	"github.com/gorilla/websocket"
)

// GetLoc GetLoc
type GetLoc struct {
	ID  int     `json:"id"`
	Lat float64 `json:"lat"`
	Lng float64 `json:"lng"`
	//Address string  `json:"address"`
}

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

var upgrader = websocket.Upgrader{} // use default options

func echo2(w http.ResponseWriter, r *http.Request) {
	c, err := upgrader.Upgrade(w, r, nil) // cはサーバのコネクション
	if err != nil {
		log.Print("upgrade:", err)
		return
	}
	defer c.Close()

	for {
		//mt, message, err := c.ReadMessage() // クライアントからのメッセージの受信(mtはクライアント識別子)
		//_, _, err := c.ReadMessage() // クライアントからのメッセージの受信(mtはクライアント識別子)

		gl := new(GetLoc)

		err := c.ReadJSON(&gl) // クライアントからのメッセージの受信(mtはクライアント識別子)

		log.Printf("ID:%d", gl.ID)
		log.Printf("Lat:%f", gl.Lat)
		log.Printf("Lng:%f", gl.Lng)

		if err != nil {
			log.Println("read:", err)
			break
		}
	}
}

func echo(w http.ResponseWriter, r *http.Request) {
	c, err := upgrader.Upgrade(w, r, nil) // cはサーバのコネクション
	if err != nil {
		log.Print("upgrade:", err)
		return
	}
	defer c.Close()

	/*
		for {
			//mt, message, err := c.ReadMessage() // クライアントからのメッセージの受信(mtはクライアント識別子)
			_, _, err := c.ReadMessage() // クライアントからのメッセージの受信(mtはクライアント識別子)
			if err != nil {
				log.Println("read:", err)
				break
			}

			// JSON(位置情報)を無理やり入れてみた

			gl := GetLoc{
				ID:  1,
				Lat: 35.653976,
				Lng: 139.796842,
			}

			//log.Printf("recv_serv: %s", gl)
			//err = c.WriteJSON(mt, gl)
			err = c.WriteJSON(gl)
			if err != nil {
				log.Println("write:", err)
				break
			}
		}
	*/

	gl := GetLoc{
		ID:  1,
		Lat: 35.653976,
		Lng: 139.796842,
	}

	//log.Printf("recv_serv: %s", gl)
	//err = c.WriteJSON(mt, gl)
	err = c.WriteJSON(gl)
	if err != nil {
		log.Println("write:", err)
	}

}

func home(w http.ResponseWriter, r *http.Request) {
	homeTemplate.Execute(w, "ws://"+r.Host+"/echo")
}

func main() {
	flag.Parse()
	log.SetFlags(0)
	http.HandleFunc("/echo2", echo2)           // echo関数を登録 (サーバとして必要)
	http.HandleFunc("/echo", echo)             // echo関数を登録 (サーバとして必要)
	http.HandleFunc("/", home)                 // home関数を登録
	log.Fatal(http.ListenAndServe(*addr, nil)) // localhost:8080で起動をセット
}

var homeTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>PruneCluster - Realworld 50k</title>

	<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.0-beta.2.rc.2/leaflet.css"/>
	<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.0-beta.2.rc.2/leaflet.js"></script>

	<script src="http://kobore.net/PruneCluster.js"></script>           <!-- これ、いずれローカルホストから取れるように換える -->
	<link rel="stylesheet" href="http://kobore.net/examples.css"/>      <!-- これも、いずれローカルホストから取れるように換える -->

	<!-- goのテンプレートのローカルって、どこになるんだろう? -->

</head>
<body>
<div id="map"></div>

<script>

	ws = new WebSocket("{{.}}"); // websocketの確立

	/*
	var print = function(message) {
		var d = document.createElement("div");
		d.textContent = message;
		output.appendChild(d);
	};
	*/

	//引数にはミリ秒を指定。(例:5秒の場合は5000)
	function sleep(a){
  		var dt1 = new Date().getTime();
  		var dt2 = new Date().getTime();
  		while (dt2 < dt1 + a){
			dt2 = new Date().getTime();
		}
  		return;
	}

    var map = L.map("map", {
        attributionControl: false,
        zoomControl: false
    }).setView(new L.LatLng(35.654543, 139.795534), 18);

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

    var leafletView = new PruneClusterForLeaflet(1,1);  // (120,20)がデフォルト

	/*
	// 送信5発(3秒単位)を送信
	ws.onopen = function (event) {
		for (let i = 0; i < 1; i++){
			ws.send("Ebata is great"); 
			//print("send: Ebata is great");
	
			//print("Start sleep");
			sleep(1000);
			//print("End sleep");
		}
	}
	*/

	var markers = [];

	// 受信すると、勝手にここに飛んでくる
	ws.onmessage = function (event) {
		// データをJSON形式に変更
		var obj = JSON.parse(event.data);

		// データをマーカーとして登録
		var marker = new PruneCluster.Marker(obj.lat, obj.lng);
		markers.push(marker);
		leafletView.RegisterMarker(marker);

        // leafletView.ProcessView();  // 変更が行われたときに呼び出されなければなりません。		
	}


	/*
	// 100人分を登録する
    var size = 1;
    var markers = [];
    for (var i = 0; i < size; ++i) {
        var marker = new PruneCluster.Marker(35.654543 + (Math.random() - 0.5) * 0.00001 * size, 139.795534 + (Math.random() - 0.5) * 0.00002 * size);

        markers.push(marker);
		leafletView.RegisterMarker(marker);
		
	}
	*/
	

	// ランダムウォークさせる
    window.setInterval(function () {
        for (i = 0; i < 1; ++i) {
			//var coef = i < size / 8 ? 10 : 1;
			var coef = 10;
            var ll = markers[i].position;
            ll.lat += (Math.random() - 0.5) * 0.00001 * coef;
            ll.lng += (Math.random() - 0.5) * 0.00002 * coef;
        }

        leafletView.ProcessView();  // 変更が行われたときに呼び出されれなければならない
	}, 500);


	// サーバを止めると、ここに飛んでくる
	ws.onclose = function(event) {
		//print("CLOSE");
		ws = null;
	}


    map.addLayer(leafletView);
</script>



</body>
</html>
`))

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

ようやく動いた。疲れた。JSONを送り込むのは、明日以降ね。

忘備録

  • カレントディレクトリの中にdistというディレクトリを作って、LeafletStyleSheet.css PruneCluster.d.ts PruneCluster.js を放り込んだ
  • カレントディレクトリの中にLeafletStyleSheet.css,PruneCluster.d.ts,PruneCluster.js,examples.cssを放り込んでおいた
  • 上記はあまり意味なかったらしい(プログラムの下を御参照)。

 

使い方は、

>go run server5.go

http://localhost:8080

で起動する。

/*
// server5.go

// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build ignore

// 使い方
// go run server5.go      (適当なシェルから)
// http://localhost:8080  (ブラウザ起動)
*/

package main

import (
	"flag"
	"html/template"
	"log"
	"net/http"

	"github.com/gorilla/websocket"
)

// GetLoc GetLoc
type GetLoc struct {
	ID  int     `json:"id"`
	Lat float64 `json:"lat"`
	Lng float64 `json:"lng"`
	//Address string  `json:"address"`
}

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

var upgrader = websocket.Upgrader{} // use default options

func echo(w http.ResponseWriter, r *http.Request) {
	c, err := upgrader.Upgrade(w, r, nil) // cはサーバのコネクション
	if err != nil {
		log.Print("upgrade:", err)
		return
	}
	defer c.Close()
	for {
		//mt, message, err := c.ReadMessage() // クライアントからのメッセージの受信(mtはクライアント識別子)
		_, _, err := c.ReadMessage() // クライアントからのメッセージの受信(mtはクライアント識別子)
		if err != nil {
			log.Println("read:", err)
			break
		}

		// JSONを無理やり入れてみた

		gl := GetLoc{
			ID:  1,
			Lat: 35.653976,
			Lng: 139.796842,
		}

		//log.Printf("recv_serv: %s", gl)
		//err = c.WriteJSON(mt, gl)
		err = c.WriteJSON(gl)
		if err != nil {
			log.Println("write:", err)
			break
		}

		/*
			log.Printf("recv_serv: %s", message) // 受信したメッセージの表示
			err = c.WriteMessage(mt, message)    // 受信したメッセージの返送
			if err != nil {
				log.Println("write:", err)
				break
			}
		*/
	}
}

func home(w http.ResponseWriter, r *http.Request) {
	//homeTemplate.Execute(w, "ws://"+r.Host+"/echo")
	homeTemplate.Execute(w, "")
}

func main() {
	flag.Parse()
	log.SetFlags(0)
	http.HandleFunc("/echo", echo)             // echo関数を登録 (サーバとして必要)
	http.HandleFunc("/", home)                 // home関数を登録
	log.Fatal(http.ListenAndServe(*addr, nil)) // localhost:8080で起動をセット
}

var homeTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>PruneCluster - Realworld 50k</title>

	<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.0-beta.2.rc.2/leaflet.css"/>
	<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.0-beta.2.rc.2/leaflet.js"></script>

	<script src="PruneCluster.js"></script>

    <link rel="stylesheet" href="examples.css"/>
</head>
<body>
<div id="map"></div>

<script>
    var map = L.map("map", {
        attributionControl: false,
        zoomControl: false
    }).setView(new L.LatLng(35.654543, 139.795534), 18);

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

    var leafletView = new PruneClusterForLeaflet(1,1);  // (120,20)がデフォルト

    var size = 100;
    var markers = [];
    for (var i = 0; i < size; ++i) {
        var marker = new PruneCluster.Marker(35.654543 + (Math.random() - 0.5) * 0.00001 * size, 139.795534 + (Math.random() - 0.5) * 0.00002 * size);

        markers.push(marker);
        leafletView.RegisterMarker(marker);
    }

    window.setInterval(function () {
        for (i = 0; i < size / 2; ++i) {
            var coef = i < size / 8 ? 10 : 1;
            var ll = markers[i].position;
            ll.lat += (Math.random() - 0.5) * 0.00001 * coef;
            ll.lng += (Math.random() - 0.5) * 0.00002 * coef;
        }

        leafletView.ProcessView();
    }, 500);

    map.addLayer(leafletView);
</script>
</body>
</html>
`))

ところが、今朝、PC断ち上げて再起動させてみたところ、昨日の状況に戻ってしまいました。

試しに、自分のサーバ(kobore.net)に、PruneCluster.jsexamples.cssをアップして、さらにdistというディレクトリ掘って、LeafletStyleSheet.cssを放りこんでみたら動きました。

<script src="PruneCluster.js"></script>
<script src="http://kobore.net/PruneCluster.js"></script>

<link rel="stylesheet" href="examples.css"/>
→ <link rel="stylesheet" href="http://kobore.net/examples.css"/>

まあ、いずれ修正するとして、動くならなんでも良いので、このまま進めます。

 

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

なんで、JSON.parse()で、パースができないのだろう? と3時間くらい悩んだあげく、大文字を小文字変換されていることに、ようやく気がつきました。

ひどくない?

気がつかなかった私が悪いの?

/*
// server4.go

// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build ignore

// 使い方
// go run server2.go      (適当なシェルから)
// http://localhost:8080  (ブラウザ起動)
*/

package main

import (
	"flag"
	"html/template"
	"log"
	"net/http"

	"github.com/gorilla/websocket"
)

// GetLoc GetLoc
type GetLoc struct {
	ID  int     `json:"id"`
	Lat float64 `json:"lat"`
	Lng float64 `json:"lng"`
	//Address string  `json:"address"`
}

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

var upgrader = websocket.Upgrader{} // use default options

func echo(w http.ResponseWriter, r *http.Request) {
	c, err := upgrader.Upgrade(w, r, nil) // cはサーバのコネクション
	if err != nil {
		log.Print("upgrade:", err)
		return
	}
	defer c.Close()
	for {
		//mt, message, err := c.ReadMessage() // クライアントからのメッセージの受信(mtはクライアント識別子)
		_, _, err := c.ReadMessage() // クライアントからのメッセージの受信(mtはクライアント識別子)
		if err != nil {
			log.Println("read:", err)
			break
		}

		// JSONを無理やり入れてみた

		gl := GetLoc{
			ID:  1,
			Lat: 35.653976,
			Lng: 139.796842,
		}

		//log.Printf("recv_serv: %s", gl)
		//err = c.WriteJSON(mt, gl)
		err = c.WriteJSON(gl)
		if err != nil {
			log.Println("write:", err)
			break
		}

		/*
			log.Printf("recv_serv: %s", message) // 受信したメッセージの表示
			err = c.WriteMessage(mt, message)    // 受信したメッセージの返送
			if err != nil {
				log.Println("write:", err)
				break
			}
		*/
	}
}

func home(w http.ResponseWriter, r *http.Request) {
	homeTemplate.Execute(w, "ws://"+r.Host+"/echo")
}

func main() {
	flag.Parse()
	log.SetFlags(0)
	http.HandleFunc("/echo", echo)             // echo関数を登録 (サーバとして必要)
	http.HandleFunc("/", home)                 // home関数を登録
	log.Fatal(http.ListenAndServe(*addr, nil)) // localhost:8080で起動をセット
}

var homeTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script>  

var print = function(message) {
    var d = document.createElement("div");
    d.textContent = message;
    output.appendChild(d);
};


//引数にはミリ秒を指定します。(例:5秒の場合は5000)
function sleep(a){
  	var dt1 = new Date().getTime();
  	var dt2 = new Date().getTime();
  	while (dt2 < dt1 + a){
		dt2 = new Date().getTime();
	}
  	return;
}


ws = new WebSocket("{{.}}");

// 送信5発(3秒単位)を送信
ws.onopen = function (event) {
	for (let i = 0; i < 5; i++){
		ws.send("Ebata is great"); 
		print("send: Ebata is great");
	
		print("Start sleep");
		sleep(1000);
		print("End sleep");
	}
}

// 受信すると、勝手にここに飛んでくる
ws.onmessage = function (event) {
   	//console.log(event.data);
	print("RESPONSE: " + event.data);
	var obj = JSON.parse(event.data);

	// test code  (JSON.parse()が大文字を無視することを確認するテスト)
	//const json = '{"ID":1, "Lat":35.653976, "Lng":139.796842}';
	//print("json:" + json);
	//var obj = JSON.parse(json);	

	print("obj:" + obj);  	
	print("ID:" + obj.id);     // obj.IDなら読み取れない
	print("Lat:" + obj.lat);   // obj.Latなら読み取れない
	print("Lng:" + obj.lng);   // obj.Lngなら読み取れない
}

// サーバを止めると、ここに飛んでくる
ws.onclose = function(event) {
    print("CLOSE");
    ws = null;
}

</script>
</head>
<body>

<div id="output"></div>

</body>

</html>
`))