Route Closest Target#
Processor RouteClosestTarget requires an additional, seperate license for street map premium data. Please contact the BDT team at bdt_support@esri.com if you are interested.
Table of Contents
Part 0: Setup BDT#
[ ]:
import bdt
bdt.auth("bdt.lic")
from bdt.processors import *
from pyspark.sql.types import StructType, StructField, DoubleType
BDT has been successfully authorized!
Welcome to
___ _ ___ __ ______ __ __ _ __
/ _ ) (_) ___ _ / _ \ ___ _ / /_ ___ _ /_ __/ ___ ___ / / / /__ (_) / /_
/ _ | / / / _ `/ / // // _ `// __// _ `/ / / / _ \/ _ \ / / / '_/ / / / __/
/____/ /_/ \_, / /____/ \_,_/ \__/ \_,_/ /_/ \___/\___//_/ /_/\_\ /_/ \__/
/___/
BDT python version: v3.3.0-v3.3.0
BDT jar version: v3.3.0-v3.3.0
Part 1: What is Route Closest Target?#
Route Closest Target will find the closest target (destination) point from each origin point using the road network.
From here on, target (destination) points will be represented as blue, and origin points as red.
The below image is an image of the sample data used in this notebook.
Route Closest Target was originally designed to solve network adequacy problems. Network adequacy is requirement in the healthcare industry that stipulates all members must have access to a hospital or provider within a set time or distance.
Part 2: Route Closest Target#
Generate Sample Data#
Two DataFrames are created, one of origin points and one of target (destination) points. In the case of healthcare network adequacy, these can be thought of as member points and provider points.
[ ]:
origin_schema = StructType([StructField("OX", DoubleType()),
StructField("OY", DoubleType())])
origin_points = [(-13040800.98123, 3864013.475744),
(-13040484.574291, 3860694.049651),
(-13040302.581114, 3863882.764771),
(-13039786.093915, 3861714.605291),
(-13041802.162892, 3862881.33078),
(-13041576.09564, 3863696.259199)]
origin_df = spark.createDataFrame(data = origin_points, schema = origin_schema)
dest_schema = StructType([StructField("DX", DoubleType()),
StructField("DY", DoubleType())])
origin_df.show(6)
dest_points = [(-13040884.714752, 3861002.234746),
(-13041944.162426, 3863283.200951),
(-13040883.863412, 3861330.721683)]
dest_df = spark.createDataFrame(data = dest_points, schema = dest_schema)
dest_df.show(5)
+------------------+--------------+
| OX| OY|
+------------------+--------------+
| -1.304080098123E7|3864013.475744|
|-1.3040484574291E7|3860694.049651|
|-1.3040302581114E7|3863882.764771|
|-1.3039786093915E7|3861714.605291|
|-1.3041802162892E7| 3862881.33078|
| -1.304157609564E7|3863696.259199|
+------------------+--------------+
+------------------+--------------+
| DX| DY|
+------------------+--------------+
|-1.3040884714752E7|3861002.234746|
|-1.3041944162426E7|3863283.200951|
|-1.3040883863412E7|3861330.721683|
+------------------+--------------+
Run ProcessorRouteClosestTarget#
RouteClosestTarget takes max minutes and max meters as processor parameters. If a target (destination) point is not found within the max minutes or max meters threshold, then search is stopped and null is emitted.
Below, RouteClosestTarget is run on the sample data created above with max minutes set to 20 and max meters set to 20,000 (about 15 miles). Eveything in the example falls within these thresholds, and therefore will be routed.
[ ]:
max_minutes = 20
max_meters = 20_000
out_df = routeClosestTarget(
origin_df,
dest_df,
maxMinutes=max_minutes,
maxMeters=max_meters,
oxField="OX",
oyField="OY",
dxField="DX",
dyField="DY",
origTolerance=500,
destTolerance=500,
allNodesAtLocation=True,
costOnly=False)
out_df.show(10)
+------------------+--------------+------------------+--------------+------------------+------------------+--------------------+
| OX| OY| DX| DY| TIME| DIST| SHAPE|
+------------------+--------------+------------------+--------------+------------------+------------------+--------------------+
|-1.3040484574291E7|3860694.049651|-1.3040884714752E7|3861002.234746|3.1869068979382313| 715.6804564880011|{[01 05 00 00 00 ...|
|-1.3039786093915E7|3861714.605291|-1.3040884714752E7|3861002.234746|3.7124784135237494|2139.0446893641515|{[01 05 00 00 00 ...|
| -1.304080098123E7|3864013.475744|-1.3041944162426E7|3863283.200951| 4.136834134419864|1744.3240514420816|{[01 05 00 00 00 ...|
| -1.304157609564E7|3863696.259199|-1.3041944162426E7|3863283.200951| 3.40619655619934| 839.292720392489|{[01 05 00 00 00 ...|
|-1.3040302581114E7|3863882.764771|-1.3041944162426E7|3863283.200951|5.3728741242577325| 2221.614199602606|{[01 05 00 00 00 ...|
|-1.3041802162892E7| 3862881.33078|-1.3040883863412E7|3861330.721683|6.7511432250957135|2548.7152223840703|{[01 05 00 00 00 ...|
+------------------+--------------+------------------+--------------+------------------+------------------+--------------------+
OX
|
OY
|
DX
|
DY
|
TIME
|
DIST
|
SHAPE
|
-1.3040484574291E7 -1.3039786093915E7 -1.304080098123E7 -1.3040302581114E7 -1.3041802162892E7 -1.304157609564E7 |
3860694.049651 3861714.605291 3864013.475744 3863882.764771 3862881.33078 3863696.259199 |
-1.3040884714752E7 -1.3040884714752E7 -1.3041944162426E7 -1.3041944162426E7 -1.3040883863412E7 -1.3041944162426E7 |
3861002.234746 3861002.234746 3863283.200951 3863283.200951 3861330.721683 3863283.200951 |
3.1295011526227183 3.779851680753321 4.382222993478608 5.508639519084289 6.4487256718030865 3.5259039595519592 |
708.4195669585819 2124.9986015075656 1744.3173031550214 2221.607529274669 2493.864300590879 839.2927167291009 |
{ …| { …| { …| { …| { …| { …| |
The below image shows each origin point routed to its closest target (destination) using ProcessorRouteClosestTarget.
Part 3: Route Closest Target 2#
RouteClosestTarget2 does the same thing as RouteClosestTarget, but instead of max time and max meters being parameters to the processor, they are required as columns in the input origin DataFrame. This allows for the max time and max meters criterion to be dynamic per row.
Generate Sample Data#
Again, two DataFrames are created, one of origin points and one of target (destination) points. The points are the same as the Route Closest Target example, but there are now two new columns in the origin DataFrame: MaxMins and MaxMeters.
[ ]:
origin_schema = StructType([StructField("OX", DoubleType()),
StructField("OY", DoubleType()),
StructField("MaxMins", DoubleType()),
StructField("MaxMeters", DoubleType())])
origin_points = [(-13040800.98123, 3864013.475744, 20.0, 15.0),
(-13040484.574291, 3860694.049651, 20.0, 15.0),
(-13040302.581114, 3863882.764771, 20.0, 15.0),
(-13039786.093915, 3861714.605291, 2.0, 0.2),
(-13041802.162892, 3862881.33078, 2.0, 0.2),
(-13041576.09564, 3863696.259199, 2.0, 0.2)]
origin_df = spark.createDataFrame(data = origin_points, schema = origin_schema)
origin_df.show(6)
dest_schema = StructType([StructField("DX", DoubleType()),
StructField("DY", DoubleType())])
dest_points = [(-13040884.714752, 3861002.234746),
(-13041944.162426, 3863283.200951),
(-13040883.863412, 3861330.721683)]
dest_df = spark.createDataFrame(data = dest_points, schema = dest_schema)
dest_df.show(3)
+------------------+--------------+-------+---------+
| OX| OY|MaxMins|MaxMeters|
+------------------+--------------+-------+---------+
| -1.304080098123E7|3864013.475744| 20.0| 15.0|
|-1.3040484574291E7|3860694.049651| 20.0| 15.0|
|-1.3040302581114E7|3863882.764771| 20.0| 15.0|
|-1.3039786093915E7|3861714.605291| 2.0| 0.2|
|-1.3041802162892E7| 3862881.33078| 2.0| 0.2|
| -1.304157609564E7|3863696.259199| 2.0| 0.2|
+------------------+--------------+-------+---------+
+------------------+--------------+
| DX| DY|
+------------------+--------------+
|-1.3040884714752E7|3861002.234746|
|-1.3041944162426E7|3863283.200951|
|-1.3040883863412E7|3861330.721683|
+------------------+--------------+
Run ProcessorRouteClosestTarget2#
RouteClosestTarget2 does not take max minutes nor max meters as processor parameters. Instead, it gets those values from the input origin DataFrame as mentoined above.
[ ]:
out_df = routeClosestTarget2(
origin_df,
dest_df,
maxMinutesField = "MaxMins",
maxMetersField = "MaxMeters",
oxField="OX",
oyField="OY",
dxField="DX",
dyField="DY",
origTolerance=500,
destTolerance=500,
allNodesAtLocation=True,
costOnly=False)
out_df.drop("DX", "DY").show(10)
+------------------+--------------+-------+---------+------------------+------------------+--------------------+
| OX| OY|MaxMins|MaxMeters| TIME| DIST| SHAPE|
+------------------+--------------+-------+---------+------------------+------------------+--------------------+
|-1.3039786093915E7|3861714.605291| 2.0| 0.2| NULL| NULL| NULL|
|-1.3040484574291E7|3860694.049651| 20.0| 15.0|3.1869068979382313| 715.6804564880011|{[01 05 00 00 00 ...|
|-1.3041802162892E7| 3862881.33078| 2.0| 0.2| NULL| NULL| NULL|
| -1.304080098123E7|3864013.475744| 20.0| 15.0| 4.136834134419864|1744.3240514420816|{[01 05 00 00 00 ...|
|-1.3040302581114E7|3863882.764771| 20.0| 15.0|5.3728741242577325| 2221.614199602606|{[01 05 00 00 00 ...|
| -1.304157609564E7|3863696.259199| 2.0| 0.2| NULL| NULL| NULL|
+------------------+--------------+-------+---------+------------------+------------------+--------------------+
OX
|
OY
|
MaxMins |
MaxMeters |
TIME
|
DIST
|
SHAPE
|
-1.3040484574291E7 -1.3039786093915E7 -1.304080098123E7 -1.3040302581114E7 -1.3041802162892E7 -1.304157609564E7 |
3860694.049651 3861714.605291 3864013.475744 3863882.764771 3862881.33078 3863696.259199 |
20.0 2.0 20.0 20.0 2.0 2.0 |
15.0
0.2
15.0
15.0
0.2
0.2
|
3.1295011526227183 null 4.382222993478608 5.508639519084289 null null |
708.4195669585819 null 1744.3173031550214 2221.607529274669 null null |
{ …| null { …| { …| null null |
The below image shows some (not all) origin points routed to their closest target (destination) using ProcessorRouteClosestTarget2.
Some origin points (circled in red) do not find a destination point . That is because the max minutes and max meters values are 2 and 0.2 for some rows in the input DataFrame, and the closest destination point is beyond those max values.