Chris Essig

Walkthroughs, tips and tricks from a data journalist in eastern Iowa

Leaflet formula: Counting markers within a radius (version 2)

with 4 comments

A couple of years ago, I open-sourced a project that allows you to count how many markers are within a circle using Leaflet. As part of the template, a user needs to enter an address to get a marker to show up on the map.

Two weeks ago, Jefferson Guerrón reached out to me because he was creating something similar that didn’t require a user to type in their address. He was using the plugin Leaflet.draw to get the marker on the map. But he was having a hard time getting a circle to show up around the marker and count the other markers within that circle.

After a few back-and-forth emails, we were able to come up with a solution. Here’s a look of the final project. And the code is available on Github.

If you have questions, don’t hesitate to e-mail or comment.

Written by csessig

December 14, 2016 at 5:15 pm

How automation can save you on deadline

leave a comment »

In the data journalism world, data dumps are pretty common occurrences. And often times these dumps are released periodically — every month, every six months, every year, etc.

Despite their periodic nature, these dumps can catch you off guard. Especially if you are working on a million things at once (but nobody does that, right?).

ss

Case in point: Every year the Centers for Medicare & Medicaid Services releases data on how much money doctors received from medical companies. If I’m not mistaken, the centers release the data now because of the great investigative work of ProPublica. The data they release is important and something our readers care about.

Last year, I spent a ton of time parsing the data to put together a site that shows our readers the top paid doctors in Iowa. We decided to limit the number of doctors shown to 100, mostly because we ran out of time.

In all, I spent at least a week on the project last year. But this year, I was not afforded that luxury because the data dump caught us off guard. Instead of a few weeks, we had a few days to put together the site and story.

Fortunately, I’ve spent quite a bit of time in the last year moving away from editing spreadsheets in Excel or Google Docs to editing them using shell scripts.

The problem with GUI editors is they aren’t easily reproducible. If you make 20 changes to a spreadsheet in Excel one year and then the next year you get an updated version of that same spreadsheet, you have to make those exactly same 20 changes again.

That sucks.

Fortunately with tools like CSVKit and Agate, you can write A LOT of Excel-like functions in a shell script or a Python script. This allows you to document what you are doing so you don’t forget. I can’t tell you how many times I’ve done something in Excel, saved the spreadsheet, then came back to it a few days later and completely forgotten what the hell I was doing.

It also makes it so you can automate your data analysis by allowing you to re-run the functions over and over again. Let’s say you perform five functions in Excel and trim 20 columns from your spreadsheet. Now let’s say you want to do that again, either because you did it wrong the first time or because you received new data. Now would you rather run a shell script that takes seconds to perform this task or do everything over in Excel?

Another nice thing about shell scripts is you can hook your spreadsheet into any number of data processing programs. Want to port your data into a SQLite database so you can perform queries? No problem. You can also create separate files for those SQL queries and run them in your shell script as well.

All of this came in handy when this year’s most paid doctors data was released. Sure, I still spent a stressful day editing the new spreadsheets. But if I hadn’t been using shell scripts, I doubt I would have gotten done by our deadline.

Another thing I was able to do was increase the number of doctors listed online. We went from 100 total doctors to every doctor who received $500 or more  (totaling more than 2,000 doctors). This means it’s much more likely that readers will find their doctors this year.

The project is static and doesn’t have a database backend so I didn’t necessarily need to limit the number of doctors last year. We just ran out of time. This year, we were able to not only update the app but give our readers much more information about Iowa doctors. And we did it in a day instead of a week.

The project is online here. The code I used to edit the 15+ gigabytes worth of data from CMS is available here.

 

 

Written by csessig

September 7, 2016 at 4:59 pm

Building your first Leaflet.js map

leave a comment »

Earlier this year, I had the privilege of teaching a class on building your first Leaflet.js map at the NICAR conference. I just realized I forgot to post the code in this blog so I figured I’d post it now. Better later than never.

If you’re interested in building your first map, check out my Github repo for the presentation.

Written by csessig

August 17, 2016 at 7:58 am

Introduction to Javascript

with 2 comments

A few weeks ago, I presented on the basics of Javascript to my fellow developers at FusionFarm.

If you want to check out my slides for the presentation, they are available here. I went over variables, objects, for loops, functions, and a whole host of other fun stuff.

If I missed anything, let me know in the comments.

Written by csessig

July 12, 2016 at 9:13 am

Posted in Javascript, Uncategorized

Tagged with

D3 formula: Splitting elements into columns

leave a comment »

icons_columnsD3 can be a tricky — but powerful — beast. A month ago, I put together my most complex D3 project to date, which helped explain Iowa’s new Medicaid system

One of the first places I started on this project was building a graph that will take an icon and divide it into buckets. I didn’t see any openly available code that replicated what I was trying to do, so I’d figure I’d post my code online for anyone to use/replicate/steal.

For this project, I put the icons into three columns. In each column, icons are placed side by side until we get four icons in a row, then another row is created. You can see this in action by clicking the button several times. And all of this can be adjusted in the code with the “row_length and “column_length” variables.

To move the icons, I overlaid three icons on top of each other. When the button is clicked, each of the icons gets sent to one of the columns. The icons shrink as they reach their column. After this transition is finished, three more icons are placed on the DOM. And then the whole process starts over.

A bunch of math is used to determine where exactly on the DOM each icon needs to go. Also, we have to keep track of how many icons are on the DOM already, so we can break the icons into new rows if need be.

Data is required to make D3 run so my data is a simple array of [0,1,2]. The array has three values because we have three columns on the page. We do some calculations on the values themselves and then use SVG’s transform attribute to properly place the icons on the DOM. We use D3’s transition function to make it appear like the icons are moving into the icons, not just being placed there.

Hopefully this helps others facing similar problems in D3. If you have any questions, just leave them in the comments.

Written by csessig

May 2, 2016 at 12:30 pm

Save your work because you never know when it will disappear

with 2 comments

We are a few weeks into the new year, but I wanted to look back at the biggest project I worked on in 2015: The redesign of KCRG.com.

While most of my blog posts are full of links, I can’t link to that site. Why? Because it’s gone.

What?

In a series of very unfortunate events, the site we spent many, many months planning and developing is already gone.

The timeline: We started preparing for the redesign, which was BADLY needed, in early 2015. We then built it over the course of several months. Finally, it was launched in July. Then, in a move that surprised every one, KCRG was bought by another company in September.

At the time, I was optimistic that the code could be ported over to their CMS. And the site wouldn’t die.

My optimism was short lived. Gray has a standard website template for all its news sites, and they wanted that template on KCRG.

So in December, the website we built disappeared for good.

 

The KCRG website you see now is the one used and maintained by Gray.

Obviously, this was a big shock for our team. Even worse, the code we wrote was proprietary and requires Newscycle Solutions to parse and display. So even if I wanted to put it on Github, it wouldn’t do anyone any good.

I’m not used to the impermanence of web. When I had my first reporting job in Galesburg, I saved all the newspapers where my stories appeared. And unless my parents’ house catches on fire, those newspapers will last for a long time. They are permanent.

Not so online. Websites disappear all the time. And those who build them have barely any record of their existence.

Projects like PastPages and the Wayback Machine keep screenshots of old websites, which is better than nothing. But their archives are far cries from the living, breathing websites we build. A screenshot can’t show you nifty Javascript.

It’s an eery feeling. What happens in five years? Ten years? Twenty years? Will any of our projects still be online? Even worse: Will technology have changed so much that these projects won’t even be capable of being viewed online? Will online even exist?

Think about websites from 1996. They are long gone. Hell, many sites from two years ago have vanished.

I don’t have good answers. Jacob Harris has mulled this topic and offered some good tips for making your projects last.

But it’s worth pondering when you’re done with projects. What can I do to save my work for the future? I have a directory of all of my projects from my Courier days on an external hard drive. I have an in-process directory for The Gazette as well.

I hold onto them like I did my old newspaper clippings. Although, I’m confident those clippings will last a lot longer than my web projects.

Written by csessig

January 21, 2016 at 11:52 am

Quick intro to PJAX

leave a comment »

Every week, the developers at Fusion Farm meet and have a code review. Last week, I presented on PJAX, which I spent a few hours digging into.

The code behind my presentation is available on my Github page.

You really need to set up a back-end project to use PJAX to its fullest. For this presentation, I focused on just the front-end components, which will at least give you an idea of how the library works.

I’ve seen PJAX (or something like it) a few times in the wild. The Chicago Tribune, for instance, uses it (or something similar) on their website. If you scroll down their page long enough, you’ll notice a new page opens up in your browser. But the entire page doesn’t refresh. Instead the URL changes smoothly as you scroll down the page.

Anyways, check out the code on my site and if you have any questions, fire them my way.

 

Written by csessig

December 21, 2015 at 9:58 am

Posted in Uncategorized

Exploring D3.js

leave a comment »

network_graph_mary_ssI’ve been wanting to use the D3.js library for some time now but I haven’t up to this point mostly because the code that runs the charts scares the crap out of me (that’s only a slight exaggeration).

Well this weekend I finally took on the scary and published my first D3 chart. The final product is a network graph that shows how one woman is helping connect people within the community. It’s not crazy awesome like most of the other D3 graphics out there but it’s a start.

The code started out as this chart from Mike Bostock and was modified to meet my needs. The final code is on GitHub.

While it wasn’t quite as intimidating as I thought it would be, a few things tripped me up. Chaining methods in D3 was something that threw me off at first. I was also unfamiliar with SVGs. This is a good primer from Scott Murray. Finally, many of the D3 methods are still mysterious me, like diagonal and the layouts. But one step at a time, right?

Some of it, however, isn’t crazy complicated. I’m going to review some of what I learned about D3 by going through some of the code that powered this project.

The code snippet below is line 47 in the chart’s script.js file. It starts the graph. It selects the empty SVG that I put in index.html, sets it’s width and height and appends a G element to it:

// Create an svg
  svg = d3.select("svg")
  	.attr("width", width + margin.right + margin.left)
  	.attr("height", height + margin.top + margin.bottom)
  	.append("g")
  	.attr("transform", "translate(" + (margin.left - line_width) + "," + margin.top + ")");

A few lines down, we create a variable called node, select all the G elements and bind data to them.

// Update the nodes
  var node = svg.selectAll("g.node")
  	.data(nodes, function(d) {
      return d.id || (d.id = ++i);

  	})

This G element contains everything in the chart and is placed just under the empty svg element in index.html

We then create a variable called nodeEnter, which appends another G element to the G element we just created. (confusing, I know).

// Enter any new nodes at the parent's previous position.
  var nodeEnter = node.enter().append("g")
    	// Set location of nodes
    	.attr("transform", function(d) {
    		if ( d['name'] === 'Mary Palmberg' && d['depth'] === 2) {
    			return "translate(" + circle_mary_location + "," + circle_mary_translate + ")";
        } else {

          
          if ( d['name'] === 'Thomas Jensen' ) {
            d.x -= 20;
          } else if ( d['name'] === 'Clarence Borck' ) {
            d.x += 20;
          } else if ( d['name'] === 'Jane Skinner' && d['connection'] === 5 ) {
            d.x -= 20;
          }
    			
          return "translate(" + d.y + "," + d.x + ")";
    		}
    	})
    	.attr("class", function(d) {
    		if (d['name'] === 'Mary Palmberg') {
    			return 'node-mary';
    		} else if (d['name'] === 'Top level') {
    			return 'node-top';
    		} else {
    			return 'node'
    		}
    	});

In our chart, these G elements are the nodes that contain our circles. Later in the code, I made lines that connect the circles. But we’re going to focus on just these G elements.

It may make more sense to inspect the graph to see what I’m talking about:

d3_mary_blog

After we append the G elements to their parent G elements, we chain a series methods to it, which set attributes for the G elements. These methods are called D3 selectors. With D3 selectors, you can select any shape in the SVG and a do a bunch of things to it. We can append new nodes to it, set CSS properties, give it a certain class name, etc. And since we bound data to the shape, we can decide what those properties will be based on the data we gave it.

This is seriously awesome, by the way.

In the code above, we used a D3 selector to set the location of the shapes using the CSS attribute transform. You’ll notice how setting that attribute is a function with a “d” parameter. “D” is equal to the data behind the shape we are drawing (remember, we appended data to it earlier). It returns an object with different properties. In this case, the property “name” is equal to the name of the person we are drawing in the circle.

For this chart, I had 16 G elements on the page. I also have objects with properties about each G element in a separate JSON file. As we loop through each G element and create it on the page, the data parameter becomes whichever G element we are on. So when we are creating Mary Palmberg’s circle, the “D” parameter will equal data about Mary Palmberg.

“D” returns an object that looks like so:

d3_mary_blog02

In the code, I’m checking to see if the G element we are on is Mary Palmberg, because her circle is placed in the middle and all other circles point to her. If we are on Mary, we set the CSS property transform to the variables called “circle_mary_location” and “circle_mary_translate”, which I set earlier in the script.js file. These basically put her circle in the middle of the page.

If it’s not Mary, we run the else part of the if-else statement. We do a couple of checks on a few people and change their x values. This is done because some of the circles were a little bit too close to together. After that, I put the data’s x and y coordinates in CSS translate property and return it. This basically puts it in the correct position on the page.

The last part should be easy to understand. All it does is set a class for the G Element depending on the person’s name. We give Mary a different class name so we can apply different styles to her circle:

d3_mary_blog03

It’s important to note that we haven’t actually created any shapes yet. Instead, G elements are just groups of shapes. The next thing we’re going to do is actually append circles to the empty G element.

nodeEnter.append("circle")
    	.attr("id", "circle-outer")
    	// Make radius smaller than inner circle
    	.attr("r", function(d) {
    		if (d['name'] === 'Mary Palmberg') {
    			if ( $(window).width() > 700) {
                    return circle_radius + 7;
                } else {
                    return circle_radius + 17;
                }
    		} else {
    			return circle_radius + 2;
    		}
    	})
    	// Color code the border around the circle
    	.style('stroke', function(d) {
    		// Color Mary differently
    		if (d['name'] === 'Mary Palmberg') {
    			// Red
    			return '#e41a1c'
    		} else if (d['connection'] === 1) {
    			// Purple
    			return "#984ea3";
    		} else if (d['connection'] === 2) {
    			// Green
    			return "#4daf4a";
    		} else if (d['connection'] === 3) {
    			// Blue
    			return "#377eb8";
    		} else if (d['connection'] === 4) {
    			// Orange
    			return "#ff7f00";
    		} else if (d['connection'] === 5) {
    			// Brown
    			return "#a65628";
    		} else {
    			// Blue
    			return "#377eb8";
    		}
    	});

  // Create the inner circle
  nodeEnter.append("circle")
    	.attr("id", "circle-inner")
    	// Radius
    	.attr("r", function(d) {
    		if (d['name'] === 'Mary Palmberg') {
    			if ( $(window).width() > 700) {
                    return circle_radius + 5;
                } else {
                    return circle_radius + 15;
                }
    		} else {
    			return circle_radius;
    		}
    	})
    	.style("fill", function(d) {
    		if ( $(window).width() > 700) {
                return "url(#" + d['name'].replace(' ', '') + ")"
            } else if ( d['name'] === 'Mary Palmberg') {
                return "url(#" + d['name'].replace(' ', '') + ")"
            }
    	});

I appended two circles to the G elements. The first is the circle around a person. I gave it an id of “circle-outer” and set it’s radius (attribute “r”) to a variable called “circle_radius”, which is set at the top of the script.js file. Once again I did a check for Mary and gave her a bigger circle since she’s in the middle. I then set the color of the circle’s stroke based on data property “connection”.

Our network graph has five total connections, so there’s a possibility of five different colors. I set the connection number in the JSON file. This is done so every connection through Mary is color-coded the same and thus the reader can see they are part of the same story. Same color = same story. And Mary stays in the middle of it all.

The second circle is the image itself. It has an id of “circle-id”. The attribute fill is set to the image of the person. The urls for each image are set in index.html under the defs tag. Each image is a pattern with a “xlink:href” property set to the url of the image. More information on how this works can be found here.

After that, I append text to have their name appear the circle. And I used this library to create the tooltips. I won’t bore you with that code as well.

There’s a few more things going on in this chart but those are the basics. Or at least as I understand them.

As with learning any new technology, my advice is to pick a project that you want to be powered by D3 and go at it. I learned a lot more about D3 making my first chart than I have with walkthroughs, video tutorials, etc. You’ll never learn it until you get your hands dirty.

Written by csessig

April 1, 2015 at 4:42 pm

Posted in d3, Javascript

Quick tip: My approach to media queries

leave a comment »

Anybody who has designed responsive websites knows that styling them is a delicate balance. You want to get the most of out of both your desktop and mobile experiences, as well as everything in between. On top of that, you’re trying to hit a ton of moving targets: Mobile and tablet sizes are always changing in size so designing for EVERY platform out there is very difficult to do.

With CSS, you target particular screen sizes — and therefore particular devices — with media queries.  I’ve seen a lot of developers write media queries to target particular devices based on their screen size. For instance, you might write a media query of 768 pixels wide to target an iPad. Or write a query of 667 pixels wide for the new iPhone 6.

The problem is: What happens when a hot, new device comes along with new dimensions? And what you wanted to design for all mobile platforms is suddenly obsolete because it’s wider or taller than the last mobile phone you were designing for?

This is why I approach media queries like so: I write a media query for every 50 pixels and simply make sure EVERYTHING works on every screen size of any pixel width. I will then add styling to only those 50-pixel benchmarks.

For instance, if the font in the header of my webpage gets too large at 630 pixels wide, I will modify to make the font smaller at 650 pixels wide. If an image is too big at 425 pixels wide, I will make it smaller at 450 pixels wide.

I also include classes that hide and show elements at each 50-pixel width to make it easier to hide and show things on the page at given widths. So, for instance “hide-400” will be a “display: none” property at 400 pixels wide. The inverse is “show-400”.

So my stylesheet ends of looking something like:

/* Styles for mobile devices */
@media (max-width: 450px) {
	.hide-450 {
		display: none;
	}

	.show-450 {
		display: block;
	}

	.show-450-inline {
		display: inline;
	}

	.header-sub-header p  {
        font-size: 13px;
        line-height: 30px;
    }
}

/* Styles from mobile devices */
@media (max-width: 400px) {
	.header h3 {
		line-height: 10px;
		font-size: 14px;
	}

	/* Universal classes */
	.hide-400 {
		display: none;
	}

	.show-400 {
		display: block;
	}

	.show-400-inline {
		display: inline;
	}
}

/* Styles for mobile devices */
@media (max-width: 350px) {
	.hide-350 {
		display: none;
	}

	.show-350 {
		display: block;
	}

	.show-350-inline {
		display: inline;
	}
}

That way if I need to hide and show something, all I need to do is add the appropriate class to it instead of going into the stylesheet every time. Bootstrap does something similar with their responsive utility classes. My approach is just more granular.

Now there is still a larger question: At what pixel width do you turn on your “mobile view” of your website (if you have one)? I use 600 pixels wide as a benchmark because that’ll target most mobile devices and give you extra room to cover the next, new device that is sure to be wider than the last. Also, articles on The Gazette are 620px wide, so I figure anything less than that I can consider a “mobile device”.

But, of course, this is all up for friendly debate.

Written by csessig

November 20, 2014 at 10:03 am

Leaflet formula: Counting markers within a radius

with 5 comments

ssThe last project I worked on at The Courier was a comprehensive guide to mental health and disability services in NE Iowa. For the project, we first wanted to explain the MHDS system to our readers and how it works. It’s a relatively new system with a lot of moving parts and no good way to search for services. We really wanted to help residents and families in need of these services, so we designed everything — from the wording we used to the way items were laid out — to be focused on our readers.

After laying out the details of the new system, we wanted to allow our readers to search for services near them. A map seemed like an obvious choice; but we wanted to do more than just have them search for their address and plot a dot on the map.

(As a side note, we used Ben Welsh‘s jquery-geocodify plugin to geocode the addresses because it’s awesome.)

Instead, we wanted to show readers how many providers were close to them, as well as how to get ahold of the service provider and directions to the facility. For the former task, we used the following process:

First, the providers are plotted on the map. Then, we ask the reader to type in their address, which is plotted on the map using jquery-geocodify. They also have a second option for radius, which will be used to determine how many providers are within whatever radius they have given. They have four options: 5, 10, 25 and 50 miles.

Then, as the markers are getting plotted on the map, we run a few Javascript functions to calculate how many markers on the map are within the radius.

I’ve pulled out just this part of the mental health project and open-sourced it. You can view the full code over at my Github page.

The demo uses data on mental health providers, which is contained in this JSON fileand plots them on the map based on each provider’s latitude and longitude coordinates.

Here’s a quick run through the Javascript that makes all this run. It’s contained within the project’s script.js file. We’ll start from the bottom of the file and move our way up.

Note: This walkthrough assumes a decent understanding of Leaflet. I’m also using Leaflet-Awesome Markers and Font Awesome in this project.

First, we’ll add the base map tiles to our map. We’ll also  add the json_group to the map, which is a FeatureGroup we will create later in the file:

// Base map
var layer = new L.StamenTileLayer('toner-background');
var map = new L.Map('map', {
    center: new L.LatLng(42,-93.3),
    minZoom: 4,
    maxZoom: 10,
    zoom: 6,
    keyboard: false,
    boxZoom: false,
    doubleClickZoom: false,
    scrollWheelZoom: false,
    maxBounds: [[33.154799,-116.586914],[50.190089,-77.563477]]
});
// Add base layer to group
map.addLayer(layer);
// Add our markers in our JSON file on the map
map.addLayer(json_group);

Next, we’ll add the code for jquery-geocodify. For the onSelect function, we will grab the location of reader’s address and then call a geocodePlaceMarkersOnMap function, which we will create later:

// jQuery Geocodify
var maxY = 43.749935;
var minY = 40.217754;
var minX = -96.459961;
var maxX = -90.175781;

var search_marker;
var search_icon = L.AwesomeMarkers.icon({
    icon: 'icon-circle',
    color: 'green'
});

$('#geocoder').geocodify({
    onSelect: function (result) {
        // Extract the location from the geocoder result
        var location = result.geometry.location;

        // Call function and place markers, circle on map
        geocodePlaceMarkersOnMap(location);
    },
    initialText: 'Zip code, city, etc...',
    regionBias: 'US',
    // Lat, long information for Cedar Valley enter here
    viewportBias: new google.maps.LatLngBounds(
        new google.maps.LatLng(40.217754, -96.459961),
        new google.maps.LatLng(43.749935, -90.175781)
    ),
    width: 300,
    height: 26,
    fontSize: '14px',
    filterResults: function (results) {
        var filteredResults = [];
        $.each(results, function (i, val) {
            var location = val.geometry.location;
            if (location.lat() > minY && location.lat() < maxY) {
                if (location.lng() > minX && location.lng() < maxX) {
                    filteredResults.push(val);
                }
            }
        });
        return filteredResults;
    }
});

Next, we’ll loop through our providers.json file, which has a variable of json_data, and create a marker for each object in our array. We’ll then add it to our json_group FeatureGroup, which is defined at the top of our script file:

// This loops through the data in our JSON file
// And puts it on the map
_.each(json_data, function(num) {
    var dataLat = num['latitude'];
    var dataLong = num['longitude'];

    // Add to our marker
    var marker_location = new L.LatLng(dataLat, dataLong);

    // Options for our circle marker
    var layer_marker = L.circleMarker(marker_location, {
        radius: 7,
        fillColor: "#984ea3",
        color: "#FFFFFF",
        weight: 1,
        opacity: 1,
        fillOpacity: 0.8
    });

    // Add events to marker
    layer_marker.on({
        // What happens when mouse hovers markers
        mouseover: function(e) {
            var layer_marker = e.target;
            layer_marker.setStyle({
                radius: 8,
                fillColor: "#FFFFFF",
                color: "#000000",
                weight: 1,
                opacity: 1,
                fillOpacity: 1
            });
        },
        // What happens when mouse leaves the marker
        mouseout: function(e) {
            var layer_marker = e.target;
            layer_marker.setStyle({
                radius: 7,
                fillColor: "#984ea3",
                color: "#FFFFFF",
                weight: 1,
                opacity: 1,
                fillOpacity: 0.8
            });
        }
    // Close event add for markers
    });

    json_group.addLayer(layer_marker);
// Close for loop
}, this);

The changeCircleRadius function is called anytime the radius is changed on the page. It first calls the pointsInCircle function, which we will define later, and then sets the radius of the circle using Leaflet’s setRadius function for circles.

Leaflet uses meters, so we will need to convert miles to meters using the milesToMeters function, which will be defined later. The mile’s value is contained within the DIV with the id of radius-selected. We’ll grab the value, which is the radius in miles, convert it to meters and then set the radius of the circle using Leaflet.

// Change circle radius when changed on page
function changeCircleRadius(e) {
    // Determine which geocode box is filled
    // And fire click event

    // This will determine how many markers are within the circle
    pointsInCircle(circle, milesToMeters( $('#radius-selected').val() ) )

    // Set radius of circle only if we already have one on the map
    if (circle) {
        circle.setRadius( milesToMeters( $('#radius-selected').val() ) );
    }
}

$('select').change(function() {
    changeCircleRadius();
});

The geocodePlaceMarkersOnMap function is called every time the reader enters an address into our geocoder and presses enter. It passes with it the location of their address, which we get from jquery-geocodify. With this function, we will create both a circle and a marker on top of the given address.

First, we set the view of the map based on this location and remove the circle on the map if there already is one. Then we actually create a circle and set how large it is based on the radius set by the user.

We then remove the marker if it’s already on the map and create a new one. When we create a new marker, we’ll set it to draggable and set its dragend method to reset the view of the map every time the reader drags the marker somewhere on the. It then calls the pointsInCircle function, which we will define next. Finally, we’ll call the same pointsInCircle function and add the marker to the map.

// This places marker, circle on map
function geocodePlaceMarkersOnMap(location) {
    // Center the map on the result
    map.setView(new L.LatLng(location.lat(), location.lng()), 10);

    // Remove circle if one is already on map
    if(circle) {
        map.removeLayer(circle);
    }
    
    // Create circle around marker with our selected radius
    circle = L.circle([location.lat(), location.lng()], milesToMeters( $('#radius-selected').val() ), {
        color: 'red',
        fillColor: '#f03',
        fillOpacity: 0.1,
        clickable: false
    }).addTo(map);
    
    // Remove marker if one is already on map
    if (search_marker) {
        map.removeLayer(search_marker);
    }
        
    // Create marker
    search_marker = L.marker([location.lat(), location.lng()], {
        // Allow user to drag marker
        draggable: true,
        icon: search_icon
    });

    // Reset map view on marker drag
    search_marker.on('dragend', function(event) {
        map.setView( event.target.getLatLng() ); 
        circle.setLatLng( event.target.getLatLng() );

        // This will determine how many markers are within the circle
        pointsInCircle( circle, milesToMeters( $('#radius-selected').val() ) );

        // Redraw: Leaflet function
        circle.redraw();

        // Clear out address in geocoder
        $('#geocoder-input').val('');
    });

    // This will determine how many markers are within the circle
    // Called when points are initially loaded
    pointsInCircle( circle, milesToMeters( $('#radius-selected').val() ) );

    // Add marker to the map
    search_marker.addTo(map);
        
// Close geocodePlaceMarkersOnMap
}

The pointsInCircle function is the last function called when one of two things happen: the user enters an address or the user drags the marker on the map. Its purpose is to find out how many markers on the map are within the circle we created and then display that number in the legend.

Fortunately, Leaflet has a really handy distanceTo method, which determines how many meters are within two points. It uses the two points’ latitude and longitude coordinates to find this out.

With the pointsInCircle function, we first capture the lat, long of the circle we’re putting on the map. We then loop through our providers.json file, find the latitude and longitude coordinates of each object and determine how far it is from our circle using the distanceTo method.

We then decide if that amount is higher or lower than the radius set by the user. If it is lower, we add it to a counter. When we’re done, that counter will equal the number of markers within the selected radius. It will be put on the map under the “Results” header.

Lastly, we will determine how many results we have and use the correct wording on the page. If we have one result, we’ll use the word “provider” (singular) in the map’s legend. Otherwise, we’ll use “providers.”

// This figures out how many points are within out circle
function pointsInCircle(circle, meters_user_set ) {
    if (circle !== undefined) {
        // Only run if we have an address entered
        // Lat, long of circle
        circle_lat_long = circle.getLatLng();

        // Singular, plural information about our JSON file
        // Which is getting put on the map
        var title_singular = 'provider';
        var title_plural = 'providers';

        var selected_provider = $('#dropdown_select').val();
        var counter_points_in_circle = 0;

        // Loop through each point in JSON file
        json_group.eachLayer(function (layer) {

            // Lat, long of current point
            layer_lat_long = layer.getLatLng();

            // Distance from our circle marker
            // To current point in meters
            distance_from_layer_circle = layer_lat_long.distanceTo(circle_lat_long);

            // See if meters is within raduis
            // The user has selected
            if (distance_from_layer_circle <= meters_user_set) {
                counter_points_in_circle += 1;
            }
        });

        // If we have just one result, we'll change the wording
        // So it reflects the category's singular form
        // I.E. facility not facilities
        if (counter_points_in_circle === 1) {
            $('#json_one_title').html( title_singular );
        // If not one, set to plural form of word
        } else {
            $('#json_one_title').html( title_plural );
        }
        
        // Set number of results on main page
        $('#json_one_results').html( counter_points_in_circle );
    }
// Close pointsInCircle
};

The final piece is to add the variables we’re using to the top of the script file. We’ll also add the milesToMeters function, which converts miles to meters.

// We'll append our markers to this global variable
var json_group = new L.FeatureGroup();
// This is the circle on the map that will be determine how many markers are around
var circle;
// Marker in the middle of the circle
var search_marker;
// Marker icon
var search_icon = L.AwesomeMarkers.icon({
    icon: 'icon-circle',
    color: 'red'
});


// Convert miles to meters to set radius of circle
function milesToMeters(miles) {
    return miles * 1069.344;
};

I hope this walkthrough helps fellow Leaflet users! If you have any questions, leave them in the comments.

Happy coding.

Written by csessig

June 22, 2014 at 1:32 pm