diff --git a/docs/api.rst b/docs/api.rst
index 0395f45..1d8935a 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -10,6 +10,9 @@ API reference
 Geography properties
 --------------------
 
+Functions that provide access to properties of :py:class:`~spherely.Geography`
+objects without side-effects (except for ``prepare`` and ``destroy_prepared``).
+
 .. autosummary::
    :toctree: _api_generated/
 
@@ -28,6 +31,9 @@ Geography properties
 Geography creation
 ------------------
 
+Functions that build new :py:class:`~spherely.Geography` objects from
+coordinates or existing geographies.
+
 .. autosummary::
    :toctree: _api_generated/
 
@@ -44,6 +50,9 @@ Geography creation
 Input/Output
 ------------
 
+Functions that convert :py:class:`~spherely.Geography` objects to/from an
+external format such as `WKT <https://en.wikipedia.org/wiki/Well-known_text>`_.
+
 .. autosummary::
    :toctree: _api_generated/
 
@@ -53,13 +62,14 @@ Input/Output
    to_wkb
    from_geoarrow
    to_geoarrow
-   Projection
 
 .. _api_measurement:
 
 Measurement
 -----------
 
+Functions that compute measurements of one or more geographies.
+
 .. autosummary::
    :toctree: _api_generated/
 
@@ -73,6 +83,9 @@ Measurement
 Predicates
 ----------
 
+Functions that return ``True`` or ``False`` for some spatial relationship
+between two geographies.
+
 .. autosummary::
    :toctree: _api_generated/
 
@@ -90,6 +103,9 @@ Predicates
 Overlays (boolean operations)
 -----------------------------
 
+Functions that generate a new geography based on the combination of two
+geographies.
+
 .. autosummary::
    :toctree: _api_generated/
 
@@ -103,6 +119,8 @@ Overlays (boolean operations)
 Constructive operations
 -----------------------
 
+Functions that generate a new geography based on input.
+
 .. autosummary::
    :toctree: _api_generated/
 
diff --git a/docs/api_hidden.rst b/docs/api_hidden.rst
index 91f6127..12058bc 100644
--- a/docs/api_hidden.rst
+++ b/docs/api_hidden.rst
@@ -8,3 +8,7 @@
    Geography
    Geography.dimensions
    Geography.nshape
+   Projection
+   Projection.lnglat
+   Projection.pseudo_mercator
+   Projection.orthographic
diff --git a/docs/conf.py b/docs/conf.py
index f75c6c6..10fd3db 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -18,18 +18,26 @@
     "python": ("https://docs.python.org/3", None),
     "numpy": ("https://numpy.org/doc/stable", None),
     "shapely": ("https://shapely.readthedocs.io/en/latest/", None),
+    "pyarrow": ("https://arrow.apache.org/docs/", None),
 }
 
-autodoc_typehints = "none"
-
 napoleon_google_docstring = False
 napoleon_numpy_docstring = True
 napoleon_use_param = False
 napoleon_use_rtype = False
 napoleon_preprocess_types = True
 napoleon_type_aliases = {
+    # general terms
+    "sequence": ":term:`sequence`",
+    "iterable": ":term:`iterable`",
+    # numpy terms
     "array_like": ":term:`array_like`",
     "array-like": ":term:`array-like <array_like>`",
+    # objects without namespace: spherely
+    "EARTH_RADIUS_METERS": "spherely.EARTH_RADIUS_METERS",
+    # objects without namespace: numpy
+    "ndarray": "~numpy.ndarray",
+    "array": ":term:`array`",
 }
 
 # source_suffix = ['.rst', '.md']
@@ -61,8 +69,6 @@
     use_repository_button=True,
     use_issues_button=True,
     home_page_in_toc=False,
-    extra_navbar="",
-    navbar_footer_text="",
 )
 
 html_static_path = ["_static"]
diff --git a/src/accessors-geog.cpp b/src/accessors-geog.cpp
index 3e489b1..9b894f2 100644
--- a/src/accessors-geog.cpp
+++ b/src/accessors-geog.cpp
@@ -26,22 +26,6 @@ PyObjectGeography convex_hull(PyObjectGeography a) {
     return make_py_geography(s2geog::s2_convex_hull(a_ptr));
 }
 
-double get_x(PyObjectGeography a) {
-    auto geog = a.as_geog_ptr();
-    if (geog->geog_type() != GeographyType::Point) {
-        throw py::value_error("Only Point geometries supported");
-    }
-    return s2geog::s2_x(geog->geog());
-}
-
-double get_y(PyObjectGeography a) {
-    auto geog = a.as_geog_ptr();
-    if (geog->geog_type() != GeographyType::Point) {
-        throw py::value_error("Only Point geometries supported");
-    }
-    return s2geog::s2_y(geog->geog());
-}
-
 double distance(PyObjectGeography a,
                 PyObjectGeography b,
                 double radius = numeric_constants::EARTH_RADIUS_METERS) {
@@ -67,66 +51,62 @@ void init_accessors(py::module& m) {
 
     m.def("centroid",
           py::vectorize(&centroid),
-          py::arg("a"),
-          R"pbdoc(
+          py::arg("geography"),
+          py::pos_only(),
+          R"pbdoc(centroid(geography, /)
+
         Computes the centroid of each geography.
 
         Parameters
         ----------
-        a : :py:class:`Geography` or array_like
-            Geography object
+        geography : :py:class:`Geography` or array_like
+            Geography object(s).
+
+        Returns
+        -------
+        Geography or array
+            A single or an array of POINT Geography object(s).
 
     )pbdoc");
 
     m.def("boundary",
           py::vectorize(&boundary),
-          py::arg("a"),
-          R"pbdoc(
+          py::arg("geography"),
+          py::pos_only(),
+          R"pbdoc(boundary(geography, /)
+
         Computes the boundary of each geography.
 
         Parameters
         ----------
-        a : :py:class:`Geography` or array_like
-            Geography object
+        geography : :py:class:`Geography` or array_like
+            Geography object(s).
+
+        Returns
+        -------
+        Geography or array
+            A single or an array of either (MULTI)POINT or (MULTI)LINESTRING
+            Geography object(s).
 
     )pbdoc");
 
     m.def("convex_hull",
           py::vectorize(&convex_hull),
-          py::arg("a"),
-          R"pbdoc(
-        Computes the convex hull of each geography.
+          py::arg("geography"),
+          py::pos_only(),
+          R"pbdoc(convex_hull(geography, /)
 
-        Parameters
-        ----------
-        a : :py:class:`Geography` or array_like
-            Geography object
-
-    )pbdoc");
-
-    m.def("get_x",
-          py::vectorize(&get_x),
-          py::arg("a"),
-          R"pbdoc(
-        Returns the longitude value of the Point (in degrees).
+        Computes the convex hull of each geography.
 
         Parameters
         ----------
-        a: :py:class:`Geography` or array_like
+        geography : :py:class:`Geography` or array_like
             Geography object(s).
 
-    )pbdoc");
-
-    m.def("get_y",
-          py::vectorize(&get_y),
-          py::arg("a"),
-          R"pbdoc(
-        Returns the latitude value of the Point (in degrees).
-
-        Parameters
-        ----------
-        a: :py:class:`Geography` or array_like
-            Geography object(s).
+        Returns
+        -------
+        Geography or array
+            A single or an array of POLYGON Geography object(s).
 
     )pbdoc");
 
@@ -135,64 +115,92 @@ void init_accessors(py::module& m) {
           py::arg("a"),
           py::arg("b"),
           py::arg("radius") = numeric_constants::EARTH_RADIUS_METERS,
-          R"pbdoc(
+          R"pbdoc(distance(a, b, radius=spherely.EARTH_RADIUS_METERS)
+
         Calculate the distance between two geographies.
 
         Parameters
         ----------
         a : :py:class:`Geography` or array_like
-            Geography object
+            Geography object(s).
         b : :py:class:`Geography` or array_like
-            Geography object
+            Geography object(s).
         radius : float, optional
-            Radius of Earth in meters, default 6,371,010
+            Radius of Earth in meters, default 6,371,010.
+
+        Returns
+        -------
+        float or array
+            Distance value(s), in meters.
 
     )pbdoc");
 
     m.def("area",
           py::vectorize(&area),
-          py::arg("a"),
+          py::arg("geography"),
+          py::pos_only(),
           py::arg("radius") = numeric_constants::EARTH_RADIUS_METERS,
-          R"pbdoc(
+          R"pbdoc(area(geography, /, radius=spherely.EARTH_RADIUS_METERS)
+
         Calculate the area of the geography.
 
         Parameters
         ----------
-        a : :py:class:`Geography` or array_like
-            Geography object
+        geography : :py:class:`Geography` or array_like
+            Geography object(s).
         radius : float, optional
-            Radius of Earth in meters, default 6,371,010
+            Radius of Earth in meters, default 6,371,010.
+
+        Returns
+        -------
+        float or array
+            Area value(s), in square meters.
 
     )pbdoc");
 
     m.def("length",
           py::vectorize(&length),
-          py::arg("a"),
+          py::arg("geography"),
+          py::pos_only(),
           py::arg("radius") = numeric_constants::EARTH_RADIUS_METERS,
-          R"pbdoc(
+          R"pbdoc(length(geography, /, radius=spherely.EARTH_RADIUS_METERS)
+
         Calculates the length of a line geography, returning zero for other types.
 
         Parameters
         ----------
-        a : :py:class:`Geography` or array_like
-            Geography object
+        geography : :py:class:`Geography` or array_like
+            Geography object(s).
         radius : float, optional
-            Radius of Earth in meters, default 6,371,010
+            Radius of Earth in meters, default 6,371,010.
+
+        Returns
+        -------
+        float or array
+            Length value(s), in meters.
 
    )pbdoc");
 
     m.def("perimeter",
           py::vectorize(&perimeter),
-          py::arg("a"),
+          py::arg("geography"),
+          py::pos_only(),
           py::arg("radius") = numeric_constants::EARTH_RADIUS_METERS,
-          R"pbdoc(
+          R"pbdoc(perimeter(geography, /, radius=spherely.EARTH_RADIUS_METERS)
+
         Calculates the perimeter of a polygon geography, returning zero for other types.
 
         Parameters
         ----------
-        a : :py:class:`Geography` or array_like
-            Geography object
+        geography : :py:class:`Geography` or array_like
+            Geography object(s).
         radius : float, optional
-            Radius of Earth in meters, default 6,371,010
+            Radius of Earth in meters, default 6,371,010.
+
+        Returns
+        -------
+        float or array
+            Perimeter value(s), in meters.
+
     )pbdoc");
 }
diff --git a/src/boolean-operations.cpp b/src/boolean-operations.cpp
index aad4cb6..f04ef4d 100644
--- a/src/boolean-operations.cpp
+++ b/src/boolean-operations.cpp
@@ -37,13 +37,19 @@ void init_boolean_operations(py::module& m) {
           py::vectorize(BooleanOp(S2BooleanOperation::OpType::UNION)),
           py::arg("a"),
           py::arg("b"),
-          R"pbdoc(
+          R"pbdoc(union(a, b)
+
         Computes the union of both geographies.
 
         Parameters
         ----------
         a, b : :py:class:`Geography` or array_like
-            Geography object
+            Geography object(s).
+
+        Returns
+        -------
+        Geography or array
+            New Geography object(s) representing the union of the input geographies.
 
     )pbdoc");
 
@@ -51,13 +57,19 @@ void init_boolean_operations(py::module& m) {
           py::vectorize(BooleanOp(S2BooleanOperation::OpType::INTERSECTION)),
           py::arg("a"),
           py::arg("b"),
-          R"pbdoc(
+          R"pbdoc(intersection(a, b)
+
         Computes the intersection of both geographies.
 
         Parameters
         ----------
         a, b : :py:class:`Geography` or array_like
-            Geography object
+            Geography object(s).
+
+        Returns
+        -------
+        Geography or array
+            New Geography object(s) representing the intersection of the input geographies.
 
     )pbdoc");
 
@@ -65,13 +77,19 @@ void init_boolean_operations(py::module& m) {
           py::vectorize(BooleanOp(S2BooleanOperation::OpType::DIFFERENCE)),
           py::arg("a"),
           py::arg("b"),
-          R"pbdoc(
+          R"pbdoc(difference(a, b)
+
         Computes the difference of both geographies.
 
         Parameters
         ----------
         a, b : :py:class:`Geography` or array_like
-            Geography object
+            Geography object(s).
+
+        Returns
+        -------
+        Geography or array
+            New Geography object(s) representing the difference of the input geographies.
 
     )pbdoc");
 
@@ -79,13 +97,20 @@ void init_boolean_operations(py::module& m) {
           py::vectorize(BooleanOp(S2BooleanOperation::OpType::SYMMETRIC_DIFFERENCE)),
           py::arg("a"),
           py::arg("b"),
-          R"pbdoc(
+          R"pbdoc(symmetric_difference(a, b)
+
         Computes the symmetric difference of both geographies.
 
         Parameters
         ----------
         a, b : :py:class:`Geography` or array_like
-            Geography object
+            Geography object(s).
+
+        Returns
+        -------
+        Geography or array
+            New Geography object(s) representing the symmetric difference of
+            the input geographies.
 
     )pbdoc");
 }
diff --git a/src/creation.cpp b/src/creation.cpp
index 3b64ac3..92ffe70 100644
--- a/src/creation.cpp
+++ b/src/creation.cpp
@@ -291,9 +291,6 @@ std::unique_ptr<Geography> create_collection(const std::vector<Geography *> &fea
 //
 
 void init_creation(py::module &m) {
-    py::options options;
-    options.disable_function_signatures();
-
     // ----- scalar Geography creation functions
 
     m.def(
@@ -313,7 +310,8 @@ void init_creation(py::module &m) {
         },
         py::arg("longitude") = py::none(),
         py::arg("latitude") = py::none(),
-        R"pbdoc(create_point(longitude: float | None = None, latitude: float | None = None) -> Geography
+        R"pbdoc(create_point(longitude=None, latitude=None)
+
         Create a POINT geography.
 
         Parameters
@@ -323,12 +321,18 @@ void init_creation(py::module &m) {
         latitude : float, optional
             latitude coordinate, in degrees.
 
+        Returns
+        -------
+        point : Geography
+            A new POINT geography object.
+
     )pbdoc");
 
     m.def("create_multipoint",
           &create_multipoint<std::pair<double, double>>,
           py::arg("points"),
-          R"pbdoc(create_multipoint(points: Sequence) -> Geography
+          R"pbdoc(create_multipoint(points)
+
         Create a MULTIPOINT geography.
 
         Parameters
@@ -337,6 +341,12 @@ void init_creation(py::module &m) {
             A sequence of (longitude, latitude) coordinates (in degrees) or
             POINT :class:`~spherely.Geography` objects.
 
+        Returns
+        -------
+        multipoint : Geography
+            A new MULTIPOINT (or POINT if a single point is passed)
+            geography object.
+
     )pbdoc")
         .def("create_multipoint", &create_multipoint<Geography *>, py::arg("points"));
 
@@ -344,7 +354,8 @@ void init_creation(py::module &m) {
          "create_linestring",
          [](py::none) { return make_geography(std::make_unique<s2geog::PolylineGeography>()); },
          py::arg("vertices") = py::none(),
-         R"pbdoc(create_linestring(vertices: Sequence | None = None) -> Geography
+         R"pbdoc(create_linestring(vertices=None)
+
         Create a LINESTRING geography.
 
         Parameters
@@ -353,6 +364,11 @@ void init_creation(py::module &m) {
             A sequence of (longitude, latitude) coordinates (in degrees) or
             POINT :class:`~spherely.Geography` objects.
 
+        Returns
+        -------
+        linestring : Geography
+            A new LINESTRING geography object.
+
         )pbdoc")
         .def(
             "create_linestring", &create_linestring<std::pair<double, double>>, py::arg("vertices"))
@@ -361,7 +377,8 @@ void init_creation(py::module &m) {
     m.def("create_multilinestring",
           &create_multilinestring<std::pair<double, double>>,
           py::arg("lines"),
-          R"pbdoc(create_multilinestring(lines: Sequence) -> Geography
+          R"pbdoc(create_multilinestring(lines)
+
         Create a MULTILINESTRING geography.
 
         Parameters
@@ -371,6 +388,12 @@ void init_creation(py::module &m) {
             a sequence of sequences of POINT :class:`~spherely.Geography` objects or
             a sequence of LINESTRING :class:`~spherely.Geography` objects.
 
+        Returns
+        -------
+        multilinestring : Geography
+            A new MULTILINESTRING (or LINESTRING if a single line is passed)
+            geography object.
+
     )pbdoc")
         .def("create_multilinestring", &create_multilinestring<Geography *>, py::arg("lines"))
         .def(
@@ -386,7 +409,8 @@ void init_creation(py::module &m) {
          py::arg("shell") = py::none(),
          py::arg("holes") = py::none(),
          py::arg("oriented") = false,
-         R"pbdoc(create_polygon(shell: Sequence | None = None, holes: Sequence | None = None) -> Geography
+         R"pbdoc(create_polygon(shell=None, holes=None, oriented=False)
+
         Create a POLYGON geography.
 
         Parameters
@@ -404,6 +428,11 @@ void init_creation(py::module &m) {
             By default (False), it will return the polygon with the smaller
             area.
 
+        Returns
+        -------
+        polygon : Geography
+            A new POLYGON geography object.
+
     )pbdoc")
         .def("create_polygon",
              &create_polygon<std::pair<double, double>>,
@@ -419,7 +448,8 @@ void init_creation(py::module &m) {
     m.def("create_multipolygon",
           &create_multipolygon,
           py::arg("polygons"),
-          R"pbdoc(create_multipolygon(polygons: Sequence) -> Geography
+          R"pbdoc(create_multipolygon(polygons)
+
         Create a MULTIPOLYGON geography.
 
         Parameters
@@ -427,12 +457,19 @@ void init_creation(py::module &m) {
         polygons : sequence
             A sequence of POLYGON :class:`~spherely.Geography` objects.
 
+        Returns
+        -------
+        multipolygon : Geography
+            A new MULTIPOLYGON (or POLYGON if a single polygon is passed)
+            geography object.
+
     )pbdoc");
 
     m.def("create_collection",
           &create_collection,
           py::arg("geographies"),
-          R"pbdoc(create_collection(geographies: Sequence) -> Geography
+          R"pbdoc(create_collection(geographies)
+
         Create a GEOMETRYCOLLECTION geography from arbitrary geographies.
 
         Parameters
@@ -440,6 +477,11 @@ void init_creation(py::module &m) {
         geographies : sequence
             A sequence of :class:`~spherely.Geography` objects.
 
+        Returns
+        -------
+        collection : Geography
+            A new GEOMETRYCOLLECTION geography object.
+
     )pbdoc");
 
     // ----- vectorized Geography creation functions
@@ -448,7 +490,8 @@ void init_creation(py::module &m) {
           py::vectorize(&point),
           py::arg("longitude"),
           py::arg("latitude"),
-          R"pbdoc(
+          R"pbdoc(points(longitude, latitude)
+
         Create an array of points.
 
         Parameters
@@ -463,7 +506,8 @@ void init_creation(py::module &m) {
     m.def("points",
           &points,
           py::arg("coords"),
-          R"pbdoc(
+          R"pbdoc(points(coords)
+
         Create an array of points.
 
         Parameters
diff --git a/src/generate_spherely_vfunc_types.py b/src/generate_spherely_vfunc_types.py
index 87a8537..a8063c3 100644
--- a/src/generate_spherely_vfunc_types.py
+++ b/src/generate_spherely_vfunc_types.py
@@ -40,12 +40,12 @@ def update_stub_file(path, **type_specs):
 def _vfunctype_factory(class_name, n_in, **optargs):
     """Create new VFunc types.
 
-    Based on the number of input arrays and optional arguments and their types."""
-    arg_names = (
-        ["geography"]
-        if n_in == 1 and not optargs
-        else list(string.ascii_lowercase[:n_in])
-    )
+    Based on the number of input arrays and optional arguments and their types.
+    """
+    arg_names = list(string.ascii_lowercase[:n_in])
+    if n_in == 1:
+        arg_names[0] = "geography"
+
     class_code = [
         f"class {class_name}(",
         "    Generic[_NameType, _ScalarReturnType, _ArrayReturnDType]",
@@ -65,6 +65,8 @@ def _vfunctype_factory(class_name, n_in, **optargs):
             f"{arg_name}: {arg_type}"
             for arg_name, arg_type in zip(arg_names, arg_types)
         )
+        if n_in == 1:
+            arg_str += ", /"
         return_type = (
             "_ScalarReturnType"
             if all(t == geog_types[0] for t in arg_types)
diff --git a/src/geoarrow.cpp b/src/geoarrow.cpp
index aac0562..0ad8f61 100644
--- a/src/geoarrow.cpp
+++ b/src/geoarrow.cpp
@@ -258,17 +258,19 @@ void init_geoarrow(py::module& m) {
     py::class_<ArrowArrayHolder>(m, "ArrowArrayHolder")
         .def("__arrow_c_array__", &ArrowArrayHolder::return_capsules);
 
-    m.def("from_geoarrow",
-          &from_geoarrow,
-          py::arg("input"),
-          py::pos_only(),
-          py::kw_only(),
-          py::arg("oriented") = false,
-          py::arg("planar") = false,
-          py::arg("tessellate_tolerance") = 100.0,
-          py::arg("projection") = Projection::lnglat(),
-          py::arg("geometry_encoding") = py::none(),
-          R"pbdoc(
+    m.def(
+        "from_geoarrow",
+        &from_geoarrow,
+        py::arg("geographies"),
+        py::pos_only(),
+        py::kw_only(),
+        py::arg("oriented") = false,
+        py::arg("planar") = false,
+        py::arg("tessellate_tolerance") = 100.0,
+        py::arg("projection") = Projection::lnglat(),
+        py::arg("geometry_encoding") = py::none(),
+        R"pbdoc(from_geoarrow(geographies, /, *, oriented=False, planar=False, tessellate_tolerance=100.0, projection=spherely.Projection.lnglat(), geometry_encoding=None)
+
         Create an array of geographies from an Arrow array object with a GeoArrow
         extension type.
 
@@ -282,7 +284,7 @@ void init_geoarrow(py::module& m) {
 
         Parameters
         ----------
-        input : pyarrow.Array, Arrow array
+        geographies : pyarrow.Array, Arrow array
             Any array object implementing the Arrow PyCapsule Protocol
             (i.e. has a ``__arrow_c_array__`` method). The type of the array
             should be one of the geoarrow geometry types.
@@ -315,19 +317,27 @@ void init_geoarrow(py::module& m) {
             Arrow array without geoarrow type but with a plain string or
             binary type, if specifying this keyword with "WKT" or "WKB",
             respectively.
+
+        Returns
+        -------
+        Geography or array
+            An array of geography objects.
+
     )pbdoc");
 
-    m.def("to_geoarrow",
-          &to_geoarrow,
-          py::arg("input"),
-          py::pos_only(),
-          py::kw_only(),
-          py::arg("output_schema") = py::none(),
-          py::arg("projection") = Projection::lnglat(),
-          py::arg("planar") = false,
-          py::arg("tessellate_tolerance") = 100.0,
-          py::arg("precision") = 6,
-          R"pbdoc(
+    m.def(
+        "to_geoarrow",
+        &to_geoarrow,
+        py::arg("geographies"),
+        py::pos_only(),
+        py::kw_only(),
+        py::arg("output_schema") = py::none(),
+        py::arg("projection") = Projection::lnglat(),
+        py::arg("planar") = false,
+        py::arg("tessellate_tolerance") = 100.0,
+        py::arg("precision") = 6,
+        R"pbdoc(to_geoarrow(geographies, /, *, output_schema=None, projection=spherely.Projection.lnglat(), planar=False, tessellate_tolerance=100.0, precision=6)
+
         Convert an array of geographies to an Arrow array object with a GeoArrow
         extension type.
 
@@ -335,8 +345,8 @@ void init_geoarrow(py::module& m) {
 
         Parameters
         ----------
-        input : array_like
-            An array of geography objects.
+        geographies : array_like
+            An array of :py:class:`~spherely.Geography` objects.
         output_schema : Arrow schema, pyarrow.DataType, pyarrow.Field, default None
             The geoarrow extension type to use for the output. This can indicate
             one of the native geoarrow types (e.g. "point", "linestring", "polygon",
@@ -368,7 +378,7 @@ void init_geoarrow(py::module& m) {
         Returns
         -------
         ArrowArrayHolder
-            A generic Arrow array object with geograhies encoded to GeoArrow.
+            A generic Arrow array object with geographies encoded to GeoArrow.
 
         Examples
         --------
diff --git a/src/geography.cpp b/src/geography.cpp
index bd527fc..bbe57b2 100644
--- a/src/geography.cpp
+++ b/src/geography.cpp
@@ -8,6 +8,7 @@
 #include <s2/s2point.h>
 #include <s2/s2polygon.h>
 #include <s2/util/coding/coder.h>
+#include <s2geography/accessors.h>
 #include <s2geography/geography.h>
 #include <s2geography/predicates.h>
 #include <s2geography/wkt-writer.h>
@@ -228,7 +229,27 @@ std::int8_t get_type_id(PyObjectGeography obj) {
 }
 
 int get_dimensions(PyObjectGeography obj) {
-    return obj.as_geog_ptr()->dimension();
+    // note: in case of a collection with features of different dimensions:
+    // - Geography::dimension() returns -1
+    // - s2geography::s2_dimension(geog) returns the max value found in collection
+    // => we want the latter here.
+    return s2geog::s2_dimension(obj.as_geog_ptr()->geog());
+}
+
+double get_x(PyObjectGeography obj) {
+    auto geog = obj.as_geog_ptr();
+    if (geog->geog_type() != GeographyType::Point) {
+        throw py::value_error("Only Point geometries supported");
+    }
+    return s2geog::s2_x(geog->geog());
+}
+
+double get_y(PyObjectGeography obj) {
+    auto geog = obj.as_geog_ptr();
+    if (geog->geog_type() != GeographyType::Point) {
+        throw py::value_error("Only Point geometries supported");
+    }
+    return s2geog::s2_y(geog->geog());
 }
 
 /*
@@ -261,18 +282,23 @@ PyObjectGeography destroy_prepared(PyObjectGeography obj) {
 void init_geography(py::module &m) {
     // Geography types
 
-    auto pygeography_types = py::enum_<GeographyType>(m, "GeographyType", py::arithmetic(), R"pbdoc(
-        The enumeration of Geography types
-    )pbdoc");
-
-    pygeography_types.value("NONE", GeographyType::None);
-    pygeography_types.value("POINT", GeographyType::Point);
-    pygeography_types.value("LINESTRING", GeographyType::LineString);
-    pygeography_types.value("POLYGON", GeographyType::Polygon);
-    pygeography_types.value("MULTIPOLYGON", GeographyType::MultiPolygon);
-    pygeography_types.value("MULTIPOINT", GeographyType::MultiPoint);
-    pygeography_types.value("MULTILINESTRING", GeographyType::MultiLineString);
-    pygeography_types.value("GEOMETRYCOLLECTION", GeographyType::GeometryCollection);
+    auto pygeography_types = py::enum_<GeographyType>(
+        m, "GeographyType", py::arithmetic(), "The enumeration of Geography types.");
+
+    pygeography_types.value("NONE", GeographyType::None, "Undefined geography type (-1).");
+    pygeography_types.value("POINT", GeographyType::Point, "Single point geography type (0).");
+    pygeography_types.value(
+        "LINESTRING", GeographyType::LineString, "Single line geography type (1).");
+    pygeography_types.value(
+        "POLYGON", GeographyType::Polygon, "Single polygon geography type (2).");
+    pygeography_types.value(
+        "MULTIPOINT", GeographyType::MultiPoint, "Multiple point geography type (3).");
+    pygeography_types.value(
+        "MULTILINESTRING", GeographyType::MultiLineString, "Multiple line geography type (4).");
+    pygeography_types.value(
+        "MULTIPOLYGON", GeographyType::MultiPolygon, "Multiple polygon geography type (5).");
+    pygeography_types.value(
+        "GEOMETRYCOLLECTION", GeographyType::GeometryCollection, "Collection geography type (6).");
 
     // Geography classes
 
@@ -323,31 +349,97 @@ void init_geography(py::module &m) {
     m.def("get_type_id",
           py::vectorize(&get_type_id),
           py::arg("geography"),
-          R"pbdoc(
+          py::pos_only(),
+          R"pbdoc(get_type_id(geography, /)
+
         Returns the type ID of a geography.
 
         - None (missing) is -1
         - POINT is 0
         - LINESTRING is 1
+        - POLYGON is 2
+        - MULTIPOINT is 3
+        - MULTILINESTRING is 4
+        - MULTIPOLYGON is 5
+        - GEOMETRYCOLLECTION is 6
 
         Parameters
         ----------
         geography : :py:class:`Geography` or array_like
             Geography object(s).
 
+        Returns
+        -------
+        type_id : int or array
+            The type id(s) of the input geography object(s).
+            See also the ``value`` property of the
+            :py:class:`GeographyType` enumeration.
+
+        See Also
+        --------
+        GeographyType
+
     )pbdoc");
 
-    m.def("get_dimensions", py::vectorize(&get_dimensions), py::arg("geography"), R"pbdoc(
-        Returns the inherent dimensionality of a geography.
+    m.def("get_dimensions",
+          py::vectorize(&get_dimensions),
+          py::arg("geography"),
+          py::pos_only(),
+          R"pbdoc(get_dimensions(geography, /)
 
-        The inherent dimension is 0 for points, 1 for linestrings and 2 for
-        polygons. For geometrycollections it is the max of the containing elements.
-        Empty collections and None values return -1.
+        Returns the inherent dimensionality of a geography.
 
         Parameters
         ----------
         geography : :py:class:`Geography` or array_like
-            Geography object(s)
+            Geography object(s).
+
+        Returns
+        -------
+        dimensions : int or array
+            The inherent dimension is 0 for points, 1 for linestrings and 2 for
+            polygons. For geometrycollections it is either the max of the containing
+            elements or -1 for empty collections.
+
+    )pbdoc");
+
+    m.def("get_x",
+          py::vectorize(&get_x),
+          py::arg("geography"),
+          py::pos_only(),
+          R"pbdoc(get_x(geography, /)
+
+        Returns the longitude value of the Point (in degrees).
+
+        Parameters
+        ----------
+        geography: :py:class:`Geography` or array_like
+            POINT Geography object(s).
+
+        Returns
+        -------
+        float or array
+            Longitude coordinate value(s).
+
+    )pbdoc");
+
+    m.def("get_y",
+          py::vectorize(&get_y),
+          py::arg("geography"),
+          py::pos_only(),
+          R"pbdoc(get_y(geography, /)
+
+        Returns the latitude value of the Point (in degrees).
+
+        Parameters
+        ----------
+        geography: :py:class:`Geography` or array_like
+            POINT Geography object(s).
+
+        Returns
+        -------
+        float or array
+            Latitude coordinate value(s).
 
     )pbdoc");
 
@@ -356,7 +448,9 @@ void init_geography(py::module &m) {
     m.def("is_geography",
           py::vectorize(&is_geography),
           py::arg("obj"),
-          R"pbdoc(
+          py::pos_only(),
+          R"pbdoc(is_geography(obj, /)
+
         Returns True if the object is a :py:class:`Geography`, False otherwise.
 
         Parameters
@@ -371,7 +465,9 @@ void init_geography(py::module &m) {
     m.def("is_prepared",
           py::vectorize(&is_prepared),
           py::arg("geography"),
-          R"pbdoc(
+          py::pos_only(),
+          R"pbdoc(is_prepared(geography, /)
+
         Returns True if the geography object is "prepared", False otherwise.
 
         A prepared geography is a normal geography with added information such as
@@ -384,7 +480,7 @@ void init_geography(py::module &m) {
         Parameters
         ----------
         geography : :py:class:`Geography` or array_like
-            Geography object(s)
+            Geography object(s).
 
         See Also
         --------
@@ -396,7 +492,9 @@ void init_geography(py::module &m) {
     m.def("prepare",
           py::vectorize(&prepare),
           py::arg("geography"),
-          R"pbdoc(
+          py::pos_only(),
+          R"pbdoc(prepare(geography, /)
+
         Prepare a geography, improving performance of other operations.
 
         A prepared geography is a normal geography with added information such as
@@ -407,10 +505,17 @@ void init_geography(py::module &m) {
         efficient to call this function on an array that partially contains
         prepared geographies.
 
+        This function updates the input geographies in-place!
+
         Parameters
         ----------
         geography : :py:class:`Geography` or array_like
-            Geography object(s)
+            Geography object(s).
+
+        Returns
+        -------
+        prepared : Geography or array
+            The same input Geography object(s) with an attached index.
 
         See Also
         --------
@@ -422,7 +527,9 @@ void init_geography(py::module &m) {
     m.def("destroy_prepared",
           py::vectorize(&destroy_prepared),
           py::arg("geography"),
-          R"pbdoc(
+          py::pos_only(),
+          R"pbdoc(destroy_prepared(geography, /)
+
         Destroy the prepared part of a geography, freeing up memory.
 
         Note that the prepared geography will always be cleaned up if the
@@ -430,10 +537,17 @@ void init_geography(py::module &m) {
         very specific circumstances, such as freeing up memory without losing
         the geographies, or benchmarking.
 
+        This function updates the input geographies in-place!
+
         Parameters
         ----------
         geography : :py:class:`Geography` or array_like
-            Geography object(s)
+            Geography object(s).
+
+        Returns
+        -------
+        unprepared : Geography or array
+            The same input Geography object(s) with no attached index.
 
         See Also
         --------
diff --git a/src/io.cpp b/src/io.cpp
index 2f88a48..f0b54e9 100644
--- a/src/io.cpp
+++ b/src/io.cpp
@@ -23,8 +23,8 @@ class FromWKT {
         m_reader = std::make_shared<s2geog::WKTReader>(options);
     }
 
-    PyObjectGeography operator()(py::str a) const {
-        return make_py_geography(m_reader->read_feature(a));
+    PyObjectGeography operator()(py::str string) const {
+        return make_py_geography(m_reader->read_feature(string));
     }
 
 private:
@@ -37,8 +37,8 @@ class ToWKT {
         m_writer = std::make_shared<s2geog::WKTWriter>(precision);
     }
 
-    py::str operator()(PyObjectGeography a) const {
-        auto res = m_writer->write_feature(a.as_geog_ptr()->geog());
+    py::str operator()(PyObjectGeography obj) const {
+        auto res = m_writer->write_feature(obj.as_geog_ptr()->geog());
         return py::str(res);
     }
 
@@ -59,8 +59,8 @@ class FromWKB {
         m_reader = std::make_shared<s2geog::WKBReader>(options);
     }
 
-    PyObjectGeography operator()(py::bytes a) const {
-        return make_py_geography(m_reader->ReadFeature(a));
+    PyObjectGeography operator()(py::bytes bytes) const {
+        return make_py_geography(m_reader->ReadFeature(bytes));
     }
 
 private:
@@ -73,8 +73,8 @@ class ToWKB {
         m_writer = std::make_shared<s2geog::WKBWriter>();
     }
 
-    py::bytes operator()(PyObjectGeography a) const {
-        return m_writer->WriteFeature(a.as_geog_ptr()->geog());
+    py::bytes operator()(PyObjectGeography obj) const {
+        return m_writer->WriteFeature(obj.as_geog_ptr()->geog());
     }
 
 private:
@@ -84,20 +84,24 @@ class ToWKB {
 void init_io(py::module& m) {
     m.def(
         "from_wkt",
-        [](py::array_t<py::str> a, bool oriented, bool planar, float tessellate_tolerance) {
-            return py::vectorize(FromWKT(oriented, planar, tessellate_tolerance))(std::move(a));
+        [](py::array_t<py::str> string, bool oriented, bool planar, float tessellate_tolerance) {
+            return py::vectorize(FromWKT(oriented, planar, tessellate_tolerance))(
+                std::move(string));
         },
-        py::arg("a"),
+        py::arg("geography"),
+        py::pos_only(),
+        py::kw_only(),
         py::arg("oriented") = false,
         py::arg("planar") = false,
         py::arg("tessellate_tolerance") = 100.0,
-        R"pbdoc(
+        R"pbdoc(from_wkt(geography, /, *, oriented=False, planar=False, tessellate_tolerance=100.0)
+
         Creates geographies from the Well-Known Text (WKT) representation.
 
         Parameters
         ----------
-        a : str or array_like
-            WKT strings.
+        geography : str or array_like
+            The WKT string(s) to convert.
         oriented : bool, default False
             Set to True if polygon ring directions are known to be correct
             (i.e., exterior rings are defined counter clockwise and interior
@@ -116,43 +120,58 @@ void init_io(py::module& m) {
             satisfy the planar edge constraint. This is only used if `planar`
             is set to True.
 
+        Returns
+        -------
+        Geography or array
+            A single or an array of geography objects.
+
     )pbdoc");
 
     m.def(
         "to_wkt",
-        [](py::array_t<PyObjectGeography> a, int precision) {
-            return py::vectorize(ToWKT(precision))(std::move(a));
+        [](py::array_t<PyObjectGeography> obj, int precision) {
+            return py::vectorize(ToWKT(precision))(std::move(obj));
         },
-        py::arg("a"),
+        py::arg("geography"),
+        py::pos_only(),
         py::arg("precision") = 6,
-        R"pbdoc(
+        R"pbdoc(to_wkt(geography, /, precision=6)
+
         Returns the WKT representation of each geography.
 
         Parameters
         ----------
-        a : :py:class:`Geography` or array_like
-            Geography object(s)
+        geography : :py:class:`Geography` or array_like
+            Geography object(s).
         precision : int, default 6
             The number of decimal places to include in the output.
 
+        Returns
+        -------
+        str or array
+            A string or an array of strings.
+
     )pbdoc");
 
     m.def(
         "from_wkb",
-        [](py::array_t<py::bytes> a, bool oriented, bool planar, float tessellate_tolerance) {
-            return py::vectorize(FromWKB(oriented, planar, tessellate_tolerance))(std::move(a));
+        [](py::array_t<py::bytes> bytes, bool oriented, bool planar, float tessellate_tolerance) {
+            return py::vectorize(FromWKB(oriented, planar, tessellate_tolerance))(std::move(bytes));
         },
-        py::arg("a"),
+        py::arg("geography"),
+        py::pos_only(),
+        py::kw_only(),
         py::arg("oriented") = false,
         py::arg("planar") = false,
         py::arg("tessellate_tolerance") = 100.0,
-        R"pbdoc(
+        R"pbdoc(from_wkb(geography, /, *, oriented=False, planar=False, tessellate_tolerance=100.0)
+
         Creates geographies from the Well-Known Bytes (WKB) representation.
 
         Parameters
         ----------
-        a : bytes or array_like
-            WKB objects.
+        geography : bytes or array_like
+            The WKB byte object(s) to convert.
         oriented : bool, default False
             Set to True if polygon ring directions are known to be correct
             (i.e., exterior rings are defined counter clockwise and interior
@@ -171,18 +190,30 @@ void init_io(py::module& m) {
             satisfy the planar edge constraint. This is only used if `planar`
             is set to True.
 
+        Returns
+        -------
+        Geography or array
+            A single or an array of geography objects.
+
     )pbdoc");
 
     m.def("to_wkb",
           py::vectorize(ToWKB()),
-          py::arg("a"),
-          R"pbdoc(
+          py::arg("geography"),
+          py::pos_only(),
+          R"pbdoc(to_wkb(geography, /)
+
         Returns the WKB representation of each geography.
 
         Parameters
         ----------
-        a : :py:class:`Geography` or array_like
-            Geography object(s)
+        geography : :py:class:`Geography` or array_like
+            Geography object(s).
+
+        Returns
+        -------
+        bytes or array
+            A bytes object or an array of bytes.
 
     )pbdoc");
 }
diff --git a/src/predicates.cpp b/src/predicates.cpp
index 38b46c0..521dfc5 100644
--- a/src/predicates.cpp
+++ b/src/predicates.cpp
@@ -69,7 +69,8 @@ void init_predicates(py::module& m) {
           py::vectorize(Predicate(s2geog::s2_intersects)),
           py::arg("a"),
           py::arg("b"),
-          R"pbdoc(
+          R"pbdoc(intersects(a, b)
+
         Returns True if A and B share any portion of space.
 
         Intersects implies that overlaps, touches and within are True.
@@ -77,7 +78,11 @@ void init_predicates(py::module& m) {
         Parameters
         ----------
         a, b : :py:class:`Geography` or array_like
-            Geography object(s)
+            Geography object(s).
+
+        Returns
+        -------
+        bool or array
 
     )pbdoc");
 
@@ -85,7 +90,8 @@ void init_predicates(py::module& m) {
           py::vectorize(Predicate(s2geog::s2_equals)),
           py::arg("a"),
           py::arg("b"),
-          R"pbdoc(
+          R"pbdoc(equals(a, b)
+
         Returns True if A and B are spatially equal.
 
         If A is within B and B is within A, A and B are considered equal. The
@@ -94,7 +100,11 @@ void init_predicates(py::module& m) {
         Parameters
         ----------
         a, b : :py:class:`Geography` or array_like
-            Geography object(s)
+            Geography object(s).
+
+        Returns
+        -------
+        bool or array
 
     )pbdoc");
 
@@ -102,13 +112,18 @@ void init_predicates(py::module& m) {
           py::vectorize(Predicate(s2geog::s2_contains)),
           py::arg("a"),
           py::arg("b"),
-          R"pbdoc(
+          R"pbdoc(contains(a, b)
+
         Returns True if B is completely inside A.
 
         Parameters
         ----------
         a, b : :py:class:`Geography` or array_like
-            Geography object(s)
+            Geography object(s).
+
+        Returns
+        -------
+        bool or array
 
     )pbdoc");
 
@@ -120,13 +135,18 @@ void init_predicates(py::module& m) {
           })),
           py::arg("a"),
           py::arg("b"),
-          R"pbdoc(
+          R"pbdoc(within(a, b)
+
         Returns True if A is completely inside B.
 
         Parameters
         ----------
         a, b : :py:class:`Geography` or array_like
-            Geography object(s)
+            Geography object(s).
+
+        Returns
+        -------
+        bool or array
 
     )pbdoc");
 
@@ -138,14 +158,19 @@ void init_predicates(py::module& m) {
           })),
           py::arg("a"),
           py::arg("b"),
-          R"pbdoc(
+          R"pbdoc(disjoint(a, b)
+
         Returns True if A boundaries and interior does not intersect at all
         with those of B.
 
         Parameters
         ----------
         a, b : :py:class:`Geography` or array_like
-            Geography object(s)
+            Geography object(s).
+
+        Returns
+        -------
+        bool or array
 
     )pbdoc");
 
@@ -153,7 +178,8 @@ void init_predicates(py::module& m) {
           py::vectorize(TouchesPredicate()),
           py::arg("a"),
           py::arg("b"),
-          R"pbdoc(
+          R"pbdoc(touches(a, b)
+
         Returns True if A and B intersect, but their interiors do not intersect.
 
         A and B must have at least one point in common, where the common point
@@ -162,7 +188,11 @@ void init_predicates(py::module& m) {
         Parameters
         ----------
         a, b : :py:class:`Geography` or array_like
-            Geography object(s)
+            Geography object(s).
+
+        Returns
+        -------
+        bool or array
 
     )pbdoc");
 
@@ -180,13 +210,18 @@ void init_predicates(py::module& m) {
               closed_options)),
           py::arg("a"),
           py::arg("b"),
-          R"pbdoc(
+          R"pbdoc(covers(a, b)
+
         Returns True if every point in B lies inside the interior or boundary of A.
 
         Parameters
         ----------
         a, b : :py:class:`Geography` or array_like
-            Geography object(s)
+            Geography object(s).
+
+        Returns
+        -------
+        bool or array
 
         Notes
         -----
@@ -206,16 +241,22 @@ void init_predicates(py::module& m) {
               closed_options)),
           py::arg("a"),
           py::arg("b"),
-          R"pbdoc(
+          R"pbdoc(covered_by(a, b)
+
         Returns True if every point in A lies inside the interior or boundary of B.
 
         Parameters
         ----------
         a, b : :py:class:`Geography` or array_like
-            Geography object(s)
+            Geography object(s).
+
+        Returns
+        -------
+        bool or array
 
         See Also
         --------
         covers
+
     )pbdoc");
 }
diff --git a/src/projections.cpp b/src/projections.cpp
index 9c2078b..c5fefad 100644
--- a/src/projections.cpp
+++ b/src/projections.cpp
@@ -7,12 +7,52 @@
 #include "pybind11.hpp"
 
 namespace py = pybind11;
-namespace s2geog = s2geography;
 using namespace spherely;
 
 void init_projections(py::module& m) {
-    py::class_<Projection>(m, "Projection")
-        .def("lnglat", &Projection::lnglat)
-        .def("pseudo_mercator", &Projection::pseudo_mercator)
-        .def("orthographic", &Projection::orthographic);
+    py::class_<Projection> projection(m, "Projection", R"pbdoc(
+        Lightweight wrapper for selecting common reference systems used to
+        project Geography points or vertices.
+
+        Cannot be instantiated directly.
+
+    )pbdoc");
+
+    projection
+        .def_static("lnglat", &Projection::lnglat, R"pbdoc(lnglat()
+
+            Selects the "plate carree" projection.
+
+            This projection maps coordinates on the sphere to (longitude, latitude) pairs.
+            The x coordinates (longitude) span [-180, 180] and the y coordinates (latitude)
+            span [-90, 90].
+
+        )pbdoc")
+        .def_static("pseudo_mercator", &Projection::pseudo_mercator, R"pbdoc(pseudo_mercator()
+
+            Selects the spherical Mercator projection.
+
+            When used together with WGS84 coordinates, known as the "Web
+            Mercator" or "WGS84/Pseudo-Mercator" projection.
+
+        )pbdoc")
+        .def_static("orthographic",
+                    &Projection::orthographic,
+                    py::arg("longitude"),
+                    py::arg("latitude"),
+                    R"pbdoc(orthographic(longitude, latitude)
+
+            Selects an orthographic projection with the given centre point.
+
+            The resulting coordinates depict a single hemisphere of the globe as
+            it appears from outer space, centred on the given point.
+
+            Parameters
+            ----------
+            longitude : float
+                Longitude coordinate of the center point, in degrees.
+            latitude : float
+                Latitude coordinate of the center point, in degrees.
+
+        )pbdoc");
 }
diff --git a/src/projections.hpp b/src/projections.hpp
index 7989053..f91f9a0 100644
--- a/src/projections.hpp
+++ b/src/projections.hpp
@@ -7,7 +7,6 @@
 
 #include "pybind11.hpp"
 
-namespace py = pybind11;
 namespace s2geog = s2geography;
 using namespace spherely;
 
diff --git a/src/spherely.cpp b/src/spherely.cpp
index fe28991..1f4dce7 100644
--- a/src/spherely.cpp
+++ b/src/spherely.cpp
@@ -15,6 +15,9 @@ void init_geoarrow(py::module&);
 void init_projections(py::module&);
 
 PYBIND11_MODULE(spherely, m) {
+    py::options options;
+    options.disable_function_signatures();
+
     m.doc() = R"pbdoc(
         Spherely
         ---------
diff --git a/src/spherely.pyi b/src/spherely.pyi
index 7168340..11186c2 100644
--- a/src/spherely.pyi
+++ b/src/spherely.pyi
@@ -89,10 +89,10 @@ class _VFunc_Nin1_Nout1(Generic[_NameType, _ScalarReturnType, _ArrayReturnDType]
     @property
     def __name__(self) -> _NameType: ...
     @overload
-    def __call__(self, geography: Geography) -> _ScalarReturnType: ...
+    def __call__(self, geography: Geography, /) -> _ScalarReturnType: ...
     @overload
     def __call__(
-        self, geography: Iterable[Geography]
+        self, geography: Iterable[Geography], /
     ) -> npt.NDArray[_ArrayReturnDType]: ...
 
 class _VFunc_Nin2_Nout1(Generic[_NameType, _ScalarReturnType, _ArrayReturnDType]):
@@ -142,11 +142,11 @@ class _VFunc_Nin1optradius_Nout1(
     def __name__(self) -> _NameType: ...
     @overload
     def __call__(
-        self, a: Geography, radius: float = 6371010.0
+        self, geography: Geography, /, radius: float = 6371010.0
     ) -> _ScalarReturnType: ...
     @overload
     def __call__(
-        self, a: Iterable[Geography], radius: float = 6371010.0
+        self, geography: Iterable[Geography], /, radius: float = 6371010.0
     ) -> npt.NDArray[_ArrayReturnDType]: ...
 
 class _VFunc_Nin1optprecision_Nout1(
@@ -155,10 +155,12 @@ class _VFunc_Nin1optprecision_Nout1(
     @property
     def __name__(self) -> _NameType: ...
     @overload
-    def __call__(self, a: Geography, precision: int = 6) -> _ScalarReturnType: ...
+    def __call__(
+        self, geography: Geography, /, precision: int = 6
+    ) -> _ScalarReturnType: ...
     @overload
     def __call__(
-        self, a: Iterable[Geography], precision: int = 6
+        self, geography: Iterable[Geography], /, precision: int = 6
     ) -> npt.NDArray[_ArrayReturnDType]: ...
 
 # /// End types
@@ -266,28 +268,36 @@ to_wkb: _VFunc_Nin1_Nout1[Literal["to_wkb"], bytes, object]
 
 @overload
 def from_wkt(
-    a: str,
+    geography: str,
+    /,
+    *,
     oriented: bool = False,
     planar: bool = False,
     tessellate_tolerance: float = 100.0,
 ) -> Geography: ...
 @overload
 def from_wkt(
-    a: list[str] | npt.NDArray[np.str_],
+    geography: list[str] | npt.NDArray[np.str_],
+    /,
+    *,
     oriented: bool = False,
     planar: bool = False,
     tessellate_tolerance: float = 100.0,
 ) -> T_NDArray_Geography: ...
 @overload
 def from_wkb(
-    a: bytes,
+    geography: bytes,
+    /,
+    *,
     oriented: bool = False,
     planar: bool = False,
     tessellate_tolerance: float = 100.0,
 ) -> Geography: ...
 @overload
 def from_wkb(
-    a: Iterable[bytes],
+    geography: Iterable[bytes],
+    /,
+    *,
     oriented: bool = False,
     planar: bool = False,
     tessellate_tolerance: float = 100.0,
@@ -304,7 +314,7 @@ class ArrowArrayExportable(Protocol):
 class ArrowArrayHolder(ArrowArrayExportable): ...
 
 def to_geoarrow(
-    input: Geography | T_NDArray_Geography,
+    geographies: Geography | T_NDArray_Geography,
     /,
     *,
     output_schema: ArrowSchemaExportable | str | None = None,
@@ -314,7 +324,7 @@ def to_geoarrow(
     precision: int = 6,
 ) -> ArrowArrayExportable: ...
 def from_geoarrow(
-    input: ArrowArrayExportable,
+    geographies: ArrowArrayExportable,
     /,
     *,
     oriented: bool = False,
diff --git a/tests/test_accessors.py b/tests/test_accessors.py
index bdf4128..9c74451 100644
--- a/tests/test_accessors.py
+++ b/tests/test_accessors.py
@@ -87,37 +87,6 @@ def test_convex_hull(geog, expected) -> None:
     assert spherely.equals(actual, expected)
 
 
-def test_get_x_y() -> None:
-    # scalar
-    a = spherely.create_point(1.5, 2.6)
-    assert spherely.get_x(a) == pytest.approx(1.5, abs=1e-14)
-    assert spherely.get_y(a) == pytest.approx(2.6, abs=1e-14)
-
-    # array
-    arr = np.array(
-        [
-            spherely.create_point(0, 1),
-            spherely.create_point(1, 2),
-            spherely.create_point(2, 3),
-        ]
-    )
-
-    actual = spherely.get_x(arr)
-    expected = np.array([0, 1, 2], dtype="float64")
-    np.testing.assert_allclose(actual, expected)
-
-    actual = spherely.get_y(arr)
-    expected = np.array([1, 2, 3], dtype="float64")
-    np.testing.assert_allclose(actual, expected)
-
-    # only points are supported
-    with pytest.raises(ValueError):
-        spherely.get_x(spherely.create_linestring([(0, 1), (1, 2)]))
-
-    with pytest.raises(ValueError):
-        spherely.get_y(spherely.create_linestring([(0, 1), (1, 2)]))
-
-
 @pytest.mark.parametrize(
     "geog_a, geog_b, expected",
     [
diff --git a/tests/test_geography.py b/tests/test_geography.py
index 6713141..fd676d2 100644
--- a/tests/test_geography.py
+++ b/tests/test_geography.py
@@ -6,6 +6,17 @@
 import spherely
 
 
+def test_geography_type() -> None:
+    assert spherely.GeographyType.NONE.value == -1
+    assert spherely.GeographyType.POINT.value == 0
+    assert spherely.GeographyType.LINESTRING.value == 1
+    assert spherely.GeographyType.POLYGON.value == 2
+    assert spherely.GeographyType.MULTIPOINT.value == 3
+    assert spherely.GeographyType.MULTILINESTRING.value == 4
+    assert spherely.GeographyType.MULTIPOLYGON.value == 5
+    assert spherely.GeographyType.GEOMETRYCOLLECTION.value == 6
+
+
 def test_is_geography() -> None:
     arr = np.array([1, 2.33, spherely.create_point(30, 6)])
 
@@ -85,6 +96,57 @@ def test_get_dimensions() -> None:
     assert spherely.get_dimensions(spherely.create_point(5, 40)) == 0
 
 
+def test_get_x_y() -> None:
+    # scalar
+    a = spherely.create_point(1.5, 2.6)
+    assert spherely.get_x(a) == pytest.approx(1.5, abs=1e-14)
+    assert spherely.get_y(a) == pytest.approx(2.6, abs=1e-14)
+
+    # array
+    arr = np.array(
+        [
+            spherely.create_point(0, 1),
+            spherely.create_point(1, 2),
+            spherely.create_point(2, 3),
+        ]
+    )
+
+    actual = spherely.get_x(arr)
+    expected = np.array([0, 1, 2], dtype="float64")
+    np.testing.assert_allclose(actual, expected)
+
+    actual = spherely.get_y(arr)
+    expected = np.array([1, 2, 3], dtype="float64")
+    np.testing.assert_allclose(actual, expected)
+
+    # only points are supported
+    with pytest.raises(ValueError):
+        spherely.get_x(spherely.create_linestring([(0, 1), (1, 2)]))
+
+    with pytest.raises(ValueError):
+        spherely.get_y(spherely.create_linestring([(0, 1), (1, 2)]))
+
+
+@pytest.mark.parametrize(
+    "empty_geog, expected",
+    [
+        (spherely.create_point(), 0),
+        (spherely.create_linestring(), 1),
+        (spherely.create_polygon(), 2),
+        (spherely.create_collection([]), -1),
+    ],
+)
+def test_get_dimensions_empty(empty_geog, expected) -> None:
+    assert spherely.get_dimensions(empty_geog) == expected
+
+
+def test_get_dimensions_collection() -> None:
+    geog = spherely.create_collection(
+        [spherely.create_point(0, 0), spherely.create_polygon([(0, 0), (1, 1), (2, 0)])]
+    )
+    assert spherely.get_dimensions(geog) == 2
+
+
 def test_prepare() -> None:
     # test array
     geog = np.array(