Source code for huracanpy.convert._geopandas

from cartopy.crs import Geodetic
from geopandas import GeoDataFrame
import numpy as np
from shapely.geometry import Point, LineString


[docs] def to_geodataframe(lon, lat, track_id=None, *, crs=None): """convert track data to a :class:`geopandas.GeoDataFrame` of geometries Parameters ---------- lon, lat : array_like Longitude and latitude points track_id : array_like, optional Track ID at each point. If track ID is given, each track is treated as a shapely.LineString. Otherwise, each point is a shapely.Point crs : cartopy.crs.Projection, optional The projection of the input data. If None, the data is assumed to be Geodetic. Returns ------- geopandas.GeoDataFrame A GeoDataFrame containing the input tracks a geometries. If track_id is not given the geometries are :class:`shapely.Point`, and if track_id is given the geometries are :class:`shapely.LineString` (split into multiple LineStrings where tracks cross the dateline) or :class:`shapely.Point` for any length-1 tracks """ if crs is None: crs = Geodetic() xyz = Geodetic().transform_points(crs, lon, lat) # Geodetic transform treats -180 as different to 180 which can lead to a wrap around # the globe when it should be not moving. Use -180 as an arbitrary convention xyz[:, 0][xyz[:, 0] == 180] = -180 # Convert tracks to a dictionary with lon, lat points as a geometry # Create the Shapely geometry from lon, lat points if track_id is None: tracks_dict = dict(geometry=[Point(xy) for xy in xyz[:, :2]]) else: track_id = np.asarray(track_id) # Split dateline crossings into two lines dateline_crossing = np.where( (np.abs(np.diff(xyz[:, 0])) > 180) & (track_id[1:] == track_id[:-1]) )[0] # Track offset as elements get input into the array for offset, idx in enumerate(dateline_crossing): # np.insert puts the new value in front of the index so add 1 loc = idx + 2 * offset + 1 track_id = np.insert(track_id, loc, track_id[loc]) track_id = np.insert(track_id, loc, track_id[loc + 1]) y_mid = 0.5 * (xyz[loc - 1, 1] + xyz[loc, 1]) # Order of insertion depends on direction of dateline crossing if xyz[loc - 1, 0] > 0: xyz = np.insert(xyz, loc, np.array([180 - 1e-7, y_mid, 0]), axis=0) xyz = np.insert(xyz, loc + 1, np.array([-180, y_mid, 0]), axis=0) else: xyz = np.insert(xyz, loc, np.array([-180, y_mid, 0]), axis=0) xyz = np.insert(xyz, loc + 1, np.array([180 - 1e-7, y_mid, 0]), axis=0) # Update the location of the dateline crossing to account for the inserted # points dateline_crossing[offset] = loc # All the indices for the start of distinct linestrings # Include the start and end indices indices = np.sort( np.concatenate( [ dateline_crossing + 1, np.where(track_id[1:] != track_id[:-1])[0] + 1, [0, len(track_id)], ] ) ) tracks_dict = dict( track_id=track_id[indices[:-1]], geometry=[ LineString(xyz[indices[n] : indices[n + 1], :2]) if (indices[n + 1] - indices[n]) > 1 else Point(xyz[indices[n], :2]) for n in range(len(indices) - 1) ], ) # Return as a GeoDataFrame return GeoDataFrame(tracks_dict, crs=Geodetic())