地図タイルをs3へ低冗長化でアップロード
作成した地図タイルをs3に低冗長化ストレージでアップする方法です。
低冗長化にしたのは、料金を安くしたいのと、元データは手元にあるので消えても大丈夫だからです。
S3へアクセスするためのアクセスキーを設定
cd ~/ emacs .aws export AWS_ACCESS_KEY_ID=hogehoge export AWS_SECRET_ACCESS_KEY=fugafuga source ~/.aws
s3-parallel-putと依存するbotoをインストール
低冗長化ストレージでアップするようにプログラムを変更
pip install boto git clone https://github.com/twpayne/s3-parallel-put cd s3-parallel-put emacs s3-parallel-put -key.set_contents_from_string(content, headers, md5=value.md5, policy=options.grant) +key.set_contents_from_string(content, headers, md5=value.md5, policy=options.grant,reduced_redundancy=True)
タイルのある場所に移動して実行
更新ではないのでstupidでスピード優先
~/github/s3-parallel-put/s3-parallel-put --bucket_region=ap-northeast-1 --bucket=ecoris --processes=5 --put=stupid --content-type="guess" --prefix=tiles/veg67 veg67 --log-filename=/home/ubuntu/log.txt
公開設定などは、こちらのエントリを参照
http://d.hatena.ne.jp/tm33/20130329/p1
料金
保管料
タイル1セット約5Gなので毎月かかる料金は
スタンダードで$5(500円)、低冗長化だと$0.4(40円)
使用料
データアクセスでの料金はこちら参照
http://qiita.com/kawaz/items/07d67a851fd49c1c183e
植生図をタイルにする(第6,7回版)
解凍
解凍すると、a.直接ファイルが作られるもの、b.フォルダの中にファイルが作られるもの、c.なぜだか他のファイルが紛れ込んでいるもの があるので取り扱い注意。
unzip.exeをダウンロードしておく。
http://gnuwin32.sourceforge.net/packages/unzip.htm
unzip.shで解凍&移動
for file in `find -type f -name "*.zip"` do ./unzip -o -q $file done for file in `find shp* -maxdepth 1 -type f -name "*"` do mv $file vegdir done mv p*.* vegdir
spatilaiteにインポート
veg.bat vegdir\*.shp
SET SHAPE_ENCODING=CP932 for %%A in (%1) do ogr2ogr -gt 65536 -append -nlt polygon -s_srs epsg:4612 -t_srs epsg:3857 -f SQLite veg67.sqlite %%A -dsco SPATIALITE=YES
それぞれファイル名と同じテーブル名に格納される。
vegテーブルにマージ
vegテーブルにマージするために以下のスクリプトでsqlを作成
$ mergesql.sh > merge.sql
echo "CREATE TABLE veg (OGC_FID INTEGER PRIMARY KEY, GEOMETRY POLYGON, mesh2_c VARCHAR, hanrei_c VARCHAR, surv_year VARCHAR, org_no VARCHAR, zukaku_no VARCHAR, shoku_c VARCHAR, shoku_n VARCHAR, dai_c VARCHAR, dai_n VARCHAR, chu_c VARCHAR, chu_n VARCHAR, sai_c VARCHAR, sai_n VARCHAR, hanrei_n VARCHAR);" for file in `find vegdir -maxdepth 1 -type f -name "*.shp"| sed -e "s/.*\/\(.*\)\..*/\1/g"` do echo "INSERT INTO veg(GEOMETRY, mesh2_c, hanrei_c, surv_year, org_no, zukaku_no, shoku_c, shoku_n, dai_c, dai_n, chu_c, chu_n , sai_c , sai_n, hanrei_n)" echo "SELECT GEOMETRY, mesh2_c, hanrei_c, surv_year, org_no, zukaku_no, shoku_c, shoku_n, dai_c, dai_n, chu_c, chu_n , sai_c , sai_n, hanrei_n" echo "FROM $file;" done
spatialiteを起動して、merge.sqlを読み込み実行
$ spatialite veg67.sqlite
> .read merge.sql shift_jis
確認
> .charset shift_jis
> select * from veg limit 5;
ジオメトリをリカバー
select RecoverGeometryColumn('veg','GEOMETRY',3857,'POLYGON','XY');
Spatial Index を作成
select CreateSpatialIndex('veg','GEOMETRY');
個別のテーブルを削除
$ dropsql.sh > drop.sql
$ spatialite veg67.sqlite
> .read drop.sql shift_jis
for file in `find vegdir -maxdepth 1 -type f -name "*.shp"| sed -e "s/.*\/\(.*\)\..*/\1/g"` do echo "select DisableSpatialIndex('$file','GEOMETRY');" echo "drop table $file;" echo "drop table idx_${file}_GEOMETRY;" done echo "VACUUM;"
tilemillで色つけ
tilemillを使って色つけします。
style.mss
Map { background-color: #99ffff; } #veg{ ::shape{ [zoom <= 11]{ line-width:0.1; polygon-opacity:1; [shoku_c='1']{polygon-fill:#fdf1ce;line-color:#fdf1ce;} [shoku_c='2']{polygon-fill:#997f60;line-color:#997f60;} [shoku_c='3']{polygon-fill:#a58f74;line-color:#a58f74;} [shoku_c='4']{polygon-fill:#178017;line-color:#178017;} [shoku_c='5']{polygon-fill:#5b9700;line-color:#5b9700;} [shoku_c='6']{polygon-fill:#003300;line-color:#003300;} [shoku_c='7']{polygon-fill:#004a00;line-color:#004a00;} [shoku_c='8']{polygon-fill:#ffff00;line-color:#ffff00;} [shoku_c='9']{polygon-fill:#8cd27d;line-color:#8cd27d;} [shoku_c='10']{polygon-fill:#868585;line-color:#868585;} [dai_n="植林地"]{polygon-fill:#697720;line-color:#697720;} [hanrei_n="開放水域"]{polygon-fill:#99ffff;line-color:#99ffff;} } [zoom >= 12]{ line-width:0.1; polygon-opacity:1; [shoku_c='1']{polygon-fill:#fdf1ce;line-color:#fdf1ce;} [shoku_c='2']{polygon-fill:#997f60;line-color:#997f60;} [shoku_c='3']{polygon-fill:#a58f74;line-color:#a58f74;} [shoku_c='4']{polygon-fill:#178017;line-color:#178017;} [shoku_c='5']{polygon-fill:#5b9700;line-color:#5b9700;} [shoku_c='6']{polygon-fill:#003300;line-color:#003300;} [shoku_c='7']{polygon-fill:#004a00;line-color:#004a00;} [shoku_c='8']{polygon-fill:#ffff00;line-color:#ffff00;} [shoku_c='10']{polygon-fill:#868585;line-color:#868585;} [dai_n="植林地"]{polygon-fill:#697720;line-color:#697720;} [dai_n="竹林"]{polygon-fill:#cccc20;line-color:#cccc20;} [dai_n="牧草地・ゴルフ場・芝地"]{polygon-fill:#69ff00;line-color:#69ff00;} [dai_n="耕作地"]{polygon-fill:#999662;line-color:#999662;} [hanrei_n="水田雑草群落"]{polygon-fill:#8cd27d;line-color:#8cd27d;} [hanrei_n="開放水域"]{polygon-fill:#99ffff;line-color:#99ffff;} [zoom >= 14]{line-width:0.5;line-color:#000000;} } } ::label[zoom >= 14]{ text-face-name: "unifont Medium"; text-fill:#000000; text-size:9; text-halo-fill:rgba(255,255,255,0.3); text-halo-radius:1; text-line-spacing:1; text-wrap-width:20; text-name: "[hanrei_n]"; [zoom >= 15] { text-size:10; } } }
project.mml
{ "bounds": [ 122.9, 20.4, 154, 45.6 ], "center": [ 140.8314, 40.7119, 10 ], "format": "png8:t=0:c=64:z=5", "interactivity": { "layer": "veg", "template_teaser": "{{{shoku_n}}}\n{{{dai_n}}}\n{{{hanrei_n}}}", "fields": [ "shoku_c" ] }, "minzoom": 1, "maxzoom": 15, "srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over", "Stylesheet": [ "style.mss" ], "Layer": [ { "geometry": "polygon", "extent": [ 122.873495939729, 24.004206926072346, 153.99615896406743, 45.66879613975789 ], "Datasource": { "type": "sqlite", "file": "veg67.sqlite", "table": "veg", "attachdb": "", "extent": "", "id": "veg", "project": "veg67", "srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over" }, "id": "veg", "class": "", "srs-name": "900913", "srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over", "advanced": {}, "name": "veg" } ], "scale": 1, "metatile": 2, "name": "vegetation67", "description": "vegetation67", "attribution": "", "legend": "" }
mbtileを出力
shmmaxの修正
これをしないとkillされる。ec2 microだとメモリが足りなくてkillされる。
sudo sysctl -w kernel.shmmax=268435456 sudo sysctl -p
タイル作成
cd /usr/share/tilemill sudo node index.js export veg67 /usr/share/mapbox/export/veg67.mbtiles --format=mbtiles --bbox=122.9,20.4,154,45.6 --minzoom=1 --maxzoom=15 --metatile=2 --scale=1 --scheme=pyramid --log --files=/usr/share/mapbox/
フォルダに展開
S3に転送
CloudFrontにドメイン設定
S3での利用料金解析
S3 programatic billingの設定
http://sssslide.com/www.slideshare.net/AmazonWebServicesJapan/awsaws-billing
GrowthForecast、Fluentd
http://techmedia-think.hatenablog.com/entry/20121102/1351850643
設定
インストール
ec2にインストール。やり方はここの通り
http://kazeburo.github.com/GrowthForecast/
起動
外からアクセスしたい場合は、ポートをec2で開けておく。データの保存場所を指定
growthforecast.pl --data-dir ~/gw --port 20008
データ保存
例えば、vegetation-billing-dailyのグラフに現時点の値=30を入力
curl -F number=30 http://localhost:20008/api/vegetation/billing/daily
データの確認、保存
ここからアクセス
http://hogehoge:20008/
もしくは、グラフだけ直接
http://hogehoge:20008/list/vegetation/billing?t=sh
daily.pngに保存
curl -o daily.png "http://localhost:20008/graph/vegetation/billing/daily?t=sh&gmode=gauge"
s3 billing csv との連携
billing.shで以下を保存して定期実行
s3cmd --force get s3://ecoris/399748833865-aws-billing-csv-2013-04.csv billing04.csv data=$(tail -n 2 billing04.csv |sed -n '1p'|awk -F"," '{gsub("\"","",$29);print $29}'|cut -d '.' -f 1) echo $data curl -F number=${data} http://localhost:20008/api/vegetation/billing/daily curl -o daily.png "http://localhost:20008/graph/vegetation/billing/daily?t=sh&gmode=gauge" s3cmd put daily.png s3://ecoris/vegetation/html/
毎時0分に実行
sudo crontab -e
0 * * * * /home/ubuntu/test.sh
fluentd(td-agent?)
fluentdを使えば、上記のログを取ってきて、GrowthForcastへ出力の部分が汎用的にできるようになるが、S3からログを取ってくるプラグインが見つからないのと、pngをアップする部分は結局、別で実行しなくてはいけないので、すべてお手製スクリプトで作成
http://help.treasure-data.com/kb/installing-td-agent-daemon/installing-td-agent-for-debian-and-ubuntu
http://d.hatena.ne.jp/naoya/20130219/1361262854
ファウンディング
クラウドファウンディング
個人スポンサー
http://www.goodsmileracing.com/personal-sponsors.html
地図タイルのスピードテスト
地図タイルの読み込みスピードをjmeterを使って計測します。
jmeterの設定
jmeterではhtmlからajaxで呼び出される地図タイルの計測はできない(らしい)ので
タイルを直接呼び出す時間を計測する。
data.csvの作成
以下のスクリプトを実行して、読み込むタイルのリストdata.csvを作成する。
ランダムに作成される中心位置から各ズームレベルごとに5×4=20枚のタイルを作成する。この場合、ズームレベル11〜15の5段階なので5×20=100タイル
#!/usr/bin/env python from math import pi,cos,sin,log,exp,atan import sys import random DEG_TO_RAD = pi/180 RAD_TO_DEG = 180/pi def minmax (a,b,c): a = max(a,b) a = min(a,c) return a class GoogleProjection: def __init__(self,levels=18): self.Bc = [] self.Cc = [] self.zc = [] self.Ac = [] c = 256 for d in range(0,levels): e = c/2; self.Bc.append(c/360.0) self.Cc.append(c/(2 * pi)) self.zc.append((e,e)) self.Ac.append(c) c *= 2 def fromLLtoPixel(self,ll,zoom): d = self.zc[zoom] e = round(d[0] + ll[0] * self.Bc[zoom]) f = minmax(sin(DEG_TO_RAD * ll[1]),-0.9999,0.9999) g = round(d[1] + 0.5*log((1+f)/(1-f))*-self.Cc[zoom]) return (e,g) def fromPixelToLL(self,px,zoom): e = self.zc[zoom] f = (px[0] - e[0])/self.Bc[zoom] g = (px[1] - e[1])/-self.Cc[zoom] h = RAD_TO_DEG * ( 2 * atan(exp(g)) - 0.5 * pi) return (f,h) def print_tiles(bbox, minZoom=1,maxZoom=18,tms_scheme=False): gprj = GoogleProjection(maxZoom+1) ll0 = (bbox[0],bbox[3]) ll1 = (bbox[2],bbox[1]) for z in range(minZoom,maxZoom + 1): px0 = gprj.fromLLtoPixel(ll0,z) px1 = gprj.fromLLtoPixel(ll1,z) xnum=0 for x in range(int(px0[0]/256.0),int(px1[0]/256.0)+1): if (x < 0) or (x >= 2**z): continue xnum+=1 if(xnum > 5): break ynum=0 for y in range(int(px0[1]/256.0),int(px1[1]/256.0)+1): if (y < 0) or (y >= 2**z): continue if tms_scheme: y = (2**z-1) - y print("%d,%d,%d" % (z,x,y)) ynum+=1 if(ynum > 4): break if __name__ == "__main__": rnd=random.random()*10 bbox = (135+rnd,35+rnd,136+rnd,36+rnd) print_tiles(bbox, 11, 15)
data_tiles.pyで保存して、以下コマンドで実行
python data_tiles.py
スピードテストの項目
- S3
- CloudFront + S3
- EC2(micro) + tilestream
- EC2(2xlarge) + tilestream
- CloudFrare + S3
- さくらVPS
- レベル11〜15のgrids読み込み有り
- レベル11〜15のgrids読み込み無し
結果
- CloudFrontを利用すると、およそ2倍のはやさ
(でも、いまいち体感速度は変わらないような。。。
CloudFrion使えばS3のバケット名が任意でOKなので、使う方向で)
- tilestream + micro は CloudFrontに比べて100倍おそい
S3で地図タイルのアクセス制御
S3に保存した地図タイルへの直呼び出しのアクセス管理ができないか検討
アクセス制御方式
http://www.tdn.co.jp/techblog/201207/55/
s3でのアクセス制御の方法は4種類らしい。
ACL、bucket policy、IAM、Query String Request
自分なりの解釈
ACL:ファイルパーミッション
bucket policy:.htaccessのようなもの
IAM:ユーザーグループのようなもの
Query String Request:Oauth認証のようなもの
地図タイルへのアクセス制限
目的:地図タイルへの直アクセスを制御したい。
- 直アクセスできるのは、許可したサイト、アプリ、ソフトから
方法:
Refererによるアクセス制御
http://www.idupree.com/dreams/s3-referer-hotlink
s3のbucket policyに以下を設定
refererが無い場合(index.htmlの読み出し)と、許可した所。※下の不具合でもっとシンプルな設定が動作しないと勘違いしていたかもしれないので、そっちを試した方がいいかも。あとで。
{ "Version": "2008-10-17", "Id": "refererGuard", "Statement": [ { "Sid": "1", "Effect": "Allow", "Principal": { "AWS": "*" }, "Action": "s3:GetObject", "Resource": "arn:aws:s3:::ecoris/*", "Condition": { "StringNotLike": { "aws:Referer": "*" } } }, { "Sid": "2", "Effect": "Allow", "Principal": { "AWS": "*" }, "Action": "s3:GetObject", "Resource": "arn:aws:s3:::ecoris/*", "Condition": { "StringLike": { "aws:Referer": "http://ecoris.s3-website-ap-northeast-1.amazonaws.com/*" } } } ] }
アプリ、ソフトからのReferer
アプリ、ソフトから地図タイルを呼び出した場合、No Refererになるっぽい。
それだとアクセス制御できないので、なんとかしたい。
Google Analytics SDKを使ってもらえば、refererが付く?
http://murapong.hatenablog.com/entry/20111220/1324379177
Refereは偽装できるので、うーん。
Query String Requestのテスト
http://www.tdn.co.jp/techblog/201207/55/
アクセスキーの発行とかは、管理が面倒そうなので現実的ではないけど、一応テスト
<html> <head> <script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/hmac-sha1.js"></script> <script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/components/enc-base64-min.js"></script> <script> var AWSAccessKeyId='hoge'; var secretAccessKey='hogehoge'; var host = "https://s3-ap-northeast-1.amazonaws.com"; var filePath = "/ecoristest/test/test.png"; function myinit(){ var dateTime = Math.floor(new Date().getTime()/1000) + 300; var stringToSign = "GET\n\n\n" + dateTime + "\n" + filePath; var signature = CryptoJS.enc.Base64.stringify(CryptoJS.HmacSHA1(stringToSign, secretAccessKey)); var url = host + filePath + "?AWSAccessKeyId=" + AWSAccessKeyId + "&Signature=" + encodeURIComponent(signature) + "&Expires=" + dateTime; document.getElementById("AuthenticationURL").innerHTML = "<a href='" + url + "'>access</a>"; } </script> </head> <body onload="myinit()"> <div id="AuthenticationURL"></div> </body> </html>