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))
(earth_radius * c.to_f).round(2)
end
def send_request
@response = conn.get "/ArcGIS/rest/services/NGS_Survey_Control_Points/MapServer/1/query", request_params
end
private
def conn
Faraday.new(:url => query_url) do |faraday|
faraday.request :url_encoded # form-encode POST params
faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
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>
<thead>
<tr>
<th>Name</th>
<th>Elevation Order</th>
<th>Stability</th>
<th>Condition</th>
<th>Link</th>
<th>Distance from coordinates</th>
</tr>
</thead>
<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 | ||
C 175 | 2 | C | MARK NOT FOUND | http://www.ngs.noaa.gov/cgi-bin/ds_mark.prl?PidBox=JK0449 | 2.85 |
The most useful part of displaying the results to a table is that it returns the condition of the monument. You can see that point C 175 says "MARK NOT FOUND". Normally a surveyor won't notice this until they are out in the field with the datasheet and it's too late at that point. You can easily skip over that point without even looking at the datasheet.
I hope you enjoyed this post and you can see the full source of the project at [https://github.com/rlafranchi/ngs-survey-control](https://github.com/rlafranchi/ngs-survey-control)
# Resources
- Calculate distance, bearing and more between Latitude/Longitude points: http://www.movable-type.co.uk/scripts/latlong.html
- Length Of A Degree Of Latitude And Longitude Calculator: http://www.csgnetwork.com/degreelenllavcalc.html