Testing survey as an extension of geopandas

  1. Kaufmann, 2024.

[1]:
import pandas as pd
import geopandas as gpd
from shapely import Point, LineString
from geometron.survey import TopoPoint, TopoLine, TopoObject
from geometron.geometries import features_to_gdf, gdf_to_features
from geometron.plot import plot_point, plot_line, plot_profile, plot_gdf_survey, plot_basemap
[2]:
@pd.api.extensions.register_dataframe_accessor("survey")
class SurveyAccessor:
    def __init__(self, gdf):
        self.__required_index = ('id', 'object')
        self.__required_fields = {'class': 'object', 'kind': 'object', 'label': 'object', 'show_label': 'boolean', 'geometry': 'geometry'}
        self._validate(gdf)
        self._gdf = gdf
        self._name = ''
        self._units = 'm'

    def _validate(self, gdf):
        # verify that required index and fields are present have the right type
        if gdf.index.name!=self.__required_index[0]:
            raise IndexError(f'An index named {self.__required_index[0]} is expected for surveys...')
        elif gdf.index.dtype!=self.__required_index[1]:
            raise IndexError(f'Wrong dtype. The type of index should be {self.__required_index[1]} not {gdf.index.dtype}.')
        for col in list(self.__required_fields.keys()):
            if col not in gdf.columns:
                raise AttributeError(f'Missing {col} in columns...')
            elif gdf[col].dtype != self.__required_fields[col]:
                raise AttributeError(f'Wrong dtype. The type of column {col} should be {self.__required_fields[col]} not {gdf[col].dtype}.')

        self._gdf = gdf.loc[:, list(self.__required_fields.keys())].astype(self.__required_fields)
        return self._gdf

    def copy(self):
        return self._gdf

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        assert isinstance(value, str)
        self._name = value

    def __getitem__(self, item):
        f = self._gdf.query(f'index=="{item}"').copy()
        return gdf_to_features(f)

    def add_features(self, features, **kwargs):
        """ Adds features to the current survey
            Parameters:
            -----------
            features: single object with __geo_interface__ | iterable of objects with __geo_interface__
                features to add to the survey

            Returns:
            --------
            geopandas.GeoDataFrame
                the updated survey

        """
        if not hasattr(features, '__iter__'):
            features = [features]
        gdf = features_to_gdf(features=features, **kwargs)
        gdf = pd.concat([self._gdf.survey.copy(), gdf], ignore_index=False)
        # copy survey properties
        gdf.survey.name = self.name
        return gdf

    def to_features(self):
        pass

    def get(self, id):
        pass

    def plot(self, **kwargs):
        ax = plot_gdf_survey(self._gdf, **kwargs)
        return ax
[3]:
def create_survey(feature_list=None, **kwargs):
    if feature_list is None:
        gdf = gpd.GeoDataFrame(columns=['id', 'class', 'kind', 'label', 'show_label', 'geometry'], geometry='geometry')
        gdf = gdf.astype({'id': 'object', 'class': 'object', 'kind': 'object', 'label': 'object', 'show_label': 'boolean', 'geometry': 'geometry'})
        gdf.set_index('id', inplace=True)
        return gdf
    elif not hasattr(feature_list, '__iter__'):
        feature_list = [feature_list]
    print([f.__geo_interface__ for f in feature_list])
    gdf = features_to_gdf(feature_list, **kwargs)
    return gdf, [f.__geo_interface__ for f in feature_list]
[4]:
L001 = TopoPoint('POINT (0. 0.)', id='L001', kind='landmark')
L002 = TopoPoint('POINT (50. 0.)', id='L002', kind='landmark')
B01 = TopoLine(LineString([L001.geometry, L002.geometry]), id='B01', kind='baseline', show_label=False)
[5]:
B01.__geo_interface__
[5]:
{'type': 'Feature',
 'properties': {'id': 'B01',
  'class': 'TopoLine',
  'kind': 'baseline',
  'label': 'B01',
  'show_label': False},
 'geometry': {'type': 'LineString', 'coordinates': ((0.0, 0.0), (50.0, 0.0))}}
[6]:
s, f_list = create_survey([L001, L002, B01])
[{'type': 'Feature', 'properties': {'id': 'L001', 'class': 'TopoPoint', 'kind': 'landmark', 'label': 'L001', 'show_label': True}, 'geometry': {'type': 'Point', 'coordinates': (0.0, 0.0)}}, {'type': 'Feature', 'properties': {'id': 'L002', 'class': 'TopoPoint', 'kind': 'landmark', 'label': 'L002', 'show_label': True}, 'geometry': {'type': 'Point', 'coordinates': (50.0, 0.0)}}, {'type': 'Feature', 'properties': {'id': 'B01', 'class': 'TopoLine', 'kind': 'baseline', 'label': 'B01', 'show_label': False}, 'geometry': {'type': 'LineString', 'coordinates': ((0.0, 0.0), (50.0, 0.0))}}]
[7]:
s
[7]:
geometry class kind label show_label
id
L001 POINT (0 0) TopoPoint landmark L001 True
L002 POINT (50 0) TopoPoint landmark L002 True
B01 LINESTRING (0 0, 50 0) TopoLine baseline B01 False
[8]:
ax = s.survey.plot()
square grid step: 10.0
../../_images/examples_geophysics_test_survey_by_extending_geopandas_8_1.png
[9]:
s.survey.name = 'test_survey'
[10]:
u = s.survey.copy()
u.survey.name
[10]:
'test_survey'
[11]:
B01_copy, crs = s.survey['B01']
crs
feature properties: {'class': 'TopoLine', 'kind': 'baseline', 'label': 'B01', 'show_label': False, 'id': 'B01'}
[12]:
B01_copy.show_label=True
B01_copy.plot()
../../_images/examples_geophysics_test_survey_by_extending_geopandas_12_0.png
[13]:
s.set_crs('epsg:3857', inplace=True)
[13]:
geometry class kind label show_label
id
L001 POINT (0 0) TopoPoint landmark L001 True
L002 POINT (50 0) TopoPoint landmark L002 True
B01 LINESTRING (0 0, 50 0) TopoLine baseline B01 False
[14]:
features, crs = gdf_to_features(s)
features, crs
feature properties: {'class': 'TopoPoint', 'kind': 'landmark', 'label': 'L001', 'show_label': True, 'id': 'L001'}
feature properties: {'class': 'TopoPoint', 'kind': 'landmark', 'label': 'L002', 'show_label': True, 'id': 'L002'}
feature properties: {'class': 'TopoLine', 'kind': 'baseline', 'label': 'B01', 'show_label': False, 'id': 'B01'}
[14]:
([<geometron.survey.survey.TopoPoint at 0x7fc624db8dc0>,
  <geometron.survey.survey.TopoPoint at 0x7fc624dc6dd0>,
  <geometron.survey.survey.TopoLine at 0x7fc624dc45e0>],
 <Projected CRS: EPSG:3857>
 Name: WGS 84 / Pseudo-Mercator
 Axis Info [cartesian]:
 - X[east]: Easting (metre)
 - Y[north]: Northing (metre)
 Area of Use:
 - name: World between 85.06°S and 85.06°N.
 - bounds: (-180.0, -85.06, 180.0, 85.06)
 Coordinate Operation:
 - name: Popular Visualisation Pseudo-Mercator
 - method: Popular Visualisation Pseudo Mercator
 Datum: World Geodetic System 1984 ensemble
 - Ellipsoid: WGS 84
 - Prime Meridian: Greenwich)
[15]:
features_to_gdf(features, crs=crs)
[15]:
geometry class kind label show_label
id
L001 POINT (0 0) TopoPoint landmark L001 True
L002 POINT (50 0) TopoPoint landmark L002 True
B01 LINESTRING (0 0, 50 0) TopoLine baseline B01 False
[16]:
L003 = TopoPoint('POINT (20. 20.)', id='L003', kind='landmark')
L004 = TopoPoint('POINT (35. 15.)', id='L004', kind='landmark')
B02 = TopoLine(LineString([L003.geometry, L004.geometry]), id='B02', kind='baseline', show_label=False)
[17]:
s = s.survey.add_features([L003, L004])
/home/su530201/.local/share/virtualenvs/geometron-VhFx4lLH/lib/python3.10/site-packages/geopandas/array.py:1638: UserWarning: CRS not set for some of the concatenation inputs. Setting output's CRS as WGS 84 / Pseudo-Mercator (the single non-null crs provided).
  return GeometryArray(data, crs=_get_common_crs(to_concat))
[18]:
s.survey.name
[18]:
'test_survey'
[19]:
s.survey.plot()
square grid step: 10.0
[19]:
<Axes: >
../../_images/examples_geophysics_test_survey_by_extending_geopandas_19_2.png
[20]:
s.survey.name
[20]:
'test_survey'
[21]:
s = s.survey.add_features(B02, crs='epsg:3857')
[22]:
s.survey.plot()
square grid step: 10.0
[22]:
<Axes: >
../../_images/examples_geophysics_test_survey_by_extending_geopandas_22_2.png
[23]:
s.survey['B01']
feature properties: {'class': 'TopoLine', 'kind': 'baseline', 'label': 'B01', 'show_label': False, 'id': 'B01'}
[23]:
(<geometron.survey.survey.TopoLine at 0x7fc62283e860>,
 <Projected CRS: EPSG:3857>
 Name: WGS 84 / Pseudo-Mercator
 Axis Info [cartesian]:
 - X[east]: Easting (metre)
 - Y[north]: Northing (metre)
 Area of Use:
 - name: World between 85.06°S and 85.06°N.
 - bounds: (-180.0, -85.06, 180.0, 85.06)
 Coordinate Operation:
 - name: Popular Visualisation Pseudo-Mercator
 - method: Popular Visualisation Pseudo Mercator
 Datum: World Geodetic System 1984 ensemble
 - Ellipsoid: WGS 84
 - Prime Meridian: Greenwich)
[24]:
s.query('kind=="baseline"').survey.plot()
square grid step: 10.0
[24]:
<Axes: >
../../_images/examples_geophysics_test_survey_by_extending_geopandas_24_2.png
[25]:
ax = s.survey.plot()
square grid step: 10.0
../../_images/examples_geophysics_test_survey_by_extending_geopandas_25_1.png
[26]:
L005 = TopoPoint('POINT (30. 30.)', id='L005', kind='landmark')
B03 = TopoLine(LineString([L001.geometry, L005.geometry]), id='B03', kind='baseline', show_label=False)
[27]:
s = s.survey.add_features([L005, B03])
/home/su530201/.local/share/virtualenvs/geometron-VhFx4lLH/lib/python3.10/site-packages/geopandas/array.py:1638: UserWarning: CRS not set for some of the concatenation inputs. Setting output's CRS as WGS 84 / Pseudo-Mercator (the single non-null crs provided).
  return GeometryArray(data, crs=_get_common_crs(to_concat))
[28]:
s.survey.plot()
square grid step: 10.0
[28]:
<Axes: >
../../_images/examples_geophysics_test_survey_by_extending_geopandas_28_2.png
[29]:
s.query('kind=="landmark"').survey.plot()
square grid step: 10.0
[29]:
<Axes: >
../../_images/examples_geophysics_test_survey_by_extending_geopandas_29_2.png