メタデータ管理OSS個人的まとめ
いろいろ触ったのでまとめる。(今後追記予定あり)
TL;DR
データガバナンスツールのOSSにおいて、世間的にデファクトスタンダード的なものも、個人的にこれは!というものも見た限りなかった。 テクニカルメタデータの収集はだいたいどこも同じな一方、ビジネスメタデータ、リネージへの取り組みには顕著な差がある。 お金があるなら有償製品を導入したほうがいいかもしれない。 1 データガバナンスツールは、JIRAみたいなビジネスツールとして捉えるべきという所感。
変更履歴
- 2020-05-18
- Egeriaを追加
前提と関心のある領域
- ベンチャーではなく様々な領域の事業を扱う大きめの企業。
- マルチクラウド、マルチベンダー、マルチプラットフォーム。データストアは数百以上。
- ETL基盤、データ分析基盤はすでに存在し、内製のメタデータ管理ツールもある。
- データ利活用よりもガバナンスを強化したい。
調べたOSS一覧と諸元
プロダクト名 | 開発元 | ライセンス | リポジトリ |
---|---|---|---|
Amundsen | lyft | Apache Licence 2.0 | https://github.com/lyft/amundsen |
datahub | Apache Licence 2.0 | https://github.com/linkedin/datahub | |
metacat | Netflix | Apache Licence 2.0 | https://github.com/Netflix/metacat |
Apache Atlas | Apache Foundation | Apache Licence 2.0 | https://github.com/apache/atlas |
Egeria | ODPi | Apache Licence 2.0 | https://github.com/odpi/egeria |
Amundsen
pros
- マイクロサービス的に画面のサービス、検索のサービス、メタデータのサービスに別れている。
- データ収集処理が抽象化されており再利用しやすそう。
cons
datahub
- もともとWhereHowsというモノリシックなガバナンスツールのリプレイスで生まれた。
- 検索にElasticsearch、メタデータのストアにNeo4jを利用。3
- Push型で、kafkaを通じてCDCを処理できる。
- Java製
pros
- テーブルレベルについてオーナーシップを細かく設定できる。
- DB単位にはできない
- avroでスキーマ管理されているので厳格な運用ができる。
- データの上流下流を定義できる。(リネージ)
- データセットが非推奨であることを明示的に設定可能。-> ライフサイクル管理の概念がある
cons
- 画面からデータを投入することができない。すべてAPI経由。人間の手を入れないほうが管理が楽という割り切りかもしれない。
- elasticsearchのバージョンが5系。ESの最新バージョンは8なので結構な遅れがある。(ES自体に破壊的変更が入ってる)
metacat
(とっつきが悪くあまり調べられなかった…)
Apache Atlas
- もともとHadoop周りのデータガバナンスを主眼にしたものが汎用になったらしい。
- 調べたOSSの中では一番機能が豊富。
- 検索はSolrかElasticsearch、メタデータストアはHBaseかCassandraが選べる。
- Java製
- IBMの研究者が "Apache Atlas is the lead contender as the choice of such an open source project." と言ってる。
pros
- 強力な型システムにより、どんなミドルウェアにも対応できる。
- ビジネスメタデータも型システムで表現すれば格納できる。
- ClassificationとLineageを組み合わせて、Classificationの伝播を表現できる。
- あるテーブルにPII(個人を識別できる情報)が入っるとフラグづけすると、 それをETLした先のテーブルにも自動的にPIIのフラグが設定される。
- Lineageが強そう。
cons
- 型システムが強力だが自分で設計する必要がある。概念の学習コストも高い。
- その汎用性ゆえか、画面の動線がいまいちで、すべての情報がフラットに見える。
- APIが公開されているので自分たちで実装したほうがよいと思った。
Egeria
- Open Data Platform initiative(ODPi)が開発する、メタデータ交換のための規格(The open metadata type system)および、その実装。
- ODPiがベンダーツール間のオープンメタデータ交換を促進する新しい Egeria Conformance Programを発表
-
オープンなビッグデータ エコシステムの簡素化、共有、および開発のための標準規格になることを目指しています。
- the open metadata type systemは、メタデータを7つのAreaに分類整理し運用しようというもの?
- バックエンドに前述のApache Atlasを採用してるぽい。
- Java製。
- リリースバージョンは1.7になってる。
pros
- 2018年から開発が始まり勢いは活発っぽい。
- the open metadata type systemの概念は有用そう。
- ガイドラインがやたら豊富。チュートリアルも用意されてるので学習しやすい(か?)
- マイクロサービスを意識した構成。
cons
- コンポーネントがやたら多い。
- webuiが見当たらない? どうやって動かすんだこれ。
まとめ
いずれのOSSも成熟してるとは言い難い。 metacatの記事でコメントされていたが、基本的に各社のビジネスにフォーカスした必要な分だけの機能となっており、再利用は難しい。 とりわけ、対応データソースにはムラがあり、実際の収集をする実装が公開されていないため、結局自分たちで実装する必要がある。また、ガバナンスの観点ではほぼカバーされておらず、いわゆる分析用途がフォーカスされている感がある。
メタデータストア部分と、データグラフ構築は再利用できるかもしれない。 APIを利用したWebUI、ワークフローなどはスクラッチするべきか。 いずれも各プロダクトの独自スキーマ定義を理解する必要があり、学習コストは高い。
未調査,非公開または有償製品
プロダクト名 | 開発元 | 有償製品 | 紹介記事 |
---|---|---|---|
Dataportal | AirBnb | Democratizing Data at Airbnb - Airbnb Engineering & Data Science - Medium | |
Alation | Alation | o | https://www.alation.com/ |
Databook | Uber | https://eng.uber.com/databook/ | |
AWS Glue DataCatalog | Amazon | o | https://docs.aws.amazon.com/ja_jp/glue/latest/dg/populate-data-catalog.html |
GCP Data Catalog | o | https://cloud.google.com/data-catalog | |
Collibra | Collibra | o | https://www.collibra.com/ |
Ground | UC Berkeley’s RISE Lab | http://www.ground-context.org/ |
Appendix
- Amundsen — Lyft’s data discovery & metadata engine - Lyft Engineering
- メタデータのABCについての言及
- Compare Alation vs. Collibra in Metadata Management Solutions | Gartner Peer Insights
- Data Dictionary: a how to and best practices - Carl Anderson - Medium
- How LinkedIn, Uber, Lyft, Airbnb and Netflix are Solving Data Management and Discovery for Machine Learning Solutions
- Data Lineage and Metadata Management: An Innovative Approach - DATAVERSITY
[^4] 2020-05-18 追記
Oracleに接続可能ななるべく小さいpython実行環境のDocker imageをビルドする
今回のお題
接続先がOracleなシステムでPythonが実行可能なコンテナイメージを作りたい。
小さいイメージは正義。
tl;dr
- Oracle公式のイメージを利用する。
- Docker multi stage buildを活用する。
- alpineは苦行。
完成形は最後のほうにあります。
要件
MUST
- 接続先は既存システムのOracle。変更不可。
- 実行環境はパブリッククラウド内。
- コードはgit管理。
- 開発言語は任意。(今回はData Classなどの機能を使ってみたかったのでpython3.7とした)
WANT
- 環境構築は手軽にやりたい。
- デプロイは高速にしたい。
構築までの道のり
まずは素朴に
ローカル確認
Dockernizeする前に、ローカルでの動作を確認します。
pythonからOracleに接続するための公式のライブラリは cx_Oracleです。 *1*2
pipenvで入れましょう。*3
$ pipenv install $ pipenv install cx_Oracle $ cat Pipfile [[source]] name = "pypi" url = "https://pypi.org/simple" verify_ssl = true [dev-packages] [packages] cx-oracle = "*" [requires] python_version = "3.7"
Oracleインスタンスの構築
接続先のOracleインスタンスもDockerで立てておきます。*4
macOSでOracle Database使いたい - Qiita
上記に従い本体のバイナリを入手します。バージョンは 19.3.0 Standard Edition 2 としました。 *5
$ git clone git@github.com:oracle/docker-images.git $ cd docker-images/OracleDatabase/SingleInstance/dockerfiles $ mv /your/download/path/to/LINUX.X64_193000_db_home.zip 19.3.0/ $ ./buildDockerImage.sh -i -s -v 19.3.0 # 完了まで10分ぐらいかかる
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE oracle/database 19.3.0-se2 91da406c90ef 23 seconds ago 6.65GB oraclelinux 7-slim 874477adb545 6 weeks ago 118MB
無事イメージが作成されました。
とくにカスタマイズはせず起動します。
$ mkdir -p $HOME/tmp/oradata $ docker run --name docker_oracle \ -p 1521:1521 -p 5500:5500 \ -v $HOME/tmp/oradata:/opt/oracle/oradata \ oracle/database:19.3.0-se2 # ..snip.. ######################### DATABASE IS READY TO USE! #########################
起動しました。コンテナが立ち上がっていることが確認できます。
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 6c7d9db8a2bb oracle/database:19.3.0-se2 "/bin/sh -c 'exec $O…" 16 minutes ago Up 16 minutes (healthy) 0.0.0.0:1521->1521/tcp, 0.0.0.0:5500->5500/tcp docker_oracle
せっかくなのでコンテナ内からOracleに接続してみましょう。
$ docker exec -it docker_oracle /bin/sh $ sqlplus /nolog $ SQL> connect sys/cO11MX9C5Bk=1@ORCLCDB as sysdba Connected. SQL> select SYSDATE from DUAL; SYSDATE --------- 26-SEP-19 SQL>
実行
さて先程構築したOracleに接続してSYSDATEをSELECTする単純なpythonを書きます。
#!/usr/bin/env python3 import cx_Oracle print(cx_Oracle.clientversion()) # パスワードは勝手に払い出される conn = cx_Oracle.connect(user='sys', password='cO11MX9C5Bk=1', dsn='localhost:1521/ORCLCDB', mode=cx_Oracle.SYSDBA) curs = conn.cursor() curs.execute('select SYSDATE from DUAL') row = curs.fetchone() print(f'Hello Oracle ! {row[0]}')
このまま実行するとクライアントライブラリがないエラーがでます。
$ pipenv run ./main.py Traceback (most recent call last): File "./main.py", line 5, in <module> print(cx_Oracle.clientversion()) cx_Oracle.DatabaseError: DPI-1047: Cannot locate a 64-bit Oracle Client library: "dlopen(libclntsh.dylib, 1): image not found". See https://oracle.github.io/odpi/doc/installation.html#macos for help
Oracleの公式*7からライブラリをダウンロードして任意の場所に展開します。
今回は $HOME/opt/oracle/instantclient_19_3
としました。 LD_LIBRARY_PATH
も通しておきましょう。
$ pipenv run ./main.py (19, 3, 0, 0, 0) Hello Oracle ! 2019-09-26 02:16:43
無事動きました。ここまで前フリでした。
Dockerfileを書く
ようやくDockerに手を付けます。
上記のpython環境をDockerfileで記述します。
まずはpython3.7およびpipenvの入ったイメージを作成します。
ベースイメージは python:3.7-slim-buster
としました。*8*9
0版
## # Building runtime image. ## FROM python:3.7-slim-buster WORKDIR /usr/local/app ADD Pipfile . ADD Pipfile.lock . ADD main.py . RUN apt-get update -y \ && apt-get -y install \ && pip install --no-cache-dir pipenv \ && pipenv install --deploy ENTRYPOINT ["pipenv", "run"]
$ docker build . -t cx_oracle-slim # 出力は省略 $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE cx_oracle-slim latest e7b3dc4bafad About a minute ago 239MB
この時点でのイメージサイズは 239MB です。
課題と対応
さてこの状態ではinstantclientが入っていないため、当然実行時にエラーとなります。
docker run -it cx_oracle-slim ./main.py Traceback (most recent call last): File "./main.py", line 5, in <module> print(cx_Oracle.clientversion()) cx_Oracle.DatabaseError: DPI-1047: Cannot locate a 64-bit Oracle Client library: "libclntsh.so: cannot open shared object file: No such file or directory". See https://oracle.github.io/odpi/doc/installation.html#linux for help
ではDockerfileでinstantclientを取得できるようにしたいところですが、Oracleのリソース取得にはライセンスの同意が必要です。
つまり、wgetやcurlでの取得が面倒です。
対処としては以下のような方策が考えられます。
instantclientのzipをgitにコミットしておきイメージビルド時に展開する。一番手軽ではありますが、gitに大きなバイナリ*10をコミットするのは望ましくありません。
instantclientのzipをコードとは異なるgitリポジトリにコミットしておく。コードとは異なるリポジトリにzipのみをコミットしておき、ビルド時にcloneしておく方策です。 コードのリポジトリはキレイになりますが、どこにリポジトリを作るのか、バージョンアップはどうするのか、という将来の課題を残します。*11
Oracle公式イメージをベースにしてpython環境を構築する。Oracle公式がgithubで公開しているイメージ
oraclelinux:x-slim
*12を利用すると、ライセンスの同意などを回避できる模様。素のイメージは368MBです。
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE oracle/instantclient 19 bdf41080d895 4 seconds ago 368MB
1版
運用保守を鑑みて、1,2は捨て、3の方針としました。
このときのDockerfileは以下です。
FROM oraclelinux:7-slim ARG release=19 ARG update=3 RUN yum -y install oracle-release-el7 \ && yum-config-manager --enable ol7_oracle_instantclient \ && yum -y install \ oracle-instantclient${release}.${update}-basic \ oracle-instantclient${release}.${update}-devel \ && rm -rf /var/cache/yum WORKDIR /usr/local/app ADD Pipfile . ADD Pipfile.lock . ENV LC_ALL=en_US.UTF-8 RUN yum update -y \ && yum -y install \ libaio \ python36 \ && rm -rf /var/cache/yum/* \ && yum clean all \ && pip3 install --no-cache-dir pipenv \ && pipenv install --deploy ADD main.py . ENTRYPOINT ["pipenv", "run"]
Dockerで立てたOracleサーバと通信するためにdocker run時にlinkしてやる必要があります。
main.pyの接続先名を localhost
から ora_server
に置き換えたのち実行します。
$ docker run -it --link docker_oracle:ora_server cx_oracle-slim ./main.py (19, 3, 0, 0, 0) Hello Oracle ! 2019-09-26 04:51:55
無事当初の目的である、Docker内からOracleと通信が可能になりました。
課題
しかしながら、oraclelinux自体がCentOSをベースにしているようで、yumでは2019-09-26現在python37は入りません。
python36を妥協して使うにしても出来上がったイメージのサイズは 685MB となかなかなサイズとなりました。
pythonのライブラリが増えてきた時を考えるともう少しダイエットさせたい気持ちがあります。
軽量化の取り組み
さてようやく本題です。
満たしたい事項を箇条書きにします。
- python37を使いたい
- instantclientのバイナリを自前で管理したくない
- イメージサイズは小さくしたい
上記を解決するために、Dockerのmulti-stage buildsを利用します。
疲れてきたのでいきなり最終的なDockerを示します。
最終版
## # Buidling intermediate image. # instantclientのみほしいが、wgetなどではユーザ認証が必要になるため、公式のイメージから拝借する ## FROM oraclelinux:7-slim AS oracle-client ARG release=19 ARG update=3 RUN yum -y install \ oracle-release-el7 \ && yum-config-manager --enable ol7_oracle_instantclient \ && yum -y install \ oracle-instantclient${release}.${update}-basic \ oracle-instantclient${release}.${update}-devel \ && rm -rf /var/cache/yum ## # Building runtime image. ## FROM python:3.7-slim-buster COPY --from=oracle-client /usr/lib/oracle /usr/lib/oracle ## XXX: バージョン指定がバラけてる ARG release=19 ARG update=3 ENV LD_LIBRARY_PATH=/usr/lib/oracle/${release}.${update}/client64/lib WORKDIR /usr/local/app ADD Pipfile . ADD Pipfile.lock . RUN apt-get update -y \ && apt-get -y install \ --no-install-recommends \ libaio1 \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* \ && pip install --no-cache-dir pipenv\ && pipenv install --system --deploy \ && pip uninstall -y \ pipenv \ virtualenv-clone \ virtualenv ADD main.py . ENTRYPOINT ["./main.py"]
最終版のイメージサイズは426MBと、第1版の40%減となりました。
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
cx_oracle-slim latest bd5a907578db 6 seconds ago 426MB
トピック
以下最終版に至るまでのトピックです。
まず oraclelinux:7-slim
から実行に必要な必要な資材を特定しました。
oracle-instantclient${release}.${update}-xxx
は /usr/lib/oracle
配下にyumでインストールされるようでした。
oraclelinux:7-slim
を AS oracle-client
としておき、こちらを COPY --from=/usr/lib/oracle
でpython37のベースイメージに持ってきます。
この時点でイメージサイズは459MBと、第1版よりも200MB以上小さくなりました。また、python3.7が使える環境です。
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
cx_oracle-slim latest 9377e5551843 6 seconds ago 459MB
/usr/lib/oracle
配下のファイルは全体で225MB、cx_Oracleにはほぼ必須のもので、削れるにしてもojdbc8.jarなどの4MBでしたのでDockerfileの可読性を踏まえてこれ以上は追求しないこととしました。
次にapt, pipenv周りを削ります。
apt-getでは --no-install-recommends
を付与することで余計なパッケージのインストールを抑止できますが、今回は効果がありませんでした。*13
pipenvは仮想環境を構築しますが、今回はDocker内のためシステムにインストールされるPythonは単一のバージョンです。したがって仮想環境用のバイナリをコピーする必要はありません。
--system
を付与することで、pipenvに書かれたパッケージをsystemにインストールしてくれます。 さらに、pipenvはinstall実行後に不要となりますので、pipからも削除可能です。これで更に30MB削減です。*14
以上です。
*1:https://oracle.github.io/python-cx_Oracle/
*2:プロダクションではSQLAlchemyを経由するのが一般的だと思うが今回は使わない。
*3:pipenvは各自入れておいてください。
*4:Oracle Cloudで楽に済ませようとしたらユーザ登録でカード決済がなぜか通らなくて泣いた。
*5:https://www.oracle.com/database/technologies/oracle-database-software-downloads.html
*6:小一時間かかった…。
*7:Instant Client for macOS (Intel x86)
*8:The best Docker base image for your Python application (July 2019)
*9:当初 `python:3.7-alpine` で試みましたがglibc周りで断念。
*10:zipは75MB程度ある
*11:経験として誰も管理せず腐る傾向にある。
*12:https://github.com/oracle/docker-images/blob/master/OracleInstantClient/dockerfiles/19/Dockerfile
*13:今後必要なパッケージが増える際に効いてくるはずです
夫が転職して1年が経ちました
新卒で入社したSIerから今の会社に転職して2016-04-01付けでちょうど1年になる。
そもそもなんで転職したくなったのか、転職していいことあったのか、
期待した結果が得られたのか、雑にまとめる。*1
転職理由
- 受託開発における、作ったら作りっぱなしの商習慣に不満があった
- 常駐先から強制される旧態依然とした開発環境でフラストレーションがあった
- ユーザに喜ばれているという実感が得たかった
転職して得たかったもの
- 自分の作ったもの/価値を提供しているものを継続的に良くしていっているという実感
- 自分の学びを業務にフィードバックする知的欲求の充足
- 世の中の誰かの課題やニーズを解決できているという満足感
実際どうだったか
だいたいOK
詳しく
- すでに構築されているそこそこ複雑なシステムの中で日々良くしていくというサイクルに組み込まれるのは楽。
- 既存システムの制約を超えない"プログラミング"の範囲では色々試せるし。
- ただし、一旦運用に乗っているものを変えることはやはり、調整コスト、そこまでしていいことあるの?、といった説明責任が生じる。
- 部署がたくさんあって、フロントエンドとバックエンド間の意識の断絶を感じることが最近多い。
- これは自分が信頼や既存システムの知識を獲得していくことでうまく立ち回るしかないのかと思う。
- 自分自身の粗忽さの影響で迷惑かけることがまだまだあるのでなんとかしたい
- 尊敬できる人は多いので、勝手を言わせてもらえば辞めないで欲しい
もっとちゃんと書きたかったけど酔っ払ってるので雑に終わる。
*1:退職エントリはカッコ悪いという風潮はどこから来るのだろうね?
TemplateインターフェースクラスでStrategyパターンを実現する
実装メモ
複数の条件をパスしたレコードだけ抽出するみたいな用途を想定。
#include <iostream> #include <vector> #include <string> /** * Template Interface class */ template <typename T> class Filter{ public: virtual bool filter(const T &v) const = 0; virtual ~Filter(){} }; /** * Templateクラスの継承は親クラスに<>をつける */ class LengthFilter : public Filter<std::string> { public: virtual bool filter(const std::string &v) const { std::cout << "LengthFilter:4" << std::endl; return v.length() > 4; } }; /** * コンストラクタ、メンバ変数をもてるのは通常のclassと変わらない */ class StringFilter : public Filter<std::string>{ const std::string matcher; public: StringFilter(const std::string &s) : matcher(s){} virtual bool filter(const std::string &v) const { std::cout << "StringFilter:" << matcher << ", target:" << v << std::endl; return v == matcher; } }; /** * 異なる型を具体化した継承 */ class IntegerFilter : public Filter<int> { public: virtual bool filter(const int &i) const { std::cout << "IntegerFilter:4" << std::endl; return i > 4; } }; int main() { // std::stringで具体化した子クラスを格納するコンテナ std::vector<std::shared_ptr<Filter<std::string> > > filters; // 型パラメータが同一なら同じコンテナに入れられる filters.emplace_back(new LengthFilter()); filters.emplace_back(new StringFilter("Hoge")); filters.emplace_back(new StringFilter("Hello")); // 型パラメータが異なればコンパイルエラー //filters.emplace_back(new IntegerFilter()); std::vector<std::string> v; v.push_back("Hello"); v.push_back("Hoge"); for(const auto &s : v){ std::cout << "target :"<< s << std::endl; for(const auto &f : filters) { // 異なるFilterクラスをそれぞれ適用する処理 if (f->filter(s)) { std::cout << "*** Match ***" << std::endl; } else { std::cout << "*** Unmatch ***" << std::endl; } } std::cout << std::endl; } return 0; }
ICUでShift-JIS, EUC-JP, UTF-8の相互変換
コード
ヘッダ
#ifndef string_encoder_hpp #define string_encoder_hpp #include <string> namespace encoding { class Encoder{ public: // From EUC-JP static std::string EucToSjis(const std::string &value); static std::string EucToUtf8(const std::string &value); // From Shift-JIS static std::string SjisToEuc(const std::string &value); static std::string SjisToUtf8(const std::string &value); // From UTF-8 static std::string Utf8ToEuc(const std::string &value); static std::string Utf8ToSjis(const std::string &value); }; } #endif /* string_encoder_hpp */
実装
#include <vector> #include <unicode/unistr.h> #include "string_encoder.hpp" namespace encoding { namespace internal { namespace encode_name { //! EUC-JP static const std::string kEucJp = "euc-jp"; //! Shift-JIS static const std::string kShiftJis = "shift-jis"; //! UTF8 static const std::string kUtf8 = "utf8"; } std::string encode(const std::string &value, const std::string &from, const std::string &to){ icu::UnicodeString src(value.c_str(), from.c_str()); // 出力バッファにnullptrを渡し変換後のバイト数が取得する const int length = src.extract(0, src.length(), nullptr, to.c_str()); // 変換 std::vector<char> result(length + 1); src.extract(0, src.length(), &result[0], to.c_str()); return std::move(std::string(result.begin(), result.end() - 1)); } } //! From EUC-JP To sjis std::string Encoder::EucToSjis(const std::string &value){ return std::move(internal::encode(value, internal::encode_name::kEucJp, internal::encode_name::kShiftJis)); } //! From EUC-JP To UTF-8 std::string Encoder::EucToUtf8(const std::string &value){ return std::move(internal::encode(value, internal::encode_name::kEucJp, internal::encode_name::kUtf8)); } //! From sjis To EUC-JP std::string Encoder::SjisToEuc(const std::string &value){ return std::move(internal::encode(value, internal::encode_name::kShiftJis, internal::encode_name::kEucJp)); } //! From sjis To UTF-8 std::string Encoder::SjisToUtf8(const std::string &value){ return std::move(internal::encode(value, internal::encode_name::kShiftJis, internal::encode_name::kUtf8)); } //! From UTF-8 To EUC-JP std::string Encoder::Utf8ToEuc(const std::string &value){ return std::move(internal::encode(value, internal::encode_name::kUtf8, internal::encode_name::kEucJp)); } //! From UTF-8 To sjis std::string Encoder::Utf8ToSjis(const std::string &value){ return std::move(internal::encode(value, internal::encode_name::kUtf8, internal::encode_name::kShiftJis)); } }
動作確認
#include <iostream> #include <string> #include <vector> #include "string_encoder.hpp" template <typename T> std::string test(const T &expected, const T &actual){ return (expected == actual ? "Match" : "Unmatch!"); } int main(int argc, const char * argv[]) { const std::string utf8_string = "aこれはウにこーど"; std::cout << utf8_string << " is utf8 string, length:" << utf8_string.length() << std::endl; std::cout << std::endl; // utf8 -> sjis const std::string sjis_string = encoding::Encoder::Utf8ToSjis(utf8_string); // binary of "aこれはウにこーど" by sjis const std::string sjis_dump = { 'a', '\x82', '\xb1', // "こ" '\x82', '\xea', // "れ" '\x82', '\xcd', // "は" '\xb3', // "ウ" '\x82', '\xc9', // "に" '\x82', '\xb1', // "こ" '\x81', '\x5b', // "ー" '\x82', '\xc7' // "ど" }; std::cout << "test sjis is " << test(sjis_dump, sjis_string) << std::endl; // sjis -> eucjp const std::string euc_string = encoding::Encoder::SjisToEuc(sjis_string); // binary of "aこれはウにこーど" by eucjp const std::string euc_dump = { 'a', '\xa4', '\xb3', // "こ" '\xa4', '\xec', // "れ" '\xa4', '\xcf', // "は" '\x8e', '\xb3', // "ウ" '\xa4', '\xcb', // "に" '\xa4', '\xb3', // "こ" '\xa1', '\xbc', // "ー" '\xa4', '\xc9' // "ど" }; std::cout << "test euc is " << test(euc_dump, euc_string) << std::endl; // eucjp -> utf8 const std::string return_utf8 = encoding::Encoder::EucToUtf8(euc_string); // check std::cout << "test utf8 is " << test(utf8_string, return_utf8) << std::endl; // 機種依存文字 const std::string euc_contains_machine_dependent_char_string = { 'a', '\xa4', '\xb3', // "こ" '\xa4', '\xec', // "れ" '\xa4', '\xcf', // "は" '\xad', '\xc0', '\x0a', // "㍉" '\xa4', '\xcb', // "に" '\xa4', '\xb3', // "こ" '\xa1', '\xbc', // "ー" '\xa4', '\xc9' // "ど" }; const std::string ㍉ = encoding::Encoder::EucToUtf8(euc_contains_machine_dependent_char_string); std::cout << "test ㍉ is " << test(euc_contains_machine_dependent_char_string, encoding::Encoder::Utf8ToEuc(㍉)) << std::endl; // 絵文字はsjisに含まれていないので変換できない const std::string 🍣 = { '\x3f', '\x0a' }; const std::string sushi_string = "🍣"; std::cout << "test 🍣 is " << test(sushi_string, encoding::Encoder::SjisToUtf8(🍣)) << std::endl; }
結果
aこれはウにこーど is utf8 string, length:25 test sjis is Match test euc is Match test utf8 is Match test ㍉ is Match test 🍣 is Unmatch!
参考文献
自作クラスのコンテナをstd::copyでバイナリとしてファイル出力する
#include <iostream> #include <sstream> #include <fstream> #include <vector> #include <numeric> class MyClass { private: class BitField { public: unsigned int a_: 10; unsigned int b_: 8; unsigned int c_: 12; unsigned int d_: 2; BitField() : a_(0), b_(0), c_(0), d_(0){} BitField(int a,int b, int c, int d) : a_(a), b_(b), c_(c), d_(d){}; std::string to_string() const { std::stringstream ss; ss << a_ << "," << b_ << "," << c_ << "," << d_; return ss.str(); } } bf_; std::string str_; public: MyClass() : str_(""){}; MyClass(int a,int b, int c, int d, const std::string &str) : bf_(a,b,c,d), str_(str){}; std::string to_string() const { std::stringstream ss; ss << bf_.to_string() << "," << str_; return ss.str(); } uint32_t get_dumpsize() const { uint32_t sum = 0; sum += sizeof(bf_); sum += str_.length(); return sum; } friend std::ostream &operator<<(std::ostream &os, const MyClass &my); }; // std::copyを使用するにはoperator<<をオーバロードする std::ostream &operator<<(std::ostream &os, const MyClass &my){ os.put(my.bf_.a_); os.put(my.bf_.b_); os.put(my.bf_.c_); os.put(my.bf_.d_); os.write(my.str_.c_str(), my.str_.length()); return os; } int main(int argc, const char * argv[]) { std::vector<MyClass> my_class_vector; for(int i = 0; i < 10; i++){ my_class_vector.emplace_back(i + 1, i * 100, i * 1000, i + 3, "end!"); } std::ofstream ofs("/Users/usadamasa/Desktop/binaryfile.bin", std::ios::binary); if(!ofs.is_open()){ std::cerr << "File Open Error" << std::endl; return 1; } // stringがメンバ変数にあるため書き込みサイズとクラスの確保しているサイズは異なる std::cout << "sizeof MyClass :" << sizeof(MyClass) << std::endl; std::cout << "write size :" << my_class_vector.at(0).get_dumpsize() << std::endl; for(const auto &v : my_class_vector){ std::cout << v.to_string() << std::endl; } std::copy(my_class_vector.begin(), my_class_vector.end(), std::ostream_iterator<MyClass>(ofs)); }
ファイル出力結果(2進数)
| a | b | c | d| str | +--------+--------+---------------+--+-----------------------------------+ |00000001|00000000|00000000 000000|11|01100101 01101110 01100100 00100001| |00000010|01100100|11101000 000000|00|01100101 01101110 01100100 00100001| |00000011|11001000|11010000 000000|01|01100101 01101110 01100100 00100001| |00000100|00101100|10111000 000000|10|01100101 01101110 01100100 00100001| |00000101|10010000|10100000 000000|11|01100101 01101110 01100100 00100001| |00000110|11110100|10001000 000000|00|01100101 01101110 01100100 00100001| |00000111|01011000|01110000 000000|01|01100101 01101110 01100100 00100001| |00001000|10111100|01011000 000000|10|01100101 01101110 01100100 00100001| |00001001|00100000|01000000 000000|11|01100101 01101110 01100100 00100001| |00001010|10000100|00101000 000000|00|01100101 01101110 01100100 00100001|
Boost::Geometryでmultilinestringをclipping
図形の上に引かれた線を、3*3などで矩形分割する必要があったため調査メモ。
環境
サンプルコード
#include <iostream> #include <vector> #include <boost/geometry/geometry.hpp> #include <boost/geometry/geometries/linestring.hpp> #include <boost/geometry/geometries/multi_linestring.hpp> #include <boost/geometry/geometries/point_xy.hpp> int main(int argc, const char * argv[]) { namespace bg = boost::geometry; using point_d2 = bg::model::d2::point_xy<double>; using linestring_d2 = bg::model::linestring<point_d2>; linestring_d2 ls; ls.push_back(bg::make<point_d2>(1.1,1.1)); point_d2 lp; bg::assign_values(lp, 2.5, 2.1); bg::append(ls, lp); // print 2 points "((1.1, 1.1), (2.5, 2.1))" std::cout << bg::dsv(ls) << std::endl; // sqrt{(2.5-1.1)^2 + (2.1-1.1)^2} = 1.72… std::cout << "distance " << bg::distance(ls.at(0), ls.at(1)) << std::endl; // boxは境界点を持ちlinestringとの計算に使える using box_d2 = bg::model::box<point_d2>; box_d2 b; bg::envelope(ls, b); std::cout << bg::dsv(b) << std::endl; // distance equal to length std::cout << "length " << bg::length(ls) << std::endl; std::cout << "number of points 1: " << ls.size() << std::endl; std::cout << "number of points 2: " << boost::size(ls) << std::endl; std::cout << "number of points 3: " << bg::num_points(ls) << std::endl; // 点から線への垂線の長さ point_d2 p(1.9, 1.2); // 0.38 std::cout << "distance of " << bg::dsv(p) << " to line: " << bg::distance(p, ls) << std::endl; // linestringはsegmentと見做して計算することもできる double d = bg::distance(p, bg::model::segment<point_d2>(ls.front(), ls.back())); std::cout << "distance " << d << std::endl; // linestringは平滑化できる linestring_d2 ls_redundancied = { {3,3}, {3.8, 4}, {6,6}, {4,9}, {5,8}, {7,7} }; linestring_d2 ls_simplified; bg::simplify(ls_redundancied, ls_simplified, 0.5); std::cout << "not simplified :" << bg::dsv(ls_redundancied) << std::endl; // removed (3.8, 4) and (5,8) std::cout << "simplified :" << bg::dsv(ls_simplified) << std::endl; // // linestringのclipping // box_d2 cb(point_d2(1.2, 1.2), point_d2(4.5, 2.5)); std::vector<linestring_d2> clipped; ls.push_back(point_d2(1.3,1.3)); // 矩形, input, output bg::intersection(cb, ls, clipped); std::cout << "before clipped : " << bg::wkt(ls) << std::endl; for(const auto &v : clipped) { // 複数の線ができるが、multilinestringとしては解釈してもらえないのでfor文で確認 std::cout << bg::wkt(v) << std::endl; } // // multilinestringのclipping // using multilinestring = boost::geometry::model::multi_linestring<linestring_d2>; multilinestring mls; linestring_d2 ls_1 = { {1,9}, {2.8, 7}, {3,6}, {4,5}, {5,4}, {6,2} }; mls.push_back(ls_1); linestring_d2 ls_2 = { {1,2}, {5.8, 3}, {5.9,4}, {6.0,5}, {6.1,6}, {9,11} }; mls.push_back(ls_2); box_d2 cb_2(point_d2(2,3), point_d2(6, 9)); multilinestring mls_clipped; bg::intersection(cb_2, mls, mls_clipped); std::cout << "before mls(A) : " << bg::wkt(mls) << std::endl; std::cout << "clipping box(B) : " << bg::wkt(cb_2) << std::endl; std::cout << "after mls(C) : " << bg::wkt(mls_clipped) << std::endl; // // multi_pointのclipping // using bg_multipoint = boost::geometry::model::multi_point<point_d2>; bg_multipoint mp; mp.push_back(point_d2(1.4, 1.4)); mp.push_back(point_d2(2.0, 2.0)); mp.push_back(point_d2(2.9, 2.9)); mp.push_back(point_d2(3.0, 3.0)); bg_multipoint clipped_mp; box_d2 b2(point_d2(1.5,1.5), point_d2(3.0, 3.0)); for(const auto &p : mp){ // intersectionではなくintersectsで1点ずつ確認する必要があるぽい if(bg::intersects(b2, p)){ clipped_mp.push_back(p); } } // MULTIPOINT((1.4 1.4),(2 2),(2.9 2.9),(3 3)) std::cout << bg::wkt(mp) << std::endl; // MULTIPOINT((2 2),(2.9 2.9),(3 3)) std::cout << bg::wkt(clipped_mp) << std::endl; }
目で見て確認*1
before mls(A)
MULTILINESTRING((1 9,2.8 7,3 6,4 5,5 4,6 2),(1 2,5.8 3,5.9 4,6 5,6.1 6,9 11 ))
clipping box(B)
POLYGON((2 3,2 9,6 9,6 3,2 3))
after mls(C)
MULTILINESTRING((2 7.88889,2.8 7,3 6,4 5,5 4,5.5 3),(5.8 3,5.9 4,6 5))
線と矩形の接点にちゃんと点がある。
なお、矩形から完全に外れたlinestringは無視されるらしい。
その他
今回はC++かつ既存コードがboostを使用していてためBoost::Geometryを使ったが、他にもいろいろあるぽい。