Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Using Drawing Tools and Maps to Find Data

DZone's Guide to

Using Drawing Tools and Maps to Find Data

· Big Data Zone
Free Resource

Free O'Reilly eBook: Learn how to architect always-on apps that scale. Brought to you by Mesosphere DC/OS–the premier platform for containers and big data.

A few years back, a good buddy of mine (Ryan LeTulle) demoed a cool real estate map application he built. I asked him to blog it (because everyone has a blog, and time to blog, right?) but he never got around to it. Last week I asked him if he minded me rebuilding what I saw him do, and he said go for it. Thanks, Ryan, for the inspiration!

Have you ever tried to use a real estate site to find homes but had trouble narrowing down your search criteria? For example, you know what city you want to live in, but also what area. If you're lucky, the site lets you search by subdivision, but how in the heck do you figure that out? My subdivision is Ivanhoe, but I lived here about 10 years before I realized that.

What you would really like, I imagine, is being able to draw your search criteria. Given a map of the city and a vague notion of where you want to live, what if we could draw a region and find results within it?

This idea relies on two main aspects. First is the ability to draw on a map. I'm going to be using Google Maps for this project. Google Maps is a pretty darn deep API. It allows you to draw markers, lines, boxes, and polygons in general on a map. It also allows for deep interaction with those UI items as well.

While I researched this idea, I discovered they had a feature called "User-editable Shapes". As you can imagine, this lets a user move and adjust a shape on a map:

This is nice - but a box isn't exactly precise enough. I decided to begin with simple lines (what Google calls Polylines). I created a map and then used click events to add markers with lines connecting them. Luckily, Google had an example of this already (Polyline complex), so I began with that. I made one modification, though. I set it up so that as soon as you had clicked three times, I'd "close" the box automatically for you.

As an example:

The code is rather simple - just notice how many lines we've drawn and on the 3rd click, automatically close the box:

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);

This worked ... OK (and you can demo it here), but I felt bad that I was forcing you to search within a four sided polygon. What I really wanted was the ability to let you click as much as you want, and when you're done, close the 'box' automatically. As an example, I've created a multi-segmented polygon here and clicked search to complete the region:

Overall, I felt like this was a good solution. Now - right away you may be asking - what happens if you draw something crazy, like, oh say this:

Don't do that. Seriously.

OK ... so, at this point, we've covered the first main aspect of this project - giving you the ability to 'draw' a region. Now comes the second. Given that we know the region, how do we find crap inside it?

I was a bit torn about this aspect. Turns out there is a client-side solution provided by Google (no surprise), but also server side code you can use as as well. CFLib has a UDF (PointInPolygon) nearly 10 years old that would do this. So, in theory, we could hit a server database, do some logic, and return the points. Or we could do it client side. Keeping the data on the server allows for quicker initial load. Keeping it in the client lets us search a bit quicker, but makes the first load 'fatter.'

I decided to go with a client-side solution mainly because I wanted to test this particular Google API, but also because I was curious how 'bad' the hit would be if I did store a large set of data on the client. In my example, I'm only storing longitude/latitude data. I'd imagine that once you click on a result, we could do an AJAX ping to get further data. Or heck, we could do so as soon as you complete your polygon. I wrote a script (you can view source here) to generate 400 different long/lat pairs that were roughly in the Lafayette area. This script outputs a JavaScript variable I could then take and save into a file called data.json.

Now that I had my data, I could update my code to load it into memory. As to how I could filter the data, I made use of Google's Geometry library. It lets you pass a long/lat point and a polygon and it returns true if the point is within it. Here is the updated edition. You want to pay attention to the doSearch function:

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);

As you can see, I loop over the data and pass it to the Geometry API. If a match is found, I add a marker. When done, I report on the total matches. You can demo this by hitting the giant demo button below. Note that hitting Search twice will destroy the universe. Don't hit search twice.

Demo

P.S. I actually tested "crossing the streams" and it worked perfectly. I knew it would. Honest.


Easily deploy & scale your data pipelines in clicks. Run Spark, Kafka, Cassandra + more on shared infrastructure and blow away your data silos. Learn how with Mesosphere DC/OS.

Topics:

Published at DZone with permission of Raymond Camden, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}