Repository: geopandas/geopandas
Pull request #2571 (2022-09-27 07:21:51)

Update geopandas.show_versions() for GEOS and GDAL
Update `geopandas.show_versions()` to work with Shapely 2, and also to use pyogrio (if fiona is not installed) to detect the GDAL version.


Patch:
diff --git a/.github/workflows/release_to_pypi.yml b/.github/workflows/release_to_pypi.yml
index a5f390c4..6664d04c 100644
--- a/.github/workflows/release_to_pypi.yml
+++ b/.github/workflows/release_to_pypi.yml
@@ -12,10 +12,10 @@ jobs:
 
     steps:
       - name: Checkout source
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3
 
       - name: Set up Python
-        uses: actions/setup-python@v2
+        uses: actions/setup-python@v4
         with:
           python-version: "3.x"
 
@@ -26,7 +26,7 @@ jobs:
           twine check --strict dist/*
 
       - name: Publish distribution to PyPI
-        uses: pypa/gh-action-pypi-publish@master
+        uses: pypa/gh-action-pypi-publish@release/v1
         with:
           user: __token__
           password: ${{ secrets.PYPI_API_TOKEN }}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index adfde9c5..5bbe201a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,16 +4,29 @@ Changelog
 Development version
 -------------------
 
+Version 0.12 (October xx, 2022)
+-------------------------------
+
+The highlight of this release is the support for Shapely 2.0. This makes it possible to
+test Shapely 2.0 (currently 2.0b1) alongside GeoPandas
+
+Note that if you also have PyGEOS installed, you need to set an environment variable
+(`USE_PYGEOS=0`) before importing geopandas to actually test Shapely 2.0 features instead of PyGEOS. See
+https://geopandas.org/en/latest/getting_started/install.html#using-the-optional-pygeos-dependency
+for more details.
+
 New features and improvements:
 
 - Added ``normalize()`` method from shapely to GeoSeries/GeoDataframe (#2537)
-- Add where filter to ``read_file`` (#2552)
+- Add ``where`` filter to ``read_file`` (#2552)
 
 Deprecations and compatibility notes:
+- Accessing the `crs` of a `GeoDataFrame` without active geometry column was deprecated and this now raises an AttributeError (#2578).
 
 Bug fixes:
+- Accessing `gdf.geometry` where the active geometry column is missing, and a column named `"geometry"` is present 
+  will now raise an `AttributeError`, rather than returning `gdf["geometry"]` (#2575) 
 
-Notes on (optional) dependencies:
 
 Version 0.11.1 (July 24, 2022)
 ------------------------------
diff --git a/ci/envs/39-no-optional-deps.yaml b/ci/envs/39-no-optional-deps.yaml
index cb6f036a..04b310a4 100644
--- a/ci/envs/39-no-optional-deps.yaml
+++ b/ci/envs/39-no-optional-deps.yaml
@@ -7,6 +7,7 @@ dependencies:
   - pandas
   - shapely
   - fiona
+  - tiledb=2.11.3
   - pyproj
   - packaging
   # testing
diff --git a/ci/envs/39-pd12-conda-forge.yaml b/ci/envs/39-pd12-conda-forge.yaml
index fc675bf9..c5984661 100644
--- a/ci/envs/39-pd12-conda-forge.yaml
+++ b/ci/envs/39-pd12-conda-forge.yaml
@@ -7,6 +7,7 @@ dependencies:
   - pandas=1.2
   - shapely
   - fiona
+  - tiledb=2.11.3
   - pyproj
   - pygeos
   - packaging
diff --git a/doc/environment.yml b/doc/environment.yml
index 79e3287f..301d10a9 100644
--- a/doc/environment.yml
+++ b/doc/environment.yml
@@ -12,7 +12,7 @@ dependencies:
   - matplotlib
   - mapclassify
   - sphinx
-  - pydata-sphinx-theme=0.9
+  - pydata-sphinx-theme
   - numpydoc
   - ipython
   - pillow
diff --git a/doc/source/about.md b/doc/source/about.md
index 2e2f0616..34cd6a3d 100644
--- a/doc/source/about.md
+++ b/doc/source/about.md
@@ -6,11 +6,11 @@
 :hidden:
 
 Team <about/team>
+Roadmap <about/roadmap>
 Citing <about/citing>
 Logo <about/logo>
 ```
 
-
 GeoPandas is an open source project to add support for geographic data to pandas objects. It
 currently implements `GeoSeries` and `GeoDataFrame` types which are subclasses of
 `pandas.Series` and `pandas.DataFrame` respectively. GeoPandas objects can act on
@@ -25,7 +25,7 @@ under the liberal terms of the BSD-3-Clause license.
 
 ```{container} button
 
-{doc}`Team <about/team>`
+{doc}`Team <about/team>` {doc}`Roadmap <about/roadmap>`
 {doc}`Citing <about/citing>` {doc}`Logo <about/logo>`
 ```
 
@@ -49,5 +49,3 @@ development.
 - **2014**: GeoPandas 0.1.0 released
 - **2020**: GeoPandas became [NumFOCUS Affiliated
   Project](https://numfocus.org/sponsored-projects/affiliated-projects)
-
-
diff --git a/doc/source/about/roadmap.md b/doc/source/about/roadmap.md
index a4ab6d98..298a926a 100644
--- a/doc/source/about/roadmap.md
+++ b/doc/source/about/roadmap.md
@@ -1,5 +1,104 @@
 # Roadmap
 
+This page provides an overview of the strategic goals for development of GeoPandas. Some
+of the tasks may happen sooner given the appropriate funding, other later with no
+specified date, and some may not happen at all if the implementation proves to be
+against the will of the community or face technical issues preventing their inclusion in
+the code base.
+
+The current roadmap is divided into two milestones. The first milestone aims at a
+release of the first major version of GeoPandas, while the second milestone is a
+longer-term vision covering enhancements that should happen in subsequent releases.
+
 ## Roadmap for GeoPandas 1.0
 
-WIP
+### Fully vectorized geometry engine
+
+GeoPandas uses `shapely` as its geometry engine, based on scalar geometries, requiring a
+loop-based implementation of most GeoPandas methods. That comes at a significant
+performance cost, which is being resolved in shapely 2.0, a new major release resulting
+from a complete rewrite of the internals using the vectorized implementation prototyped
+in the `PyGEOS` project. At this moment, GeoPandas supports `shapely<2.0`,
+`shapely>=2.0`, and `PyGEOS` as possible geometry engines, which causes friction in the
+development process and uneven performance on the user side based on what geometry
+engine the user happens to be using.
+
+GeoPandas 1.0 will require `shapely>=2.0` and deprecate both older shapely and `PyGEOS`
+engines. This change should simplify the code base allowing more manageable maintenance
+and a lower barrier to entry for new contributors.
+
+### Feature parity with shapely
+
+Even though GeoPandas uses shapely as the geometry engine, not all its functions are
+exposed at a GeoPandas level. This has resulted in a less convenient API and a need to
+switch between `GeoSeries` objects and lists or arrays of geometries, potentially
+risking the data loss or corruption as the CRS is not included in such operations. In
+the first phase, all element-wise operations (e.g. `segmentize`, or
+`minimum_bounding_circle`) should be exposed as `GeoSeries` methods. The feature parity
+should be reached in the second phase, covering all relevant functions.
+
+### Clarity of the API
+
+The first version of the GeoPandas API is nearly ten years old. The PyData ecosystem has
+significantly changed in the meantime, and some of the early decisions may no longer be
+future-proof. Ahead of GeoPandas 1.0, the API will be revised to ensure that all the
+necessary deprecations occur before the major release to provide the stability of the
+API for the coming years.
+
+### Pruned dependencies
+
+GeoPandas offers functionality for every step of a typical geospatial workflow, from
+reading of the GIS file formats to geometry operations and handling of Coordinate
+Reference Systems (CRS) and transformation of geometries between them. However, GIS I/O
+depends on a relatively heavy C++ library `GDAL` and CRS management on another C++
+library `PROJ`, even though not every application based on GeoPandas is necessarily
+geospatial. GeoPandas 1.0 should eliminate the hard dependency on both `GDAL` and `PROJ`
+and offer the basic capability of a GeoDataFrame with a minimal set of dependencies
+limited to `pandas` and `shapely`.
+
+## Beyond GeoPandas 1.0
+
+Additional work is planned for a longer time frame, stretching beyond GeoPandas 1.0
+without a specific target release.
+
+### S2 geometry engine
+
+The geometry engine used in GeoPandas is `shapely`, which serves as a Python API for
+`GEOS`. It means that all geometry operations in GeoPandas are planar, using (possibly)
+projected coordinate reference systems. Some applications focusing on the global context
+may find planar operations limiting as they come with troubles around anti-meridian and
+poles. One solution is an implementation of a spherical geometry engine, namely `S2`,
+that should eliminate these limitations and offer an alternative to `GEOS`.
+
+The GeoPandas community is currently working together with the R-spatial community that
+has already exposed `S2` in an R counterpart of GeoPandas `sf` on Python bindings for
+`S2`, that should be used as a secondary geometry engine in GeoPandas.
+
+### Lighter-weight geospatial I/O
+
+In order to support lighter-weight installations of GeoPandas that do not depend on
+heavier and difficult to install libraries such as GDAL, additional I/O libraries should
+be developed and integrated into GeoPandas as optional dependencies.  These should be
+simpler to install and not require binary dependencies, which would lower the barrier to
+entry for GeoPandas users that need basic I/O support for a limited number of GIS
+formats such as ESRI Shapefiles or GeoPackages.
+
+### Prepared geometries
+
+GeoPandas is using spatial indexing for the operations that may benefit from it. Further
+performance gains can be achieved using prepared geometries. Preparation creates a
+spatial index of individual line segments of geometries, greatly enhancing the speed of
+spatial predicates like `intersects` or `contains`. Given that the preparation has
+become less computationally expensive in `shapely` 2.0, GeoPandas should expose the
+preparation to the user but, more importantly, use smart automatic geometry preparation
+under the hood.
+
+### Static plotting improvements
+
+GeoPandas currently covers a broad range of geospatial tasks, from data exploration to
+advanced analysis. However, one moment may tempt the user to use different software -
+plotting. GeoPandas can create static maps based on ``matplotlib``, but they are a bit
+basic at the moment. It isn't straightforward to generate a complex map in a
+production-quality which can go straight to an academic journal or an infographic. We
+want to change this and remove barriers which we currently have and make it simple to
+create beautiful maps.
diff --git a/doc/source/docs/user_guide/geometric_manipulations.rst b/doc/source/docs/user_guide/geometric_manipulations.rst
index d29eeb6a..614251c5 100644
--- a/doc/source/docs/user_guide/geometric_manipulations.rst
+++ b/doc/source/docs/user_guide/geometric_manipulations.rst
@@ -18,7 +18,7 @@ Constructive Methods
 .. attribute:: GeoSeries.boundary
 
   Returns a :class:`~geopandas.GeoSeries` of lower dimensional objects representing
-  each geometries's set-theoretic `boundary`.
+  each geometry's set-theoretic `boundary`.
 
 .. attribute:: GeoSeries.centroid
 
diff --git a/doc/source/gallery/spatial_joins.ipynb b/doc/source/gallery/spatial_joins.ipynb
index 81f79a00..cfb80b72 100644
--- a/doc/source/gallery/spatial_joins.ipynb
+++ b/doc/source/gallery/spatial_joins.ipynb
@@ -202,7 +202,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "We're not limited to using the `intersection` binary predicate. Any of the `Shapely` geometry methods that return a Boolean can be used by specifying the `op` kwarg."
+    "We're not limited to using the `intersection` binary predicate. Any of the `Shapely` geometry methods that return a Boolean can be used by specifying the `predicate` kwarg."
    ]
   },
   {
diff --git a/geopandas/_compat.py b/geopandas/_compat.py
index f7673fa4..01f6fb6c 100644
--- a/geopandas/_compat.py
+++ b/geopandas/_compat.py
@@ -79,6 +79,8 @@ def set_use_pygeos(val=None):
     global USE_SHAPELY_20
     global PYGEOS_SHAPELY_COMPAT
 
+    env_use_pygeos = os.getenv("USE_PYGEOS", None)
+
     if val is not None:
         USE_PYGEOS = bool(val)
     else:
@@ -86,7 +88,6 @@ def set_use_pygeos(val=None):
 
             USE_PYGEOS = HAS_PYGEOS
 
-            env_use_pygeos = os.getenv("USE_PYGEOS", None)
             if env_use_pygeos is not None:
                 USE_PYGEOS = bool(int(env_use_pygeos))
 
@@ -133,6 +134,22 @@ def set_use_pygeos(val=None):
         except ImportError:
             raise ImportError(INSTALL_PYGEOS_ERROR)
 
+    if USE_PYGEOS and env_use_pygeos is None and SHAPELY_GE_20:
+        warnings.warn(
+            "Shapely 2.0 is installed, but because PyGEOS is also installed, "
+            "GeoPandas will still use PyGEOS by default for now. To force to use and "
+            "test Shapely 2.0, you have to set the environment variable USE_PYGEOS=0. "
+            "You can do this before starting the Python process, or in your code "
+            "before importing geopandas:"
+            "\n\nimport os\nos.environ['USE_PYGEOS'] = '0'\nimport geopandas\n\n"
+            "In a future release, GeoPandas will switch to using Shapely by default. "
+            "If you are using PyGEOS directly (calling PyGEOS functions on geometries "
+            "from GeoPandas), this will then stop working and you are encouraged to "
+            "migrate from PyGEOS to Shapely 2.0 "
+            "(https://shapely.readthedocs.io/en/latest/migration_pygeos.html).",
+            stacklevel=6,
+        )
+
     USE_SHAPELY_20 = (not USE_PYGEOS) and SHAPELY_GE_20
 
 
diff --git a/geopandas/_vectorized.py b/geopandas/_vectorized.py
index e9646c6a..e5736f2a 100644
--- a/geopandas/_vectorized.py
+++ b/geopandas/_vectorized.py
@@ -998,7 +998,13 @@ def relate(data, other):
 
 def unary_union(data):
     if compat.USE_SHAPELY_20:
-        return shapely.union_all(data)
+        data = shapely.union_all(data)
+        if data is None:  # shapely 2.0a1
+            return None
+        if data.is_empty:  # shapely 2.0
+            return None
+        else:
+            return data
     elif compat.USE_PYGEOS:
         return _pygeos_to_shapely(pygeos.union_all(data))
     else:
diff --git a/geopandas/base.py b/geopandas/base.py
index b5a44502..437a6119 100644
--- a/geopandas/base.py
+++ b/geopandas/base.py
@@ -424,7 +424,7 @@ GeometryCollection
     @property
     def boundary(self):
         """Returns a ``GeoSeries`` of lower dimensional objects representing
-        each geometries's set-theoretic `boundary`.
+        each geometry's set-theoretic `boundary`.
 
         Examples
         --------
diff --git a/geopandas/geodataframe.py b/geopandas/geodataframe.py
index a563c905..bf531fb1 100644
--- a/geopandas/geodataframe.py
+++ b/geopandas/geodataframe.py
@@ -123,8 +123,10 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
     GeoSeries : Series object designed to store shapely geometry objects
     """
 
-    # TODO: remove "_crs" in 0.12
-    _metadata = ["_crs", "_geometry_column_name"]
+    _metadata = ["_geometry_column_name"]
+
+    _internal_names = DataFrame._internal_names + ["geometry"]
+    _internal_names_set = set(_internal_names)
 
     _geometry_column_name = DEFAULT_GEO_COLUMN_NAME
 
@@ -132,9 +134,6 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
         with compat.ignore_shapely2_warnings():
             super().__init__(data, *args, **kwargs)
 
-        # TODO: to be removed in 0.12
-        self._crs = None
-
         # set_geometry ensures the geometry data have the proper dtype,
         # but is not called if `geometry=None` ('geometry' column present
         # in the data), so therefore need to ensure it here manually
@@ -347,9 +346,6 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
         level = _ensure_geometry(level, crs=crs)
         frame[geo_column_name] = level
         frame._geometry_column_name = geo_column_name
-
-        # TODO: to be removed in 0.12
-        frame._crs = level.crs
         if not inplace:
             return frame
 
@@ -431,19 +427,14 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
         GeoDataFrame.to_crs : re-project to another CRS
 
         """
-        # TODO: remove try/except in 0.12
         try:
             return self.geometry.crs
         except AttributeError:
-            # the active geometry column might not be set
-            warnings.warn(
-                "Accessing CRS of a GeoDataFrame without a geometry column is "
-                "deprecated and will be removed in GeoPandas 0.12. "
-                "Use GeoDataFrame.set_geometry to set the active geometry column.",
-                FutureWarning,
-                stacklevel=2,
+            raise AttributeError(
+                "The CRS attribute of a GeoDataFrame without an active "
+                "geometry column is not defined. Use GeoDataFrame.set_geometry "
+                "to set the active geometry column."
             )
-            return self._crs
 
     @crs.setter
     def crs(self, value):
@@ -459,15 +450,11 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
                 self.geometry.values.crs = value
             else:
                 # column called 'geometry' without geometry
-                self._crs = None if not value else CRS.from_user_input(value)
-
-                # TODO: raise this error in 0.12. This already raises a FutureWarning
-                # TODO: defined in the crs property above
-                # raise ValueError(
-                #     "Assigning CRS to a GeoDataFrame without an active geometry "
-                #     "column is not supported. Use GeoDataFrame.set_geometry to set "
-                #     "the active geometry column.",
-                # )
+                raise ValueError(
+                    "Assigning CRS to a GeoDataFrame without an active geometry "
+                    "column is not supported. Use GeoDataFrame.set_geometry to set "
+                    "the active geometry column.",
+                )
 
     def __setstate__(self, state):
         # overriding DataFrame method for compat with older pickles (CRS handling)
@@ -1449,24 +1436,8 @@ individually so that features may have different properties
             if pd.api.types.is_scalar(value) or isinstance(value, BaseGeometry):
                 value = [value] * self.shape[0]
             try:
-                # TODO: remove this use of _crs in 0.12
-                warn = False
-                if not (hasattr(self, "geometry") and hasattr(self.geometry, "crs")):
-                    crs = self._crs
-                    warn = True
-                else:
-                    crs = getattr(self, "crs", None)
+                crs = getattr(self, "crs", None)
                 value = _ensure_geometry(value, crs=crs)
-                if warn and crs is not None:
-                    warnings.warn(
-                        "Setting geometries to a GeoDataFrame without a geometry "
-                        "column will currently preserve the CRS, if present. "
-                        "This is deprecated, and in the future the CRS will be lost "
-                        "in this case. You can use set_crs(..) on the result to "
-                        "set the CRS manually.",
-                        FutureWarning,
-                        stacklevel=2,
-                    )
             except TypeError:
                 warnings.warn("Geometry column does not contain geometry.")
         super().__setitem__(key, value)
diff --git a/geopandas/tools/_show_versions.py b/geopandas/tools/_show_versions.py
index 3d03ca46..c24ab325 100644
--- a/geopandas/tools/_show_versions.py
+++ b/geopandas/tools/_show_versions.py
@@ -48,8 +48,14 @@ def _get_C_info():
         geos_version = "{}.{}.{}".format(*shapely._buildcfg.geos_version)
         geos_dir = shapely._buildcfg.geos_library_path
     except Exception:
-        geos_version = None
-        geos_dir = None
+        try:
+            from shapely import geos_version_string
+
+            geos_version = geos_version_string
+            geos_dir = None
+        except Exception:
+            geos_version = None
+            geos_dir = None
 
     try:
         import fiona
@@ -64,6 +70,22 @@ def _get_C_info():
     except Exception:
         gdal_dir = None
 
+    if gdal_version is None:
+        try:
+            import pyogrio
+
+            gdal_version = pyogrio.__gdal_version_string__
+            gdal_dir = None
+        except Exception:
+            pass
+        try:
+            # get_gdal_data_path is only available in pyogrio >= 0.4.2
+            from pyogrio import get_gdal_data_path
+
+            gdal_dir = get_gdal_data_path()
+        except Exception:
+            pass
+
     blob = [
         ("GEOS", geos_version),
         ("GEOS lib", geos_dir),


Test patch:
diff --git a/ci/envs/310-latest-conda-forge.yaml b/ci/envs/310-latest-conda-forge.yaml
index cbec0b22..48319f9e 100644
--- a/ci/envs/310-latest-conda-forge.yaml
+++ b/ci/envs/310-latest-conda-forge.yaml
@@ -7,6 +7,7 @@ dependencies:
   - pandas
   - shapely
   - fiona
+  - tiledb=2.11.3
   - pyproj
   - pygeos
   - packaging
diff --git a/ci/envs/38-latest-conda-forge.yaml b/ci/envs/38-latest-conda-forge.yaml
index 12c2a27e..b605aab6 100644
--- a/ci/envs/38-latest-conda-forge.yaml
+++ b/ci/envs/38-latest-conda-forge.yaml
@@ -8,6 +8,7 @@ dependencies:
   - shapely
   # - fiona  # build with only pyogrio
   - libgdal
+  - tiledb=2.11.3
   - pyproj
   - pygeos
   - packaging
@@ -27,6 +28,4 @@ dependencies:
   - SQLalchemy
   - libspatialite
   - pyarrow
-  - pip
-  - pip:
-    - pyogrio
+  - pyogrio
diff --git a/ci/envs/39-latest-conda-forge.yaml b/ci/envs/39-latest-conda-forge.yaml
index 6a61ddbb..3475107c 100644
--- a/ci/envs/39-latest-conda-forge.yaml
+++ b/ci/envs/39-latest-conda-forge.yaml
@@ -1,13 +1,14 @@
 name: test
 channels:
   - conda-forge
-  - conda-forge/label/shapely_dev
+  # - conda-forge/label/shapely_dev
 dependencies:
   - python=3.9
   # required
   - pandas=1.3
-  - shapely=2
+  # - shapely=2
   - fiona
+  - tiledb=2.11.3
   - pyproj
   # use this build to have one with only shapely 2.0 installed
   # - pygeos
@@ -34,4 +35,7 @@ dependencies:
   - pyarrow
   # doctest testing
   - pytest-doctestplus
-
+  - pip
+  - pip:
+      - --pre
+      - shapely==2.0b1
diff --git a/geopandas/tests/test_crs.py b/geopandas/tests/test_crs.py
index a587e5ae..1d9200a2 100644
--- a/geopandas/tests/test_crs.py
+++ b/geopandas/tests/test_crs.py
@@ -297,12 +297,14 @@ class TestGeometryArrayCRS:
 
         # geometry column without geometry
         df = GeoDataFrame({"geometry": [0, 1]})
-        with pytest.warns(
-            FutureWarning, match="Accessing CRS of a GeoDataFrame without a geometry"
+        with pytest.raises(
+            ValueError,
+            match="Assigning CRS to a GeoDataFrame without an active geometry",
         ):
             df.crs = 27700
-        with pytest.warns(
-            FutureWarning, match="Accessing CRS of a GeoDataFrame without a geometry"
+        with pytest.raises(
+            AttributeError,
+            match="The CRS attribute of a GeoDataFrame without an active",
         ):
             assert df.crs == self.osgb
 
@@ -310,8 +312,9 @@ class TestGeometryArrayCRS:
         df = GeoDataFrame({"col": range(10)}, geometry=self.arr)
         df["geom2"] = df.geometry.centroid
         subset = df[["col", "geom2"]]
-        with pytest.warns(
-            FutureWarning, match="Accessing CRS of a GeoDataFrame without a geometry"
+        with pytest.raises(
+            AttributeError,
+            match="The CRS attribute of a GeoDataFrame without an active",
         ):
             assert subset.crs == self.osgb
 
@@ -355,17 +358,13 @@ class TestGeometryArrayCRS:
         arr = from_shapely(self.geoms)
         df = GeoDataFrame({"col1": [1, 2], "geometry": arr}, crs=4326)
 
-        # create a dataframe without geometry column, but currently has cached _crs
+        # override geometry with non geometry
         with pytest.warns(UserWarning):
             df["geometry"] = 1
 
-        # assigning a list of geometry object will currently use _crs
-        with pytest.warns(
-            FutureWarning,
-            match="Setting geometries to a GeoDataFrame without a geometry",
-        ):
-            df["geometry"] = self.geoms
-        assert df.crs == self.wgs
+        # assigning a list of geometry object doesn't have cached access to 4326
+        df["geometry"] = self.geoms
+        assert df.crs is None
 
     @pytest.mark.parametrize(
         "scalar", [None, Point(0, 0), LineString([(0, 0), (1, 1)])]
diff --git a/geopandas/tests/test_geodataframe.py b/geopandas/tests/test_geodataframe.py
index db639447..9e3112f6 100644
--- a/geopandas/tests/test_geodataframe.py
+++ b/geopandas/tests/test_geodataframe.py
@@ -361,6 +361,17 @@ class TestDataFrame:
         with pytest.raises(AttributeError, match=msg_no_other_geo_cols):
             GeoDataFrame().geometry
 
+    def test_get_geometry_geometry_inactive(self):
+        # https://github.com/geopandas/geopandas/issues/2574
+        df = self.df.assign(geom2=self.df.geometry).set_geometry("geom2")
+        df = df.loc[:, ["BoroName", "geometry"]]
+        assert df._geometry_column_name == "geom2"
+        msg_geo_col_missing = "is not present. "
+        # Check that df.geometry raises if active geometry column is missing,
+        # it should not fall back to column named "geometry"
+        with pytest.raises(AttributeError, match=msg_geo_col_missing):
+            df.geometry
+
     def test_align(self):
         df = self.df2
 
diff --git a/geopandas/tests/test_merge.py b/geopandas/tests/test_merge.py
index 0de01f34..5794b6ca 100644
--- a/geopandas/tests/test_merge.py
+++ b/geopandas/tests/test_merge.py
@@ -112,11 +112,11 @@ class TestMerging:
         # Note this is not consistent with concat([gdf, gdf], axis=1) where the
         # left metadata is set on the result. This is deliberate for now.
         assert type(result) is GeoDataFrame
-        self._check_metadata(result, geometry_column_name=None, crs=None)
+        assert result._geometry_column_name is None
         assert_index_equal(pd.Index([0, 1]), result.columns)
 
         gseries2.name = "foo"
         result2 = pd.concat([gseries2, self.gseries], axis=1)
         assert type(result2) is GeoDataFrame
-        self._check_metadata(result2, geometry_column_name=None, crs=None)
+        assert result._geometry_column_name is None
         assert_index_equal(pd.Index(["foo", 0]), result2.columns)