Formula For State Plane to Lat/Lon Conversion

61594
27
08-07-2014 10:20 AM
Labels (1)
GeorgePorto
Occasional Contributor

Does anyone know where I can find the equation to put into Excel that will convert CT State Plane coordinates to Lat/Lon?  I know ways to programmatically do this using ArcGIS but have a scenario/workflow where we may need to do this from within Excel.  Thanks!

27 Replies
DavidFrame
New Contributor

Hey, I'm trying to do something similar. I'm srid 2237. NAD 83 state plane Florida west zone. survey ft. My projection doesn't have standard parallels. But I do have a "Scale_Factor". So, I'm not sure if I can use your stored procs without some changes?

0 Kudos
AnnaritaMacri
Occasional Contributor

@MelitaKennedy  These posts are pretty old.  Did converting state plane to lat/long in SQL become any easier?  Also, the document link for the guidelines is no longer available. Is there a new link for guidance notes on this?

0 Kudos
MelitaKennedy
Esri Notable Contributor

Hello, 

This is going to hit a bunch of different questions that have come up over the years that I missed. Sorry!

I don't know about any possible changes at the SQL level because I haven't worked with RDBMS for years. If your RDBMS has a 'spatial' component, there's likely a function like 'project' or 'projection' that should do the equivalent. It might even take WKID numbers. Ah, check out the Microsoft SQL Server Spatial Tools. Actually, from that topic there's a link in the Tip that takes you to a GitHub opensource set of Spatial Tools. 

The IOGP (absorbed EPSG years ago; EPSG name was kept for recognition purposes) Guidance Notes related to coordinate reference systems, geodesy, coordinate transformations/operations are available or linked from this page. The one you want if you're going to implement the math of a map projection is 7-2. Currently, it's available from the IOGP bookstore, but it's free. You just need to fill out your email etc. You might get some emails from IOGP; but you can unsubscribe. 

re: NAD27. NAD27 uses Clarke 1866 versus GRS80, so you would need to change the semimajor axis and the semiminor axis OR the flattening value, depending on what is being used. Unfortunately the conversion between NAD83 and NAD27 in the US uses a grid file and would be quite a bit to implement. I've never done it myself. 

There are some 'equation-based' datum transformation methods but the ones between NAD27 and WGS84 (rather than NAD83) are not very accurate. 

Melita

SeanO_Halloran
Emerging Contributor

I'm using a state plane zone that does not have values for Standard Parallel 1 & 2.  Do you use a value of 0 in these cases for the functions for calculating latitude and longitude?

0 Kudos
MelitaKennedy
Esri Notable Contributor

Hi Sean, 

The posted code is for Lambert conformal conic. It sounds like your zone (which one?) is using either transverse Mercator or Hotine oblique Mercator. Either of those projections would need to be implemented.

Melita

0 Kudos
SeanO_Halloran
Emerging Contributor

Thanks Melita.  I'm looking at Idaho East NAD 83 US foot zone 1100 which is Transverse Mercator.

0 Kudos
ScottFedak2085
Frequent Contributor

It would be awesome if coordinate transformations were built into SQL Server as they are in the PostGIS extension for Postgresql, but we can't be so lucky and this is awesome. Math always takes me a very long time to work through and I can't even imagine trying to tackle this. Someone in my office has utilized these formulas, so I can confirm that they do work!

I do have one question regarding these formulas; can it be assumed that they do not perform a datum transformation? In other words, you can use these formulas to transform coordinates in a Lambert conformal conic projections to it's CRS equivalent based on the GRS 80 Ellipsoid; but you would have to perform an additional transformation to get NAD27 (Clarke ellipsoid of 1866) or WGS 84 coordinates.

From what I've gathered, those datum transformations include even more complex math, but I could be interpreting things incorrectly. Thanks all! 

0 Kudos
Pukawai
Occasional Contributor

Glad I came across this thread, and can't thank @mpboyle enough for posting his code almost 9 years ago. I looked at the document he used and a similar one several years ago, but got lost in the math.

I had a problem where I wanted to use the same Arcade expression in ArcGIS Pro as well as AGOL to display Latitude/Longitude in a popup, but since there is no support for reprojection in Arcade, I converted this SQL Server code to arcade. The code below checks the spatial reference and converts a point coordinate to Lat/Lon from either Cal SP Zone 5 or Web Mercator. Bear in mind that for other states and zones you would have to replace the False Easting/Northing, Standard Parallels, and Latitude of Origin with appropriate values, and that all this only applies to states that use Lambert Conic for their Spate Plane projection.

//Stolen from: https://community.esri.com/t5/coordinate-reference-systems-questions/formula-for-state-plane-to-lat-lon-conversion/td-p/870543/page/2

function CalSP5ToLatLon(dblX, dblY) {
//// Projection Parameters
	var dblPi = PI
	var dblFX = 6561666.666666666  //False Easting
	var dblFY = 1640416.666666667   //False Northing
	var dblCmd = -118.0   //Central Meridian
	var dblCmr = dblCmd * (dblPi / 180.0)
	var dblSP1d = 34.03333333333333   //Standard Parallel 1
	var dblSP1r = dblSP1d * (dblPi / 180.0)
	var dblSP2d = 35.46666666666667   //Standard Parallel 2
	var dblSP2r = dblSP2d * (dblPi / 180.0)
	var dblLOd = 33.5   //Latitude of Origin
	var dblLOr = dblLOd * (dblPi / 180.0)
	var dblSmjrM = 6378137.0   //Semi Major Axis meters
	var dblSmjrF = dblSmjrM * 3.2808333
	var dblSmnrM = 6356752.314140356   //Semi Minor Axis meters
	var dblSmnrF = dblSmnrM * 3.2808333
	var dblIflat = dblSmjrM / (dblSmjrM - dblSmnrM)   // Inverse Flattening
	var dblFlat = 1 / dblIflat
	var dblE = Pow((2 * dblFlat - Pow(dblFlat,2)) , 0.5)

	var dblM1 = Cos(dblSP1r) / Sqrt((1 - Pow(dblE, 2) * Pow(Sin(dblSP1r), 2)))   
	var dblM2 = Cos(dblSP2r) / Sqrt((1 - Pow(dblE, 2) * Pow(Sin(dblSP2r), 2)))

	var dblT1 = Tan((dblPi / 4) - (dblSP1r / 2)) / Pow((1 - (dblE * Sin(dblSP1r))) / (1 + (dblE * Sin(dblSP1r))), (dblE / 2))   
	var dblT2 = Tan((dblPi / 4) - (dblSP2r / 2)) / Pow((1 - (dblE * Sin(dblSP2r))) / (1 + (dblE * Sin(dblSP2r))), (dblE / 2))   
	var dblTF = TAN((dblPi / 4) - (dblLOr / 2)) / Pow((1 - (dblE * SIN(dblLOr))) / (1 + (dblE * SIN(dblLOr))), (dblE / 2))    

	var dblN = (Log(dblM1) - Log(dblM2)) / (Log(dblT1) - Log(dblT2))   
	var dblF = dblM1 / (dblN * Pow(dblT1, dblN))   
	var dblRF = dblSmjrF * dblF * Pow(dblTF, dblN)  

	var dblRZ = Pow(Pow((dblX - dblFX), 2) + Pow((dblRF - (dblY - dblFY)), 2), 0.5)

	var dblTZ = Pow((dblRZ / (dblSmjrF * dblF)), (1 / dblN))      
	var dblZZ = Atan((dblX - dblFX) / (dblRF - (dblY - dblFY))) 

//// Calc Longitude      
	var dblLonR = ((dblZZ / dblN) + dblCmr)   
	var dblLonD = (dblLonR * 180) / dblPi

//// Calc Latitude      
 
	var dblLatTR = (dblPi / 2) - 2 * Atan(dblTZ)  
	var dblLat1R = (dblPi / 2) - (2 * Atan((dblTZ * Pow((1 - (dblE * SIN(dblLatTR))) / (1 + (dblE * Sin(dblLatTR))), (dblE / 2)))))
	var dblLat2R = (dblPi / 2) - (2 * Atan((dblTZ * Pow((1 - (dblE * SIN(dblLat1R))) / (1 + (dblE * Sin(dblLat1R))), (dblE / 2)))))
	var dblLat3R = (dblPi / 2) - (2 * Atan((dblTZ * Pow((1 - (dblE * SIN(dblLat2R))) / (1 + (dblE * Sin(dblLat2R))), (dblE / 2)))))
	var dblLat4R = (dblPi / 2) - (2 * Atan((dblTZ * Pow((1 - (dblE * SIN(dblLat3R))) / (1 + (dblE * Sin(dblLat3R))), (dblE / 2)))))
	var dblLatFR = (dblPi / 2) - (2 * Atan((dblTZ * Pow((1 - (dblE * SIN(dblLat4R))) / (1 + (dblE * Sin(dblLat4R))), (dblE / 2)))))
	var dblLatFD = (dblLatFR * 180) / dblPi

	return [dblLatFD, dblLonD]
}

function WebMercToLatLon(x, y) {
    // Converts XY point from Spherical Mercator EPSG:900913 to lat/lon in WGS84 Datum
    // Fuente: http://www.maptiler.org/google-maps-coordinates-tile-bounds-projection/
    var originShift = 2.0 * PI * 6378137.0 / 2.0;

    var lon = (x / originShift) * 180.0;
    var lat = (y / originShift) * 180.0;

    lat = 180.0 / PI * (2.0 * Atan( Exp( lat * PI / 180.0)) - PI / 2.0);
    return [lat, lon];
}

var LatLon;
var strOut;
var x = Geometry($feature).x;
var y = Geometry($feature).y;
var spatRef = Geometry($feature).spatialReference;

if (spatRef['wkid'] == 102100) {
	LatLon = WebMercToLatLon(x, y);
	strOut = 'Latitude: &nbsp;&nbsp;&nbsp;' + LatLon[0] + '<BR>' + 'Longitude: ' + LatLon[1];
}else if (spatRef['wkid'] == 2229) {
	LatLon = CalSP5ToLatLon(x, y);
	strOut = 'Latitude: &nbsp;&nbsp;&nbsp;' + LatLon[0] + '<BR>' + 'Longitude: ' + LatLon[1];
}else{
	strOut = 'Unknown Spatial Reference: ' + spatRef;
}


return { 
	type : 'text', 
	//text : 'Latitude:  ' 
	//text : 'Latitude: &nbsp;&nbsp;&nbsp;' + LonLat[0] + '<BR>' + 'Longitude: ' + LonLat[1] //this property supports html tags 
	//text :  x + '<BR>' + y
	text :  strOut

}