JavaScriptでAND検索を実装した話(3Dマップ上に〜その2)

5434 0

さて、前回の続き。
前回は無事、3Dスフィア(球体)上にマーカーを設置することができました(=3D座標上に正確にマーカーを配置)

今回はAND検索部分を実装していきます。
方式としては、[ { key : value, key : value, ... }, { key : value, key : value, ... } ]のオブジェクトの配列を絞り込みのプログラムに渡すと、条件{ 特定のkey : 条件 }に合致するデータを新しい配列で返す形式です。

マーカーのindexとデータのindexは共通なので(※前回のJSONデータ参照)、データの元々のindexも合わせて返ってきてほしい(そのindexに応じたマーカーの表示非表示を切り替える為)。

ポイントとしては下記

  1. マーカーは絞り込みに応じて表示/非表示を切り替える必要がある
  2. 絞り込みには「リセット機能」(絞り込みを全てはずす)がある
  3. チェックボックスの状態が変化する毎に絞り込みを実行
  4. 2つのhtml(2D/3D)で絞り込みの結果を保持

1.マーカー表示切り替え

3Dと2D(googleMap)で少し違うので、それぞれの関数を用意。3DマップではDOMを使っているのでDOM操作で。googleMapではmaps.Markerなので提供されているメソッドで処理。

// 3Dマーカー用
function globeMarkerToggle(flag,num){
	var status = (flag) ? 'visible' : 'hidden';
	$(selector).eq(num).css({'visibility': status})
}
// 2Dマーカー用
function mapMarkerToggle(flag,num){
	markers[num].setOptions({ visible: flag })
	var test = (markers[num].visible === false) ? true : false;
	var test2 = (markers[0].visible === false) ? true : false; // 日本用
}

これを条件分岐で使用すればOK。

2.リセット

  • すべてのチェックボックスのチェックを外す
  • マップ上のマーカーをすべて表示する

の2つの処理を行えばOK。

function allVisible(type){
	if(type === '3D'){
		for(var i = 0; i < COMMON.markers; i++){
			globeMarkerToggle(true,i)
		}
	}else{
		for(var i = 0; i < COMMON.markers; i++){
			mapMarkerToggle(true,i)
		}
	}
	$('#japan .info').removeClass('hidden');
	resultData = null;
	resultData2 = null;
	clearStorage(); // 保持データ削除
	countNumber(COMMON.markers)
}

// リセットを押した時の処理
reset.on('click',function(){
	$(checkbox).prop('checked',false);
	allVisible(type) // typeは3Dか2Dかをwindow.load時に判定して格納した変数
});

3の前に、先に4

データを保持する方法は色々ありましたが、保持は閲覧中のみで良いとのことだったのでsessionStorageを使うことにしました。(cookieでも問題ありません。データの取り扱いが楽だからという理由でsessionStorageを採用しました)

Storageに保存するのは、表示されるマーカのindexではなく、checkboxのindexに->そこから再度マーカーの表示状態を処理という方式をとりました。

// 保存用
function saveStorage(){ //checkbox:checked index save(array).
	var target = $('checkboxのselector')
	var arr = [];
	target.each(function(i){
		if($(this).prop('checked')){ arr.push(i) }
	})
	sessionStorage.setItem('checked', JSON.stringify(arr))
}

// 読み出し用
function loadStorage(){
	COMMON.refined = true;
	var value = JSON.parse(sessionStorage.getItem('checked'));
	var target = $('checkboxのselector');
	var length = value.length;
	for (var i = 0; i < length; i++) {
		var n = value[i];
		target.eq(n).prop('checked',true);
	}
}

// 削除用
function clearStorage(){
	sessionStorage.removeItem('checked')
}

3.チェックボックの状態に応じて絞り込み

都度実行するのは、正直あんまりいい実装じゃないんじゃないかなー…と思います。かなりの高頻度で処理が走ることになる為、負荷が高すぎるでしょう。

ベターなのはチェックボックスをチェックし終わった後に「絞り込み開始ボタン」みたいなものを押して、その時初めて実行されるようにする実装かなと思います。(説明はしましたが、リアルタイム感が欲しかったんでしょう)

少しでも処理を高速化する為に、毎回JSONを参照せずに最初にデータをキャッシュさせて(結構メモリ食うので微妙ですが…)、キャッシュデータはそのまま保持し、絞り込み結果等は別変数へ格納するようにしました。

また、絞り込みを行う際に、初回と2回目以降で結構処理が異なった為、本来であれば条件分岐で一つのメソッド内で処理すれば良いところではありますが、
前述の『かなり高頻度で処理が走ることになる』ために、条件分岐の頻度を最小限にして、少しでも処理不可を下げるということを優先した結果、メソッドを2つに分けるという(今思えば)非効率なこともしていました。

完成型

var MT ={};

MT.NarrowData = (function($){
	var self = this;
	var balloon = $('#map-balloon span');
	var db;
	var db2;
	var originLength;
	var resultData;
	var resultData2;
	var checked = 0;

	// マーカー系の処理

	// storage系の処理

	function _markersToggle(searchTrg,resultData,resultLength){
		var checkCount = 0;
		if(COMMON.MODE === '3D'){
			if(searchTrg === 'japan'){
				for (var i = 0; i < resultLength; i++) {
					//resultDataにあるindexは処理をスルー
					if(i === resultData[checkCount]){ checkCount++;continue; }
					_toggeleInfo(false,i)
				}
			}else{
				for (var i = 0; i < resultLength; i++) {
					//resultDataにあるindexは処理をスルー
					if(i === resultData[checkCount]){ checkCount++;continue; }
					_globeMarkerToggle(false,i)
				}
			}
		}else{
			if(searchTrg === 'japan'){
				for (var i = 0; i < resultLength; i++) {
					//resultDataにあるindexは処理をスルー
					if(i === resultData[checkCount]){ checkCount++;continue; }
					_toggeleInfo(false,i)
				}
			}else{
				for (var i = 0; i < resultLength; i++) {
					//resultDataにあるindexは処理をスルー
					if(i === resultData[checkCount]){ checkCount++;continue; }
					_mapMarkerToggle(false,i)
				}
			}
		}
	}

	function _search(data,type){
		var searchTrg = (type) ? 'japan' : 'world';
		var searchData = (searchTrg === 'japan') ? db2 : db;

		var tmp = [];
		var key = data[0];
		var val = data[1];
		var now_result = (searchTrg === 'japan') ? resultData2 : resultData;

		if(now_result instanceof Array){
			var count = now_result.length;
			var checkData = now_result;
		}else{
			var count = (searchTrg === 'japan') ? searchData.length : COMMON.markers;
			var checkData = searchData;
		}

		var TMPresultData = [];
		var loopRsult = [];
		for (var i = 0; i < count; i++) {
			/*
				このfor文の中で前ループ分の結果に対して判定
			*/
			if(now_result instanceof Array){
				var index = now_result[i];
			}else{
				var index = i;
			}

			var checkData = searchData[index]; //絞り込み済みデータを抽出

			if(checkData[key] instanceof Array){
				var tmpV = checkData[key];
				var childlength = tmpV.length;
				var tm_result = false;
				for (var ci = 0; ci < childlength; ci++) {
					if( tmpV[ci] === val ){ tm_result = true; } 
				}
				var result = (tm_result) ? true : false;
			}else{
				var result = (checkData[key] === val) ? true : false;
			}
			if(i === 0){result = true}

			tmp[i] = result;
			if(tmp[i]){ loopRsult.push(index) }// iでpushすると2回目以降indexが狂うため×
		}
		TMPresultData = loopRsult;

		//上のfor分の結果からまとめて消すマーカーを処理
		var resultLength = searchData.length;
		_markersToggle(searchTrg,TMPresultData,resultLength);

		if(searchTrg === 'japan'){
			resultData2 = TMPresultData;
			var refinedLength = resultData2.length;
		}else{
			resultData = TMPresultData;
			var refinedLength = resultData.length;
			countNumber(refinedLength)
			_saveStorage()
		}
	}

	function _research(data,type){ //絞り込み2回目以降
		// init Markers condition.
		if(COMMON.MODE === '3D'){
			_allVisible('3D');
		}else{
			_allVisible('MAP');
		}
		var searchTrg = (type) ? 'japan' : 'world';
		var searchData = (searchTrg === 'japan') ? db2 : db;
		var newData = [];
		var newDataLenght = data.length;

		for (var i = 0; i < newDataLenght; i++) {
			var str = data[i].split(':')
			newData.push(str)
		}

		newDataLenght = newData.length;

		var TMPresultData = [];
		/*
			このfor文の中で前ループ分の結果に対して判定
			初回のループは全件走査
			2回目以降は初回のループで絞り込まれた件数から走査
			外側のループが条件の個数、内側のループでデータとの照合を行う
		*/
		for(var i = 0; i < newDataLenght; i++){
			var loopTmp = [];
			var loopResult = [];

			var count = (i === 0) ? COMMON.markers : TMPresultData.length;
			for (var j = 0; j < count; j++) {

				if(i === 0){
					var index = j
				}else{
					var index = TMPresultData[j]
				}
				var checkData = db[index]; //絞り込み済みデータを抽出
				var newCondition = newData[i]; //判定条件

				var key = newData[i][0]; //条件のkey
				var val = newData[i][1]; //条件の値
				var result = (checkData[key] === val) ? true : false;

				if(i === 0){result = true}

				loopTmp[j] = result;
				if(loopTmp[j]){ loopResult.push(index) }
			}
			TMPresultData = loopResult;
		}
		loopTmp = null;
		loopResult = null;

		//上のfor分の結果からまとめて消すマーカーを処理
		var resultLength = db.length;
		_markersToggle(searchTrg,TMPresultData,resultLength);

		resultData = TMPresultData;
		var refinedLength = resultData.length;
		countNumber(refinedLength)
		_saveStorage(TMPresultData);
	}

	function _creatObj(type,strings){
		var setType = (type) ? type : 'default';
		if(!COMMON.refined){ COMMON.refined = true }
		if( setType === 'default' ){
			var str = strings.split(':');
			_search(str);
			_search(str,'japan')
		}else if( setType === 'increase'){
			// ここでforループさせて配列化したうえでresearchに渡す
			var arr = [];
			var target = $('#mop-container input:checked')
			var count = target.length
			for (var i = 0; i < count; i++) {
				arr.push(target.eq(i).val())
			}
			_research(arr);

		}
	}

	var initialize = function(){
		db = COMMON.dataList;
		db2 = COMMON.dataList2;
		originLength = db.length
		var checkbox = $('#map-container input');
		var reset = $('#reset');
		var submit = $('#submit');

		// checked Reset.
		reset.on('click',function(){
			$('[name="queries"]').prop('checked',false);
			if(COMMON.refined){
				_allVisible(COMMON.MODE)
			}
		});

		checkbox.on('change',function(){
			if(COMMON.refined){ var checking = checked; }
			checked = $('#map-container input:checked').length;
			
			if(checked > 0){
				var val = $(this).val()

				if(checked < checking){
					// マーカーが増える際は、現状チェックされているcheckboxすべてからvalueを受け取り、一つ前の状態と比較する必要がある
					_creatObj('increase',val)
				}else{
					_creatObj(0,val)
				}

			}else{
				_allVisible(COMMON.MODE)	
				countNumber(originLength);
			}
		})
	}

	return {
		// Pubkic Methods
		init: function(){
			initialize();
		},
		count: function(to){
			countNumber(to);
		},
		japanCount: function(num){
			japansCounter(num);
		}
	}
})(jQuery);

(※全部をまとめて書くとsyntaxhighlighterがバグったので分割して中略)
(※注釈:これ以前に分割して書いていたコードはすべてプライベートメソッドとして設定していたので、本来関数名の前に「_(アンダースコア)」を入れていました。)

しかし…ここに来て事件発生。

「…やっぱり、絞り込みの結果が0になるのはなんだか……」

(え…?AND検索なんだから当然じゃ……)

「OR検索に変えてもらえませんか?」

(……。)

次回、「JavaScriptでOR検索を実装した話」、つづく…?

JSON

Comments

Add a Comment

メールアドレスが公開されることはありません。

認証コード * Time limit is exhausted. Please reload CAPTCHA.

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください