To optimize a route in Python, you can use the Maptitude GISDK. This example can be run outside of Maptitude, even with the Maptitude application being closed. It can be run from within Visual Studio Code, for example.
Table of Contents
1. Download and Install Software
2. Create a Python file
3. Run the Python Script
4. Python Script Execution
Step 1: Download and Install Software
- Download and install Microsoft Visual Studio Code.
- Download and install a free trial of Maptitude mapping software, or use your own license of desktop Maptitude.
- Install the caliperpy package
Step 2: Create a Python File
- Open Visual Studio Code.
- Create a Python file and copy the code below into the file.
- Save the file.
import sys
import os
import traceback
import caliperpy
import pandas as pd
from tkinter import Tk
from tkinter.filedialog import askopenfilename
# Function to prompt user for an Excel file and return the file path
def get_excel_file():
Tk().withdraw()
return askopenfilename(title="Select the Excel file containing addresses", filetypes=[("Excel files", "*.xlsx *.xls")])
# Function to read addresses from the Excel file into a pandas DataFrame
def read_addresses_from_excel(file_path):
df = pd.read_excel(file_path)
print("Data from Excel file:\n", df)
# List of possible column name variations
address_variants = ["Street Address", "Address", "road", "location", "Street"]
city_variants = ["City", "town", "municipality", "location"]
zip_variants = ["ZIP Code", "postal code", "zip", "postcode", "Post Code", "Postal District"]
# Function to detect the correct column name from possible variants
def detect_columns(df, variants):
for variant in variants:
matching_cols = [col for col in df.columns if variant.lower() in col.lower()]
if matching_cols:
return matching_cols[0]
return None
# Detecting the columns
address_col = detect_columns(df, address_variants)
city_col = detect_columns(df, city_variants)
zip_col = detect_columns(df, zip_variants)
if not (address_col and city_col and zip_col):
raise ValueError("The Excel file must contain columns for address, city, and ZIP code.")
return df[[address_col, city_col, zip_col]].to_dict('records'), address_col, city_col, zip_col
# Function to open the default map using MapWizard
def open_default_map(dk):
dataset = dk.CreateGisdkObject(None, "MapWizard.DataSet", None)
dataset.LoadRegion(True)
if dataset.GetRegionFile() is None:
print("No data installed for the selected region.")
return None
basemap = dk.CreateGisdkObject(None, "MapWizard.BaseMap", dataset)
ccmap = basemap.SelectMapForStartup()
if ccmap.Path is None:
print("Could not find a default or initial map for the current region.")
return None
if basemap.Open():
mapname = basemap.GetName()
title = dk.GetMapTitle(mapname)
dk.SetMapFile(mapname, "Initial Map")
dk.SetMapTitle(mapname, title)
basemap.Draw()
return mapname
return None
# Main function to manage the entire workflow
def main():
print("Python interpreter:", sys.executable)
dk = None
try:
# Get the Excel file path from the user
excel_file = get_excel_file()
if not excel_file:
print("No Excel file selected. Exiting.")
return
# Read addresses from the Excel file and get the detected column names
addresses, address_col, city_col, zip_col = read_addresses_from_excel(excel_file)
script_path = caliperpy.GetDirectory(sys.argv[0])
app_name = "Maptitude"
log_file = None if caliperpy.IsApplicationRunning(app_name) else os.path.join(script_path, f"{app_name}.log")
if log_file:
try:
os.remove(log_file)
except OSError:
pass
# Connect to Maptitude
dk = caliperpy.Maptitude.connect(log_file=log_file)
if not dk.IsConnected():
print("Cannot connect to", app_name)
return
print(dk.Dump(dk.GetProgram()))
# Open the default map
map_name = open_default_map(dk)
if map_name is None:
print("Failed to open the default map.")
dk.Close()
return
# Use the extracted addresses as stops for routing
stop_list = []
for address in addresses:
if pd.notna(address.get(address_col)) and pd.notna(address.get(city_col)):
stop = {
"address": address.get(address_col),
"city": address.get(city_col),
"postal_code": str(address.get(zip_col)),
"silent": 1,
"Coordinate": None,
"StopName": "",
"StopDuration": 30
}
# Attempt to find coordinates for the address
finder = dk.CreateGisdkObject(None, "Data.Finder", None).SetRegion()
result = finder.Find("ADDRESS", stop)
if result is not None and "Coord" in dict(result):
stop["Coordinate"] = dict(result)["Coord"]
stop_list.append(stop)
print(f"Address found: {stop['address']}, {stop['city']}, {stop['postal_code']}")
else:
print(f"Cannot find address: {stop['address']}, {stop['city']}, {stop['postal_code']}")
# Break if three valid stops have been found
if len(stop_list) == 3:
break
# Ensure three valid addresses are found
if len(stop_list) < 3:
print("Could not find three valid addresses for routing. Exiting.")
dk.Close()
return
# Routing with the stops
router = dk.CreateGisdkObject(None, "Routing.Router", None)
router.Minimize = "Time"
router.StartTime = dk.CreateTime(9, 0, 0) # 9 AM
router.EndTime = dk.CreateTime(17, 0, 0) # 5 PM
router.IncludeRestStops = True
router.TimeBetweenRests = 240 # minutes
router.RestStopDuration = 20 # minutes
router.FuelPrice = 2.38 # dollars
# Find the shortest path visiting the stops optimized with fixed first stop
path = router.Calculate(stop_list, {"Fix": "First"})
if path is None:
print(router.ErrorMessage)
dk.Close()
return
# Create direction reports in Excel, PDF
excel_report_path = os.path.join(script_path, "Shortest Path Report Time.xlsx")
pdf_report_path = os.path.join(script_path, "Shortest Path Report Time.pdf")
router.CreateReport({"PathObj": path, "OpenReport": 0, "FileName": excel_report_path})
router.CreateReport({"PathObj": path, "OpenReport": 0, "FileName": pdf_report_path})
# Disconnect from Maptitude
caliperpy.Maptitude.disconnect()
if caliperpy.IsApplicationRunning(app_name):
print(app_name, "continues to run.")
else:
print(app_name, "has stopped running.")
except Exception as e:
traceback.print_exc()
if dk:
dk.Close()
finally:
if dk and dk.IsConnected():
dk.Close()
caliperpy.Maptitude.disconnect()
if __name__ == "__main__":
main()
Step 3: Run the Python Script
- In Visual Studio Code, run the script. You can press F5 to do this, for example.
- When prompted, choose an Excel file with addresses for the country that you installed with Maptitude, e.g.,
- “C:\Users\[USER]\Documents\Caliper\Maptitude 2024\Tutorial\Sample Customers United States.xlsx”
- “C:\Users\[USER]\Documents\Caliper\Maptitude 2024\Tutorial\Sample Customers United Kingdom.xlsx”
Step 4: Python Script Execution
- The code prompts the user to select an Excel file containing address information using a file dialog.
- It reads the selected Excel file into a Pandas DataFrame.
- The code attempts to detect columns for street address, city, and postal code by matching various common column names.
- It raises an error if the required columns for address, city, and postal code are not found.
- The code connects to the Maptitude application using `caliperpy` and attempts to open the default map for the selected region.
- It searches for coordinates of up to three valid addresses using the Maptitude geocoding functionality.
- If fewer than three valid addresses are found, the code stops execution.
- The code creates a routing object, sets parameters like start/end times, rest stops, and fuel price, and calculates the shortest route.
- It generates routing reports in Excel and PDF formats, saving them to the script’s directory.
- The code includes exception handling to manage errors and ensures the Maptitude connection is properly closed after execution.
- It manages Maptitude log files in the script’s directory, deleting any old log file before connecting to the application.