How to Calculate Street Trees per Kilometre of Street

On this page

Problem statement

"How many street trees does this neighbourhood have?" is the wrong question for comparing places, because neighbourhoods differ in size and in how much street they contain. Street trees can only grow along streets, so the natural denominator is the length of street: trees per kilometre. Getting it needs a street network clipped to the same areas as the trees, in metres, with only real streets in it — not highways, ramps, paths or the outline of the shore.

This page computes trees per kilometre of street for New York's 2020 neighbourhoods from the 2015 tree census and the city's street centreline, and compares it with the simpler trees per square kilometre.

Quick answer

Keep street segments of the centreline, clip them to neighbourhood polygons with overlay, sum their lengths in a metric CRS, count trees per neighbourhood with a spatial join, and divide:

import geopandas as gpd
from shapely import wkt

cl = pd.read_csv("data/nyc-centerline-2026-10-15.zip", usecols=["the_geom", "RW_TYPE", "STATUS", "NONPED"], low_memory=False)
cl = gpd.GeoDataFrame(cl, geometry=cl.the_geom.map(wkt.loads), crs=4326).to_crs(32118)
streets = cl[(cl.RW_TYPE == 1) & (cl.STATUS == 2) & (cl.NONPED.isna())]
ntas = pd.read_csv("data/nyc-nta-2020-2026-10-15.zip").rename(columns=str.lower)
ntas = gpd.GeoDataFrame(ntas, geometry=ntas.the_geom.map(wkt.loads), crs=4326).to_crs(32118)
ntas = ntas[ntas.ntatype == 0][["nta2020", "ntaname", "boroname", "geometry"]]
seg = gpd.overlay(streets[["geometry"]], ntas, how="intersection", keep_geom_type=True)
km = seg.assign(km=seg.length / 1000).groupby("nta2020").km.sum()
alive = trees[trees.status == "Alive"]
pts = gpd.GeoDataFrame(alive, geometry=gpd.points_from_xy(alive.x_sp, alive.y_sp), crs=2263).to_crs(32118)
n = gpd.sjoin(pts[["geometry"]], ntas, predicate="within").groupby("nta2020").size()
dens = ntas.set_index("nta2020").assign(street_km=km, trees=n)
dens["per_km"] = dens.trees / dens.street_km
print(f"{dens.trees.sum():,.0f} trees on {dens.street_km.sum():,.0f} km of street in {len(dens)} residential neighbourhoods")
print(f"trees per km of street: city {dens.trees.sum() / dens.street_km.sum():.0f}, neighbourhood median {dens.per_km.median():.0f}")
print(dens.sort_values("per_km")[["ntaname", "boroname", "street_km", "trees", "per_km"]].iloc[[0, 1, -2, -1]].round(0).to_string())
643,943 trees on 9,767 km of street in 197 residential neighbourhoods
trees per km of street: city 66, neighbourhood median 66
                                     ntaname   boroname  street_km  trees  per_km
nta2020                                                                          
MN0502                  Midtown-Times Square  Manhattan       40.0   1033    26.0
MN0501   Midtown South-Flatiron-Union Square  Manhattan       24.0    702    29.0
MN0803             Upper East Side-Yorkville  Manhattan       19.0   2133   111.0
MN0702             Upper West Side (Central)  Manhattan       37.0   4297   116.0

643,943 trees stand along 9,767 km of street in the city's 197 residential neighbourhoods: 66 per kilometre, and the median neighbourhood has the same. The extremes are both in Manhattan. Midtown–Times Square has 26 trees per km and Midtown South–Flatiron 29; the Upper West Side and Upper East Side–Yorkville have 116 and 111, more than four times as many on similar streets.

Choropleth of New York's 2020 residential neighbourhoods by street trees per kilometre of street: low values in Midtown Manhattan and industrial areas, high values on the Upper East and West Sides and in parts of Staten Island. Choropleth of New York's 2020 residential neighbourhoods by street trees per kilometre of street: low values in Midtown Manhattan and industrial areas, high values on the Upper East and West Sides and in parts of Staten Island.
Street trees per kilometre of street, 2020 residential NTAs.

Streets, metres and polygons

1. Which centreline segments are streets

print("segments by roadway type (1 = street):", cl.RW_TYPE.value_counts().head(6).to_dict())
print("non-pedestrian flag:", cl.NONPED.value_counts(dropna=False).to_dict())
print(f"kept: {len(streets):,} of {len(cl):,} segments, {streets.length.sum() / 1000:,.0f} km")
segments by roadway type (1 = street): {1: 99359, 6: 5989, 2: 4127, 10: 3836, 9: 3570, 3: 3367}
non-pedestrian flag: {nan: 108161, 'V': 10161, 'D': 3991}
kept: 96,545 of 122,313 segments, 10,033 km

The centreline contains every kind of roadway: 99,359 segments are type 1, streets, and the rest are highways, bridges, ramps, paths and other types where nobody plants street trees. 10,161 segments are flagged V and 3,991 D in the non-pedestrian column. Keeping streets that exist and allow pedestrians leaves 96,545 segments and 10,033 km.

2. Measure in metres

print(streets.crs.name, "| units:", streets.crs.axis_info[0].unit_name)
print(f"segment length: median {streets.length.median():.0f} m")
NAD83 / New York Long Island | units: metre
segment length: median 81 m

Everything is reprojected to EPSG:32118, the New York Long Island state plane in metres, so lengths are lengths: a median segment, roughly one block, is 81 m.

3. Clip streets to neighbourhoods

print(f"{len(ntas)} residential neighbourhoods (ntatype 0); street km after clipping {seg.length.sum() / 1000:,.0f}")
print(f"trees not inside any of them: {len(pts) - n.sum():,}")
197 residential neighbourhoods (ntatype 0); street km after clipping 9,767
trees not inside any of them: 8,230

Non-residential areas — parks, airports, cemeteries — are left out, which removes their streets and the 8,230 trees that stand in them or fall outside every residential polygon. overlay cuts segments that cross a boundary, so each kilometre counts in exactly one neighbourhood.

Scatter of trees per square kilometre against trees per kilometre of street for each neighbourhood, coloured by borough; the two rise together, with Manhattan neighbourhoods highest on both. Scatter of trees per square kilometre against trees per kilometre of street for each neighbourhood, coloured by borough; the two rise together, with Manhattan neighbourhoods highest on both.
Two denominators for the same trees (rank correlation 0.86).

4. Per kilometre or per square kilometre

dens["per_km2"] = dens.trees / (dens.geometry.area / 1e6)
print(f"rank correlation of trees per street km and per square km: {dens.per_km.corr(dens.per_km2, method='spearman'):.2f}")
print(dens.sort_values("per_km2").iloc[[-1]][["ntaname", "per_km", "per_km2"]].round(0).to_string())
rank correlation of trees per street km and per square km: 0.86
                               ntaname  per_km  per_km2
nta2020                                                
MN0802   Upper East Side-Carnegie Hill   107.0   2189.0

Trees per square kilometre ranks neighbourhoods similarly (0.86), but rewards dense street grids: Carnegie Hill has 107 trees per km of street and 2,189 per square kilometre, because its blocks are short and its streets close together. Per kilometre of street is the fairer measure of how well streets are planted; per area is closer to how much canopy a resident lives under.

Boroughs and room for more

The data used on this page

New York City Department of Parks and Recreation, TreesCount! 2015 Street Tree Census on NYC Open Data: one row for every street tree, stump and dead tree mapped by staff and volunteers in 2015–2016, with species, trunk diameter, health and site conditions. Streets are NYC's street centerline (CSCL) and neighbourhoods the 2020 Neighborhood Tabulation Areas, both in copies saved on this site on 15 October 2026 because the city edits them continually.

from pathlib import Path

import numpy as np
import pandas as pd
import requests

Path("data").mkdir(exist_ok=True)
SOCRATA = "https://data.cityofnewyork.us/api/views/{}/rows.csv?accessType=DOWNLOAD"
f = Path("data/nyc-street-tree-census-2015.csv")
if not f.exists():
    # TreesCount! 2015, the finished census (220 MB), NYC Open Data dataset uvpi-gqnh
    f.write_bytes(requests.get(SOCRATA.format("uvpi-gqnh"), timeout=1800).content)
# NYC's centreline and 2020 neighbourhood files are edited continually; these are the copies of 15 October 2026
# (live: NYC Open Data datasets inkn-q76z and 9nt8-h7nd).
for name in ["nyc-centerline-2026-10-15.zip", "nyc-nta-2020-2026-10-15.zip"]:
    if not Path(f"data/{name}").exists():
        Path(f"data/{name}").write_bytes(requests.get(f"https://data.spatialworkflow.io/{name}", timeout=600).content)
trees = pd.read_csv(f, low_memory=False)
print(f"{len(trees):,} trees, {trees.status.eq('Alive').sum():,} alive")
683,788 trees, 652,173 alive

Example 1: by borough

bor = dens.groupby("boroname")[["trees", "street_km"]].sum()
bor["per_km"] = bor.trees / bor.street_km
print(bor.round(0).astype(int).to_string())
                trees  street_km  per_km
boroname                                
Bronx           78712       1281      61
Brooklyn       167539       2522      66
Manhattan       61184        815      75
Queens         235378       3646      65
Staten Island  101130       1503      67

By borough the differences are small: Manhattan has 75 trees per km of street, the Bronx 61, the others 65–67. The variation is within boroughs, between neighbourhoods.

Close-up map of East Williamsburg in Brooklyn showing street centrelines and street trees as dots: residential blocks lined with trees and an industrial area with streets but almost no trees. Close-up map of East Williamsburg in Brooklyn showing street centrelines and street trees as dots: residential blocks lined with trees and an industrial area with streets but almost no trees.
The two inputs together: centreline streets and the trees along them.

Example 2: how full could the streets be

spacing_m = 1000 / dens.per_km
print(f"implied spacing: median {spacing_m.median():.0f} m between trees per km of centreline")
print(f"neighbourhoods under 40 trees per km: {(dens.per_km < 40).sum()}; over 100: {(dens.per_km > 100).sum()}")
implied spacing: median 15 m between trees per km of centreline
neighbourhoods under 40 trees per km: 8; over 100: 7

Sixty-six trees per kilometre of centreline means one tree every 15 m — counting both sides of the street, so 30 m apart on each side in a typical neighbourhood. 8 neighbourhoods have fewer than 40 trees per km and 7 more than 100. How to find gaps in street tree planting finds where the missing trees would go.

Choosing the denominator

A rate is only as fair as its denominator. Population, area and street length each answer a different question: trees per resident is about who benefits, trees per area about canopy, trees per kilometre of street about how fully the planting sites are used. Street trees live on streets, and a neighbourhood with long industrial streets and few residents should not look well planted because it has few people, nor poorly planted because it is large.

The work is in making the denominator honest. The centreline is a model of every roadway the city maintains, so it must be filtered to the kind of street a tree could stand on, cut at the neighbourhood boundaries, and measured in a projected CRS. Each of those choices is visible in the code and changes the result, which is why the counts of what was kept are printed alongside it.

Pitfalls with per-kilometre rates

  • Filter the centreline to streets; highways and ramps inflate the denominator.
  • Measure in metres. Degrees and survey feet give wrong lengths.
  • Clip segments at boundaries with overlay, or long segments are counted twice.
  • Centreline length counts both sides once. Halve the spacing for one side of the street.
  • Parks have streets and trees too. Decide whether non-residential areas belong in the comparison.

FAQ

How many street trees per kilometre does New York have?

66 per kilometre of street centreline across the 197 residential neighbourhoods, from 26 in Midtown–Times Square to 116 on the central Upper West Side.

Should I use trees per km of street or per square kilometre?

Per km of street for how fully streets are planted; per square kilometre for canopy. They rank New York's neighbourhoods similarly (0.86), but dense street grids score higher per area.