Tag Archives: ABQ

Open Data Disclaimers and Terms of Service

7 Apr

I saw a tweet by @waldojaquith that commented on the State of NY Open Data Portals TOS. The TOS states that you cannot access the page

“…by using an automated device, script, bot, spider, crawler or scraper”

I can guess their intent is to prevent people from hitting the site with bots and overloading the server. But a device and script? This seems at best, too broad, at worst, ignorance as to how people will want to use the data. Furthermore, will I be prosecuted from doing so or will my IP address be blocked? Is this even enforceable? Can a government block a private citizen from a civic website?

This tweet made me curious about the city I live in – Albuquerque. I was dumbfounded by the content of their disclaimer/TOS. It read

“The City may require a user of this data to terminate any and all display, distribution or other use of any or all of the data provided at this website for any reason…”

Really? The City can call me and tell me to remove the bar chart of car thefts by month from my website because it is based on their data? Or, that I can no longer send the PublicArt.kmz file to someone?

I love Albuquerque Open Data. I find it quite useful and very good. So when the page says that

“The City makes no warranty, representation, or guaranty as to the content, accuracy, timeliness, or completeness of any of the data provided at this website.”

it makes me question the quality of the data. I get it, the data might be wrong, don’t hold it as gospel. Seems to be common sense to me. But this statement creates a distrust in the data. Is it authoritative? After reading the TOS, I would not take it to be.

If I were a gambling man, I would bet this is the work of the Legal Department. The same people that probably have email signatures saying if they send you confidential information by mistake you are in trouble for not deleting it. Yeah, I don’t think so Matlock. Saying it doesn’t make it true.

Government is made up of many departments. Those departments create a bunch of data. That data finds its way to an open data website probably run by the IT Department. The IT department just puts it out there and doesn’t really know anything about the data. They don’t make it so why would they stand by it? They point you to the department that made it. But that department probably didn’t care to release it in the first place and don’t want to be bothered exporting it or updating it. Or worse yet, fielding calls from citizens about it. But if someone is going to put out some data, there needs to be a level of trust or quality in the data.

Is it better to put out some data of quality by choice than to be legally obligated to provide it as the result of a FOIA request? I would imagine open data cuts down on a lot of those requests, saving a lot of time in money.

Once you put it out, please don’t think you have any control over what I do with it or who I send it to.

I do not know the answer so I am putting the question out there, and I did so on Twitter as well:

Has a City ever been sued for the quality of their open data?

 

Advertisements

Historic Bus Location Data

27 Mar

Last night I was trying to think of interesting uses for a Rasberry Pi. One thing I came up with was a data logger. But what to log? Then I thought about a previous post on the Albuquerque Realtime Bus Data. Hmmm. What if I wanted to show the bus locations using a time slider. What if I want to see if they ever deviated from their routes, or if they deviated from their schedules? I can’t really do any analysis without the historic data, and the City does not give that out currently. So I think I find a use – logging Albuquerque Bus Data.

I don’t have a Rasberry Pi, yet, so I wrote a python script on my desktop to test the logger.

I am not going to post the code because I don’t know the impact on the Albuquerque server. I will give a brief explanation. The City has KML files for each bus route. Each route has multiple buses. I grabbed a single route – 766, and parsed the results. I initially sent the results to a csv – as you can see in the data below this post. Writing to CSV is not too helpful when the data gets large (I am not going to say BIG). Once I knew it worked, I sent the data to a MongoDB that was spatially indexed. In the database, I can now:

Get the total records

                         collection.count()

Get all the Records

                        for x in collection.find():
                                      print x

Get a specific bus number

                       for x in collection.find({‘number’:’6903′}):
                                           print x

Or find near a lat,lng

                       for x in collection.find({“loc”:{“$near”:[35.10341,-106.56711]}}).limit(3):
                                           repr(x)

With a database, multiple people can query it and perform operations on it. Lastly, if the data gets larger, Mongo can be split (sharding) across multiple machines to hold it all.

My MongoDB records look like:

{u’loc’: [35.08156, -106.6287], u’nextstop’: u’Central @ Cornell scheduled at 3:45 PM’, u’number’: u’6903′, u’time’: u’3:46:52 PM’,
u’_id’: ObjectId(‘5515cfd814cd2829e4c1b718′), u’speed’: u’20.5 MPH’}

Here is the results of my original run. I ran the script for 7 minutes and got the following results for Route 766.

Bus

Hard to see, but displays bus locations along route 766 over a 7 minute period.

 

6409,0.0 MPH,1:34:02 PM,Central @ San Mateo (Rapid) scheduled at 1:31 PM,-106.58642,35.07778
6904,1.9 MPH,1:34:03 PM,Central @ Edith scheduled at 1:31 PM,-106.64776,35.08401
6411,0.0 MPH,1:33:56 PM,CUTC Bay B scheduled at 1:39 PM,-106.72266,35.07726
6407,21.7 MPH,1:34:00 PM,Central @ 1st (across from A.T.C.) scheduled at 1:34 PM,-106.64317,35.08359
6410,35.4 MPH,1:33:53 PM,Central @ San Mateo (Rapid) scheduled at 1:36 PM,-106.58247,35.07749
6403,0.0 MPH,1:34:02 PM,Indian School @ Louisiana scheduled at 1:40 PM,-106.57089,35.10305
6903,29.8 MPH,1:33:56 PM,Central @ Atrisco scheduled at 1:36 PM,-106.69049,35.08443
6409,0.0 MPH,1:35:14 PM,Louisiana @ Central (Rapid) scheduled at 1:36 PM,-106.5852,35.07764
6904,1.9 MPH,1:35:14 PM,Central @ Edith scheduled at 1:31 PM,-106.6478,35.08413
6411,0.0 MPH,1:35:07 PM,Next stop is CUTC Bay B scheduled at 1:44 PM,-106.72525,35.07886
6407,0.0 MPH,1:35:11 PM,Copper @ 2nd scheduled at 1:34 PM,-106.64784,35.08417
6410,19.3 MPH,1:35:17 PM,Central @ Carlisle (Rapid) scheduled at 1:40 PM,-106.58734,35.07802
6403,0.0 MPH,1:35:14 PM,Indian School @ Louisiana scheduled at 1:40 PM,-106.57087,35.10306
6903,14.9 MPH,1:35:08 PM,Central @ Tingley (Rapid) scheduled at 1:38 PM,-106.68485,35.08576
6409,23.6 MPH,1:36:38 PM,Louisiana @ Central (Rapid) scheduled at 1:36 PM,-106.58084,35.07723
6904,18.0 MPH,1:35:53 PM,Central @ Edith scheduled at 1:31 PM,-106.647,35.08373
6411,0.0 MPH,1:36:31 PM,Next stop is CUTC Bay B scheduled at 1:44 PM,-106.72525,35.07885
6407,0.6 MPH,1:36:35 PM,Copper @ 5th scheduled at 1:35 PM,-106.64944,35.08541
6410,0.0 MPH,1:36:40 PM,Central @ Carlisle (Rapid) scheduled at 1:40 PM,-106.59512,35.07883
6403,0.0 MPH,1:36:42 PM,Indian School @ Louisiana scheduled at 1:40 PM,-106.57086,35.10306
6903,31.7 MPH,1:36:33 PM,Central @ Rio Grande (Rapid) scheduled at 1:40 PM,-106.67824,35.09252
6409,37.9 MPH,1:37:49 PM,Louisiana @ Central (Rapid) scheduled at 1:36 PM,-106.57203,35.07627
6904,0.6 MPH,1:37:55 PM,Central @ Cedar (Rapid) scheduled at 1:33 PM,-106.63771,35.08276
6411,0.0 MPH,1:37:55 PM,Central @ Coors scheduled at 1:47 PM,-106.72526,35.07885
6407,1.9 MPH,1:37:47 PM,Copper @ 5th scheduled at 1:35 PM,-106.6496,35.08487
6410,0.0 MPH,1:37:52 PM,Central @ Yale (UNM) scheduled at 1:44 PM,-106.60369,35.07979
6403,0.0 MPH,1:37:57 PM,Indian School @ Louisiana scheduled at 1:40 PM,-106.57087,35.10306
6903,0.0 MPH,1:37:56 PM,Central @ Rio Grande (Rapid) scheduled at 1:40 PM,-106.67159,35.09515
6409,0.0 MPH,1:39:14 PM,Louisiana @ Central (Rapid) scheduled at 1:36 PM,-106.56872,35.07595
6904,18.0 MPH,1:38:19 PM,Central @ Cedar (Rapid) scheduled at 1:33 PM,-106.63713,35.08265
6411,0.0 MPH,1:39:19 PM,Central @ Coors scheduled at 1:47 PM,-106.72527,35.07884
6407,3.1 MPH,1:39:10 PM,Central @ Rio Grande (Rapid) scheduled at 1:40 PM,-106.65295,35.086
6410,31.1 MPH,1:39:16 PM,Central @ Yale (UNM) scheduled at 1:44 PM,-106.61167,35.08084
6403,5.6 MPH,1:39:23 PM,Indian School @ Louisiana scheduled at 1:40 PM,-106.5707,35.10368
6903,23.0 MPH,1:39:20 PM,Gold @ 5th (Rapid) scheduled at 1:44 PM,-106.67011,35.09438
6409,26.1 MPH,1:40:37 PM,Louisiana @ Lomas scheduled at 1:38 PM,-106.56848,35.07723
6904,24.9 MPH,1:40:10 PM,Central @ Cedar (Rapid) scheduled at 1:33 PM,-106.63585,35.08255
6411,0.0 MPH,1:40:31 PM,Central @ Coors scheduled at 1:47 PM,-106.72526,35.07884
6407,23.0 MPH,1:40:36 PM,Central @ Rio Grande (Rapid) scheduled at 1:40 PM,-106.65675,35.0863
6410,34.2 MPH,1:40:40 PM,Central @ Yale (UNM) scheduled at 1:44 PM,-106.61567,35.0811
6403,0.0 MPH,1:40:42 PM,Indian School @ Louisiana scheduled at 1:40 PM,-106.56875,35.10221
6903,23.6 MPH,1:40:32 PM,Gold @ 5th (Rapid) scheduled at 1:44 PM,-106.66327,35.08892
6409,0.6 MPH,1:41:49 PM,Indian School @ Uptown Loop Road scheduled at 1:42 PM,-106.56849,35.08691
6904,24.9 MPH,1:41:55 PM,Central @ Cedar (Rapid) scheduled at 1:33 PM,-106.63585,35.08255
6411,0.0 MPH,1:41:55 PM,Central @ Coors scheduled at 1:47 PM,-106.72526,35.07884
6407,0.0 MPH,1:41:58 PM,Central @ Rio Grande (Rapid) scheduled at 1:40 PM,-106.65822,35.08648
6410,28.6 MPH,1:41:52 PM,Central @ Yale (UNM) scheduled at 1:44 PM,-106.6216,35.08113
6403,0.0 MPH,1:42:01 PM,Louisiana @ Lomas scheduled at 1:45 PM,-106.56779,35.10184
6903,23.0 MPH,1:41:55 PM,Gold @ 5th (Rapid) scheduled at 1:44 PM,-106.65819,35.08629
6409,41.0 MPH,1:43:13 PM,Indian School @ Uptown Loop Road scheduled at 1:42 PM,-106.56863,35.09282
6904,24.9 MPH,1:42:15 PM,Central @ Cedar (Rapid) scheduled at 1:33 PM,-106.6217,35.08093
6411,0.0 MPH,1:43:19 PM,Central @ Coors scheduled at 1:47 PM,-106.72528,35.07883
6407,9.9 MPH,1:43:10 PM,Central @ Rio Grande (Rapid) scheduled at 1:40 PM,-106.661,35.08784
6410,34.2 MPH,1:43:16 PM,Central @ Mulberry (Rapid) scheduled at 1:47 PM,-106.62524,35.08125
6403,16.8 MPH,1:43:22 PM,Louisiana @ Lomas scheduled at 1:45 PM,-106.56635,35.10156
6903,19.3 MPH,1:43:19 PM,Gold @ 5th (Rapid) scheduled at 1:44 PM,-106.6549,35.084

the Unofficial Guide to Albuquerque Open Data

10 Feb

Over the weekend I became ill and so had some time on my hands. This is what I cam up with: The Unofficial Guide to Albuqerque Open Data.

TheUnofficialGuideToAlbuquerqueOpenData

CSV in JavaScript Using Albuquerque Open Data

28 Jan

In my last two posts, I showed you how to read KML (XML) over the web using AJAX. In this example, we will do the same with a tab delimited file from Albuquerque Open Data. The application will look like the image below.

The finished app. Choose a committee and see their financial information.

The finished app. Choose a committee and see their financial information.

 

Get the Data

On the Albuquerque Open Data site, we will use Campaign Finalized. You will see two options: XML or CSV. We will use the CSV – which is really tab delimited. Grab the data using AJAX.

var url = “http://data.cabq.gov/government/campaignfinalized/CampaignReportFinalizedVersionCABQ-en-us.csv”;

http=new XMLHttpRequest();
http.open(“GET”, 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 results=http.responseText;

Parse the Data

The data is tab delimited in rows that have a newline character at the end. This file also has new line characters in fields – but they are wrapped in quotes. Handling this will be difficult on our own, so let’s use a library for it. Download PapaParse 4. This will simplify the process significantly. We can grab the data with:

p=Papa.parse(results,{
delimiter:”\t”,
newline:”\n” });

Now I have an array with each line as p.data[x] and each item as p.data[x][0-8]. For this example, we will use the committee name(p.data[x][0]) and the amount(p.data[x][8]).

Let’s populate the dropdown box. First, we need to create it in the HTML and add a default value.

<select id=’D1′></select>
var select = document.getElementById(“D1”);
var d= document.createElement(“option”);
d.value = -1;
d.id=-1;
d.textContent = “—Choose—“;
select.appendChild(d);

We can now iterate through our data and create an array of individual groups.

for(r=1;r<p.data.length;r++){
if(groups.indexOf(p.data[r][0].trim())>-1){
//do nothing
}
else{groups.push(p.data[r][0].trim());}
}

And then populate the combo box

for(g=0;g<groups.length-1;g++){
var opt = document.createElement(“option”);
opt.value = g;
opt.id=g;
opt.textContent = groups[g];
select.appendChild(opt);
}

Displaying the Data

We need to add an Event Listener on the combo box and execute a function.

document.getElementById(‘D1’).addEventListener(‘change’, money, false);

The function will grab the value of the item selected then get its text content (name). With this information we can pull all the data from the CSV for the financials of the committee selected. Lastly, update the DOM to display the results.

function money(m){
m=document.getElementById(this.value).textContent;
console.log(m);
for(y=0;y<p.data.length;y++){
if(p.data[y][0].trim()==m){
if(parseFloat(p.data[y][8])>0){
raised+=parseFloat(p.data[y][8]);
}
else{spent+=parseFloat(p.data[y][8]);}
}
else{}
}
document.getElementById(“output”).innerHTML='<h3>Raised:</h3>’+raised.toLocaleString()+'<h3>Spent:</h3>’+spent.toLocaleString()+'<h3>Difference</h3>’+(raised+spent).toLocaleString();

}

Without Papa Parse 4, this would have been a much more complicated task. There is no CSV standard, so dealing with individuals CSV files requires a lot of modification. In this example, having new line characters in a field wreaked havoc on my first attempts to parse it out manually. Also, I originally set the PapaParse option header=true, but the application complained. Instead of figuring out why it didn’t like it, I just started my loops at an index of 1 – dropping the headers at index 0.

Albuquerque Live Bus Tracker

26 Jan

The City of Albuquerque has put out a lot of good data and they have done so in several different formats. There is one data set that I love and despise at the same time – the Real Time Bus data. I love it because I think it is an excellent example of open data. It is extremely useful as it is updated every minute. I despise it because it is in KML. I will give the City the benefit of the doubt and assume they have done this because their software for tracking the buses uses KML by default so it was an easy choice – just spit out what we already have.

As a developer, KML is worthless to me. I want a stream of data in JSON or CSV. I have struggled with what to do for some time now – ignore the bus data or figure out a way to build something with it? In 2013, I wrote a post using the real time bus data that required me to use some tools that I don’t usually pull out – OpenLayers and PHP. OpenLayers allows for the importing of a KML, however, it only did so via a local file. I had to find a way to get the file from the City Website to my server. PHP came to the rescue and the app downloaded the data then rendered it in an OpenLayers map. A fairly large amount of overhead for what should be a simple task – read bus data.

Two years later, almost to the day, I have revisited the bus data. This time I think I have defeated the dreaded KML. In this post, I will show you how to conquer KML files using JavaScript.

Route 766 Live in Leaflet.js

Route 766 Live in Leaflet.js

The Solution

The solution that came to mind was to use AJAX to read the KML file off the City webpage. The first thing I did was to write an AJAX request using the city URL and inspected the response.

XMLHttpRequest cannot load http://domain1 Origin http://domain2 is not allowed by Access-Control-Allow-Origin.

Already off to a great start. I was about ready to wait another two years before looking at the City Bus Data again. I looked in to Chrome and found that you can launch it without web security. I created a shortcut for it on my Desktop:

C:\Program Files(x86)\Google\Chrome\Application\chrome.exe” –args –disable-web-security

When I ran the code again, I got back a bunch of XML. Bingo! that is exactly what I wanted. Now I had to figure out how to parse it. Enter the JavaScript DOMParser().

var xmlDoc = new DOMParser().parseFromString(http.responseText,’text/xml’);

That is all it took to get the KML in to a parsable format. To add the buses to the map I grabbed the coordinates using

var coords = xmlDoc.getElementsByTagName(“coordinates”);

Grabbing the length of the coords object, I looped through mapping the coordinates.

for(i=0;i<Object.keys(coords).length-1;i++){
var xy=coords[i].childNodes[0].nodeValue.split(“,”);
markers.addLayer(L.marker([xy[1],xy[0]]));}

The coordinates are (Long,Lat) in KML and Leaflet.js expects (Lat,Long) so the indexes are switched. Also, the markers are added to a FeatureGroup so that I can easily clear them all and redraw them every 62 seconds.

The application is wrapped in a function getBus() and executed using

 window.setInterval(getBus,62000);

Now every 62 seconds the map will refresh and the buses will have moved.

More Information

There is more data in the KML: Vehicle Number, Speed, Msg Time and Next Stop. The KML file has unfortunately put these values in a table. You cannot grab them by Tag Name easily – they all have the same tag. You can see them all using:

var vehicleNumber=xmlDoc.getElementsByTagName(“td”);
for(i=0;i<Object.keys(vehicleNumber).length-1;i++)
{console.log(vehicleNumber[i].childNodes[0].nodeValue);}

This is where I really wish the data were cleaner – or in JSON. A little data wrangling is good for us, so that will be my next move.

Geocoding with Albuquerque Open Data

16 Jan

Albuquerque provides a large assortment of open data on their website. One service that I would find useful is a geocoding service. Digging around, I was able to find one that was not listed on the open data page. In my previous post, I showed you how to fake a geocoding service using the address points data set, but it had a serious flaw – it required an exact match. In this post, I will show you the correct way to geocode using the proper service.

Geocoding an Address

The first step is to create a widget that will allow a user to enter the address we want to find. The CSS and HTML below will create the widget for us.

#AddressSearchBox{
background-color: rgba(255, 255, 255, 0.5);
border: 1px solid #000;
-moz-border-radius: 15px;
border-radius: 15px;
height:40px;
width:350px;
padding-top:10px;
padding-left:5px;
position: absolute;
left: 40%;
top: 3px;
z-index: 100000;
}

<div style=”text-align: center”><div style=”text-align: left; margin: 0 auto; width: 50%;”>
<div id=”AddressSearchBox”> <center><b>Address:</b><input type=’text’ id=’addr’ name=’to’><button onclick=’GeocodeAddress()’>Search</button><center></div>
</div></div>

Now that we can accept user input, we need to write the GeocodeAddress function. The function is an AJAX call to the geocoding service. It will return a series of candidates with scores, however, for this example I will just grab the first result.  The code below sends an address, asks for the results in wkid:4326 and as JSON. We grab the first result, add a marker to the map and zoom.

function GeocodeAddress(){
var params = “Street=”+document.getElementById(“addr”).value+”&f=json&outSR=4326″;
var url = “http://coagisweb.cabq.gov/arcgis/rest/services/locators/CABQ_Composite/GeocodeServer/findAddressCandidates&#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 thexy= JSON.parse(http.responseText);
x=thexy.candidates[0].location.x;
y=thexy.candidates[0].location.y;
console.log(x+”,”+y);
var yousearchedfor = L.marker([thexy.candidates[0].location.y,thexy.candidates[0].location.x]).addTo(map).bindPopup(‘<h3>’+thexy.candidates[0].address+'</h3>’).openPopup();
map.setView([thexy.candidates[0].location.y,thexy.candidates[0].location.x],18);
}}
http.send(params);}

That is all there is to it. The image below shows the results for 123 Sentral. Notice the street name is actually central. Because we are using a real geocoding service in this example we do not need exact matches.

Searching for Sentral

Now you can enter an address and find the result on a map. But with ESRI gecoding services you can also reverse geocode.

Reverse Geocoding

Reverse geocoding takes a point and returns the address. We will allow the user to click on the map and will return a marker with the address of the click. Our map will return coordinates in wkid:4326 (lat,long) but the service we are using is using wkid:3857. Now we have a problem – the points we will pass do not match the expected input to the service and we will receive no results back. I addressed projections in a previous post and  how we could pass an inSR and an outSR to the service. If you look again at the code above, we asked the geocoding service to outSR=4326, so can’t we just do that again in this example? No. The reverse geocoding service will not take inSR as a parameter. Here is the solution:

when an ESRI service does not allow an inSR parameter – for example adding a feature or reverse geocoding – you can assign the inSR value in the geometry you are sending.

What does this mean? It means that when the user clicks we need to send a point object to the service that looks like:

 location = {x:-106 , y:35, “spatialReference” : {“wkid” : 4326}}

Now we can code the reverse geocoding portion of our application. using Leaflet.js we can write:

map.on(“click”,function(e){ CODE HERE});

Inside the function, we need to make an AJAX call to the service passing the geometry as a parameter – with our spatial reference – and then get the results, draw a marker, and zoom. The code is shown below.

map.on(“click”,function(e){
var marker=L.marker(e.latlng);
coords=marker.toGeoJSON();
var urlgeocode=”http://coagisweb.cabq.gov/arcgis/rest/services/locators/CABQ_Composite/GeocodeServer/reverseGeocode&#8221;;
a1=”location={x:”;
a2=String(coords.geometry.coordinates[0]);
a3=”,y:”;
a4=String(coords.geometry.coordinates[1]);
a5=’,”spatialReference” : {“wkid” : 4326}}&distance=1000&f=json&outSR=4326′;
var geocodeparams =a1.concat(a2,a3,a4,a5);
var 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) {
results=JSON.parse(http.responseText);
L.marker([results.location.y,results.location.x]).addTo(map)
.bindPopup(‘<h3>’+results.address.Street+'</h3>’)
.openPopup();
map.setView([results.location.y,results.location.x],18);
}}
http.send(geocodeparams);
});

Now when a user clicks on the map, they will get the image below.

Reverse Geocoding

Reverse Geocoding

Fake Geocoding with CABQ Open Data

15 Jan

Geocoding addresses is hard. For starters, there tend to be very few free services. The services that are free also tend to limit the number of queries you can make in a day or at a time. So what is a community developer to do? In this article, I will show you how to use Albuquerque Open Data to create a fake geocoding service.

Open Data

Browsing the Albuquerque Open Data site, I noticed they have published address points. Clicking it, we can choose a REST service for this data. If you look at the available fields in the data you will see one called GeoAddress. I used the ESRI form to grab some sample data to see what was in the field. From the looks of it, it was a concatenation of streetnumber, streetname, streetdesignation and streetquadrant. We will use this to build our geocoder.

The Map and Widget

Before I start with the details, here is an image of the final application with the address for Chipotle entered.

The Final Geocoding Application.

The Final Geocoding Application.

 

To create the above application, we need to style the widget that allows the user to type an address and press enter or click the submit button. Below is the CSS required and then the HTML for the widget.

#AddressSearchBox{
background-color: rgba(255, 255, 255, 0.5);
border: 1px solid #000;
-moz-border-radius: 15px;
border-radius: 15px;
height:40px;
width:350px;
padding-top:10px;
padding-left:5px;
position: absolute;
left: 40%;
top: 3px;
z-index: 100000;
}

<div style=”text-align: center”>
<div style=”text-align: left; margin: 0 auto; width: 50%;”>
<div id=”AddressSearchBox”> <center><b>Address:</b><input type=’text’ id=’addr’ name=’to’ onkeypress=’handleKeyPress(event)‘><button onclick=’GeocodeAddress()‘>Search</button><center></div>
</div>
</div>

The HTML contains functions for when the user presses enter and if they click the button. Those functions will be created in our Leaflet.js map.

The handleKeyPress function will just check if the user pressed the enter key and then call the GeocodeAddress function. We put this in because many users hit enter instead of clicking the UI buttons.

function handleKeyPress(e){
var key=e.keyCode || e.which;
if (key==13){
GeocodeAddress();
}}

The application functionality all happens in the GeocodeAddress function. Because this is not a real geocoding service, we are using a MapServer service to query a field of addresses and then grabbing the result. Our query will look for the feature which has a GeoAddress equal to what the user submitted. The user entered address must be in all CAPS. For this to work, the match must be exact. As I mentioned, this is not a real geocoding service, but a way to fake one. The code below queries the service, gets the result, adds a marker to the map and zooms in.

function GeocodeAddress(){

var params = “where=GeoAddress='”+document.getElementById(“addr”).value+”‘&f=json&outSR=4326&outFields=*”;
var url = “http://coagisweb.cabq.gov/arcgis/rest/services/public/cadastral/MapServer/6/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) {
alert(http.responseText);
var result= JSON.parse(http.responseText);
L.marker([result.features[0].geometry.y,result.features[0].geometry.x]).addTo(map);
map.setView([result.features[0].geometry.y,result.features[0].geometry.x],18);

}}
http.send(params);
}

It is by no means perfect, but it is a quick and dirty way to find an address if a real geocoding service has not been provided. In my next post, I will show you how to geocode address when a service has been provided.