Repository: SciTools/cartopy

Issue:
Remove the "true_scale_latitude" parameter to Stereographic.
#275 added the ability to define a "true_scale_latitude" keyword to the Stereographic projection (I think the work was a result of SciTools/iris#518). Investigate what this actually means - I don't think that a latitude of true scale makes any sense at all for a Stereographic projection.

Heads up to @bblay who was the original author.

Remove the "true_scale_latitude" parameter to Stereographic.
#275 added the ability to define a "true_scale_latitude" keyword to the Stereographic projection (I think the work was a result of SciTools/iris#518). Investigate what this actually means - I don't think that a latitude of true scale makes any sense at all for a Stereographic projection.

Heads up to @bblay who was the original author.



Discussion:
If it's any help, the [proj4 docs](ftp://ftp.remotesensing.org/proj/OF90-284.pdf) mention this parameter for polar stereographic projections:
_"For the polar aspects, the latitude of true scale, ϕ<sub>s</sub>, may be employed in lieu of k<sub>0</sub> when ϕ<sub>s</sub> ≠ ±90°
(the default value)."_
And the definition includes _"lat_ts"_:
_+proj=stere +lat_0=ϕ<sub>0</sub> +k=k<sub>0</sub> +lat_ts= ϕ<sub>0</sub>_

The idea of a latitude of true scale doesn't make any sense for stereographic projections in general. The exception is for polar stereographic projections. You can see this by following a line of constant latitude on a stereographic map. Unless it is a polar stereographic map you will see the scale vary considerably along the line. This is perhaps most easily demonstrated by the varying spacing of the meridians that cut across the line.

This is borne out by testing with the `proj` command line tool. The parameter `lat_ts` only affects the results when `lat_0` is ±90°.

```
$ echo "10 10" | proj +proj=stere +lon_0=0 +lat_0=0 +lat_ts=0
2215070.44  2234184.70

$ echo "10 10" | proj +proj=stere +lon_0=0 +lat_0=0 +lat_ts=45
2215070.44  2234184.70

$ echo "10 10" | proj +proj=stere +lon_0=0 +lat_0=90 +lat_ts=0
930427.40   -5276715.99

$ echo "10 10" | proj +proj=stere +lon_0=0 +lat_0=90 +lat_ts=45
1583482.02  -8980372.82
```

In short, the documentation is wrong. :wink: 

> In short, the documentation is wrong.

If only there were some source of definitive proj4 documentation which we could actually update...

Thanks @pelson and @rhattersley for pointing this out.
If we remove true scale from the Stereographic class we'd have to make sure it's available for polar projections, do you agree? Would you prefer that in a separate PR?

If it's any help, the [proj4 docs](ftp://ftp.remotesensing.org/proj/OF90-284.pdf) mention this parameter for polar stereographic projections:
_"For the polar aspects, the latitude of true scale, ϕ<sub>s</sub>, may be employed in lieu of k<sub>0</sub> when ϕ<sub>s</sub> ≠ ±90°
(the default value)."_
And the definition includes _"lat_ts"_:
_+proj=stere +lat_0=ϕ<sub>0</sub> +k=k<sub>0</sub> +lat_ts= ϕ<sub>0</sub>_

The idea of a latitude of true scale doesn't make any sense for stereographic projections in general. The exception is for polar stereographic projections. You can see this by following a line of constant latitude on a stereographic map. Unless it is a polar stereographic map you will see the scale vary considerably along the line. This is perhaps most easily demonstrated by the varying spacing of the meridians that cut across the line.

This is borne out by testing with the `proj` command line tool. The parameter `lat_ts` only affects the results when `lat_0` is ±90°.

```
$ echo "10 10" | proj +proj=stere +lon_0=0 +lat_0=0 +lat_ts=0
2215070.44  2234184.70

$ echo "10 10" | proj +proj=stere +lon_0=0 +lat_0=0 +lat_ts=45
2215070.44  2234184.70

$ echo "10 10" | proj +proj=stere +lon_0=0 +lat_0=90 +lat_ts=0
930427.40   -5276715.99

$ echo "10 10" | proj +proj=stere +lon_0=0 +lat_0=90 +lat_ts=45
1583482.02  -8980372.82
```

In short, the documentation is wrong. :wink: 

> In short, the documentation is wrong.

If only there were some source of definitive proj4 documentation which we could actually update...

Thanks @pelson and @rhattersley for pointing this out.
If we remove true scale from the Stereographic class we'd have to make sure it's available for polar projections, do you agree? Would you prefer that in a separate PR?


Resolving patch:
diff --git a/lib/cartopy/crs.py b/lib/cartopy/crs.py
--- a/lib/cartopy/crs.py
+++ b/lib/cartopy/crs.py
@@ -1310,12 +1310,27 @@ def y_limits(self):
 class Stereographic(Projection):
     def __init__(self, central_latitude=0.0, central_longitude=0.0,
                  false_easting=0.0, false_northing=0.0,
-                 true_scale_latitude=None, globe=None):
+                 true_scale_latitude=None,
+                 scale_factor=None, globe=None):
         proj4_params = [('proj', 'stere'), ('lat_0', central_latitude),
                         ('lon_0', central_longitude),
                         ('x_0', false_easting), ('y_0', false_northing)]
-        if true_scale_latitude:
+
+        if true_scale_latitude is not None:
+            if central_latitude not in (-90., 90.):
+                warnings.warn('"true_scale_latitude" parameter is only used '
+                              'for polar stereographic projections. Consider '
+                              'the use of "scale_factor" instead.')
             proj4_params.append(('lat_ts', true_scale_latitude))
+
+        if scale_factor is not None:
+            if true_scale_latitude is not None:
+                raise ValueError('It does not make sense to provide both '
+                                 '"scale_factor" and "true_scale_latitude". '
+                                 'Ignoring "scale_factor".')
+            else:
+                proj4_params.append(('k_0', scale_factor))
+
         super(Stereographic, self).__init__(proj4_params, globe=globe)
 
         # TODO: Let the globe return the semimajor axis always.
@@ -1358,17 +1373,23 @@ def y_limits(self):
 
 
 class NorthPolarStereo(Stereographic):
-    def __init__(self, central_longitude=0.0, globe=None):
+    def __init__(self, central_longitude=0.0, true_scale_latitude=None,
+                 globe=None):
         super(NorthPolarStereo, self).__init__(
             central_latitude=90,
-            central_longitude=central_longitude, globe=globe)
+            central_longitude=central_longitude,
+            true_scale_latitude=true_scale_latitude,  # None is +90
+            globe=globe)
 
 
 class SouthPolarStereo(Stereographic):
-    def __init__(self, central_longitude=0.0, globe=None):
+    def __init__(self, central_longitude=0.0, true_scale_latitude=None,
+                 globe=None):
         super(SouthPolarStereo, self).__init__(
             central_latitude=-90,
-            central_longitude=central_longitude, globe=globe)
+            central_longitude=central_longitude,
+            true_scale_latitude=true_scale_latitude,  # None is -90
+            globe=globe)
 
 
 class Orthographic(Projection):


Test patch:
diff --git a/lib/cartopy/tests/crs/test_stereographic.py b/lib/cartopy/tests/crs/test_stereographic.py
--- a/lib/cartopy/tests/crs/test_stereographic.py
+++ b/lib/cartopy/tests/crs/test_stereographic.py
@@ -1,4 +1,4 @@
-# (C) British Crown Copyright 2013 - 2017, Met Office
+# (C) British Crown Copyright 2013 - 2018, Met Office
 #
 # This file is part of cartopy.
 #
@@ -52,14 +52,42 @@ def test_eccentric_globe(self):
                             [-3932.82587779, 3932.82587779], decimal=4)
 
     def test_true_scale(self):
-        # The "true_scale_latitude" parameter to Stereographic appears
-        # meaningless. This test just ensures that the correct proj4
-        # string is being created. (#339)
-        stereo = ccrs.Stereographic(true_scale_latitude=10)
-        expected = ('+ellps=WGS84 +proj=stere +lat_0=0.0 +lon_0=0.0 '
-                    '+x_0=0.0 +y_0=0.0 +lat_ts=10 +no_defs')
+        # The "true_scale_latitude" parameter only makes sense for
+        # polar stereographic projections (#339 and #455).
+        # For now only the proj4 string creation is tested
+        # See test_scale_factor for test on projection.
+        globe = ccrs.Globe(ellipse='sphere')
+        stereo = ccrs.NorthPolarStereo(true_scale_latitude=30, globe=globe)
+        expected = ('+ellps=sphere +proj=stere +lat_0=90 +lon_0=0.0 '
+                    '+x_0=0.0 +y_0=0.0 +lat_ts=30 +no_defs')
+        assert stereo.proj4_init == expected
+
+    def test_scale_factor(self):
+        # See #455
+        # Use spherical Earth in North Polar Stereographic to check
+        # equivalence between true_scale and scale_factor.
+        # In these conditions a scale factor of 0.75 corresponds exactly to
+        # a standard parallel of 30N.
+        globe = ccrs.Globe(ellipse='sphere')
+        stereo = ccrs.Stereographic(central_latitude=90., scale_factor=0.75,
+                                    globe=globe)
+        expected = ('+ellps=sphere +proj=stere +lat_0=90.0 +lon_0=0.0 '
+                    '+x_0=0.0 +y_0=0.0 +k_0=0.75 +no_defs')
         assert stereo.proj4_init == expected
 
+        # Now test projections
+        lon, lat = 10, 10
+        projected_scale_factor = stereo.transform_point(lon, lat,
+                                                        ccrs.Geodetic())
+
+        # should be equivalent to North Polar Stereo with
+        # true_scale_latitude = 30
+        nstereo = ccrs.NorthPolarStereo(globe=globe, true_scale_latitude=30)
+        projected_true_scale = nstereo.transform_point(lon, lat,
+                                                       ccrs.Geodetic())
+
+        assert projected_true_scale == projected_scale_factor
+
     def test_eastings(self):
         stereo = ccrs.Stereographic()
         stereo_offset = ccrs.Stereographic(false_easting=1234,