How to make a geospatial Rest Api web service with Python, Flask and Shapely - Tutorial
/Geospatial analysis is not limited to a single desktop software or a python kernel; if you use massive spatial analysis or if you work on a team that want some specific spatial output the use of a Rest Api might be convenient. We have developed a basic, introductory but clear tutorial of a geospatial Rest Api that implements the Post method and Get method and returns the centroid of a polygon and the list of elements with their coordinates respectively. Tutorial is done in Windows, however a real Rest server that runs several high energy spatial queries is expected to run in Linux.
Tutorial
Code
This is the code of the rest api:
# app.py from flask import Flask, request, jsonify from shapely.geometry import shape import json app = Flask(__name__) polygons = [ ] def _find_next_id(): print(polygons) if len(polygons) > 0: return max(polygon["id"] for polygon in polygons) + 1 else: return 1 @app.get("/centroids") def get_countries(): return jsonify(polygons) @app.post("/centroids") def add_polygon(): if request.is_json: polygon = json.loads(request.get_json()) polyCoords = polygon["features"][0]["geometry"]["coordinates"] polyShape = shape({ "type": "MultiPolygon", "coordinates":polyCoords }) polygon["centroidx"] = polyShape.centroid.x polygon["centroidy"] = polyShape.centroid.y polygon["id"] = _find_next_id() polygon["name"] = polygon["features"][0]["properties"]["name"] #remove unwanted keys polygon.pop("features") polygon.pop("type") polygons.append(polygon) return polygon, 201 return {"error": "Request must be JSON"}, 415
Jupyter notebook that calls and represents data from the Rest api:
import requests, json
import folium
import geopandas as gpd
#open geospatial data
facDf = gpd.read_file('../In/schoolsHospitalsPoliceFire.gpkg')
#plot polygon
facDf.plot()
<AxesSubplot:>
#show dataframehead
facDf.head()
amenity | name | geometry | |
---|---|---|---|
0 | school | Pioneer Elementary School | MULTIPOLYGON (((-116.34768 43.64752, -116.3475... |
1 | school | Centennial High School | MULTIPOLYGON (((-116.33940 43.65290, -116.3389... |
2 | school | Joplin Elementary School | MULTIPOLYGON (((-116.33274 43.65530, -116.3300... |
3 | school | Lowell Scott Middle School | MULTIPOLYGON (((-116.35409 43.65206, -116.3539... |
4 | school | Cecil D. Andrus Elementary School | MULTIPOLYGON (((-116.34585 43.66075, -116.3447... |
#get the first row
firstRow = facDf.loc[[0]]
type(firstRow)
geopandas.geodataframe.GeoDataFrame
#first row as json
rowJson = firstRow.to_json()
rowJson
'{"type": "FeatureCollection", "features": [{"id": "0", "type": "Feature", "properties": {"amenity": "school", "name": "Pioneer Elementary School"}, "geometry": {"type": "MultiPolygon", "coordinates": [[[[-116.347681, 43.6475172], [-116.3475058, 43.6475166], [-116.346734, 43.6475155], [-116.3466762, 43.6475261], [-116.3466453, 43.6475518], [-116.3466085, 43.6475852], [-116.3465764, 43.6475987], [-116.3465489, 43.6476038], [-116.3465174, 43.6476076], [-116.3464713, 43.6476106], [-116.346431, 43.6476106], [-116.3463823, 43.6476086], [-116.3463207, 43.6476059], [-116.346322, 43.6474886], [-116.3444521, 43.6474892], [-116.3444149, 43.6474889], [-116.3444291, 43.6458487], [-116.3445183, 43.6458486], [-116.3477109, 43.6458464], [-116.347681, 43.6475172]]]]}}]}'
#url of the rest api
apiUrl = "http://127.0.0.1:5000/centroids"
#get all polygons, none to the moment
response = requests.get(apiUrl)
response.json()
[{'centroidx': -116.34606811481463,
'centroidy': 43.64667797982817,
'id': 1,
'name': 'Pioneer Elementary School'},
{'centroidx': -116.33714292646543,
'centroidy': 43.65049255824704,
'id': 2,
'name': 'Centennial High School'}]
#add one poly
response = requests.post(apiUrl,json=rowJson)
responseDict = response.json()
responseDict
{'centroidx': -116.34606811481463,
'centroidy': 43.64667797982817,
'id': 3,
'name': 'Pioneer Elementary School'}
import folium
m = folium.Map(
location=[responseDict['centroidy'],responseDict['centroidx']],
tiles="cartodbpositron",
zoom_start=16,
)
centroidMarker = folium.Marker(location=[responseDict['centroidy'],responseDict['centroidx']])
centroidMarker.add_to(m)
polyJson = folium.GeoJson(data=rowJson,
style_function=lambda x: {'fillColor': 'orange'})
polyJson.add_to(m)
m
#get all polygons, one to the moment
response = requests.get(apiUrl)
response.json()
[{'centroidx': -116.34606811481463,
'centroidy': 43.64667797982817,
'id': 1,
'name': 'Pioneer Elementary School'},
{'centroidx': -116.33714292646543,
'centroidy': 43.65049255824704,
'id': 2,
'name': 'Centennial High School'},
{'centroidx': -116.34606811481463,
'centroidy': 43.64667797982817,
'id': 3,
'name': 'Pioneer Elementary School'}]
### All step involved for second polygon
#get the second row
secondRow = facDf.loc[[1]].to_json()
#url of the rest api
apiUrl = "http://127.0.0.1:5000/centroids"
#add one poly
response = requests.post(apiUrl,json=secondRow)
#get respones
responseDict = response.json()
print(responseDict['centroidx'],responseDict['centroidy'])
-116.33714292646543 43.65049255824704
#get all polygons, two to the moment
response = requests.get(apiUrl)
response.json()
[{'centroidx': -116.34606811481463,
'centroidy': 43.64667797982817,
'id': 1,
'name': 'Pioneer Elementary School'},
{'centroidx': -116.33714292646543,
'centroidy': 43.65049255824704,
'id': 2,
'name': 'Centennial High School'},
{'centroidx': -116.34606811481463,
'centroidy': 43.64667797982817,
'id': 3,
'name': 'Pioneer Elementary School'},
{'centroidx': -116.33714292646543,
'centroidy': 43.65049255824704,
'id': 4,
'name': 'Centennial High School'}]
Input data
You can download the input data from this link.