Mapping the Urban Forest: Exploring Tree Data in Salinas, California 🌳

GIS
Python
Data Visualization
Author

Olamide Adu

Published

June 15, 2025

Urban trees are more than just greenery along our streets—they provide shade, improve air quality, and shape the character of a city. Thanks to open data initiatives, we can analyze and visualize tree inventories to better understand the composition and health of urban forests.

In this post, we’ll walk through an exploration of the City of Salinas, California’s tree inventory dataset, using Python tools like GeoPandas, Matplotlib, and Contextily to map and categorize the trees.

First we will import the packages used in this post. We can use either uv, pip, conda

Show the code
import pandas as pd
import geopandas as gpd
import contextily as cx
from janitor import drop_constant_columns
import matplotlib.pyplot as plt

Data Importation

The data used in this analysis was downloaded from opendatasoft. We start by loading the tree inventory GeoJSON file with geopandas. Since many datasets contain placeholder values like N/A, we replace them with pandas actual NA value–pd.NA. To simplify the dataset, we also remove constant columns (columns with the same value for all rows).

Show the code
inv_tbl_raw = gpd.read_file("data/tree-inventory-cityofsalinas.geojson")
inv_tbl = inv_tbl_raw.replace("N/A", pd.NA).drop_constant_columns()
inv_tbl.head()
Table 1: Data preview
objectid id uniqueid address street onstr fromstr tostr side site ... spacesize subzone parkquad zone district stmpvcnt globalid suffix geo_point_2d geometry
0 32031 30339 IH 20140709074052 283 CHAPARRAL ST MARYAL DR DEADEND CHAPARRAL ST Right 1 ... 10.0 <NA> <NA> <NA> 4 No {FFA7B88F-19F4-4FA2-9008-C6954AA0EB05} None { "lon": -121.64605930227704, "lat": 36.703282... POINT (-121.64606 36.70328)
1 32070 2496 BS 20140424130746 183 DENNIS AV DENNIS AV MIAMI ST TAMPA ST Front 1 ... 5.0 <NA> <NA> <NA> 2 No {E2350667-2941-42E7-A09B-A90151C81680} None { "lon": -121.60828225058304, "lat": 36.675840... POINT (-121.60828 36.67584)
2 32084 8825 BS 20140516133448 1271 MORENO DR TOWT ST MORENO WY PASEO GRANDE Rear 5 ... 6.0 <NA> <NA> North/East Maintenance District 1 No {37810329-E153-4E5B-9AFE-6F9CFD3C60BD} None { "lon": -121.60240705321014, "lat": 36.687945... POINT (-121.60241 36.68795)
3 32085 8833 BS 20140516134752 1255 MORENO DR TOWT ST MORENO WY PASEO GRANDE Rear 1 ... 7.0 <NA> <NA> North/East Maintenance District 1 No {C56D21E6-D6EA-4F76-941C-5C8CEBC53826} None { "lon": -121.6030825123258, "lat": 36.6876225... POINT (-121.60308 36.68762)
4 32092 9077 BS 20140519122755 1252 MORENO DR MORENO DR CAMARILLO CT CAYUCOS CIR Front 1 ... 10.0 <NA> <NA> North/East Maintenance District 1 No {DBDAD81C-8515-4AD0-A5EA-39291339AADD} None { "lon": -121.60290566856168, "lat": 36.687211... POINT (-121.60291 36.68721)

5 rows × 35 columns

We have interesting variables available in the dataset, but, we do not need all.

Show the code
inv_tbl.columns
Index(['objectid', 'id', 'uniqueid', 'address', 'street', 'onstr', 'fromstr',
       'tostr', 'side', 'site', 'spp', 'dbh', 'stems', 'height', 'width',
       'condstruc', 'condcrwn', 'cond', 'ohutility', 'klir', 'inspect',
       'hdscape', 'vsiblty', 'sound', 'grow', 'spacesize', 'subzone',
       'parkquad', 'zone', 'district', 'stmpvcnt', 'globalid', 'suffix',
       'geo_point_2d', 'geometry'],
      dtype='object')

We’ll only make use of spp, dbh, geometry, and cond .

Show the code
retain_col = [
    "spp", "dbh",
    "stems", "height", 
    "width", "cond", 
    "grow","geometry"
]
inv_tbl = inv_tbl.loc[:, retain_col]
inv_tbl.head()
Table 2: Data retained
spp dbh stems height width cond grow geometry
0 Fraxinus spp. 1.0 1.0 0 to 10 0 to 10 <NA> Tree Lawn POINT (-121.64606 36.70328)
1 Fraxinus oxycarpa 7.0 1.0 21 to 30 21 to 30 Fair Parkway POINT (-121.60828 36.67584)
2 Pyrus calleryana 10.0 1.0 21 to 30 21 to 30 Good Back Up POINT (-121.60241 36.68795)
3 Celtis sinensis 11.0 1.0 21 to 30 21 to 30 Fair Back Up POINT (-121.60308 36.68762)
4 Quercus ilex 11.0 1.0 21 to 30 21 to 30 Good Tree Lawn POINT (-121.60291 36.68721)

Table 2 shows the retained columns. The data is currently using the WGS 84 Geographic coordinate reference system.

Show the code
inv_tbl.crs
<Geographic 2D CRS: EPSG:4326>
Name: WGS 84
Axis Info [ellipsoidal]:
- Lat[north]: Geodetic latitude (degree)
- Lon[east]: Geodetic longitude (degree)
Area of Use:
- name: World.
- bounds: (-180.0, -90.0, 180.0, 90.0)
Datum: World Geodetic System 1984 ensemble
- Ellipsoid: WGS 84
- Prime Meridian: Greenwich

Mapping All Trees

Let’s take a quick view of how the trees are planted in Salinas. We will convert to a mercator projector to work well with contextily.

Show the code
inv_tbl = inv_tbl.to_crs(epsg=3857)

fig, ax = plt.subplots(figsize=(10, 6))
inv_tbl.plot(
    ax=ax, 
    color="red", 
    alpha=0.4, 
    markersize=2
)
cx.add_basemap(ax, source=cx.providers.OpenTopoMap)
plt.axis("off")
plt.tight_layout()
Figure 1: Tree distribution across Salinas. Trees are the red dots

Figure 1 gives us a city-wide snapshot of tree distribution in Salinas and we can tell that Salinas is quite green.

Show the code
inv_tbl.explore(
    column="grow",
    categorical=True, 
)
Make this Notebook Trusted to load map: File -> Trust Notebook