鯖江の来週末、7/24(土)は第2回、さばぷら開催!
固定URLでJSONを返すAPI「data.json」を作ってくれたので、さばぷらマップをリアルタイム更新化しました。

さばぷらマップ」(src on GitHub)
空から視点のぐるぐる回る地図は、オープンソース「Adopt Geodata」プロジェクトを活用しています。 APIで取得したJSONを使って、柔軟にデータ構築できるように「地図語り」をバージョンアップ!(参考、custom.html

画面録画ツール「screen-recoder」を画面サイズ指定とカーソルON/OFFオプションを追加するバージョンアップ。より気軽に共有できるようになりました

画面録画ツール screen-recorder」(src on GitHub

Code for FUKUIの初勉強会「GitHubはじめのいっぽ」


今回の会場は、越前市、中学生、高先生から社会人まで幅広く、11名で学びました。


GitHubはじめのいっぽ
スライドもオープンデータとして公開!題材として地域安全マップを使いましたが、ちょっとややこしかったので、もっとシンプルなものに更新。


マップアプリの元 - かんたんマップアプリ」(src on GitHub)
HTML1ファイルのみ、CSV形式でデータを加えると、マップとして反映するアプリです。 <!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width"> <script type="module" src="https://code4fukui.github.io/csv-map/csv-map.js"></script> <title>テストマップ</title> </head><body> <h1>テストマップ</h1> <csv-map> 名前,URL,geo3x3,photo ヨコガワ分店,https://volga-rice.jimdofree.com/,,volga-yokogawa.jpg サンドーム福井,http://www.sankan.jp/sundome/,E9138732236 福井高専,https://www.fukui-nct.ac.jp/,E9138732251953 </csv-map> </body> </html> こういうものはどうつくるといい? など、Issueもお気軽にどうぞ!

割と便利な、ウェブサイトへのかんたん地図組み込み、csv-mapタグmap-viewタグ。 マーカー色を指定したいとのご要望と合わせて、オープソースleaflet.spriteを教えていただいたので、ESモジュール化して対応。

csv-map tag demo
タグのcolor属性か、csvのcolor項目が、下記7色に該当すればその色のマーカーとなります。

colors: "blue", "green", "orange", "yellow", "red", "purple", "violet"

csv-map、CSVファイルのsrc属性での指定に加え、タグ内部へのCSV直接記述にも対応しました(参考HTML

leaflet.sprite-esは、Leaflet.mjsと組み合わせて、自在な地図アプリにも組み込めます。 import L from "https://code4sabae.github.io/leaflet-mjs/leaflet.mjs"; import { LeafletSprite } from "https://taisukef.github.io/leaflet.sprite-es/src/sprite.js"; LeafletSprite.init(L); // const color = "red"; const icon = L.spriteIcon(color) const marker = L.marker(latlng, { icon }); // ご活用ください!

csv-mapタグをベースに簡易化して、ウェブサイトにアクセス情報としての地図埋め込みに使う拡張タグ「map-viewタグ」を作りました。
map-view tag demo
name、緯度経度、zoomレベルを設定するだけでOK! <script type="module" src="https://code4fukui.github.io/map-view/map-view.js"></script> <map-view name="Hana道場" latlng="35.944571,136.186228" zoom="10"></map-view> サイトの表示優先なので、スクロールで地図が拡大縮小してしまうことがないように設定済み。
クリックするとGoogleマップのナビへリンクしています。

地図は日本国内を網羅する国土交通省、国土地理院の地理院地図
APIはオープンソースのLeafletをESモジュール化した、Leaflet.mjsです。

緯度経度付きのCSVデータを簡単に地図表示化するcsv-mapタグをバージョンアップ、位置情報付き写真でマッピングできるようにしました。(csv-map with exif-js on GitHub)

ふくいれきしまっぷ(仮)
ここで使っているCSVは、とってもシンプル、name, photo, url のみ(data.csv)
写真をGitHubにアップロードして、タイトルと関連URLを追記していくだけで地図上での整理ができます。

わずか2行のHTMLで、CSVデータをマップ表示できます <script type="module" src="https://code4fukui.github.io/csv-map/csv-map.js"></script> <csv-map height="80vh" src=./data.csv></csv-map> こちら、福井市東京事務所さんの縁で白鷺舎さんと始まった福井の歴史プロジェクト with Code for FUKUI、福井ゆかりの歴史スポットを写真と共に収集・公開予定。プルリクもぜひどうぞ!

GitHub? プルリク? という方、Code for FUKUIで研修も開催します!

気軽で多彩な共同作業&情報発信していきましょう!

緯度経度やGeo3x3の用に、地球上の場所を表す方法「ジオコードシステム」はいろいろあります。地理院地図でも参照可能な「場所情報コード/uPlace」を調べて実装、緯度経度地図に組み込みました。

緯度経度地図
マップを動かすとリアルタイムに緯度経度、Geo3x3、uPlaceが変わります。uPlaceは約3m単位で論理場所情報コードで表示しています。Geo3x3はこの地図での細かさに合わせてlevel20としています。

uPlace 場所情報コードは2種類あります。
1. シリアル付き場所情報コード、細かい緯度経度と所有者をデータベースに登録申請できます
2. 論理場所情報コード、申請不要で緯度経度と高さ情報で生成できます(精度約3m)

ブラウザとDenoで使えるESモジュールとして実装したので、下記のように簡単にエンコード、デコード可能です。(src on GitHub)

import { uPlace } from "https://taisukef.github.io/uPlace/uPlace.js"; const code = "00001B000000000309DF3925687B1D00"; const pos = uPlace.decode(code); console.log(pos); const code2 = uPlace.encode(35.942729, 136.198835, 8); // めがね会館8F console.log(code2);

3m精度で建物の階数指定できるので郵便や配達などにも便利そうです。

uPlaceは128bit/16byte/16進法32文字の ucode system の一種。 場所情報コードの仕様書を元に実装し、deno test でテストできるテストコード uPlace.test.js も書いたので割とちゃんと動きます。 (参考、テスト駆動開発(TDD)で安心、JavaScriptプログラミング!Denoでwebアプリ開発編

下記、uPlace.js内デコードする部分のJavaScriptのコードです。JavaScriptでのビット演算は32bitまでなので、緯度経度部分を分割するひと手間かけてます。

const decode = (code) => { if (!code || typeof code != "string" || code.length != 32) { throw new Error("not a ucode (hex encoded 32byte str)"); } const version = parseInt(code.substring(0, 1), 16); const tldcode = parseInt(code.substring(1, 1 + 4), 16); const classcode = parseInt(code.substring(1 + 4, 1 + 4 + 1), 16); const dc = parseInt(code.substring(1 + 4 + 1, 1 + 4 + 1 + 10), 16); const ic1 = parseInt(code.substring(1 + 4 + 1 + 10, 1 + 4 + 1 + 10 + 8), 16); const ic2 = parseInt(code.substring(1 + 4 + 1 + 10 + 8), 16); //console.log(version, tldcode, classcode, dc, ic1.toString(16), ic2.toString(16)); // .toString(16), ic) if (version != 0 || tldcode != 1 || classcode != 11 || dc != 3) { throw new Error("not a uPlace"); } const type = ic1 >>> (62 - 32); if (type != 0) { throw new Error("only supported type is 0"); } const south = (ic1 >> (61 - 32)) & 1; const latsec = (ic1 >> (1 + 23 + 8 + 1 + 6 - 32)) & 0x3fffff; // 22bit const west = (ic1 >> (23 + 8 + 1 + 6 - 32)) & 1; const lngsec = (((ic1 & 0x3f) << 17) | ((ic2 >>> (8 + 1 + 6)))) & 0x7fffff; // 23bit const levelx = (ic2 >> (1 + 6)) & 0xff; const level = levelx >= 0xfe ? levelx : levelx - 50; const levelmid = (ic2 >> 6) & 1; const serial = ic2 & 0x3f; const lat = latsec / (10 * 60 * 60) * (south ? -1 : 1); const lng = lngsec / (10 * 60 * 60) * (west ? -1 : 1); if (lat < -90 || lat > 90 || lng < -180 || lng > 180) { throw new Error("out of the earth"); } // console.log(type, south, latsec, lat, west, lngsec, lng, level, levelmid, serial); return { lat, lng, level, levelmid, serial }; };

32文字の固定長で長いプリフィックスがあるので、他のコードと間違いにくいところが結構いい感じです。 大まかに示したり、より高い精度で示すことはできないので、そんな場合はGeo3x3が便利です。 位置情報、DXしましょう!

2021/5/12、UNVT(国連ベクトルタイル)ワークショップ日本語版開催!
UNVTワークショップ日本語版 公開録画 | 512

さばえを楽しくまちあるき「さばぷら」がオープンデータ化された記念に、国連ベクトルタイルの派生プロジェクト「Adopt Geodata」の一環「a storytelling helper / 地図語り」を使ってみました。

さばぷらマップ」(src on GitHub)
ひとまずAPI替わりに download.js でスクレイピングし、geo3x3.csv で作成した位置情報を作成し、make.js で生成。


オリジナル地図作りを進めるため「オープンな地図と地図アプリの動かし方」を解析し、オープンストリートマップのデータ「JA:PBFフォーマット - OpenStreetMap Wiki / osm.pbf」の読み込みをESモジュール化。 base64-jsと、Bufferを移植し、osm-pbf-parserをESモジュール化した「OSMPBFParser.js」ができました。Denoでサクッと動かせます。

import { OSMPBFParser } from "https://taisukef.github.io/osm-pbf-parser/es/OSMPBFParser.js"; const parser = await new OSMPBFParser((items) => { console.log(items); }).init(); const url = "./test/extract.osm.pbf"; const file = await Deno.open(url); for await (const value of Deno.iter(file)) { parser.parse(value); } file.close();

こちら sh2 で紹介している、オープンストリートマップ(OSM)のデータダウンロード「Geofabrik Download Server」から、osm.pbf 形式のファイルをダウンロードして変換できます。(src on GitHub)

2021/5/12、UNVT(国連ベクトルタイル)ワークショップ日本語版開催!
UNVTワークショップ日本語版 公開録画 | 512

オープンな地図アプリづくり。オープンストリートマップと地理院地図から国連ベクトルタイル(UNVT)への変換環境が整ったので、ちょっと寄り道して、地図ライブラリを自作してみることにしました。

3DライブラリThree.jsはうれしいことに、ESモジュール対応が進んでいたので、ベクトルタイルを読み込み、WebGLで表示させてぐるぐる回すサンプル完成!

mapthree-es demo1」(src on GitHub)
JavaScriptが主に書かれた82行のHTMLが1ファイルのみ。ローカルで開いても動きます。apikeyも何か別にインストール必要も一切ない、クリーンなJavaScriptです。(vector-tile、point-geometryをforkしてESモジュール化して使用しています)

zxyのディレクトリ毎に分割された地図、あとは大きさとオフセットが設定して、拡大率毎に順次読み込み表示するようにすれば地図っぽくなりそうです。

下記、82行のHTML、意外と簡単なつくりですよっ

<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width"> <script type="module"> import { Pbf } from "https://taisukef.github.io/pbf/Pbf.js"; import { fetchBin } from "https://code4sabae.github.io/js/fetchBin.js"; import { VectorTile } from "https://taisukef.github.io/vector-tile-js/VectorTile.js"; import * as THREE from "https://unpkg.com/three@0.128.0/build/three.module.js"; import { OrbitControls } from 'https://cdn.jsdelivr.net/npm/three@0.121.1/examples/jsm/controls/OrbitControls.js'; const addPbfToScene = async (scene, url, color) => { const data = await fetchBin(url); const tile = new VectorTile(new Pbf(data)); for (const name in tile.layers) { const layer = tile.layers[name]; for (let j = 0; j < layer.length; j++) { const feature = layer.feature(j); const geo = feature.loadGeometry(); // make for (const g of geo) { const vertices = []; for (const p of g) { vertices.push(new THREE.Vector3(p.x / 5, 0, p.y / 5)); } const geometry = new THREE.BufferGeometry().setFromPoints(vertices); const material = new THREE.PointsMaterial({ size: 2, color, }); const mesh = new THREE.Line(geometry, material); //const mesh = new THREE.Points(geometry, material); scene.add(mesh); } } } }; onload = async () => { document.body.style.margin = "0"; document.body.style.overflow = "hidden"; const canvas = document.createElement("canvas"); document.body.appendChild(canvas); const renderer = new THREE.WebGLRenderer({ canvas }); const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(45); camera.far = 10000; addPbfToScene(scene, "https://taisukef.github.io/sh2/zxy/7/112/50.pbf", 0xffffff); addPbfToScene(scene, "https://taisukef.github.io/sh2/zxy/9/450/202.pbf", 0x8888ff); addPbfToScene(scene, "https://taisukef.github.io/sh2/zxy/13/7190/3216.pbf", 0xff8800); addPbfToScene(scene, "https://taisukef.github.io/sh2/zxy/13/7190/3217.pbf", 0x88ff88); // const controls = new OrbitControls(camera, renderer.domElement); let th = 0; const tick = () => { th += .02; camera.position.x = 1000 * Math.sin(th); camera.position.z = 1000 * Math.cos(th); camera.position.y = 1000; camera.lookAt(new THREE.Vector3(500, 0, 500)); renderer.render(scene, camera); requestAnimationFrame(tick); }; tick(); onresize = () => { const width = innerWidth; const height = innerHeight; renderer.setPixelRatio(devicePixelRatio); renderer.setSize(width, height); camera.aspect = width / height; camera.updateProjectionMatrix(); }; onresize(); }; </script> </head> </html>

一緒に作ってみたい人、GitHubへどうぞ!→ mapthree-es on GitHub

GTFSとは、Googleマップとも連携する便利なバス情報オープンデータの世界標準です。

GTFSと、リアルタイムな位置情報オープンデータ、RTFSリアルタイムを表示するwebアプリを作りました。(GTFS = General Transit Feed Specification)

GTFSmap - 宇野自動車
IchigoJamで開発されたバスロケーションシステム用デバイス「いちごロケ」が活躍する岡山県の「宇野バス」さんの様子を表示。


GTFSmap - 宇野自動車
拡大した様子。建物がなんちゃって3D表示されてます。右クリックしながらマウス操作でグリグリ回転できて楽しいです。 マーカーが奥行き無関係になっているのは直したい。地図ライブラリ「MapLibre」はオープンソースなので直してプルリクすることもできます。

Googleが提唱するGTFSは、GitHub上「google/transit」でその仕様策定が進められています。「GTFS リアルタイムとは」に多言語でまとめられています。 変更プロセスの概要基本方針など、世界中でひとつのものを作るプロセスにワクワクします。

GTFS公開している方、ブラウザから直接取得できるようにHTTPS化と、CORS対応していただけるとうれしいです。 最近のブラウザではHTTPSとHTTPを混ぜて使用することができず、CORS対応のヘッダーがないと取得できません。 「Access-Control-Allow-Origin: *」とHTTPのレスポンスヘッダーに付けていただけると、プロキシーを通さず直接アクセスできるようになります。

GTFSリアルタイム一覧」(公共交通オープンデータ一覧 (GTFS、標準的なバス情報フォーマット(GTFS-JP))より抜粋)
HTTPS対応5事業者のアクセスを試みましたが、CORS対応しているところはまだなかったので、今回はすべてプロキシー経由としました。 こういったことも、ドキュメントとして追記検討も良さそうですね。

実現するために準備したもの
1. zlib.js のESモジュール化 (src on GitHub)
GTFSはCSVファイル(拡張子はtxt)をzip圧縮してあるので、展開するコードzlib.jsunzip.min.jsをESモジュール化して、unzip.jsを作成。 改造ポイントは2つ、this を globalThis に変えて、export。Denoやブラウザから同じコードでシンプルに使えます。

import { unzip } from "https://taisukef.github.io/zlib.js/es/unzip.js"; const data = await Deno.readFile("wakayama.zip"); // or fetch const zips = unzip(data); const filenames = zips.getFilenames(); console.log(filenames); const plain = new TextDecoder().decode(zips.decompress(filenames[0])); console.log(plain);

2. gtfs2json 作成 (src on GitHub)
1で準備した unzip.js と CSV.js を使って簡単。

import { CSV } from "https://js.sabae.cc/CSV.js"; import { unzip } from "https://taisukef.github.io/zlib.js/es/unzip.js"; const gtfs2json = (bin) => { const res = {}; const zips = unzip(bin); const fns = zips.getFilenames(); for (const fn of fns) { const data = new TextDecoder().decode(zips.decompress(fn)); const csv = CSV.decode(data); const json = CSV.toJSON(csv); const name = fn.substring(0, fn.length - 4); // cut .txt res[name] = json; } return res; }; export { gtfs2json };

3. GTFSリアルタイムをJSON化するコードを作成 (src on GitHub)
Node.jsに対応している gtfs-realtime-bindings を発見、Protocol Buffers にしか依存していないので、移植は楽。 ちょっと前に移植した protobuf-es.js がすぐに役立った。importとexportを変更してできあがり。JavaScript / Deno対応とプルリク。

4. GTFSモジュールとしてまとめる
URLを渡すとJSONでそれぞれ返すモジュールにすると便利。

import { GtfsRealtimeApi } from "https://taisukef.github.io/gtfs-realtime-bindings/deno/gtfs-realtime.js"; import { gtfs2json } from "./gtfs2json.js"; import { fetchBin } from "./fetchBin.js"; const GTFS = { async fetch(url) { const data = await fetchBin(url); const json = gtfs2json(data); return json; }, async fetchRT(url) { const data = await fetchBin(url); const feed = GtfsRealtimeApi.transit_realtime.FeedMessage.decode(data); return feed; }, }; export { GTFS };

5. webアプリ、GTFSマップを作る (src on GitHub)
あとは、GTFSモジュール、Maplibre、Geo3x3を使って組み立てるだけ!

import { GTFS } from "https://taisukef.github.io/gtfs-map/GTFS.js"; import { maplibregl } from "https://taisukef.github.io/maplibre-gl-js/maplibre-gl-es.js"; import { sleep } from "https://js.sabae.cc/sleep.js"; import { Geo3x3 } from "https://taisukef.github.io/Geo3x3/Geo3x3.mjs"; onload = async () => { const url_gtfs = "http://www3.unobus.co.jp/opendata/GTFS-JP.zip"; const url_gtfsrt = "http://www3.unobus.co.jp/GTFS/GTFS_RT-VP.bin"; const update_interval_sec = 15; // 地図表示 (MapLibre API: https://maplibre.org/maplibre-gl-js-docs/api/ ) const mapgl = maplibregl; const map = new mapgl.Map({ container: "map", hash: true, style: 'https://taisukef.github.io/gsivectortile-3d-like-building/building3d.json', zoom: 15, minZoom: 4, maxZoom: 17.99, bearing: -40, pitch: 60, }); // GTFS取得とタイトル表示 const gtfs = await GTFS.fetch(url_gtfs); console.log(gtfs); title.textContent = "GTFSmap - " + gtfs.agency[0]?.agency_name; // バス停表示 const lls = new mapgl.LngLatBounds(); for (const e of gtfs.stops) { const ll = [e.stop_lon, e.stop_lat]; const marker = new mapgl.Marker({ color: "#ff9999" }).setLngLat(ll).addTo(map); lls.extend(ll); const geo3x3 = Geo3x3.encode(e.stop_lat, e.stop_lon, 20); const popup = new mapgl.Popup({ className: "popup" }) .setLngLat(ll) .setHTML(`<a href=${e.stop_url}>${e.stop_name}</a><br><a href=https://geo3x3.com/#${geo3x3}>${geo3x3}</a>`) .setMaxWidth("300px"); marker.setPopup(popup); } map.fitBounds(lls); // update_interval_sec秒おきにGTFSrealtimeでバス情報の取得と表示 for (;;) { const feed = await GTFS.fetchRT(url_gtfsrt); for (const e of feed.entity) { const pos = e.vehicle.position; const ll = [pos.longitude, pos.latitude]; const marker = new mapgl.Marker().setLngLat(ll).addTo(map); const popup = new mapgl.Popup({ className: "popup" }) .setLngLat(ll) .setHTML(JSON.stringify(e.vehicle)) .setMaxWidth("300px"); marker.setPopup(popup); } await sleep(update_interval_sec * 1000); } };

index.htmlを手元において、ブラウザで開くだけで動きますよ!


ブラウザでデバッガーを立ち上げると、JSON化されたGTFSデータを読めます。


GTFSmap 和歌山バス
こちら、和歌山のバスのGTFSを表示してみるサンプルです。みなさんのまち、GTFSオープンデータはありますか?

試験公開されている国土交通省国土地理院による「地理院地図Vector」は、GitHubでのオープンソースとして公開されているので、改造自在! MapBoxで表示されていたコードを派生して独自オープンソースで開発されているMapLibreに変更したものを作成しました。

3D like building map」(src on GitHub
シンプルなJavaScriptのコードで、はじめられるマップアプリができました!

<script type="module"> import { maplibregl } from "https://taisukef.github.io/maplibre-gl-js/maplibre-gl-es.js"; const map = new maplibregl.Map({ container: 'map', hash: true, style: './building3d.json', center: [139.762529,35.680757], zoom: 15, minZoom: 4, maxZoom: 17.99, bearing: -40, pitch: 60, doubleClickZoom: false, localIdeographFontFamily: false }); //UI map.addControl(new maplibregl.NavigationControl(), 'bottom-right'); map.addControl(new maplibregl.ScaleControl()); map.showTileBoundaries = false; map.showCollisionBoxes = false; //地図切替え btn_setstylestd.onclick = () => { map.setStyle('./building3d.json'); }; btn_setstylephoto.onclick = () => { map.setStyle('./building3dphoto.json'); }; btn_setstyledark.onclick = () => { map.setStyle('./building3ddark.json'); }; </script>

オープンストリートマップ版のデータと比較すると、地理院地図の方がデータは一見豊富そう。 いろいろこれにさまざまなオープンデータなどを足して、いいとこ取りするなどすると、良い地図ができそうです。


taisukef/gsivectortile-3d-like-building: ベクトルタイルを用いた3D風地図 on GitHub」
呼び出すだけのシンプルなプロジェクトから始められます。

ベクトルタイルと呼ばれる、タイル状に分割されたベクトルデータは、pbfというバイナリ形式のデータファイルになっています。 バイナリデータはJSONやCSVなどのテキストファイルと違ってテキストエディタでさっと見ることはできませんが、同じ中身でも容量が小さく押さえられ、高速に処理できます。特に、地図のような多量のデータではその差が大きくでます。

鯖江独自のデータをどんどん追加するために、pbfを扱いやすいESモジュール対応しました。
forked 「taisukef/pbf: A low-level, lightweight protocol buffers implementation in JavaScript.
forked 「taisukef/ieee754: Read/write IEEE754 floating point numbers from/to a Buffer or array-like object.
Denoで下記コードでお試しください。(例えば、fetch_test.jsとして保存)

import { Pbf } from "https://taisukef.github.io/pbf/Pbf.js"; import { Tile } from "https://taisukef.github.io/pbf/bench/vector_tile.js"; const url = "https://cyberjapandata.gsi.go.jp/xyz/experimental_bvmap/6/57/25.pbf"; const data = await (await fetch(url)).arrayBuffer(); const tile = Tile.read(new Pbf(data)); console.log(tile);

動かす

$ deno run --allow-net fetch_test.js

Denoは、Node.jsと違って、何もインストールしなくてもすぐ動かせて便利ですね!

pbfは、Googleが開発したバイナリ化規格 Protocol Buffers の軽量版として開発されたもので、その比較として登場していたので、protobuf-es.js としてついでにES対応。
taisukef/protobuf-es.js: Protocol Buffers for JavaScript (& TypeScript).
下記のようなコードで、protoファイルを使った読み込みなども試せます。

import { protobuf } from "https://taisukef.github.io/protobuf-es.js/dist/protobuf-es.js" const data = await Deno.readFile('../test/fixtures/12665.vector.pbf'); protobuf.load('vector_tile.proto', async (err, res) => { const tile = res.lookup('vector_tile.Tile'); const decoded = tile.decode(data); console.log("decode", decoded); const tileJSON = JSON.stringify(decoded); await Deno.writeTextFile("test-protobuf.json", tileJSON); });

24年前は全部自分で実装していたバイナリ化と地図表示。現代はオープンソースの力でサクサク進みますね!
オープンで楽しいオリジナル地図づくり、着々進行中。

Tweet
クリエイティブ・コモンズ・ライセンス
この作品は「Creative Commons — CC BY 4.0」の下に提供されています。
CC BY / @taisukef / アイコン画像 / プロフィール画像 / RSS