May 4, 2008. If you use the Maps add-on software in Plone, you may wish to use a Python script to post locations from a list of latitude and longitude and other data for the locations. The script described here does this for locations of birding spots and hiking trails in southeast Texas. The script was used on the Golden Triangle Audubon Society web site.

This script adds map locations to a Plone web site that is running the Maps add-on software. In this example, we add locations for birding locations and hiking trails in southeast Texas, for the Golden Triangle Audubon Society's web site. Data for these locations includes the county, short name (the name to be used within Plone, as part of the web address of the location), title (name of the location), and latitude and longitude coordinates. This location data was actually created on an earlier version of the web site, and it was saved off for recreation later, which this script does.

The data was pasted into a Python file as line data. You will see the data contained within a triple-quoted string. This string is split into lines of data by use of Python's string.split() method, which we tell to split on the newline, the end-of-line marker, '\n'.

Once we have the data as lines, we loop through the lines and call split() again, this time telling it to split each line on commas. That gives us the data broken out into variables: county, short_name, title, latitude, and longitude.

We loop through one time and add the data to a dictionary using the county name for the key. The values stored in the dictionary, which is called counties_and_locations, is the rest of the data put in a tuple. A tuple is a list of data variables marked by containing parentheses. Data in a tuple is immutable, or unchanging, so this is a fitting storage choice for this static data.

The script will create folders for each county, and will create separate map location objects in the county folders. First , we call Plone's invokeFactory() method (or, the underlying Zope's) to create a high-level folder to contain the county folders. Then we sort the county names, and loop through them, using them in their role as keys to the data dictionary. For each county, we first use invokeFactory() method to create a folder for the county. Then we get the list of locations in the county, which is stored in the dictionary. We sort the location data by the title, so that our locations will be added to the county folder in alphabetical order. Then we are ready to loop through the locations for a given county and call invokeFactory(), this time to create Location objects, passing the location's short_name, title, and latitude and longitude values.

And that's it. We could have the script publish the items, but I did that manually by clicking the main containing folder and using the Contents tab to publish the whole batch of folders and locations in one fell swoop.

I also set the Display for each county folder to be "Map View" -- the desired result, a Google map. For example:

Jefferson County, Texas Birding Locations

Here's the code of the Python script:

context.invokeFactory(id='gtas-locations', type_name='Folder', title='Locations By County')
gtasMapFolder = getattr(context, 'gtas-locations')

# Each line of the data has county, short name for location, location name, latitude, longitude

gtas_data_string = """Angelina County, boykin-springs, Boykin Springs, 31.0891040579  -94.2649269104  
Hardin County, firetower-road, Firetower Road, 30.4753075972 -94.2317962646 
Hardin County, kirby-nature-trail, Kirby Nature Trail, 30.464877 -94.340544 
Hardin County, turkey-creek-trail, Turkey Creek Trail at Gore Store Road, 30.5208642936 -94.3437194824 
Jasper County, ebenezer-park, Ebenezer Park, 31.0739613524 -94.128112793 
Jasper County, sundew-trail, Sundew Trail, 30.552357 -94.411697 
Jasper County, hen-house-unit, Hen House Ridge Unit of MDSP, 30.8459421801 -94.1698265076 
Jasper County, walnut-ridge, Walnut Ridge Unit of MDSP, 30.8631840565 -94.1792678833 
Jasper County, jasper-state-fish-hatchery, Jasper State Fish Hatchery, 30.9466969075 -94.1282844543 
Jasper County, sam-rayburn-reservoir-dam, Sam Rayburn Reservoir Dam, 31.0652862889 -94.0881156921 
Jasper County, sandy-creek-park, Sandy Creek Park, 30.8132183064 -94.1634750366 
Jefferson County, cattail-marsh, Cattail Marsh, 30.007831 -94.143133 
Jefferson County, hillebrandt-bayou-watershed, Hillebrandt Bayou, 29.9285685604 -94.1104745865 
Jefferson County, mcfaddin-nwr, McFaddin National Wildlife Refuge, 29.6685150554 -94.074382782 
Jefferson County, northwest-jefferson-county, Northwest Jefferson County, 29.9580578427 -94.3557357788 
Jefferson County, pilot-station-road, Pilot Station Road, 29.6941668206 -93.852596283 
Jefferson County, pleasure-island, Pleasure Island, 29.8518853121 -93.9424610138 
Jefferson County, sabine-woods, Sabine Woods, 29.6955 -93.958325 
Jefferson County, sea-rim-state-park, Sea Rim State Park, 29.675674346 -94.0439987183 
Jefferson County, taylor-bayou-watershed, Taylor Bayou Watershed, 29.8806251864 -94.2571806908 
Jefferson County, texas-point-nwr, Texas Point National Wildlife Refuge, 29.7078848448 -93.9207458496 
Jefferson County, tyrell-park, Tyrrell Park, 30.0245811058 -94.1486048698 
Newton County, bon-wier, Bon Wier, 30.7393275156 -93.6443710327 
Newton County, wild-azalea-canyons, Wild Azalea Canyons, 30.8932393995 -93.6531257629 
Orange County, bessie-heights, Bessie Heights, 30.0439842247 -93.9228057861 
Orange County, claiborne-west-park, Claiborne West Park, 30.13111667 -93.9212822914 
Orange County, baileys-fish-camp, Bailey's Fish Camp, 29.9845275279 -93.8405799866 
Orange County, tony-houseman-sp, Tony Houseman S. P. (Blue Elbow Swamp), 30.1228950059 -93.7134647369 
San Augustine County, san-augustine-park, San Augustine Park, 31.2023771513 -94.0707778931 
San Augustine County, turkey-hill-wa, Turkey Hill Wilderness Area, 31.3652171566 -94.1710281372 
San Augustine County, jackson-hill-park, Jackson Hill Park, 31.28500592 -94.310760498 
San Augustine County, harvey-creek-park, Harvey Creek Park, 31.2142695783 -94.2644119263 
San Augustine County, powell-park, Powell Park, 31.1137976621 -94.1130924225 
Tyler County, beechwoods-trail, Beechwoods Trail, 30.731212296 -94.2309379578 
Tyler County, campers-cove-park, Campers Cove Park, 30.8220636965 -94.2022705078 
Tyler County, cherokee-unit, Cherokee Unit of MDSP, 30.8539003543 -94.2149734497"""

counties_and_locations = {}
for line in gtas_data_string.split('\n'):
    line = line.strip()
    county,short_name, title,location = line.split(',')
    county = county.strip()
    short_name = short_name.strip()
    title = title.strip()
    lat,lon = location.split()
    lat = float(lat)
    lon = float(lon)

    if county in counties_and_locations:
        counties_and_locations[county].append((short_name, title, lat, lon))
    else:
        counties_and_locations[county] = [(short_name, title, lat, lon)]

# Sort the county names
counties = counties_and_locations.keys()
counties.sort()

for county in counties:
    county_folder_short_name = '-'.join([word.lower() for word in county.split()])
    gtasMapFolder.invokeFactory(id=county_folder_short_name, type_name='Folder', title=county)
    countyFolder = getattr(gtasMapFolder, county_folder_short_name)

    # Sort the data on title, which is the third data item
    counties_and_locations[county].sort(lambda x,y:cmp(x[2],y[2]))

    for location_data_item in counties_and_locations[county]:
        short_name,title,lat,lon = location_data_item
        countyFolder.invokeFactory(id=short_name, title=title, 
                                   type_name='GeoLocation', geolocation=(lat,lon))
        newLocation = getattr(countyFolder, short_name)
        print newLocation.geolocation

return printed

To use the script, I pasted it into a Script (Python) using the Plone web site's ZMI (Zope Management Interface), putting the script in the portal_skins/custom folder, the typical place to put such scripts. Then I invoked the script in my home folder on the web site first, to trouble shoot it. I had to do some iterative debugging/copying/re-invoking, as usual, but once it was working, I then invoked it in the main map folder for the site. Putting scripts in portal_skins/custom is handy that way, because you can call them from anywhere on the Plone site (at any folder location).