Note
Go to the end to download the full example code.
Creating external locations#
In this example, we show how to use Polaris-Studio and GeoPandas to create external locations along roads that traverse the boundary of the modelling area.
Imports#
from pathlib import Path
import geopandas as gpd
from polaris.network.consistency.network_objects.location import Location
from polaris.network.consistency.network_objects.location_links import link_types_for_ext
from polaris.network.network import Network
from polaris.utils.database.db_utils import commit_and_close
project_file = Path("/tmp/Bloomington_external_locations/Bloomington-Supply.sqlite")
net = Network.from_file(project_file, False)
2025-08-17 10:05:24 UTC+0000 - Working with file on /tmp/Bloomington_external_locations/Bloomington-Supply.sqlite
Create the new model supply and open it We would normally use the national network as the source of roads entering and exiting the model area For the sake of an example, let’s use the Bloomington network itself
national_file = Path("/tmp/Bloomington_external_locations/Bloomington-Supply.sqlite")
national_net = Network.from_file(national_file, False)
2025-08-17 10:05:24 UTC+0000 - Working with file on /tmp/Bloomington_external_locations/Bloomington-Supply.sqlite
Grabbing the necessary layers#
We also make sure that the layers are in the same projection
zone_layer = net.tables.get("zone")
national_links = national_net.tables.get("link").to_crs(zone_layer.crs)
We can be a little fancier and put a little buffer around all zones before we dissolve them This avoids problems with all sorts of imprecisions in the zone boundaries All Polaris models have projections in meters, so we can just go ahead and add a 10m buffer This would be a disaster if we were working in lat/lon
zone_polygon = zone_layer.buffer(10).union_all()
model_boundary = zone_polygon.boundary
Find all links that cross the zone line#
crossing_link = national_links[national_links.crosses(model_boundary)]
For each link, we add a location that is 50 meters within the model area
all_externals = []
for idx, rec in crossing_link.iterrows():
link = rec.geo
intersec = link.intersection(model_boundary)
intersec = list(intersec.geoms) if intersec.geom_type == "MultiPoint" else [intersec]
for interc_point in intersec:
projection = link.project(interc_point)
alt1 = link.interpolate(min(projection + 50, link.length))
if zone_polygon.contains(alt1):
all_externals.append(alt1)
else:
all_externals.append(link.interpolate(max(projection - 50, 0)))
Let’s make sure we don’t add locations too close to each other. Say 200 meters?
min_dist = 200
extern = gpd.GeoDataFrame({"geometry": gpd.GeoSeries(all_externals, crs=zone_layer.crs)}).set_geometry("geometry")
repeat = extern.sjoin_nearest(extern, max_distance=min_dist, rsuffix="r", exclusive=True, distance_col="dist")
repeat = repeat[repeat.index_r < repeat.index]
extern = extern.drop(repeat.index)
We also should only add locations that are close to Freeways/Expressways in the model Loading external trips in locations connected to other link types causes problems in the traffic model 3km to the closest Freeway should make for any discrepancies between the national and model networks
max_dist = 3000
print(link_types_for_ext)
model_links = net.tables.get("link")[["link", "type", "geo"]]
model_links = model_links[model_links["type"].str.upper().isin(link_types_for_ext)]
extern = extern.sjoin_nearest(model_links, max_distance=max_dist).dropna()[extern.columns]
['EXPRESSWAY', 'FREEWAY', 'EXTERNAL']
Let’s add all these locations
max_loc = net.tables.get("Location").location.max() + 1
with commit_and_close(net.path_to_file, spatial=True) as conn:
conn.execute("DELETE FROM Location WHERE land_use='EXTERNAL'")
conn.commit()
for extern in extern.geometry.tolist():
loc = Location(max_loc, net.geotools, net.tables, conn)
loc.land_use = "EXTERNAL"
loc.notes = f"External location"
loc.geo = extern
loc.avg_parking_cost = loc.stop_flag = loc.dir = loc.offset = loc.setback = loc.x = loc.y = loc.tod_distance = 0
loc.truck_org = loc.truck_des = loc.auto_org = loc.auto_des = 1
loc.transit = loc.area_type = loc.lu_area = loc.anchored = loc.popsyn_region = 0
loc.link = net.geotools.get_link_for_point_by_mode(loc.geo, ["AUTO"])
loc.save(conn)
max_loc += 1
# Let's add a single external zone, as this is just an example
# break
DON’T FORGET TO REBUILD LOCATION-LINKS AND RUN GEO-CONSISTENCY#
from polaris.utils.database.db_utils import commit_and_close
net.geo_consistency.update_all()
net.tools.tables.refresh_cache()
net.tools.rebuild_location_links()
net.tools.rebuild_location_parking(maximum_distance=200)
2025-08-17 10:05:29 UTC+0000 - zone geo association for Ev_charging_Stations
2025-08-17 10:05:29 UTC+0000 - zone geo association for Location
2025-08-17 10:05:30 UTC+0000 - zone geo association for Parking
2025-08-17 10:05:30 UTC+0000 - zone geo association for Node
2025-08-17 10:05:30 UTC+0000 - zone geo association for Micromobility_Docks
2025-08-17 10:05:30 UTC+0000 - zone geo association for Transit_Stops
2025-08-17 10:05:30 UTC+0000 - Updating link and walk_link for Location Table
2025-08-17 10:05:30 UTC+0000 - Updating bike_link and walk_link for Location Table
2025-08-17 10:05:31 UTC+0000 - Updating bike_link and walk_link for Parking Table
2025-08-17 10:05:34 UTC+0000 - location geo association for Ev_charging_Stations
2025-08-17 10:05:34 UTC+0000 - area_type geo association for Link
2025-08-17 10:05:34 UTC+0000 - county geo association for Location
2025-08-17 10:05:34 UTC+0000 - popsyn_region geo association for Location
2025-08-17 10:05:35 UTC+0000 - Searching for location link candidates
2025-08-17 10:05:35 UTC+0000 - We could not connect location 2857 with a link of any of the types ['EXPRESSWAY', 'FREEWAY', 'EXTERNAL']
2025-08-17 10:05:35 UTC+0000 - Rebuilding Location Links
2025-08-17 10:05:36 UTC+0000 - Saving Location Links
Closes project#
net.close()
2025-08-17 10:05:36 UTC+0000 - Network closed at /tmp/Bloomington_external_locations/Bloomington-Supply.sqlite
Total running time of the script: (0 minutes 13.776 seconds)