Repository: geopandas/geopandas

Issue:
ENH: parity between GeoDataFrame.to_json and GeoSeries.to_json
> Hey, guys @jdmcbr @martinfleis , just bringing to your attention that while you fixed this issue for GeoDataFrame, id field is still obligatory for GeoSeries, there is no option to remove it. You might want to add it for GeoSeries as well.
>
> My version of geopandas is 0.14.2.

_Originally posted by @kiranzo in https://github.com/geopandas/geopandas/issues/1637#issuecomment-1958551545_
            

Some attributes shall be included in `GeoSeries.to_json` as well.


Discussion:

Resolving patch:
diff --git a/geopandas/geoseries.py b/geopandas/geoseries.py
--- a/geopandas/geoseries.py
+++ b/geopandas/geoseries.py
@@ -1,6 +1,5 @@
 from __future__ import annotations
 
-import json
 import typing
 from typing import Optional, Any, Callable, Dict
 import warnings
@@ -1190,12 +1189,31 @@ def estimate_utm_crs(self, datum_name: str = "WGS 84"):
         """
         return self.values.estimate_utm_crs(datum_name)
 
-    def to_json(self, **kwargs) -> str:
+    def to_json(
+        self,
+        show_bbox: bool = True,
+        drop_id: bool = False,
+        to_wgs84: bool = False,
+        **kwargs,
+    ) -> str:
         """
         Returns a GeoJSON string representation of the GeoSeries.
 
         Parameters
         ----------
+        show_bbox : bool, optional, default: True
+            Include bbox (bounds) in the geojson
+        drop_id : bool, default: False
+            Whether to retain the index of the GeoSeries as the id property
+            in the generated GeoJSON. Default is False, but may want True
+            if the index is just arbitrary row numbers.
+        to_wgs84: bool, optional, default: False
+            If the CRS is set on the active geometry column it is exported as
+            WGS84 (EPSG:4326) to meet the `2016 GeoJSON specification
+            <https://tools.ietf.org/html/rfc7946>`_.
+            Set to True to force re-projection and set to False to ignore CRS. False by
+            default.
+
         *kwargs* that will be passed to json.dumps().
 
         Returns
@@ -1224,7 +1242,9 @@ def to_json(self, **kwargs) -> str:
         --------
         GeoSeries.to_file : write GeoSeries to file
         """
-        return json.dumps(self.__geo_interface__, **kwargs)
+        return self.to_frame("geometry").to_json(
+            na="null", show_bbox=show_bbox, drop_id=drop_id, to_wgs84=to_wgs84, **kwargs
+        )
 
     def to_wkb(self, hex: bool = False, **kwargs) -> Series:
         """


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
@@ -55,6 +55,9 @@ def setup_method(self):
         self.l1 = LineString([(0, 0), (0, 1), (1, 1)])
         self.l2 = LineString([(0, 0), (1, 0), (1, 1), (0, 1)])
         self.g5 = GeoSeries([self.l1, self.l2])
+        self.esb3857 = Point(-8235939.130493107, 4975301.253789809)
+        self.sol3857 = Point(-8242607.167991625, 4966620.938285081)
+        self.landmarks3857 = GeoSeries([self.esb3857, self.sol3857], crs="epsg:3857")
 
     def teardown_method(self):
         shutil.rmtree(self.tempdir)
@@ -201,9 +204,62 @@ def test_to_json(self):
         Test whether GeoSeries.to_json works and returns an actual json file.
         """
         json_str = self.g3.to_json()
-        json.loads(json_str)
+        data = json.loads(json_str)
+        assert "id" in data["features"][0].keys()
+        assert "bbox" in data["features"][0].keys()
         # TODO : verify the output is a valid GeoJSON.
 
+    def test_to_json_drop_id(self):
+        """
+        Test whether GeoSeries.to_json works when drop_id is True.
+        """
+        json_str = self.g3.to_json(drop_id=True)
+        data = json.loads(json_str)
+        assert "id" not in data["features"][0].keys()
+
+    def test_to_json_no_bbox(self):
+        """
+        Test whether GeoSeries.to_json works when show_bbox is False.
+        """
+        json_str = self.g3.to_json(show_bbox=False)
+        data = json.loads(json_str)
+        assert "bbox" not in data["features"][0].keys()
+
+    def test_to_json_no_bbox_drop_id(self):
+        """
+        Test whether GeoSeries.to_json works when show_bbox is False
+        and drop_id is True.
+        """
+        json_str = self.g3.to_json(show_bbox=False, drop_id=True)
+        data = json.loads(json_str)
+        assert "id" not in data["features"][0].keys()
+        assert "bbox" not in data["features"][0].keys()
+
+    @pytest.mark.skipif(not compat.HAS_PYPROJ, reason="Requires pyproj")
+    def test_to_json_wgs84(self):
+        """
+        Test whether the wgs84 conversion works as intended.
+        """
+        text = self.landmarks3857.to_json(to_wgs84=True)
+        data = json.loads(text)
+        assert data["type"] == "FeatureCollection"
+        assert "id" in data["features"][0].keys()
+        coord1 = data["features"][0]["geometry"]["coordinates"]
+        coord2 = data["features"][1]["geometry"]["coordinates"]
+        np.testing.assert_allclose(coord1, self.esb.coords[0])
+        np.testing.assert_allclose(coord2, self.sol.coords[0])
+
+    def test_to_json_wgs84_false(self):
+        """
+        Ensure no conversion to wgs84
+        """
+        text = self.landmarks3857.to_json()
+        data = json.loads(text)
+        coord1 = data["features"][0]["geometry"]["coordinates"]
+        coord2 = data["features"][1]["geometry"]["coordinates"]
+        assert coord1 == [-8235939.130493107, 4975301.253789809]
+        assert coord2 == [-8242607.167991625, 4966620.938285081]
+
     def test_representative_point(self):
         assert np.all(self.g1.contains(self.g1.representative_point()))
         assert np.all(self.g2.contains(self.g2.representative_point()))