Goalist Developers Blog

JenkinsでスタンドアローンなPlayFrameworkアプリをビルド・デプロイする

こんにちは。ゴーリスト開発のイイオです。

今回はJenkinsおじさんを使役します。
とはいえほんとうに手動でぽちぽちやっていたのを置き換えただけなので
もっと便利なプラグインとか使い方があるような気がします。

f:id:y-iio:20170825104518p:plain
よく知らなくてすまんな…おじさん

PlayFrameworkとは入社時から1年弱の付き合いですがいまだにあまり仲良くない。

https://www.playframework.com/documentation/ja/2.4.x

前提

ビルド環境

  • Ubuntu 14.04.5 LTS
  • Jenkins 2.46.2
  • PlayFramework 2.4 Java

実行環境

  • Amazon Linux AMI release 2017.03

JenkinsサーバはbitnamiでEC2上に立てた

なんかbitnami使ったおかげで楽だったりもしましたが、だいたい詰まったのこのせいな気がしなくもない。
それについてはこちらをご覧ください。

developers.goalist.co.jp

Playビルド用の環境整える

qiita.com

qiita.com

Jenkins > Global Tool Configuration > JDK > JDK追加
Java SE Development Kit 8u131

Jenkinsにリモートでシェル実行できるプラグイン入れる

Jenkins でリモートサーバーのコマンドを実行できる SSH pluginを使ってみた | Whaison JUGEM! StudyNoteBook .

Jenkins > 認証情報 > System > グローバルドメイン > 認証情報の追加
でリモートサーバー接続用のSSHユーザー名と秘密鍵を登録

Jenkins > 設定 > SSHリモートホスト
でリモートサーバーを登録

実行環境でアプリとPID置き場を作っておく

sudo mkdir /var/play
sudo mkdir /var/run/play

パーミッションも適当に与える

やってること

  1. Jenkinsでgithub上のソースから任意のブランチを指定してビルド
  2. リモートサーバーで稼働しているアプリ停止
  3. Jenkinsでビルドしてリモートサーバーの成果物更新
  4. リモートサーバーのアプリ再起動

ダウンタイムがある!ので
作業中はELBを別インスタンスに向けるとか、そのへんも自動化するとかあるだろうけど
漢らしく置いておくことにします٩( ‘ω’ )و

詳しく

1. Jenkinsでgithub上のソースから任意のブランチを指定してビルド

ビルド設定
こんなかんじでパラメータにブランチ名直書きでやってます。

f:id:y-iio:20170825104227p:plain

f:id:y-iio:20170825104326p:plain

なんか選択肢で選ぶ方法とかあるらしいけど複雑なのでやらない。

2. リモートサーバーで稼働しているアプリを停止

ビルド手順の追加 > リモートホストでシェルを実行

if test -e /var/run/play/play.pid; then kill $(cat /var/run/play/play.pid); fi
rm -rf /var/play/hrog-map-api-SNAPSHOT.zip
rm -rf /var/play/hrog-map-api-SNAPSHOT

3. Jenkinsでビルドして、リモートサーバーの成果物更新

ビルド手順の追加 > シェルの実行

bin/activator dist
scp -P 22 -i /path/to/key/key.pem ${WORKSPACE}/target/universal/PROJECT_NAME.zip USER@~~~~~~~.compute.amazonaws.com:/var/play

もっとこのへんいい方法あるんだろうな〜〜

4. リモートサーバーのアプリ再起動

ビルド手順の追加 > リモートホストでシェルを実行

unzip -o /var/play/hrog-map-api-SNAPSHOT.zip -d /var/play
nohup /var/play/hrog-map-api-SNAPSHOT/bin/hrog-map-api -Dconfig.resource=application_dev.conf -Dpidfile.path=/var/run/play/play.pid > out.log 2> err.log < /dev/null &

だめだったら

パーミッションを適切に与えればなんとかなる!!!根も葉もない

AWS Marketplaceからbitnamiを使ってEC2インスタンス上にJenkinsサーバーを立てる

こんにちは、ゴーリスト開発のイイオです。

bitnamiのオールインワンパッケージで
超楽にJenkinsおじさんを召喚できるのでは、という企ての話です。
実際のところ超楽だったかというと???なかんじですが…

bitnami.com

手順

bitnami Jenkinsの「See in AWS Marketplace」をクリック
f:id:y-iio:20170824172032p:plain

インスタンスタイプを選択し
f:id:y-iio:20170824172048p:plain

任意のVPC設定して囲み、自動割り当てパブリックIPを有効にする

qiita.com

タグ設定でわかりやすい名前をつけておく
f:id:y-iio:20170824172158p:plain

インスタンス作成ボタンを押してちょっと待つ

初期化が終わったら割り当ててくれたURLにアクセス
f:id:y-iio:20170824172248p:plain

ウェイ

特にポート設定とかしてないけどhttp://wariatehost/にアクセスすれば
http://wariatehost/jenkins/にフォワードしてくれる。
あとインスタンス起動したら勝手にJenkinsも起動してくれる。

user/bitnamiで初回ログイン

f:id:y-iio:20170824172350p:plain
ドキュメントにはインスタンスのシステムログからパスワード見ろって書かれてるけど
システムログに無いしuser/bitnamiでいけた。謎

勧められるがままに「Install suggested plugins」

で再読み込みしたら
f:id:y-iio:20170824172406p:plain
ウェイ

気をつけること

タイムゾーン変更する

インスタンス立てた時に登録したキーペアでssh接続して変更

ssh -i "/path/to/key/key.pem" ubuntu@~~~~~~~~~~~~~~~.compute.amazonaws.com
timedatectl set-timezone Asia/Tokyo

Jenkinsの実行ユーザーは「tomcat」

github接続時のキー登録するのに大変時間を無駄にしたので…

まあシステム情報見たらわかることでしたが
f:id:y-iio:20170824172448p:plain

tomcatユーザーになってから、こちら参考に鍵登録しました。

blog.duck8823.com

結論

普通にインスタンス立ててyumでインストールしたらいいとおもう。

qiita.com

おわり

Macのローカルサーバーに接続して、iPhone/iPadの実機で動作確認・デバックする

開発環境のMacで立てたローカルサーバに、iPadの実機端末からアクセスしたときのメモです。

実行環境

  • macOS Sierra 10.12.5
  • iOS 10.3.2
  • iPadとMacが同じwifiネットワークに接続している
  • Lightningケーブルで接続している

実機から開発環境のローカルサーバーにアクセス

このあたり参照して「hoge.local」でいけるかな〜と思ったらできず… yuji-ueda.hatenadiary.jp

qiita.com

結局IP指定で行きました tacamy.hatenablog.com

Angular CLI でng serveしてローカルサーバーたててるんですけど
このときにオプションで自分のIP直で指定しないと外から見れなかったです。

ng serve --host <MY_IP>

実機のSafariからみてみる
http://MY_IP:PORT

f:id:y-iio:20170823191813p:plain
ウェイ

デバッグする

PC側の開発者ツール使ってデバッグします。
こちらを参照しました。
www.tam-tam.co.jp

f:id:y-iio:20170823191918p:plain

f:id:y-iio:20170823191935p:plain

ウェイウェイ

感想

これで開発速度が65918723倍になりました。やったぜ

簡易O/Rマッパー作ってみた。

ども、開発部の小野です。

今、簡易O/Rマッパー作っています。今回は簡易O/Rマッパーについて書いてみます。

O/Rマッパーって何?

sqlを直で全部書かなくても、DBにsqlを投げてくれるやつです。

検索条件をちょっと書いてあげるだけで、sqlを投げてくれます。時短ですね。

実際

作ってみた簡易O/Rマッパーです。

public class Work1 {

	public static void main (String[] arg) throws SQLException{
		JdbcManager ajm = new JdbcManager("jdbc:mysql://localhost:3306/data?characterEncoding=UTF-8","root","password");
		List<Map<String,Object>> resultMap1 = ajm.selectBySql("select emp_nm from emp_mst").getResultList();
	}
}

このWork1クラスで簡易O/Rマッパーを実際に使っています。JdbcManagerがそれですね。

ではJdbcManagerクラスを見てみましょう。

public class JdbcManager {

	String db_url;
	String db_user_id;
	String db_user_password;

	public JdbcManager(String db_url,String db_user_id,String db_user_password){
		this.db_url = db_url;
		this.db_user_id = db_user_id;
		this.db_user_password = db_user_password;
	}

	public SqlSelect selectBySql(String sql){
		SqlSelect ss = new SqlSelect(this,sql);
		return ss;
	}

	public AutoSelect from(String tableNm){
		AutoSelect as = new AutoSelect(this, tableNm);
		return as;
	}

}

selectBySqlというメソッドがありますね。このメソッドの引数にsqlを渡して、検索を実行しています。

ではselectBySqlメソッドの戻り値である、SqlSelectクラスを見てみましょう。

public class SqlSelect {

	JdbcManager ajm;
	String sql;

	public SqlSelect(JdbcManager ajm, String sql) {
		this.ajm = ajm;
		this.sql = sql;
	}

	public List<Map<String, Object>> getResultList() throws SQLException{
		Select sl =new Select(this.ajm,this.sql);
		return sl.sqlExecute();
	}
}

getResultListというメソッドがありますね。このメソッドを実行することで、検索結果がListになって帰ってきます。上で見ると分かるようにMapのListですね。

メソッドの中を見てみると、さらにSelectクラスのslというインスタンスでsqlExecuteというメソッドを使用しています。

このメソッドで、実際にクエリーを実行しています。

Selectクラスを見てみましょう。

public class Select {

	JdbcManager ajm;
	String sql;

	public Select(JdbcManager ajm, String sql) {
		this.ajm = ajm;
		this.sql = sql+";";
	}

	public List<Map<String, Object>> sqlExecute() throws SQLException {

		try (Connection connection = DriverManager.getConnection(this.ajm.db_url, this.ajm.db_user_id,
				this.ajm.db_user_password); Statement statement = connection.createStatement();) {

			ResultSet rs = statement.executeQuery(this.sql);
			ResultSetMetaData rsmd = rs.getMetaData();

			List<Map<String, Object>> resultList = new ArrayList<Map<String, Object>>();

			while (rs.next()) {
				Map<String, Object> map = new HashMap<String, Object>();
				for (int i = 1; i <= rsmd.getColumnCount(); i++) {
					map.put(rsmd.getColumnName(i), rs.getObject(i));
				}
				resultList.add(map);
			}
			rs.close();

			return resultList;
		}
	}
}

ConnectionにJdbcManagerインスタンスに持たせた、DBのURL,user名,パスワードを渡して、DBサーバーに接続しています。

あとは、Work1クラスから持ってきたsqlをexecuteQueryでDBに投げ込んでみるだけです。単簡ですね。

帰ってきた結果をリストに整理してチョチョイです。

まとめ

O/Rマッパーで単簡に時短。

次はテーブル名やwhere句の要素だけ渡して、クエリを実行してくれるやつも紹介出来たらいいですね~。

HRエキスポに行ってきました。

f:id:bbbbbbbbb9:20170817163537j:plain

導入

こんにちは。
四月に新卒で入社して、開発部でエンジニアをしています。バンナイです。

2017/7/28にHRエキスポというものに参加してきました。

www.hr-expo.jp

その目的は

  • 人材業界または人事向けのサービスのチェック。どのようなサービスが存在しているのか知る。

  • 人材業界または人事の抱える問題をシステムで解決することに関する知見を深める。

  • 人材業界または人事の仕事のこれからの行く末を見極める。

です。

何か具体的な目的があるわけではなく、人材業界に関わっているエンジニアとして漠然と知見を深めるということが目的でした。

実際に見たサービス

Google Recruiting with AdWords

Recruiting with AdWordsとは

  • スマホは求職者が最も活用しているツールだが1回あたりの起動時間は1分11秒と短いため、スマホを多用する求職者に対して短時間で効率的にアプローチすることが求められる。
  • 短時間で会社の魅力を伝えるには動画が有効

という二つの前提を踏まえて、Googleが提案するソリューションだそうです。

一言で言うと『ターゲットとする求職者に狙いを定め、行動を喚起するYouTube動画広告』。

Googleの高度なターゲティング技術を使うと、年齢、趣味趣向、アクセス位置、使用しているデバイス等の情報を利用して動画広告を見てもらいたい層に集中的に動画広告を見せることができるという話を聞いて、Googleのターゲティング技術と求人サービスとの相性の良さに気づかされました。

また実際に明光義塾の動画広告を観させてもらったのですが、生徒と先生の関わり合いが丹念に描かれていてとても感情に訴えかけてくるものだなと思いました。

以上の理由からRecruiting with AdWordsはこれからの求人市場においてどんどん影響力を増していくのではないかという可能性を感じました。

ゴーリストは求人媒体のクローリングを行っているのですが、今後動画による求人が活性化してきたら動画のクローリングとかもできると面白いかもなと思いました。どうやればできるのか分からないけれど…

www.google.co.jp

リファラル採用を活性化するクラウドサービス リフカム

リファラル採用 = 企業に所属する社員・アルバイトから友人を紹介・推薦してもらう採用方法

リファラル採用におけるコミュニケーションコストの削減、リファラル採用の社内での定着を狙いとしたサービス。

  • 社員に紹介を依頼する。
  • 社員の紹介の手間を減らす。
  • リファラル採用の効果測定と応募者管理。

ということができるとのことでした。
リファラル採用というとどうしても属人的になりがちというイメージがあったので、リファラル採用をシステムを運用できるのだったら興味深いサービスだなと思いました。

実際に使用画面を見せてもらったが、誰がいつ何人友達を紹介してくれたとかが、視覚的にとても分かりやすかった。

手間やコミュニケーションコストがボトルネックになって今まで出来なかったことをできるようにするというのはシステムが目指すべき一つの形だと思います。

refcome.com

採用HP作成ツール[エンゲージ]

誰でもカンタンに無料で採用HP作成ができるサービス。

実際にこのサービスでつくられたサービスをいくつか見せてもらったがかなり見た目よくリッチな感じでした。

綺麗なホームページが誰でも簡単に作れてしまうということで個人的には衝撃を受けました。 「どうして無料なんですか?」と質問してみました。

「このサービスはエンジャパンという求人媒体も持っている会社が提供しており各企業のホームページがリッチになればそれだけ人と企業の良い出会いがもっと生まれそれはエンジャパンにとってもいいこと」だそうです。
興味深い考え方だと思いました。

簡単にリッチなものが作れるというわけでまさにシステムが解決すべき問題であったと思いました。

en-gage.net

総括

最新のサービスに触れてサービスや業界の動向について学ぶことは重要だと思いました。

ゴーリスト開発部では個々人が皆プロダクトマネージャーになることが求められます。 またゴーリスト開発部では技術は手段であり本来の目的 = プロダクト、提供される価値等、から逸れないことも求められます。

今後も定期的にこのような取り組みをしていきたいです。

angular-google-mapsとカスタムオーバーレイでDOMをマーカーっぽく使う

こんにちは。ゴーリスト開発のイイオです。

先輩エンジニアモリツグさんの投稿で覚えもめでたい、angular-google-mapsでカスタムオーバーレイの続編です。

developers.goalist.co.jp

今回はカスタムオーバーレイでDOMをマーカーっぽく描画する必要に迫られた場合の解決方法を記しておきます。

やったことまとめ

  1. カスタムオーバーレイを描画する
  2. カスタムオーバーレイを緯度経度指定で描画してマーカーっぽく見せる
  3. カスタムオーバーレイにクリックイベントをつける

とりあえずできたものはこんな感じに動かせます。

f:id:y-iio:20170808153347g:plain

マーカーを表示して
ズームインしたらうまいこと位置調整が入って
マーカークリックイベント拾ってアラート表示
マーカーを非表示にする
みたいな流れです。

ここからが解説だ

何は無くともangular-google-mapsを入れます。
npm i --save @agm/core

app.module.tsにインポート文追加します。
APIキーはご自分のをドウゾ。

@NgModule({
  ・・・
  imports: [
    AgmCoreModule.forRoot({
      apiKey: 'YOUR_API_KEY'
    })
  ],
  ・・・
})

コンパイル時に google is not defined で怒られるので型定義も入れておきます。
npm i --save @types/googlemaps

使うところでインポート文を入れます。
import {} from '@types/googlemaps';

angular-google-maps公式のGetting startedを見ながら地図を表示させる。
いや決してめんどくさくなったわけではオボボボボ
CSSで高さ指定しないと表示できないので注意です。

で、先人の知恵を見ながらこんな感じに書いてみた。
任意のタイミングでマーカー表示/非表示したいのでちょっと変えてます。

app.component.ts

import { Component } from '@angular/core';
import {} from '@types/googlemaps';
import { GoogleMap } from '@agm/core/services/google-maps-types';

const cont: object = {
  label: '株式会社ゴーリスト',
  lat: 35.695568,
  lng: 139.771055
};

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  public lat: number = 35.695568;
  public lng: number = 139.771055;
  public zoom: number = 15;
  private nativeMap: GoogleMap;
  private overlays: any[] = [];

  public onMapReady(map: GoogleMap): void {
    this.nativeMap = map;
  }

  public toggleShowMarker(): void {
    if (this.overlays.length > 0) {
      this.removeMarker();
    } else {
      this.drawMarker();
    }
  }

  private drawMarker(): void {

    class CustomOverlay extends google.maps.OverlayView {
      map_;
      content_;
      div_;

      constructor(map, content, self) {
        super();

        this.map_ = map;
        this.content_ = content
        this.div_ = null;

        this.setMap(map)

        self.overlays.push(this);
      }
      /**
       * onAdd is called when the map's panes are ready and the overlay has been
       * added to the map.
       */
      onAdd() {
        const div = document.createElement('div');
        div.style.borderStyle = 'none';
        div.style.borderWidth = '0px';
        div.style.position = 'absolute';
        div.innerHTML = '<div class="marker">' + this.content_.label + '</div>';

        this.div_ = div;

        // Add the element to the "overlayLayer" pane.
        const panes = this.getPanes();
        panes.overlayLayer.appendChild(div);

        // Add click event
        panes.overlayMouseTarget.appendChild(div);
        google.maps.event.addDomListener(div, 'click', () => {
          self.onMarkerClicked();
        });
        google.maps.event.trigger(this, 'ready');
      };

      draw() {
        const overlayProjection = this.getProjection();
        const pixel = overlayProjection.fromLatLngToDivPixel(
          new google.maps.LatLng(this.content_.lat, this.content_.lng)
        );

        // Resize the div to fit the indicated dimensions.
        const el = this.div_;
        const content = el.children[0];
        const content_height = content.clientHeight;
        const content_width = content.clientWidth;
        el.style.top = pixel.y - (content_height + 7) + 'px';
        el.style.left = pixel.x - (content_width / 2) + 'px';
      };

      // The onRemove() method will be called automatically from the API if
      // we ever set the overlay's map property to 'null'.
      onRemove() {
        this.div_.parentNode.removeChild(this.div_);
        this.div_ = null;
      };

    }

    const self = this;
    if (cont) {
      new CustomOverlay(this.nativeMap, cont, self);
    }
  }

  private removeMarker(): void {
    this.overlays.forEach((item) => {
      item.setMap(null);
    });
    this.overlays = [];
  }

  private onMarkerClicked(): void {
    alert('marker clicked');
  }
}

onMapReadyでネイティブな Google Mapオブジェクトを変数に特攻ブッコむ。

drawMarkerのインナークラスでカスタムオーバレイを上書き、
newしたカスタムオーバーレイを配列に入れて保持。
(もともと複数マーカー表示していたのでこんなんなっている)

クリックイベントはDOM作ってからgoogle.maps.event.addDomListener()のところで指定。

描画時の位置は OverlayView.getProjection().fromLatLngToDivPixel()で緯度経度から特定。
吹き出しの矢印がちょうど緯度経度の位置に刺さるようにtopとleftのスタイル調整。
地図リサイズ時にdraw()がよびだされるのでその度に表示位置は変わる。

removeMarkerで作ったカスタムオーバーレイを破棄する。
OverlayView.setMap(null);のところ)

みたいなかんじです。

他の部分のソースはこちら

マーカーのCSSはapp.component.scssに書いてもかからなかったのだけど
src/styles.scssのほうでグローバル指定したらいけました。

app.component.html

<agm-map [latitude]="lat" [longitude]="lng" [zoom]="zoom" (mapReady)="onMapReady($event)"></agm-map>
<button (click)="toggleShowMarker()">マーカー表示</button>

app.component.scss

agm-map {
  height: 500px;
  width: 500px;
}

src/styles.scss

/* You can add global styles to this file, and also import other style files */
.marker {
  padding: 5px 6px;
  border-radius: 3px;
  width: auto;
  background: tomato;
  color: #fff;
  opacity: 0.9;
  cursor: pointer;

  &:after {
    top: 100%;
    left: 50%;
    border: solid transparent;
    content: " ";
    height: 0;
    width: 0;
    position: absolute;
    pointer-events: none;
    border-color: rgba(255, 53, 31, 0);
    border-top-color: tomato;
    border-width: 12px 6px 0 6px;
    margin-left: -6px;
  }
}

まとめ

angular-google-mapsの日本語記事なさすぎて嘆きます。
そしてほんとうに
コンポーネントをカスタムオーバーレイに突っ込めるような修正がangular-google-mapsに入ることを期待しています!!!

angular-google-mapsでカスタムオーバーレイする

こんにちわ、 ゴーリスト開発のモリツグです。

AdobeがFlashのサポートを2020年に終了することを発表しましたね!
全国に50人はいるであろう私を含むFlex愛好家はついにこの時が来たかという気持ちだとおもいます。
Angularはそんな悲しみに暮れるFlex愛好家にはピッタリなのでお勧めです。

前回はAngular4でangular-google-mapsを使うという内容を書いたのですが、更にカスタムオーバーレイを行う必要に迫られた場合の解決方法を残しておこうと思います。

はじめに

カスタムオーバーレイとはこんな感じにGoogleMap上に画像などの任意のDOM要素を配置できる機能です。 JavaScriptで使う方法は公式説明に詳しくあるのですが、angular-google-mapsで使うにはどうしたらいいのか。そもそも出来るのか、というのが今回の内容になります。

手順

とりあえず先人の知識を探りますこのリンクがものすごく参考になりました。というかほぼこれで解決します。
前述のリンクは純粋にTypeScriptでカスタムオーバーレイする場合です。
私が調べた限りではangular-google-mapsはカスタムオーバーレイを何とかしてくれる方法を提供してくれていません。
したがって今回はイメージ的にはangular-google-mapsのAgmMapクラスが保持しているであろうネイティブなGoogleMapのインスタンスを引っ張りだし、先人の知識を利用する形になります。(正確にはGoogleMapsAPIWrapperというServiceが保持している)
このような方法をとるのでAngularのComponentをそのまま突っ込むようなことはできず、ネイティブなDOMの追加になります。
AgmMapにはmapReadyというイベントが用意されており、AgmMapの初期化が完了するとネイティブなGoogleMapのインスタンスをイベントの引数としてemitしてくれます。この飛んできたGoogleMapに対して先人の知識を適用すれば完了です。

ソースコードと注意点

plunkerのデモはこちらになります。
なんか緑っぽい拡大縮小されない異質な地図が表示されていると思います。 drawメソッドの中で計算すれば拡大縮小時にオーバーレイされた画像も良い感じに描画されます。
htmlとTypeScriptの関係個所だけを念のため貼っておきます。

<agm-map (省略) (mapReady)="onMapReady($event)">(省略)</agm-map>
  public onMapReady(nativeMap:GoogleMap):void {

    class USGSOverlay extends google.maps.OverlayView  { // user inner class to avoid error of 'google is not defined'
      //export class USGSOverlay extends google.maps.OverlayView { // exportするとここのgoogleがgoogle is not definedにつかまる
      image_;
      map_;
      div_;

      constructor(map) {
        super(); //  we need to call super

        this.image_  = 'https://developers.google.com/maps/documentation/' +
          'javascript/examples/full/images/talkeetna.png';
        this.map_ = map;

        // Define a property to hold the image's div. We'll
        // actually create this div upon receipt of the onAdd()
        // method so we'll leave it null for now.
        this.div_ = null;

        // Explicitly call setMap on this overlay.
        this.setMap(map);
      }
      /**
       * onAdd is called when the map's panes are ready and the overlay has been
       * added to the map.
       */
      onAdd(){
        const div = document.createElement('div');
        div.style.borderStyle = 'none';
        div.style.borderWidth = '0px';
        div.style.position = 'absolute';

        // Create the img element and attach it to the div.
        const img = document.createElement('img');
        img.src = this.image_;
        img.style.width = '100%';
        img.style.height = '100%';
        img.style.position = 'absolute';
        div.appendChild(img);

        this.div_ = div;

        // Add the element to the "overlayLayer" pane.
        const panes = this.getPanes();
        panes.overlayLayer.appendChild(div);
      };

      draw(){ // 今回は緯度経度を位置に変換することはせずいったん直接指定で地図を表示する
        // We use the south-west and north-east
        // coordinates of the overlay to peg it to the correct position and size.
        // To do this, we need to retrieve the projection from the overlay.
        const overlayProjection = this.getProjection();

        // Retrieve the south-west and north-east coordinates of this overlay
        // in LatLngs and convert them to pixel coordinates.
        // We'll use these coordinates to resize the div.
        //  const sw = overlayProjection.fromLatLngToDivPixel(this.bounds_.getSouthWest());
        //  const ne = overlayProjection.fromLatLngToDivPixel(this.bounds_.getNorthEast());
        const sw = {x:100, y:200};
        const ne = {x:200, y:100};


        // Resize the image's div to fit the indicated dimensions.
        const div = this.div_;
        div.style.left = sw.x + 'px';
        div.style.top = ne.y + 'px';
        div.style.width = (ne.x - sw.x) + 'px';
        div.style.height = (sw.y - ne.y) + 'px';
      };

      // The onRemove() method will be called automatically from the API if
      // we ever set the overlay's map property to 'null'.
      onRemove(){
        this.div_.parentNode.removeChild(this.div_);
        this.div_ = null;
      };
    }
    this.overlay = new USGSOverlay(nativeMap);
  }

以下のクラスはonMapReadyメソッドでやっているようにインナークラスにしないと動きません。

class USGSOverlay extends google.maps.OverlayView {
  //省略
}

多分タイミング的な問題だと思うのですが、exportして使おうとすると実行時にgoogle is not definedといわれて怒られます。 色々やってみましたが全然解決できなかったので凄腕の方が解決してくれるのを待つことにします。
また今回はとりあえずカスタムオーバーレイを使うまでを目的としたので緯度経度から描画位置への変換はせず決め打ちです。参考リンクのソースではその辺の変換もやっているようです。

まとめ

angular-google-mapsでカスタムオーバーレイする場合の手順を解説してみました。
ほとんどstackoverflowのコピーじゃないかという意見もありますが、全くもってその通りです。私が紹介したのは「AgmMapのmapReadyがネイティブなGoogleMapのインスタンスを引数にemitされる」という部分だけです。
他力本願でComponentをカスタムオーバーレイに突っ込めるような修正がangular-google-mapsに入ることを期待しています。