Tag Archives: ESRI

Excel Geocoder

17 Apr

ESRI makes maps for Office. I thought this could be interesting and went to work on trying to insert a map in to a spreadsheet. I did not have much luck. Instead, I decided to throw together a quick macro that would geocode addresses in a spreadsheet and give back coordinates. I do not think VB Script can parse JSON, so the result is ugly, but you get the idea.

Start with a spreadsheet of addresses

sheet

 

Then create a Macro – I copied the code for reading the URL from Ryan Farley.

Sub Macro1()
Range(“A2”).Select
i = 2

Do Until IsEmpty(ActiveCell)

URL = “http://coagisweb.cabq.gov/arcgis/rest/services/locators/CABQ_NetCurr/GeocodeServer/findAddressCandidates?f=json&outSR=4326&street=” & ActiveCell.Value
Dim objHttp
Set objHttp = CreateObject(“Msxml2.ServerXMLHTTP”)
objHttp.Open “GET”, URL, False
objHttp.Send
Cells(i, 2).Value = objHttp.ResponseText
i = i + 1
ActiveCell.Offset(1, 0).Select
Set objHttp = Nothing
Loop

End Sub

The code starts at cell A2 and reads addresses until it reaches an empty cell. It takes the value and sends it to the ESRI REST endpoint for the City of Albuquerque Geocoding Service. It sets the cell next to it with the results. They should really be parsed, but I am too lazy and was just curious if it could be done. It can. The result is below.

results

I am still thinking of how to embed a web page in the sheet.

Advertisements

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

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.

Load GeoJSON in ArcMap

2 Feb

It came up on Twitter that you cannot edit GeoJSON in ArcMap. That gave me the idea to try and write a plugin that allows just that. This post will layout a procedure for editing GeoJSON in ArcMap. The final code solution needs work, but it is a good start.

Reading GeoJSON in ArcMap

I grabbed a text file from my Github and saved it to my desktop. This is not the best example, but it was good enough to test the process. I started by reading the file in to ArcMap. The code reads in the file and converts it to JSON. It then iterates through the features, grabbing the coordinates and putting them in to an array.  Lastly, it writes it to a default geodatabase.

import arcpy
from arcpy import env
import json
d=open(r”C:\Users\paul\Desktop\art.geojson”)
data=json.load(d)
points=[]
i=0
for x in data[“features”]:
points.append(arcpy.PointGeometry(arcpy.Point(data[“features”][i][1],data[“features”][i][0])))
i=i+1
arcpy.CopyFeatures_management(points,”testlayer13″)

You will have a map like the image below.

GeoJSON pointsl oaded via Python in ArcMap

GeoJSON pointsl oaded via Python in ArcMap

Writing GeoJSON in ArcMap

You can now use the editing toolbar to draw and edit features as you would any other data source in ArcMap. In the image below, I have deleted most of the features.

Features remaining after edit.

Features remaining after edit.

 

When you have finished editing, you can save out the data as GeoJSON by reading the feature class, grabbing the field that holds the geometry and then writing it out to a text file.

arcpy.env.workspace=r”c:\users\paul\documents\arcgis\default.gdb”
desc=arcpy.Describe(r”c:\users\paul\documents\arcgis\default.gdb\testlayer13″)
shapefieldname=desc.ShapeFieldName
rows = arcpy.SearchCursor(“testlayer13″)
out=open(r”c:\users\paul\desktop\DONE.txt”,”w+”)
out.write(‘{“type”: “FeatureCollection”,”features”: [\n’)
for row in rows:
feat=row.getValue(shapefieldname)
pnt=feat.getPart()
out.write(‘[‘+str(pnt.X)+’,’+str(pnt.Y)+’],’)
out.write(‘]}’)
out.close()

The output is a textfile with GeoJSON.

The GeoJSON output

The GeoJSON output

 

Not pretty, but a start for how I would go about it. Would need to figure out how to do this for polygons and polylines.

Parcel Values in Bernalillo County

6 Jan

In the last post, you learned how to use the ESRI REST API to access data. I am going to show one more example. this example will grab 2500 parcel records and return the total property value for all the parcels.

The example is almost identical to the previous post. You will create an array to hold the values of each parcel. Then we will use a reduce function to sum them. The full code is below.

Results of REST query

Results of REST query

<html>
<head>
<title>PUBLIC ART</title>
</head>
<body>
<div id=”title”>Loading…</div>
<div id=”total”></div>
<div id=”tax”></div>
<script>
var query;
var holdtotal=[0];

var params = “where=1=1&outfields=TOTVALUE&f=json”;
var url = “http://coagisweb.cabq.gov/arcgis/rest/services/public/BernCoParcels/MapServer/0/query&#8221;;
http=new XMLHttpRequest();
http.open(“POST”, url, true);
http.setRequestHeader(“Content-type”, “application/x-www-form-urlencoded”);
http.onreadystatechange = function() {
if(http.readyState == 4 && http.status == 200) {
theGeom= JSON.parse(http.responseText);
var key, count = 0;
for(key in theGeom.features) {
if(theGeom.features.hasOwnProperty(key)) {
count++;
}}
document.getElementById(“title”).innerHTML=”<h3>I grabbed “+count+” parcels in Bernalillo County”;

for(i=0;i<count;i++){

holdtotal.push(theGeom.features[i].attributes.TOTVALUE);
}

var grandtotal=holdtotal.reduce(function(t,n){return t+n},0);

document.getElementById(“total”).innerHTML=”The total value of the parcels is $”+grandtotal.toLocaleString();

}}

http.send(params);
</script>
</body>
</html>

 

The above code should be straight forward with the exception of the variable grandtotal. The reduce function allows you to iterate through an array and add the previous value to the next returning a single value. the variable t is the total starting at 0 and n is the next value. when it is added, the value of t increases and adds itself to the next item. Then you just write the value to HTML.

With this technique you can create forms that would allow users to query data that you already publish in GIS. You can change your where clause to only grab residential properties, properties owned by a specific person or by lot size. Try some variations like where=acreage>600.

Database Queries Using GIS: The ESRI REST API Without Geography

6 Jan

When people think of GIS they think of maps. But GIS is about data and if we think about it as data it is not special. In this post, I will show you how to use your GIS data as a database and nothing more. I am going to build a website that displays a table of all the public art in Albuquerque with the artist, title and a link to an image of the piece.

First, go to the Albuquerque Public Data website and select Public Art. You will see a directory listing. Choose the last option: PublicArtREST. You will then see a bunch of the ArcServer information for the service. This URL could be used to load the data in to a map for display, but we are only interested in the tabular data at this time. On the bottom of the page you will see the supported operations. We are going to use the query operation. Click the query link and you will see a form. In the where box, type 1=1 and then click the button query (GET). a list of features will show up under the form. See the geographic data available as (x.y) coordinates? In the outfields box, type a *. Then set the return geometry option to False. Now press Query (GET) again. Now you have all the data from the database (there are x,y fields only because they are an actual column in the table. This is not the geometry from ESRI. You can tell because the projection changed – it is now in wkid:4326 and the ESRI version is in wkid:3857).

Now you know where the data lives and how to get it without the geographic portion. The next step is to grab it using AJAX. The key line in the AJAX query will be the parameters:

var params = “where=1=1&outfields=*&f=json”;

You used the first two parameters in the form. The 1=1 returns all the records(ArcServer limits the number of results so a large dataset may only return between 1000-2500 unless it is set for more). The outfield=* returns all the fields for each feature. The last parameter “f=JSON” is returning the JSON data and not the HTML underneath the form. This makes it easy to parse in JavaScript.

The complete code will create two DIV elements: one for the count of public art pieces; and one for the table of results. It will make an AJAX post request, count the records and write out the number, then create a table of the results by sticking each line in to an array then dumping it at the end. The result is below with the complete code after the image.

Table of Public Art with Link to Image

Table of Public Art with Link to Image

<html>
<head>
<title>PUBLIC ART</title>
</head>
<body>
<div id=”count”></div>
<div id=”data”></div>
<script>
var lines=[“<table border=’1′><tr><td>TITLE</td><td>ARTIST</td><td>IMAGE URL</td></tr>”];
var params = “where=1=1&outfields=*&f=json”;
var url = “http://coagisweb.cabq.gov/arcgis/rest/services/public/PublicArt/MapServer/0/query&#8221;;
http=new XMLHttpRequest();
http.open(“POST”, url, true);
http.setRequestHeader(“Content-type”, “application/x-www-form-urlencoded”);
http.onreadystatechange = function() {
if(http.readyState == 4 && http.status == 200) {
theGeom= JSON.parse(http.responseText);
var key, count = 0;
for(key in theGeom.features) {
if(theGeom.features.hasOwnProperty(key)) {
count++;
}}
document.getElementById(“count”).innerHTML=”<h3>There are “+count+” pieces of Public Art in Albuquerque</h3>”;

for(i=0;i<count;i++){

lines.push(“<tr><td>”+theGeom.features[i].attributes.TITLE+”</td><td>”+theGeom.features[i].attributes.ARTIST+”</td><td><a href='”+theGeom.features[i].attributes.IMAGE_URL+”‘>Image</a></td></tr>”);}

lines.push(“</table>”);

document.getElementById(“data”).innerHTML=lines.join(” “);

}}

http.send(params);
</script>
</body>
</html>

Now you know how to use a GIS service to retrieve data without a map. You can refine your query to only pieces by a certain artist, or create a table that lists artists and the number of pieces they have in the collection.

Happy Hacking.

ESRI JavaScript API and DOJO – I Hate You.

8 Nov

I am by no means a JavaScript expert and herein may be my problem, but I will rant anyway – if I am wrong, feel free to explain why or just troll me (I will approve your comment).

I hate the ESRI JavaScript API and I have finally figured out why – I do not like Dojo. I may be the old man yelling “get off my lawn!” but I like simplicity. I am not the only one. So does Vladimir Agafonkin – check out his talk at FOSS4G “How Simplicity Will Save GIS.” Vladimir is the creator of Leaflet.js. I love Leaflet. It is simple, but it can do complicated things. There are several plugins listed on the Leaflet website and you can always write your own.

The ESRI JavaScript API does not feel simple (their Leaflet.js plugin is. That’s right. They have a plugin for Leaflet too, so why Dojo?). Maybe it is just my lack of knowledge in Dojo and I am going through the 7 Stages of Learning Dojo. I refuse to learn Dojo. Not because there is anything wrong with Dojo, but because I think ESRI chose it for all the wrong reasons.

I think ESRI always goes down the wrong road and for the wrong reasons. When ESRI decided to get a “modern” web platform, what did they do? They released Silverlight and Flex APIs. Maybe you like them, I do not. Just like Dojo, it is not the technology I have an issue with but rather my issue is with why I think ESRI chose them.

Why Did ESRI Choose Silverlight, Flex and Dojo?

They wanted it to look pretty and have a bunch of widgets already built.

Therein lies the reason for my hatred of all three of those ESRI APIs. They did it for looks – bells and whistles – first and foremost. You don’t have to build everyhting, just provide the framework for users to do so. Leaflet has a way to create user controls: var MyControl = L.Control.extend …

It always seems like ESRI is in a hurry to put something out. I am almost surprised they didn’t just use GeoEXT.js. I mean, you can move a frame around the screen!

I will continue to use ESRI applications on a daily basis. I will also use Leaflet and the ESRI Plugin – I actually use Leaflet Vector Layers plugin more. I will not use the JavaScript API and I will not learn Dojo (at least anytime soon).

I will end the post on a positive note – I have read that ArcObjects is dead and in ArcGIS Pro, the .NET coding is much simpler. I found ArcGIS Desktop Explorer to have an easier API than ArcMap so if it is anything like that, I will be a happy camper.

Here is to ESRI abandoning Python, JavaScript and .NET and making us all program in Go.

 

ESRI Geocoding Widget Using the REST API and JavaScript

20 Oct
Geocoder Widget with the result dislpayed.

Geocoder Widget with the result dislpayed.

I need to geocode in my web apps and I decided to try writing my own geocoder using my ArcServer geocoding service and the ESRI REST API. I have made two version: one that only requires including the css and js files and one that requires a line of code to initialize. The second one allows me to switch the geocoder I want to use. The geocoder requres a Leaflet.js map – only to add the marker. If you want to use a different mapping library, just change the code in the JavaScript file after the line if(http.readyState == 4 && http.status == 200) {. The http.responseText will contain a JSON string of the results. Do whatever you want with them.

To use the first one, the HTML looks as follows:
<html>
<head><title>DMDVIEW REST Services</title>
<link rel=”stylesheet” href=”geocoder.css” />
<link rel=”stylesheet” href=”http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.css&#8221; />
<style>
html, body, #map {
padding: 0;
margin: 0;
height: 100%;
}

</style>
</head>
<body>
<div id=”map”></div>
<script src=”http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.js”></script&gt;
<script src=”geocoderMap.js”></script>
<script>

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

</script>
</body>
</html>

 

The CSS:

#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: 0px;
z-index: 100000;
}
#first{
text-align: center;
}
#second{
text-align: left;
margin: 0 auto;
width: 50%;
}

 

The JavaScript file does all the work. It creates the nested <div>’s to the HTML, formats them and then lets you enter an address and either puch the button or press enter. The result is a point on the map at the first result with the popup open, displaying the result name. Just add your ArcServer geocoding service URL and you can include a geocoder widget with 2 includes.

Here is the JavaScript:

(function() {

var b = document.getElementsByTagName(‘body’)[0];
var first = document.createElement(‘div’);
var second = document.createElement(‘div’);
var addressSearchBox = document.createElement(‘div’);

first.id = “first”;
second.id = “second”;
addressSearchBox.id = “AddressSearchBox”;

b.appendChild(first);
first.appendChild(second);
second.appendChild(addressSearchBox);

var text = “<center><b>Address:</b><input type=’text’ id=’addr’ name=’to’><button id=’search’ >Search</button><center>”
addressSearchBox.innerHTML=text;

var textbox = document.getElementById(“addr”);
var button = document.getElementById(“search”);

function GeocodeAddress(){
addressFromAddressSearchBox=document.getElementById(“addr”).value;
var params = “Street=”+addressFromAddressSearchBox+”&f=json&outSR=4326”;
var url = “http://YourArcServerName/ArcGIS/rest/services/YourGeolocatorServiceName/GeocodeServer/findAddressCandidates&#8221;;
if (window.XMLHttpRequest)
{
http=new XMLHttpRequest();
}
else
{
http=new ActiveXObject(“Msxml2.XMLHTTP”);
}

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);
var yousearchedfor = L.marker([thexy.candidates[0].location.y,thexy.candidates[0].location.x]).addTo(map).bindPopup(‘<h3>’+addressFromAddressSearchBox+'</h3>’).openPopup();
map.setView([thexy.candidates[0].location.y,thexy.candidates[0].location.x],18);
}//end if
}
http.send(params);
}
button.onclick=function geocode(){
GeocodeAddress();

}

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

}}

}
());

 

I mentioned that I had a second example that allowed you to switch the service at run time, or just use the default. This example requires a line of code in your HTML:

new geocoder();

or

new geocoder(“http://the service you want to use.com/arcgis/services/rest/….”);

 

the CSS is the same. The only change is in the JavaScript. Here is the code:

( function( window, undefined, url ) {

function geocoder(url) {
this.url=url;
url = typeof url !== ‘undefined’ ? url : “http://YourArcServer/ArcGIS/rest/services/YourServiceName/GeocodeServer/findAddressCandidates&#8221;;
var b = document.getElementsByTagName(‘body’)[0];
var first = document.createElement(‘div’);
var second = document.createElement(‘div’);
var addressSearchBox = document.createElement(‘div’);

first.id = “first”;
second.id = “second”;
addressSearchBox.id = “AddressSearchBox”;

b.appendChild(first);
first.appendChild(second);
second.appendChild(addressSearchBox);

var text = “<center><b>Address:</b><input type=’text’ id=’addr’ name=’to’><button id=’search’ >Search</button><center>”
addressSearchBox.innerHTML=text;

var textbox = document.getElementById(“addr”);
var button = document.getElementById(“search”);
function GeocodeAddress(){
addressFromAddressSearchBox=document.getElementById(“addr”).value;
var params = “Street=”+addressFromAddressSearchBox+”&f=json&outSR=4326”;

if (window.XMLHttpRequest)
{
http=new XMLHttpRequest();
}
else
{
http=new ActiveXObject(“Msxml2.XMLHTTP”);
}

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);
var yousearchedfor = L.marker([thexy.candidates[0].location.y,thexy.candidates[0].location.x]).addTo(map).bindPopup(‘<h3>’+addressFromAddressSearchBox+'</h3>’).openPopup();
map.setView([thexy.candidates[0].location.y,thexy.candidates[0].location.x],18);
}//end if
}
http.send(params);
}

button.onclick=function geocode(){
GeocodeAddress();
}

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

}

window.geocoder = geocoder;

} )( window );