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

  1. Setup BDT

  2. What Is Route Closest Target?

  3. Route Closest Target

    1. Generate Sample Data

    2. Run ProcessorRouteClosestTarget

  4. Route Closest Target 2

    1. Generate Sample Data

    2. Run ProcessorRouteClosestTarget2

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.

drawing

  • 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.

drawing

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.

drawing