Tag Archives: Leaflet.js

Python Wrapper for Leaflet

20 Mar

I recently stumbled upon Folium – a python wrapper for leaflet. I was excited and it seemed to work well. I slowly ran in to problems and the pages loaded slow. I probably did something wrong on my end, but decided to write a simple wrapper on my own.

My wrapper is a python function for different Leaflet features such as map and marker. When you call each function, it writes a string to a file to generate the HTML. Below is my python code (pyLeaflet.py).

class l(object):

def __init__(self,path):
self.path=path
self.f=open(self.path,”w+”)
self.f.write(‘<html><head><title>Map From Python</title><link rel=”stylesheet” href=”http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.css&#8221; /></head><body><script src=”http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.js”></script><div style=”height:900px; width:900px” id=”map”></div><script>\n’)

def map(self, lat,long,zoom):
self.lat=lat
self.long=long
self.zoom=zoom
self.f.write(“var map = L.map(‘map’, {center: [“+str(self.lat)+”,”+str(self.long)+”], zoom:”+str(self.zoom)+”});\n”)
self.f.write(“L.tileLayer(‘http://{s}.tile.osm.org/{z}/{x}/{y}.png’).addTo(map);\n”)
def marker(self,lat,long, popup=””):
self.x=lat
self.y=long
self.popup=popup
self.f.write(‘L.marker([‘+str(self.x) +’,’+str(self.y)+’]).bindPopup(“‘+str(self.popup)+'”).addTo(map);\n’)
def onclick():

def makeMap(self):
self.f.write(‘</script></body></html>’)
self.f.close()

To use the code, follow the example below.

>>> from pyLeaflet import l
>>> L=l(“Paul.html”)
>>> L.map(35,-106,8)
>>> L.marker(35,-106)
>>> L.marker(34,-106,”Hello from Python”)
>>> L.makeMap()

The output will be an HTML file called paul.html that displays a map with a maker.

Advertisements

Aerial Imagery of Albuquerque and Surrounding Counties

10 Mar

When you need an aerial image, do you go to Google and take a screenshot? If you live in Albuquerque or the surrounding area, you no longer need to do that. The City of Albuquerque publishes aerial imagery of Albuquerque and the surrounding areas as open data. In this post, I will show you how to retrieve an image.

The Service

The Imagery Service provides a list of all the available years.

The historic data is a great addition.

The historic data is a great addition.

For this tutorial, click on Imagery/Aerials2014. At the bottom of the page, choose Export Image. You will see the full extent of the imagery and a form below.

Capture2

To get an image, you need to enter the lower left and upper right corner coordinates of a bounding box in NAD83(HARN) / New Mexico Central (ftUS). I have no clue what this would be. You can change it to WGS84 (4326) as we have done in previous examples, but still, how would I know the coordinates without clicking on a map and checking the points? The form is pretty much useless to me. Our application will provide a better GUI – map based – to the form.

The Application

Our application will be a Leaflet.js map that allows the user to zoom to their desired location. The map window will be used to select what is exported from the imagery service.

The Logic

The application is a basic Leaflet.js map. We will add an onClick function that grabs the image.

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

getImage();

});

The getImage() function will make an AJAX call to the service. It will pass the coordinates of the map window as parameters to get back an image in a popup. You can then right click and save image as…

function getImage(){

b=map.getBounds();
var bounds=b._southWest.lng+”,”+b._southWest.lat+”,”+b._northEast.lng+”,”+b._northEast.lat;

var url = “http://coagisweb.cabq.gov/arcgis/rest/services/Imagery/Aerials2014/ImageServer/exportImage&#8221;;
var params=”f=json&bbox=”+bounds+“&bboxSR=4326&size=1028,1028&imageSR=4326”;

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

result= JSON.parse(http.responseText);
window.open(result.href);

}}

http.send(params);

}

The above code starts by assigning the map bounds to a variable. We then create a bounds string in the format the service wants – lower left corner to top right corner. We pass the bounds string in to the parameters string and make the request.

The result contains an href property that is the URL to the image. We open a popup to the URL so the user can save the image.

Map on the left. Click. Image popup on the right.

Map on the left. Click. Image popup on the right.

Now the user can zoom to the desired extent and click the map to get back an image. Much easier than finding the coordinates and entering them in to the form.

 

Reverse Geocoding a Line

3 Mar

In Geocoding with ABQ Open Data, I showed you how to geocode an address and how to reverse geocode a point when the user clicks on the map. Geocoding is an operation performed on points or addresses, so what do we do with lines? In this post I will show you how I am geocoding a line.

The Need

I have an application that allows a user to select a location for where they would like to perform some construction work and need a permit. The application started by allowing the user to specify a point or address. As I met with the users of the application, it turned out that having line features was crucial. Work may be performed along a stretch of road and the approvers of the permit wanted to be able to find at least the starting address when looking at the data in a tabular format – mostly for things like how many permits are on Central Ave.

The Solution

Alert box showing the start and end addresses of a line

Alert box showing the start and end addresses of a line

The solution uses the same reverse geocoding process as my previous post, but parses a line feature in to points. I am using the leaflet.draw plugin to allow the user to draw the line on a map. The draw plugin has a draw:created event that searches to see the type of object drawn. My code runs in the if (type == ‘polyline’) block.  The code below converts the layer drawn to GeoJSON and then the GeoJSON coordinates to a string split on the comma. Using the string at index 0 and 1, I have the starting point of the line. Using the index of length-1 and length-2 I can grab the last point. I use length because the user can draw a line segment with an unlimited number of points so I will not know where the line ends and do not want to assume a two point line. when I have the points, I call reverseGeocode().

var tojson=layer.toGeoJSON();
var x=String(tojson.geometry.coordinates).split(“,”);

//start point
var lt=parseFloat(x[1]);
var ln=parseFloat(x[0]);
reverseGeocodeLineFrom(ln,lt);

//endpoint
var ltend=parseFloat(x[x.length-1]);
var lnend=parseFloat(x[x.length-2]);
reverseGeocodeLineTo(lnend,ltend);

The reverseGeocode function just makes an AJAX call to the ESRI REST API service for my geocoder passing the point and parsing the response.

function reverseGeocodeLineFrom(a,b){

var urlgeocode=”http://servername/ArcGIS/rest/services/wgs84locator/GeocodeServer/reverseGeocode&#8221;;

var geocodeparams = “location=”+a+”,”+b+”&distance=1000&f=json”;
var http;
http=new XMLHttpRequest();
http.open(“POST”, urlgeocode, 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) {

addressAsJSON=JSON.parse(http.responseText);
alert(addressAsJSON.address.Street);
}
}
http.send(geocodeparams);
}

Now I have a street address for the start and end of my line.

Use GIS to Fill a Form

27 Feb

We use maps to answer questions and the information taken from a map may need to find its way somewhere else. In this post, I will show you how to take a map and populate form data with the properties in a GIS feature class just by clicking a location on the map.

The Application

1

The application will consist of a form with a button to launch a map. When the map opens, the user will click on a parcel. The map will make a call to an Albuquerque Open Data GIS service and retrieve the information for the parcel. The map will send the data to the form.

The Code

The form is a simple Bootstrap form.

<form class=”form-horizontal” role=”form”>
<div class=”form-group”>
<label class=”control-label col-sm-2″ for=”email”>Street Number:</label>
<div class=”col-sm-10″>
<input type=”text” class=”form-control” id=”snumber” placeholder=”Enter Street Number”>
</div>
</div>
<div class=”form-group”>
<label class=”control-label col-sm-2″ for=”pwd”>Street Name:</label>
<div class=”col-sm-10″>
<input type=”text” class=”form-control” id=”sname” placeholder=”Enter Street Name”>
</div>
</div>
<div class=”form-group”>
<label class=”control-label col-sm-2″ for=”email”>Street Designation:</label>
<div class=”col-sm-10″>
<input type=”text” class=”form-control” id=”sdes” placeholder=”Enter Street Designation”>
</div>
</div>
<div class=”form-group”>
<label class=”control-label col-sm-2″ for=”pwd”>Street Quadrant:</label>
<div class=”col-sm-10″>
<input type=”text” class=”form-control” id=”squad” placeholder=”Enter Street Quadrant”>
</div>
</div>
<div class=”form-group”>
<div class=”col-sm-offset-2 col-sm-10″>
<button type=”submit” onclick=”openMap()” class=”btn btn-default”>Open Map</button>
</div>
</div>
</form>

The button calls a JavaScript function which opens the map in a new window.

function openMap(){window.open(“fillformmap.html”,”Map”,”width=600,height=600″);}

The goal is to fill the form with data from the map. The map that opens is a standard Leaflet.js map. I use an on click function to create a buffer – just to make sure we hit a parcel – and send the buffer to the service query and ask for the parcel information back (for more information on the buffer with Turf.js see my post on Albuquerque Incidents).

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

a=L.marker(e.latlng);
b=a.toGeoJSON();
buffered = turf.buffer(b,.005,”miles”);
L.geoJson(buffered).addTo(map);
var result = turf.featurecollection(buffered.features.concat(b));

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

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

var url = “http://coagisweb.cabq.gov/arcgis/rest/services/public/fullviewer/MapServer/9/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);
……..CODE GOES HERE….
}}

http.send(params);
});

Nothing special about the code above, it is been used in several previous posts. To send the data to the form, we need to use the code below.

opener.document.getElementById(“sname”).value=result.features[0].attributes.STREETNAME;
opener.document.getElementById(“snumber”).value=result.features[0].attributes.STREETNUMBER;
opener.document.getElementById(“squad”).value=result.features[0].attributes.STREETQUADRANT;
opener.document.getElementById(“sdes”).value=result.features[0].attributes.STREETDESIGNATION;
window.close();

The above code grabs the text boxes by ID for the document that opened the map. It sets the value of those text boxes to the value returned from the map query. Now we have a completed form.

2 Sometimes clicking a map is better than filling out information by hand. I only queried a single GIS feature, but do not think that is the limit. We could easily query a number of layers to populate a form. Just add another AJAX call and pass the results.

Am I Near Public Art? How about Crime?

26 Feb

When you are new to a city, it is hard to walk around and carry a map of all the sites. Wouldn’t it be nice if your phone could alert you when you were near something of interest? In this post, I will show you how to create an alert system.

The Application

The application will build off of our earlier post using Albuquerque Crime Data. In that application, the user clicked the map and was presented with all crimes in the last 180 days within a half mile of the click.

This application will use the same logic but replace the click with the users location.

Building the Application

We will start with a Leaflet.js Mobile map. Place the following HTML in the head of your document.

<meta name=”viewport” content=”width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no” />

<style>
body {
padding: 0;
margin: 0;
}
html, body, #map {
height: 100%;
}
</style>

This will make the map look nice on mobile – and desktop as well. Then create the map.

var map = L.map(‘map’);

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

map.locate({setView: true, maxZoom: 16,enableHighAccuracy:true,watch:true});

With mobile maps, we do not pass the setView to the map, we add that parameter to the locate() function. Now we need two functions: one for found and one for not found.

map.on(‘locationfound’, gotcha);

function gotcha(e){ALL OUR CODE GOES HERE}

map.on(‘locationerror’, onLocationError);

function onLocationError(e) {
alert(e.message);}

When our location is found, we want to setup the buffer and prep for calling the Albuquerque Data for Public Art or Incidents.

a=L.marker(e.latlng);
b=a.toGeoJSON();
L.circle(e.latlng, 20).addTo(map);
buffered = turf.buffer(b,.05,”miles”);
var result = turf.featurecollection(buffered.features.concat(b));
g='{“rings”:’+JSON.stringify(buffered.features[0].geometry.coordinates)+’}’;

We create a marker, convert it to GeoJSON to use with Turf.js, then buffer and get the rings to use in the query.

Now we can make our standard AJAX call to an ESRI REST Endpoint. I have commented out the URL for Public Art and the marker. You can uncomment them and comment out the incident url and marker to switch the application.

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

//var url = “http://coagisweb.cabq.gov/arcgis/rest/services/public/PublicArt/MapServer/0/query&#8221;;
var url = “http://coagisweb.cabq.gov/arcgis/rest/services/public/APD_Incidents/MapServer/0/query&#8221;;
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);
…….CODE GOES HERE……..

}}
http.send(params);

Now we get back all the incidents, or art, within 300 feet (.05 miles) of our location. We need to take the results and add them to the map and notify the user they are near something of interest.

for(x=0;x<Object.keys(result.features).length;x++){
xy=result.features[x].geometry;
pattern.push(100);
pattern.push(100);
L.marker([result.features[x].geometry.y,result.features[x].geometry.x]).bindPopup(“<h3>”+result.features[x].attributes.CVINC_TYPE+”</h3>”).addTo(map);}
//L.marker([result.features[x].geometry.y,result.features[x].geometry.x]).bindPopup(“<h3>”+result.features[x].attributes.TITLE+”</h3>”).addTo(map);}

navigator.vibrate(pattern);
pattern=[];

As we move, the service is queried again, constantly watching us and letting us know when we are near something of interest. The vibration pattern is 100ms for each feature we are near and pause for 100ms between each feature. If we are near a lot of features -say crime incidents – our phone will vibrate many times. Possibly signaling a dangerous part of town. If we are using public art, we know we are near a particularly artistic part of the city and maybe we should explore more.

To use the same map for multiple features, you could switch up the vibration patterns, say short fast vibrations for art and longer for crime. Maybe vibrate once for crime but the duration is a function of the number of incidents – 10 incidents results in a 10 second vibration.

Just an idea on how to use open data to alert new comers to the features of the city that they may not be aware of.

Load ESRI Data in Map Without Plugin

12 Feb

I am sure at some point I have shown how to load ESRI REST API data in to a Leaflet.js map. I usually use a plugin called Leaflet Vector Layers which works great, but sometimes you may want to do it for yourself. In this example, I will show you how to parse points from an ESRI REST API Endpoint and map them with no additional leaflet.js plugins.

The map with filming location points

The map with filming location points

As we have done with all the REST API examples, you will make an AJAX call to the service. We will use a where clause of 1=1, an outSR of 4326, and out fields = *. We will also specify f=json so that we can easily parse it.

var params = “f=json&where=1=1&outSR=4326&outFields=*”;
var url = “http://coagisweb.cabq.gov/arcgis/rest/services/public/FilmLocations/MapServer/0/query&#8221;;

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) {
Code goes here
}}}http.send(params);

Now all we need to do is convert the response to JSON, parse out the coordinates and throw in a popup with some additional attributes. Put the code below in place of the “code goes here” line.

var result= JSON.parse(http.responseText);
console.log(Object.keys(result.features).length)
for(x=0;x<Object.keys(result.features).length;x++){
L.marker([result.features[x].geometry.y,result.features[x].geometry.x]).addTo(map).bindPopup(“<h3>”+result.features[x].attributes.Title+”</h3><h3>”+result.features[x].attributes.Type+”</h3><h3><a href='”+result.features[x].attributes.IMDbLink+”‘>IMDB</a></h3><h3>”+result.features[x].attributes.Address+”</h3><h3>”+result.features[x].attributes.Site+”</h3><h3>”+result.features[x].attributes.ShootDate+”</h3><h3>”+result.features[x].attributes.OriginalDetails+”</h3>”);

The ESRI REST API will hand back the features and geometry. Once you have them, parsing them for the coordinates is simple. For a polygon or polyline, it becomes slightly more difficult.

Adding Polygons to the map

Adding Polygons to the map

To parse a polygon, we will use the Municipal Limits data. The AJAX call is the same as for points, but we will create a geoJSON object for each boundary passing the rings as the coordinates.

for(x=0;x<Object.keys(result.features).length;x++){
var pts=result.features[x].geometry.rings;
var b = [{
“type”: “Polygon”,
“coordinates”:pts}];

var bStyle = {
“color”: “red”,
“opacity”:1,
“weight”: 10
};

var project = L.geoJson(b, { style: bStyle}).addTo(map);
}

Now you know how to parse points and polygons from an ESRI REST API Endpoint with no additional libraries or plugins.

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