小さなエンドウ豆

まだまだいろいろ勉強中

Railsでdb:seedを用いたデータの用意

db:seed とは

Rails アプリに予め必要なデータをデータベースに一括で投入することができる機能。今回はアプリ内でenumっぽく使いたいデータをどんどん投入していきます。

手順

まずはモデルとmigrateファイルを作り、db:migrateを実行します。

$ rails generate model area name:string
$ rails db:migrate # railsは5系です

これでAreaテーブルが作成されます。(ここらへん曖昧)

で、Rails プロジェクトの db/seeds.rb と以下のように編集します。

Area.create({name: '北海道'})
Area.create({name: '東北'})
Area.create({name: '関東'})
Area.create({name: '甲信越'})
Area.create({name: '東海・北陸'})
Area.create({name: '近畿'})
Area.create({name: '中国'})
Area.create({name: '四国'})
Area.create({name: '九州'})

保存して以下のコマンドを実行します。

$ rails db:seed

すると Area テーブルに9行のレコードが生成されます。 試しに rails console で見てみます。

$ rails console

irb> Area.all

   (2.9ms)  SET NAMES utf8,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
  Area Load (1.5ms)  SELECT  `areas`.* FROM `areas` LIMIT 11
=> #<ActiveRecord::Relation [#<Area id: 1, name: "北海道", created_at: "2017-08-11 04:16:13", updated_at: "2017-08-11 04:16:13">, #<Area id: 2, name: "東北", created_at: "2017-08-11 04:16:13", updated_at: "2017-08-11 04:16:13">, #<Area id: 3, name: "関東", created_at: "2017-08-11 04:16:13", updated_at: "2017-08-11 04:16:13">, #<Area id: 4, name: "甲信越", created_at: "2017-08-11 04:16:13", updated_at: "2017-08-11 04:16:13">, #<Area id: 5, name: "東海・北陸", created_at: "2017-08-11 04:16:13", updated_at: "2017-08-11 04:16:13">, #<Area id: 6, name: "近畿", created_at: "2017-08-11 04:16:13", updated_at: "2017-08-11 04:16:13">, #<Area id: 7, name: "中国", created_at: "2017-08-11 04:16:13", updated_at: "2017-08-11 04:16:13">, #<Area id: 8, name: "四国", created_at: "2017-08-11 04:16:13", updated_at: "2017-08-11 04:16:13">, #<Area id: 9, name: "九州", created_at: "2017-08-11 04:16:13", updated_at: "2017-08-11 04:16:13">]>

まあなんかいけてるっぽいw

db/seeds.rb に以下のように書くとCSVからデータを投入することもできる

require "csv"

CSV.foreach('db/company.csv') do |row|
  Company.create({area_id: row[0]}, {name: row[1]})
end

めっちゃ便利!

leaflet.jsの地図に駅をプロットする

leaflet.jsの地図に駅のレイヤーをオーバーレイする

前回作ったtopojsonをleaflet.jsを使って描画した日本地図に載せていきます。
方法としてはD3.jsでjsonを読み込んでレイヤーを作り、日本地図にオーバーレイする。
では始めます。 (鉄道データ引用元

複数のjsonファイルを読み込む

今回駅情報を記述したstation.jsonと線路情報を記述したrail.jsonを同時に読み込む必要があったため以下のように記述した。

d3.json("rail_road.json", function(railjson){
    d3.json("Station.json", function(stationjson) {
        railDraw(railjson, stationjson);
    });
});

この方法だとjsonファイルが何個もあるときどんどん深くなっていくのでおすすめできないかも… shpファイルをQGISなどを使って一つにまとめる方法や前のブログでやった変換時に複数のgeojsonファイルを指定して、一つのtopojsonに吐く方法などあるらしいのでそちらを使った方が良いかもしれない

leafletの地図にオーバーレイ

leaflet.jsで生成した地図にオーバーレイでレイヤー(svg)を追加する方法

var svg = d3.select(map.getPanes().overlayPane)
    .append("svg")
    .attr('class', 'leaflet-zoom-hide')
    .attr({width:960,height:500});

線路情報の描画

線路情報はjsonファイルの中を覗くとわかるが、緯度経度のリストがいくつも並んでいる形式になっている。以下例

"features": [
{ "type": "Feature", "properties": { "N02_001": "23", "N02_002": "5", "N02_003": "沖縄都市モノレール線", "N02_004": "沖縄都市モノレール" }, "geometry": { "type": "LineString", "coordinates": [ [ 127.67948, 26.21454 ], [ 127.6797, 26.21474 ], [ 127.67975, 26.2148 ], [ 127.68217, 26.21728 ], [ 127.68357, 26.21862 ], [ 127.68394, 26.21891 ], [ 127.68419, 26.21905 ] ] } },
{ "type": "Feature", "properties": { "N02_001": "12", "N02_002": "5", "N02_003": "いわて銀河鉄道線", "N02_004": "アイジーアールいわて銀河鉄道" }, "geometry": { "type": "LineString", "coordinates": [ [ 141.29139, 40.3374 ], [ 141.29176, 40.33723 ], [ 141.29243, 40.33692 ], [ 141.29323, 40.33654 ], [ 141.29379, 40.33624 ], [ 141.29411, 40.33608 ], [ 141.2949, 40.33563 ], [ 141.29624, 40.33477 ], [ 141.29813, 40.33354 ], [ 141.29862, 40.33317 ] ] } },
{ "type": "Feature", "properties": { "N02_001": "12", "N02_002": "5", "N02_003": "いわて銀河鉄道線", "N02_004": "アイジーアールいわて銀河鉄道" }, "geometry": { "type": "LineString", "coordinates": [ [ 141.27554, 40.23936 ], [ 141.27567, 40.23884 ], [ 141.27587, 40.23827 ], [ 141.27622, 40.23756 ], [ 141.27656, 40.23694 ], [ 141.27722, 40.23579 ], [ 141.27789, 40.23462 ], [ 141.27856, 40.23344 ], [ 141.27922, 40.23236 ], [ 141.27983, 40.23153 ], [ 141.28006, 40.23123 ], [ 141.28011, 40.23118 ], [ 141.28034, 40.23094 ], [ 141.28092, 40.23042 ], [ 141.28207, 40.22963 ], [ 141.28328, 40.22883 ], [ 141.28345, 40.22874 ] ] } },
{ "type": "Feature", "properties": { "N02_001": "12", "N02_002": "5", "N02_003": "いわて銀河鉄道線", "N02_004": "アイジーアールいわて銀河鉄道" }, "geometry": { "type": "LineString", "coordinates": [ [ 141.28659, 40.26092 ], [ 141.28538, 40.25874 ] ] } },
...

このgeometryの部分がそうである。これをsvgのpath要素で結んでいくイメージ

以下にD3でsvg要素にpathを追加していく例を記述します。

var g1 = svg.append("g");
    
//緯度経度->パスジェネレーター関数作成
var transform = d3.geo.transform({point: projectPoint});
var path = d3.geo.path().projection(transform);

 featureElement = g1.selectAll("path")
    .data(geo_rail_json.features)
    .enter()
    .append("path")
    .attr({
        "stroke": "red",
        "fill": "green",
        "fill-opacity": 0.4,
    });

function projectPoint(x, y) {
    var point = map.latLngToLayerPoint(new L.LatLng(y, x));
    this.stream.point(point.x, point.y);
}

上記のソースはネットでググったら同じようなものが色々出てきてそれを引用しましたが、いまいち何やっているかわからぬ。。 勉強しなければいけませんね

駅情報のプロット

駅情報を記述したjsonファイルであるstation.jsonはこんな感じ

"features": [
{ "type": "Feature", "properties": { "N02_001": "11", "N02_002": "2", "N02_003": "指宿枕崎線", "N02_004": "九州旅客鉄道", "N02_005": "二月田" }, "geometry": { "type": "LineString", "coordinates": [ [ 130.63035, 31.25405 ], [ 130.62985, 31.25459 ] ] } },
{ "type": "Feature", "properties": { "N02_001": "23", "N02_002": "5", "N02_003": "沖縄都市モノレール線", "N02_004": "沖縄都市モノレール", "N02_005": "古島" }, "geometry": { "type": "LineString", "coordinates": [ [ 127.70279, 26.23035 ], [ 127.70309, 26.23093 ] ] } },
{ "type": "Feature", "properties": { "N02_001": "24", "N02_002": "5", "N02_003": "東京臨海新交通臨海線", "N02_004": "ゆりかもめ", "N02_005": "お台場海浜公園" }, "geometry": { "type": "LineString", "coordinates": [ [ 139.77818, 35.62961 ], [ 139.77888, 35.63 ] ] } },
...

なぜか緯度経度の組みが2つずつあるような形式。おそらく南東と北西の点を取ってきているイメージ。四角形にすると右斜め下と左斜め上の点の緯度経度(わかりにくい)

これらを考慮しプロットするソースが以下の通りである

var g2 = svg.append("g");
 

geo_station_json.features.forEach(function(d){
    d.LatLng = new L.LatLng(d.geometry.coordinates[0][1], d.geometry.coordinates[0][0]);
});
 

stationElement = g2.selectAll("image")
    .data(geo_station_json.features)
    .enter()
    .append("image")
    .attr({"xlink:href":"./station.svg",
        "width":15,
        "height":10,
       });

今回はプロットする点を画像にしました。いつも通りだとsvgにcircle要素を追加していくのですが、image要素を追加していくことで画像がプロットされます。  

完成図

こちらが今回作った駅と線路をleaflet.jsに追加した地図です。 f:id:h-piiice16:20170611103900p:plain

気持ち悪い…拡大すると f:id:h-piiice16:20170611104103p:plain

なんかずれてる 直すのはまた今度にします。

今回作ったもの GitHub - hiracky16/RailRoad

topojsonへの変換と鉄道データの描画

geojson から topojson への変換

geojsonからtopojsonへの変換はググったらたくさん出てきてどれもnodejsのパッケージのtopojsonを使ったものが多かった。今回もそれを使います。

$ npm i -g topojson

$ geo2topo -q 1e6 railroad=N02-15_RailroadSection.json > N02-15_RailroadSection.topojson

-q 1e6というオプションが変換時に大切なものらしく ほぼ常時つけた方がよいらしい

railroadとはtopojsonでのオブジェクト名になるらしい。

D3.jsによるtopojsonの描画

変換したtopojsonファイルを描画していきます。
使うライブラリはみんな大好きD3です。

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  <meta charset="utf-8" />
  <title></title>
  <style>
  </style>
</head>
<body>
  <h1>Rail Road Test</h1>
  <script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script>
  <script src="//d3js.org/topojson.v0.min.js" charset="utf-8"></script>
  <script src="test.js"></script>
</body>

次にtest.jsについて

var width = 800;
var height = 800;

var svg = d3.select("body")
  .append("svg")
  .attr("width", width)
  .attr("height", height);

d3.json("rail_road.topojson", function(error, topo){

  var data = topojson.object(topo, topo.objects.railroad)
  var projection = d3.geo.mercator()
    .center(d3.geo.centroid(data))
    .scale(1200)
    .translate([width/2, height/2])

  var path = d3.geo.path().projection(projection);

  svg.selectAll("path")
    .data(data.geometries)
    .enter()
    .append("path")
    .attr("d", path);
});

dataの受け渡しの際にさっき設定したオブジェクト名"railroad"を指定して行っています。

上記のhtmlをブラウザで表示すると

f:id:h-piiice16:20170527225844p:plain

とまあわかりにくい

しかし描画できてるっぽいので今日はここまで!

次はこのsvg要素をオーバーレイとして、leaflet.jsなどを使い地図上に鉄道データをのせていきたいとおもいます。

国土数値情報の鉄道データ(shp)をgeojsonへ変換

手順

  1. データ のダウンロード
  2. QGISのインストー
  3. .shp -> .json の変換

それぞれ説明します。

データのダウンロード

データはこちらです。
このページの下の方からダウンロードしてきます。
データが年度で分かれているのですが、27年をダウンロードしました。

QGISのインストー

Macの場合、ここから
今回はおそらく最新のQGISのバージョン2.18.7-1 をダウンロードしました。
DLが済むとzipファイルを展開して、GDALとmatplotlib、QGISインストーラを起動します。

開発元が未確認のため開けません についての解決法

単にクリックしてインストールしようとしたところ「開発元が未確認のため開けません」というメッセージが出てインストールできませんでした。
解決策として、右クリックで「このアプリケーションで開く」を押すと開くの選択肢ができるので、このやり方でインストールを行いました。

インストールが終わると以下の1行をPATHに追加します。

/Library/Frameworks/GDAL.framework/Programs

.shp -> .json の変換

この変換は以下のコマンドで行えます。

ogr2ogr -f GeoJSON N02-15_RailroadSection.json N02-15_RailroadSection.shp

React-Dropzoneが便利

ドラックアンドドロップでファイルをアップロードしたい

最近?のWEBサービスではよくファイルをアップロードする際ドラックアンドドロップで行う場合が多い。
今回はそれを自分のサービスの中にも使いたいと思い実装してみました。

Reactにはそんな願いを叶える便利なコンポーネントとしてreact-dropzoneというものがある。
今回はこれを使って手っ取り早く実装することにしました。

React-Dropzone

実装したサービスの画面はこれです。 f:id:h-piiice16:20170423212401p:plain

ここにファイルをドラックアンドドロップで持っていくと、デベロッパーツールのコンソールにファイル名が表示されるサンプルを作りました。

実装のためにまずはreact-dropzoneをnpm経由でインストールします

npm install --save-dev react-dropzone

そして以下のようにコンポーネントを作成します。

import Dropzone from 'react-dropzone';
import React, {Component} from 'react';
import {render} from 'react-dom';

export default class UploadFile extends Component {

  handleOnDrop(files) {
    files.map(file => console.log(file.name))
  }

  render() {
    return( 
      <Dropzone
        onDrop={this.handleOnDrop}
        accept="image/gif,image/jpeg,image/png,image/jpg" >
          <div>
            Drag and Drop files Here!
            <p>format: gif/png/jpeg/jpg</p>
          </div>
      </Dropzone>
    );
  }
}

にはいろいろなアトリビュートが設定できるらしく、デフォで必要そうなonDrop(アップロード時の振る舞い)にコールバック関数としてhandleDrop()を、accept(受け入れるファイルの形式)に画像ファイルの形式を記述しました。

次回はアップロードされたファイルをサーバサイドにpostして保存するところまで行きたいと思います。

SQL小技集

SQL小技

最近SQLを書くことが多いので学んだことをまとめたいと思います。

2つのテーブルを更新したい

勝手な認識でupdate文は1つのテーブルに対し行うものだと思ってました。
しかし以下のように2つのテーブルに対して実行することができます。

update users u, players p
set u.id=1, p.user_id=1
where u.name="piiice" and p.name="piiice"

こんな感じです。

SQLを間違えてしまった…

間違いは誰にでもある。にんげんだもの

とはいえ、SQLの間違いはとってもやばいです。(語彙力)
こんな時に役に立つのがbegin, rollback, commitです。

begin, rollback, commitは制御構文といってcrud操作の中でもupdate, deleteで使うことが多いです。
これを用いるとsqlで加えた変更をなしにできることができます。 例はこちらです。

-- 1
begin;

-- 2
update user
set id=1, name="piiice";

-- 3
select *
from user;

-- rollback; -- 4
-- commit; -- 5

まず1を実行します。
次に2のupdateを実行します。この時点でDB自体の値は変わっていません。
3で2のupdateが正しいかどうか確認します。<- ここ小技!
もし間違いがあれば4を実行し、updateをなかったことにします。
間違がなければ5を実行し、値の更新を受け入れます。

これでSQLコワクナイ

servletのコンパイル2

modelパッケージの指定の仕方

servletコンパイルする際
modelがインポートできない問題に直面し なんとか解決出来たので忘れないように書いておく

構成はこんなかんじ

webapps/
└── example
    └── WEB-INF
        ├── classes
        │   ├── model
        │   └── servlet
        └── jsp

クラスパスを':'でつなげるところがコツらしい

javac -classpass /usr/share/tomcat7/lib/servlet-api.jar:./webapps/example/WEB-INF/classes webapps/example/WEB-INF/classes/servlet/Test.java