Repository: geopandas/geopandas

Issue:
ENH: expose points_from_xy as a GeoSeries method
> We have currently only one function of a similar nature - `geopandas.points_from_xy` which stands a bit out of general API. It feels ideal to have these functions exposed as `GeoSeries.from_wkt()` rather than top-level API as the one for points. 

We should probably think about exposing `points_from_xy` as a `GeoSeries.from_..` constructor as well. One reason that I like it being top-level (and returning an array and not GeoSeries) is that you have less concern about indexes. For example if you currently do:

```
geopandas.GeoDataFrame(
    {'col': [1, 2]}, geometry=geopandas.points_from_xy([1, 2], [3, 4]), index=['a', 'b']
)
```

this currently works. But if `points_from_xy` would return a GeoSeries, the above would no longer work (or at least, you would have to rewrite it to keep it working). 

Now, I think it's certainly still useful to have those `from_..` methods on GeoSeries as well (and the fact that those accept a Series and then preserve the index certainly helps.

_Originally posted by @jorisvandenbossche in https://github.com/geopandas/geopandas/issues/1710#issuecomment-787473730_


Discussion:

Resolving patch:
diff --git a/geopandas/geoseries.py b/geopandas/geoseries.py
--- a/geopandas/geoseries.py
+++ b/geopandas/geoseries.py
@@ -19,6 +19,7 @@
     from_shapely,
     from_wkb,
     from_wkt,
+    points_from_xy,
     to_wkb,
     to_wkt,
 )
@@ -428,6 +429,52 @@ def from_wkt(cls, data, index=None, crs=None, **kwargs):
         """
         return cls._from_wkb_or_wkb(from_wkt, data, index=index, crs=crs, **kwargs)
 
+    @classmethod
+    def from_xy(cls, x, y, z=None, index=None, crs=None, **kwargs):
+        """
+        Alternate constructor to create a :class:`~geopandas.GeoSeries` of Point
+        geometries from lists or arrays of x, y(, z) coordinates
+
+        In case of geographic coordinates, it is assumed that longitude is captured
+        by ``x`` coordinates and latitude by ``y``.
+
+        Parameters
+        ----------
+        x, y, z : iterable
+        index : array-like or Index, optional
+            The index for the GeoSeries.
+        crs : value, optional
+            Coordinate Reference System of the geometry objects. Can be anything
+            accepted by
+            :meth:`pyproj.CRS.from_user_input() <pyproj.crs.CRS.from_user_input>`,
+            such as an authority string (eg "EPSG:4326") or a WKT string.
+        **kwargs
+            Additional arguments passed to the Series constructor,
+            e.g. ``name``.
+
+        Returns
+        -------
+        GeoSeries
+
+        See Also
+        --------
+        GeoSeries.from_wkt
+        points_from_xy
+
+        Examples
+        --------
+
+        >>> x = [2.5, 5, -3.0]
+        >>> y = [0.5, 1, 1.5]
+        >>> s = geopandas.GeoSeries.from_xy(x, y, crs="EPSG:4326")
+        >>> s
+        0    POINT (2.50000 0.50000)
+        1    POINT (5.00000 1.00000)
+        2    POINT (-3.00000 1.50000)
+        dtype: geometry
+        """
+        return cls(points_from_xy(x, y, z, crs=crs), index=index, crs=crs, **kwargs)
+
     @classmethod
     def _from_wkb_or_wkb(
         cls, from_wkb_or_wkt_function, data, index=None, crs=None, **kwargs


Test patch:
diff --git a/geopandas/tests/test_geoseries.py b/geopandas/tests/test_geoseries.py
--- a/geopandas/tests/test_geoseries.py
+++ b/geopandas/tests/test_geoseries.py
@@ -322,6 +322,22 @@ def test_to_wkb(self):
     def test_to_wkt(self):
         assert_series_equal(pd.Series([self.t1.wkt, self.sq.wkt]), self.g1.to_wkt())
 
+    def test_from_xy_points(self):
+        x = self.landmarks.x.values
+        y = self.landmarks.y.values
+        index = self.landmarks.index.tolist()
+        crs = self.landmarks.crs
+        assert_geoseries_equal(
+            self.landmarks, GeoSeries.from_xy(x, y, index=index, crs=crs)
+        )
+
+    def test_from_xy_points_indexless(self):
+        x = np.array([0.0, 3.0])
+        y = np.array([2.0, 5.0])
+        z = np.array([-1.0, 4.0])
+        expected = GeoSeries([Point(0, 2, -1), Point(3, 5, 4)])
+        assert_geoseries_equal(expected, GeoSeries.from_xy(x, y, z))
+
 
 def test_missing_values_empty_warning():
     s = GeoSeries([Point(1, 1), None, np.nan, BaseGeometry(), Polygon()])