Calculating distances from Points to Polygon Borders in Python — A Paris Example

Maximilian Hofmann
Mar 19 · 5 min read

There are many great packages which help you to work with geospatial data in Python. However, if you need to customize, it can get tricky.

I want to show how to calculate distances in meters between latitude, longitude pairs and polygons. Consider that I don’t want to calculate the distance to the midpoint of the polygon, but to the closest border.

I will show this using the following example: how far is Notre Dame away from every Arrondissement?

All code is available in my Git. I will include links to relevant functions instead of writing out the syntax in this post.

Setting up your Environment and getting the Data

For visualisation we will use the folium library in python. For geometries we will use geopy. Install the packages with:

pip install folium geopy

I want to show the methodology using arrondissements data from Paris. Please download the GeoJSON file from https://www.data.gouv.fr/en/datasets/arrondissements-1/#_ and save the file in your project root directory.

Let’s now have a look at the arrondissement as well as the location of Notre Dame in Paris.

Paris Arrondissements and Location of Notre Dame

That’s the code to generate this map.

import folium
from folium.features import DivIcon
import geojson

with open('arrondissements.geojson') as f:
data = geojson.loads(f.read())

# Set your point of interest. In our case that is Notre Dame.
poi_name = 'Notre Dam'
poi = [48.864716, 2.349014]
# Map it!
m = folium.Map(location=poi, zoom_start=12, tiles='cartodbpositron')
# Display Arrondissements
folium.GeoJson(data).add_to(m)
# Display Arrondissements Numbers
for arrond in data['features']:
loc = arrond['properties']['geom_x_y']
nr = arrond['properties']['c_ar']
folium.map.Marker(location=loc, icon=DivIcon(
icon_size=(150, 36),
icon_anchor=(0, 0),
html='<div style="font-size: 15pt; color:rgb(41,111,255)">{}</div>'.format(str(nr))
)).add_to(m)

# Display POI
folium.CircleMarker(location=poi, color='red', radius=5, fill='red').add_to(m)
# Display POI Name
folium.map.Marker(location=poi, icon=DivIcon(
icon_size=(150, 36),
icon_anchor=(0, 0),
html='<div style="font-size: 18pt; color: red">{}</div>'.format(poi_name)
)).add_to(m)

m.save('map_1.html')

Calculating the Distance to one Arrondissement

Whereas Python is great with calculating distances in Cartesian Coordinate Systems, some workarounds are required if you are working with geospatial data.

  1. Outputs of libraries need to be converted in usable metrics. See @jbndir reply here.
  2. Distance calculations need to take into account the spherical form of the earth. We can use the Haversine formula to calculate great circle distance as outlined here. (Note that this becomes increasingly important for big distances)

Another Challenge — Measuring the distance to what?

We want to measure the distance of a point to a polygon. But a polygon can be separated in various line segments and points. Should we measure the distance to lines, points or both?

Let’s look at a simple example:

We have a line between A and B and want to calculate the minimum distance to C. As we all know, the shortest distance is the orthogonal projection onto the line AB and the closest point on the line is X.

However in the following simple example, we are not able to project an orthogonal line, which is why the minimum distance is to the closest point.

Middle Ground — Measure the distance to the Midpoint!

In order to keep our algorithm lean, let’s not account for these specific cases and always calculate the distance to the middle point of our lines. Especially in accurately defined polygons (on a small space), the differences are negligible.

Let’s follow the midpoint calculation outlined here and the implementation in Python here (thanks, @nelfin).

def midpoint(a, b):
...
Please find the function here.

Having the midpoint, we can now calculate the distance to a point:

def calculate_dist_to_line(line_a_lat, line_a_lng, line_b_lat, line_b_lng, point_object):
...
Please find the function here.

Now, a polygon consists of many different lines, which is why we need to loop through all lines and find the one with the lowest distance:

def get_min_distance_to_arr(arr_coords, point_object, unit='m'):
...
Please find the function here.

Nice! Let’s look at one Arrondissement now

Using our functions, we can easily get the minimum distance and the PolyLine for any Arrondissement:

# Example with Arrondissement 20
coords = [x['geometry']['coordinates'][0] for x in data['features'] if x['properties']['c_ar'] == 20][0]
p = Point(latitude=poi[0],longitude=poi[1])distance, line = fu.get_min_distance_to_arr(arr_coords=coords,point_object=p)
line_midpoint = fu.get_line_midpoint(line)

The plot also looks good — and accurate!

The shortest distance from Notre Dame to the border of Arrondissement 20.

The code:

# Map it!
m = folium.Map(location=poi, zoom_start=12, tiles='cartodbpositron')

# Display POI
folium.CircleMarker(location=poi, color='red', radius=5, fill='red').add_to(m)
# Display POI Name
folium.map.Marker(location=poi, icon=DivIcon(
icon_size=(150, 36),
icon_anchor=(0, 0),
html='<div style="font-size: 18pt; color: red">{}</div>'.format(poi_name)
)).add_to(m)

for i, _ in enumerate(coords):
if i+1 < len(coords):
folium.PolyLine(locations=[(coords[i][1],coords[i][0]),(coords[i+1][1],coords[i+1][0])], color='blue').add_to(m)
else:
folium.PolyLine(locations=[(coords[i][1], coords[i][0]), (coords[0][1], coords[0][0])],
color='blue').add_to(m)

folium.PolyLine(locations=line, color='red').add_to(m)
folium.PolyLine(locations=[poi,[line_midpoint.latitude,line_midpoint.longitude]], color='red').add_to(m)
folium.CircleMarker(location=[line_midpoint.latitude,line_midpoint.longitude], color='red', radius=5, fill='red').add_to(m)

new_line_mp = fu.get_line_midpoint([poi,[line_midpoint.latitude,line_midpoint.longitude]])
folium.map.Marker(location=[new_line_mp.latitude,new_line_mp.longitude], icon=DivIcon(
icon_size=(150, 36),
icon_anchor=(0, 0),
html='<div style="font-size: 10pt; color: red">Distance: {} Meters</div>'.format(round(dist))
)).add_to(m)

m.save('map_2.html')

Let’s do this with all other Arrondissements!

Since we have the basic logic ready, it’s easy to loop through it and plot all data points together.

If you’re interested in finding out which arrondissement is closest, use the output of the function get_min_distance_to_arr!

The map doesn’t look pretty, but is correct :)

That’s the code:

# Map all!
m = folium.Map(location=poi, zoom_start=12, tiles='cartodbpositron')

# Display POI
folium.CircleMarker(location=poi, color='red', radius=5, fill='red').add_to(m)
# Display POI Name
folium.map.Marker(location=poi, icon=DivIcon(
icon_size=(150, 36),
icon_anchor=(0, 0),
html='<div style="font-size: 18pt; color: red">{}</div>'.format(poi_name)
)).add_to(m)

# Display Arrondissements
folium.GeoJson(data).add_to(m)
# Display Arrondissements Numbers
for arrond in data['features']:
loc = arrond['properties']['geom_x_y']
nr = arrond['properties']['c_ar']
folium.map.Marker(location=loc, icon=DivIcon(
icon_size=(150, 36),
icon_anchor=(0, 0),
html='<div style="font-size: 15pt; color:rgb(41,111,255)">{}</div>'.format(str(nr))
)).add_to(m)

for arrond in data['features']:

coords = arrond['geometry']['coordinates'][0]
p = Point(latitude=poi[0],longitude=poi[1])
dist, line = fu.get_min_distance_to_arr(arr_coords=coords,point_object=p)
line_midpoint = fu.get_line_midpoint(line)

# for i, _ in enumerate(coords):
# if i+1 < len(coords):
# folium.PolyLine(locations=[(coords[i][1],coords[i][0]),(coords[i+1][1],coords[i+1][0])], color='blue').add_to(m)
# else:
# folium.PolyLine(locations=[(coords[i][1], coords[i][0]), (coords[0][1], coords[0][0])],
# color='blue').add_to(m)

folium.PolyLine(locations=line, color='red').add_to(m)
folium.PolyLine(locations=[poi,[line_midpoint.latitude,line_midpoint.longitude]], color='red').add_to(m)
folium.CircleMarker(location=[line_midpoint.latitude,line_midpoint.longitude], color='red', radius=5, fill='red').add_to(m)

new_line_mp = fu.get_line_midpoint([poi,[line_midpoint.latitude,line_midpoint.longitude]])
folium.map.Marker(location=[line_midpoint.latitude,line_midpoint.longitude], icon=DivIcon(
icon_size=(150, 36),
icon_anchor=(0, 0),
html='<div style="font-size: 10pt; color: red">Distance: {} Meters</div>'.format(round(dist))
)).add_to(m)

m.save('map_3.html')

Reach out with any questions you might have!

Analytics Vidhya

Analytics Vidhya is a community of Analytics and Data…

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store