かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

TypeScript Object.entries の型をいい感じにしたい

環境

  • TypeScript v5

引数のオブジェクトを Record<string, unknown> とするパターン

返り値に as を使わないとタイプエラーになる

返り値 Entries を定義するパターン

type Entries<T> = (keyof T extends infer U
  ? U extends keyof T
    ? [U, T[U]]
    : unknown
  : unknown)[];

export const strictEntries = <T extends Record<string, unknown>>(
  object: T,
): Entries<T> => {
  return Object.entries(object) as Entries<T>;
};

return Object.entries(object) as Entries<T>; にしないと type error になる

返り値を [keyof T, T[keyof T]][] とするパターン

export const strictEntries = <T extends Record<string, unknown>>(
  object: T,
): [keyof T, T[keyof T]][] => {
  return Object.entries(object) as [keyof T, T[keyof T]][];
};

return Object.entries(object) as [keyof T, T[keyof T]][]; にしないと type error になる

オブジェクトを Record<string, any> にすれば返り値を [keyof T, T[keyof T]][] にすれば as を使わなくても良い

export const strictEntries = <T extends Record<string, any>>(
  object: T,
): [keyof T, T[keyof T]][] => {
  return Object.entries(object);
};

これなら type error にならない

詳しい挙動は調べてないが、どちらでもいい感じに形推論されるようになったからヨシ
おわり


[参考]

かもメモ 10周年と活動の見直しについて

自分は忘れっぽいのでやったことを後から自分でまた検索するだろうから検索しやすいブログとして書き残しておこうと始めたこのブログは 2024年で10年目になりました。
早いもので私自身は何も技術的に進歩できてない気もします… その一方役に立ったとかSNSで見かけると嬉しくもなりました。

そんなこんなで 10周年なのですが、本ブログについて活動を見直そうと思います。
端的に言えば、深く調べず備忘録的に書き残す程度になる想定です。

なぜ方針を変えるのか

  1. 無料公開する労力と合わなくなってきたから
  2. 問題解決は生成AIの時代になってきたから

1. 無料公開する労力と合わなくなってきたから

収益目的のブログではないのですが、それなりに調べたり、パターンを洗い出していると記事を作るにも時間が必要です。
時間がかかっている = 工数が発生している状態です。

今までは貼っている広告である程度の収益が出ていたのでモチベーションにもなっていました。
コロナ時期からPV数は下がってないのに広告収益が下がり始めました。その後広告収益は回復基調にあったのですが2022年を転機に PV / 収益とも下降の一途を辿っておりもはやこのブログでの広告収益は見込めないという認識になっています。

無料公開したまま収益を目的にするなら、まとめサイトみたいに広告を貼りまくり誤クリックを誘発するような広告枠も設けていく方向性しか無いでしょう (広告と仕組み的には大差ないマイニングプログラムが日本では否定されてしまったので)、しかしその様な広告を貼りまくったり誤クリック誘発するような広告は自分が良いと思うユーザー体験とはかけ離れている。むしろ反対なのでそうはしたくありません。(今入っている記事内に自動で差し込まれる広告も個人的には辞めたい)

閲覧数も減り収益にもならないので、このブログで書いているような内容は需要がなくなってきているという事だと思います。であれば自分だけ分かれば良い備忘録・ログとして書き残す程度以上に工数をかける必然性がないという判断となりました。

2. 問題解決は生成AIの時代になってきたから

コロナ時期に広告単価が下がるのは理解できます。では、2022年の転機は何だったのしょう? ChatGTP や Copilot といった AI がより身近になった年だったのではないかと思います。

プログラミング領域の疑問は生成AIでが得意とする領域です。このブログで扱っているようなプログラミングでの質問はもはや ChatGTP に聞いたり Copilot を使ったほうがより早く簡単に解決できる時代になっているのではないでしょうか?そしてデキるエンジニアほどそれらのツールを使いこなしているものです。
このブログに辿り着くような人は、今ハマっている問題の解決方法が欲しかったのであり、長ったらしい説明が欲しい訳ではでは無いでしょう。
多くは公式ドキュメントや GitHub / Stackoverflow を探せば分かることです。そのような AI が得意とする分野とモロに被ってしまう内容を懇切丁寧にまとめてもあまり意味がありません。なので自分用のメモなら調べた際に出てきた参考リンクを貼っておく程度で事足りるのでその様な方針にしたいと思います

 

以上のような理由から、かもメモ は 10周年ですがただの雑な自分用備忘録に転換します。
今までの記事はそのまま残しておこうと思いますが、今後しっかり調べたまとめは Zenn や Note に移行させる可能性があります。

生成 AI の台頭は無料公開されているサイトはまとめられるソースとなっていくでしょうから、どんどんページが閲覧することに依存する広告収入は下がっていくことになるのではないでしょうか。
車が発明され一般化した時、馬車の時代は終わり道端の馬糞を片付ける仕事は無くなりました。それでも車を無かったことにはできないように技術の進歩はラチェットです。
生成AIの台頭と一般化は無かったことには出来ない。しかしそうなっていくとコンテンツを作るにもお金が必要である以上、コンテンツはどんどん有料化したりログインしないと見れない世界になっていくのかもしれません。
本がかつてはお金持ちの知識階層だけのものだったのが、大学・図書館の発明で無料公開され、 web の発明で更に誰しもが知にアクセスできるようになりました。
コンテンツがどんどん有料化していくのであれば情報や知が時代に逆行しているようにも思えますが、資本主義的な最適化である捉えれば致し方ないようにも思えます。そうなればお金を払えない者はアクセスできる知や情報が制限されるかもしれません。

情報や知の時代の変わり目のようなものに立ち会えている肌感覚があり、歴史好きとしては未来がどうなるかとても興味深いです。実際問題として、どのような未来になるかは分かりませんが知へのアクセスはオープンで平等であって欲しいと願います。

おわり


歴ログは10周年ですがnoteに引っ越します - 歴ログ -世界史専門ブログ-

OpenStreetMap + React Leaflet で地図を表示したメモ

最近は地図関係のアプリを開発してて、ライブラリも多く実装したものを定期的にメモに残しておこうと思う

Google Map の API が高額でいろいろなレイヤーを重ね合わせるのが少し面倒そうだったので、OpenStreetMap を使う方法を探索した

OpenSteetMap を使えるライブラリはかなりたくさんあり色々試したが、本記事では React Leaflet でシンプルな地図を表示させたメモです

環境
  • React 18.2.0
  • leaflet 1.9.4
  • react-leaflet: 4.2.1

React Leaflet を使う準備

$ npm i react-leaflet leaflet
$ npm i -D @types/leaflet

React Leafret で OpenStreetMap を表示する

// Map.tsx
import { latLng } from "leaflet";
import { MapContainer, TileLayer } from "react-leaflet";
// leaflet のスタイルがないと地図が崩れる
import "leaflet/dist/leaflet.css";
// 地図を表示させるコンテナの height が必要
import "./map.css";

const position = latLng([35.3607411, 138.727262]);
const zoom = 5;

export const Map = (): JSX.Element => {
  return (
    <MapContainer center={position} zoom={zoom}>
      <TileLayer
        attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>'
        {/* OpenStreetMap のスタイルを選択して指定 */}
        url="https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png"
        maxZoom={13}
        minZoom={2}
      />
    </MapContainer>
  );
};
/* map.css */
.leaflet-container {
  width: 100%;
  height: 100vh;
}

React Leaflet + OpenStreetMap

これでシンプルな地図が表示できた。
ハマりどころは Leaflet の CSS (leaflet/dist/leaflet.css) が必要なことと、Leaflet が出力するコンテナの高さがないと地図が表示されないこと。TypeScript だと position を latLng 関数を挟まないと Type Error になることくらい

クリックした箇所に marker を表示させる

useMapEvent hook で地図上のイベントを取得できるので、クリックされた lat, long を取得して Marker として表示させる

// LocationMarkers.tsx
import { useState } from "react";
import { LatLng, latLng, icon } from "leaflet";
import { Marker, Popup, useMapEvent } from "react-leaflet";

const ICON = icon({
  iconUrl: "/images/leaflet/marker-icon.png",
  iconSize: [25, 41],
  iconAnchor: [12, 41],
  popupAnchor: [0, -30],
});

export const LocationMarkers = (): JSX.Element => {
const [markers, setMarkers] = useState<LatLng[]>([]);
  // useMapEvent は `<MapContainer>` の内側からしか呼び出せない
  const map = useMapEvent("click", (e) => {
    const { lat, lng } = e.latlng;
    setMarkers((markers) => [...markers, latLng(lat, lng)]);
  });

  return (
    <>
      {markers.map((marker) => (
        <Marker key={marker.toString()} position={marker} icon={ICON}>
          <Popup>{marker.toString()}</Popup>
        </Marker>
      ))}
    </>
  );
};

<MapContainer> 内に作成した <LocationMarkers /> コンポーネントを追加すれば OK

// Map.tsx
export const Map = (): JSX.Element => {
  return (
    <MapContainer center={position} zoom={zoom}>
      <TileLayer
        attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>'
        url="https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png"
        maxZoom={13}
        minZoom={2}
      />
      <LocationMarkers />
    </MapContainer>
  );
};

markers

地図のクリックした箇所にマーカーが表示されマーカーをクリックするとポップアップが表示されるようになった!

🐞 デフォルトのマーカー画像が表示されない問題

Leaflet.Marker.prototype.options.iconLeaflet.icon を渡す方法・Leaflet.Icon.Default.mergeOptions にアイコン画像を設定する方法を issue でみつけたが leaflet v1.9.4, react-leaflet v4.2.1 の環境では画像が壊れたままになってしまったので、カスタムアイコンを作って表示させた

h3-js で取得したエリアを React Leaflet で表示させる

h3geo.org h3-js は Uber の開発した世界を六角形のグリッドで表すJavaScript のライブラリ。
h3-js で取得した六角形のエリアを lat, long の矩形 (polygon) として地図上に表示させてみます

$ npm i h3-js

h3 では世界をどのサイズの六角形のグリッド化しているのかというサイズを resolution という数字で表しています。
今回はマーカーと同じようにクリックした位置が含まれる resolution を Polygon 化して地図上にプロットします。

// HexLayer.tsx
import { FC, useState } from "react";
import { LatLng } from "leaflet";
import { Polygon, useMapEvent } from "react-leaflet";
import { cellToBoundary, CoordPair, latLngToCell } from "h3-js";

const resolution = 3;

export const HexLayer: FC = () => {
  const [hexagons, setHexagons] = useState<{index: string, polygon: CoordPair[]}[]>([]);
  const map = useMapEvent("click", (e) => {
    const { lat, lng } = e.latlng;
    const h3Index = latLngToCell(lat, lng, resolution);
    const boundary = cellToBoundary(h3Index);

    setHexagons((hexagons) => [...hexagons, {
      index: h3Index,
      polygon: boundary,
    }]);
  });

  return (
    <>
      {hexagons.map(({ index, polygon }) => (
        <Polygon key={index} positions={polygon} />
      ))}
    </>
  );
}

h3 hex

h3-js で取得したエリアを Polygon として表示させることが出来ました!
※ Leaflet は左右に地図を移動させると経度が -600 や 1000 のように -180 - 180 の範囲を超えてしまうようです。h3-js の cellToBoundary で変換した座標は -180 〜 180 の間に収まる数値に変換されるため、地図を移動させまくると Ploygon が表示されないようにみえてしまいます

Sample

所管

React-Leaflet の example には実例が色々とコードと共に載っているので、扱いやすいライブラリだなと感じました。
一方で Leaflet に依存しているので、ちょっとした問題があると Leaflet 側まで見に行かなければならない事もあるのは注意が必要そうです (経度が延々大きくも小さくもなる問題とか)

また React-Leaflet は地図上に表示させているマーカーや矩形を 画像や SVG といった HTML 要素で表現しているので表示量が多くなると Canvas ベースのものより処理が重くなるのではないかな〜という気がしています。 単体でできることが豊富なのでサクッと地図を使ったモックなどを作るのには向いてそうですね!

おわり


[参考]

13歳からの地政学 サクッと読めて面白かった!