Slide 1

Slide 1 text

移動で地域をカラフルに newmo 幾何アルゴリズムで なめらかなピン操作を iOSDC Japan 2026 Track B / レギュラートーク(20分) 永野 一馬 / なで @nade_kaz

Slide 2

Slide 2 text

自己紹介 永野 一馬 / なで(@nade_kaz) 2025年3月〜 newmo。タクシー配車アプリと、点呼・給与・勤怠などタク シー会社のシステム DX iOS だけでなく Web・Backend も / なで肩 ⇨ なで / 数学は専門外 新大阪駅で実証実験中(〜10月):事前予約で newmo 専用乗り場から並ば ずに乗れます。大阪にお越しの際はぜひ タクシー配車アプリ「newmo」 2024年12月 大阪から提供開始 自動点呼「newmo点呼」 国交省認定 2025/7・2026/2 新大阪駅 予約タクシー実証実験 JR西日本と 2026/8〜10。自動運転との 結節を見据えて 2

Slide 3

Slide 3 text

今日の話の元ネタ tech.newmo.me ・ 2025-12-15 Coding Agentと幾何学アルゴ リズムを活用した地図上の滑らか なピン操作の実現 tech.newmo.me ・ 2026-02-24 【お蔵入り】ライドシェアド ライバーアプリのSwiftUI作 品集! tech.newmo.me ・ 2025-12-11 プロダクトチーム全員でAIエ ージェント縛りの開発を一週 間試しました 今日の話の元ネタ 3

Slide 4

Slide 4 text

今日のテーマ 数学ガチ勢の 講義? 幾何アルゴリズム 🤔 地図 SDK の 使い方講座? AI に全部 書かせた話? 4

Slide 5

Slide 5 text

今日のテーマ 幾何アルゴリズム 🤔 ❌ ❌ 数学ガチ勢の 地図 SDK の 講義? 使い方講座? ❌ AI に全部 書かせた話? ⇨ 地図 UX を、幾何(= 座標変換や図形計算)で 「なめらか」にする話 5

Slide 6

Slide 6 text

なぜ、いま幾何なのか? 1 幾何は 20 世紀に枯れた知識 凸包も Graham Scan も 1972 年。実装例が豊富で、Agent の精度が高い 2 純粋なロジックは TDD と相性がいい 成功・失敗が明確。期待値をテストで書き、実装は Agent に任せる 3 Agent から数学を学ぶのは楽しい 「なぜこの式?」をその場で聞ける。手計算より Agent の方が正しかったことも 6

Slide 7

Slide 7 text

今回のゴール 座標変換と図形計算を、 自分のアプリの UX 改善に使える 現在地ガイドライン、ピンの押し出しといった マップ上のリッチな機能に、幾何がどう活用されているか 提供された API に必要なアルゴリズムがなかった場合に 「自分で作る」という選択肢を選べるように 学生時代ぶりの知識に社会人になってから出会ったときの、 ちょっとした喜び 7

Slide 8

Slide 8 text

アウトライン 1 現在地とピンを結ぶガイドライン 2 乗車禁止エリアとピンの押し出し 3 隣接エリアと凸包による統合 8

Slide 9

Slide 9 text

1 現在地とピンを結ぶ ガイドライン 見たことのある、あの点線。その描画と軽量化 Smooth out the movement, back and forth, over there and here. newmo

Slide 10

Slide 10 text

地図アプリ利用者の目的 「目的地を決めて、 今いる場所からそこへ向かう」 地図を使うアプリの多くは、突き詰めるとこのための道具 タクシー配車も同じ。この操作の気持ちよさが、体験の質を左右する 10

Slide 11

Slide 11 text

乗車位置をピンで決める 乗車位置 現在地 1 地図を動かして、中央のピンで乗車 位置を決める 2 「ここで乗る」で確定 3 配車 ⇨ ドライバーがその場所へ ここで乗る 11

Slide 12

Slide 12 text

現在地とピンを結ぶ点線、見たことある人?🙋 画面の外(見えていない) 現在地(画面外) 地図を動かすと現在地が画面外へ。 それでも線は、画面の端まで残る 12

Slide 13

Slide 13 text

なぜ、この点線があった方がいい? 現在地が画面外に出ても、どっちにあるか意識できる 現在地からかなり離れた場所で、誤って呼んでしまうのを防げる 現在地から乗車位置へ向かう時の位置関係が、視覚的にわかる 動かした分だけ線が追従する。インタラクティブで直感的 13

Slide 14

Slide 14 text

実現①:現実世界の座標で、地図上に結ぶ 描画済みの領域(タイル) 画面 画面外にも描いている 現在地 現在地とピン(緯度経度)を、地図側 の Polyline で結ぶ 現実世界 ⇨ 地図 素直。でも重い 地図は、画面より大きな巨大なスクロ ールビュー。見えない所まで描く そこに、指を動かすたびに変わる線を 描き直す ⇨ 全体の更新に巻き込まれ る 14

Slide 15

Slide 15 text

そもそも「座標」って、どこにあるんだっけ? 現実世界 緯度・経度 実際の場所 ⇄ 地図 位置・ポリゴン タイルの上 ⇄ スマホ画面 ピクセル(CGPoint) 見えている範囲だけ 現実世界 ⇨ 地図 をやめて、現実世界 ⇨ 画面 で解く 15

Slide 16

Slide 16 text

実現②:現実世界 ⇨ 画面の座標へ(軽量化) 現在地(緯度経度)を、地図の投影で画面上の CGPoint に変換 地図の上に別レイヤーを重ね、SwiftUI の Path で点線を描く 地図ビューには何も渡さない ⇨ 地図は再描画されない 描くのは画面の中だけ。現在地が画面外なら、画面枠との交点まで 16

Slide 17

Slide 17 text

画面外なら、画面枠との交点で止める 画面 ここで止める ピン(画面中央) 現在地(画面外) ピン ⇨ 現在地 の直線と、画面枠が交わる点。縦と横、先にぶつかる方を採用 17

Slide 18

Slide 18 text

参考:【数学B】直線のベクトル方程式 媒介変数 t P(t) = C + t × (T − C) 画面(x = xmax の辺) ピン(画面の中心) T 現在地 t C から T に向かって「何割」進んだか(0 〜 1) P(t) その割合の位置にある点 C C(t = 0) t = 0.5 枠に当たる t T(t = 1) つまり 上下左右の辺ごとに「その辺に着く t」 を計算し、いちばん小さい t(最初にぶつかる辺) で線を止める 18

Slide 19

Slide 19 text

画面枠との交点 — コード — // 画面枠との交点比率 t を x, y それぞれで求め、小さい方を採用 let tx = dx == 0 ? .infinity : ((dx > 0 ? bounds.maxX : bounds.minX) - center.x) / dx let ty = dy == 0 ? .infinity : ((dy > 0 ? bounds.maxY : bounds.minY) - center.y) / dy let t = min(tx, ty) return CGPoint(x: center.x + dx * t, y: center.y + dy * t) 19

Slide 20

Slide 20 text

実現③:現在地マーカー(半径 r)を考慮する Before:線が上に乗る After:r + 線幅ぶん離す Path は地図と別階層。マーカーの上から線を描くので、中心まで 引くと汚い ⇨ r + 線幅ぶん手前で止める 20

Slide 21

Slide 21 text

参考:【中3】三平方の定理と単位ベクトル ベクトルの大きさ・単位ベクトル C(ピン) ここから描く(r + 線幅) |v| = √(vx²+vy²) e(長さ1) T(現在地) vx |v| = √(vx² + vy²), e = v ÷ |v| 現在地からピンへ向かう矢印(横 vx、縦 vy) |v| その矢印の長さ(三平方の定理) e 長さを 1 にした矢印。向きだけを表す r 現在地マーカーの半径 v vy つまり 始点 = 現在地 + e × (r + 線幅)。「ピ ンの方向に、r + 線幅 だけ進んだ点」から線を 描く 21

Slide 22

Slide 22 text

マーカーを避ける — コード — // 現在地 → ピン方向の単位ベクトル let ux = v.x / length, uy = v.y / length // マーカー半径+線幅ぶん、ピン側へ寄せた点を始点に let adjusted = Self.currentLocationPinRadius + Self.lineWidth return CGPoint(x: target.x - ux * adjusted, y: target.y - uy * adjusted) 22

Slide 23

Slide 23 text

座標系の選択 座標変換は 「どの座標系で解くか」を 選ぶ道具、と捉えることができる 23

Slide 24

Slide 24 text

2 乗車禁止エリアと ピンの押し出し 地図上のポリゴンと、描けないエリア Smooth out the movement, back and forth, over there and here. newmo

Slide 25

Slide 25 text

地図上には、乗車禁止エリアのポリゴンがある 隣り合う・重なる 大小さまざま 乗り降りできない場所は、ポリゴンで 地図に描いてある 外部データ。大小さまざま、数も多い 隣り合う、隙間がある、重なる 25

Slide 26

Slide 26 text

禁止エリア以外にも、呼ばれると困る場所がある 公園の中、ビルの中… 呼ばれても合流しづらい、わかりづらい これも禁止エリアとして描く? ⇨ 地図がポリゴンだらけになる。表示できない 表示できない「非推奨エリア」に呼ばれるのを、どう防ぐ? 26

Slide 27

Slide 27 text

段階①:「ここで乗る」を押したら、サーバーがエラー この場所では乗車できません ここで乗る 地図には何も描かない 「ここで乗る」でサーバーがバリデー ション ⇨ エラー表示 機能としては、これで成立 でも、押すまでわからないのは面倒 27

Slide 28

Slide 28 text

段階②:ピンをそこに置いた時だけ、表示を変える このエリアでの乗車はできません ✕ ここで乗る そのエリアに来た時だけ、ピンが変わ り「ここで乗る」が押せない 禁止エリアと同じ扱い 押す前に気づける でも、どこならセーフ?がわからない 28

Slide 29

Slide 29 text

じゃあ、自動で外に押し出したい 置けない場所に来たら、 置ける場所へそっと逃がす どこへ? ⇨ いちばん近い、エリアの外 いつ? ⇨ 指を離した時 どうやって? ⇨ 地図上の幾何で解く 非推奨エリ 置ける場所に収まった 指を離すと、 いちばん近い外へ地図がすっと動く 29

Slide 30

Slide 30 text

いつ、何をする? 1 地図を動かしている間:内外判定だけ ⇨ ピンの色と吹き出しを切り替える 2 指を離した瞬間(カメラ停止):エリアの中なら、外の座標を計算 3 その座標へカメラをアニメーションで移動 ⇨ ピンが「そっと逃げる」 ドラッグ中は判定だけ。重い計算は止まった時に1回(ここも一度、性能で直した) 30

Slide 31

Slide 31 text

いまここ 現実世界 緯度・経度 ⇄ 地図 位置・ポリゴン 幾何が住む場所 ⇄ スマホ画面 ピクセル(CGPoint) ポリゴンとピンの位置関係 ⇨ 地図上の幾何として解く 31

Slide 32

Slide 32 text

押し出しは3ステップ 1 各辺への最近点(垂線の足)⇨ いち ばん近い辺を選ぶ 2 押し出す向き:ピン ⇨ 足 の直線 (辺に垂直で、外向き) 3 その向きに一定距離ずらす 禁止エリア ピン 垂線の足 32

Slide 33

Slide 33 text

参考:【数学B】内積と正射影 正射影ベクトル (P − A) · (B − A) t= , |B − A|² P(ピン) AH = t · AB A(t = 0) B(t = 1) H(垂線の足) H = A + t × (B − A) 辺の両端の点 P ピン H P から辺に下ろした垂線の足。辺の上で P にいちばん近 い点 t A から H までが、AB の「何割」か · 内積。x 同士と y 同士を掛けて足す A, B つまり t が 0〜1 なら H は辺の上。0 より小さ ければ A、1 より大きければ B がいちばん近い ⇨ max(0, min(1, t)) 33

Slide 34

Slide 34 text

最近点は t を 0…1 に丸めるだけ // 線分 start→end 上への射影パラメータ t を 0...1 に clamp let t = max(0, min(1, (px * dx + py * dy) / lengthSquared)) // 線分上の最近点(垂線の足)。t が 0 か 1 なら端点 let foot = CLLocationCoordinate2D( latitude: start.latitude + t * dy, longitude: start.longitude + t * dx ) 辺ごとに計算 ⇨ 距離が最小の辺を採用 34

Slide 35

Slide 35 text

押し出す向き 通常 ⇨ ピン → 足 の直線 境界線上 ⇨ 辺の法線 辺に垂直で、外向き 直線の長さ 0 ⇨ 向きが決まらない ピン(辺の上) ピン(エリア内) 2方向ある ⇨ 外側の方 境界線上(共有辺・隙間ゼロ・再帰の途中)では直線の長さが 0 ⇨ 辺に垂直な向きのうち、外側になる方を選ぶ 35

Slide 36

Slide 36 text

参考:【中学】垂直な直線と法線ベクトル 法線ベクトル に垂直な矢印は (−d , d ) または (d , −d ) d = (dx, dy) ( dy , − dx ) 2方向ある ⇨ 外側の方を採用 y x y x 辺に沿った矢印(横 dx、縦 dy) 法線 辺に垂直な矢印。x と y を入れ替えて片方の符号を反 転すると 90° 回る ± 垂直な矢印は表と裏の 2 本ある d d = ( dx , dy ) (−dy, dx) つまり 2 本のうち、少し進んでみてポリゴンの 外に出る方を採用。コードでは辺の方位角 ± 90° で同じことをしている 36

Slide 37

Slide 37 text

押し出す向き — コード — // 境界のすぐ近くでは法線、離れていれば直線 if distanceBetween(coordinate, foot) < 1.0 { calculateNormalHeading(at: foot, on: path) ?? 0 } else { computeHeading(from: coordinate, to: foot) } 境界線上の判定は「足まで 1m 未満」。法線は2方向あるので、少し進んで外側になる方を採用 37

Slide 38

Slide 38 text

どれくらい逃がす? — メートルを緯度経度に戻す — 「2m 逃がす」の 2m は距離。緯度経度は角度 足の位置と向きは、緯度経度を平面とみなして計算できる (数十 m の範囲なら誤差は無視できる) でも「その向きに 2m」は、緯度経度にそのまま足せない 方位角と距離から、球面上で 2m 先の緯度経度を求めてカメラに渡す 最後は必ず緯度経度に戻す。地図を動かす指示は、緯度経度でしか出せない 38

Slide 39

Slide 39 text

参考:【数学II】弧度法と弧の長さ l = Rθ l=R×θ θR l = Rθ 緯度・経度は「角度」 地球:R ≈ 6,371 km ⇔ θ=l÷R 地球の半径。約 6,371 km θ 中心角。ラジアン(360° = 2π) l 弧の長さ = 地面の上を進む距離 R つまり 緯度経度は「角度」。2 m 進みたいなら θ = 2 ÷ 6,371,000 ラジアンだけ角度を動か す。コードの distance / R がこれ 39

Slide 40

Slide 40 text

これで完成、、? 隣接エリアは、想定より難しかった 😇 A から押し出した先が、隣の B B の判定が働き、A へ押し戻す 境界で振動し、無限ループに 外部データの隙間・重なりを手で直 す? ⇨ 無理 A B A⇨B⇨A⇨B⇨… 40

Slide 41

Slide 41 text

3 隣接エリアと 凸包による統合 逃げた先にも、エリアがあったら? Smooth out the movement, back and forth, over there and here. newmo

Slide 42

Slide 42 text

発想:回避領域を動的に広げる 「押し出し先がまた禁止エリアなら、そこも取り込む」 1 2 3 4 ピンが A ⇨ A の外へ押し出す 押し出し先が B ⇨ A+B を統合 統合エリアの境界から、もう一度 入らなくなるまで再帰(上限5回) 外殻の外へ、一発で脱出 A+B を凸包で1つに 凸包(Convex Hull)= 点の集まりを輪ゴム で囲んだ外形 42

Slide 43

Slide 43 text

そもそも凸包アルゴリズムの歴史 凸包の歴史① 1972 Graham Scan 極角でソート ⇨ 左折だけ残す。O(N log N) 凸包の歴史② 1973 ギフト包装法(Gift wrapping) 包み紙を巻くように外周をたどる。簡単、O(N·h) 凸包の歴史③ 1979 Monotone chain(Andrew) x でソート ⇨ 上下の殻を作る。Graham の派生 参照:Wikipedia「Graham scan」「Gift wrapping algorithm」/ 50年前に枯れている(前提 1) 43

Slide 44

Slide 44 text

検討:統合の仕方 1 ポリゴン結合(Polygon Union)— 断念 凹みは保てる。でも低速、自己交差で壊れやすい 2 凹包(Concave Hull)— 断念 大小さまざまなエリアでは壊れやすい 3 凸包(Convex Hull)— 採用 O(N log N)、枯れている。凹みは消える 4 外接矩形(Bounding Box)— 断念 最速だが L 字を潰しすぎ 44

Slide 45

Slide 45 text

なぜ凸包? 完璧な形状 < 速さと堅牢さ 指を離すたびに走り、カメラが動く前に終わる ⇨ 速さ 外部データの隙間・重なりに耐える ⇨ 堅牢さ 1972年から枯れている ⇨ 実装が安定(前提 1) 45

Slide 46

Slide 46 text

注意点:凸包は凹みを埋める 凸包は、凹みを埋めてしまう 膨らみ(Bulge) 本当は置けるのに避ける A 点線 = A+B の凸包 B 影響が出るのは「押し出しでループし た時」だけ 直接そこに置けば、制御は走らない 「自動で逃がす時だけ、少し広めに避 ける」割り切り 46

Slide 47

Slide 47 text

コード(骨格・簡略化) // 通過した禁止エリアを凸包で1つの境界に let unified = createUnifiedPath(from: accumulated) let foot = findClosestEdge(to: origin, on: unified).foot let next = computeOffset(from: foot, distance: 2, heading: h) // 押し出し先が、まだ別の禁止エリアの中? 安全なら終了 let hits = allRestricted.filter { $0.contains(next) } if hits.isEmpty { return next } // 見つかったエリアを蓄積して再帰(起点は変えない・上限5回) return push(origin, accumulated: accumulated + hits, depth: depth + 1) 47

Slide 48

Slide 48 text

凸包は Graham Scan で 極角でソートして、左折だけ残す 内側の点は「右折」で除去 起点(最下点) 48

Slide 49

Slide 49 text

参考:【数学B】外積の符号と回転の向き 2次元の外積(行列式) c b 右折 ⇨ 負 ⇨ 捨てる a 左折 ⇨ 正 ⇨ 残す c b a (bx − ax)(cy − ay) − (by − ay)(cx − ax) 順番に並んだ 3 つの点 外積 a→b と a→c の 2 本の矢印が「どちら回りか」を表 す数。三角形の面積公式 ½|x1y2 − x2y1| の中身 a, b, c つまり 正なら反時計回り(左折)で残す、負な ら時計回り(右折)で捨てる、0 なら一直線。極 角は atan2 で求めてソート 49

Slide 50

Slide 50 text

Graham Scan — コード — // 起点を基準に極角でソート(O(N log N) の肝) let sorted = points .filter { distanceBetween($0, start) >= 0.1 } .sorted { polarAngle($0) < polarAngle($1) } // atan2 // 左折だけ残す(O(N))。外積の符号で右折を落とす return sorted.reduce(into: [start]) { hull, p in removeRightTurns(&hull, newPoint: p) hull.append(p) } 50

Slide 51

Slide 51 text

Before / After — 隣接エリアの押し出し — Before:今いるエリアだけ見る A B After:入った先を取り込む(凸包) A B C C 6 回目:A の外へ ⇨ B の中 …終わらない 3 回目:起点から A+B+C の凸包の外へ ⇨ 脱出 同じ位置から開始。Before は A と B を往復し続ける After は起点を固定したまま凸包を広げ、3 回目で外へ 51

Slide 52

Slide 52 text

おさらい)3つの座標 現実世界 地図 スマホ画面 ⇄ ⇄ 緯度・経度 押し出し=ここで幾何 点線=ここに落として描く 問題ごとに、解きやすい座標系で解いた 52

Slide 53

Slide 53 text

体験の変化 — 従来 vs 現在 — 課題 現在地を見失う 点線の描画 非推奨エリア 隣接エリア 従来 ピンだけ動く 地図側で描いて重い・消える 押してからエラー、どこがセーフか不明 往復して止まらない 現在 点線で結ぶ 画面座標で、画面内だけ描く 置ける場所へ、そっと押し出す 凸包で統合、一発で脱出 53

Slide 54

Slide 54 text

まとめ 1 現在地との関係は線で見せ、置けない場所は操作で伝える 2 座標変換は「どの座標系で解くか」を選ぶ道具 点線は画面座標で描く、押し出しは地図上の幾何で解く 3 隣接エリアは「回避領域を動的に広げる」。完璧な形状 < 速さと堅牢さ 4 枯れた数学は Agent が得意。テストで期待値を書く 54

Slide 55

Slide 55 text

参考資料 技術ブログ:Coding Agent と幾何学アルゴリズムを活用した地図上の滑ら かなピン操作の実現 参照:tech.newmo.me/entry/geometry-pin-moving Wikipedia:凸包/Graham scan/Gift wrapping algorithm ご清聴ありがとうございました アンケートお待ちしてます! 55