Initial implementation of Orthographic projection (spherical formulas) implemented from Wikipedia and Snyder equations.
https://issues.apache.org/jira/browse/SIS-223 - not yet completed because missing ellipsoidal formulas.
This work implied revisiting the way we copy names and identifiers from an existing ParameterDescriptor for creating a new ParameterDescriptor.
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/AlbersEqualArea.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/AlbersEqualArea.java
index 4ee8e02..fcb6d03 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/AlbersEqualArea.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/AlbersEqualArea.java
@@ -170,8 +170,9 @@
          * GeoTIFF: NatOriginLat
          */
         LATITUDE_OF_FALSE_ORIGIN = createLatitude(
-                renameAlias(LambertConformal2SP.LATITUDE_OF_FALSE_ORIGIN, Citations.GEOTIFF, Mercator1SP.LATITUDE_OF_ORIGIN, builder)
-                .rename(Citations.OGC, "latitude_of_center"), true);
+                renameAlias(builder,   LambertConformal2SP.LATITUDE_OF_FALSE_ORIGIN,    // Copy from this parameter…
+                            Citations.GEOTIFF, Mercator1SP.LATITUDE_OF_ORIGIN)          // … except for this name.
+                    .rename(Citations.OGC, "latitude_of_center"), true);
         /*
          * EPSG:    Longitude of false origin
          * OGC:     longitude_of_center
@@ -180,8 +181,9 @@
          * GeoTIFF: NatOriginLong
          */
         LONGITUDE_OF_FALSE_ORIGIN = createLongitude(
-                renameAlias(LambertConformal2SP.LONGITUDE_OF_FALSE_ORIGIN, Citations.GEOTIFF, Mercator1SP.LONGITUDE_OF_ORIGIN, builder)
-                .rename(Citations.OGC, "longitude_of_center"));
+                renameAlias(builder,   LambertConformal2SP.LONGITUDE_OF_FALSE_ORIGIN,
+                            Citations.GEOTIFF, Mercator1SP.LONGITUDE_OF_ORIGIN)
+                    .rename(Citations.OGC, "longitude_of_center"));
         /*
          * EPSG:    Latitude of 1st standard parallel
          * OGC:     standard_parallel_1
@@ -210,7 +212,8 @@
          * GeoTIFF: FalseEasting
          */
         EASTING_AT_FALSE_ORIGIN = createShift(
-                renameAlias(LambertConformal2SP.EASTING_AT_FALSE_ORIGIN, Citations.GEOTIFF, LambertConformal1SP.FALSE_EASTING, builder));
+                renameAlias(builder, LambertConformal2SP.EASTING_AT_FALSE_ORIGIN,
+                            Citations.GEOTIFF, LambertConformal1SP.FALSE_EASTING));
         /*
          * EPSG:    Northing at false origin
          * OGC:     false_northing
@@ -219,7 +222,8 @@
          * GeoTIFF: FalseNorthing
          */
         NORTHING_AT_FALSE_ORIGIN = createShift(
-                renameAlias(LambertConformal2SP.NORTHING_AT_FALSE_ORIGIN, Citations.GEOTIFF, LambertConformal1SP.FALSE_NORTHING, builder));
+                renameAlias(builder, LambertConformal2SP.NORTHING_AT_FALSE_ORIGIN,
+                            Citations.GEOTIFF, LambertConformal1SP.FALSE_NORTHING));
 
         PARAMETERS = builder
                 .addIdentifier(             "9822")
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/ESRI.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/ESRI.java
index 70c45d0..3ee4617 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/ESRI.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/ESRI.java
@@ -145,8 +145,8 @@
      * @return the given {@code builder}, for method call chaining.
      */
     static ParameterBuilder copyNames(final ParameterBuilder builder, final ParameterDescriptor<Double> template) {
-        return builder.addName(MapProjection.sameNameAs(Citations.ESRI,  template))
-                      .addName(MapProjection.sameNameAs(Citations.OGC,   template))
-                      .addName(MapProjection.sameNameAs(Citations.PROJ4, template));
+        return builder.addNameAndIdentifier(Citations.ESRI,  template)
+                      .addNameAndIdentifier(Citations.OGC,   template)
+                      .addNameAndIdentifier(Citations.PROJ4, template);
     }
 }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/LambertConformal2SP.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/LambertConformal2SP.java
index 2b7f8eb..5d895ed 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/LambertConformal2SP.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/LambertConformal2SP.java
@@ -237,7 +237,7 @@
                 .addName(Citations.NETCDF,   "LambertConformal")
                 .addName(Citations.GEOTIFF,  "CT_LambertConfConic_2SP")
                 .addName(Citations.GEOTIFF,  "CT_LambertConfConic")
-                .addName(sameNameAs(Citations.PROJ4, LambertConformal1SP.PARAMETERS))
+                .addNameAndIdentifier(Citations.PROJ4, LambertConformal1SP.PARAMETERS)
                 .addIdentifier(Citations.GEOTIFF,  "8")
                 .addIdentifier(Citations.MAP_INFO, "3")
                 .addIdentifier(Citations.S57,      "6")
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MapProjection.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MapProjection.java
index d1675b6..3c4df75 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MapProjection.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MapProjection.java
@@ -308,8 +308,12 @@
     /**
      * Returns the name of the given authority declared in the given parameter descriptor.
      * This method is used only as a way to avoid creating many instances of the same name.
+     *
+     * @param  authority   the authority for which to get the name.
+     * @param  parameters  where to get name for the given authority.
+     * @throws NoSuchElementException if the given authority has not been found.
      */
-    static GenericName sameNameAs(final Citation authority, final GeneralParameterDescriptor parameters) {
+    private static GenericName sameNameAs(final Citation authority, final GeneralParameterDescriptor parameters) {
         for (final GenericName candidate : parameters.getAlias()) {
             if (candidate instanceof Identifier && ((Identifier) candidate).getAuthority() == authority) {
                 return candidate;
@@ -322,37 +326,80 @@
      * Copies name, aliases and identifiers of the given {@code template}, except the alias and identifiers of the
      * given authority which are replaced by the alias and identifiers of the same authority in {@code replacement}.
      *
+     * @param  builder      an initially clean builder where to add the names and identifiers.
      * @param  template     the parameter from which to copy names and identifiers.
      * @param  toRename     authority of the alias to rename.
      * @param  replacement  the parameter from which to get the new name for the alias to rename.
-     * @param  builder      an initially clean builder where to add the names and identifiers.
      * @return the given {@code builder}, for method call chaining.
      *
      * @since 0.8
      */
-    static ParameterBuilder renameAlias(final ParameterDescriptor<Double> template, final Citation toRename,
-            final ParameterDescriptor<Double> replacement, final ParameterBuilder builder)
+    static ParameterBuilder renameAlias(final ParameterBuilder builder, final ParameterDescriptor<Double> template,
+                                        final Citation toRename, final ParameterDescriptor<Double> replacement)
+    {
+        renameAliases(builder, template, new Citation[] {toRename}, new ParameterDescriptor<?>[] {replacement});
+        return builder;
+    }
+
+    /**
+     * Same as above {@link #renameAlias(ParameterBuilder, ParameterDescriptor, Citation, ParameterDescriptor)
+     * renameAlias(…)} but with two aliases to rename.
+     *
+     * @param  builder      an initially clean builder where to add the names and identifiers.
+     * @param  template     the parameter from which to copy names and identifiers.
+     * @param  s1           authority of the first alias to rename.
+     * @param  r1           the parameter from which to get the new name for the first alias to rename.
+     * @param  s2           authority of the second alias to rename.
+     * @param  r2           the parameter from which to get the new name for the second alias to rename.
+     * @return the given {@code builder}, for method call chaining.
+     *
+     * @since 1.1
+     */
+    static ParameterBuilder renameAlias(final ParameterBuilder builder, final ParameterDescriptor<Double> template,
+                                        final Citation s1, final ParameterDescriptor<Double> r1,
+                                        final Citation s2, final ParameterDescriptor<Double> r2)
+    {
+        renameAliases(builder, template, new Citation[] {s1, s2}, new ParameterDescriptor<?>[] {r1, r2});
+        return builder;
+    }
+
+    /**
+     * Implementation of {@code renameAlias(…)} methods.
+     */
+    private static void renameAliases(final ParameterBuilder builder, final ParameterDescriptor<Double> template,
+            final Citation[] toRename, final ParameterDescriptor<?>[] replacement)
     {
         builder.addName(template.getName());
-        GenericName newName = sameNameAs(toRename, replacement);
-        Identifier newCode = IdentifiedObjects.getIdentifier(replacement, toRename);
+        final GenericName[] newNames = new GenericName[toRename.length];
+        final Identifier[]  newCodes = new Identifier [toRename.length];
+        for (int i=0; i<toRename.length; i++) {
+            newNames[i] = sameNameAs(toRename[i], replacement[i]);
+            newCodes[i] = IdentifiedObjects.getIdentifier(replacement[i], toRename[i]);
+        }
         for (GenericName alias : template.getAlias()) {
-            if (((Identifier) alias).getAuthority() == toRename) {
-                if (newName == null) continue;
-                alias = newName;
-                newName = null;
+            final Citation authority = ((Identifier) alias).getAuthority();
+            for (int i=0; i<toRename.length; i++) {
+                if (authority == toRename[i]) {
+                    if (newNames[i] == null) continue;
+                    alias = newNames[i];
+                    newNames[i] = null;
+                    break;
+                }
             }
             builder.addName(alias);
         }
         for (Identifier id : template.getIdentifiers()) {
-            if (id.getAuthority() == toRename) {
-                if (newCode == null) continue;
-                id = newCode;
-                newCode = null;
+            final Citation authority = id.getAuthority();
+            for (int i=0; i<toRename.length; i++) {
+                if (authority == toRename[i]) {
+                    if (newCodes[i] == null) continue;
+                    id = newCodes[i];
+                    newCodes[i] = null;
+                    break;
+                }
             }
             builder.addIdentifier(id);
         }
-        return builder;
     }
 
     /**
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Mercator2SP.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Mercator2SP.java
index 18e79c2..5529538 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Mercator2SP.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Mercator2SP.java
@@ -132,7 +132,7 @@
                 .addName(Citations.OGC,     "Mercator_2SP")
                 .addName(Citations.ESRI,    "Mercator")
                 .addName(Citations.NETCDF,  "Mercator")
-                .addName(sameNameAs(Citations.PROJ4, Mercator1SP.PARAMETERS))
+                .addNameAndIdentifier(Citations.PROJ4, Mercator1SP.PARAMETERS)
                 .addIdentifier(Citations.MAP_INFO, "26")    // MapInfo names this projection "Regional Mercator".
                 .addIdentifier(Citations.S57,       "8")
                 .createGroupForMapProjection(
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MercatorSpherical.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MercatorSpherical.java
index 64cd5f5..8ef6651 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MercatorSpherical.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MercatorSpherical.java
@@ -67,7 +67,7 @@
 
         PARAMETERS = addNameAndLegacy(addIdentifierAndLegacy(builder, IDENTIFIER, "9841"),
                 "Mercator (Spherical)", "Mercator (1SP) (Spherical)")   // "Mercator (Spherical)" starting from EPSG version 7.6
-                .addName(sameNameAs(Citations.PROJ4, Mercator1SP.PARAMETERS))
+                .addNameAndIdentifier(Citations.PROJ4, Mercator1SP.PARAMETERS)
                 .createGroupForMapProjection(
                         Mercator1SP.LATITUDE_OF_ORIGIN,
                         Mercator1SP.LONGITUDE_OF_ORIGIN,
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/ObliqueMercator.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/ObliqueMercator.java
index 9215a92..323cddd 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/ObliqueMercator.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/ObliqueMercator.java
@@ -33,7 +33,7 @@
  *
  * @author  Rueben Schulz (UBC)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
  *
  * @see <a href="http://geotiff.maptools.org/proj_list/hotine_oblique_mercator.html">GeoTIFF parameters for Hotine Oblique Mercator</a>
  *
@@ -62,10 +62,9 @@
      *   <caption>Parameter names</caption>
      *   <tr><td> EPSG:    </td><td> Latitude of projection centre </td></tr>
      *   <tr><td> OGC:     </td><td> latitude_of_center </td></tr>
-     *   <tr><td> ESRI:    </td><td> Latitude_Of_Origin </td></tr>
-     *   <tr><td> GeoTIFF: </td><td> NatOriginLat </td></tr>
-     *   <tr><td> Proj4:   </td><td> lat_0 </td></tr>
      *   <tr><td> ESRI:    </td><td> Latitude_Of_Center </td></tr>
+     *   <tr><td> GeoTIFF: </td><td> CenterLat </td></tr>
+     *   <tr><td> Proj4:   </td><td> lat_0 </td></tr>
      * </table>
      * <b>Notes:</b>
      * <ul>
@@ -83,10 +82,9 @@
      *   <caption>Parameter names</caption>
      *   <tr><td> EPSG:    </td><td> Longitude of projection centre </td></tr>
      *   <tr><td> OGC:     </td><td> longitude_of_center </td></tr>
-     *   <tr><td> ESRI:    </td><td> Central_Meridian </td></tr>
-     *   <tr><td> GeoTIFF: </td><td> NatOriginLong </td></tr>
-     *   <tr><td> Proj4:   </td><td> lonc </td></tr>
      *   <tr><td> ESRI:    </td><td> Longitude_Of_Center </td></tr>
+     *   <tr><td> GeoTIFF: </td><td> CenterLong </td></tr>
+     *   <tr><td> Proj4:   </td><td> lonc </td></tr>
      * </table>
      */
     public static final ParameterDescriptor<Double> LONGITUDE_OF_CENTRE;
@@ -156,18 +154,21 @@
     static {
         final ParameterBuilder builder = builder();
 
-        LATITUDE_OF_CENTRE = createLatitude(builder.addNamesAndIdentifiers(AlbersEqualArea.LATITUDE_OF_FALSE_ORIGIN)
-                .reidentify(Citations.EPSG, "8811")
-                .rename    (Citations.EPSG, "Latitude of projection centre")
-                .addName   (Citations.ESRI, "Latitude_Of_Center")
-                .rename    (Citations.NETCDF), false);                  // Remove the netCDF name.
+        LATITUDE_OF_CENTRE = createLatitude(builder
+                .addIdentifier("8811")
+                .addName("Latitude of projection centre")
+                .addNameAndIdentifier(Citations.OGC,     AlbersEqualArea.LATITUDE_OF_FALSE_ORIGIN)
+                .addName             (Citations.ESRI,   "Latitude_Of_Center")
+                .addNameAndIdentifier(Citations.GEOTIFF, Equirectangular.LATITUDE_OF_ORIGIN)
+                .addNameAndIdentifier(Citations.PROJ4,   Equirectangular.LATITUDE_OF_ORIGIN), false);
 
-        LONGITUDE_OF_CENTRE = createLongitude(builder.addNamesAndIdentifiers(AlbersEqualArea.LONGITUDE_OF_FALSE_ORIGIN)
-                .reidentify(Citations.EPSG,  "8812")
-                .rename    (Citations.EPSG,  "Longitude of projection centre")
-                .addName   (Citations.ESRI,  "Longitude_Of_Center")
-                .rename    (Citations.PROJ4, "lonc")
-                .rename    (Citations.NETCDF));                         // Remove the netCDF name.
+        LONGITUDE_OF_CENTRE = createLongitude(builder
+                .addIdentifier("8812")
+                .addName("Longitude of projection centre")
+                .addNameAndIdentifier(Citations.OGC,     AlbersEqualArea.LONGITUDE_OF_FALSE_ORIGIN)
+                .addName             (Citations.ESRI,    "Longitude_Of_Center")
+                .addNameAndIdentifier(Citations.GEOTIFF, Equirectangular.LONGITUDE_OF_ORIGIN)
+                .addName             (Citations.PROJ4,   "lonc"));
 
         AZIMUTH = builder
                 .addIdentifier("8813")
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Orthographic.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Orthographic.java
new file mode 100644
index 0000000..41fe49c
--- /dev/null
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Orthographic.java
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.referencing.provider;
+
+import javax.xml.bind.annotation.XmlTransient;
+import org.opengis.parameter.ParameterDescriptor;
+import org.opengis.parameter.ParameterDescriptorGroup;
+import org.opengis.referencing.operation.PlanarProjection;
+import org.apache.sis.parameter.Parameters;
+import org.apache.sis.parameter.ParameterBuilder;
+import org.apache.sis.metadata.iso.citation.Citations;
+import org.apache.sis.referencing.operation.projection.NormalizedProjection;
+
+import static org.apache.sis.internal.referencing.provider.AbstractProvider.builder;
+
+
+/**
+ * The provider for <cite>"Orthographic"</cite> projection (EPSG:9840).
+ *
+ * @author  Rueben Schulz (UBC)
+ * @author  Martin Desruisseaux (Geomatys)
+ *
+ * @see <a href="http://geotiff.maptools.org/proj_list/orthographic.html">GeoTIFF parameters for Orthographic</a>
+ *
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+@XmlTransient
+public class Orthographic extends MapProjection {
+    /**
+     * For compatibility with different versions during deserialization.
+     */
+    private static final long serialVersionUID = -8669271590570783071L;
+
+    /**
+     * The operation parameter descriptor for the <cite>Latitude of natural origin</cite> (φ₀) parameter value.
+     * Valid values can be -90° or 90° only. There is no default value.
+     *
+     * <!-- Generated by ParameterNameTableGenerator -->
+     * <table class="sis">
+     *   <caption>Parameter names</caption>
+     *   <tr><td> EPSG:    </td><td> Latitude of natural origin </td></tr>
+     *   <tr><td> OGC:     </td><td> latitude_of_origin </td></tr>
+     *   <tr><td> ESRI:    </td><td> Latitude_Of_Center </td></tr>
+     *   <tr><td> NetCDF:  </td><td> latitude_of_projection_origin </td></tr>
+     *   <tr><td> GeoTIFF: </td><td> CenterLat </td></tr>
+     *   <tr><td> Proj4:   </td><td> lat_0 </td></tr>
+     * </table>
+     */
+    public static final ParameterDescriptor<Double> LATITUDE_OF_ORIGIN;
+
+    /**
+     * The operation parameter descriptor for the <cite>Longitude of natural origin</cite> (λ₀) parameter value.
+     * Valid values range is [-180 … 180]° and default value is 0°.
+     *
+     * <!-- Generated by ParameterNameTableGenerator -->
+     * <table class="sis">
+     *   <caption>Parameter names</caption>
+     *   <tr><td> EPSG:    </td><td> Longitude of natural origin </td></tr>
+     *   <tr><td> OGC:     </td><td> central_meridian </td></tr>
+     *   <tr><td> ESRI:    </td><td> Longitude_Of_Center </td></tr>
+     *   <tr><td> NetCDF:  </td><td> longitude_of_projection_origin </td></tr>
+     *   <tr><td> GeoTIFF: </td><td> CenterLong </td></tr>
+     *   <tr><td> Proj4:   </td><td> lon_0 </td></tr>
+     * </table>
+     */
+    public static final ParameterDescriptor<Double> LONGITUDE_OF_ORIGIN;
+
+    /**
+     * The operation parameter descriptor for the <cite>Scale factor at natural origin</cite> (k₀) parameter value.
+     * Valid values range is (0 … ∞) and default value is 1. This is not formally a parameter of this projection.
+     *
+     * <!-- Generated by ParameterNameTableGenerator -->
+     * <table class="sis">
+     *   <caption>Parameter names</caption>
+     *   <tr><td> OGC:     </td><td> scale_factor </td></tr>
+     *   <tr><td> ESRI:    </td><td> Scale_Factor </td></tr>
+     *   <tr><td> Proj4:   </td><td> k </td></tr>
+     * </table>
+     * <b>Notes:</b>
+     * <ul>
+     *   <li>Optional</li>
+     * </ul>
+     */
+    public static final ParameterDescriptor<Double> SCALE_FACTOR = Mercator2SP.SCALE_FACTOR;
+
+    /**
+     * The operation parameter descriptor for the <cite>False easting</cite> (FE) parameter value.
+     * Valid values range is unrestricted and default value is 0 metre.
+     *
+     * <!-- Generated by ParameterNameTableGenerator -->
+     * <table class="sis">
+     *   <caption>Parameter names</caption>
+     *   <tr><td> EPSG:    </td><td> False easting </td></tr>
+     *   <tr><td> OGC:     </td><td> false_easting </td></tr>
+     *   <tr><td> ESRI:    </td><td> False_Easting </td></tr>
+     *   <tr><td> NetCDF:  </td><td> false_easting </td></tr>
+     *   <tr><td> GeoTIFF: </td><td> FalseEasting </td></tr>
+     *   <tr><td> Proj4:   </td><td> x_0 </td></tr>
+     * </table>
+     */
+    public static final ParameterDescriptor<Double> FALSE_EASTING = LambertConformal1SP.FALSE_EASTING;
+
+    /**
+     * The operation parameter descriptor for the <cite>False northing</cite> (FN) parameter value.
+     * Valid values range is unrestricted and default value is 0 metre.
+     *
+     * <!-- Generated by ParameterNameTableGenerator -->
+     * <table class="sis">
+     *   <caption>Parameter names</caption>
+     *   <tr><td> EPSG:    </td><td> False northing </td></tr>
+     *   <tr><td> OGC:     </td><td> false_northing </td></tr>
+     *   <tr><td> ESRI:    </td><td> False_Northing </td></tr>
+     *   <tr><td> NetCDF:  </td><td> false_northing </td></tr>
+     *   <tr><td> GeoTIFF: </td><td> FalseNorthing </td></tr>
+     *   <tr><td> Proj4:   </td><td> y_0 </td></tr>
+     * </table>
+     */
+    public static final ParameterDescriptor<Double> FALSE_NORTHING = LambertConformal1SP.FALSE_NORTHING;
+
+    /**
+     * The group of all parameters expected by this coordinate operation.
+     */
+    static final ParameterDescriptorGroup PARAMETERS;
+    static {
+        final ParameterBuilder builder = builder();
+
+        LATITUDE_OF_ORIGIN = createLatitude(
+                renameAlias(builder,       LambertConformal1SP.LATITUDE_OF_ORIGIN,          // Copy from this parameter…
+                            Citations.ESRI,    ObliqueMercator.LATITUDE_OF_CENTRE,          // … except for this name.
+                            Citations.GEOTIFF, ObliqueMercator.LATITUDE_OF_CENTRE), true);
+
+        LONGITUDE_OF_ORIGIN = createLongitude(
+                renameAlias(builder,       LambertConformal1SP.LONGITUDE_OF_ORIGIN,
+                            Citations.ESRI,    ObliqueMercator.LONGITUDE_OF_CENTRE,
+                            Citations.GEOTIFF, ObliqueMercator.LONGITUDE_OF_CENTRE));
+
+        PARAMETERS = builder.addIdentifier("9840")
+                .addName(                    "Orthographic")
+                .addName(Citations.OGC,      "Orthographic")
+                .addName(Citations.ESRI,     "Orthographic")
+                .addName(Citations.NETCDF,   "Orthographic")
+                .addName(Citations.GEOTIFF,  "CT_Orthographic")
+                .addName(Citations.S57,      "Orthographic")
+                .addName(Citations.S57,      "ORT")
+                .addName(Citations.PROJ4,    "ortho")
+                .addIdentifier(Citations.GEOTIFF, "21")
+                .addIdentifier(Citations.S57,     "10")
+                .createGroupForMapProjection(
+                        LATITUDE_OF_ORIGIN,
+                        LONGITUDE_OF_ORIGIN,
+                        SCALE_FACTOR,
+                        FALSE_EASTING,
+                        FALSE_NORTHING);
+    }
+
+    /**
+     * Constructs a new provider.
+     */
+    public Orthographic() {
+        super(PARAMETERS);
+    }
+
+    /**
+     * Returns the operation type for this map projection.
+     */
+    @Override
+    public Class<PlanarProjection> getOperationType() {
+        return PlanarProjection.class;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @return the map projection created from the given parameter values.
+     */
+    @Override
+    protected final NormalizedProjection createProjection(final Parameters parameters) {
+        return new org.apache.sis.referencing.operation.projection.Orthographic(this, parameters);
+    }
+}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicB.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicB.java
index d254585..4d17593 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicB.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicB.java
@@ -100,6 +100,10 @@
      *   <tr><td> ESRI:    </td><td> Scale_Factor </td></tr>
      *   <tr><td> Proj4:   </td><td> k </td></tr>
      * </table>
+     * <b>Notes:</b>
+     * <ul>
+     *   <li>Optional</li>
+     * </ul>
      */
     static final ParameterDescriptor<Double> SCALE_FACTOR;
 
@@ -120,7 +124,8 @@
 
         SCALE_FACTOR = createScale(builder
                 .addNamesAndIdentifiers(Mercator2SP.SCALE_FACTOR)
-                .setRemarks(notFormalParameter("Polar Stereographic (variant A)")).setDeprecated(true));
+                .setRemarks(notFormalParameter("Polar Stereographic (variant A)"))
+                .setRequired(false).setDeprecated(true));
 
         PARAMETERS = builder
                 .addIdentifier(IDENTIFIER)
@@ -128,7 +133,7 @@
                 .addName(Citations.S57,  "Polar stereographic")
                 .addName(Citations.S57,  "PST")
                 .addIdentifier(Citations.S57, "11")
-                .addName(sameNameAs(Citations.PROJ4, PolarStereographicA.PARAMETERS))
+                .addNameAndIdentifier(Citations.PROJ4, PolarStereographicA.PARAMETERS)
                 .createGroupForMapProjection(
                         STANDARD_PARALLEL,
                         LONGITUDE_OF_ORIGIN,
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/TransverseMercator.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/TransverseMercator.java
index 6b440b9..3671f3c 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/TransverseMercator.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/TransverseMercator.java
@@ -116,8 +116,8 @@
         LATITUDE_OF_ORIGIN = createLatitude(builder
                 .addNamesAndIdentifiers(Mercator1SP.LATITUDE_OF_ORIGIN), true);
 
-        LONGITUDE_OF_ORIGIN = createLongitude(renameAlias(Mercator1SP.LONGITUDE_OF_ORIGIN,
-                Citations.NETCDF, LambertConformal2SP.LONGITUDE_OF_FALSE_ORIGIN, builder));
+        LONGITUDE_OF_ORIGIN = createLongitude(renameAlias(builder, Mercator1SP.LONGITUDE_OF_ORIGIN,             // Copy from this parameter…
+                                         Citations.NETCDF, LambertConformal2SP.LONGITUDE_OF_FALSE_ORIGIN));     // … except for this name.
 
         SCALE_FACTOR = createScale(builder
                 .addNamesAndIdentifiers(Mercator1SP.SCALE_FACTOR)
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/Builder.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/Builder.java
index 0c71bf6..aefc1d5 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/Builder.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/Builder.java
@@ -168,7 +168,7 @@
  * </div>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.1
  *
  * @param <B>  the builder subclass.
  *
@@ -681,12 +681,12 @@
 
 
     /**
-     * Returns {@code true} if the given name or identifier is deprecated.
+     * Returns {@code true} if the given name or identifier is non-null and non-deprecated.
      *
      * @see #isDeprecated()
      */
-    private static boolean isDeprecated(final Object object) {
-        return (object instanceof Deprecable) && ((Deprecable) object).isDeprecated();
+    private static boolean isValid(final Object object) {
+        return (object != null) && !((object instanceof Deprecable) && ((Deprecable) object).isDeprecated());
     }
 
     /**
@@ -696,7 +696,7 @@
      * <p>This is a convenience method for using an existing object as a template, before to modify
      * some names by calls to {@link #rename(Citation, CharSequence[])}.</p>
      *
-     * @param  object  the object from which to copy the references to names and identifiers.
+     * @param  object  the object from which to copy the names and identifiers.
      * @return {@code this}, for method call chaining.
      *
      * @since 0.6
@@ -704,16 +704,46 @@
     public B addNamesAndIdentifiers(final IdentifiedObject object) {
         ensureNonNull("object", object);
         for (final Identifier id : object.getIdentifiers()) {
-            if (!isDeprecated(id)) {
+            if (isValid(id)) {
                 addIdentifier(id);
             }
         }
         Identifier id = object.getName();
-        if (!isDeprecated(id)) {
+        if (isValid(id)) {
             addName(id);
         }
         for (final GenericName alias : object.getAlias()) {
-            if (!isDeprecated(alias)) {
+            if (isValid(alias)) {
+                addName(alias);
+            }
+        }
+        return self();
+    }
+
+    /**
+     * Adds the non-deprecated names and identifiers from the given object for the specified authority.
+     * This is a convenience method for reusing name and identifier already declared for another object.
+     *
+     * @param  authority  the authority for which to copy the name and identifier.
+     * @param  object     the object from which to copy the name and identifier.
+     * @return {@code this}, for method call chaining.
+     *
+     * @since 1.1
+     */
+    public B addNameAndIdentifier(final Citation authority, final IdentifiedObject object) {
+        ensureNonNull("authority", authority);
+        ensureNonNull("object", object);
+        for (final Identifier id : object.getIdentifiers()) {
+            if (isValid(id) && authority.equals(id.getAuthority())) {
+                addIdentifier(id);
+            }
+        }
+        Identifier id = object.getName();
+        if (isValid(id) && authority.equals(id.getAuthority())) {
+            addName(id);
+        }
+        for (final GenericName alias : object.getAlias()) {
+            if (isValid(alias) && (alias instanceof Identifier) && authority.equals(((Identifier) alias).getAuthority())) {
                 addName(alias);
             }
         }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Orthographic.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Orthographic.java
new file mode 100644
index 0000000..0601b48
--- /dev/null
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Orthographic.java
@@ -0,0 +1,184 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.referencing.operation.projection;
+
+import java.util.EnumMap;
+import org.apache.sis.internal.util.Numerics;
+import org.opengis.referencing.operation.Matrix;
+import org.opengis.referencing.operation.OperationMethod;
+import org.opengis.parameter.ParameterDescriptor;
+import org.apache.sis.parameter.Parameters;
+import org.apache.sis.referencing.operation.matrix.Matrix2;
+import org.apache.sis.util.ComparisonMode;
+import org.apache.sis.util.Workaround;
+
+import static java.lang.Math.*;
+import static org.apache.sis.internal.referencing.provider.Orthographic.*;
+
+
+/**
+ * <cite>Orthographic</cite> projection (EPSG:9840). See "Orthographic projection" on
+ * <a href="https://en.wikipedia.org/wiki/Orthographic_projection_in_cartography">Wikipedia</a> and
+ * <a href="http://mathworld.wolfram.com/OrthographicProjection.html">MathWorld</a> for an overview.
+ *
+ * <h2>Description</h2>
+ * This is a perspective azimuthal (planar) projection that is neither conformal nor equal-area.
+ * It resembles a globe viewed from a point of perspective at infinite distance.
+ * Only one hemisphere can be seen at a time.
+ * While not useful for accurate measurements, this projection is useful for pictorial views of the world.
+ *
+ * @author  Rueben Schulz (UBC)
+ * @author  Martin Desruisseaux (Geomatys)
+ * @author  Rémi Maréchal (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+public class Orthographic extends NormalizedProjection {
+    /**
+     * For compatibility with different versions during deserialization.
+     */
+    private static final long serialVersionUID = -321305496803338120L;
+
+    /**
+     * Sine and cosine of latitude of origin.
+     */
+    private final double sinφ0, cosφ0;
+
+    /**
+     * Work around for RFE #4093999 in Sun's bug database
+     * ("Relax constraint on placement of this()/super() call in constructors").
+     */
+    @Workaround(library="JDK", version="1.8")
+    private static Initializer initializer(final OperationMethod method, final Parameters parameters) {
+        final EnumMap<ParameterRole, ParameterDescriptor<Double>> roles = new EnumMap<>(ParameterRole.class);
+        roles.put(ParameterRole.CENTRAL_MERIDIAN, LONGITUDE_OF_ORIGIN);
+        roles.put(ParameterRole.FALSE_EASTING,    FALSE_EASTING);
+        roles.put(ParameterRole.FALSE_NORTHING,   FALSE_NORTHING);
+        return new Initializer(method, parameters, roles, Initializer.AUTHALIC_RADIUS);
+    }
+
+    /**
+     * Creates an orthographic projection from the given parameters.
+     * The {@code method} argument can be the description of one of the following:
+     *
+     * <ul>
+     *   <li><cite>"Orthographic"</cite>.</li>
+     * </ul>
+     *
+     * @param method      description of the projection parameters.
+     * @param parameters  the parameter values of the projection to create.
+     */
+    public Orthographic(final OperationMethod method, final Parameters parameters) {
+        this(initializer(method, parameters));
+    }
+
+    /**
+     * Work around for RFE #4093999 in Sun's bug database
+     * ("Relax constraint on placement of this()/super() call in constructors").
+     */
+    @Workaround(library="JDK", version="1.8")
+    private Orthographic(final Initializer initializer) {
+        super(initializer);
+        final double φ0 = toRadians(initializer.getAndStore(LATITUDE_OF_ORIGIN));
+        sinφ0 = sin(φ0);
+        cosφ0 = cos(φ0);
+    }
+
+    /**
+     * Converts the specified (λ,φ) coordinate and stores the (<var>x</var>,<var>y</var>) result in {@code dstPts}.
+     * The units of measurement are implementation-specific (see subclass javadoc).
+     *
+     * @return the matrix of the projection derivative at the given source position,
+     *         or {@code null} if the {@code derivate} argument is {@code false}.
+     * @throws ProjectionException if the coordinate can not be converted.
+     */
+    @Override
+    public Matrix transform(final double[] srcPts, final int srcOff,
+                            final double[] dstPts, final int dstOff,
+                            final boolean derivate) throws ProjectionException
+    {
+        final double λ    = srcPts[srcOff  ];
+        final double φ    = srcPts[srcOff+1];
+        final double cosλ = cos(λ);
+        final double sinλ = sin(λ);
+        final double cosφ = cos(φ);
+        final double sinφ = sin(φ);
+        final double cosφ_cosλ = cosφ*cosλ;
+        final double sinφ0_sinφ = sinφ0*sinφ;                   // Note: φ₀ here is φ₁ in Snyder.
+        final double cosc = sinφ0_sinφ + cosφ0*cosφ_cosλ;       // Snyder (5-3) page 149
+        final double x, y;
+        /*
+         * c is the distance from the center of orthographic projection.
+         * If that distance is greater than 90° (identied by cos(c) < 0)
+         * then the point is on the opposite hemisphere and should be clipped.
+         */
+        if (cosc >= 0) {
+            x = cosφ * sinλ;                        // Snyder (20-3)
+            y = cosφ0*sinφ - sinφ0*cosφ_cosλ;       // Snyder (20-4)
+        } else {
+            x = Double.NaN;
+            y = Double.NaN;
+        }
+        if (dstPts != null) {
+            dstPts[dstOff  ] = x;
+            dstPts[dstOff+1] = y;
+        }
+        if (!derivate) {
+            return null;
+        }
+        return new Matrix2(cosφ_cosλ,
+                          -sinφ*sinλ,
+                           sinφ0 * x,
+                           cosφ0 * cosφ + sinφ0_sinφ*cosλ);
+    }
+
+    /**
+     * Converts the specified (<var>x</var>,<var>y</var>) coordinates
+     * and stores the result in {@code dstPts} (angles in radians).
+     */
+    @Override
+    protected void inverseTransform(final double[] srcPts, final int srcOff,
+                                    final double[] dstPts, final int dstOff)
+            throws ProjectionException
+    {
+        /*
+         * Note: Synder said that equations become undetermined if ρ=0.
+         * But setting the radius R to 1 allows simplifications to emerge.
+         * In particular sin(c) = ρ, so terms like sin(c)/ρ disappear.
+         */
+        final double x    = srcPts[srcOff  ];
+        final double y    = srcPts[srcOff+1];
+        final double ρ2   = x*x + y*y;                          // sin(c) = ρ/R, but in this method R=1.
+        final double cosc = sqrt(1 - ρ2);                       // NaN if ρ > 1.
+        dstPts[dstOff  ]  = atan2(x, cosc*cosφ0 - y*sinφ0);     // Synder (20-15) with ρ = sin(c)
+        dstPts[dstOff+1]  = asin(cosc*sinφ0 + y*cosφ0);         // Synder (20-14) where y⋅sin(c)/ρ = y/R with R=1.
+    }
+
+    /**
+     * Compares the given object with this transform for equivalence.
+     */
+    @Override
+    public boolean equals(final Object object, final ComparisonMode mode) {
+        if (super.equals(object, mode)) {
+            final Orthographic that = (Orthographic) object;
+            return Numerics.epsilonEqual(sinφ0, that.sinφ0, mode) &&
+                   Numerics.epsilonEqual(cosφ0, that.cosφ0, mode);
+        }
+        return false;
+    }
+}
diff --git a/core/sis-referencing/src/main/resources/META-INF/services/org.opengis.referencing.operation.OperationMethod b/core/sis-referencing/src/main/resources/META-INF/services/org.opengis.referencing.operation.OperationMethod
index 26726f5..5aa20e9 100644
--- a/core/sis-referencing/src/main/resources/META-INF/services/org.opengis.referencing.operation.OperationMethod
+++ b/core/sis-referencing/src/main/resources/META-INF/services/org.opengis.referencing.operation.OperationMethod
@@ -54,6 +54,7 @@
 org.apache.sis.internal.referencing.provider.ObliqueMercatorCenter
 org.apache.sis.internal.referencing.provider.ObliqueMercatorTwoPoints
 org.apache.sis.internal.referencing.provider.ObliqueMercatorTwoPointsCenter
+org.apache.sis.internal.referencing.provider.Orthographic
 org.apache.sis.internal.referencing.provider.ZonedTransverseMercator
 org.apache.sis.internal.referencing.provider.Sinusoidal
 org.apache.sis.internal.referencing.provider.Polyconic
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProvidersTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProvidersTest.java
index a78648b..c6e497d 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProvidersTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProvidersTest.java
@@ -103,6 +103,7 @@
             ObliqueMercatorCenter.class,
             ObliqueMercatorTwoPoints.class,
             ObliqueMercatorTwoPointsCenter.class,
+            Orthographic.class,
             ZonedTransverseMercator.class,
             SatelliteTracking.class,
             Sinusoidal.class,
@@ -153,7 +154,7 @@
         final Map<GeneralParameterDescriptor, GeneralParameterDescriptor> parameters = new HashMap<>();
         final Map<Object, Object> namesAndIdentifiers = new HashMap<>();
         for (final Class<?> c : methods()) {
-            final OperationMethod method = (OperationMethod) c.newInstance();
+            final OperationMethod method = (OperationMethod) c.getConstructor((Class[]) null).newInstance((Object[]) null);
             final ParameterDescriptorGroup group = method.getParameters();
             final String operationName = group.getName().getCode();
             for (final GeneralParameterDescriptor param : group.descriptors()) {
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/OrthographicTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/OrthographicTest.java
new file mode 100644
index 0000000..2f83695
--- /dev/null
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/OrthographicTest.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.referencing.operation.projection;
+
+import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.referencing.operation.transform.CoordinateDomain;
+import org.apache.sis.internal.referencing.provider.MapProjection;
+import org.apache.sis.parameter.Parameters;
+import org.apache.sis.test.DependsOn;
+import org.junit.*;
+
+import static java.lang.StrictMath.*;
+
+
+/**
+ * Tests the {@link Orthographic} class.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @author Rémi Maréchal (Geomatys)
+ *
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+@DependsOn(NormalizedProjectionTest.class)
+public final strictfp class OrthographicTest extends MapProjectionTestCase {
+    /**
+     * Creates a new instance of {@link Orthographic}.
+     *
+     * @param  φ0  latitude of projection centre.
+     */
+    private void create(final double φ0) {
+        final MapProjection provider = new org.apache.sis.internal.referencing.provider.Orthographic();
+        final Parameters parameters = parameters(provider, false);
+        parameters.parameter("latitude_of_origin").setValue(φ0);
+        transform = new Orthographic(provider, parameters);
+        final double delta = toRadians(100.0 / 60) / 1852;              // Approximatively 100 metres.
+        derivativeDeltas = new double[] {delta, delta};
+        validate();
+    }
+
+    /**
+     * Tests the equatorial projection on a sphere.
+     * This method uses points from Snyder table 22.
+     *
+     * @throws TransformException should never happen.
+     */
+    @Test
+    public void testEquatorial() throws TransformException {
+        create(0);
+        tolerance = 1E-4;                       // Accuracy of numbers provided in Snyder tables.
+        verifyTransform(
+            new double[] {                      // (λ,φ) coordinates in radians to project.
+                toRadians( 0), toRadians( 0),
+                toRadians(20), toRadians( 0),
+                toRadians( 0), toRadians(40),
+                toRadians(30), toRadians(20),
+                toRadians(20), toRadians(80)
+            },
+            new double[] {                      // Expected (x,y) results.
+                0.0,           0.0,
+                0.3420,        0.0,
+                0.0,           0.6428,
+                0.4698,        0.3420,
+                0.0594,        0.9848
+            });
+
+        tolerance = NORMALIZED_TOLERANCE;
+        verifyInDomain(CoordinateDomain.GEOGRAPHIC_RADIANS_HALF_λ, 209067359);
+        verifyDerivative(toRadians(5), toRadians(3));
+    }
+
+    /**
+     * Tests the oblique projection on a sphere.
+     * This method uses points from Snyder table 23.
+     *
+     * @throws TransformException should never happen.
+     */
+    @Test
+    public void testOblique() throws TransformException {
+        create(40);
+        tolerance = 1E-4;                       // Accuracy of numbers provided in Snyder tables.
+        verifyTransform(
+            new double[] {                      // (λ,φ) coordinates in radians to project.
+                toRadians( 0), toRadians(40),
+                toRadians( 0), toRadians( 0),
+                toRadians(20), toRadians( 0),
+                toRadians( 0), toRadians(50),
+                toRadians(30), toRadians(20),
+                toRadians(20), toRadians(80)
+            },
+            new double[] {                      // Expected (x,y) results.
+                0.0,           0.0,
+                0.0,          -0.6428,
+                0.3420,       -0.6040,
+                0.0,           0.1736,
+                0.4698,       -0.2611,
+                0.0594,        0.6495
+            });
+
+        tolerance = NORMALIZED_TOLERANCE;
+        verifyDerivative(toRadians(5), toRadians(30));
+    }
+
+    /**
+     * Tests the polar projection on a sphere.
+     * There is no reference points for this method;
+     * we just test consistency between forward and inverse methods.
+     *
+     * @throws TransformException should never happen.
+     */
+    @Test
+    public void testPolarNorth() throws TransformException {
+        create(+90);
+        tolerance = NORMALIZED_TOLERANCE;
+        verifyInDomain(CoordinateDomain.GEOGRAPHIC_RADIANS_NORTH, 753524735);
+        verifyDerivative(toRadians(5), toRadians(85));
+    }
+
+    /**
+     * Tests the polar projection on a sphere.
+     * There is no reference points for this method;
+     * we just test consistency between forward and inverse methods.
+     *
+     * @throws TransformException should never happen.
+     */
+    @Test
+    public void testPolarSouth() throws TransformException {
+        create(-90);
+        tolerance = NORMALIZED_TOLERANCE;
+        verifyInDomain(CoordinateDomain.GEOGRAPHIC_RADIANS_SOUTH, 753524735);
+        verifyDerivative(toRadians(5), toRadians(-85));
+    }
+}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java b/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
index 8020457..5e26fe4 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
@@ -191,6 +191,7 @@
     org.apache.sis.referencing.operation.projection.SinusoidalTest.class,
     org.apache.sis.referencing.operation.projection.PolyconicTest.class,
     org.apache.sis.referencing.operation.projection.MollweideTest.class,
+    org.apache.sis.referencing.operation.projection.OrthographicTest.class,
     org.apache.sis.referencing.operation.projection.SatelliteTrackingTest.class,
 
     // Coordinate operation and derived Coordinate Reference Systems (cyclic dependency).