チュートリアル10
提供: svg2wiki
(版間での差分)
(→使用するデータ) |
(→rasterMesh.html) |
||
(1人の利用者による、間の30版が非表示) | |||
3行: | 3行: | ||
特徴的なコードはレイヤーに紐付いたwebAppにあります。 | 特徴的なコードはレイヤーに紐付いたwebAppにあります。 | ||
+ | |||
+ | |||
+ | * 実際の動作は、[https://svgmap.org/devinfo/devkddi/tutorials/mesh3/mesh3.html こちら]をクリック。 | ||
+ | * 使用ファイルの[https://www.svgmap.org/devinfo/devkddi/tutorials/mesh3.zip ZIPアーカイブファイル] | ||
+ | |||
+ | |||
+ | |||
==使用するデータ== | ==使用するデータ== | ||
− | + | 地理院が[https://fgd.gsi.go.jp/download/geoid.php こちらのページで公開するジオイド高データ](TEXTデータ)を使用します。 | |
+ | |||
+ | [https://svgmap.org/devinfo/devkddi/tutorials/mesh3/gsigeo2011_ver2_1.asc 実際に使用するデータ] | ||
このデータの詳細な仕様は上記サイトで配布されているパッケージ同梱文書('''asc取扱説明書.pdf''')に記載されていますが、基本的にはテキストの[[チュートリアル8#Raster|Raster形式]]です。 | このデータの詳細な仕様は上記サイトで配布されているパッケージ同梱文書('''asc取扱説明書.pdf''')に記載されていますが、基本的にはテキストの[[チュートリアル8#Raster|Raster形式]]です。 | ||
− | + | データ形式としては、カンマ区切りでなく空白文字区切り 一つのRaw(桁)が1行で終結せず、255文字で改行される点が注意点です | |
− | + | データの内容としては、グリッドデータの原点の定義と、それをビットイメージ画像として可視化したときの原点との違いに注意が必要です。(下記2点) | |
* Y(緯度)軸の向きが逆のため、元のラスターデータの原点は南端、ビットイメージの原点は上端、 | * Y(緯度)軸の向きが逆のため、元のラスターデータの原点は南端、ビットイメージの原点は上端、 | ||
* ラスターデータの原点は、それをビットイメージとして可視化したときのピクセルの中心位置なのに対し、ビットイメージを配置するときの原点はピクセルの左上隅 | * ラスターデータの原点は、それをビットイメージとして可視化したときのピクセルの中心位置なのに対し、ビットイメージを配置するときの原点はピクセルの左上隅 | ||
+ | * [[ファイル:Raster.png|350px]] [https://svgmap.org/devinfo/devkddi/tutorials/mesh3/mesh3_raster_exp.svg 説明図svg] | ||
+ | |||
+ | ==[https://svgmap.org/devinfo/devkddi/tutorials/mesh3/mesh3.html mesh3.html]== | ||
+ | これまでと特に変わったところはありません。 | ||
+ | ==[https://svgmap.org/devinfo/devkddi/tutorials/mesh3/Container.svg Container.svg]== | ||
+ | これまでと特に変わったところはありません。 | ||
+ | ==[https://svgmap.org/devinfo/devkddi/tutorials/mesh3/rasterMesh.svg rasterMesh.svg]== | ||
+ | *WebAppが(下記rasterMesh.html)が紐付けられた空白のコンテンツです。 | ||
+ | *表示とともにwebAppのウィンドが出現するように指定しています。 | ||
+ | *これまでと特に変わったところはありません。 | ||
+ | |||
+ | <pre> | ||
+ | <?xml version="1.0" encoding="UTF-8"?> | ||
+ | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" data-controller="rasterMesh.html#exec=appearOnLayerLoad" viewBox="-42.8202042942663, -49.9999999999999, 513.842451531196, 600" property="Local government codes"> | ||
+ | |||
+ | <globalCoordinateSystem srsName="http://purl.org/crs/84" transform="matrix(100,0,0,-100,0,0)" /> | ||
+ | </svg> | ||
+ | </pre> | ||
+ | |||
+ | ==[https://svgmap.org/devinfo/devkddi/tutorials/mesh3/rasterMesh.html rasterMesh.html], [https://svgmap.org/devinfo/devkddi/tutorials/mesh3/rasterMesh.js rasterMesh.js]== | ||
+ | |||
+ | 読み込んだテキストのラスターデータを用いてビットイメージを動的に生成、これを紐付けられたrasterMesh.svgに張り付けて可視化します。 | ||
+ | |||
+ | *<code>onload=async function()</code> | ||
+ | **<code>await buildData()</code> メッシュデータを読み込みグローバル変数に保存する非同期関数 | ||
+ | **<code>var duri = buildImage()</code> | ||
+ | ***指定されたCanvas要素を作業用に使用し、読み込んだデータからビットイメージを生成、[https://developer.mozilla.org/ja/docs/Web/HTTP/Basics_of_HTTP/Data_URIs dataURI]として出力する | ||
+ | ***<code>canvas.toDataURL()</code> [https://developer.mozilla.org/ja/docs/Web/API/HTMLCanvasElement/toDataURL canvasオブジェクトのtoDataURL]でPNGビットイメージをDataURLとして生成しています | ||
+ | **<code>imageGeoArea</code> [[#.E4.BD.BF.E7.94.A8.E3.81.99.E3.82.8B.E3.83.87.E3.83.BC.E3.82.BF|データの注意点]]での指摘の通り、ビットイメージをSVG座標に張り付けるための領域情報を計算しています | ||
+ | **<code>buildSvgImage()</code> 生成したビットイメージ(dataURI)およびその領域情報を使って、webAppに紐付いたSVG DOMの中に、ビットイメージを張り付ける | ||
+ | ***<code>svgImage</code> このwebAppに紐付いたSVGコンテンツのDOM(Documentオブジェクト)があらかじめ定義されている | ||
+ | **<code>svgMap.refreshScreen()</code> 非同期での読み込みとデータ生成・SVGMapのDOM編集が完了したら再描画を明示し画面に反映する ([[解説書#.E5.86.8D.E6.8F.8F.E7.94.BB.E3.81.AE.E5.88.B6.E9.99.90|参考]]) | ||
+ | |||
+ | |||
+ | rasterMesh.js | ||
+ | <pre> | ||
+ | // Description: | ||
+ | // MeshData Visualizer. | ||
+ | // | ||
+ | // History: | ||
+ | // 2022/02/14 : 1st rev. | ||
+ | |||
+ | // 読み込んだASCIIデータを保持するグローバル変数 | ||
+ | var geoidGrid=[]; | ||
+ | var dataProps; | ||
+ | |||
+ | onload = async function(){ | ||
+ | // メッシュデータを読み込みグローバル変数に保存 | ||
+ | await buildData(); | ||
+ | // 読み込んだデータからdataURIとしてビットイメージを生成 | ||
+ | var duri = buildImage(geoidGrid,document.getElementById("geoidCanvas")); | ||
+ | // 生成した画像の地理的な範囲 | ||
+ | // 画像になると、グリッドの点は画像のピクセルの中心となることに注意! | ||
+ | var imageGeoArea={ | ||
+ | lng0: dataProps.glomn - dataProps.dglo/2, | ||
+ | lat0: dataProps.glamn - dataProps.dgla/2, | ||
+ | lngSpan: dataProps.nlo * dataProps.dglo, | ||
+ | latSpan: dataProps.nla * dataProps.dgla | ||
+ | } | ||
+ | if ( typeof(svgMap)=="object" ){ | ||
+ | buildSvgImage(duri,imageGeoArea); // SVGコンテンツを生成 | ||
+ | svgMap.refreshScreen(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | async function buildData(){ | ||
+ | var gtxt = await loadText("gsigeo2011_ver2_1.asc"); | ||
+ | |||
+ | gtxt = gtxt.split("\n"); | ||
+ | |||
+ | dataProps = getHeader(gtxt[0]); | ||
+ | var gx=0, gy=0; | ||
+ | var geoidGridLine=[]; | ||
+ | for ( var i = 1 ; i < gtxt.length ; i++){ | ||
+ | var na = getNumberArray(gtxt[i]); | ||
+ | gx += na.length; | ||
+ | geoidGridLine = geoidGridLine.concat(na); | ||
+ | if ( gx >= dataProps.nlo ){ | ||
+ | geoidGrid.push(geoidGridLine); | ||
+ | geoidGridLine=[]; | ||
+ | gx=0; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | function buildImage(geoidGrid, canvas){ | ||
+ | // | ||
+ | canvas.width=dataProps.nlo; | ||
+ | canvas.height=dataProps.nla; | ||
+ | var context = canvas.getContext('2d'); | ||
+ | var imageData = context.getImageData(0, 0, canvas.width, canvas.height); | ||
+ | var pixels = imageData.data; | ||
+ | for ( var py = 0 ; py < dataProps.nla ; py++ ){ | ||
+ | var dy = dataProps.nla - 1 - py | ||
+ | for ( var px = 0 ; px < dataProps.nlo ; px++ ){ | ||
+ | var base = (dy * dataProps.nlo + px) * 4; | ||
+ | if ( geoidGrid[py][px]!=999){ | ||
+ | |||
+ | var hue = (1-(geoidGrid[py][px]-dataProps.minVal)/(dataProps.maxVal-dataProps.minVal))*270; | ||
+ | var rgb = HSVtoRGB(hue,255,255); | ||
+ | |||
+ | pixels[base + 0] = rgb.r; // Red | ||
+ | pixels[base + 1] = rgb.g; // Green | ||
+ | pixels[base + 2] = rgb.b; // Blue | ||
+ | pixels[base + 3] = 255; // Alpha | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | context.putImageData(imageData, 0, 0); | ||
+ | |||
+ | var duri = canvas.toDataURL('image/png'); | ||
+ | return ( duri ); | ||
+ | } | ||
+ | |||
+ | function getHeader(line){ | ||
+ | var datas = parseLine(line); | ||
+ | return { | ||
+ | glamn:Number(datas[0]), | ||
+ | glomn:Number(datas[1]), | ||
+ | dgla:Number(datas[2]), | ||
+ | dglo:Number(datas[3]), | ||
+ | nla:Number(datas[4]), | ||
+ | nlo:Number(datas[5]), | ||
+ | ikind:Number(datas[6]), | ||
+ | vern:datas[7], | ||
+ | minVal:9e99, | ||
+ | maxVal:-9e99 | ||
+ | } | ||
+ | } | ||
+ | |||
+ | function getNumberArray(line){ | ||
+ | var ans = []; | ||
+ | var lineArray = parseLine( line ); | ||
+ | for ( var col of lineArray){ | ||
+ | var val = Number(col); | ||
+ | if ( val != 999){ | ||
+ | if ( val > dataProps.maxVal){ | ||
+ | dataProps.maxVal=val; | ||
+ | } | ||
+ | if ( val < dataProps.minVal){ | ||
+ | dataProps.minVal=val; | ||
+ | } | ||
+ | } | ||
+ | ans.push(val); | ||
+ | } | ||
+ | return ( ans ); | ||
+ | } | ||
+ | |||
+ | function parseLine(line){ | ||
+ | var ans = line.trim().split(/\s+/) | ||
+ | return (ans); | ||
+ | } | ||
+ | |||
+ | |||
+ | async function loadText(url){ // テキストデータをfetchで読み込む | ||
+ | messageDiv.innerText="ジオイド高データを読み込み中です"; | ||
+ | var response = await fetch(url); | ||
+ | var txt = await response.text(); | ||
+ | messageDiv.innerText=""; | ||
+ | return ( txt ); | ||
+ | } | ||
+ | |||
+ | function HSVtoRGB (h, s, v) { // from http://d.hatena.ne.jp/ja9/20100903/1283504341 | ||
+ | var r, g, b; // 0..255 | ||
+ | while (h < 0) { | ||
+ | h += 360; | ||
+ | } | ||
+ | h = h % 360; | ||
+ | |||
+ | // 特別な場合 saturation = 0 | ||
+ | if (s == 0) { | ||
+ | // → RGB は V に等しい | ||
+ | v = Math.round(v); | ||
+ | return {'r': v, 'g': v, 'b': v}; | ||
+ | } | ||
+ | s = s / 255; | ||
+ | |||
+ | var i = Math.floor(h / 60) % 6, | ||
+ | f = (h / 60) - i, | ||
+ | p = v * (1 - s), | ||
+ | q = v * (1 - f * s), | ||
+ | t = v * (1 - (1 - f) * s); | ||
+ | |||
+ | switch (i) { | ||
+ | case 0 : | ||
+ | r = v; g = t; b = p; break; | ||
+ | case 1 : | ||
+ | r = q; g = v; b = p; break; | ||
+ | case 2 : | ||
+ | r = p; g = v; b = t; break; | ||
+ | case 3 : | ||
+ | r = p; g = q; b = v; break; | ||
+ | case 4 : | ||
+ | r = t; g = p; b = v; break; | ||
+ | case 5 : | ||
+ | r = v; g = p; b = q; break; | ||
+ | } | ||
+ | return {'r': Math.round(r), 'g': Math.round(g), 'b': Math.round(b)}; | ||
+ | } | ||
+ | |||
− | + | // 以下はSVGMapレイヤーとして動かしたときに有効になる関数 | |
− | + | var CRSad = 100; // svgmapコンテンツのCRSきめうち・・ | |
− | + | function buildSvgImage(imageDataUri,imageParam){ | |
− | + | // dataURLを受け取って、SVGMapコンテンツに画像を張り付ける | |
− | + | var rct = svgImage.createElement("image"); | |
− | + | rct.setAttribute("x", imageParam.lng0 * CRSad); | |
− | + | rct.setAttribute("y", -(imageParam.lat0 + imageParam.latSpan) * CRSad); | |
− | + | rct.setAttribute("width", imageParam.lngSpan * CRSad); | |
− | + | rct.setAttribute("height", imageParam.latSpan * CRSad); | |
− | * | + | rct.setAttribute("xlink:href",imageDataUri); |
− | + | rct.setAttribute("style","image-rendering:pixelated"); // メッシュデータなので拡大次画像のピクセルをくっきりさせる | |
− | + | var root = svgImage.documentElement; | |
− | + | root.appendChild(rct); | |
+ | } | ||
+ | </pre> |
2024年7月19日 (金) 10:24時点における最新版
目次 |
[編集] チュートリアル10 WebApp Layer WebApp Layer メッシュデータのビットイメージ化
メッシュデータ(グリッドデータ)はラスターデータとも呼ばれるように、Webコンテンツとして一般的に使われるビットイメージデータ形式(PNGやJPEGなど)とほぼ同等の形式です。そこでメッシュデータを動的にビットイメージコンテンツ(PNG形式)化し、地図画面上に表示するWebAppを構築してみます。性能面でのメリットがあります。
特徴的なコードはレイヤーに紐付いたwebAppにあります。
- 実際の動作は、こちらをクリック。
- 使用ファイルのZIPアーカイブファイル
[編集] 使用するデータ
地理院がこちらのページで公開するジオイド高データ(TEXTデータ)を使用します。
このデータの詳細な仕様は上記サイトで配布されているパッケージ同梱文書(asc取扱説明書.pdf)に記載されていますが、基本的にはテキストのRaster形式です。
データ形式としては、カンマ区切りでなく空白文字区切り 一つのRaw(桁)が1行で終結せず、255文字で改行される点が注意点です
データの内容としては、グリッドデータの原点の定義と、それをビットイメージ画像として可視化したときの原点との違いに注意が必要です。(下記2点)
- Y(緯度)軸の向きが逆のため、元のラスターデータの原点は南端、ビットイメージの原点は上端、
- ラスターデータの原点は、それをビットイメージとして可視化したときのピクセルの中心位置なのに対し、ビットイメージを配置するときの原点はピクセルの左上隅
- 説明図svg
[編集] mesh3.html
これまでと特に変わったところはありません。
[編集] Container.svg
これまでと特に変わったところはありません。
[編集] rasterMesh.svg
- WebAppが(下記rasterMesh.html)が紐付けられた空白のコンテンツです。
- 表示とともにwebAppのウィンドが出現するように指定しています。
- これまでと特に変わったところはありません。
<?xml version="1.0" encoding="UTF-8"?> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" data-controller="rasterMesh.html#exec=appearOnLayerLoad" viewBox="-42.8202042942663, -49.9999999999999, 513.842451531196, 600" property="Local government codes"> <globalCoordinateSystem srsName="http://purl.org/crs/84" transform="matrix(100,0,0,-100,0,0)" /> </svg>
[編集] rasterMesh.html, rasterMesh.js
読み込んだテキストのラスターデータを用いてビットイメージを動的に生成、これを紐付けられたrasterMesh.svgに張り付けて可視化します。
onload=async function()
await buildData()
メッシュデータを読み込みグローバル変数に保存する非同期関数var duri = buildImage()
- 指定されたCanvas要素を作業用に使用し、読み込んだデータからビットイメージを生成、dataURIとして出力する
canvas.toDataURL()
canvasオブジェクトのtoDataURLでPNGビットイメージをDataURLとして生成しています
imageGeoArea
データの注意点での指摘の通り、ビットイメージをSVG座標に張り付けるための領域情報を計算していますbuildSvgImage()
生成したビットイメージ(dataURI)およびその領域情報を使って、webAppに紐付いたSVG DOMの中に、ビットイメージを張り付けるsvgImage
このwebAppに紐付いたSVGコンテンツのDOM(Documentオブジェクト)があらかじめ定義されている
svgMap.refreshScreen()
非同期での読み込みとデータ生成・SVGMapのDOM編集が完了したら再描画を明示し画面に反映する (参考)
rasterMesh.js
// Description: // MeshData Visualizer. // // History: // 2022/02/14 : 1st rev. // 読み込んだASCIIデータを保持するグローバル変数 var geoidGrid=[]; var dataProps; onload = async function(){ // メッシュデータを読み込みグローバル変数に保存 await buildData(); // 読み込んだデータからdataURIとしてビットイメージを生成 var duri = buildImage(geoidGrid,document.getElementById("geoidCanvas")); // 生成した画像の地理的な範囲 // 画像になると、グリッドの点は画像のピクセルの中心となることに注意! var imageGeoArea={ lng0: dataProps.glomn - dataProps.dglo/2, lat0: dataProps.glamn - dataProps.dgla/2, lngSpan: dataProps.nlo * dataProps.dglo, latSpan: dataProps.nla * dataProps.dgla } if ( typeof(svgMap)=="object" ){ buildSvgImage(duri,imageGeoArea); // SVGコンテンツを生成 svgMap.refreshScreen(); } } async function buildData(){ var gtxt = await loadText("gsigeo2011_ver2_1.asc"); gtxt = gtxt.split("\n"); dataProps = getHeader(gtxt[0]); var gx=0, gy=0; var geoidGridLine=[]; for ( var i = 1 ; i < gtxt.length ; i++){ var na = getNumberArray(gtxt[i]); gx += na.length; geoidGridLine = geoidGridLine.concat(na); if ( gx >= dataProps.nlo ){ geoidGrid.push(geoidGridLine); geoidGridLine=[]; gx=0; } } } function buildImage(geoidGrid, canvas){ // canvas.width=dataProps.nlo; canvas.height=dataProps.nla; var context = canvas.getContext('2d'); var imageData = context.getImageData(0, 0, canvas.width, canvas.height); var pixels = imageData.data; for ( var py = 0 ; py < dataProps.nla ; py++ ){ var dy = dataProps.nla - 1 - py for ( var px = 0 ; px < dataProps.nlo ; px++ ){ var base = (dy * dataProps.nlo + px) * 4; if ( geoidGrid[py][px]!=999){ var hue = (1-(geoidGrid[py][px]-dataProps.minVal)/(dataProps.maxVal-dataProps.minVal))*270; var rgb = HSVtoRGB(hue,255,255); pixels[base + 0] = rgb.r; // Red pixels[base + 1] = rgb.g; // Green pixels[base + 2] = rgb.b; // Blue pixels[base + 3] = 255; // Alpha } } } context.putImageData(imageData, 0, 0); var duri = canvas.toDataURL('image/png'); return ( duri ); } function getHeader(line){ var datas = parseLine(line); return { glamn:Number(datas[0]), glomn:Number(datas[1]), dgla:Number(datas[2]), dglo:Number(datas[3]), nla:Number(datas[4]), nlo:Number(datas[5]), ikind:Number(datas[6]), vern:datas[7], minVal:9e99, maxVal:-9e99 } } function getNumberArray(line){ var ans = []; var lineArray = parseLine( line ); for ( var col of lineArray){ var val = Number(col); if ( val != 999){ if ( val > dataProps.maxVal){ dataProps.maxVal=val; } if ( val < dataProps.minVal){ dataProps.minVal=val; } } ans.push(val); } return ( ans ); } function parseLine(line){ var ans = line.trim().split(/\s+/) return (ans); } async function loadText(url){ // テキストデータをfetchで読み込む messageDiv.innerText="ジオイド高データを読み込み中です"; var response = await fetch(url); var txt = await response.text(); messageDiv.innerText=""; return ( txt ); } function HSVtoRGB (h, s, v) { // from http://d.hatena.ne.jp/ja9/20100903/1283504341 var r, g, b; // 0..255 while (h < 0) { h += 360; } h = h % 360; // 特別な場合 saturation = 0 if (s == 0) { // → RGB は V に等しい v = Math.round(v); return {'r': v, 'g': v, 'b': v}; } s = s / 255; var i = Math.floor(h / 60) % 6, f = (h / 60) - i, p = v * (1 - s), q = v * (1 - f * s), t = v * (1 - (1 - f) * s); switch (i) { case 0 : r = v; g = t; b = p; break; case 1 : r = q; g = v; b = p; break; case 2 : r = p; g = v; b = t; break; case 3 : r = p; g = q; b = v; break; case 4 : r = t; g = p; b = v; break; case 5 : r = v; g = p; b = q; break; } return {'r': Math.round(r), 'g': Math.round(g), 'b': Math.round(b)}; } // 以下はSVGMapレイヤーとして動かしたときに有効になる関数 var CRSad = 100; // svgmapコンテンツのCRSきめうち・・ function buildSvgImage(imageDataUri,imageParam){ // dataURLを受け取って、SVGMapコンテンツに画像を張り付ける var rct = svgImage.createElement("image"); rct.setAttribute("x", imageParam.lng0 * CRSad); rct.setAttribute("y", -(imageParam.lat0 + imageParam.latSpan) * CRSad); rct.setAttribute("width", imageParam.lngSpan * CRSad); rct.setAttribute("height", imageParam.latSpan * CRSad); rct.setAttribute("xlink:href",imageDataUri); rct.setAttribute("style","image-rendering:pixelated"); // メッシュデータなので拡大次画像のピクセルをくっきりさせる var root = svgImage.documentElement; root.appendChild(rct); }