チュートリアル5
提供: svg2wiki
目次[非表示] |
チュートリアル5 WebApp Layer タイルピラミッド
実際の動作は、こちらをクリック。
tutorial5.html
- SVGMapのコアプログラムファイル(SVGMapLv0.1_r17.js, SVGMapLv0.1_LayerUI2_r4.js)を読み込み、SVGMapの各種APIを利用可能にする。
- 地図表示部分を(DIVで)定義し、そこに表示するレイヤをまとめたSVGファイル(Containers.svg)を読み込む(上記SVGMapのコアプログラムにて自動的にVisibleになっているレイヤが表示される)。
- ズームアップ・ズームダウン・GPSの各ボタンの表示とクリック時の動作(SVGMapのコアプログラムのそれぞれのAPIを呼び出す)を定義。
- ズームアップボタン:svgMap.zoomup() APIを呼び出すことで地図をズームアップする。
- ズームダウンボタン:svgMap.zoomdown() APIを呼び出すことで地図をズームダウンする。
- GPSボタン:svgMap.gps() APIを呼び出すことで、現在地(PCやスマートフォンの位置、特定できる場合のみ)を中心にズームアップ表示する。
- 中心を表す十字マークを表示。
- 上記十字マークが示している地図上の緯度・経度の表示(実際には、地図の移動時に地図の中心の緯度・経度を表示する)。
<!DOCTYPE html> <html> <title>SVGMapLevel0.1-Rev14-Draft Tutorial5 DynamicContents</title> <!-- viewport 知表示領域を画面全体とする定義 --> <meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1.0,maximum-scale=1.0" /> <meta charset="UTF-8"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <!-- SVGMapのコアAPIの読み込み --> <script type="text/javascript" src="../js/SVGMapLv0.1_r14.js"></script> <body bgcolor="#ffffff" style="overflow:hidden;" > <!-- 地図SVGファイルを複数含む(このチュートリアルでは5ファイルのみ)コンテナファイル(Container.svg)の読み込み --> <div id="mapcanvas" data-src="Container.svg"></div> <div id="gui"> <!-- ズームアップボタン --> <img id="zoomupButton" style="left: 5px; top: 5px; position: absolute;" src="../img/zoomup.png" onclick="svgMap.zoomup()" width="20" height="20" /> <!-- ズームダウンボタン --> <img id="zoomdownButton" style="left: 5px; top: 25px; position: absolute;" src="../img/zoomdown.png" onclick="svgMap.zoomdown()" width="20" height="20" /> <!-- GPSボタン --> <img id="gpsButton" style="left: 5px; top: 45px; position: absolute;" src="../img/gps.png" onclick="svgMap.gps()" width="20" height="20" /> <!-- 画面右上に表示するタイトル --> <font color="blue" style="right: 5px; top: 5px; position: absolute;" >SVGMapLevel0.1 Rev14 Draft : Tutorial5 DynamicContents</font> <!-- 画面右下に表示する --> <font color="blue" style="right: 5px; bottom: 5px; position: absolute;" size="-2" >by SVGMap tech.</font> <!-- 中央に表示される十字マーク --> <img id="centerSight" style="opacity:0.5" src="../img/Xcursor.png" width="15" height="15"/> <!-- 画面左下に表示される十字マークの緯度・経度(タイトル) --> <font id="posCmt" size="-2" color="brown" style="left: 5px; bottom: 5px; position: absolute;">Lat,Lng:</font> <!-- 画面左下に表示される十字マークの緯度・経度(実際の値の初期表示) --> <font id="centerPos" size="-2" color="brown" style="left: 50px; bottom: 5px; position: absolute;" >lat , lng</font> </div> </body> </html>
Container.svg
- 表示する各レイヤ用のSVGファイルを読み込む(dynamicOSM_r10.svgのみを読み込んでいる)。
<?xml version="1.0" encoding="UTF-8"?> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:go="http://purl.org/svgmap/profile" viewBox="12300 -4600 2200 2200" > <globalCoordinateSystem srsName="http://purl.org/crs/84" transform="matrix(100.0,0.0,0.0,-100.0,0.0,0.0)" /> <!-- OpenStreetMap用SVGファイルを表示状態として読み込む --> <animation x="-30000" y="-30000" width="60000" height="60000" xlink:href="dynamicOSM_r10.svg" title="OpenStreetMap(Global)" class="basemap switch" visibility="visible"/> </svg>
dynamicOSM_r11.svg
外部のOpenStreetMapを表示するためのWebAppが紐付けられたSVG Mapコンテンツレイヤーファイル。
- SVGコンテンツのドキュメント要素(svg要素)のdata-controller属性でウェブアプリケーション(javascriptコードが載ったhtmlコンテンツ)を参照することで、SVGMapコンテンツのレイヤーにウェブアプリを紐づけます。
- #exec=hiddenOnLayerLoad ハッシュは、ウェブアプリケーションを画面非表示状態で起動させます。解説書#レイヤー固有のUIの詳細を参照
- ウェブアプリでDOMを直接生成するので、コンテンツの中身はほとんど空になっています。
<?xml version="1.0" encoding="UTF-8"?> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-42.8202042942663, -49.9999999999999, 513.842451531196, 600" data-controller="dynamicOSM_r11.html#exec=hiddenOnLayerLoad" > <globalCoordinateSystem srsName="http://purl.org/crs/84" transform="matrix(100,0,0,-100,0,0)" /> </svg>
dynamicOSM_r11.html
OpenStreetMap(OSM)は縮尺に応じたピラミッド状の256×256ピクセルのタイルに分割された、ピラミッド状のイメージを配信しています。(OSM Slippy map tilenames, OSM ZoomLevels)
これを使用して、表示範囲とズームレベルに適したタイルをOpenStreetMapのサーバから動的に取得・表示する機能(Level of Detail)を実装しています。
Note: ここで使用しているOpenStreetMapのタイルはいわゆるウェブメルカトル図法上での均等メッシュタイルとなっています。一方、このチュートリアルで使用しているSVGMapコンテンツは正距方位図法(Plate Caree:経度緯度をそのままX,Y平面に展開した図法、業務や技術系の分野でよく使われる)を用いています。そのため図法変換が必要になりますが、本チュートリアルでは簡単化のためにタイルの1次変換だけで済ませています。日本列島レベルぐらいの小縮尺では少し図法の違いによるずれが見えるかもしれません。(SVGMap.jsではより正確な図法変換にも対応しています。)
- WebApp Layer機構
- SVGMapレイヤーに紐づいたウェブアプリであらかじめ設定される各種関数、変数
onload
通常のwebAppと同様、このhtmlに関係するリソースの読み込み完了後実行される関数preRenderFunction()
この関数を定義すると、画面の再描画の度に、その直前にこの関数が実行されます。svgImage
紐付けられたSVGMapコンテンツのDOMsvgImagesProps
- svgMap
getTileSet
現在のズームレベルと表示領域において、表示すべきタイルをリストアップする関数
<!doctype html>
<html>
<head>
<meta charset="utf-8"></meta>
<title>OpenStreerMap Dynamic Layer</title>
</head>
<script>
// Dynamic OpemStreetMap Layer for SVGMap Sample for SVGMapLevel0 > r10
// Programmed by Satoru Takagi
// Copyright (C) 2013 by Satoru Takagi @ KDDI CORPORATION
//
// License:
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 3 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see (http://www.gnu.org/licenses/) .
// iframe化を想定した動的レイヤーのプロトタイプ
// (JavaScriptをインポートSVGコンテンツに置くことができる。)
// 地図データとしては、OpenStreetMapを利用(比較的容易に他にも置き換えられる)
//
//
// このコードの動作環境では、以下があらかじめ設定される
// document:このドキュメント自身
// svgImage:このドキュメントに紐づいたSVGMapコンテンツ
// svgMap.getGeoViewBox(): 地理的なビューボックス
// svgImageProps:このドキュメントに紐づいたSVGMapコンテンツの各種プロパティ
// svgImageProps.scale: スケール(画面座標に対する、このsvgコンテンツの座標のスケール)
//
// 2013/01/24 : 1st ver.
// 2022/01/31 : WebApp layerに移植
// このファイルの読み込み時に実行する
onload = function(){
// このスクリプトが読み込まれた直後、refreshScreen()を呼ぶことで、
//下記preRenderFunctionが初回実行される
svgMap.refreshScreen();
}
function preRenderFunction(){
// 再描画直前に実行されるコールバック関数
var level = 8;
// ズームレベルを計算(3から18)
var level = Math.floor( Math.LOG2E * Math.log(svgImageProps.scale) + 7.5);
if (level > 18 ){
level = 18;
} else if ( level < 3 ){
level = 3;
}
// この地図の地理座標におけるviewBox内表示させる、tileのXYとそのHashKeyを取得する
var tileSet = getTileSet( svgMap.getGeoViewBox() , level )
// 現在読み込まれているimageというタグ名を持った(地図のタイルごとのイメージ)要素を取得
console.log("tileSet:",tileSet);
var currentTiles = svgImage.getElementsByTagName("image");
// 取得できた各タイル分以下を繰り返し、既に読み込み済みのものは再利用、表示範囲外のものは削除する
for ( var i = currentTiles.length - 1 ; i >= 0 ; i-- ){
var oneTile = currentTiles[i];
var qkey = oneTile.getAttribute("metadata");
if ( tileSet[qkey] ){
// すでにあるのでスキップさせるフラグ立てる。
tileSet[qkey].exist = true;
} else {
// ないものなので、消去
oneTile.parentNode.removeChild(oneTile);
}
}
// 表示させるタイル分以下を繰り返し、読み込まれていないファイルを読込み要素に加える
for ( var tkey in tileSet ){
if ( ! tileSet[tkey].exist ){
var addTile = getTile( tileSet[tkey].x , tileSet[tkey].y , level , this.CRS );
svgImage.getElementsByTagName("svg")[0].appendChild(addTile);
}
}
}
// 指定された場所のタイル(分割された地図イメージ)を取得
function getTile( tileX , tileY , level , crs ){
// tileX、tileYの座標、levelのズームレベルのタイルのURLを取得。
var tileURL = getURL( tileX , tileY , level);
// タイルのSVGにおけるbboxを得る
var tLatLng = XY2latLng( tileX * tilePix , tileY * tilePix, level );
var tSvg = svgMap.transform( tLatLng.lng , tLatLng.lat , crs );
var tLatLngBR = XY2latLng( tileX * tilePix + tilePix , tileY * tilePix + tilePix , level );
var tSvgBR = svgMap.transform( tLatLngBR.lng , tLatLngBR.lat , crs );
tSvg.width = tSvgBR.x - tSvg.x; // 効率悪い・・改善後回し
tSvg.height = tSvgBR.y - tSvg.y;
// 取得するタイル要素を作成し、各属性をセットする。
var cl = svgImage.createElement("image");
cl.setAttribute("x" , tSvg.x);
cl.setAttribute("y" , tSvg.y);
cl.setAttribute("width" , tSvg.width);
cl.setAttribute("height" , tSvg.height);
cl.setAttribute("xlink:href" , tileURL.URL);
cl.setAttribute("metadata" , tileURL.Key);
return ( cl );
}
// 指定された地図座標geoViewBoxに、levelのズームレベルの地図を表示する場合に、必要なタイルのXYのセットを返却する
function getTileSet( geoViewBox , level ){
var TileSet = new Object();
if ( geoViewBox.y + geoViewBox.height > 85.05113 ){
geoViewBox.height = 85.05113 - geoViewBox.y;
}
if ( geoViewBox.y < -85.05113 ){
geoViewBox.y = -85.05113;
}
// 指定エリアの、tileのXYとそのHashKeyを返却する
var tlxy = latLng2XY( geoViewBox.y + geoViewBox.height , geoViewBox.x , level );
var tileTLxy = XY2TileXY( tlxy );
var brxy = latLng2XY( geoViewBox.y , geoViewBox.x + geoViewBox.width, level );
var tileBRxy = XY2TileXY( brxy );
// 必要な高さ・幅分のタイル個数分以下を繰り返す
for ( var i = tileTLxy.y ; i <= tileBRxy.y ; i++ ){
for ( var j = tileTLxy.x ; j <= tileBRxy.x ; j++ ){
// タイルのXYとズームレベルからHashKeyを取得する
var qkey = getKey( j, i, level);
// 上記で取得したHashKeyごとに、必要なタイル情報を設定する
TileSet[qkey] = new Object();
TileSet[qkey].x = j;
TileSet[qkey].y = i;
}
}
return ( TileSet );
}
// 緯度・経度からXYに変換
function latLng2XY( lat , lng , lvl ){
var size = lvl2Res(lvl);
var sinLat = Math.sin(lat * Math.PI / 180.0);
var pixelX = (( lng + 180.0 ) / 360.0 ) * size;
var pixelY = (0.5 - Math.log((1 + sinLat) / (1.0 - sinLat)) / (4 * Math.PI)) * size;
return {
x : pixelX ,
y : pixelY
}
}
// XYからタイルのXYに変換
function XY2TileXY( xy ){
var tileX = Math.floor(xy.x / tilePix);
var tileY = Math.floor(xy.y / tilePix);
return {
x : tileX ,
y : tileY
}
}
var tilePix = 256;
// ズームレベルからタイルの一片のサイズを返却
function lvl2Res( lvl ){
var j = 1;
for(var i = 0 ; i < lvl ; i++){
j = j * 2;
}
return ( j * tilePix );
}
// XYから緯度・経度に変換
function XY2latLng( px , py , lvl ){
var size = lvl2Res(lvl);
var x = ( px / size ) - 0.5;
var y = 0.5 - ( py / size);
var lat = 90 - 360 * Math.atan(Math.exp(-y * 2 * Math.PI)) / Math.PI;
var lng = 360 * x;
return{
lat : lat ,
lng : lng
}
}
var sva = new Array(
"a" ,
"b" ,
"c"
);
var svNumb = 0;
var culture ="en-US";
var bingRoadSearchPart = ".jpeg?g=849&mkt=" + culture + "&shading=hill";
// タイルのXYとズームレベルからURLを返却する
function getURL( tx , ty , lvl ){
// XYとズームレベルからHashKeyを取得
var tile_ans = getKey( tx , ty , lvl );
// OpenStreetMapのURLを組み立てる
var mapServerURL = "http://" + sva[svNumb] + ".tile.openstreetmap.org/" + lvl + "/" + tx + "/" + ty + ".png";
// 複数の同様のサーバを順次切り替えながら使用することで、地図イメージ取得時の負荷分散を行う。
++ svNumb;
if ( svNumb > 2 ){
svNumb = 0;
}
return {
URL : mapServerURL ,
Key : tile_ans
}
}
// HashKeyを生成し返却する
function getKey(tx , ty , lvl){
return ( tx + "_" + ty + "_" + lvl );
}
</script>
<body>
<h3>OpenStreerMap Dynamic Layer</h3>
</body>
</html>