チュートリアル15
提供: svg2wiki
目次 |
チュートリアル15 WebApp Layer 伸縮スクロールに応じたベクトル地理情報サービス結合
動的にベクトルデータが生成・配信されているサービスをSVGMap.jsに結合します。チュートリアル14に対して、こちらは伸縮スクロールする度にその表示領域に応じたデータをサービスから取得して表示します。またチュートリアル14はgeoJsonデータのサービスでしたがこちらはCSVデータです。
結合するサービスはNatural Resources Canadaが提供している、Geoname Service API(カナダの地名データサービス)です。
- 実際の動作は、こちらをクリック。
- ソースコードのディレクトリ
vectorService1.html
- チュートリアル14と特に違いはありません。
Container.svg
- チュートリアル14と特に違いはありません。
CanadianGeoNames.svg
- チュートリアル14と特に違いはありません。アイコンの色もこちらは赤に固定しています。
<?xml version="1.0" encoding="UTF-8"?> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="120,-50,30,30" data-controller="CanadianGeoNames.html#exec=appearOnLayerLoad"> <globalCoordinateSystem srsName="http://purl.org/crs/84" transform="matrix(1,0,0,-1,0,0)"/> <defs> <g id="p0"> <circle cx="0" cy="0" r="6" fill="red"/> </g> </defs> </svg>
CanadianGeoNames.html
REST API
少し複雑ですので、Geographical names in Canada : Geoname Serviceが提供するAPIのうち今回使用する部分をまとめます。
使用するクエリパラメータ
今回は以下の二つを使います。
- bbox 西、南、東、北の座標(世界測地系の度の値)をカンマ区切りで指定
- num 出力する最大数
- .csv拡張子 厳密にはクエリパラメータではありませんが、パス部の拡張子を設定することで指定したメディアが配信されます。(デフォルトはhtml)今回はCSVを使うことにします。
- クロスオリジン設定 今回は別ドメインのサービスにアクセスすることになるので、ウェブサービスがクロスオリジンアクセスを許可している必要があります。Geographical names in Canada : Geoname Serviceは許可されているようです。
配信されるデータ
クエリパラメータに基づいたCSVデータが返信されます。1行目がスキーマ行2行目以降にデータ続きます。以下のカラムがあるようです。latitude、longitudeカラムで地図上にPointフィーチャを設置、nameカラムでそのタイトルを設置できそうです。その他のカラムはシンボルをクリックしたときにプロパティとして表示できるでしょう。データを取得してみると、カンマを含むデータがダブルクォーテーションでエスケープされていることがあるようです。
- id
- name
- language.code
- language.href
- syllabic
- feature.id
- feature.href
- category
- status.code
- status.href
- concise.code
- concise.href
- generic.code
- generic.href
- location
- province.code
- province.href
- map
- relevance
- accuracy
- latitude
- longitude
- decision
コード
addEventListener("zoomPanMap", getGeoNames)
: 伸縮スクロールの度にサービスに問い合わせ、紐づいたレイヤーのSVGMapコンテンツのDOMを構築し可視化します。- 1ステップ前(伸縮スクロール前)のデータは、たとえ現ステップでも存在していたとしても単純に全消去してから再配置しています。(タイリングなどの、より高度な仕組みは実装していない)
getGeoNames()
: サービスにと合わせて可視化する非同期関数svgMap.getGeoViewBox()
: 地理的な表示領域を得るgetCanadianGeoNamesReq()
: 表示領域をもとにサービスへのクエリを組み立てawait getCsv()
: クエリを使って非同期でCSVを取得・パースline.split(...)
: こちらの記事をもとにダブルクォーテーションエスケープを加味してパース
drawPoints()
: 取得したデータを可視化します。 今回はCSVから直接SVGのuse要素を作り、可視化しています。schema
: 紐づいたレイヤーのSVGMapコンテンツのドキュメント要素のpropertyにスキーマを設置"transform", `ref(svg,${lng},${-lat})`
: svg1.2のTransformRefを使い、サイズが変化しないアイコンを設置しています。- 参考:サポートされている属性
"content"
: メタデータをcsvで設置"xlink:href", "#p0"
: defs要素内のid:p0のシンボル(赤い丸)を参照svgMap.refreshScreen();
: SVGMapコンテンツのDOM生成完了したら再描画する- 参考: 再描画の制限
<!doctype html> <html> <head> <title>basic dynamic wms layer controller</title> <meta charset="utf-8"></meta> </head> <script> var canadianGeoNamesService = "https://geogratis.gc.ca/services/geoname/en/geonames.csv"; onload=function(){ addEventListener("zoomPanMap", getGeoNames); getGeoNames(); } var crsAD=1; var maxItems=100; async function getGeoNames(){ var geoViewBox = svgMap.getGeoViewBox(); // 地理的な表示領域を得る var req = getCanadianGeoNamesReq(geoViewBox); // 表示領域をもとにサービスへのクエリを組み立てる var csv = await getCsv(req); // クエリを使って非同期でCSVを取得 if ( csv.length > maxItems){ // 最大数以上の場合メッセージを出す messageDiv.innerText="Exceeded maximum number. Please zoom in."; }else{ messageDiv.innerText=""; } drawPoints(csv); // 取得したデータを可視化する } function getCanadianGeoNamesReq(geoArea){ var area_x0=geoArea.x; var area_y0=geoArea.y; var area_x1=geoArea.x+geoArea.width; var area_y1=geoArea.y+geoArea.height; var ans = `${canadianGeoNamesService}?bbox=${area_x0},${area_y0},${area_x1},${area_y1}&num=${maxItems}`; return ( ans ); } async function getCsv(url){ var response = await fetch(url); var txt = await response.text(); txt = txt.split("\n"); var ans = []; for ( var line of txt ){ // https://www.ipentec.com/document/csharp-read-csv-file-by-regex ダブルクォーテーションエスケープを加味したcsvパース line = line.split(/,(?=(?:[^"]*"[^"]*")*[^"]*$)/); if (line.length > 1){ ans.push(line); } } return ( ans ); } function drawPoints(csv){ removeUses(); var schema = csv[0].join(); var latCol=csv[0].indexOf("latitude"); var lngCol=csv[0].indexOf("longitude"); svgImage.documentElement.setAttribute("property",schema); for ( var i = 1 ; i < csv.length ; i++){ var point = csv[i]; var meta = point.join(); var lat = Number(point[latCol]); var lng = Number(point[lngCol]); var use=svgImage.createElement("use"); use.setAttribute("xlink:href","#p0"); use.setAttribute("content",meta); use.setAttribute("x",0); use.setAttribute("y",0); use.setAttribute("transform",`ref(svg,${lng},${-lat})`); svgImage.documentElement.appendChild(use); } svgMap.refreshScreen(); } function removeUses(){ var uses = svgImage.getElementsByTagName("use"); for ( var i = uses.length-1 ; i >=0 ; i--){ uses[i].remove(); } } </script> <body> <h3>Canadian GeoNames layer controller</h3> <p>Get CanadianGeoNames Features from <a href="https://www.nrcan.gc.ca/maps-tools-and-publications/maps/geographical-names-canada/application-programming-interface-api/9249" target="_blank">Canadian GeoNames Search Service</a></p> <div id="messageDiv" style="color:red">-</div> </body> </html>