three.js GeoGlobe + OpenWeatherMap


画像クリックでサンプルページを表示

画像クリックでサンプルページを表示

Flash か js のコンテンツで、3D の地球儀上に該当地点を示すマーカーを立てて、その地点の天気を別枠で表示というようなことをやる必要がでてきたみたいなので、調査をかねて three.js でサンプルを作成してみました。

3次元上へのマーカー表示はちょっと情報が古いですが こちら を参考にさせていただき、この Flash を元に three.js で実装しました。
それに加えて、座標をプロットする際に OpenWeatherMap からその地点の天気データを取得してきて、画面上部のフォームに表示するようにしてみました。

っていうか js やるんだったらいつまでもテキストエディタで書いてないで、そろそろちゃんと IDE を導入しないといけないのかな…

今回のソースは下記です。

・earth.js

var container;
var camera;
var scene;
var geometry;
var material;
var spherMesh;
var renderer;
var controls;
var radius = 200;
var latitudeDegreeOffset = 90;
var longitudeDegreeOffset = 0;
var markers = [];
var tempOfset = 273.15;

setup();

function setup()
{
    container = document.getElementById('container');
    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
    camera.position.z = 700;

    scene = new THREE.Scene();
    geometry = new THREE.SphereGeometry(radius, 30, 30);
    material = new THREE.MeshBasicMaterial({
        map: THREE.ImageUtils.loadTexture("earth.jpg")
    });
    spherMesh = new THREE.Mesh(geometry, material);
    spherMesh.overdraw = true;
    spherMesh.rotation.y = 60;
    scene.add(spherMesh);

    renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    container.appendChild(renderer.domElement);

    controls = new THREE.OrbitControls(camera, renderer.domElement);

    renderer.setSize( window.innerWidth, window.innerHeight );

    render();
}

function render()
{
    requestAnimationFrame(render);
    spherMesh.rotation.y -= 0.001;
    controls.update();
    renderer.render(scene, camera);
}

//
function translateGeoCoords( latitude, longitude, radius )
{
  	latitude = Math.PI * latitude / 180;
	longitude = Math.PI * longitude / 180;

	latitude -= ( latitudeDegreeOffset * ( Math.PI / 180 )); // offset latitude by n degrees (in radians).
	longitude -= ( longitudeDegreeOffset * ( Math.PI / 180 )); // offset longitude by n degrees (in radians).

	var x = radius * Math.sin( latitude ) * Math.cos( longitude );
	var y = radius * Math.sin( latitude ) * Math.sin( longitude );
	var z = radius * Math.cos( latitude );

	return new THREE.Vector3( -x, z, y ); // threejs の右手座標系に合わせて値を入れる(上記変換式は左手座標系)
}

//
function onClick()
{
  var lat = form1.field1.value;
  var lon = form1.field2.value;

  // test
  apiRequest(lat, lon);
}

function onClear()
{
  for (var i = 0; i < markers.length; i++) {
    spherMesh.remove(markers[i]);
    markers[i].geometry.dispose();
    markers[i].material.dispose();
    markers[i] = null;
  }

  markers = [];
  form1.field3.value = "";
}

//
function addMarker(lat, lon, col)
{
  if(isNaN(lat) || isNaN(lon))
  {
    alert("NaN")
    return;
  }

  var vec3 = translateGeoCoords(lat, lon, radius);
  var geo = new THREE.BoxGeometry(2,2,20);
  var mat = new THREE.MeshBasicMaterial({color:col});
  markers.push(new THREE.Mesh(geo, mat));
  markers[markers.length-1].position.set(vec3.x, vec3.y, vec3.z);
  spherMesh.add(markers[markers.length-1]);

  markers[markers.length-1].lookAt(spherMesh.position);
}


/**
* OpenWeatheMap API
**/
function getOpenWeatheMapQueri(lat, lon)
{
  var api = "http://api.openweathermap.org/data/2.5/weather";
  var queri = api + "?lat=" + lat + "&lon=" + lon;
  return queri;
}

// OpenWeatheMap API の呼び出し
function apiRequest(lat, lon)
{
  var httpObj = new XMLHttpRequest();
  httpObj.open("get", getOpenWeatheMapQueri(lat, lon), true);

  // onload ハンドラ
  httpObj.onload = function(){
  	var json = JSON.parse(this.responseText);
    console.log(json);

    var weather = json.weather[0].main;
    var temp = new Number(json.main.temp);
    temp -= tempOfset;

    form1.field3.value = json.name + "  |  weather : " + weather + "  |  temp : " + temp.toFixed(1) + "°C  humidity : " + json.main.humidity + "%";

    //
    var col;
    switch (weather)
    {
      case "Clear":
        col = 0xFF9900;
        break;
      case "Clouds":
        col = 0xCCAAFF;
        break;
      case "Rain":
        col = 0x0099FF;
        break;
      case "Snow":
        col = 0xFFFFFF;
        break;
      default:
        col = 0xFF0000;
    }
    addMarker(lat, lon, col);
  }
  httpObj.send(null);
}

//
window.addEventListener('resize', function()
{
	renderer.setSize(window.innerWidth, window.innerHeight);
	camera.aspect = window.innerWidth / window.innerHeight;
	camera.updateProjectionMatrix();
}, false );

・index.html

<!DOCTYPE html>
	<html>
	<head>
		<meta charset="UTF-8" />
		<title>Three.js Geo Globe</title>
		<link rel="stylesheet" type="text/css" href="style.css" />
		<script type="text/javascript" src="../three.min.js"></script>
		<script src="../OrbitControls.js"></script>
	</head>
	<body>
		<form name="form1">
			<p>  latitude : <input type="text" name="field1" size="10" value="33.6">
				 longitude : <input type="text" name="field2" size="10"  value="130.4">
				 <input class="button1" type="button" value="ADD GeoLocation Marker" onClick=onClick()>
				<input class="button2" type="button" value="CLEAR GeoLocation Marker" onClick=onClear()>
				<output type="text" name="field3" size="14">
			</p>
		</form>
		<div id="container"></div>
		<script type="text/javascript" src="earth.js"></script>
	</body>
</html>

・style.css

* {
  margin: 0;
  padding: 0;
  border: 0;
  overflow: hidden;
  font-family: "Arial", sans-serif;
  font-size: 11px;
}

p {
  background: #CCCCCC;
  line-height: 32px;
}

input.button1 {
  background: #990000;
  color: #ffffff;
  width: 180px;
  margin: 2px;
  line-height: 20px;
}

input.button1:hover {
  background: #ffffff;
  color: #990000;
}

input.button2 {
  background: #ff6600;
  color: #ffffff;
  width: 200px;
  margin: 2px;
  line-height: 20px;
}

input.button2:hover {
  background: #ffffff;
  color: #ff6600;
}

body {
  background: #000000;
}