# NGS Survey Control REST API

March 26, 2015

I've been working for a Land Surveying company as both a field chief and a project manager for almost a decade now. After deciding to pursue a career in web development, I've been trying to think of ways to use my newly found knowledge to benefit the Land Surveying Community. I've noticed that the NGS website is outdated and not easily accessible on mobile devices so I decided to put together a quick web page to make the search for survey control that much easier.

## Demo

A demo for the application can be found at ngs-survey-control.herokuapp.com. All you need to know is the particular coordinates of a site you are working on and you can search for results within ten miles of the site.

## Behind the Scenes

The application is built on the Ruby programming language but could be easily applied to other languages. The following ruby class is used to make the request to the API and it only depends on the Faraday gem. There's quite a bit of math related to figuring out the latitudes and longitudes of the envelope of the query. Essentialy the `envelope_distance` method creates a 10 by 10 mile square (or close to it) with the center being the requested coordinates. The `distance_to` method will calculate the distance between a particular result and the initialized location. The `results_within_mile` method narrows the results down to a radial search based on the entered distance.

``````class NgsQuery
attr_accessor :latitude, :longitude, :response

def initialize(latitude, longitude)
@latitude = latitude
@longitude = longitude
send_request
end

def results_within_mile(distance)
JSON.parse(response.body)['features'].select { |feature| distance_to(feature['geometry']) < distance.to_f }
end

def distance_to(location={})
# Haversine Formula
φ1 = latitude.to_f * Math::PI / 180
φ2 = location['y'].to_f * Math::PI / 180
long1 = longitude.to_f * Math::PI / 180
long2 = location['x'].to_f * Math::PI / 180

earth_radius = (6371000 * 0.000621371).to_f # miles
Δφ = (φ2-φ1)
Δλ = (long2-long1)

a = (Math.sin(Δφ/2) * Math.sin(Δφ/2)) + (Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ/2) * Math.sin(Δλ/2))
c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a))

end

def send_request
@response = conn.get "/ArcGIS/rest/services/NGS_Survey_Control_Points/MapServer/1/query", request_params
end

private

def conn
faraday.request  :url_encoded             # form-encode POST params
end
end

def request_params
{
f: "json",
outSR: "4326",
geometryType: 'esriGeometryEnvelope',
geometry: envelope_distance,
inSR: "4326",
outFields: "DATA_SRCE,NAME,LAST_COND,ELEV_ORDER,STABILITY"
}
end

def query_url
"http://maps1.arcgisonline.com"
end

def envelope_distance
lat = latitude.to_f * Math::PI / 180

m1 = 111132.92
m2 = -559.82
m3 = 1.175
m4 = -0.0023
p1 = 111412.84
p2 = -93.5
p3 = 0.118

latlen = m1 + (m2 * Math.cos(2 * lat)) + (m3 * Math.cos(4 * lat)) + (m4 * Math.cos(6 * lat))
longlen = (p1 * Math.cos(lat)) + (p2 * Math.cos(3 * lat)) + (p3 * Math.cos(5 * lat))

latfeet = latlen * 3.280833333
latsm = latfeet / 5280
latenv = 10 / latsm

longfeet = longlen * 3.280833333
longsm = longfeet / 5280
longenv = 10 / longsm
"{xmin: #{longitude.to_f - longenv}, ymin: #{latitude.to_f - latenv}, xmax: #{longitude.to_f + longenv}, ymax: #{latitude.to_f + latenv}}"
end
end
``````

## Retrieving Results

You can initialize and output results like so:

``````@query = NgsQuery.new('40','-105')
@results = query.results_within_mile(2)
# =>
[
{
"attributes" => {
"DATA_SRCE"   => "http://www.ngs.noaa.gov/cgi-bin/ds_mark.prl?PidBox=AB3292",
"NAME"        => "BASELINE",
"LAST_COND"   => "GOOD",
"ELEV_ORDER"  => " ",
"STABILITY"   => "A"
},
"geometry" => { "x" => -104.97833635353035, "y" => 39.99991861318452 }
},
{
"attributes" => {
"DATA_SRCE"   => "http://www.ngs.noaa.gov/cgi-bin/ds_mark.prl?PidBox=AI3578",
"NAME"        => "LUCY",
"LAST_COND"   => "GOOD",
"ELEV_ORDER"  => " ",
"STABILITY"   => "C"
},
"geometry" => { "x" => -105.01147022230495, "y" => 40.00009957099936 }
},
{
"attributes" => {
"DATA_SRCE"   => "http://www.ngs.noaa.gov/cgi-bin/ds_mark.prl?PidBox=KK2064",
"NAME"        => "SLATER",
"LAST_COND"   => "GOOD",
"ELEV_ORDER"  => " ",
"STABILITY"   => "C"
},
"geometry" => { "x" => -104.9939545780444, "y" => 39.99348464681532 }
}
]
``````

## Creating the Views

Here's a simple html table built using erb syntax where `@results` is an array of results similar to what's shown above and `@query` is the initialized NgsQuery class. The application that I'm using is built on the Ruby on Rails framework which uses the `link_to` method to generate the link for the datasheet.

``````<table>
<tr>
<th>Name</th>
<th>Elevation Order</th>
<th>Stability</th>
<th>Condition</th>
<th>Distance from coordinates</th>
</tr>
<tbody>
<% @results.each do |result| %>
<tr>
<td><%= result['attributes']['NAME'] %></td>
<td><%= result['attributes']['ELEV_ORDER'] %></td>
<td><%= result['attributes']['STABILITY'] %></td>
<td><%= result['attributes']['LAST_COND'] %></td>
<td><%= link_to result['attributes']['DATA_SRCE'], result['attributes']['DATA_SRCE'], target: "_blank" %></td>
<td><%= @query.distance_to(result['geometry']) %></td>
</tr>
<% end %>
</tbody>
</table>
``````

## Sample of Results that can be ouput to a table

Name Elevation Order Stability Condition Link Distance from Coordinates
AS P 313 C MONUMENTED http://www.ngs.noaa.gov/cgi-bin/ds_mark.prl?PidBox=KK1950 1.77
BALD MTN GOOD http://www.ngs.noaa.gov/cgi-bin/ds_mark.prl?PidBox=KK1949 1.77