Статьи

Использование инструментов рисования и карт для поиска данных

Несколько лет назад мой приятель ( Райан ЛеТулле ) продемонстрировал классное приложение на карте недвижимости, которое он создал. Я попросил его написать в блоге (потому что у всех есть блог и время для блога, верно?), Но он так и не нашел его. На прошлой неделе я спросил его, не возражает ли он, чтобы я восстановил то, что я видел, как он делает, и он сказал, что сделай это. Спасибо, Райан, за вдохновение!

Вы когда-нибудь пытались использовать сайт по недвижимости, чтобы найти дома, но не могли сузить критерии поиска? Например, вы знаете, в каком городе вы хотите жить, но также и в каком районе. Если вам повезет, сайт позволит вам искать по подразделениям, но как, черт возьми, вы это выясните? Мое подразделение — Айвенго, но я жил здесь около 10 лет, прежде чем понял это.

Я думаю, что вам действительно хотелось бы получить критерии поиска. Учитывая карту города и смутное представление о том, где вы хотите жить, что, если бы мы могли нарисовать регион и найти в нем результаты?

Эта идея опирается на два основных аспекта. Во-первых, это возможность рисовать на карте. Я собираюсь использовать Google Maps для этого проекта. Карты Google — чертовски глубокий API. Это позволяет рисовать маркеры, линии, прямоугольники и многоугольники в целом на карте. Это также позволяет глубоко взаимодействовать с этими элементами пользовательского интерфейса.

Исследуя эту идею, я обнаружил, что у них есть функция под названием «Редактируемые пользователем формы» . Как вы можете себе представить, это позволяет пользователю перемещать и корректировать фигуру на карте:

Это хорошо — но коробка не совсем точна. Я решил начать с простых строк (то, что Google называет Polylines). Я создал карту, а затем использовал события щелчка, чтобы добавить маркеры с линиями, соединяющими их. К счастью, у Google уже был пример этого ( комплекс Polyline ), поэтому я начал с этого. Я сделал одну модификацию, хотя. Я настроил его так, чтобы, как только вы нажали три раза, я автоматически «закрыл» окно для вас.

Например:

Код довольно прост — просто обратите внимание, сколько линий мы нарисовали, и при третьем щелчке автоматически закройте окно:

function initialize() {

  var mapOptions = {
		center: new google.maps.LatLng(-34.397, 150.644),
		zoom: 8,
		mapTypeId: google.maps.MapTypeId.ROADMAP
	};

	var map = new google.maps.Map(document.getElementById("map-canvas"),mapOptions);

	var polyOptions = {
		strokeColor: '#000000',
		strokeOpacity: 1.0,
		strokeWeight: 3
	}

	var markers = [];
	
	poly = new google.maps.Polyline(polyOptions);
	poly.setMap(map);
	
	google.maps.event.addListener(map, 'click', function(event) {

		var path = poly.getPath();

		path.push(event.latLng);
		
		markers.push(new google.maps.Marker({
			position: event.latLng,
			title: '#' + path.getLength(),
			map: map
		}));
		
		if(path.getLength() == 4) {
			console.log('box it');
			path.push(markers[0].getPosition());
			console.dir(markers);
		}

	});
}

google.maps.event.addDomListener(window, 'load', initialize);

Это сработало … Хорошо (и вы можете продемонстрировать это здесь ), но мне стало плохо, когда я заставлял вас искать внутри четырехстороннего многоугольника. Что я действительно хотел, так это возможность позволять вам нажимать столько раз, сколько вы хотите, и когда вы закончите, автоматически закрывайте «ящик». В качестве примера я создал многосегментный многоугольник здесь и щелкнул поиск, чтобы завершить регион:

В целом, я чувствовал, что это было хорошее решение. Теперь, прямо сейчас, вы можете спросить, что произойдет, если вы нарисуете что-то сумасшедшее, как, ну, скажем так:

Не делай этого. Шутки в сторону.

Хорошо … Итак, на данный момент мы рассмотрели первый основной аспект этого проекта — дать вам возможность «нарисовать» регион. Теперь приходит второй. Учитывая, что мы знаем регион, как мы можем найти в нем дерьмо?

Я был немного разорван об этом аспекте. Оказывается, есть решение на стороне клиента, предоставленное Google (не удивительно), но также и код на стороне сервера, который вы также можете использовать. У CFLib есть UDF ( PointInPolygon ), которому почти 10 лет, и он будет этим заниматься. Таким образом, теоретически мы можем получить доступ к базе данных сервера, выполнить некоторую логику и вернуть очки. Или мы могли бы сделать это на стороне клиента. Хранение данных на сервере обеспечивает более быструю начальную загрузку. Хранение этого в клиенте позволяет нам искать немного быстрее, но делает первую загрузку «толще».

Я решил использовать клиентское решение главным образом потому, что хотел протестировать этот конкретный API Google, а также потому, что мне было любопытно, насколько «плохим» будет удар, если я сохраню большой набор данных на клиенте. В моем примере я храню только данные долготы / широты. Я полагаю, что, как только вы нажмете на результат, мы можем выполнить пинг AJAX, чтобы получить дополнительные данные. Или, черт возьми, мы можем сделать это, как только вы закончите свой полигон. Я написал скрипт (вы можете посмотреть исходный код здесь ), чтобы сгенерировать 400 различных длинных / латовых пар, которые были примерно в районе Лафайетта. Этот скрипт выводит переменную JavaScript, которую я мог бы затем сохранить и сохранить в файле с именем data.json.

Теперь, когда у меня были данные, я мог обновить свой код, чтобы загрузить его в память. Что касается того, как я мог фильтровать данные, я использовал библиотеку Google Geometry . Он позволяет вам передать точку long / lat и многоугольник, и он возвращает true, если точка находится внутри него. Вот обновленное издание. Вы хотите обратить внимание на функцию doSearch:

function initialize() {

  var locData;
	
	var searchButton = document.querySelector("#searchButton");
	var resultsDiv = document.querySelector("#results");
	
	var mapOptions = {
		center: new google.maps.LatLng(30.223178, -92.024231),
		zoom: 12,
		mapTypeId: google.maps.MapTypeId.ROADMAP
	};

	var map = new google.maps.Map(document.getElementById("map-canvas"),mapOptions);

	var polyOptions = {
		strokeColor: '#000000',
		strokeOpacity: 1.0,
		strokeWeight: 3
	}

	var blueIcon = {
		url:"http://www.google.com/intl/en_us/mapfiles/ms/micons/blue-dot.png"
	};
	
	var markers = [];
	
	poly = new google.maps.Polyline(polyOptions);
	poly.setMap(map);
	
	google.maps.event.addListener(map, 'click', function(event) {

		var path = poly.getPath();

		path.push(event.latLng);
		
		markers.push(new google.maps.Marker({
			position: event.latLng,
			title: '#' + path.getLength(),
			icon:blueIcon,
			map: map
		}));
		
	});
	
	searchButton.addEventListener("click", function(e) {
		var path = poly.getPath();
		if(path.getLength() < 4) {
			alert('Please select at least 4 points.');
			return;
		}
		path.push(markers[0].getPosition());
		searchButton.setAttribute("disabled","disabled");
		doSearch();
	});
	
	$.get("data.json", {}, function(res) {
		locData=res;
		searchButton.innerText = "Search";
		searchButton.removeAttribute("disabled");;
	});
	
	function doSearch() {
		results.innerHTML = "<i>Searching for matches...</i>";
		var totalFound = 0;
		for(var i=0; i<locData.length; i++) {
			//I should probably cache this creation!
			var point = new google.maps.LatLng(locData[i][0],locData[i][1]);
			if(google.maps.geometry.poly.containsLocation(point,poly)) {
				var m = new google.maps.Marker({
					position: point,
					title: 'Location '+i,
					map: map
				});
				totalFound++;
			} 
		}
		results.innerHTML = "Found " + totalFound + " results.";		
	}
	
}

google.maps.event.addDomListener(window, 'load', initialize);

Как видите, я перебираю данные и передаю их в Geometry API. Если совпадение найдено, я добавляю маркер. Когда все будет готово, я сообщу об итогах матчей. Вы можете продемонстрировать это, нажав гигантскую демонстрационную кнопку ниже. Обратите внимание, что двойное нажатие кнопки «Поиск» уничтожит вселенную. Не нажимайте дважды.

демонстрация

PS Я на самом деле проверил «пересечение потоков», и это работало отлично Я знал, что так и будет. Честный.