2018年10月31日水曜日

OpenLayers5 Workshop - 4.3 Render sea level

4 Raster Operations
4.3 Render sea level
平均海面を描画

In the previous step, we rendered the Terrain-RGB tiles directly on the map. What we want to do is render sea level on the map instead. And we want users to be able to adjust the height above sea level and see the adjusted height rendered on the map. We'll use a raster source to work with the elevation data directly and get the user input from an input slider on the page.

前回のステップで、Terrain-RGB タイルを直接マップ上に描画しました。(今回)したいことは、かわりに、平均海面をマップ上に描画します。そして、ユーザが海抜の高さを調整し、マップ上に描画された調節された高さを見ることができるようにします。elevation(標高)データで直接操作するためにラスタソースを使い、ページ上のインプットスライダからユーザインプット(input)を取得します。

Let's add the controls to the page first. In your index.html, add the following label and input slider:

最初にコントロ0るをページに追加しましょう。index.html に次のラベルとインプットスライダを追加します:
<label id="slider">
 Sea level
 <input id="level" type="range" min="0" max="100" value="1"/>
 +<span id="output"></span> m
</label>
Now add some style to those controls (in the <style> of your index.html):

では、(index.html の <style> に)いくつかのスタイルをそれらのコントロールに追加します:
#slider {
 position: absolute;
 bottom: 1rem;
 width: 100%;
 text-align: center;
 text-shadow: 0px 0px 4px rgba(255, 255, 255, 1);
}
Instead of directly rendering the R, G, B, A values from the Terrain-RGB tiles, we want to manipulate the pixel values before rendering. The raster source allows you to do this by accepting any number of input sources and an operation. This operation is a function that gets called for every pixel in the input sources. We only have one input source (elevation), so it will get called with an array of one pixel, where a pixel is a [red, green, blue, alpha] array. The operation also gets called with a data object. We'll use the data object to pass along the value of the input slider.

Terrain-RGB タイルから R、G、B、A 値を直接描画するかわりに、描画する前にピクセル値を操作するようにします。ラスタソース(raster source)は、任意の数のインプットソース(input source)と operation(オペレーション)を受け取ることによってこれを行うことを許可します。この operation はインプットソースのすべてのピクセルの呼び出し(call)を取得するファンクションです。1つのインプットソース(elevation)だけ持ち、そのため、1つのピクセルの配列を伴う呼び出しを取得し、ピクセルは、[red, green, blue, alpha] 配列になっています。operation は data(データ)オブジェクトを伴う呼び出しも取得します。インプットスライダの値に沿って渡すために data オブジェクトを使います。

First, import the RasterSource and ImageLayer (in main.js):

最初に、(main.js に)RasterSource と ImageLayer をインポートします:
import ImageLayer from 'ol/layer/Image';
import RasterSource from 'ol/source/Raster';
Add the function below to your main.js. This function decodes the input elevation data — transforming red, green, and blue values into a single elevation measure. For elevation values at or below the user selected value, the function returns a partially transparent blue pixel. For values above the user selected value, the function returns a transparent pixel.

下の function を main.js に追加します。このファンクションは、インプット elevation データを、赤(red)、緑(green)、青(blue)の値を単一の elevation 量に変換するために、デコード(復号化)します。ユーザが選択した、または、それより低い elevation 値の場合には、ファンクションは部分的に透明な青ピクセルを返します。ユーザが選択した値より高い場合には、ファンクションは透明なピクセルを返します。
function flood(pixels, data) {
 const pixel = pixels[0];
 if (pixel[3]) {
  // decode R, G, B values as elevation
  const height = -10000 + ((pixel[0] * 256 * 256 + pixel[1] * 256 + pixel[2]) * 0.1);
  if (height <= data.level) {
   // sea blue
   pixel[0] = 145; // red
   pixel[1] = 175; // green
   pixel[2] = 186; // blue
   pixel[3] = 255; // alpha
  } else {
   // transparent
   pixel[3] = 0;
  }
 }
 return pixel;
}
Create a raster source with a single input source (the elevation data), and configure it with the flood operation.

単一の入力ソース(elevation データ)でラスターソースを作成し、 flood operation でそれを設定します。
const raster = new RasterSource({
 sources: [elevation],
 operation: flood
});
Listen for changes on the slider input and re-run the raster operations when the user adjusts the value.

ユーザが値を調節するとき、スライダインプット(input)と raster operation の再実行の変更に対してリッスンします。
const control = document.getElementById('level');
const output = document.getElementById('output');
control.addEventListener('input', function() {
 output.innerText = control.value;
 raster.changed();
});
output.innerText = control.value;
The beforeoperations event is fired before the pixel operations are run on the raster source. This is our opportunity to provide additional data to the operations. In this case, we want to make the range input value (meters above sea level) available.

beforeoperations イベントは、pixel operations がラスタソースに対して実行される前に、始動します。これは、追加 data を operations に提供するための契機です。この場合、入力された値の範囲(標高メートル)を利用可能にします。
raster.on('beforeoperations', function(event) {
 event.data.level = control.value;
});
Finally, render the output from the raster operation by adding the source to an image layer. Replace the tile layer with an image layer that uses our raster source (modify the layers array in main.js):

最後に、ソースをイメージレイヤに追加することによって raster operation から出力を描画します。ラスタソースを使用するイメージレイヤで タイルレイヤを置き換えます(main.js で layers 配列を修正します):
new ImageLayer({
 opacity: 0.8,
 source: raster
})
With all this in place, the map should now have a slider that let's users control changes in sea level.

すべてこれを正しい場所に用いると、マップはユーザが海面の変化を調節できるスライダを保持できます。

Sea level rise in Boston

■□ Debian9 で試します■□
「Render sea level」の例を表示します。「Map setup」で使用した index.html のバックアップを保存して次のように修正します。

user@deb9-vmw:~/openlayers-workshop-en$ cp index.html index.html_mapsetup
user@deb9-vmw:~/openlayers-workshop-en$ vim index.html
<!DOCTYPE html>
<html>
 <head>
  <meta charset="utf-8">
  <title>OpenLayers</title>
  <style>
   html, body, #map-container {
    margin: 0;
    height: 100%;
    width: 100%;
    font-family: sans-serif;
    }
   #slider {
    position: absolute;
    bottom: 1rem;
    width: 100%;
    text-align: center;
    text-shadow: 0px 0px 4px rgba(255, 255, 255, 1);
   }
  </style>
 </head>
 <body>
  <div id="map-container"></div>
  <label id="slider">
   Sea level
   <input id="level" type="range" min="0" max="100" value="1"/>
   +<span id="output"></span> m
  </label>
 </body>
</html>
「Render elevation data」で使用した main.js のバックアップを保存して次のように修正します。

user@deb9-vmw:~/openlayers-workshop-en$ cp main.js main.js_elevation
user@deb9-vmw:~/openlayers-workshop-en$ vim main.js
import 'ol/ol.css';
import Map from 'ol/Map';
import View from 'ol/View';
import TileLayer from 'ol/layer/Tile';
import XYZSource from 'ol/source/XYZ';
import {fromLonLat} from 'ol/proj';
import ImageLayer from 'ol/layer/Image';
import RasterSource from 'ol/source/Raster';
const key = '<your-default-public-token>';
const elevation = new XYZSource({
 url: 'https://api.mapbox.com/v4/mapbox.terrain-rgb/{z}/{x}/{y}.pngraw?access_token=' + key,
 crossOrigin: 'anonymous'
});
function flood(pixels, data) {
 const pixel = pixels[0];
 if (pixel[3]) {
  // decode R, G, B values as elevation
  const height = -10000 + ((pixel[0] * 256 * 256 + pixel[1] * 256 + pixel[2]) * 0.1);
  if (height <= data.level) {
   // sea blue
   pixel[0] = 145; // red
   pixel[1] = 175; // green
   pixel[2] = 186; // blue
   pixel[3] = 255; // alpha
  } else {
   // transparent
   pixel[3] = 0;
  }
 }
 return pixel;
}
const raster = new RasterSource({
 sources: [elevation],
 operation: flood
});
const control = document.getElementById('level');
const output = document.getElementById('output');
control.addEventListener('input', function() {
 output.innerText = control.value;
 raster.changed();
});
output.innerText = control.value;
raster.on('beforeoperations', function(event) {
 event.data.level = control.value;
});
new Map({
 target: 'map-container',
 layers: [
  new TileLayer({
   source: new XYZSource({
    url: 'http://tile.stamen.com/terrain/{z}/{x}/{y}.jpg'
   })
  }),
  new ImageLayer({
   opacity: 0.8,
   source: raster
  })
 ],
 view: new View({
  center: fromLonLat([-71.06, 42.37]),
  zoom: 12
 })
});
http://localhost:3000/ とブラウザでマップを開きます。(もし開かなければ、'npm start' を実行してください。



0 件のコメント: