class CRSUtility {

	public static CoordinateReferenceSystem getCRSFromGridCoordSystem(GridCoordSystem gcs) {

		ProjectionImpl projection = gcs.getProjection();
		if (projection == null) {
			return DefaultGeographicCRS.WGS84;
		}
	
		Map<String, Parameter> parameterMap = new HashMap<String, Parameter>();
		for (Parameter p : projection.getProjectionParameters()) {
			parameterMap.put(p.getName(), p);
		}

		GeodeticDatum datum = generateGeodeticDatum(parameterMap);

		Parameter gridMappingNameParameter = parameterMap.get("grid_mapping_name");

		String gridMappingName = gridMappingNameParameter == null ?
				"LatLon" :
				gridMappingNameParameter.getStringValue();


		GeographicCRS geographicCRS = (datum == DefaultGeodeticDatum.WGS84) ?
				DefaultGeographicCRS.WGS84 :
				new DefaultGeographicCRS(
					"CF-Derived Geographic CRS",
					datum,
					DefaultEllipsoidalCS.GEODETIC_2D);

		if ("LatLon".equals(gridMappingName) || "latitude_longitude".equals(gridMappingName)) {

            ProjectionRect projectionRect = gcs.getBoundingBox();
            boolean longitude360 = projectionRect.getMaxX() > 180;
            if (longitude360) {
                
                Map<String, Object> crsPoperties = new HashMap<String, Object>();
                
                crsPoperties.put(IdentifiedObject.NAME_KEY, "CRS LON [0,360]");

                ExtentImpl extent = new ExtentImpl();
                List<GeographicExtent> extentList = new ArrayList<GeographicExtent>();
                extentList.add(new GeographicBoundingBoxImpl(0, 360, -90, 90));
                extent.setGeographicElements(extentList);
                crsPoperties.put(ReferenceSystem.DOMAIN_OF_VALIDITY_KEY, extent);

                return new DefaultDerivedCRS(
                    "CF-Derived CRS LON [0,360]",
                    geographicCRS,
                    longitudeCenter180Modulo360Transform,
                    DefaultEllipsoidalCS.GEODETIC_2D);
            } else {
                return geographicCRS;
            }
		}

		try {

            Unit<Length> axisLengthUnit = generateAxisLengthUnit(gcs);

			DefaultMathTransformFactory transformFactory =
						new DefaultMathTransformFactory();

			ParameterValueGroup parameterGroup = null;
		
			if ("albers_conical_equal_area".equals(gridMappingName)) {

				parameterGroup =
						transformFactory.getDefaultParameters("Albers_Conic_Equal_Area");
				ProjectionParameterAdapter adapter = new ProjectionParameterAdapter(axisLengthUnit, parameterMap, parameterGroup);
				adapter.transferAngle("longitude_of_central_meridian", "longitude_of_center");
				adapter.transferAngle("latitude_of_projection_origin", "latitude_of_center");
				adapter.transferLength("false_easting", "false_easting");
				adapter.transferLength("false_northing", "false_northing");
				adapter.transferStandardParallels();
				adapter.transferEllipsoid(datum.getEllipsoid());

			} else if ("azimuthal_equidistant".equals(gridMappingName)) {

				throw new RuntimeException("azimuthal_equidistant projection is not supported");

			} else if ("lambert_azimuthal_equal_area".equals(gridMappingName)) {

				parameterGroup =
						transformFactory.getDefaultParameters("Lambert_Azimuthal_Equal_Area");
				ProjectionParameterAdapter adapter = new ProjectionParameterAdapter(axisLengthUnit, parameterMap, parameterGroup);
				adapter.transferAngle("longitude_of_projection_origin", "longitude_of_center");
				adapter.transferAngle("latitude_of_projection_origin", "latitude_of_center");
				adapter.transferLength("false_easting", "false_easting");
				adapter.transferLength("false_northing", "false_northing");
				adapter.transferEllipsoid(datum.getEllipsoid());

			} else if ("lambert_conformal_conic".equals(gridMappingName)) {

                boolean is2SP = ProjectionParameterAdapter.getStandardParallelCount(parameterMap) == 2;
				parameterGroup = is2SP ?
                        transformFactory.getDefaultParameters("Lambert_Conformal_Conic_2SP") :
						transformFactory.getDefaultParameters("Lambert_Conformal_Conic_1SP");
				ProjectionParameterAdapter adapter = new ProjectionParameterAdapter(axisLengthUnit, parameterMap, parameterGroup);
				adapter.transferAngle("longitude_of_central_meridian", "central_meridian");
				adapter.transferAngle("latitude_of_projection_origin", "latitude_of_origin");
				adapter.transferLength("false_easting", "false_easting");
				adapter.transferLength("false_northing", "false_northing");
                if (is2SP) { 
                    adapter.transferStandardParallels();
                } // else { /* scale_factor parameter is default of 1.0. */ }
				adapter.transferEllipsoid(datum.getEllipsoid());

			} else if ("lambert_cylindrical_equal_area".equals(gridMappingName)) {

				throw new RuntimeException("lambert_cylindrical_equal_area projection is not supported");

			} else if ("mercator".equals(gridMappingName)) {

				// presence of "standard_parallel" indicates Mercator_2SP while
				// "scale_factor_at_projection_origin" indicates Mercator_1SP
                boolean is2SP = parameterMap.containsKey("standard_parallel");
				parameterGroup = is2SP ?
						transformFactory.getDefaultParameters("Mercator_2SP") :
						transformFactory.getDefaultParameters("Mercator_1SP");
				ProjectionParameterAdapter adapter = new ProjectionParameterAdapter(axisLengthUnit, parameterMap, parameterGroup);
				adapter.transferAngle("longitude_of_projection_origin", "central_meridian");
				adapter.transferLength("false_easting", "false_easting");
				adapter.transferLength("false_northing", "false_northing");
				if (is2SP) {
                    adapter.transferStandardParallels();
                } else {
                    adapter.transferUnitless("scale_factor_at_projection_origin", "scale_factor");
                }
				adapter.transferEllipsoid(datum.getEllipsoid());

			} else if ("orthographic".equals(gridMappingName)) {

				parameterGroup =
						transformFactory.getDefaultParameters("Orthographic");
				ProjectionParameterAdapter adapter = new ProjectionParameterAdapter(axisLengthUnit, parameterMap, parameterGroup);
				adapter.transferAngle("longitude_of_projection_origin", "central_meridian");
				adapter.transferAngle("latitude_of_projection_origin", "latitude_of_origin");
				adapter.transferLength("false_easting", "false_easting");
				adapter.transferLength("false_northing", "false_northing");
				adapter.transferEllipsoid(datum.getEllipsoid());

			} else if ("polar_stereographic".equals(gridMappingName)) {

				parameterGroup =
						transformFactory.getDefaultParameters("Polar_Stereographic");
				ProjectionParameterAdapter adapter = new ProjectionParameterAdapter(axisLengthUnit, parameterMap, parameterGroup);
				adapter.transferAngle("straight_vertical_longitude_from_pole", "central_meridian");
				adapter.transferAngle("latitude_of_projection_origin", "latitude_of_origin");
				adapter.transferUnitless("scale_factor_at_projection_origin", "scale_factor");
				adapter.transferLength("false_easting", "false_easting");
				adapter.transferLength("false_northing", "false_northing");
				adapter.transferStandardParallels();
				adapter.transferEllipsoid(datum.getEllipsoid());

			} else if ("rotated_latitude_longitude".equals(gridMappingName)) {

				throw new RuntimeException("rotated_latitude_longitude projection is not supported");

			} else if ("stereographic".equals(gridMappingName)) {

				parameterGroup =
						transformFactory.getDefaultParameters("Stereographic");
				ProjectionParameterAdapter adapter = new ProjectionParameterAdapter(axisLengthUnit, parameterMap, parameterGroup);
				adapter.transferAngle("longitude_of_projection_origin", "central_meridian");
				adapter.transferAngle("latitude_of_projection_origin", "latitude_of_origin");
				adapter.transferUnitless("scale_factor_at_projection_origin", "scale_factor");
				adapter.transferLength("false_easting", "false_easting");
				adapter.transferLength("false_northing", "false_northing");
				adapter.transferEllipsoid(datum.getEllipsoid());

			} else if ("transverse_mercator".equals(gridMappingName)) {

				parameterGroup =
						transformFactory.getDefaultParameters("Transverse_Mercator");
				ProjectionParameterAdapter adapter = new ProjectionParameterAdapter(axisLengthUnit, parameterMap, parameterGroup);
				adapter.transferUnitless("scale_factor_at_central_meridian", "scale_factor");
				adapter.transferAngle("longitude_of_central_meridian", "central_meridian");
				adapter.transferAngle("latitude_of_projection_origin", "latitude_of_origin");
				adapter.transferLength("false_easting", "false_easting");
				adapter.transferLength("false_northing", "false_northing");
				adapter.transferEllipsoid(datum.getEllipsoid());

			} else if ("vertical_perspective".equals(gridMappingName)) {

				throw new RuntimeException("vertical_perspective projection is not supported");

            }

			if (parameterGroup == null) {
				throw new RuntimeException("unknown grid_mapping_name");
			}

			CartesianCS cartesianCS = DefaultCartesianCS.PROJECTED.usingUnit(axisLengthUnit);

			/* DOESN'T WORK - settings axis units is not enough, must also perform operation
			 * in transform.  see below...
			MathTransform transform = transformFactory.createParameterizedTransform(parameterGroup);
			ProjectedCRS projectedCRS = new DefaultProjectedCRS(
					"CF-Derived Projected CRS",
					geographicCRS,
					transform,
					cartesianCS);
			 */

			/*  WORKAROUND - PART 1:  This call will generate ConcatenatedTransform
			 *  instance with ability to scale values as needed.
			 */
			MathTransform transform = transformFactory.createBaseToDerived(
						geographicCRS,
						parameterGroup,
						cartesianCS);

			/*  WORKAROUND - PART 2: Wrap ConcatenatedTransform instance so
			 *  that MathTransform.getParameterDescriptors() returns something
			 *  other than null...
			 */
			if (! (transform instanceof ConcatenatedTransform)) {
				throw new RuntimeException("Expected instance of ConcatenatedTransform");
			}
			MathTransform wrapped = new ConcatenatedTransformAdapter((ConcatenatedTransform)transform);

			/*  WORKAROUND - PART 3: Create ProjectedCRS instance around
			 *  wrapped ConcatenatedTransform instance with same axis used
			 *  to generate ConcatenatedTransform
			 */
			ProjectedCRS projectedCRS = new DefaultProjectedCRS(
				"CF-Derived Projected CRS",
				geographicCRS,
				wrapped,
				cartesianCS);

			return projectedCRS;

		} catch (NoSuchIdentifierException e) {
			throw new RuntimeException(e);
		} catch (FactoryException e) {
			throw new RuntimeException(e);
		}
	}

}