Repository: geopandas/geopandas

Issue:
BUG: `GeoSeries` constructor will change the CRS of inputting data
- [ ] I have checked that this issue has not already been reported.

- [x] I have confirmed this bug exists on the latest version of geopandas.

- [x] (optional) I have confirmed this bug exists on the main branch of geopandas.

---

**Note**: Please read [this guide](https://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports) detailing how to provide the necessary information for us to reproduce your bug.

#### Code Sample, a copy-pastable example

```python
>>> import pandas as pd
>>> from shapely.geometry import Point
>>> import geopandas as gpd
>>> gs = gpd.GeoSeries([Point(1, 1), Point(2, 2), Point(3, 3)])
>>> s = pd.Series(gs)
>>> gs.crs
None
>>> gs_another = gpd.GeoSeries(s, crs=4326)
>>> s.values.crs  # should be `None`
epsg:4326
>>> gs.crs  # should be `None`
epsg:4326
>>> gs_another.crs
epsg:4326
```

#### Problem description

When I input a `Series(GeoSeries(...))` data into a `GeoSeries(data)` constructor.
This would directly mutate the inner `GeoSeries(...)` attributes of `Series(GeoSeries(...))`.

#### Expected Output

```python
>>> gs = gpd.GeoSeries([Point(1, 1), Point(2, 2), Point(3, 3)])
>>> s = pd.Series(gs)
>>> gs.crs
None
>>> gs_another = gpd.GeoSeries(s, crs=4326)
>>> s.values.crs
None
>>> gs.crs
None
>>> gs_another.crs
epsg:4326
```

#### Reason may cause this problem

When `data` is a `Series(GeometryArray)` or `Series(GeoSeries)` type, the following lines wouldn't work.
The CRS is still passed to `GeometryArray`.

https://github.com/geopandas/geopandas/blob/28462f8e7fd893e01be52f444e142a1a00c36f8e/geopandas/geoseries.py#L153-L156

#### Output of ``geopandas.show_versions()``

<details>

SYSTEM INFO
-----------
python     : 3.9.13 | packaged by conda-forge | (main, May 27 2022, 16:50:36) [MSC v.1929 64 bit (AMD64)]
executable : C:\Users\zero\mambaforge\envs\work\python.exe
machine    : Windows-10-10.0.19044-SP0

GEOS, GDAL, PROJ INFO
---------------------
GEOS       : None
GEOS lib   : None
GDAL       : 3.5.0
GDAL data dir: None
PROJ       : 9.0.1
PROJ data dir: C:\Users\zero\mambaforge\envs\work\Library\share\proj

PYTHON DEPENDENCIES
-------------------
geopandas  : 0.11.0
pandas     : 1.4.2
fiona      : 1.8.21
numpy      : 1.22.4
shapely    : 1.8.2
rtree      : 1.0.0
pyproj     : 3.3.1
matplotlib : 3.5.2
mapclassify: 2.4.3
geopy      : None
psycopg2   : None
geoalchemy2: None
pyarrow    : None
pygeos     : 0.12.0

</details>


Discussion:
jdmcbr: Looks like this introducing quite a few test failures. From a quick look, some of the failures look like slightly surprising expectations of functionality given the inputs. I'll dig in more soon. 
jorisvandenbossche: > Looks like this introducing quite a few test failures. 

Took a look at those failures, and so the reason is that we were now always copying the data when passed a Series, and not only when a `crs` was specified (and this then interfered with things like just getting a column of the dataframe (`_geodataframe_constructor_sliced`).

We already had a code block about copying in case crs was passed just above, that worked for the case of GeometryArray or GeoSeries input. So I integrated your fix into that code block.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0da3ab12..2aff602e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -98,6 +98,7 @@ Bug fixes:
   geometry column (#2933).
 - Fix bug in `GeoDataFrame` constructor where if `geometry` is given a named
   `GeoSeries` the name was not used as the active geometry column name (#3237).
+- Fix bug in `GeoSeries` constructor when passing a Series and specifying a `crs` to not change the original input data (#2492).
 - Fix regression preventing reading from file paths containing hashes in `read_file`
   with the fiona engine (#3280). An analgous fix for pyogrio is included in
   pyogrio 0.8.1.
diff --git a/geopandas/geoseries.py b/geopandas/geoseries.py
index e650503d..22aec170 100644
--- a/geopandas/geoseries.py
+++ b/geopandas/geoseries.py
@@ -154,8 +154,12 @@ class GeoSeries(GeoPandasBase, Series):
     """
 
     def __init__(self, data=None, index=None, crs: Optional[Any] = None, **kwargs):
-        if hasattr(data, "crs") and crs:
-            if not data.crs:
+        if (
+            hasattr(data, "crs")
+            or (isinstance(data, pd.Series) and hasattr(data.array, "crs"))
+        ) and crs:
+            data_crs = data.crs if hasattr(data, "crs") else data.array.crs
+            if not data_crs:
                 # make a copy to avoid setting CRS to passed GeometryArray
                 data = data.copy()
             else:
diff --git a/geopandas/io/arrow.py b/geopandas/io/arrow.py
index 96647227..dcecf85e 100644
--- a/geopandas/io/arrow.py
+++ b/geopandas/io/arrow.py
@@ -79,6 +79,37 @@ def _remove_id_from_member_of_ensembles(json_dict):
                 member.pop("id", None)
 
 
+# type ids 0 to 7
+_geometry_type_names = [
+    "Point",
+    "LineString",
+    "LineString",
+    "Polygon",
+    "MultiPoint",
+    "MultiLineString",
+    "MultiPolygon",
+    "GeometryCollection",
+]
+_geometry_type_names += [geom_type + " Z" for geom_type in _geometry_type_names]
+
+
+def _get_geometry_types(series):
+    """
+    Get unique geometry types from a GeoSeries.
+    """
+    arr_geometry_types = shapely.get_type_id(series.array._data)
+    # ensure to include "... Z" for 3D geometries
+    has_z = shapely.has_z(series.array._data)
+    arr_geometry_types[has_z] += 8
+
+    geometry_types = Series(arr_geometry_types).unique().tolist()
+    # drop missing values (shapely.get_type_id returns -1 for those)
+    if -1 in geometry_types:
+        geometry_types.remove(-1)
+
+    return sorted([_geometry_type_names[idx] for idx in geometry_types])
+
+
 def _create_metadata(df, schema_version=None):
     """Create and encode geo metadata dict.
 
@@ -106,13 +137,7 @@ def _create_metadata(df, schema_version=None):
     for col in df.columns[df.dtypes == "geometry"]:
         series = df[col]
 
-        # ensure to include "... Z" for 3D geometries
-        geometry_types = {
-            f"{geom} Z" if has_z else geom
-            for geom, has_z in zip(series.geom_type, series.has_z)
-        }
-        geometry_types = sorted(Series(list(geometry_types)).dropna())
-
+        geometry_types = _get_geometry_types(series)
         if schema_version[0] == "0":
             geometry_types_name = "geometry_type"
             if len(geometry_types) == 1:


Test patch:
diff --git a/geopandas/io/tests/test_arrow.py b/geopandas/io/tests/test_arrow.py
index 747fc408..9cfad24b 100644
--- a/geopandas/io/tests/test_arrow.py
+++ b/geopandas/io/tests/test_arrow.py
@@ -141,6 +141,12 @@ def test_create_metadata_with_z_geometries():
         for geom_type in metadata["columns"]["geometry"]["geometry_types"]
     )
 
+    metadata = _create_metadata(df.iloc[5:7])
+    assert metadata["columns"]["geometry"]["geometry_types"] == [
+        "MultiPolygon",
+        "Polygon Z",
+    ]
+
 
 def test_crs_metadata_datum_ensemble():
     pyproj = pytest.importorskip("pyproj")
diff --git a/geopandas/tests/test_geoseries.py b/geopandas/tests/test_geoseries.py
index 68ae7e09..6f809c73 100644
--- a/geopandas/tests/test_geoseries.py
+++ b/geopandas/tests/test_geoseries.py
@@ -658,6 +658,18 @@ class TestConstructor:
         assert s.name == g.name
         assert s.index is g.index
 
+    @pytest.mark.skipif(not HAS_PYPROJ, reason="pyproj not available")
+    def test_from_series_no_set_crs_on_construction(self):
+        # https://github.com/geopandas/geopandas/issues/2492
+        # also when passing Series[geometry], ensure we don't change crs of
+        # original data
+        gs = GeoSeries([Point(1, 1), Point(2, 2), Point(3, 3)])
+        s = pd.Series(gs)
+        result = GeoSeries(s, crs=4326)
+        assert s.values.crs is None
+        assert gs.crs is None
+        assert result.crs == "EPSG:4326"
+
     def test_copy(self):
         # default is to copy with CoW / pandas 3+
         arr = np.array([Point(x, x) for x in range(3)], dtype=object)