You can reproject your points from EPSG:4326 to EPSG:2953 (NAD_1983_CSRS_New_Brunswick_Stereographic) using pyproj like so:
import shapefile
import pyproj
from shapely.geometry import (Point, shape) # shape() is a function to convert geo objects through the interface
from shapely.ops import transform
from_crs = pyproj.CRS('EPSG:4326')
to_crs = pyproj.CRS('EPSG:2953')
transformer = pyproj.Transformer.from_crs(from_crs, to_crs, always_xy=True).transform
shp = shapefile.Reader('NBRoads/geonb_nbrn-rrnb_road-route.shp')
min_distance = 3 # distance where point is considered "on" a road
points_to_check = ((-68.41856,47.28858), (-68.4162, 47.28940)) #example coordinates
all_shapes = shp.shapes()
all_records = shp.records()
for i in range(len(all_shapes)):
boundary = shape(all_shapes[i])
for lonlat in points_to_check:
point_to_check = transform(transformer, Point(lonlat))
if boundary.distance(point_to_check) < min_distance:
name = all_records[i][2]
print("The point is in", name)
If you have fiona
installed, I prefer that to shapefile
as you can read more data types than just shapefiles and the syntax is a bit cleaner:
import fiona
import pyproj
from shapely.geometry import (Point, shape) # shape() is a function to convert geo objects through the interface
from shapely.ops import transform
from_crs = pyproj.CRS('EPSG:4326')
to_crs = pyproj.CRS('EPSG:2953')
transformer = pyproj.Transformer.from_crs(from_crs, to_crs, always_xy=True).transform
shapefile = 'NBRoads/geonb_nbrn-rrnb_road-route.shp'
min_distance = 3 # distance (in metres) where point is considered "on" a road
points_to_check = ((-68.41856, 47.28858), (-68.4162, 47.28940)) # example coordinates
with fiona.open(shapefile) as shp:
for rec in shp:
for lonlat in points_to_check:
point_to_check = transform(transformer, Point(lonlat))
boundary = shape(rec['geometry'])
if boundary.distance(point_to_check) < min_distance:
name = rec['properties']['name']
print("The point is in", name)
You can also use geopandas
which I like, but it has quite a different syntax. This example buffers the points, by the min distance, then uses a geopandas.sjoin
spatial join:
import geopandas as gpd
from shapely.geometry import Point
from_crs = 'EPSG:4326'
to_crs = 'EPSG:2953'
shapefile = 'NBRoads/geonb_nbrn-rrnb_road-route.shp'
min_distance = 3 # distance (in metres) where point is considered "on" a road
points_to_check = ((-68.41856, 47.28858), (-68.4162, 47.28940)) # example coordinates
# separate the coordinates
lons, lats = list(zip(*points_to_check))
# create the geometry
points = [Point(lon, lat) for lon, lat in points_to_check]
# build the GeoDataframe
d = {'lon': lons, 'lat': lats, 'geometry': points}
gdfp = gpd.GeoDataFrame(d, crs=from_crs).to_crs(to_crs)
# Buffer the points
gdfp['geometry'] = gdfp.buffer(min_distance)
# Join lines to buffered points
gdfl = gpd.read_file(shapefile)
gdfp['name'] = gpd.sjoin(gdfp, gdfl, how='left')['name']
print(gdfp[['lon', 'lat', 'name']])
And if you have lots of points in a csv, you use the pandas read_csv method instead of looping through a list of points:
import numpy as np
import pandas as pd
import geopandas as gpd
from shapely.geometry import Point
from_crs = 'EPSG:4326'
to_crs = 'EPSG:2953'
in_csv = 'test.csv'
results = 'test_results.csv'
shapefile = 'testline.shp'
min_distance = 3 # distance (in metres) where point is considered "on" a road
dfp = pd.read_csv(in_csv) # add delimeter=', ' if csv has a space after the comma
# Build the GeoDataframe
gdfp = gpd.GeoDataFrame(dfp, crs=from_crs, geometry=gpd.points_from_xy(dfp.lon, dfp.lat))
# Reproject
gdfp = gdfp.to_crs(to_crs)
# Reproject and Buffer the points
gdfp['geometry'] = gdfp.buffer(min_distance)
# Join lines to buffered points with spatial join
gdfl = gpd.read_file(shapefile)
# Spatial join
gdfp['name'] = gpd.sjoin(gdfp, gdfl, how='left')['name']
# write out to csv
gdfp.replace(np.nan, '', regex=True)[['lon', 'lat', 'name']].to_csv(results)