or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

data-analysis.mddata-cleaning.mdfeed-operations.mdgeospatial.mdindex.mdtime-series.mdvalidation.md

geospatial.mddocs/

0

# Geospatial Operations

1

2

Geographic analysis, spatial filtering, and coordinate system transformations.

3

4

## Geometry Conversion

5

6

{ .api }

7

```python

8

def geometrize_stops(stops: pd.DataFrame, *, use_utm: bool = False) -> gpd.GeoDataFrame:

9

"""

10

Convert stops DataFrame to GeoDataFrame with Point geometries.

11

12

Args:

13

stops: Stops DataFrame with stop_lat and stop_lon columns

14

use_utm: If True, convert to appropriate UTM projection for accurate distance calculations

15

16

Returns:

17

GeoDataFrame with Point geometries created from coordinates

18

"""

19

20

def ungeometrize_stops(stops_g: gpd.GeoDataFrame) -> pd.DataFrame:

21

"""

22

Convert stops GeoDataFrame back to regular DataFrame with lat/lon columns.

23

24

Args:

25

stops_g: Stops GeoDataFrame with Point geometries

26

27

Returns:

28

Regular DataFrame with stop_lat and stop_lon columns extracted from geometries

29

"""

30

31

def geometrize_shapes(shapes: pd.DataFrame, *, use_utm: bool = False) -> gpd.GeoDataFrame:

32

"""

33

Convert shapes DataFrame to GeoDataFrame with LineString geometries.

34

35

Args:

36

shapes: Shapes DataFrame with shape_pt_lat, shape_pt_lon, shape_pt_sequence

37

use_utm: If True, convert to appropriate UTM projection

38

39

Returns:

40

GeoDataFrame with LineString geometries for each shape_id

41

"""

42

43

def ungeometrize_shapes(shapes_g: gpd.GeoDataFrame) -> pd.DataFrame:

44

"""

45

Convert shapes GeoDataFrame back to regular DataFrame with lat/lon points.

46

47

Args:

48

shapes_g: Shapes GeoDataFrame with LineString geometries

49

50

Returns:

51

Regular DataFrame with individual shape points and coordinates

52

"""

53

```

54

55

## Spatial Data Access

56

57

{ .api }

58

```python

59

def get_stops(feed: Feed, date: str | None = None, trip_ids: list[str] | None = None,

60

route_ids: list[str] | None = None, *, in_stations: bool = False,

61

as_gdf: bool = False, use_utm: bool = False) -> pd.DataFrame:

62

"""

63

Get stops data with optional filtering and geographic conversion.

64

65

Args:

66

feed: Feed object containing stops data

67

date: Date string to filter by active service (optional)

68

trip_ids: List of trip IDs to filter stops by (optional)

69

route_ids: List of route IDs to filter stops by (optional)

70

in_stations: If True, include parent stations for grouped stops

71

as_gdf: If True, return as GeoDataFrame with Point geometries

72

use_utm: If True and as_gdf=True, convert to UTM projection

73

74

Returns:

75

DataFrame or GeoDataFrame with stops information and optional geometries

76

"""

77

78

def get_shapes(feed: Feed, *, as_gdf: bool = False, use_utm: bool = False) -> pd.DataFrame | None:

79

"""

80

Get shapes data with optional geographic conversion.

81

82

Args:

83

feed: Feed object containing shapes data

84

as_gdf: If True, return as GeoDataFrame with LineString geometries

85

use_utm: If True and as_gdf=True, convert to UTM projection

86

87

Returns:

88

DataFrame, GeoDataFrame, or None if no shapes data exists

89

"""

90

91

def get_routes(feed: Feed, date: str | None = None, time: str | None = None,

92

*, as_gdf: bool = False, use_utm: bool = False,

93

split_directions: bool = False) -> pd.DataFrame:

94

"""

95

Get routes data with optional filtering and shape geometries.

96

97

Args:

98

feed: Feed object containing route data

99

date: Date string to filter by active service (optional)

100

time: Time string to filter by active trips at that time (optional)

101

as_gdf: If True, include route shape geometries

102

use_utm: If True and as_gdf=True, convert to UTM projection

103

split_directions: If True, create separate records for each direction

104

105

Returns:

106

DataFrame or GeoDataFrame with route information and optional geometries

107

"""

108

109

def get_trips(feed: Feed, date: str | None = None, time: str | None = None,

110

*, as_gdf: bool = False, use_utm: bool = False) -> pd.DataFrame:

111

"""

112

Get trips data with optional filtering and shape geometries.

113

114

Args:

115

feed: Feed object containing trip data

116

date: Date string to filter by active service (optional)

117

time: Time string to filter by active trips at that time (optional)

118

as_gdf: If True, include trip shape geometries

119

use_utm: If True and as_gdf=True, convert to UTM projection

120

121

Returns:

122

DataFrame or GeoDataFrame with trip information and optional geometries

123

"""

124

```

125

126

## Spatial Analysis Functions

127

128

{ .api }

129

```python

130

def compute_bounds(feed: Feed, stop_ids: list[str] | None = None) -> np.array:

131

"""

132

Compute bounding box of stops in the feed.

133

134

Args:

135

feed: Feed object containing stops data

136

stop_ids: List of stop IDs to include, or None for all stops

137

138

Returns:

139

Array with [min_lon, min_lat, max_lon, max_lat] bounding box

140

"""

141

142

def compute_convex_hull(feed: Feed, stop_ids: list[str] | None = None) -> sg.Polygon:

143

"""

144

Compute convex hull polygon containing all specified stops.

145

146

Args:

147

feed: Feed object containing stops data

148

stop_ids: List of stop IDs to include, or None for all stops

149

150

Returns:

151

Shapely Polygon representing the convex hull of stop locations

152

"""

153

154

def compute_centroid(feed: Feed, stop_ids: list[str] | None = None) -> sg.Point:

155

"""

156

Compute centroid point of the convex hull of specified stops.

157

158

Args:

159

feed: Feed object containing stops data

160

stop_ids: List of stop IDs to include, or None for all stops

161

162

Returns:

163

Shapely Point representing the centroid of the stop convex hull

164

"""

165

166

def get_stops_in_area(feed: Feed, area: sg.Polygon) -> pd.DataFrame:

167

"""

168

Get all stops that fall within a polygonal area.

169

170

Args:

171

feed: Feed object containing stops data

172

area: Shapely Polygon defining the area boundary

173

174

Returns:

175

DataFrame with stops that intersect the specified area

176

"""

177

178

def get_shapes_intersecting_geometry(feed: Feed, geometry: sg.base.BaseGeometry,

179

shapes_g: gpd.GeoDataFrame | None = None,

180

*, as_gdf: bool = False) -> pd.DataFrame | None:

181

"""

182

Get shapes that intersect with a given geometry.

183

184

Args:

185

feed: Feed object containing shapes data

186

geometry: Shapely geometry to test intersection with

187

shapes_g: Pre-computed shapes GeoDataFrame (optional, will compute if None)

188

as_gdf: If True, return as GeoDataFrame with geometries

189

190

Returns:

191

DataFrame or GeoDataFrame with shapes intersecting the geometry, or None if no shapes

192

"""

193

```

194

195

## Geometry Building Functions

196

197

{ .api }

198

```python

199

def build_geometry_by_shape(feed: Feed, shape_ids: list[str] | None = None,

200

*, use_utm: bool = False) -> dict:

201

"""

202

Build mapping from shape IDs to their LineString geometries.

203

204

Args:

205

feed: Feed object containing shapes data

206

shape_ids: List of shape IDs to include, or None for all shapes

207

use_utm: If True, convert geometries to UTM projection

208

209

Returns:

210

Dictionary mapping shape_id -> Shapely LineString geometry

211

"""

212

213

def build_geometry_by_stop(feed: Feed, stop_ids: list[str] | None = None,

214

*, use_utm: bool = False) -> dict:

215

"""

216

Build mapping from stop IDs to their Point geometries.

217

218

Args:

219

feed: Feed object containing stops data

220

stop_ids: List of stop IDs to include, or None for all stops

221

use_utm: If True, convert geometries to UTM projection

222

223

Returns:

224

Dictionary mapping stop_id -> Shapely Point geometry

225

"""

226

```

227

228

## Feed Spatial Operations

229

230

{ .api }

231

```python

232

def restrict_to_area(feed: Feed, area: sg.Polygon) -> Feed:

233

"""

234

Restrict feed to only trips that intersect with a polygonal area.

235

236

Args:

237

feed: Feed object to restrict

238

area: Shapely Polygon defining the area boundary

239

240

Returns:

241

New Feed object containing only data for trips intersecting the area

242

"""

243

```

244

245

## Distance and Shape Operations

246

247

{ .api }

248

```python

249

def append_dist_to_shapes(feed: Feed) -> Feed:

250

"""

251

Add shape_dist_traveled field to shapes table based on cumulative distances.

252

253

Args:

254

feed: Feed object with shapes data

255

256

Returns:

257

New Feed object with shape_dist_traveled added to shapes table

258

"""

259

260

def append_dist_to_stop_times(feed: Feed) -> Feed:

261

"""

262

Add shape_dist_traveled field to stop_times table based on shape distances.

263

264

Args:

265

feed: Feed object with stop_times and shapes data

266

267

Returns:

268

New Feed object with shape_dist_traveled added to stop_times table

269

"""

270

271

def create_shapes(feed: Feed, *, all_trips: bool = False) -> Feed:

272

"""

273

Create shape geometries by connecting stops in trip order.

274

275

Args:

276

feed: Feed object containing stops and trips data

277

all_trips: If True, create shapes for all trips; if False, only for trips without existing shapes

278

279

Returns:

280

New Feed object with generated shapes table

281

"""

282

```

283

284

## Coordinate System and Units

285

286

{ .api }

287

```python

288

def convert_dist(feed: Feed, new_dist_units: str) -> Feed:

289

"""

290

Convert all distance measurements in feed to new units.

291

292

Args:

293

feed: Feed object to convert

294

new_dist_units: Target distance units ("km", "m", "mi", "ft")

295

296

Returns:

297

New Feed object with distances converted to new units

298

"""

299

300

# Distance utility constants and functions

301

WGS84: str = "EPSG:4326" # Standard geographic coordinate system

302

DIST_UNITS: list = ["ft", "mi", "m", "km"] # Valid distance units

303

304

def is_metric(dist_units: str) -> bool:

305

"""Check if distance units are metric (m or km)."""

306

307

def get_convert_dist(dist_units_in: str, dist_units_out: str) -> Callable:

308

"""Get conversion function between distance units."""

309

```

310

311

## GeoJSON Export Functions

312

313

{ .api }

314

```python

315

def routes_to_geojson(feed: Feed, route_ids: list[str] | None = None,

316

*, split_directions: bool = False, include_stops: bool = False) -> dict:

317

"""

318

Convert routes to GeoJSON format for web mapping.

319

320

Args:

321

feed: Feed object containing routes and shapes data

322

route_ids: List of route IDs to export, or None for all routes

323

split_directions: If True, create separate features for each direction

324

include_stops: If True, include stop markers in the GeoJSON

325

326

Returns:

327

GeoJSON dictionary with route LineString features and optional stop Point features

328

"""

329

330

def stops_to_geojson(feed: Feed, stop_ids: list[str] | None = None) -> dict:

331

"""

332

Convert stops to GeoJSON format for web mapping.

333

334

Args:

335

feed: Feed object containing stops data

336

stop_ids: List of stop IDs to export, or None for all stops

337

338

Returns:

339

GeoJSON dictionary with stop Point features and properties

340

"""

341

342

def trips_to_geojson(feed: Feed, trip_ids: list[str] | None = None,

343

*, include_stops: bool = False) -> dict:

344

"""

345

Convert trips to GeoJSON format showing trip paths.

346

347

Args:

348

feed: Feed object containing trips and shapes data

349

trip_ids: List of trip IDs to export, or None for all trips

350

include_stops: If True, include stop sequences for each trip

351

352

Returns:

353

GeoJSON dictionary with trip LineString features and optional stop sequences

354

"""

355

356

def shapes_to_geojson(feed: Feed, shape_ids: list[str] | None = None) -> dict:

357

"""

358

Convert shapes to GeoJSON format.

359

360

Args:

361

feed: Feed object containing shapes data

362

shape_ids: List of shape IDs to export, or None for all shapes

363

364

Returns:

365

GeoJSON dictionary with shape LineString features

366

"""

367

368

def stop_times_to_geojson(feed: Feed, trip_ids: list[str] | None = None) -> dict:

369

"""

370

Convert stop times to GeoJSON format showing scheduled stops.

371

372

Args:

373

feed: Feed object containing stop_times and stops data

374

trip_ids: List of trip IDs to export stop times for

375

376

Returns:

377

GeoJSON dictionary with stop time Point features and schedule properties

378

"""

379

```

380

381

## Interactive Mapping Functions

382

383

{ .api }

384

```python

385

def map_routes(feed: Feed, route_ids: list[str] | None = None,

386

route_short_names: list[str] | None = None,

387

color_palette: list[str] | None = None, *, show_stops: bool = False) -> None:

388

"""

389

Create interactive Folium map displaying routes.

390

391

Args:

392

feed: Feed object containing routes and shapes data

393

route_ids: List of route IDs to map, or None for all routes

394

route_short_names: List of route short names to map (alternative to route_ids)

395

color_palette: List of colors for route visualization

396

show_stops: If True, display stops along routes

397

398

Returns:

399

Folium map object (displays in Jupyter notebooks)

400

"""

401

402

def map_stops(feed: Feed, stop_ids: list[str] | None = None,

403

stop_style: dict | None = None) -> None:

404

"""

405

Create interactive Folium map displaying stops.

406

407

Args:

408

feed: Feed object containing stops data

409

stop_ids: List of stop IDs to map, or None for all stops

410

stop_style: Dictionary with Leaflet circleMarker style parameters

411

412

Returns:

413

Folium map object showing stop locations and information

414

"""

415

416

def map_trips(feed: Feed, trip_ids: list[str] | None = None,

417

color_palette: list[str] | None = None, *, show_stops: bool = False,

418

show_direction: bool = False) -> None:

419

"""

420

Create interactive Folium map displaying trip paths.

421

422

Args:

423

feed: Feed object containing trips and shapes data

424

trip_ids: List of trip IDs to map, or None for sample of trips

425

color_palette: List of colors for trip visualization

426

show_stops: If True, display stops along trip paths

427

show_direction: If True, show directional arrows on trip paths

428

429

Returns:

430

Folium map object showing trip routes and optional stops/directions

431

"""

432

433

# Map styling constants

434

STOP_STYLE: dict # Default Leaflet circleMarker parameters for stops

435

```

436

437

## Screen Line Analysis

438

439

{ .api }

440

```python

441

def compute_screen_line_counts(feed: Feed, screen_lines: list[sg.LineString],

442

dates: list[str]) -> pd.DataFrame:

443

"""

444

Count trip crossings of screen lines (geographic barriers for transit analysis).

445

446

Args:

447

feed: Feed object containing trip and shape data

448

screen_lines: List of Shapely LineString geometries representing screen lines

449

dates: List of dates to analyze crossings for

450

451

Returns:

452

DataFrame with crossing counts by screen line, route, direction, and date

453

"""

454

```

455

456

## Usage Examples

457

458

### Basic Geometric Operations

459

460

```python

461

import gtfs_kit as gk

462

import geopandas as gpd

463

from shapely.geometry import Point, Polygon

464

465

# Load feed

466

feed = gk.read_feed("data/gtfs.zip")

467

468

# Convert stops to GeoDataFrame

469

stops_gdf = gk.geometrize_stops(feed.stops, use_utm=True)

470

print(f"Created {len(stops_gdf)} stop geometries")

471

print("CRS:", stops_gdf.crs)

472

473

# Convert shapes to GeoDataFrame

474

if feed.shapes is not None:

475

shapes_gdf = gk.geometrize_shapes(feed.shapes, use_utm=True)

476

print(f"Created {len(shapes_gdf)} shape geometries")

477

478

# Convert back to regular DataFrame

479

stops_df = gk.ungeometrize_stops(stops_gdf)

480

print("Converted back to lat/lon coordinates")

481

```

482

483

### Spatial Analysis

484

485

```python

486

# Compute feed bounds and centroid

487

bounds = gk.compute_bounds(feed)

488

print(f"Feed bounds: {bounds}") # [min_lon, min_lat, max_lon, max_lat]

489

490

hull = gk.compute_convex_hull(feed)

491

print(f"Convex hull area: {hull.area}")

492

493

centroid = gk.compute_centroid(feed)

494

print(f"Feed centroid: ({centroid.x}, {centroid.y})")

495

496

# Analyze specific area

497

area_of_interest = Polygon([

498

(bounds[0], bounds[1]),

499

(bounds[2], bounds[1]),

500

(bounds[2], bounds[3]),

501

(bounds[0], bounds[3])

502

])

503

504

stops_in_area = gk.get_stops_in_area(feed, area_of_interest)

505

print(f"Stops in area: {len(stops_in_area)}")

506

```

507

508

### Spatial Data Retrieval

509

510

```python

511

# Get stops as GeoDataFrame

512

sample_date = gk.get_dates(feed)[0]

513

stops_gdf = gk.get_stops(feed, date=sample_date, as_gdf=True, use_utm=False)

514

print(f"Active stops on {sample_date}: {len(stops_gdf)}")

515

516

# Get routes with geometries

517

routes_gdf = gk.get_routes(feed, date=sample_date, as_gdf=True, split_directions=True)

518

if len(routes_gdf) > 0:

519

print(f"Active routes: {len(routes_gdf)}")

520

print("Route geometry types:", routes_gdf.geometry.geom_type.value_counts())

521

522

# Get trips with shapes

523

trips_gdf = gk.get_trips(feed, date=sample_date, as_gdf=True)

524

trips_with_shapes = trips_gdf[~trips_gdf.geometry.isna()]

525

print(f"Trips with shapes: {len(trips_with_shapes)}")

526

```

527

528

### Feed Spatial Restriction

529

530

```python

531

# Define area of interest (e.g., city center)

532

city_center = Point(centroid.x, centroid.y).buffer(0.01) # ~1km buffer

533

534

# Restrict feed to area

535

area_feed = gk.restrict_to_area(feed, city_center)

536

print(f"Original feed: {len(feed.trips)} trips")

537

print(f"Restricted feed: {len(area_feed.trips)} trips")

538

539

# Compare service coverage

540

original_stops = len(feed.stops)

541

restricted_stops = len(area_feed.stops)

542

print(f"Stops: {original_stops} -> {restricted_stops} ({restricted_stops/original_stops:.1%})")

543

```

544

545

### Shape Analysis and Creation

546

547

```python

548

# Add distance information to shapes

549

if feed.shapes is not None:

550

feed_with_dists = gk.append_dist_to_shapes(feed)

551

print("Added shape_dist_traveled to shapes")

552

553

# Also add to stop_times

554

feed_with_dists = gk.append_dist_to_stop_times(feed_with_dists)

555

print("Added shape_dist_traveled to stop_times")

556

else:

557

# Create shapes from stop connections

558

feed_with_shapes = gk.create_shapes(feed, all_trips=True)

559

print(f"Created shapes for {len(feed_with_shapes.shapes)} shape sequences")

560

```

561

562

### Geometry Mapping and Intersection

563

564

```python

565

# Build geometry mappings

566

shape_geometries = gk.build_geometry_by_shape(feed, use_utm=True)

567

stop_geometries = gk.build_geometry_by_stop(feed, use_utm=True)

568

569

print(f"Built geometries for {len(shape_geometries)} shapes")

570

print(f"Built geometries for {len(stop_geometries)} stops")

571

572

# Find shapes intersecting with area

573

if feed.shapes is not None:

574

intersecting_shapes = gk.get_shapes_intersecting_geometry(feed, city_center, as_gdf=True)

575

if intersecting_shapes is not None:

576

print(f"Shapes intersecting area: {len(intersecting_shapes)}")

577

```

578

579

### Distance Unit Conversion

580

581

```python

582

# Check current units

583

print(f"Current distance units: {feed.dist_units}")

584

585

# Convert to different units

586

feed_metric = gk.convert_dist(feed, "km")

587

print(f"Converted to: {feed_metric.dist_units}")

588

589

# Convert to imperial

590

feed_imperial = gk.convert_dist(feed, "mi")

591

print(f"Converted to: {feed_imperial.dist_units}")

592

593

# Verify conversion functions

594

is_metric_km = gk.is_metric("km")

595

is_metric_mi = gk.is_metric("mi")

596

print(f"km is metric: {is_metric_km}, mi is metric: {is_metric_mi}")

597

```

598

599

### GeoJSON Export

600

601

```python

602

# Export routes to GeoJSON

603

routes_geojson = gk.routes_to_geojson(feed, split_directions=True, include_stops=True)

604

print(f"Routes GeoJSON has {len(routes_geojson['features'])} features")

605

606

# Export specific stops

607

major_stops = gk.get_stops(feed, as_gdf=True).nlargest(10, 'stop_id')['stop_id'].tolist()

608

stops_geojson = gk.stops_to_geojson(feed, stop_ids=major_stops)

609

610

# Export trip paths

611

sample_trips = feed.trips['trip_id'].head(5).tolist()

612

trips_geojson = gk.trips_to_geojson(feed, trip_ids=sample_trips, include_stops=True)

613

614

# Export shapes

615

if feed.shapes is not None:

616

shapes_geojson = gk.shapes_to_geojson(feed)

617

print(f"Shapes GeoJSON has {len(shapes_geojson['features'])} features")

618

619

# Export stop times for specific trips

620

stop_times_geojson = gk.stop_times_to_geojson(feed, trip_ids=sample_trips)

621

```

622

623

### Interactive Mapping

624

625

```python

626

# Create route map

627

gk.map_routes(feed,

628

route_ids=None, # All routes

629

color_palette=['red', 'blue', 'green', 'orange', 'purple'],

630

show_stops=True)

631

632

# Map specific stops with custom styling

633

custom_style = {

634

'radius': 8,

635

'fillColor': 'blue',

636

'color': 'black',

637

'weight': 2,

638

'fillOpacity': 0.7

639

}

640

641

gk.map_stops(feed,

642

stop_ids=major_stops,

643

stop_style=custom_style)

644

645

# Map trip paths with directions

646

gk.map_trips(feed,

647

trip_ids=sample_trips,

648

color_palette=['red', 'blue', 'green'],

649

show_stops=True,

650

show_direction=True)

651

652

# Use default stop styling

653

print("Default stop style:", gk.STOP_STYLE)

654

```

655

656

### Screen Line Analysis

657

658

```python

659

from shapely.geometry import LineString

660

661

# Define screen lines (e.g., major barriers like rivers or highways)

662

screen_line1 = LineString([(centroid.x - 0.01, centroid.y - 0.01),

663

(centroid.x + 0.01, centroid.y + 0.01)])

664

screen_line2 = LineString([(centroid.x - 0.01, centroid.y + 0.01),

665

(centroid.x + 0.01, centroid.y - 0.01)])

666

667

screen_lines = [screen_line1, screen_line2]

668

669

# Analyze crossings

670

dates = gk.get_dates(feed)[:7] # First week

671

crossings = gk.compute_screen_line_counts(feed, screen_lines, dates)

672

673

if len(crossings) > 0:

674

print("Screen line crossings:")

675

print(crossings.groupby(['screen_line_id', 'date'])['num_crossings'].sum())

676

```