Automated creation of a new model from scratch#

On this example, we show how to use Polaris-Studio automated tools to the most complicated portions of models, including:

  • Network from OpenStreetMaps

  • EV Charger locations from NREL

  • Zoning system from the Census

  • Transit routes from GTFS provided by MobilityData (if available)

Many of these steps require API keys, which are not provided by Polaris-Studio. The user will need to obtain them from the respective providers. For the purpose of this example, API keys are obtained from the environment variables CENSUS_API_KEY, MOBILITY_DATABASE_API_KEY & EV_API_KEY.

Imports#

import os
from pathlib import Path
from tempfile import gettempdir

import geopandas as gpd

from polaris.network.network import Network

sphinx_gallery_thumbnail_path = ‘../../examples/model_building/model_from_scratch.png’

# We define a simple polygon to surround the Marshalltown area
polygon = "Polygon ((-92.99085569 42.08026803, -92.98770013 41.98745385, -92.85185308 41.98499102, -92.85059085 42.08331266, -92.99085569 42.08026803))"
geo = gpd.GeoSeries.from_wkt([polygon], crs=4326)
model_area = gpd.GeoDataFrame({"data": [1]}, geometry=geo)

Create the new model supply and open it

rootdir = Path(gettempdir())

Network.create(rootdir / "my_model_supply.sqlite", srid=26916, jumpstart=True)
net = Network.from_file(rootdir / "my_model_supply.sqlite", False)

Creating the zoning system#

Census API keys can be obtained from https://api.census.gov/data/key_signup.html

net.populate.add_zoning_system(model_area, "tracts", os.environ.get("CENSUS_API_KEY"), 2021)
Using the default year of 2021
Using the default year of 2021
Using FIPS code '19' for input 'Iowa'
Using FIPS code '19' for input 'Iowa'

Let’s take a look at what we generated

net.data_tables.get_geo_layer("Zone").plot()
plot new model from scratch
<Axes: >

Adding EV Chargers#

NREL API keys can be obtained from https://developer.nrel.gov/signup/

We will try to cluster chargers that are within 50m from each other. The algorithm has some randomness, so we will try 5 times

net.populate.add_ev_chargers(os.environ.get("EV_API_KEY"), max_dist=50, clustering_attempts=5)
Using the default year of 2021

Let’s take a look at what we generated

net.data_tables.get_geo_layer("EV_Charging_Stations").plot()
plot new model from scratch
<Axes: >

We will add parking facilities from OpenStreetMap The default tags for parking facilities are (“surface”, “underground”, “multi-storey”, “rooftop”) and the sample rate is 100% net.populate.add_parking(facility_types=(“surface”, “underground”, “multi-storey”, “rooftop”), sample_rate=1.0)

net.populate.add_parking()
/usr/local/lib/python3.11/site-packages/urllib3/connectionpool.py:1099: InsecureRequestWarning: Unverified HTTPS request is being made to host 'overpass-api.de'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
  warnings.warn(
/usr/local/lib/python3.11/site-packages/urllib3/connectionpool.py:1099: InsecureRequestWarning: Unverified HTTPS request is being made to host 'overpass-api.de'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
  warnings.warn(
/usr/local/lib/python3.11/site-packages/urllib3/connectionpool.py:1099: InsecureRequestWarning: Unverified HTTPS request is being made to host 'overpass-api.de'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
  warnings.warn(
/usr/local/lib/python3.11/site-packages/urllib3/connectionpool.py:1099: InsecureRequestWarning: Unverified HTTPS request is being made to host 'overpass-api.de'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
  warnings.warn(
/usr/local/lib/python3.11/site-packages/urllib3/connectionpool.py:1099: InsecureRequestWarning: Unverified HTTPS request is being made to host 'overpass-api.de'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
  warnings.warn(
/usr/local/lib/python3.11/site-packages/urllib3/connectionpool.py:1099: InsecureRequestWarning: Unverified HTTPS request is being made to host 'overpass-api.de'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
  warnings.warn(
/usr/local/lib/python3.11/site-packages/urllib3/connectionpool.py:1099: InsecureRequestWarning: Unverified HTTPS request is being made to host 'overpass-api.de'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
  warnings.warn(
/usr/local/lib/python3.11/site-packages/urllib3/connectionpool.py:1099: InsecureRequestWarning: Unverified HTTPS request is being made to host 'overpass-api.de'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
  warnings.warn(

Let’s take a look at what we generated

net.data_tables.get_geo_layer("Parking").plot()
plot new model from scratch
<Axes: >

Adding Locations#

And don’t forget to add external locations in case you are going to have external travel

Control totals for residential locations come straight from Census (default to latest data available from ACS) We need control totals for non-residential locations, however

# Let's say we have data for that
gjson = """
{
"type": "FeatureCollection",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
"features": [
{ "type": "Feature", "properties": { "BUSINESS": 2.0, "DISTRIBUTION": 5.0, "EDUCATION": 3.0, "HIGHER_EDUCATION": 2.0, "HOTEL": 3.0, "INDUSTRY": 1.0, "MAJ_SHOP": 1.0, "MEDICAL": 1.0, "RETAIL": 2.0 }, "geometry": { "type": "Polygon", "coordinates":  [ [ [ -92.826439648222348, 41.951002108252993 ], [ -92.819935474384991, 42.09468522120526 ], [ -93.007373938606676, 42.126614801861322 ], [ -92.951792816723909, 41.938585049108966 ], [ -92.826439648222348, 41.951002108252993 ] ] ]} }
]
}
"""

with open(rootdir / "control_data.geojson", "w") as fl:
    fl.write(gjson)
control_totals = gpd.read_file(rootdir / "control_data.geojson")

net.populate.add_locations(
    control_totals=control_totals,
    census_api_key=os.environ.get("CENSUS_API_KEY"),
    residential_sample_rate=0.25,
    other_sample_rate=1.0,
)
Using the default year of 2021
Using the default year of 2021
Using FIPS code '19' for input 'Iowa'
Using FIPS code '19' for input 'Iowa'
net.data_tables.get_geo_layer("Location").plot()
plot new model from scratch
<Axes: >

Adding Road Network#

Adding a network from OpenStreetMap requires a large number of parameters to be set. We will use the default values for most of them

simpli_par = {
    "simplify": True,  # We do want to simplify the network instead of keeping 100% of the OSM links
    "keep_transit_links": True,  # We want to keep all links for which there is at least one transit route
    "accessibility_level": "block-groups",
    # We will simplify the network based on zones. Using "location" would result on a denser network
    "maximum_network_capacity": False,
    # We want to use a more intense path finding algorithm to search for virtually all alternative routes
}
imput_par = {
    "algorithm": "knn",  # "knn" or "iterative"
    # "max_iter": 10, # Maximum number of iterations in the imputation algorithm. Only needed for "iterative" algorithm
    "fields_to_impute": ("speed_ab", "speed_ba", "lanes_ab", "lanes_ba"),
}

net_constructor = net.populate.create_network(simplification_parameters=simpli_par, imputation_parameters=imput_par)
Using the default year of 2021
Using the default year of 2021
Using FIPS code '19' for input 'Iowa'

There are two steps in simplifying the network

# The first one is to download all relevant GTFS feeds from the mobility database
# It will download the feeds to the project folder inside supply/gtfs
# It will skip all downloads if there are pre-existing GTFS feeds in that path

# You must first obtain a mobility database API key on https://mobilitydatabase.org/sign-up
mobility_database_api_key = os.environ.get("MOBILITY_DATABASE_API_KEY")
feed_count = net_constructor.download_gtfs(mobility_database_api_key=mobility_database_api_key)
print(f"{feed_count} GTFS feeds were found in the mobility database for this area")

# If we had a GTFS feed available for this area we could add it to the model folder before continuing to network build
# dest_path = Path(net.path_to_file).parent / "supply" / "gtfs"
# dest_path.mkdir(parents=True, exist_ok=True)
# shutil.copy(join(SRC_FOLDER, "gtfs_feed.zip"), str(dest_path / "gtfs_feed.zip"))
0 GTFS feeds were found in the mobility database for this area

The second step is to run the network build and import, which will run the simplification process if requested

net_constructor.build_network()

# And then import the transit data from our GTFS feed
# If you don't provide a date of service to be imported, we software will choose the one with the most services running
# for each feed available for import
net_constructor.import_transit(map_match=False, date="")
Total polygons: 1                                 :   0%|          | 0/1 [00:00<?, ?it/s]
Processing chunks                                 :   0%|          | 0/1 [00:00<?, ?it/s]
Adding network links                              : 0it [00:00, ?it/s]
/builds/polaris/code/polarislib/polaris/prepare/supply_tables/network/parameter_imputation.py:33: UserWarning: Geometry is in a geographic CRS. Results from 'centroid' are likely incorrect. Use 'GeoSeries.to_crs()' to re-project geometries to a projected CRS before this operation.

  gdf_links["centroid"] = gdf_links.geometry.centroid
/builds/polaris/code/polarislib/polaris/prepare/supply_tables/network/parameter_imputation.py:34: UserWarning: Geometry is in a geographic CRS. Results from 'centroid' are likely incorrect. Use 'GeoSeries.to_crs()' to re-project geometries to a projected CRS before this operation.

  gdf_links["centroid_x"] = gdf_links.centroid.x
/builds/polaris/code/polarislib/polaris/prepare/supply_tables/network/parameter_imputation.py:35: UserWarning: Geometry is in a geographic CRS. Results from 'centroid' are likely incorrect. Use 'GeoSeries.to_crs()' to re-project geometries to a projected CRS before this operation.

  gdf_links["centroid_y"] = gdf_links.centroid.y
Using FIPS code '19' for input 'Iowa'
Using FIPS code '19' for input 'Iowa'
Using FIPS code '19' for input 'Iowa'
Using FIPS code '19' for input 'Iowa'
Using FIPS code '19' for input 'Iowa'
Using FIPS code '19' for input 'Iowa'
Using FIPS code '19' for input 'Iowa'
walk                                              :   0%|          | 0/52 [00:00<?, ?it/s]
Equilibrium Assignment                            :   0%|          | 0/2 [00:00<?, ?it/s]
Using FIPS code '19' for input 'Iowa'
Using FIPS code '19' for input 'Iowa'
Using FIPS code '19' for input 'Iowa'
Using FIPS code '19' for input 'Iowa'
Using FIPS code '19' for input 'Iowa'
Using FIPS code '19' for input 'Iowa'
Using FIPS code '19' for input 'Iowa'
car_freight                                       :   0%|          | 0/52 [00:00<?, ?it/s]
Equilibrium Assignment                            :   0%|          | 0/500 [00:00<?, ?it/s]

Closes project#

print("REFERENCE: You can keep navigating the map after closing the project")
net.close(False)
REFERENCE: You can keep navigating the map after closing the project

Total running time of the script: (3 minutes 6.652 seconds)

Gallery generated by Sphinx-Gallery