Skip To Content ArcGIS for Developers Sign In Dashboard

ArcGIS API for Python

Guide to Network Analysis (Part 4 - Find Closest Facilities)

Introduction

We have learned about Network Datasets and Network Analysis services in Part 1, how to find routes from one point to another, and among multiple points in Part 2, and how to generate service area in Part 3, let's move onto the fourth topic - how to find the closest facility. Please refer to the road map below if you want to revisit the previous topics or jump to the next topic -

  • Network Dataset and Network Analysis services (Part 1)
  • Find Routes (Part 2)
  • Generate Service Area (Part 3)
  • Find Closest Facility (You are here!)
  • Generate Origin Destination Cost Matrix (Part 5)
  • Solve Location Allocation (Part 6)
  • Vehicle Routing Problem Service (Part 7)

The API of Finding Closest Facility is useful when you need to find out individual locations near a source feature (in this guide, we call it incidents layer) in a sense that the API can calculate the actual distance between each location in the incidents layer and the closest source (in the facilities layer). For instance, if you want to know the distance of some eagle nests from a river and what's the closest point [1].

Or you can also have the API find the distance between each location (in the incidents layer) and several source features (in the facilities layer), which can be interpreted as [1]:

  • If you want to see which areas are near more than one source and which areas are near only one. For instance, a fast food chain might need to see which areas have many customers near several restaurants.
  • If you need to know the second or third closest source for each location. For instance, a patient might need to know the nearest and the second nearest hospitals to his/her home address.
  • If you want to compare distance to other factors. For example, customer's distance from the store would need to be weighted against the number of store visits.

Fig 1. When a criminal incident happens, police patrol system needs to figure out which patrol vehicles are closest to the incident, in order to send the police officers over to the scene (source: [2]).

This part of guide to Networking Analysis will help you measure the cost of traveling between incidents and facilities, specify how many to find and whether the direction of travel is toward or away from them, and determine which are nearest to one another, and at the end display the best routes between incidents and facilities, report their travel costs, and return driving directions [2]. In this part of guide, you'll be introduced to the workflow to find the closest hospital to an accident.

Now that we have learned why finding closest facility is needed, and the basis of measuring the nearness, it is time to make your hands dirty with some real implementations!

Problem statement

The goal of this page is to determine which facilities are closest to an incident. For this, let us assume the following scenario:

Jim is an operator working for Medical Emergency Response Office, and needs to schedule for emergency response vehicles to pick up patients from the location of incidents and transport to the nearest hospitals. Now given the three incidents reported in Redlands and Loma Linda, can Jim find the nearest hospitals for each of these incidents, and also provide them routes and directions?

Now that Jim's objectives are defined, we can go onto break down the problem:

  • Data: where to access the input datasets
  • Methods: what tools can be used to build the network model and perform closest facility analysis
  • Tables and maps: deliverables in which directions and routes are visualized.

Let's first access and/or explore the input datasets (in this case, the incidents and facilities feature class).

Data

The first step to everything is always importing all required modules and establishing a GIS connection to ArcGIS Online organization or your enterprise for ArcGIS.

If you have already set up a profile to connect to your ArcGIS Online organization, execute the cell below to load the profile and create the GIS class object. If not, use a traditional username/password log-in e.g. my_gis = GIS('https://www.arcgis.com', 'arcgis_python', 'P@ssword123', verify_cert=False, set_active=True)

In [1]:
from arcgis.gis import GIS
import arcgis.network as network
from arcgis.features import FeatureLayer, Feature, FeatureSet, use_proximity, FeatureCollection
import pandas as pd
import time
import datetime as dt
In [2]:
my_gis = GIS(profile="your_online_profile")

Define Incidents and Facilities Layer

Finding a closest facility can be associated with a local network dataset or a network service hosted in ArcGIS Online or ArcGIS Enterprise. Here, we will be using an existing feature layer that contains hospitals derived from various sources (refer SOURCE field) from the Homeland Infrastructure Foundation-Level Data (HIFLD) database (https://gii.dhs.gov/HIFLD), which can be accessed from esri_livingatlas.

If you do not have access to the hospital layer provided by Esri_livingatlas as referenced in the cell below, an alternative approach is for you to download the hospital listing of San Bernadino County from this source and publish the csv to the organization before proceeding forward.

In [4]:
""" This try-except block will help you download the CSV and publish to current GIS object, if 
    "You do not have permissions to access this resource or perform this operation."
"""
try:
    hospital_item = my_gis.content.get("a2817bf9632a43f5ad1c6b0c153b0fab")
except RuntimeError as ne:
    try:
        print("Trying from an alternative source...")
        hospital_item = my_gis.content.get("50fb63f303304835a048d16bd86c3024")
    except RuntimeError as ne:
        print("Trying to publish from csv...")
        import requests
        import csv
        import os

        out_file_name = 'hospitals_SB_county.csv'
        url = "https://data.chhs.ca.gov/datastore/dump/641c5557-7d65-4379-8fea-6b7dedbda40b?q=&sort=_id+asc&fields=OSHPD_ID%2CFACILITY_NAME%2CLICENSE_NUM%2CFACILITY_LEVEL_DESC%2CDBA_ADDRESS1%2CDBA_CITY%2CDBA_ZIP_CODE%2CCOUNTY_CODE%2CCOUNTY_NAME%2CER_SERVICE_LEVEL_DESC%2CTOTAL_NUMBER_BEDS%2CFACILITY_STATUS_DESC%2CFACILITY_STATUS_DATE%2CLICENSE_TYPE_DESC%2CLICENSE_CATEGORY_DESC%2CLATITUDE%2CLONGITUDE&filters=%7B%22COUNTY_CODE%22%3A+%5B%2236%22%5D%7D&format=csv"
        download = requests.get(url)

        with open(out_file_name, 'w') as out_file:
            out_file.writelines(download.text)
            print(out_file_name)
        csv_item = my_gis.content.add({}, out_file_name)
        hospital_item = csv_item.publish()
display(hospital_item)
You do not have permissions to access this resource or perform this operation.
Trying from an alternative source...
hospitals_SB_county
Feature Layer Collection by arcgis_python
Last Modified: September 27, 2019
0 comments, 10 views

From the Feature Layer item, we will get the first Feature Layer which we will use for the rest of this notebook.

In [4]:
hospital_fl = hospital_item.layers[0]
hospital_fl
Out[4]:
<FeatureLayer url:"https://services7.arcgis.com/JEwYeAy2cc8qOe3o/arcgis/rest/services/hospitals_SB_county/FeatureServer/0">
In [5]:
""" If you are using the exisiting layer from Esri_LivngAtlas, there is a "County" column in the dataset;
    or else, the feature layer collection published from the downloaded CSV file is already targetted at SB County.
"""
try:
    facilities = hospital_fl.query(where="County='SAN BERNARDINO' AND State='CA'", as_df=False)
except RuntimeError as re:
    """ when seeing 'Invalid field: County' parameter is invalid
    """
    print("Trying from an alternative approach...")
    facilities = hospital_fl.query(where="Dba_city='REDLANDS' or Dba_city='LOMA LINDA'", as_df=False)
display(facilities)
'Invalid field: County' parameter is invalid
Trying from an alternative approach...
<FeatureSet> 33 features

Now we have the facilities layer ready. It has 33 hospitals as features. We can go onto define the incidents layer. Here, we randomly picked three locations in Redlands, CA.

In [6]:
incidents_json = {
                    "features": [{"attributes": {"CurbApproach": 0,
                                                 "ID": "C100045",
                                                 "Name": "Incident at Esri"},
                                  "geometry": {"x": -117.19569523299998, "y": 34.05608640000003}},
                                 {"attributes": {"CurbApproach": 0,
                                                 "ID": "F100086",
                                                 "Name": "Incident at APT"},
                                  "geometry": {"x": -117.20520037855628, "y": 34.04472649163186}},
                                 {"attributes": {"CurbApproach": 0,
                                                 "ID": "C100097",
                                                 "Name": "Incident at Walmart"},
                                  "geometry": {"x": -117.222253, "y": 34.065378}}],
                    "spatialReference": {"wkid": 4326, "latestWkid": 4326},
                    "geometryType": "esriGeometryPoint",
                    "fields" : [
                        {"name" : "ID", "type" : "esriFieldTypeString", "alias" : "ID", "length" : "50"},
                        {"name" : "Name", "type" : "esriFieldTypeString", "alias" : "Name", "length" : "50"},
                        {"name" : "CurbApproach", "type" : "esriFieldTypeInteger", "alias" : "CurbApproach"}
                    ]
                }
incidents = FeatureSet.from_dict(incidents_json)

Let's have the two layers visualized in the map view.

In [78]:
map1 = my_gis.map('Redlands, CA', zoomlevel=12)
map1