go-mode + eglotを使ってEmacsのGolang開発環境を整える
普段 Golang でサーバコードを書くときはもっぱら Emacs を使っています。Go Modules に移行してから gocode が動作しなくなったので最近はやりの LSP(Language Server Protocol) を導入することにしました。Go の Language Server は gopls 、Emacs のLSPクライアントは eglot を使います
動作確認バージョン
Emacsは25以上、Golangは1.11以上なら問題なく動作するはずです。
gopls のインストール
Go の Language Server は3種類あります。一番下の gopls が Golang の公式 Language Server です。
一長一短あるようですが公式が安心そうなので gopls をインストールします。
$ GO111MODULE=on go get -u golang.org/x/tools/cmd/gopls
goimportsのインストール
Go の import を整理してくれるコマンドもついでにインストールしておきます。
$ go get golang.org/x/tools/cmd/goimports
init.el の設定
Emacs の LSP クライアントは lsp-mode が有名ですが、動作がめちゃくちゃ重かったのと余計な表示がいっぱい出てきたのでシンプルな eglot を使うことにしました。入力補完は company-mode を使います。あと GOPATH や GOROOT など環境変数設定のために exec-path-from-shell を使います。
;; パッケージ管理サーバ (require 'package) (add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/") t) (add-to-list 'package-archives '("marmalade" . "http://marmalade-repo.org/packages/")) (package-initialize) ;; パッケージ情報の更新 (package-refresh-contents) ;; インストールするパッケージのリスト (defvar my/favorite-packages '( use-package exec-path-from-shell )) ;; my/favorite-packagesからインストールしていないパッケージをインストール (dolist (package my/favorite-packages) (unless (package-installed-p package) (package-install package))) ;; シェルに設定されている環境変数を引き継ぐ (exec-path-from-shell-initialize) ;; eglot ;; M-.で定義ジャンプ、M-,でジャンプ先からもどる ;; eglot はデフォルトの Language Server として go-langserver を使うので golsp に変更する ;; 事前に go get -u golang.org/x/tools/cmd/gopls しておくこと (use-package eglot :ensure t ;自動インストール :config (define-key eglot-mode-map (kbd "M-.") 'xref-find-definitions) (define-key eglot-mode-map (kbd "M-,") 'pop-tag-mark) (add-to-list 'eglot-server-programs '(go-mode . ("gopls"))) (add-hook 'go-mode-hook #'eglot-ensure)) ;; company-mode (use-package company :ensure t :config (global-company-mode) (setq company-idle-delay 0) (setq company-minimum-prefix-length 1) (setq company-dabbrev-downcase nil) (setq company-selection-wrap-around t)) (use-package go-mode :ensure t :commands go-mode :config (setq gofmt-command "goimports") (add-hook 'before-save-hook 'gofmt-before-save))
プロジェクトルートが見つからない時の対策
Monorepo 構成で Go プロジェクトが複数存在する場合に eglot がプロジェクトルートが判別できずローカルのパッケージの補完ができないことがあります(eglotはproject.elをつかってgitのファイル構成からルートを判別するようです)。その場合は projectile-mode を使ってプロジェクトルートを判別させます。init.elに以下のスクリプトを追加してから
;; Bridge projectile and project together so packages that depend on project ;; like eglot work (use-package projectile :ensure t) (defun my-projectile-project-find-function (dir) (let ((root (projectile-project-root dir))) (and root (cons 'transient root)))) (projectile-mode t) (with-eval-after-load 'project (add-to-list 'project-find-functions 'my-projectile-project-find-function))
こんな感じでプロジェクトルートのディレクトリに空の . projectile ファイルを配置してください。
├ .git ├ foo_service ├ main.go └ .projectile ├ bar_service ├ main.go └ .projectile └ baz_service ├ main.go └ .projectile
保管で Keyword argument のエラーが出た時の対処方法
error in process filter: Keyword argument :version not one of (:uri :diagnostics)
のエラーが出るときは eglot のバージョンアップをすると解消します。
参考
Python を使って地域メッシュコードから緯度経度ポリゴンを算出する
日本の地域メッシュコードから緯度経度ポリゴンを算出する Python3.7 のプログラムを作成しました。 前回の記事の逆バージョンです。
プログラム
3次メッシュコード(基準地域メッシュコード)から緯度経度ポリゴンを算出します。精度を出すためにミリ秒で計算しています。
# ミリ秒 MILLISECOND = 3600000 class Grid(object): def __init__(self, code, parent=None, divide=0): size = len(code) // 2 # コードの上位桁 self.upper = int(code[:size]) # コードの下位桁 self.lower = int(code[size:]) self.height_ms = MILLISECOND * (40 / 60) self.width_ms = MILLISECOND self.parent = parent if parent: self.height_ms = parent.height_ms / divide self.width_ms = parent.width_ms / divide def origin(self): if self.parent: p = self.parent.origin() lat_ms = p[0] + (self.height_ms * self.upper) lon_ms = p[1] + (self.width_ms * self.lower) else: # 親が存在しない=1次メッシュ lat_ms = self.upper * MILLISECOND / 1.5 lon_ms = (self.lower + 100) * MILLISECOND return lat_ms, lon_ms class Code2Polygon(object): def __init__(self, code): first_code = code[0:4] second_code = code[4:6] third_code = code[6:8] # 1次メッシュ self.__first = Grid(first_code) # 2次メッシュ(1次メッシュを8分割) self.__second = Grid(second_code, self.__first, 8) # 3次メッシュ(2次メッシュを10分割) self.__third = Grid(third_code, self.__second, 10) def __to_polygon(self, s, w, n, e): return [s, w], [s, e], [n, e], [n, w], [s, w] def __to_bounds(self, origin, height_ms, width_ms): south = origin[0] west = origin[1] north = south + height_ms east = west + width_ms return south / MILLISECOND, west / MILLISECOND, north / MILLISECOND, east / MILLISECOND def third_bounds(self): origin = self.__third.origin() return self.__to_bounds(origin, self.__third.height_ms, self.__third.width_ms) def third(self): t = self.third_bounds() return self.__to_polygon(t[0], t[1], t[2], t[3])
3次メッシュのみ算出してます。
使い方
Code2Polygon クラスのコンストラクタに地域メッシュコードを渡してポリゴンを取得します。
def test_code2polygon(): # 渋谷付近のメッシュコード c2p = Code2Polygon('53393595') print(c2p.third())
上記プログラムの出力結果は以下のようになります。
([35.65833333333333, 139.6875], [35.65833333333333, 139.7], [35.666666666666664, 139.7], [35.666666666666664, 139.6875], [35.65833333333333, 139.6875])
参考記事
Python を使って緯度経度から地域メッシュコードを算出する
緯度経度(latitude, longitude)から日本の地域メッシュコードを算出する Python3.7 のプログラムを作成しました。
プログラム
緯度経度から1-3次メッシュコード(基準地域メッシュコード)を算出します。精度を出すためにミリ秒で計算しています。
import math # 計算の単位(ミリ秒) MILLISECOND = 3600000 class FirstGrid(object): def __init__(self, lat, lon): ''' 1次メッシュコード計算 ''' self.lat = lat self.lon = lon # メッシュの高さ self.height_ms = MILLISECOND * (40 / 60) # メッシュの幅 self.width_ms = MILLISECOND # メッシュコードの上位桁 self.upper = int(math.floor(lat * 15 / 10)) # メッシュコードの下位桁 self.lower = int(math.floor(lon - 100)) # 南端(緯度) self.south_ms = self.upper * MILLISECOND / 1.5 # 西端(経度) self.west_ms = (self.lower + 100) * MILLISECOND def code(self): ''' メッシュコード ''' return f'{self.upper}{self.lower}' def origin(self): ''' メッシュの南西端(緯度経度) ''' return self.south_ms / MILLISECOND, self.west_ms / MILLISECOND class Grid(FirstGrid): def __init__(self, parent, divide): ''' 2,3次メッシュコード計算 :param parent 親メッシュ :param divide 分割単位 ''' self.lat = parent.lat self.lon = parent.lon lat_ms = parent.lat * MILLISECOND lon_ms = parent.lon * MILLISECOND # 親メッシュの高さと幅から当該メッシュの高さと幅を算出する self.height_ms = parent.height_ms / divide self.width_ms = parent.width_ms / divide h = self.height_ms w = self.width_ms # 上位桁 self.upper = int(math.floor((lat_ms - parent.south_ms) / h)) # 下位桁 self.lower = int(math.floor((lon_ms - parent.west_ms) / w)) # 南端 self.south_ms = self.upper * h + parent.south_ms # 西端 self.west_ms = self.lower * w + parent.west_ms def code(self): return f'{self.upper}{self.lower}' def origin(self): return self.south_ms / MILLISECOND, self.west_ms / MILLISECOND class LatLon2Code(object): def __init__(self, lat, lon): f = FirstGrid(lat, lon) # 2次メッシュは1次メッシュを8分割する s = Grid(f, 8) self.__first = f self.__second = s # 3次メッシュは2次メッシュを10分割する self.__third = Grid(s, 10) def first(self): return self.__first.code() def second(self): return f'{self.__first.code()}{self.__second.code()}' def third(self): return f'{self.__first.code()}{self.__second.code()}{self.__third.code()}'
地域メッシュは英語だと Grid Square と表現するらしいのでクラス名は Grid にしています。1次メッシュの計算が特殊で2次3次は同じ計算をするので、1次と2次3次でクラスを分けました。
使い方
LatLon2Code クラスのコンストラクタに緯度経度を渡してコードを取得します。
def test_latlon2code(): # 渋谷 ll2c = LatLon2Code(35.6640352, 139.6982122) assert(ll2c.first() == '5339') assert(ll2c.second() == '533935') assert(ll2c.third() == '53393595')
参考記事
PostGISでgeometry型からgeography型に変換するときは座標系に注意する
SRID=4612(JGD2000測地系+地理座標系)のgeometry型フィールドだとgeography型に変換できる
postgres=> SELECT ST_SetSRID(geom, 4612)::geography FROM map WHERE id=1; st_setsrid ---------------------------------------------------- 0101000020041200006B405577CB756140404749F6A1BF4140
SRID=3857(WGS84測地系球面+メルカトル図法)のgeometry型フィールドをgeography型に変換しようとするとエラーになる
postgres=> SELECT ST_SetSRID(geom, 3857)::geography FROM map WHERE id=1; ERROR: Only lon/lat coordinate systems are supported in geography.
EPSG: 3857は投影座標系で単位がメートルなのでダイレクトに変換できないみたいです。
参考
プライバシーポリシー
はじめに
A Day In The Life(https://glassonion.hatenablog.com、以下当サイト)は、個人情報に関する法令等を順守し、個人情報を適切に取り扱います。
個人情報の管理
当サイトは、お問い合わせいただいた内容についての確認・相談、情報提供のためのメール送信(返信)の目的以外には使用しません。また知り得た個人情報を第三者に開示することは、警察・裁判所など公的機関からの書面をもった請求以外に一切利用いたしません。
広告について
当ブログでは、第三者配信の広告サービス(Googleアドセンス)を利用しており、ユーザーの興味に応じた商品やサービスの広告を表示するため、クッキー(Cookie)を使用しております。
クッキーを使用することで当サイトはお客様のコンピュータを識別できるようになりますが、お客様個人を特定できるものではありません。 Cookieを無効にする方法やGoogleアドセンスに関する詳細は「広告 – ポリシーと規約 – Google」をご確認ください。
また、当ブログは、Amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイトプログラムである、Amazonアソシエイト・プログラムの参加者です。
免責事項
当ブログからのリンクやバナーなどで移動したサイトで提供される情報、サービス等について一切の責任を負いません。 また当ブログのコンテンツ・情報について、できる限り正確な情報を提供するように努めておりますが、正確性や安全性を保証するものではありません。情報が古くなっていることもございます。 当サイトに掲載された内容によって生じた損害等の一切の責任を負いかねますのでご了承ください。
著作権について
当ブログで掲載している文章や画像などにつきましては、無断転載することを禁止します。 当ブログは著作権や肖像権の侵害を目的としたものではありません。著作権や肖像権に関して問題がございましたら、お問い合わせメールよりご連絡ください。迅速に対応いたします。
リンクについて
当ブログは基本的にリンクフリーです。リンクを行う場合の許可や連絡は不要です。 ただし、インラインフレームの使用や画像の直リンクはご遠慮ください。
お問い合わせ
当ブログに関するお問い合わせは以下のメールアドレスまでおねがいします。 glassonion999 @ gmail.com
Golang書くときのちょっとしたテクニック
MarshalJSONを使ってJSONに表示用のフィールドを追加する
無限ループしないように元の構造体を拡張する
// UTCな時間をJsonに変換するタイミングでJSTに変換する例 import "time" type Hoge struct { ID uint CreatedAt time.Time } func (h Hoge) MarshalJSON() ([]byte, error) { type Alias Hoge return json.Marshal(&struct { Alias CreatedAtJST time.Time }{ Alias: (Alias)(h), CreatedAtJST: h.CreatedAt.In(time.LoadLocation("Asia/Tokyo")), } }
参考
Enum
数値系Enum
ゼロ値をUnknownにするのがGolang流
type DeviceType uint const ( Unknown DeviceType = iota Android IOS )
文字列系Enum
type Status string const ( Success Status = "success" Failure Status = "failure" )
関数にオプショナルな引数を設定したいとき
FunctionalOptionPatternを使って実装する
https://blog.web-apps.tech/go-functional-option-pattern/
リクエストボディの読み出しが1回しかできないときの対処方法
リクエストボディを ioutil.ReadAll 関数で読み出すと2回目以降は中身空っぽになります。それの回避方法です。
http - How to read response body twice in Golang middleware? - Stack Overflow
Gotestでいい感じのスタブを作る方法
gomockインストールするのはちょっとなぁって時に良いです。