20200830のNode.jsに関する記事は3件です。

httpとwebsocketの兼用(統合)サーバーを作る

一例です。

前準備

前提・環境

  • node.js: v10.16.0(async/awaitをサポートしている程度のバージョン)
  • npm: v6.14.2(上のnode.jsとマッチするバージョンであればOK)
  • windows 10 64bit

※ async/awaitは簡易化のために使用してるだけなので、必須ではないです。

ディレクトリ構造

[ルートフォルダ]
|- public
  |- index.html
|- utils
  |- getLocalIp.js
|- server.js
|- …その他package.json等

依存モジュールのインストール

npm i node-static ws

サーバー側

/server.js
const { createServer } = require("http");
const { Server: FileServer } = require("node-static");
const { Server: WebSocketServer } = require("ws");
const getLocalIp = require("./utils/getLocalIp");

const PORT_NUM = 8080;

(async () => {
  // ローカルipアドレス取得
  const host = await getLocalIp().catch((err) => {
    throw err;
  });

  // httpサーバー立ち上げ
  const fileServer = new FileServer(`${__dirname}/public`);
  const server = createServer((request, response) => {
    request
      .addListener("end", function () {
        fileServer.serve(request, response);
      })
      .resume();
  });

  // websocketサーバー立ち上げ(兼用)
  const wsServer = new WebSocketServer({
    server: server,
  });

  // クライアントとの接続確立後
  wsServer.on("connection", (ws) => {
    ws.on("message", (message) => {
      console.log("Received: " + message);

      if (message === "hello") {
        ws.send("hello from server!");
      }
    });
  });

  // サーバーリッスン
  server.listen(PORT_NUM, host);
  server.on("listening", () => {
    // 準備完了でコンソールにURL表示
    const addressInfo = server.address();
    console.log(`http://${addressInfo.address}:${addressInfo.port}`);
  });
})();

/utils/getLocalIp.js
/**
 * ローカルipアドレスを取得。非同期
 * @see https://stackoverflow.com/a/9542157
 */
module.exports = function () {
  return new Promise((resolve, reject) => {
    require("dns").lookup(require("os").hostname(), (
      error,
      address
    )=> {
      if (error) {
        reject(error);
        return;
      }
      resolve(address);
    });
  });
};

クライアント側

/public/index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Websocket sample</title>
  </head>

  <h1>Websocket sample</h1>
  <body>
    <script>
      // URLがそのままwsとのコネクション用アドレスになる
      const { host, pathname, protocol } = window.location;
      const wsProtocol = protocol === "http:" ? "ws://" : "wss://";
      const address = wsProtocol + host + pathname;
      const socket = new window.WebSocket(address);

      // サーバーからのデータ
      socket.onmessage = (res)=> {
        if (typeof res.data === 'string') {
          alert(`Message from server: ${res.data}`)
        }
      };

      // コネクション確立後
      socket.onopen = () => {
        // メッセージ送ってみる
        socket.send('hello')
      }
    </script>
  </body>
</html>

サーバー起動

node server.jsで起動して、ターミナルに表示されるURLにアクセスするとalertでメッセージが表示される…はず。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

IMI住所変換コンポーネントを改造してリバースジオコーディングに対応してみた

先日、経産省が公開する「住所変換コンポーネント」についての性能を調べた記事を投稿しました。

IMI住所変換コンポーネントでいろんな住所を正規化してみた
https://qiita.com/uedayou/items/4c9d30fc031a9bf6762e

「IMI住所変換コンポーネント」は、表記にゆれがある住所表記を正規化してくれるとても便利なツールです。

たとえば

霞が関2 -> 東京都千代田区霞が関二丁目

のようになります。

このツールのもう一つの大きな特徴として、その住所に対する位置情報(緯度経度)も取得することができる点があります。

霞が関2 -> 東京都千代田区霞が関二丁目 -> {"緯度": "35.675551", "経度": "139.750413"}

所謂「ジオコーディング機能」があります。
これは、ツール内にあらかじめ住所に対する位置情報データが含まれることを意味し、作り方によってはこの逆、位置情報(緯度経度)から住所にも変換することができるのではないか?と思いました。

{"緯度": "35.675551", "経度": "139.750413"} -> 東京都千代田区霞が関二丁目

これを一般的にリバースジオコーディング(逆ジオコーディング)と言いますが、本家コードを改造してこのリバースジオコーディング機能をつけてみました。

使い方

ツールを使うにはまず、Node.jsとリバースジオコーディング対応住所変換コンポーネントモジュールのインストールが必要です。経産省が公開するものとは違うものをインストールします。
ファイルは

https://github.com/uedayou/imi-enrichment-address-plus/releases/download/v1.0.1/imi-enrichment-address-plus-1.0.1.tgz

にあります。このツールには3つの使い方があります。

  • (1) コマンドラインインターフェイス
  • (2) Web API
  • (3) Node.js

(1) は、npm でグローバルにインストール

npm install -g https://github.com/uedayou/imi-enrichment-address-plus/releases/download/v1.0.1/imi-enrichment-address-plus-1.0.1.tgz

(2), (3) はローカルインストールしてください。

npm install https://github.com/uedayou/imi-enrichment-address-plus/releases/download/v1.0.1/imi-enrichment-address-plus-1.0.1.tgz

(1) コマンドラインインターフェイス

リバースジオコーディングをコマンドラインで利用する場合は

$ imi-enrichment-address-plus --lat [緯度] --lng [経度]
$ imi-enrichment-address-plus --lat 35.675551 --lng 139.750413
出力
{
  "@context": "https://imi.go.jp/ns/core/context.jsonld",
  "@type": "場所型",
  "住所": {
    "@type": "住所型",
    "表記": "東京都千代田区霞が関二丁目",
    "都道府県": "東京都",
    "都道府県コード": "http://data.e-stat.go.jp/lod/sac/C13000",
    "市区町村": "千代田区",
    "市区町村コード": "http://data.e-stat.go.jp/lod/sac/C13101",
    "町名": "霞が関",
    "丁目": "2"
  },
  "地理座標": {
    "@type": "座標型",
    "緯度": "35.675551",
    "経度": "139.750413"
  }
}

(2) Web API

ツール内にWebサーバ機能があり、サーバ経由でWeb APIとして利用することができます。

$ node node_modules/imi-enrichment-address-plus/bin/server.js 8080

上記を実行して http://localhost:8080/ をブラウザで開くと以下のようなWebページが開きます。

Webページ

ここの緯度経度の部分の変換ボタンを押すと、実行結果には以下のように表示されます。

実行結果

プログラム上から使う場合は、以下のJSONをPOSTしてください。

{
  "@type": "座標型",
  "緯度": "[緯度の値]",
  "経度": "[経度の値]"
}
cURL
$ curl -X POST -H 'Content-Type: application/json' -d '{"@type": "座標型","緯度": "35.170915","経度": "136.881537"}' localhost:8080
出力
{
  "@context": "https://imi.go.jp/ns/core/context.jsonld",
  "@type": "場所型",
  "住所": {
    "@type": "住所型",
    "表記": "愛知県名古屋市中村区名駅一丁目",
    "都道府県": "愛知県",
    "都道府県コード": "http://data.e-stat.go.jp/lod/sac/C23000",
    "市区町村": "名古屋市",
    "区": "中村区",
    "市区町村コード": "http://data.e-stat.go.jp/lod/sac/C23105",
    "町名": "名駅",
    "丁目": "1"
  },
  "地理座標": {
    "@type": "座標型",
    "緯度": "35.170778",
    "経度": "136.882494"
  }
}

(3) Node.js

ツールはNode.jsのモジュールなので、Node.js のコード上からも利用可能です。
リバースジオコーディングは以下のように利用できます。

const convert = require('imi-enrichment-address-plus');
const point = {
  lat: 35.675551,
  lng: 139.750413
}
convert(point).then(json=>{
  console.log(json);
});

利用についての注意

本ツールのリバースジオコーディングは、半径1km内から最も近い代表点を持つ住所データを1件返します。与える緯度経度によっては必ずしもその位置の住所を表さない可能性がありますので、くれぐれも正確なデータではなくある程度あいまいなデータであることを留意して使用してください。

ただ、間違っていたとしてもかなり近い住所であることがほとんどだと思います。ざっくりとした住所でも構わない場合も結構あると思いますが、そういう場合はかなり便利だと思います。

ソースコード

ソースコードは以下のリポジトリで公開しています。
詳しい使い方は、README.mdを参照してください。

https://github.com/uedayou/imi-enrichment-address-plus

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

80mm フィルム (58mm フィルム) を発明してみた

レシート用のサーマルロール紙 (幅 80 mm or 58 mm) を映画のフィルムにしちゃいます!

01.jpg

receiptline シリーズ最終回では、インスタントカメラを作って、最後にレシートプリンターを自撮り連写しました。
この写真を眺めていたら、動画もできるんじゃないか?と思ったのです。

02.jpg

自動用紙カットを解除

receiptline は、印刷が終わると用紙を自動でカットしてくれます。
映画のフィルムにするには、この自動用紙カットを解除しなくてはなりません。
変換ライブラリ lib/receiptline.js のソースコードを調べます。

lib/receiptline.js
//
// ESC/POS
//
const _escpos = {
    // start printing: ESC @ GS a n ESC M n FS ( A pL pH fn m ESC SP n FS S n1 n2 ESC 3 n ESC { n
    open: printer => '\x1b@\x1da\x00\x1bM0\x1c(A' + $(2, 0, 48, 0) + '\x1b \x00\x1cS\x00\x00\x1b3\x00\x1b{' + $(printer.upsideDown),
    // finish printing: GS r n
    close: function () {
        return this.cut() + '\x1dr1';
    },

見つけました。close メソッドの this.cut() + を削除します。

lib/receiptline.js
//
// ESC/POS
//
const _escpos = {
    // start printing: ESC @ GS a n ESC M n FS ( A pL pH fn m ESC SP n FS S n1 n2 ESC 3 n ESC { n
    open: printer => '\x1b@\x1da\x00\x1bM0\x1c(A' + $(2, 0, 48, 0) + '\x1b \x00\x1cS\x00\x00\x1b3\x00\x1b{' + $(printer.upsideDown),
    // finish printing: GS r n
    close: function () {
        // return this.cut() + '\x1dr1';
        return '\x1dr1';
    },

インスタントカメラを改造

インスタントカメラのコードと実行環境を流用します。
映像クリックで撮影開始/終了するように変更しました。

wwwroot/index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>80mm Film Camera</title>
    <script type="text/javascript">
        async function initialize() {
            const video = document.querySelector('video');
            const canvas = document.querySelector('canvas');
            let timer = 0;
            // video
            video.srcObject = await navigator.mediaDevices.getUserMedia({ audio: false, video: { width: 1280, height: 720 } });
            video.onclick = event => {
                if (timer > 0) {
                    // stop
                    clearInterval(timer);
                    timer = 0;
                } else {
                    // start
                    timer = setInterval(() => {
                        // image
                        canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
                        let data = `{i:${canvas.toDataURL('image/png').slice(22)}}\n`;
                        // barcode
                        const now = new Date();
                        const iso = new Date(now.getTime() - now.getTimezoneOffset() * 60000).toISOString();
                        data += `{c:${iso.replace(/\D/g, '').slice(2, 14)};o:ean,24}|`;
                        // send data
                        const xhr = new XMLHttpRequest();
                        xhr.open('POST', 'printer');
                        xhr.setRequestHeader('Content-Type', 'text/plain; charset=utf-8');
                        xhr.send(data);
                    }, 2000);
                }
            };
        }
    </script>
</head>
<body onload="initialize()">
    <video autoplay style="width: 100%;"></video>
    <canvas width="512" height="288" style="display: none;"></canvas>
</body>
</html>
  • レシートプリンター
    • TM-T88V (80mm)
  • 記録メディア
    • 80 ミリフィルム (80mm 幅のサーマルロール紙)
  • アスペクト比
    • 16 : 9
  • 画素数
    • 512 x 288 ピクセル
  • フレームレート
    • 0.5 fps (2 秒間隔で印刷)
  • 音声
    • なし

ストリームデータ処理にすれば、もっとフレームレートを上げられると思います。

80 ミリカメラ

80 ミリカメラのハードウェアです。家にあるものを組み合わせました。

03.jpg

  • ノートパソコン (カメラデバイス)
  • レシートプリンター
  • AC アダプタ
  • LAN ケーブル
  • ポータブル電源
  • ベビーキャリア

レシートプリンターが据え置き型なので重装備になってしまいました。
撮影しているとフィルムがはみ出してくるので、巻き取り装置がほしいです。

80 ミリ映写機

80 ミリ映写機は、まだこれから。そこで・・・

撮影済みのフィルムをイメージスキャナで取り込んで GIF アニメーションを作りました。
倍速でお届けします!

04.gif

05.gif

06.gif

いかがでしょうか?
また何か作ったら投稿します。ではまた!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む