Photoruction工事中!

Photoructionの開発ブログです!

建設テックのエンジニアって特別な何かがあるの?

建設テックのエンジニアって特別な何かがあるの?

はじめましての人ははじめまして Androidエンジニアの久木田です。

今回は建設テックで施工管理サービスを提供しているエンジニアって他のIT企業とどれだけ違うのかについての記事です。 (ちなみに、多分他の会社でも当てはまるだろうと思って書いていますが、Photoructionでの経験を元にしています)

TL;DR

めちゃくちゃ特別なことはなく、建設業界の知識や業務理解は当然必要ですが、toB SaaSの企業って多分大体こんなもんだよねって感じです。

特別なこと

どんな特別なことがあるんだろうと思って記事を開いた人が多いんじゃないかと思うので、最初に特別なことについてまとめておきます。

  1. PDFをよく扱う
  2. オフラインでも業務アプリとして問題なく機能する必要がある
  3. 写真の中に黒板をオーバーレイする

PDFをよく扱う

図面などを扱う機会が多いためなのと、日報などの報告書がPDFで出力されるためです。 どうしても外部ライブラリに頼ることになるので辛みが出てくる場面ではあります。

オフラインでも業務アプリとして問題なく機能する必要がある

他にはあまりそういうアプリってないなと思っています。(オンラインである必要のないアプリというのはありますが) そもそもどうしてオフラインでの動作が必要かというと、わかりやすいのが山中での土木工事だとモバイルネットワークの圏外なことは想像に難くないと思います。 また、高層ビルの建設時などはモバイルネットワークのアンテナよりも上に行くと電波が入らなくなっていきます。 そういった場面でも問題なく動作可能であることが重要であるため、建設テックのアプリではオフラインで動作するようになっています。

写真の中に黒板をオーバーレイする

写真内に情報が表示できないといけないことが多くあるので、写真の一部に電子小黒板というものを表示できるようになっています。 カメラを使うアプリは色々ありますが、オーバーレイの機能があるのはリッチなカメラアプリくらいかなと思うので珍しい機能かなと思います。

正直、これくらいですかね、、、

toCtoB SaaSの違い

建設テックで特別なことはそれくらいなんですが、そもそもtoB SaaSと、toCサービスを開発するのに大きな違いがあります。 ただし、弊社がVerticalなtoBなのでHorizontalな企業とは違いがある可能性があります。

  1. 提供先の業務知識が必要
  2. ドッグフーディングできない

提供先の業務知識が必要

これは言わずもがなだとは思いますが、知識量や解像度が高くないとこの機能だれがいつ使うんだという想像すらできないし、企画時により良い提案もできないのでとにかくここは豊富であるに越したことないです。 (僕は入社後にドメイン出身者が家を建てる一通りの工程を解説してもらったりしました)

ドッグフーディングできない

他の産業向けのサービスなので、普段使うことは絶対にないし、業務で使うにも想定されてる使い方とは違うので改善点の洗い出しがあまりできません。 なので、ユーザーインタビューがとても重要です。また、ドメイン出身者のいわゆるドメインエキスパートの存在は重要です。

他のWeb企業と変わらないこと

色々違いを書きましたが、多くは普通のWeb企業と変わらないです。 使用技術もAWSPHP、アプリはネイティブで作成されています。開発フローもスクラムですし、GitHubやSlackも使ってます。

まとめ

特に大事なのはやっぱり業界理解なのかなと思っています。 ここをおろそかにするといい製品を作れないし、使われていてもなんで使われるのかとかもよくわからないままなのかなと思います。 また、今回は 建設テックでの記事ですが、他の業界やHorizontal SaaSの企業だとどうなのかとかも気になりますね!

株式会社フォトラクションでは一緒に働く仲間を募集しています

リリースノートの作成で楽をする

はじめに

こんにちは、Photoructionでモバイルチームのテックリードをしている數田です。

Photoruction Advent Calendar 2022の25日目の記事になります。

現在フォトラクションのモバイルアプリは基本的に毎週水曜日にリリースしています、リリース回数を増やすと、手動でリリースノートを作成していると漏れが発生する可能性が高くなります。

リリースノートを自動で作成する手段を検討すると、GitHubAutomatically generated release notesが追加されたので利用するようにしました。

GitHub ReleaseページでGenerate releasenotesを実行すると以下のようになります。

## What's Changed
* [JIRA-1] 新機能1 by {GitHub User} in {Github repo}/pull/101
* [JIRA-2] 新機能2 by {GitHub User} in {Github repo}/pull/102
* [JIRA-3] バグ改修1 by {GitHub User} in {Github repo}/pull/103

## New Contributors
* {GitHub User} made their first contribution in {Github repo}/pull/1

**Full Changelog**: {GitHub repo}/commits/1.0.1

What's ChangedにマージされたPRが全て出るので、変更の内容を分類したくなります。

**Automatically generated release notes ではPull Requestにラベルをつけると分類できるようになります。

Pull Requestに自動でラベルをつける方法

リリースノートに機能追加、バグ改修、リファクタリングが分類して書いてあると影響範囲がわかりやすそうです。

Pull Requestに手動でラベルを付与する運用にすると漏れが発生するので、自動で付与する方法を考えます。

GitHub Actionsの PR Labeler を利用するとブランチ名を元にPRのラベルを自動で付与することができます。ブランチのプレフィックスにそれぞれがわかるように設定します。

Feature: ['feature/*']
bug: ['bugs/*']
refactor: ['refactor/*']

課題管理システム

リリースノートから課題管理のチケットにリンクが貼られていると便利ですがGithubのIssues以外のJIRAなどを利用していると、リリースノートからに課題のURLを書くのも面倒です。GitHubAutolink referencesでJIRAなどの設定をすることによって、JIRA-1と書くだけでPull RequestやIssueなどでリンクが自動で設定されます。

リリースノートの設定

リリースノートでPull Requestを分類する設定をを.github/release.ymlに追加します。

changelog:
  categories:
    - title: 新機能 
      labels:
        - Feature
    - title: 不具合修正
      labels:
        - bug
    - title: リファクタリング
      labels:
        - refactor
    - title: Other Changes
      labels:
        - "*"

GitHub ReleaseページでGenerate releasenotesを実行すると以下のようになります。

株式会社フォトラクションでは一緒に働く仲間を募集しています

Pathlibでディレクトリ・ファイルを操作する

はじめに

こんにちは、株式会社フォトラクションでPMをしている峠です。

業務でディレクトリやファイル操作でパスを扱うことが多く、これらを Pathlib と呼ばれるPythonのライブラリを用いて操作しています。

今回はPathlibでできる操作をいくつか紹介したいと思います。

Pythonではパス操作時にos などのライブラリを使うことが一般的なイメージがありますが、弊社ではPathlibを使用しています。(参考:pathlibドキュメント

1. 基本パス操作

from pathlib import Path

# Pathインスタンスを作成
path = Path()

"""カレントパスを取得"""
current_path = path.cwd()
print(current_path) => "/content"

"""パスの連結"""
# ①「/」で連結
dir1_path = current_path / 'dir1'
file1_path = dir1_path / 'file1.txt'
print(dir1_path) => "/content/dir1"
print(file1_path) => "/content/dir1/file1.txt"
# ②「joinpath()」で連結
dir2_path = current_path.joinpath('dir2')
file2_path = dir2_path.joinpath('file2.txt')
print(dir2_path) => "/content/dir2"
print(file2_path) => "/content/dir2/file2.txt"

"""ディレクトリの判定"""
dir1_is_dir = dir1_path.is_dir()
file1_is_dir = file1_path.is_dir()
print(dir1_is_dir) => "True"
print(file1_is_dir) => "False"

"""ファイルの判定"""
dir1_is_file = dir1_path.is_file()
file1_is_file = file1_path.is_file()
print(dir1_is_file) => "False"
print(file1_is_file) => "True"

"""ディレクトリの作成"""
# parents=True:中間ディレクトリも含めて作成する
# exist_ok=True:既存のディレクトリが指定されていてもエラーを出力しない
dir1_path.mkdir(exist_ok=True,parents=True)

"""ファイルの作成"""
# exist_ok=True:既存のディレクトリが指定されていてもエラーを出力しない
file1_path.touch(exist_ok=True)

"""パスの存在を確認"""
dir1_exist = dir1_path.exists()
file1_exist = file1_path.exists()
print(dir1_exist) => "True"
print(file1_exist) => "True"
dir2_exist = dir2_path.exists()
file2_exist = file2_path.exists()
print(dir2_exist) => "False"
print(file2_exist) => "False"

2. ディレクトリ・ファイルの操作

"""ディレクトリ・ファイルの名前を取得"""
dir1_name = dir1_path.name
file1_name = file1_path.name
print(dir1_name) => "dir1"
print(file1_name) => "file1.txt"

"""ディレクトリ・ファイルの拡張子を除いた名前を取得"""
dir1_stem = dir1_path.stem
file1_stem = file1_path.stem
print(dir1_stem) => "dir1"
print(file1_stem) => "file1"

"""ディレクトリ・ファイルの拡張子を取得(なければ取得しない)"""
dir1_suffix = dir1_path.suffix
file1_suffix = file1_path.suffix
print(dir1_suffix) => ""
print(file1_suffix) => ".txt"

"""パスを文字列としてを取得"""
dir1_str = str(dir1_path)
file1_str = str(file1_path)
print(dir1_str) => "/content/dir1"
print(file1_str) => "/content/dir1/file1.txt"

3. ディレクトリ・ファイル一覧の取得

# dir1に追加でディレクトリ・ファイルを作成
dir1_path.joinpath('file2.txt').touch(exist_ok=True)
dir1_path.joinpath('dir1-1').mkdir(exist_ok=True)
dir1_path.joinpath('dir1-2').mkdir(exist_ok=True)

"""ディレクトリ・ファイルの一覧をリストで取得"""
path_list = [path for path in dir1_path.iterdir()]
print(path_list) => "[PosixPath('/content/dir1/file2.txt'),"
                                            "PosixPath('/content/dir1/dir1-2'), "
                                            "PosixPath('/content/dir1/file1.txt')," 
                                            "PosixPath('/content/dir1/dir1-1')]"

"""ディレクトリの一覧をリストで取得"""
dir_list = [path for path in dir1_path.iterdir() if path.is_dir()]
print(dir_list) => "[PosixPath('/content/dir1/dir1-2'),"
                                        "PosixPath('/content/dir1/dir1-1')]"

"""ファイルの一覧をリストで取得"""
file_list = [path for path in dir1_path.iterdir() if path.is_file()]
print(file_list) => "[PosixPath('/content/dir1/file2.txt'),"
                                        " PosixPath('/content/dir1/file1.txt')]"

最後に

Pathlibはファイル操作機能が豊富で使い方も感覚的なのでとても便利です。

よかったら使ってみてください。

参考

pathlib - Object-oriented filesystem paths

株式会社フォトラクションでは一緒に働く仲間を募集しています

カスタマーサポートのお仕事と醍醐味

こんにちは!

フォトラクションでカスタマーサポートを担当している勝木(かつき)と申します。

いつもは弊社のTS部の(弊社ではTechnology Service部=通称:TS部と呼んでいます)の皆さんが記事を投稿していますが、

今回は趣旨を変えて、弊社が開発、提供しているフォトラクションというサービスが実際にどうやって使われているのか??

僕を含めカスタマーサポートというポジションがどういった仕事に取り組んでいるのか?

という部分を中心にお話しできたらなと思っています!

カスタマーサポートって何している人たち??

カスタマーサポート(以下、長いので「サポート」と略します)って何?というところから簡単に紹介しますと、

読んで字のごとく、「お客様を支援する」ことが僕たちサポートのお仕事です。

よく電話対応する人たちでしょ?と思われがちですが、

そしてそれもまったくもってお仕事の一つではありますがざっくりとこんな感じです↓

  • 電話やメールでのフォトラクション利用に関しての問い合わせ対応
  • ヘルプページやマニュアルなどの作成
  • 導入初期のユーザーに対しての運用方法のすり合わせと操作説明会の実施
  • 導入後の運用支援とモニタリング
  • ユーザーからの要望の集約と社内展開

こんな感じで、サービスの活用を促すポジションがフォトラクションのサポートの業務となってまして、

そのためユーザーと日々接する機会が非常に多いポジションになっています。

サポートとしての醍醐味

建設現場では、実際の工事を行うだけでなく、その工事過程の写真で記録をしたり、報告書として書類にまとめたり、設計図と実際の現場を見比べて相違がないかをチェックしたり、工程を管理したりなど、現場に従事されている方は日々一つの現場でさまざまな業務をする必要があります。

フォトラクションというサービスはそんな建設業のお仕事を支援するためのツールです。

僕たちサポートは前述したように、フォトラクションを利用いただいているユーザーの方々から操作に関するお問い合わせや、操作説明の場でユーザーと接することが多いのですが、

フォトラクションを利用いただくなかで、

  • 工事写真の撮影から書類の作成までの作業時間が半分以下になった
  • 現場の情報共有が格段に楽になった
  • フォトラクションを見ながら会議ができるようになって紙いらず

と嬉しいお声をいただくことがあります。

これらのお声はしっかりと価値を提供できている何よりの証ですし、ユーザーとの距離が一番近いサポートだからこそ受け取れる声だと思っています。

詳しくはフォトラクションの導入事例をみていただけると、実際に利用されている方々のお声が掲載されていますのでご興味ある方はぜひ!

こうした嬉しいお声をいただくと同時に「もっとこうして欲しい」「こういった機能があればもっと楽になるのに」だったりとサービスへのフィードバックや機能要望をいただくことも増えていきます。

こういったお声もサービスをより良いものへと進化させていくのに必要なものなので、

一つ一つの意見を集約して、社内に展開しサービスの改善に反映されていくのもサポートの醍醐味だなあと日々の仕事のなかで感じます。

ユーザーからの声を受け止め、TS含め社内へしっかりと声を届けサービスを強化していく。

このサイクルを続けることで建設業全体に大きなインパクトを与えるものとなっていくと信じて、僕たちは今日もユーザーの一番近くでサポートを続けていくのでした。

株式会社フォトラクションでは一緒に働く仲間を募集しています

建設業特化のBPO機能について

こんにちは!株式会社フォトラクションでプロダクトマネージャーをしています南風原はえばる)です。

Photoruction Advent Calendar 2022の22日目の記事になります。

はじめに

Webエンジニアからプロダクトマネージャーに転身して早4ヶ月…

開発者だった時以上にプロダクトに向き合い、プロダクトの成長を目指して日々チーム一丸となってプロダクト開発に取り組んでいます。

私が主に携わっているプロダクトである「BPO機能」は、今年10月頃に新規リリースした機能になります。

今回は、たまに夢に出てくるくらい愛してやまない「BPO機能」について紹介していこうと思います😘

BPO機能とは?

BPO機能とは、工事の事前準備や、書類作成、データ入力などをサポートをするアウトソーシングサービスである「建設BPO」を画面上から簡単に利用できる機能です。

工事現場からノンコア業務を解放し、生産性向上を実現するとともに、生産データの蓄積を促進を目指しています。

また、Next SaaSである「BPaaS」の考え方に基づいて開発しており、SaaS利用以上の生産性向上を可能とするサービスとなっています。

BPaaSに関してはCEO中島さんが詳しく説明してるのでぜひ読んでみてください!

note.com

BPO機能の具体的な機能説明をする前に、建設BPOをどういったタイミングで利用するのかを施工管理業務の一つである「配筋検査」業務を例に説明していこうと思います。

建設BPOを利用しない従来の配筋検査業務のフローとしては以下のような手順で業務が進んでいきます。

  1. 図面準備
    1. 配筋検査を行う対象物件の図面を用意する
  2. 検査準備
    1. 工事写真の撮影時に用いる黒板の作成
    2. 図面上の検査を行う箇所にピンを設置(検査項目を事前に登録したりする)
  3. 検査
    1. 図面と現場を比較し検査していく
    2. 検査結果を記録していく
  4. 写真撮影
    1. 黒板を用いて工事写真を撮っていく
  5. 台帳化
    1. 検査結果や工事写真をまとめた台帳を作成し関係者各位に報告する

建設BPOを利用すると、「検査準備」をまるっと当社で対応することができるため工事の事前準備をする手間が省け、コアの業務に集中することができるメリットがあります。

また建設BPOは、当社で開発しているaoz cloudという建設業界特化型AIとオペレーターをかけ合わせて低コスト・高スピード・高品質で必要なデータを作成し納品しています。

建設BPOは以前から提供しているBPOサービスでしたが、BPO機能がリリースされる前は直接ユーザーとメールのやり取りをして運用していました。

ただ、以前の運用では以下のような課題がありました。

  • メニューの標準化がされず拡販がしにくい状態
  • BPOの利用状況が見えにくい状態

今回、建設業向けのクラウドサービスPhotoructionから、建設BPOを簡単に利用することができるBPO機能をリリースしたことにより、今後建設BPOを多くのユーザーに利用していただくことが可能になったのと、建設業の生産性向上により一層貢献していける可能性が広がりました!

BPO機能でできること

現時点でのBPO機能でできることを簡単に紹介していきます。

主にできることとしては以下のとおりです。

  • BPOサービスの見積・発注
  • 利用状況の確認
    • 見積金額の確認
    • 納品データの確認など

BPO機能で利用できるサービスメニューの一覧・詳細を確認することができます。

利用したいサービスメニューの見積依頼と利用状況の確認ができます。

また、ユーザー向けの画面とは別で社内メンバーが利用する管理画面の開発も行っています。

社内向け管理画面の詳細については内緒ということで🤫

技術スタックについて

最後に、BPO機能の開発で用いている技術スタックの紹介になります。

新規でゼロから開発していることもあり、割とモダンな開発環境なのかなと思います。

まとめ

簡単にですがBPO機能の紹介をさせていただきました!

少しでも興味持っていただけましたでしょうか??

今後もより一層ユーザーに価値提供していくためにいろいろ作りたい機能があります。

今年から新しく提供している機能でもあるため、プロダクトとしてもまだまだ未熟です。

ぜひ、この記事を読んで興味を持っていただいた方や、新規プロダクトの開発、BPaaSの開発をしてみたい方がいらっしゃいましたらぜひご応募もしくはカジュアルにお話しましょう〜!!!🤝

株式会社フォトラクションでは一緒に働く仲間を募集しています

JavaScriptでアニメーションを行う

こんにちは!株式会社フォトラクションウェブエンジニアのジョンです!

今回はウェブアニメーションAPIの簡単な使い方を紹介したいと思います!

ウェブアニメーションAPIとは

ウェブアニメーションのドラフトを初めて作成されたのは2013年の6月で決して新しい機能ではありません。基本的にはウェブアニメーションAPICSSアニメーションをJavaScriptにコントロールするためのAPIです。

アニメーションをJavaScriptにコントロールさせることでいくつかのメリットがありますが個人的に一番重要なのはアニメーションの開始と終了時のわかりやすさです。

普通はCSSでアニメーションを行う時にアニメーションの間隔と合わせてJavaScriptの処理を遅らせることが多いです。ここで普段はsetTimeoutがよく使われています。

ブラウザーサポート

ウェブアニメーションは新しい機能ではないため、ほとんどの最新版ブラウザーにはすでに対応されているはずです。

でも、なんとなくブラウザーウェブアニメーションを対応していない場合はPolyfillを使ってウェブアニメーションを使うことができます。

animateファンクション

ウェブアニメーションの一番簡単な使い方は.animate()ファンクションを使うことです。.animate()ファンクションは2つの引数を受け取っています。

第一引数

第一引数にはキーフレームを指定します。値はオブジェクトかオブジェクトの配列かになります。オブジェクトの内容はCSSと同じプロパーティとなります。フォーマットはキャメルケースになります。

// 方法1
const keyframes = {
    backgroundColor: ['red', 'blue'], // 'red'は開始値で'blue'は終了値になります
        left: [0, '500px'] // '0'は開始値で'500px'は終了値になります
};

// 方法2
const keyframes = [
        {
            backgroundColor: 'red', // 'red'は開始値
                left: 0 // '0'は開始値
        },
        {
            backgroundColor: 'blue', // blue'は終了値
                left: '500px' // '500px'は終了値
        }
];

第二引数

第二引数にはアニメーションのオプションを指定します。値はミリ秒単位でのアニメーションの再生時間となる数字またはKeyframeEffect()のオプションの引数になります。

// 方法1
document.querySelector('#box').animate(keyframes, 1000); // アニメーションは1秒走ります

// 方法2
const options = {
    delay: 300, // アニメーションを遅らせる(ミリ秒単位)
        duration: 1000, // アニメーションの再生時間(ミリ秒単位)
        easing: 'ease', // アニメーションの[イージング](https://ics.media/entry/18730/)(動きの加減速)
    fill: 'forwards' // アニメーション終了した際にリセットしない
};
document.querySelector('#box').animate(keyframes, options);

合わせますと

上記を合わせますと下記の通りにCSSなしでもアニメーションを動かせます。

Animationオブジェクト

.animate()ファンクションは[Animation](https://developer.mozilla.org/ja/docs/Web/API/Animation)オブジェクトを返しています。Animationオブジェクトには3つイベントがあります。

  • cancel - [Animation.cancel()](https://developer.mozilla.org/ja/docs/Web/API/Animation/cancel)メソッドが呼び出されるか、アニメーションの再生状態が他の状態から "idle"へ遷移した場合に発行されます。
  • finish - アニメーションの再生が終了した時に発行されます。
  • remove - アニメーションが取り除かれた時 (すなわち、 active置換状態に遷移した時)に発行されます。

finish イベントを待つことでsetTimeoutを使わずにアニメーションが終わったとたん処理を実行することができます。

最後に

ウェブアニメーションAPIにはこちらにカバーできない機能が結構あります。もっとアドバンスなアニメーションなどもできますが、ほとんどのUIアニメーションなら上記のやり方でカバーできています。ウェブアニメーションAPIに関してもっと知りたいのであれば、この記事を書いているところに参照したサイトや記事などを書きに提供いたしますので是非読んでみてください。

References

PyMuPDFでPDFを操作する⓶

こんにちは!株式会社Photoructionでエンジニアとしてインターンをしている渡邉圭太郎です。 Photoruction Advent Calendar 202220日目の記事です。

はじめに

自分たちのチームでは、PDFを扱って画像解析を行うことが多いです。以前まではpdfminerを使用していましたが、日本語ドキュメントの豊富さなどから現在はPyMuPDFというライブラリを使用しています。本記事はPyMuPDFを使ったPDFのベクターファイルの取得方法を紹介します。

PDFのベクターファイルとは

PDFのファイル形式には大きわけてラスターファイルとベクターファイルの二つが存在します。それぞれの特徴としては以下の通りです。

ラスターファイル

ラスターファイルとは、色のついた小さい正方形であるピクセル(画素)を大量に組み合わせた画像で、写真などの高精細な画像を形成できます。ピクセル(画素)数が多いほど高画質になり、少ないほど低画質になります。画像のピクセル数は、ファイル形式によって異なります。(https://www.adobe.com/jp/creativecloud/file-types/image/comparison/raster-vs-vector.htmlから引用)

ベクターファイル

ベクターファイルは、数式、直線、曲線を使用して、グリッド上の固定点により画像を表示します。ベクターファイルにはピクセルはありません。ベクターファイルは、数式によってシェイプ、境界線、塗りの色を表現し、画像を構築します。 ベクター画像はサイズが変わっても数式により再計算できるため、品質に影響を及ぼすことなく拡大縮小できます。(https://www.adobe.com/jp/creativecloud/file-types/image/comparison/raster-vs-vector.htmlから引用)

要するにベクターファイルは矩形や文字情報が画像としてではなく格納されています。これはPyMuPDFを使用すると簡単に取得できるのです。

PDFの読み込み

PyMuPDFの読み込みについては、11日目の酒井さんの記事をぜひ参考にしてください!

図形の取得

# PDFファイルパスの読み込み
pdf_path = input()

# 1ページ目の要素を取得
document = fitz.open(pdf_path)
page = document[0]

# 図形の取得
paths = page.get_cdrawings()
line_list, rect_list, quad_list, curve_list = [], [], [], []
for path in paths:
    items = path['items']
      for item in items:
          if item[0] == 'l':
              line_list.append([item[1], item[2]])
              # pdfにlineを追加する場合
              # page.draw_line(line[0], line[1], color=(1, 0, 0))
          elif item[0] == 're':
              rect_list.append(item[1])
          elif item[0] == 'qu':
              quad_list.append(item[1])
                    elif item[0] == 'c':
                            curve_list.append(item[1])

テキストの取得

# PDFファイルパスの読み込み
pdf_path = input()

# 1ページ目の要素を取得
document = fitz.open(pdf_path)
page = document[0]

# テキストの取得
words = page.get_text_words()

text_info = []
coordinates_list = []
for w in words:
    x1, y1, x2, y2 = w[:4]
    x1, y1 = fitz.Point(x1, y1) * page.rotation_matrix
    x2, y2 = fitz.Point(x2, y2) * page.rotation_matrix
    x1, y1, x2, y2 = min(x1, x2), min(y1, y2), max(x1, x2), max(y1, y2)
    if x1 == x2:
        x1 -= 0.1
        x2 += 0.1
    if y1 == y2:
        y1 -= 0.1
        y2 += 0.1
  
    text_info.append({'text': w[4], 'coordinates': [x1, y1, x2, y2]})

おわりに

PyMuPDFでは他にもPDFを編集したりする操作も可能となっています。もし気になる方はチェックしてみてください!

株式会社フォトラクションでは一緒に働く仲間を募集しています