自分用Gitまとめ

裏で実装してソース管理したかったのでgitをローカルで使ってみました. masterに本番のソースを持ってきて,機能ごとにbranch切って実装する感じです.

Gitリポジトリを立てる

なにはともあれまずはGitリポジトリを立てないと始まりません. 今回は「D:\Sample」にGitリポジトリを立てることにします.

md D:\Sample
cd D:\Sample
git init

これで「D:\Sample」にGitリポジトリが作られました.

.gitignoreファイルを作る

gitに管理させないファイルを指定するために.gitignoreファイルを用意します. リポジトリの直下に用意します.

cd D:\Sample
New-Item .gitignore -ItemType File

ソースコードを追加する

「D:\Sample」にソースコードを追加しましょう. 今回は「WebApp」フォルダにソースコードが入っているとします。

Sample
┣WebApp
┗.gitignore

追加したらGitに変更を通知しましょう.

git add .

いよいよコミットです.

git commit -m 'masterブランチを作成します.'

ブランチを切る

まずは現在のブランチを確認します.

git branch -a

masterのみが表示されると思います. *は現在どのブランチを使用しているかを表しています.

* master

では,masterから新しくブランチを切ります.

git branch NewBranch

現在のブランチを確認します.

git branch -a

NewBranchが追加されました. *は現在どのブランチを使用しているかを表しています.

NewBranch
* master

では新しく作ったブランチに切り替えます.

git checkout NewBranch

現在のブランチを確認します.

git branch -a

NewBranchに切り替わりました.

* NewBranch
master

では新しく作ったブランチに変更を加えましょう. 変更を加えたら後は変更を通知してコミットします.

git add .
git commit -m 'ブランチに変更を加えます.'

masterの変更を切ったブランチに反映する

masterに変更があったとします. それを切ったブランチに反映してみます.

cd D:\Sample
git checkout master
New-Item b.txt -ItemType File
git add .
git commit -m 'masterにb.txtを追加しました.'
git checkout newbranch
git merge master

GIS用語まとめ

久々の投稿です. 今回は新人に教えたGISの用語をまとめました.

QGIS

https://www.qgis.org/ja/site/

シェープファイルを閲覧,加工できるGISフリーソフト

OSM

https://www.openstreetmap.org/ https://wiki.openstreetmap.org/wiki/Main_Page

OSM(OpenStreetMap)は地理情報をフリーで利用出来るように立ち上げられたプロジェクトである. OSMから地図やシェープファイルがフリーで提供されている.

シェープファイル

https://www.esrij.com/gis-guide/esri-dataformat/shapefile/ https://www.esrij.com/cgi-bin/wp/wp-content/uploads/documents/shapefile_j.pdf

図形情報や属性情報がまとまったファイルを指す. ESRI社が提唱したフォーマットでGISで標準となっている. シェープファイルには少なくとも下記ファイルが含まれる.

拡張子 内容
.shp 図形情報が格納されている.
.dbf 属性情報が格納されている.
.shx 図形と属性の対応関係が格納されている.

現行の制約では,「1つのシェープファイルには1つのジオメトリしか格納することができない」となっている.

ベースマップ

背景地図のことを指す. ESRIから無償のベースマップが公開されている.

地図 URL
白地図 https://services.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer
衛星画像 https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer
道路地図 https://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer
起伏図 https://services.arcgisonline.com/ArcGIS/rest/services/World_Terrain_Base/MapServer
地形図 https://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer

ベースマップにレイヤを重ねることで任意の情報と地理情報を組み合わせて分析することができる.

空間参照系

CRS(Coordinate Reference System)ともいう. 地球の実際の場所を二次元でどのように表現するかを決定する. 主に投影座標系と地理座標系に分類される.

投影座標系

3次元の地球を2次元の平面に投影してXY座標で表現する座標系を投影座標系という. 地図アプリのデファクトスタンダードとして使用されている投影座標系はWebメルカトルである.

Webメルカトル

Webメルカトルは昔EPSG:900913が使われていたが現在はEPSG:3857を使う. Webメルカトルでは地球に円筒をかぶせて,2次元の平面に投影する.

f:id:mt9116:20181211155925p:plain

経線はそれぞれ等間隔,平行となる. 緯線は平行だが,経度が赤道から離れるほど間隔が大きくなる.

f:id:mt9116:20181211155938p:plain

赤道と本初子午線の交点を中心とし,中心から何m離れているかを座標で表現する.

f:id:mt9116:20181211155949p:plain

地理座標系

EPSGコードは4326. 緯度,経度,高度を用いて表現する.

f:id:mt9116:20181211160003p:plain

赤道と本初子午線の交点を中心とし,座標の単位は角度で表す.

f:id:mt9116:20181211160013p:plain

EPSG

http://www.epsg.org/

EUROPEAN Petroleum Survey Group. 空間参照系や座標変換の定義をまとめている.

フィーチャ

現実世界の物(標識,道路,建物等)をベクターデータ化したもの. 地物ともいう. ポイント,ライン,ポリゴンの3種類存在する.

WKT(Well-known text)

フィーチャをテキスト形式で表現するフォーマット.

Type Exanple Image
Point POINT (139.621002802592 35.7119406409266) f:id:mt9116:20181211160039p:plain
Line LINESTRING (139.548599326586 35.7376245768467,139.607394853493 35.7118940200852,139.657922259429 35.7230822398456) f:id:mt9116:20181211160056p:plain
Polygon POLYGON *1 f:id:mt9116:20181211160108p:plain

WKTの実用例

SQL Serverでの実用例を示す. 例えば以下のようなフィーチャを格納するテーブルがあったとする.

CREATE TABLE SAMPLE_FEATURE(SHAPE geometry NULL);

以下のようにWKTからgeometryインスタンスを生成することでフィーチャを登録することができる.

INSERT INTO SAMPLE_FEATURE(SHAPE) VALUES(geometry::STPointFromText('POINT(135 35)', 4326));

DBを確認すると確かにフィーチャが登録されていることがわかる.

SELECT SHAPE FROM SAMPLE_FEATURE;
SHAPE
0xE6100000010C0000000000E060400000000000804140

WKT形式でフィーチャを取得することもできる.

SELECT SHAPE.STAsText() FROM SAMPLE_FEATURE;
SHAPE
POINT(135 35)

参考

*1:139.540790545669 35.7797450710378,139.600504752684 35.7756458256734,139.599126732522 35.741352953488,139.552733387072 35.7424714324349,139.540790545669 35.7797450710378

【ASP.NET MVC】スクリプトをBundleしたりMinifyしたりする

ASP.NET MVCではwebpack使わなくてもスクリプトをバンドルできます.
各画面にソースのパスを書くのは面倒なのでBundleConfigでまとめてみます.

Microsoft.AspNet.Web.Optimizationの取得

Microsoft.AspNet.Web.OptimizationをNuGetから取得します.

BundleConfigの作成

App_StartフォルダにBundleConfig.csを追加します.

using System.Web.Optimization;

public class BundleConfig
{
    public static void RegisterBundles(BundleCollection bundles)
    {
        bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
            "~/Scripts/jquery-{version}.js"));    
        bundles.Add(new ScriptBundle("~/scripts/hoge").Include(
            "~/Scripts/Sample/hoge.js",
            "~/Scripts/Sample/fuga.js",
            "~/Scripts/Sample/piyo.js"));
    }
}

BundleConfigをGlobal.asaxへ登録する

Global.asax.csでBundleConfigを呼び出すようにします.

using System.Web.Optimization;

public class Global : HttpApplication
{
    void Application_Start(object sender, EventArgs e)
    {
        BundleConfig.RegisterBundles(BundleTable.Bundles);
    }
}

画面からBundleを呼び出す

あとは画面で呼び出すだけです.

@using System.Web.Optimization

<head>
    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/hoge")
</head>

結果

<head>
    <script src="/map/scripts/jquery?v=WvOgJns1M1ZZkySVvxnR5qZTgNQ_UWnAsm21lcWbuMs1"></script>
    <script src="/map/scripts/hoge?v=fsiac-cyztW0v5rUgi-a9vvPTsOHXerrhW6x3w1IiHw1"></script>
</head>

ソースのパスをBundleConfigにまとめることが出来ました.
また、BundleConfigで{version}と指定することでライブラリのバージョンを更新しても気にする必要がなくなりました.
更に、結果のソースを見てみるとminifyされていることが分かります.

var hoge=function(){function n(){}return n.prototype.JsonToDocType=function(n){ ...

すてき.

【ASP.NET】面倒なオブジェクトマッピングはAutoMapperにやらせよう

AutoMapperって?

オブジェクトの詰め替えをしてくれる.NETのライブラリです. マッパーを使うことでオブジェクトの詰め替えをその都度記述する必要がなくなるのでコーディング量を減らすことが出来ます. また、オブジェクト間のプロパティの対応関係を局所化することが出来るので、修正箇所が限定されます.

AutoMapperの取得

NuGetからAutoMapperを取得してください.

AutoMapperを使わない場合

MVCではよくEntityをViewModelに詰め替えることがあるかと思います. 例えばDBから取得したユーザをViewModelに詰め替えて返す場合こんな感じになるかと思います.

public class UserController
{
    public ActionResult Detail(string name)
    {
        // DBからUSERを取得したとする
        var user = Service.FindUserByName(name);
        // USER -> UserViewModel
        var viewModel = new UserViewModel()
        {
            UserId = user.USER_ID;
            UserName = user.USER_NAME;
            Tel = user.TEL;
            Mail = user.MAIL;
        }
        return View(viewModel);
    }
}

Entity

public class USER
{
    public string USER_ID { get; set; }
    public string USER_NAME { get; set; }
    public string TEL { get; set; }
    public string MAIL { get; set; }
}

ViewModel

public class UserViewModel
{
    public string UserId { get; set; }
    public string UserName { get; set; }
    public string Tel { get; set; }
    public string Mail { get; set; }
}

先ほどのControllerに一覧を返すメソッドを追加した場合はこんな感じに.

public class UserController
{
    public ActionResult Detail(string name)
    {
        // DBからUSERを取得したとする
        var user = Service.FindUserByName(name);
        // USER -> UserViewModel
        var viewModel = new UserViewModel()
        {
            UserId = user.USER_ID;
            UserName = user.USER_NAME;
            Tel = user.TEL;
            Mail = user.MAIL;
        }
        return View(viewModel);
    }

    public ActionResult List()
    {
        // DBからUSER一覧を取得したとする
        var users = Service.FindUsers();
        var viewModels = new List<UserViewModel>();
        foreach(var user in users)
        {
            // USER -> UserViewModel
            var viewModel = new UserViewModel()
            {
                UserId = user.USER_ID;
                UserName = user.USER_NAME;
                Tel = user.TEL;
                Mail = user.MAIL;
            };
            viewModels.Add(viewModel);
        }         
        return View(viewModels);
    }    
}

詰め替えまくってますね(笑). 例えば「USERにFAXも追加しよう」となったとき,詰め替えている箇所全て修正する必要がありますね.

public class UserController
{
    public ActionResult Detail(string name)
    {
        // DBからUSERを取得したとする
        var user = Service.FindUserByName(name);
        // USER -> UserViewModel
        var viewModel = new UserViewModel()
        {
            UserId = user.USER_ID;
            UserName = user.USER_NAME;
            Tel = user.TEL;
            // 追加
            Fax = user.FAX;
            Mail = user.MAIL;
        }
        return View(viewModel);
    }

    public ActionResult List()
    {
        // DBからUSER一覧を取得したとする
        var users = Service.FindUsers();
        var viewModels = new List<UserViewModel>();
        foreach(var user in users)
        {
            // USER -> UserViewModel
            var viewModel = new UserViewModel()
            {
                UserId = user.USER_ID;
                UserName = user.USER_NAME;
                Tel = user.TEL;
                // 追加
                Fax = user.FAX;
                Mail = user.MAIL;
            };
            viewModels.Add(viewModel);
        }         
        return View(viewModels);
    }    
}

いちいち該当箇所を探すのは面倒ですよね. そこでAutoMapperです.

AutoMapperConfigの作成

AutoMapperConfig.csにマッピング情報を追加します. App_Start配下に用意してくださいね.

using AutoMapper;

public class AutoMapperConfig
{
    public static void RegisterAutoMappings()
    {
        Mapper.Initialize(cfg =>
        {
            // USER -> UserViewModel
            cfg.CreateMap<USER, UserViewModel>()
                .ForMember(d => d.UserId, o => o.MapFrom(s => s.USER_ID))
                .ForMember(d => d.UserName, o => o.MapFrom(s => s.USER_NAME))
                .ForMember(d => d.Tel, o => o.MapFrom(s => s.TEL))
                .ForMember(d => d.Mail, o => o.MapFrom(s => s.MAIL));
        });
    }
}

Global.asaxへの登録

public class Global : HttpApplication
{
    void Application_Start(object sender, EventArgs e)
    {
        AutoMapperConfig.RegisterAutoMappings();
    }
}

AutoMapperの利用

あとはControllerを修正します.

using AutoMapper;

public class UserController
{
    public ActionResult Detail(string name)
    {
        // DBからUSERを取得したとする
        var user = Service.FindUserByName(name);
        // USER -> UserViewModel
        var viewModel = Mapper.Map<UserViewModel>(user);
        return View(viewModel);
    }

    public ActionResult List()
    {
        // DBからUSER一覧を取得したとする
        var users = Service.FindUsers();
        var viewModels = new List<UserViewModel>();
        foreach(var user in users)
        {
            // USER -> UserViewModel
            var viewModel = Mapper.Map<UserViewModel>(user);
            viewModels.Add(viewModel);
        }         
        return View(viewModels);
    }    
}

超すっきりしましたね! これで「USERにFAXも追加しよう」となったときはAutoMapperConfigを修正するだけで済みます.

using AutoMapper;

public class AutoMapperConfig
{
    public static void RegisterAutoMappings()
    {
        Mapper.Initialize(cfg =>
        {
            // USER -> UserViewModel
            cfg.CreateMap<USER, UserViewModel>()
                .ForMember(d => d.UserId, o => o.MapFrom(s => s.USER_ID))
                .ForMember(d => d.UserName, o => o.MapFrom(s => s.USER_NAME))
                .ForMember(d => d.Tel, o => o.MapFrom(s => s.TEL))
                .ForMember(d => d.Fax, o => o.MapFrom(s => s.FAX))
                .ForMember(d => d.Mail, o => o.MapFrom(s => s.MAIL));
        });
    }
}

最後に

AutoMapperConfigにMapping情報を書けばMapperを呼び出すだけで簡単にマッピングされるようになります. マッピング情報がAutoMapperConfig一箇所にまとまるのでコードの見通しも良くなりメンテも捗るはずです.

【SQL Server】移行後のDBと移行前のDBのデータが同じかどうかを確認してみた

新旧DBのなかみが同じことを確認してみた

DBを移行してデータが本当に同じかどうかを検証してみた.
SQL Server 2008 -> SQL Server 2016.

方針

全件データをファイルに掃き出してハッシュ値を比べることにした.
もっとスマートなやり方知ってる人いたら誰か教えて!

  1. DBのテーブル一覧を取得する
  2. 各テーブルのデータを全件取得するSQLを作成する
  3. 新旧DBの各テーブルのデータをファイルに掃き出す
  4. ファイルのハッシュ値を比べて等しいことを確認する

スクリプト

1.DBのテーブル一覧を取得する

SQLはこんなかんじ.
SQL Serverではsys.objectからテーブル情報を取得出来る.
type = 'U'でユーザーテーブルを指定した.

USE [TEST_TABLE]
SELECT name FROM sys.objects WHERE type = 'U' AND name LIKE 'TEST_%' ORDER BY name;

あとはsqlcmdでSQLを叩く.
-oで結果を出力する.

$hostName = [System.Environment]::MachineName
$userName = "sa"
$password = "password"
$sql = "getTableNames.sql"
$csv = "tableNames.csv"
sqlcmd -S $hostName -U $userName -P $password -i $sql -o $csv

結果はこんなかんじ.

データベース コンテキストが 'TEST' に変更されました。
name                                                   
-------------------------------------------------------
TEST_USER
...
TEST_ROLE

(xxx 行処理されました)

2.各テーブルのデータを全件取得するSQLを作成する

1.でテーブル名一覧を取得したので各テーブルのデータを全件取得するSQLを作成する.
テンプレを用意してテーブル名をつっこんでSQLを発行するようにした.

USE [TEST]

DECLARE @sql nvarchar(max);
DECLARE @tblName nvarchar(max);

SET @tblName = N'{TABLE_NAME}';
SET @sql = N'SELECT * FROM ' + @tblName;

exec sp_executesql @sql

テーブル名一覧はコピペで用意しちゃった.

$template = "D:\Sample\SQL\template.sql"
$sqlOutDir = "D:\Sample\SQL\Test"
$tableNames = (
    "TEST_USER",
    ...
    "TEST_ROLE"
)
$tableNames | % { (Get-Content -Path $template) -replace "{TABLE_NAME}", $_ | Out-File -Encoding String -FilePath ("{0}\{1}.sql" -f $sqlOutDir, $_) }

3.新旧DBの各テーブルのデータをファイルに掃き出す

新旧DBそれぞれで2.で作成したSQLを実行する.

# 2.で作成したSQLが置いてあるとこ
$sqlDir = "D:\Sample\SQL\Test"
# 全件データの出力先
$outDir = "D:\Sample\Result\New"
# $outDir = "D:\Sample\Result\Old"
Get-ChildItem -Path $sqlDir -Filter *.sql | % { Execute-Sql -Sql ("{0}\{1}" -f $sqlDir, $_.Name) -FileName ($_.Name -split ".")[0] -OutDir $outDir }

function Execute-Sql(){
    Param(
        $Sql, 
        $FileName,
        $OutDir
    )
    $hostName = [System.Environment]::MachineName
    $userName = "sa"
    $password = "password"
    $csvName = ("{0}\{1}.csv" -f $OutDir, $FileName)
    sqlcmd -S $hostName -U $userName -P $password -i $Sql -o $csvName -s ","
}

4.ファイルのハッシュ値を比べて等しいことを確認する

$str = "ファイル,比較結果,新DB,旧DB`n"
Get-ChildItem -Path $RelResultDir -Filter *.csv | ForEach-Object { $str += Get-ComparedResultStr -FileName ($_.Name -split "\.")[0] -NewFileDir ("{0}\{1}" -f $RelResultDir, $_.Name) }
$str | Out-File "CompareResult.csv" -Encoding default

funciton Get-ComparedResultStr(){
    Param(
        $NewDataDir,
        $FileName
    )

    # 新DBのハッシュ値
    $NewHash = (Get-FileHash -Path $NewDataDir).Hash
    # 旧DBのデータを格納しているディレクトリに切替
    $OldDataDir = $NewDataDir -replace "New", "Old"
    # 旧DBのハッシュ値
    $OldHash = (Get-FileHash -Path $OldDataDir).Hash
    # CSVのデータ行
    return ("{0},{1},{2},{3}`n" -f $FileName, ($NewHash -eq $OldHash), $NewHash, $OldHash)
}

比較結果はこんなかんじ.

ファイル,比較結果,新DB,旧DB
TEST_USER,True,A8BD1EB1209E732645D9DB396A7EA969DB26EF105090FC8C2FB2C4E9E0A28A7E,A8BD1EB1209E732645D9DB396A7EA969DB26EF105090FC8C2FB2C4E9E0A28A7E
...
TEST_ROLE,True,DFE6BB21EEE7ED304D72F02B77974ADA00F8E8958A477045CAE0461B3A4EFF11,DFE6BB21EEE7ED304D72F02B77974ADA00F8E8958A477045CAE0461B3A4EFF11

これでスクリプトで正しくデータ移行できたか検証できた.

matplotlibの日本語表示問題を解決する

matplotlibのとうふを撲滅する

f:id:mt9116:20180719150741p:plain

にほんごがとうふになっちゃう...

日本語に対応したフォントを用意する

https://ipafont.ipa.go.jp/ からIPAexゴシックフォントをダウンロードする.

matplotlibの設定ファイルのパスを調べる

import matplotlib
matplotlib.matplotlib_fname()

設定ファイルを編集する

matplotlibrcファイルを適当なエディタで開いて下の行を追加する.

font.family : IPAexGothic

フォントファイルを配置する

設定ファイルのパス/fonts/tffにダウンロードしたipaexg.tffを置く.

キャッシュを再構築する

import matplotlib
matplotlib.font_manager._rebuild()

以上でとうふは消えます.

f:id:mt9116:20180719150754p:plain

tesseractでOCR@Windows7

OCRに興味があったので試してみた.

tesseract

OCRエンジンのオープンソース. https://github.com/tesseract-ocr/tesseract

tesseractのインストール

Windows7でtesseractを使えるようにした. まず, https://github.com/parrot-office/tesseract/releases/tag/3.5.1 からtesseract-Win64.zipをダウンロードした. ダウンロードしたらディレクトリ(C:\Program Files\Tesseract-OCR)を作成して, zipファイルをC:\Program Files\Tesseract-OCRに展開した.

辞書データの準備

https://github.com/tesseract-ocr/tesseract/wiki/Data-Files のeng.traineddata, jpn.traineddataをダウンロードする. ディレクトリ(C:\Program Files\Tesseract-OCR\tessdata)を作成して, eng.traineddata, jpn.traineddataを置く.

環境変数の設定

変数
Path C:\Program Files\Tesseract-OCR
TESSDATA_PREFIX C:\Program Files\Tesseract-OCR\tessdata

tesseractを試す

コマンドプロンプトでtesseractを実行する. sample.pngをtesseractでテキストにしてみる.

tesseract sample.png result -l jpn

画像

sample.png

結果 result.txt

何言つてんだよ! !
その崖っぷちが最髙のチヤンスなんだぜ ! !

まつおかしゅうぞう

ちゃんとテキスト化できた(笑)

pyocr

tesseractをpythonから使えるようにしたラッパー. https://gitlab.gnome.org/World/OpenPaperwork/pyocr

pyocrのインストール

環境はanaconda@windows7. pyocr用に仮想環境を作ってpyocrをインストールする.

> conda create -n ocr python=3.6
> conda info -e
# conda environments:
#
base                  *  c:\tools\Anaconda3
ocr                      c:\tools\Anaconda3\envs\ocr
> activate ocr
(ocr)> pip install pyocr

pyocrからtesseractを呼び出せるか確認

https://gitlab.gnome.org/World/OpenPaperwork/pyocr のUsage Initializationで検証する.

test.py

from PIL import Image
import sys

import pyocr
import pyocr.builders

tools = pyocr.get_available_tools()
if len(tools) == 0:
    print("No OCR tool found")
    sys.exit(1)
# The tools are returned in the recommended order of usage
tool = tools[0]
print("Will use tool '%s'" % (tool.get_name()))
# Ex: Will use tool 'libtesseract'

langs = tool.get_available_languages()
print("Available languages: %s" % ", ".join(langs))
lang = langs[0]
print("Will use lang '%s'" % (lang))
# Ex: Will use lang 'fra'
# Note that languages are NOT sorted in any way. Please refer
# to the system locale settings for the default language
# to use.
(ocr)> python test.py
Will use tool 'Tesseract (sh)'
Available languages: eng, jpn
Will use lang 'eng'

tesseractが正しく認識されているようだ.

pyocrを使ってみる

img_to_txt.py

from PIL import Image
import sys
sys.path.append('/path/to/dir')

import codecs
import pyocr
import pyocr.builders

tool = pyocr.get_available_tools()[0]
builder = pyocr.builders.TextBuilder()

# 画像から文字列を取得する
txt = tool.image_to_string(
    Image.open('sample2.png'),
    lang='jpn',
    builder=builder
)

# 文字列を書き出す
with codecs.open("result2.txt", 'w', encoding='utf-8') as file_descriptor:
    builder.write_file(file_descriptor, txt)
(ocr)> python img_to_txt.py

画像

sample2.PNG

結果 restult2.txt

ー番になるつていつたよな?
日本ーなるつつつたよな!
ぬるま湯なんかつかつてんじゃねぇよお前! !

まつおかしゆうぞう

アツイ言葉が画像から読み取れた(笑)

今後

とりあえず軽く試してみて思った以上に使えそうなことが分かった. 次は手書きの文字でもちゃんと認識できるのか試したい.