ナチュラル @rch850

ナチュラル丼発祥の地、福井からお届けします。技術的な話題とか、雑談とか。デザイン変えました @ 2019/03/13

もう競プロで Go 書くときは fmt.Scan しない

今年度に入ってから、周りの人達の影響もあって AtCoder をちまちまと始めました。4回のレーティングを経て茶色です。自分の色弱の傾向だと、茶色と緑を見分けるのがかなり難しいので、早く水色になりたいです。

atcoder.jp

言語は勉強も兼ねてなるべく Go にしてます。ソートしたり文字列処理したり、普通に Golang 書いてるときとは違う知識が要るので、戸惑うことも多かったですが、少しずつ慣れてきてます。特に AtCoder は Go 1.6 なので、ソートがなかなかハードル高かったです。

昨晩は少し時間があったので Codeforces にも参加してみました (Codeforces Round #563)。Problem B がほぼソートするだけだったので、サクッと書いて submit したところ、なんと Time limit exceeded。 sort.Ints でのソートが遅い?なんで??ってことで色々調べたのですが分からず、結局 C++ で書いて通しました。Wrong answer 2回も出してるのは気にしないでください。

f:id:rch850:20190605000114p:plain

これ以上 Golang で競プロ続けるのは厳しいのかなーとがっかりしてたのですが、ソートじゃなくて入出力が遅かったのでは??と冷静に考え始めました。

本題のソートをせずに問題文(N=100000)を読み込むだけのコードで試したところ、手元の環境で 1.5 秒程度かかってしまいました。よかった、ソートは遅くなかったんだ。

それで何かいい感じに入出力してくれる方法はないかと調べたところ、この記事に行き当たりました。

or3.hatenablog.com

ここの nextInt() をありがたく使わせてもらったところ、無事に 0.3 秒程度で解くことができました。これでこれからも Go で競プロ続けられます。

AtCoder にあと1回参加すれば参加回数が5回となって「参加回数が5回に満たないため、推定される実力よりも大幅に低いレーティングとなっています」から脱することができるのですが、週末の ABC 129 は残念ながら Aqours 5th LoveLive! 〜Next SPARKLING!!〜と被ってるので参加できません。また次の機会に参加したいです。

Leaflet で福井県の年度別市町村人口を出してみた

前回の記事

rch850.hatenablog.com

白地図を出すだけでは芸がないので、福井県の年度別市町村人口を表示してみました。表示するときは、少しデータの取得に時間がかかります。10秒も待てば終わると思います。

だいたいこんな感じです。

f:id:rch850:20190530005738p:plain

市区町村の人口を出すにあたってやったことは、大きく分けて2つです。

  • 人口に応じて市区町村の色を変える
  • 年度を切り替えられるようにする

人口データは福井県のオープンデータ「市町別・年齢別・男女別人口 」を使っています。

人口に応じて市区町村の色を変える

L.getJSON の第2引数に style 関数を渡すことで、何かの数字に応じて色を変えることができます。この関数が人口に応じた色の設定を返します。ポイントは fillColorpopulation に応じて変えているところです。

引数などの細かい条件はドキュメントに詳しく書いてあります。

let populations = []
function style(feature) {
  const mname = mcodeToNameMap[feature.properties.municipality_code];
  const population = populations.reduce((p, row) => {
    if (row[2] === mname) p += row[4]
      return p
  }, 0)
  console.log(`${mname} => ${population}`)
  return {
    weight: 2,
    dashArray: '3',
    fillColor: population > 100000 ? '#800026' :
               population > 80000  ? '#BD0026' :
               population > 60000  ? '#E31A1C' :
               population > 40000  ? '#FC4E2A' :
               population > 30000  ? '#FD8D3C' :
               population > 20000   ? '#FEB24C' :
               population > 10000   ? '#FED976' :
               '#FFEDA0'
  }
}
const g = L.geoJSON(geojson, { style }).addTo(mymap);

年度を切り替えられるようにする

人口データは2010年から2015年にかけて6年分のデータが入っているので、せっかくだから年度を切り替えて表示できるようにしました。

const yearEl = document.querySelector('#year')
rxjs.fromEvent(yearEl, 'change')
  .subscribe(() => { updateYear(Number(yearEl.value)) })

function updateYear(year) {
  google.script.run.withSuccessHandler((p) => {
    populations = p
    console.log(populations);
    g.eachLayer(l => g.resetStyle(l))
  }).getPopulations(year);
}

range input を動かしたら updateYear を呼び出して populations を更新し、地図の見た目を更新しています。地図の見た目は g.eachLayer(l => g.resetStyle(l)) で更新することができます。これで style 関数が再評価されるようです。イベント処理に RxJS を使っていますが深い意味はないです。

ここの実装については公式サンプルの Interactive Choropleth Map が参考になりました。

まとめ

これらをまとめた一連の実装です。全市区町村の geojson から、ある都道府県の市区町村だけを抽出するコードとか、市区町村の名前とコード (MstMunicipality.code) を対応付けるコードとか、本質でないコードもあるので、少し長めです。

(function () {
  let mymap = L.map('mapid').setView([35.8, 136.5], 8);
  // これらの cache-control ヘッダーでキャッシュが禁止されている。
  Promise.all([
    // 市区町村の形状データ
    fetch('https://www.itdashboard.go.jp/js/data/municipality.geojson').then(resp => resp.json()),
    // 都道府県と市区町村の対応付データ
    fetch('https://www.itdashboard.go.jp/Api/getData.json?dataset=MstMunicipality').then(resp =>resp.json())
  ]).then(([geojson, municipalities]) => {
    return extractPrefecture(geojson, municipalities, '18')
  }).then(([geojson, municipalities]) => {
    const mcodeToNameMap = municipalities.reduce((map, value) => {
      map[value.MstMunicipality.code] = value.MstMunicipality.name;
      return map;
    }, {})
    let populations = []
    function style(feature) {
      const mname = mcodeToNameMap[feature.properties.municipality_code];
      const population = populations.reduce((p, row) => {
        if (row[2] === mname) p += row[4]
          return p
      }, 0)
      console.log(`${mname} => ${population}`)
      return {
        weight: 2,
        dashArray: '3',
        fillColor: population > 100000 ? '#800026' :
            population > 80000  ? '#BD0026' :
            population > 60000  ? '#E31A1C' :
            population > 40000  ? '#FC4E2A' :
            population > 30000  ? '#FD8D3C' :
            population > 20000   ? '#FEB24C' :
            population > 10000   ? '#FED976' :
                        '#FFEDA0'
      }
    }
    const g = L.geoJSON(geojson, { style }).addTo(mymap);
    updateYear(2015);
    const yearEl = document.querySelector('#year')
    rxjs.fromEvent(yearEl, 'change')
      .subscribe(() => { updateYear(Number(yearEl.value)) })
    function updateYear(year) {
      google.script.run.withSuccessHandler((p) => {
        populations = p
        console.log(populations);
        g.eachLayer(l => g.resetStyle(l))
      }).getPopulations(year);
    }
  })
  
  function extractPrefecture(geojson, municipalities, prefectureCode) {
    // MstMunicipality: {
    //   code: string,
    //   name: string,
    //   re_code: string,
    //   re_name: string
    // }
    const extractedMunicipalities = municipalities.raw_data.filter(d => d.MstMunicipality.re_code ===prefectureCode)
    const mcodes = extractedMunicipalities.map(d => Number(d.MstMunicipality.code))
    const extractedGeojson = {
      ...geojson,
      features: geojson.features.filter(feature => mcodes.indexOf(feature.properties.municipality_code)!== -1)
    }
    return [extractedGeojson, extractedMunicipalities]
  }
})()

(6/1 追記)

これは Google Apps Script (GAS) でホスティングしている HTML で実装しました。途中で唐突に google.script.run が出てくるのはそのためです。福井県の年度別市町村人口をシートに転記して、それを GAS で作った getPopulations 関数経由で読み込んでいます。

GAS 側のコードはこれだけです。

function doGet() {
  return HtmlService.createHtmlOutputFromFile('map')
}

function getPopulations(year) {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet()
  var range = sheet.getRange(2, 1, sheet.getMaxRows() - 1, 7);
  var rows = range.getValues();
  var results = [];
  for (var i = 0; i < rows.length; i++) {
    if (rows[i][0] != String(year)) continue;
    results.push(rows[i]);
  }
  return results;
}

Leaflet で都道府県の白地図

Leaflet って何?白地図が書けるってほんと?白地図のデータはどこから手に入れるの?ライセンスは?調べてみました!

Leaflet って何?

Leaflet はモバイルフレンドリーなインタラクティブマップのための JavaScript ライブラリです。

白地図が書けるってほんと?

Leaflet では GeoJSON 形式のデータを描画できます。つまり GeoJSON 形式の白地図さえ入手できれば白地図を書くことができます。

ITダッシュボードで表示されている地図は Leaflet で描画されていて、通常の地図画像のレイヤーに白地図のレイヤーが重なっています。

白地図のデータはどこから手に入れるの?

ITダッシュボードの白地図がそのまま使えるといいですね。

先ほどのページで Chrome の Developer Tools を開いて様子を見てみると、色々それらしい通信をしていることが見て取れます。どうやら都道府県の GeoJSON は https://www.itdashboard.go.jp/js/data/prefectures.geojson から取得しているようです。

それを使って白地図を描画するコードです。

<!doctype html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Learning Leaflet</title>
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.5.1/dist/leaflet.css"
        integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ=="
        crossorigin=""/>
    <style>
        #mapid { height: 360px; }
    </style>
</head>
<body>
    <div id="mapid"></div>
    <div>出典:<a href="https://www.itdashboard.go.jp">ITダッシュボード</a></div>

    <!-- Make sure you put this AFTER Leaflet's CSS -->
    <script src="https://unpkg.com/leaflet@1.5.1/dist/leaflet.js"
        integrity="sha512-GffPMF3RvMeYyc1LWMHtK8EbPv0iNZ8/oTtHPx9/cc2ILxQ+u905qIwdpULaqDkyBKgOaB57QTMg7ztg8Jm2Og=="
        crossorigin=""></script>
    <script>
        let mymap = L.map('mapid').setView([36.29667, 137.61629], 5);
        fetch('https://www.itdashboard.go.jp/js/data/prefectures.geojson')
        .then(resp => resp.json())
        .then(json => {
            L.geoJSON(json).addTo(mymap);
        })
    </script>
</body>
</html>

ライセンスは?

こんな便利な白地図データ、使わない手はありませんね。気になるライセンスはどうなっているのでしょうか?

ITダッシュボードの利用規約によれば、出典の記載などのルールに従うことで自由に使うことができます。CC BY での利用もできます。

オチは?

先ほどまで見えていたITダッシュボードですが、2019-05-21 01:20:00 現在、503 Service Unavailable となってしまいました。

f:id:rch850:20190521012227p:plain

調子に乗りすぎたのでしょうか、たまたまでしょうか。たまたまであることを祈ります。

(10分後)

アクセスできるようになりました。たまたま落ちていたのでしょう。よかったよかった。

本番環境での source maps についてのご意見たち

source maps ってあるじゃないですか。難読化、圧縮された JavaScriptCSS から、難読化前の場所を復元してくれるあれ。

デバッグを容易にするという点で source maps が有用なのは誰もが認めるところです。

では本番環境ではどうするか。作る?公開する?色々なところからのご意見をまとめました。

webpack

webpack は本番でも source maps を持っておくことを推奨しています。

We encourage you to have source maps enabled in production, as they are useful for debugging as well as running benchmark tests.

Production | webpack より

しかし公開すべきではないとしています。

You should configure your server to disallow access to the Source Map file for normal users!

Devtool | webpack より

DHH

DHH は source maps 公開して知見を共有しようぜという姿勢です。

このツイートについてはこちらの記事が詳しいです。

kyamashiro.hateblo.jp

CSS Tricks

こちらも公開して知見を広めようぜという姿勢です。

css-tricks.com

  1. It might help you track down bugs in production more easily
  2. It helps other people learn from your website more easily

まとめ

source maps を作ること自体は、参考にした3つの意見すべて賛成でした。source maps を作るには追加のビルド時間がかかるというデメリットもありますが、これは許容するようです。

しかし公開するかどうかは意見が分かれました。趣味の開発ならともかく、業務での開発となると、非公開を選ぶことが多そうに思えます。

余談

いくつかのフロントエンドのログ集約サービスの source maps 対応状況です

サービス 対応状況
Sentry 対応している
New Relic 対応している
TrackJS 対応している
Rollbar 対応している
Loggly 対応していない

ふくもく会その51

ふくもく会その51でやったことまとめ。

Glide でふくもく会アプリを作る

Glide でなんか作ったろーと思ってたので、過去のふくもく会のリストを。4つぐらい入力したあたりで、これ手でやる作業じゃないなと思い、Google Apps Script から connpass の API 呼び出してスプレッドシートに転記してみることに。

Rust

時間が余ったので Rust で Hello World しました。それだけ。環境構築は以前 WASI やろうとしたときにできてました。

その他雑談

  • 午前中はだいたい競プロの話をしていました。PG-Battle という企業、学校対抗のコンテストがあるそうです。今年は9月末。ISUCON 本戦の前の週じゃん。
  • DP (動的計画法) 勉強会的なものが催されてました。AtCoderEducational DP Contest というのがあったそうで、それの問題を見ながら。

昨日と今日

昨日の夜は、カッとなって Elixir に手を出してみた。

Getting Started の「1. はじめに」から「4. Pattern matching」まで。= は代入演算子じゃなくてマッチ演算子。味わい深いですね。明日も頑張ろう!と、このときは思っていた。

今日は、昼間に「以前に Amazon Machine Learning で作ったサザエさんじゃんけん予想を SageMaker あたりで作りなおせるかなー」とか考えてたんだけど、気が変わるのは早いもので、AWS Amplify でのホスティングを試してみた。

➜ amplify --help

  █████╗  ██╗    ██╗ ███████╗ 
 ██╔══██╗ ██║    ██║ ██╔════╝ 
 ███████║ ██║ █╗ ██║ ███████╗ 
 ██╔══██║ ██║███╗██║ ╚════██║ 
 ██║  ██║ ╚███╔███╔╝ ███████║ 
 ╚═╝  ╚═╝  ╚══╝╚══╝  ╚══════╝ 

 █████╗  ███╗   ███╗ ██████╗  ██╗      ██╗ ███████╗ ██╗   ██╗
██╔══██╗ ████╗ ████║ ██╔══██╗ ██║      ██║ ██╔════╝ ╚██╗ ██╔╝
███████║ ██╔████╔██║ ██████╔╝ ██║      ██║ █████╗    ╚████╔╝ 
██╔══██║ ██║╚██╔╝██║ ██╔═══╝  ██║      ██║ ██╔══╝     ╚██╔╝  
██║  ██║ ██║ ╚═╝ ██║ ██║      ███████╗ ██║ ██║         ██║   
╚═╝  ╚═╝ ╚═╝     ╚═╝ ╚═╝      ╚══════╝ ╚═╝ ╚═╝         ╚═╝   

かっこE。(みやすさのために改行入れてます)

ホスティングの Getting Started では analytics とやらにデータを送るんだけど、それを見る手段が Amazon Pinpoint でびっくりした。Pinpoint 使ったことないっていうかちょっと記憶にない名前ですね、ごめんなさい。モバイルアプリでの Events とか Funnels とか、要は Analytics 系のもろもろができるみたいです。なんでも AWS で見られるようにしたい!ってときは使うとよさそう。

無事ホスティングできたので、次はまたぜんぜん違うことで遊んでみようと思います。

spacefish にした

ゴマンとある、いや、ゴジュウぐらいか?それぐらいありきたりな話だけど、個人的な作業記録として spacefish にした話を。

spacefish は fish shell 用のプロンプトで awesome-fish の Prompts にもリストアップされているものです。Prompts には5つのプロンプトがリストアップされていて、うち3つが Powerline 系、2つがシンプル系で、spacefish は後者のシンプル系です。なになに系っていうとなんだか🍜が食べたくなるのでこのあたりでやめます。

fish のパッケージ管理には fisher を使っているので、インストールは案内にある通り

fisher add matchai/spacefish

を使いました。これで fishfile にも追記されてゴキゲン。

が、これだけでは絵文字らしきものが豆腐表示(四角)だったので Requirements にある Powerline Fonts を入れました。Powerline 系じゃないけど Powerline Fonts です。あ、また🍜食べたくなってきた。

手順は "On other environments..." に書いてある通り実行しました。

# clone
git clone https://github.com/powerline/fonts.git --depth=1
# install
cd fonts
./install.sh
# clean-up a bit
cd ..
rm -rf fonts

が、これだけでは(ryだったので、もしや Hyper.js 固有の問題かなと思い、Issue を検索すると、そのものずばりの「For Hyper.app users...」という issue がありました。

そこに書いてある通り Hyper.app の設定で fontFamily"Meslo LG S for Powerline" を追加したら、無事に spacefish らしい画面になりました。

f:id:rch850:20190328003533p:plain
spacefish

めでたしめでたし 🚀