Tag Archives: Turf.js

Density Map with Turf.js

18 Feb

In a previous post, I showed how to interpolate using a TIN and Turf.js. Turf.js has other interpolation tools and in the post, I will show you how to construct a dot density map using the hex feature.

The Application

For this application, we will use Albuquerque Air Pollution Sources data and map which parts of the city have the highest density of pollution sources.  The application is available on JS Bin and will look like the image below.

The I-25 Corridor contains the highest density of pollution sources.

The I-25 Corridor contains the highest density of pollution sources.

How to Create the Application

To create this application, we will need to reuse the inside function as we did in the post on TINs. Instead of searching the TIN object, we will search the HEX object. the first step is to construct the hex grid over Albuquerque. to do so, we need to specify the bounding box and size of the hexagons.

var bbox = [-106.754150390625, 35.02887183968363,-106.47674560546875, 35.18615531474442];
var size = .01;
var hexgrid = turf.hex(bbox, size);
for(var x=0;x<Object.keys(hexgrid.features).length;x++){
hexgrid.features[x].properties.count=0;}

In the above code, we iterated through the grid and added a property to each hexagon called count. This is where we will store the point count for each hexagon.

Next, we can setup the function for coloring the GeoJSON hexagon grid when we are done. I stole this straight from the leaflet.js website. I only changed the values in getColor().

function getColor(d) {
return d > 15 ? ‘#800026’ :
d > 13 ? ‘#BD0026’ :
d > 11 ? ‘#E31A1C’ :
d > 6 ? ‘#FC4E2A’ :
d > 4 ? ‘#FD8D3C’ :
d > 2 ? ‘#FEB24C’ :
d > 0 ? ‘#FED976’ :
‘#FFFFFF’;}

function style(feature) {
return {
fillColor: getColor(feature.properties.count),
weight: 2,
opacity: 1,
color: ‘white’,
dashArray: ‘3’,
fillOpacity: 0.7
};}

We will add the Albuquerque Pollution Sources to the map using our standard AJAX code. Each point will be converted to GeoJSON and pushed to an array.

var params=”f=json&outSR=4326&outFields=*&where=1=1″;
var url = “http://coagisweb.cabq.gov/arcgis/rest/services/public/environmentalissues/MapServer/1/query&#8221;;
console.log(params);
http=new XMLHttpRequest();
http.open(“POST”, url, true);
http.setRequestHeader(“Content-type”, “application/x-www-form-urlencoded”);
http.onreadystatechange = function() {//Call a function when the state changes.
if(http.readyState == 4 && http.status == 200) {
var result= JSON.parse(http.responseText);
for(x=0;x<Object.keys(result.features).length;x++){

var t = L.marker([result.features[x].geometry.y,result.features[x].geometry.x]);//.addTo(map);
p.push(t.toGeoJSON());
}
countem();
}}
http.send(params);

When we have all the points, we call countem() to create the dot density map.

function countem(){

for(var y=0;y<Object.keys(hexgrid.features).length-1;y++){

for(var c=0;c<p.length-1;c++){
var poly=turf.polygon(hexgrid.features[y].geometry.coordinates);

if(turf.inside(p[c],poly)){hexgrid.features[y].properties.count+=1;console.log(hexgrid.features[y].properties.count);}
} //end 2nd for
}//end 1st for

L.geoJson(hexgrid,{style: style}).addTo(map);}

The above code creates a loop for each hexagon in the grid. The second loop iterates through each point and tests if it is in the hexagon. If it is, we add 1 to the count. When we have tested every hexagon, we add the grid to the map as GeoJSON and color it.

To Do

This would be a great application to use with the Albuquerque Crime data. I tested it and there are several incidents missing coordinates, so I stuck to something simpler for our proof of concept.

Turf.js: Adding Markers to a Line at Specific Intervals

16 Feb

If it is not obvious, I love Turf.js. As I start to use it in my production work, I am always looking at the possibilities it opens up for my applications. So far, I have only been using simple buffers to help create my query geometry to send to ArcServer. And in that simple regard, it has cut down my workload significantly. I am always looking for new ways I can use Turf.js to solve problems and I think I found one. In this post, I will show you how to add markers to a line segment at specified intervals.

 

Result: Markers added every mile along I-40 Segment

Result: Markers added every mile along I-40 Segment

My Work

I do a lot of web mapping for road work. I currently have an application that allows a user to draw a line along a road to specify where they would like to work. Knowing the length of the work area and seeing it broken in to intervals could be very useful for me later in the process.

Turf for Distance

The first thing I need to do is convert a line from Leaflet,js to use in Turf.js. Leaflet to Turf is simple. Take a Leaflet geometry and call .toGeoJSON() on it. now you have an object that can be used with Turf. To get the length of a line using turf, you just use .lineDistance(line, units).

var l = L.polyline([[35.05586,-106.82007],[35.06204, -106.7926],
[35.10699, -106.70334],[35.10474, -106.67313],
[35.10671, -106.65287],[35.10559, -106.62987],
[35.10755, -106.6151],[35.10362, -106.58627],
[35.06232, -106.48121]]);

var jsonLine=l.toGeoJSON();
var length = turf.lineDistance(jsonLine, ‘miles’);
var dist=Math.floor(length);

The code above creates a line along I-40. It is then converted to GeoJSON and the length is calculated in miles using Turf. I use Math.floor to round down by removing the decimal places.

Add Markers at Mile Intervals

You can add a marker at an interval using turf.along(line,distance,units). This is great, but it only adds a single point. We need to setup a loop that drops markers every mile. We grabbed the distance for this purpose.

The first thing to do is create he result object and add the line to it.

var result = {
“type”: “FeatureCollection”,
“features”: [jsonLine]
};

Next, we will setup a loop that adds a marker for every mile along the line. The loop adds the result to the result features array.

for(step=1;step<dist+1;step++){

result.features.push(turf.along(jsonLine, step,’miles’));

}

Now you can add the result to the map.

L.geoJson(result).addTo(map);

Turf works effortlessly with Leaflet. Leaflet to Turf using toGeoJSON() and Turf to Leaflet using L.geoJson().

Improving Albuquerque Crime Incidents Query

10 Feb

In my last post, I showed how to query the Albuquerque Crime Incident. This post will improve on it.

In my original post, you had to do the following:

  1. Catch a point on click
  2. Draw a buffer
  3. Get the envelope of the buffer
  4. Query

There was nothing wrong with this, but it does bring in incidents beyond a half mile – the envelope is a square representation of the buffer. But we used the envelope parameter in the REST endpoint so we had no choice.

Well, let’s use the polygon parameter and only grab points in our buffer!

In the original code, after the line:

buffered = turf.buffer(b,.5,”miles”);

add the line below.

g='{“rings”:’+JSON.stringify(buffered.features[0].geometry.coordinates)+’}’;

You can remove the envelope line from the original code.

Now, just change your parameters to reflect the geometryType=Polygon and the new geometry=g.

var params=”f=json&outSR=4326&outFields=*&geometryType=esriGeometryPolygon&spatialRel=esriSpatialRelIntersects&inSR=4326&geometry=”+g;

You will now have incidents only in your buffer.

Capture

Query Albuquerque Open Data with Turf.js

9 Feb

I have posted on the Albuquerque Crime Incident data showing how to use Python and for converting the Date field. I have also demostrated how to use Turf.js for analysis. In this post, I will show you how to combine the two to limit your query of the Albuquerque Crime Incident Data.

The Idea

The goal of this post is to allow a user to click on a map and get back all the crime data within a half mile of the click. The end result will look like the image below.

Capture

 

Create the Geometry

To make this work, we need to catch where the user clicks on the map and convert it to GeoJSON so we can use it in our Turf.js operations without having to convert it to a Turf.point.

map.on(“click”,function(e){

a=L.marker(e.latlng);
b=a.toGeoJSON();

});

Now that we have a point, we need to create the buffer and then grab the envelope.

var buffered = turf.buffer(b,.5,”miles”);
var result = turf.featurecollection(buffered.features.concat(b));
enveloped = turf.envelope(result);

Query the Service

With the envelope, we can query the REST endpoint and pass the lower left and upper right coordinates to get the Crime Incidents in the bounding box. I have done several posts on making an AJAX request to ArcServer and will not go in to detail here. Just note that we will have two parameters that I do not use often.

geometryType=esriGeometryEnvelope

spatialRel=esriSpatialRelIntersects

And a geometry parameter that will take the corner points of the envelope. The code below shows how to make the query.

var params=”f=json&outSR=4326&outFields=*&geometryType=esriGeometryEnvelope&spatialRel=esriSpatialRelIntersects&inSR=4326&geometry=”+enveloped.geometry.coordinates[0][0][0]+”,”+enveloped.geometry.coordinates[0][0][1]+”,”+enveloped.geometry.coordinates[0][2][0]+”,”+enveloped.geometry.coordinates[0][2][1];
var url = “http://coagisweb.cabq.gov/arcgis/rest/services/public/APD_Incidents/MapServer/0/query&#8221;;
console.log(params);
http=new XMLHttpRequest();
http.open(“POST”, url, true);
http.setRequestHeader(“Content-type”, “application/x-www-form-urlencoded”);
http.onreadystatechange = function() {//Call a function when the state changes.
if(http.readyState == 4 && http.status == 200) {
console.log(http.responseText);
var result= JSON.parse(http.responseText);
for(x=0;x<Object.keys(result.features).length;x++){
xy=result.features[x].geometry;
console.log(result.features[x].geometry);
adate=new Date(result.features[x].attributes.date);
L.marker([result.features[x].geometry.y,result.features[x].geometry.x]).bindPopup(“<h3>”+result.features[x].attributes.CVINC_TYPE+”</h3><p>”+adate.toString()).addTo(map);}
}}

http.send(params);

I added a popup showing the crime type and then formatted the epoch data to a human readable string. Now if you click on the map, you will get back a series of incidents. To improve on this, you could pass a specific date range but the data itself is for the last 180 days (rolling, meaning tomorrow one day will be dropped off).

Live Scenario Planning in Leaflet.js with Turf.js

22 Jan

When I started scenario planning, I had to run my data through a model to get some output. To try a new scenario, I had to rerun the model with new data and then compare the outputs. The process was slow. With the tools currently available, we can do better. I wrote a simpl live scenario planning application using Leaflet.js using Turf.js as the “model”. I put model in quotes because for the example we will simply sum a field in each marker.

The application showing the values of two fields in our data.

The application showing the values of two fields in our data.

The above image is my application. It takes points in Albuquerque and sums two different fields and displays the results in a bar chart below. The application starts as follows:

1. Create a Leaflet.js Map.

var map = L.map(‘map’, {
center: [35.10418, -106.62987],
zoom:15
});

L.tileLayer(‘http://{s}.tile.osm.org/{z}/{x}/{y}.png’).addTo(map);

2. Add my base scenario points and a polygon for the area of interest.

var p1 = turf.point(-106,35, {“marker-color”: “#6BC65F”,”title”: 100, “description”: “Not in Polygon”, “someOtherProperty”:3 });
var p2 = turf.point(-106.62987,35.10418, {“marker-color”: “#6BC65F”,”title”: 4, “description”: “In Polygon”, “someOtherProperty”:6 });
var p3 = turf.point(-106.64429,35.14125, {“marker-color”: “#6BC65F”,”title”: 1, “description”: “Also in Polygon”, “someOtherProperty”:5 });

var polygon = turf.polygon([ [
[-106.73355,35.21197],[-106.73355,35.04911],[ -106.51932,35.04911],[-106.49872,35.19177]
]], {
“fill”: “#6BC65F”,
“stroke”: “#6BC65F”,
“stroke-width”: 5,
“title”:”Polygon”,
“description”:”A sample polygon”
});

var p = turf.featurecollection([polygon]);
var t = turf.featurecollection([p1,p2,p3]);

3. Add the Turf.js points to the map as GeoJSON in Leaflet.js

function onEachFeature(feature, layer) {

layer.bindPopup(“<h3>Add this number: “+feature.properties.title+”</h3>”+feature.properties.description);
}

L.geoJson(t, {
onEachFeature: onEachFeature
}).addTo(map);

4. Using Turf.js, get the sum of the base scenario for two fields.

var sum = turf.sum(p,t,”title”,”titleOutput”);
var sum = turf.sum(p,t,”someOtherProperty”,”otherOutput”);

5. Setup both charts using the base scenario. The example only shows the code for the first chart.

var data={
labels: [“Title Value”],
datasets: [
{label: “Title Value”,
fillColor: “rgba(151,187,205,0.5)”,
strokeColor: “rgba(151,187,205,0.8)”,
highlightFill: “rgba(151,187,205,0.75)”,
highlightStroke: “rgba(151,187,205,1)”,
data: [sum.features[0].properties.titleOutput]}]};

var ctx = document.getElementById(“myChart”).getContext(“2d”);
var myBarChart = new Chart(ctx).Bar(data);

6. Hold the value we will change in a variable so we can change it later.

var x=sum.features[0].properties.titleOutput;

7. Setup an onclick method to modify the scenario. The method will add a marker and provide a textbox to set a value.

map.on(“click”,function(e){
L.marker(e.latlng).addTo(map).bindPopup(‘Add Title Value:<input type=”text” id=”v”><br><button onclick=”add()”>Add</button>’).openPopup();
});

8. Lastly, when the user clicks the button on the new marker, we update the scenario and redraw the chart.

function add(){
myBarChart.destroy();
newValue=Number(document.getElementById(“v”).value);
x+=newValue;
var d = {
labels: [“Title Value”],
datasets: [{
label: “Title Value”,
fillColor: “rgba(151,187,205,0.5)”,
strokeColor: “rgba(151,187,205,0.8)”,
highlightFill: “rgba(151,187,205,0.75)”,
highlightStroke: “rgba(151,187,205,1)”,
data: [x]}]};
myBarChart= new Chart(ctx).Bar(d);
}

If you add a point in the study are, you can add a new Title Value and the chart will update as in the image below.

Title Value is now 10.

Title Value is now 10.

This is a simplistic example but it shows the possibilities for live updating a dashboard as the data changes. This method allows for quick iterations in your models.

Geoprocessing on the Client Side with Turf.js

7 Jan

Geoprocessing has been primarily a desktop activity. Using ArcServer, you can publish geoprocessing services. This takes geoprocessing off the desktop but requires communication between a client and server. I don’t mind you downloading my data and processing it on your desktop, but I really don’t like the idea of you using my CPU and memory running some harebrained geoprocessing task off my server. Given the advances in web technology, especially JavaScript, can’t we come up with something better? Can’t we let the client handle the work?

We can with Turf.js.

Using Turf.js you can perform a large number of commonly used geoprocessing functions client side.  In this post, I will show you how to buffer, point in polygon and sum a field for points in a polygon.

Buffer a Point

1. Using Leaflet.js, create a map and add a tile layer:

  • var map = L.map(‘map’, { center: [35.10418, -106.62987],zoom: 9});
  • L.tileLayer(‘http://{s}.tile.osm.org/{z}/{x}/{y}.png’).addTo(map);

2. Create two points using turf.point and Long,Lat.

  • pointOne = turf.point(-106.32568,35.11542);
  • pointTwo = turf.point(-106.33,35.22)

3. The points are now in GeoJSON. to add them to Leaflet.js us L.geoJson.

  • L.geoJson(pointOne).addTo(map);
  • L.geoJson(pointTwo).addTo(map);

4. Buffer a point and assign the result to a variable. Then add the buffer to the map. the buffer function takes a feature (point, line, polygon, feature collection), a distance, and the units (miles, kilometers or degrees).

  • var b = turf.buffer(pointOne,2,”miles”);
  • L.geoJson(b).addTo(map);

Now you should have a map that looks like the one below.

Two points with one buffered.

Two points with one buffered.

Point in Polygon

Now that we have two points and a buffer, let’s perform a point in polygon.

1. Create a polygon from the buffer.

  • var polygon = turf.polygon(b.features[0].geometry.coordinates, {
    “fill”: “#6BC65F”,
    “stroke”: “#6BC65F”,
    “stroke-width”: 5,
    “title”:”Polygon”,
    “description”:”A sample polygon”
    });

2. To PIP, use turf.inside() passing the point and polygon as parameters. the result will be true or false.

  • alert(“pointTwo is inside? “+turf.inside(pointTwo, polygon));

Now you will be alerted that the point is not inside the polygon.

Point not in Polygon

Point not in Polygon

In the previous example, the features did not have any attributes. In the next geoprocessing example, we will calculate a value from points in a polygon.

Using Statistics: Sum

1. This example starts with a Leaflet.js map.

  • var map = L.map(‘map’, {center: [35.10418, -106.62987],zoom: 9});
  • L.tileLayer(‘http://{s}.tile.osm.org/{z}/{x}/{y}.png’).addTo(map);

2. Add a function for iterating through features so we can add a popup.

  • function onEachFeature(feature, layer) {
    layer.bindPopup(“<h3>Add this number: “+feature.properties.title+”</h3>”+feature.properties.description);}

3. Now add your points, but this time we will add properties to the points.

  • var p1 = turf.point(-106,35, {“marker-color”: “#6BC65F”,”title”: 100, “description”: “Not in Polygon”, “someOtherProperty”:”I am another property” });
  • var p2 = turf.point(-106.62987,35.10418, {“marker-color”: “#6BC65F”,”title”: 4, “description”: “In Polygon”, “someOtherProperty”:”I am another property” });
  • var p3 = turf.point(-106.64429,35.14125, {“marker-color”: “#6BC65F”,”title”: 1, “description”: “Also in Polygon”, “someOtherProperty”:”I am another property” });

4. To sum a filed, you will need at least one polygon – you can use multiple as well.

  • var polygon = turf.polygon([ [
    [-106.73355,35.21197],[-106.73355,35.04911],[ -106.51932,35.04911],[-106.49872,35.19177]
    ]], {
    “fill”: “#6BC65F”,
    “stroke”: “#6BC65F”,
    “stroke-width”: 5,
    “title”:”Polygon”,
    “description”:”A sample polygon”
    });

5. Create feature collections for the polygon(s) and points. Add them to the map using an option to call your onEachFeature function.

  • var p = turf.featurecollection([polygon]);
    var t = turf.featurecollection([p1,p2,p3]);
  • L.geoJson(p).addTo(map);
  • L.geoJson(t, {
    onEachFeature: onEachFeature
    }).addTo(map);

6. Now pass the sum function the polygon, points, the field to sum and the name of the output field.

  • var sum = turf.sum(p,t,”title”,”output”);

7. when you click the map you will get the result. Notice the marker with the value of 100 is ignored since it is outside the polygon.

  • map.on(“click”,function(){alert(sum.features[0].properties.output);});

Lastly, when you can click a marker and the popup information is displayed.

sum

sum2

 

Running geoprocessing tasks without having to pass data back and forth from client to server is the way to go. It also means your browser can now work as a simple desktop GIS application.