May 4, 2008


Posting Map Locations to a Plone Website


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:





























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))


        counties_and_locations[county] = [(short_name, title, lat, lon)]


# Sort the county names

counties = counties_and_locations.keys()



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