blog
blab, blandish, blare, blast, blat, blather, blazen, bleat, blear, bleed, bleep, blench, blend, blight, blink, blit, blitz, bloat, block, blog , bloom, bloop, blossom, blot, blotch, blow, bluff, blunder, blunge, blur, blurt, blush, bluster
a-python-script-to-make-a-google-earth-kmz
This script makes thumbnails of jpg images using Python PIL, then writes a KML file based on data provided to group photos by location, finally zipping it all up into a self-contained KMZ file.
Taking photographs while traveling is a very common activity. I do it for both research and pleasure. Google Earth is a great way to showcase photographs for locations you visit, or locations in a series along a path. In Google Earth, a discrete location can be set with a placemark, for which you can provide a description. Using well-known HTML methods in the description, you can add links to photographs. Placemarks are described in a KML file. The KML file, along with the images, is zipped up into a KMZ file to complete the job, and the KMZ file can be distributed to others who simply open it to view the locations and photographs.
Here is what Google Earth looks like for a placemark made by this script:
For this project I exported selected photos from iPhoto to a trip images folder at full image size. I used photographs from a trip to Seattle and the Olympic Peninsula of Washington in 2007. I renamed the files with a prefix olympic-001, so that filenames became olympic-003-DSC_0056.jpg, olympic-003-DSC_0057.jpg, etc. for a given location.
In the python file, I typed the prefix (e.g., olympic-003) for each location, the latitude and longitude, and a description into a series of text strings, delimited by the vertical bar (|), which works well as a data delimiter. The script first parses this data into a Python dictionary of location codes and the images for each location.
Then a 'kmz' directory is created.
And into this 'kmz' directory, into a subdirectory called 'files', are written thumbnail images for the photographs, appending '-thumb' to the file basenames.
The KML file is written to contain the placemarks for the locations. The main trick here is to embed HTML tags for the description and thumbnail image links within a CDATA-bounded description section. The thumbnail image links have the form 'files/olympic-003-DSC_0057-thumb.jpg'.
Finally, an os.system() call is made to zip up the KML file and the thumbnail images in the files directory. Here's the actual KMZ file (olympic.kmz) made in this example.
Here's the script,
pykmz.py
UPDATE: Added a proper package under revision control so I can fiddle with it.
UPDATE 2: Changed the name from kmzmaker to pykmz, because there is already a Java-based kmzmaker.
And here is the screen output from the script, for this example:
jeffs-macbook:pykmz jeff$ python pykmz.py ---------------- Thumbnails... Wrote: kmz/files/olympic-001-DSC_0003_thumb.jpg Wrote: kmz/files/olympic-002-DSC_0013_thumb.jpg Wrote: kmz/files/olympic-003-DSC_0055_thumb.jpg Wrote: kmz/files/olympic-003-DSC_0056_thumb.jpg Wrote: kmz/files/olympic-003-DSC_0057_thumb.jpg Wrote: kmz/files/olympic-003-DSC_0060_thumb.jpg Wrote: kmz/files/olympic-004-DSC_0070_thumb.jpg Wrote: kmz/files/olympic-004-DSC_0075_thumb.jpg ---------------- KML file... Wrote: kmz/olympic.kml ---------------- Zipping KMZ... adding: files/ (stored 0%) adding: files/olympic-001-DSC_0003_thumb.jpg (deflated 1%) adding: files/olympic-002-DSC_0013_thumb.jpg (deflated 3%) adding: files/olympic-003-DSC_0055_thumb.jpg (deflated 1%) adding: files/olympic-003-DSC_0056_thumb.jpg (deflated 3%) adding: files/olympic-003-DSC_0057_thumb.jpg (deflated 6%) adding: files/olympic-003-DSC_0060_thumb.jpg (deflated 1%) adding: files/olympic-004-DSC_0070_thumb.jpg (deflated 5%) adding: files/olympic-004-DSC_0075_thumb.jpg (deflated 2%) adding: olympic.kml (deflated 73%) Zipped: kmz/olympic.kmz
downloading-waypoints-and-tracks-with-garmin-geko
I forgot how to do this and it took me way too long to figure it out again, so here are notes for myself and anyone else who has these GPS units and a Mac.
The Garmin Geko 201/301 GPS Units
The Garmin Geko 201 is an inexpensive basic unit. Amazon currently has this unit for $91.40. Garmin has nice user manuals available as PDFs, e.g. the Geko 201 user manual .
The Garmin Geko 301 is also a basic unit, with an integrated compass added. Amazon currently has the unit for $148.95. I bought mine as a reconditioned unit for somewhat less, can't recall.
These units use the etrex data cable, available from Amazon for about $25:
The Keyspan USB to Serial Adapter
This is a handy and robust adapter, which Amazon now has for about $25.
The purpose of the device, as its "No Serial Port, No Problem!" descriptor says, is used to connect legacy non-USB devices to a computer. The Keyspan unit comes with a utility called Keyspan Serial Assistant, which simply shows the port name after you install the driver:
This helps you learn the name of the driver when you are using software needing to communicate with a target device, and to confirm that it is working. Note the names KeySerial1 and USA19H1a2P1.1, which is how the port will show in software setups -- either one works for this Garmin hook-up.
The product identifier for this Keyspan unit is USA-19HS. The HS designation, for High Speed, marks this unit as a replacement for an earlier version of the device. Some software currently lists the HS version as compatible, but not earlier ones.
LoadMyTracks
This free utility software from ClueTrust of Virginia does the job of downloading tracks and waypoints to your Mac. After the Keyspan driver is installed, check it with the Keyspan Serial Assistant, then fire up LoadMyTracks:
Note that I selected Garmin Serial for the protocol, and picked KeySerial1 as the port where I connected the GPS unit -- the GPS unit uses an old-style serial cable, which is connected via the Keyspan USB adapter. You might think, as I did initially, that it would show up on the Mac as a USB connection, but no, it is still a serial connection. For this task, my normal routine is to check Tracks and Waypoints, and KML for the output format, before downloading from the GPS unit to my Mac.
A key point -- the one I forgot: before the GPS unit is recognized and is ready for the connection, you have to navigate on the GPS unit to Menu, then Setup, then Interface, then select Garmin as the protocol. I sometimes connect to my GPS using the NMEA interface for realtime downloading of data using Python and gpsd, and I forgot to switch it back to Garmin, causing me to chase red herrings for too long. Hence, this blog post!
Now to using the data...
tag-cloud-image
This Python program extends the tag cloud idea to image generation, doing a little filtering on text to remove stop words and common words first. The program uses the Python Imaging Library (PIL) to generate the image. It doesn't do colors yet, but that will be easy to add. This blog post starts development of a proper software program, called TagCloudImage. You follow may development in the software directory.
Tag clouds are handy visual summaries used to represent the frequency of keywords used on a web site. One of the biggest tag clouds I've seen is the one that shows the frequency of categories of software hosted at launchpad.net . You also see one showing keyword frequency on this web site, which is using using the Plone TagCloud product. As you read in TagCloud's description, the frequencies are classed using a power function, following the suggestion by Anders Pearson . This algorithm is also used in TagCloudImage.
I've played around with applying it to long text items. Tag clouds offer promise for quickly grasping what a body of text concerns. I am thinking of applying it to student reports, for example.
I do a good proportion of my work on the Unix command line, so a simple image-generating program is useful. TagCloudImage could be extended as a Plone software add-on. For example, in the Plone environment, a site administrator could periodically create a new snapshot tag cloud image.
TagCloudImage could also be extended with graphics, using simple outlined or filled rectangles as background to text items, or using specialized cloud, bubble, or other backgrounds. The graphics library currently used is the Python Imaging Library (PIL) .
TagCloudImage does the following:
- Find all the words and make a list of unique words in the text, removing punctuation.
- Remove stop words ., using sources from here and here .
- Remove common words, using this source of 1000 or so common words .
- Count the words that survive the filtering.
- Use the power function algorithm mentioned above to put the words into classes.
- Draw and save a tag cloud image showing the words in alphabetical order and in a font and size according to definition of each class.
Here's the code (Everything is included in this file, except for the list of common words ):
#!/usr/bin/env python
import math
from string import punctuation
import Image
import ImageDraw
import ImageFont
# See also, for the power function to get tag classes:
# http://thraxil.com/users/anders/posts/2005/12/13/scaling-tag-clouds/
# and
# http://behemoth.ccnmtl.columbia.edu/test/clouds/cloud.txt
# For stop words:
# http://www.dcs.gla.ac.uk/idom/ir_resources/linguistic_utils/stop_words
stop_words = """a about above across after afterwards again against all
almost alone along already also although always am among amongst amoungst
amount an and another any anyhow anyone anything anyway anywhere are
around as at back be became because become becomes becoming been before
beforehand behind being below beside besides between beyond bill both
bottom but by call can cannot cant co computer con could couldnt cry
de describe detail do done down due during each eg eight either eleven
else elsewhere empty enough etc even ever every everyone everything
everywhere except few fifteen fify fill find fire first five for
former formerly forty found four from front full further get give go
had has hasnt have he hence her here hereafter hereby herein hereupon
hers herself him himself his how however hundred i ie if in inc indeed
interest into is it its itself keep last latter latterly least less
ltd made many may me meanwhile might mill mine more moreover most
mostly move much must my myself name namely neither never nevertheless
next nine no nobody none noone nor not nothing now nowhere of off
often on once one only onto or other others otherwise our ours
ourselves out over own part per perhaps please put rather re same see
seem seemed seeming seems serious several she should show side since
sincere six sixty so some somehow someone something sometime sometimes
somewhere still such system take ten than that the their them
themselves then thence there thereafter thereby therefore therein
thereupon these they thick thin third this those though three through
throughout thru thus to together too top toward towards twelve twenty
two un under until up upon us very via was we well were what whatever
when whence whenever where whereafter whereas whereby wherein
whereupon wherever whether which while whither who whoever whole whom
whose why will with within without would yet you your yours yourself
yourselves"""
stop_words = stop_words.split()
# For more stop words:
# http://dev.mysql.com/tech-resources/articles/full-text-revealed.html
more_stop_words = """a's, able, about, above, according, accordingly,
across, actually, after, afterwards, again, against, ain't, all,
allow, allows, almost, alone, along, already, also, although, always,
am, among, amongst, an, and, another, any, anybody, anyhow, anyone,
anything, anyway, anyways, anywhere, apart, appear, appreciate,
appropriate, are, aren't, around, as, aside, ask, asking, associated,
at, available, away, awfully, be, became, because, become, becomes,
becoming, been, before, beforehand, behind, being, believe, below,
beside, besides, best, better, between, beyond, both, brief, but, by,
c'mon, c's, came, can, can't, cannot, cant, cause, causes, certain,
certainly, changes, clearly, co, com, come, comes, concerning,
consequently, consider, considering, contain, containing, contains,
corresponding, could, couldn't, course, currently, definitely,
described, despite, did, didn't, different, do, does, doesn't, doing,
don't, done, down, downwards, during, each, edu, eg, eight, either,
else, elsewhere, enough, entirely, especially, et, etc, even, ever,
every, everybody, everyone, everything, everywhere, ex, exactly,
example, except, far, few, fifth, first, five, followed, following,
follows, for, former, formerly, forth, four, from, further,
furthermore, get, gets, getting, given, gives, go, goes, going, gone,
got, gotten, greetings, had, hadn't, happens, hardly, has, hasn't,
have, haven't, having, he, he's, hello, help, hence, her, here,
here's, hereafter, hereby, herein, hereupon, hers, herself, hi, him,
himself, his, hither, hopefully, how, howbeit, however, i'd, i'll,
i'm, i've, ie, if, ignored, immediate, in, inasmuch, inc, indeed,
indicate, indicated, indicates, inner, insofar, instead, into,
inward, is, isn't, it, it'd, it'll, it's, its, itself, just, keep,
keeps, kept, know, knows, known, last, lately, later, latter,
latterly, least, less, lest, let, let's, like, liked, likely, little,
look, looking, looks, ltd, mainly, many, may, maybe, me, mean,
meanwhile, merely, might, more, moreover, most, mostly, much, must,
my, myself, name, namely, nd, near, nearly, necessary, need, needs,
neither, never, nevertheless, new, next, nine, no, nobody, non, none,
noone, nor, normally, not, nothing, novel, now, nowhere, obviously,
of, off, often, oh, ok, okay, old, on, once, one, ones, only, onto,
or, other, others, otherwise, ought, our, ours, ourselves, out,
outside, over, overall, own, particular, particularly, per, perhaps,
placed, please, plus, possible, presumably, probably, provides, que,
quite, qv, rather, rd, re, really, reasonably, regarding, regardless,
regards, relatively, respectively, right, said, same, saw, say,
saying, says, second, secondly, see, seeing, seem, seemed, seeming,
seems, seen, self, selves, sensible, sent, serious, seriously, seven,
several, shall, she, should, shouldn't, since, six, so, some,
somebody, somehow, someone, something, sometime, sometimes, somewhat,
somewhere, soon, sorry, specified, specify, specifying, still, sub,
such, sup, sure, t's, take, taken, tell, tends, th, than, thank,
thanks, thanx, that, that's, thats, the, their, theirs, them,
themselves, then, thence, there, there's, thereafter, thereby,
therefore, therein, theres, thereupon, these, they, they'd, they'll,
they're, they've, think, third, this, thorough, thoroughly, those,
though, three, through, throughout, thru, thus, to, together, too,
took, toward, towards, tried, tries, truly, try, trying, twice, two,
un, under, unfortunately, unless, unlikely, until, unto, up, upon,
us, use, used, useful, uses, using, usually, value, various, very,
via, viz, vs, want, wants, was, wasn't, way, we, we'd, we'll, we're,
we've, welcome, well, went, were, weren't, what, what's, whatever,
when, whence, whenever, where, where's, whereafter, whereas, whereby,
wherein, whereupon, wherever, whether, which, while, whither, who,
who's, whoever, whole, whom, whose, why, will, willing, wish, with,
within, without, won't, wonder, would, would, wouldn't, yes, yet,
you, you'd, you'll, you're, you've, your, yours, yourself,
yourselves, zero"""
more_stop_words = more_stop_words.split(', ')
stop_words = stop_words + filter(lambda x:x not in stop_words, more_stop_words)
# For common words:
#
# http://www.uri.edu/comm_service/cued_speech/amerpron.html
#
# which says about the source fo the 1000 or so words:
#
# compiled mostly from words in a special dictionary by Robert
# Shaw, The New Horizon Ladder Dictionary, New York: Popular
# Library, Inc., 1969, for the United States Information Agency.
# The bias favors words in print rather than frequency of spoken
# English words.
common_words = open('common-words.txt').read().split()
excluded_words = stop_words + filter(lambda x:x not in stop_words, common_words)
width = 450
height = width
image_left_margin = 5
image_right_margin = 5
# Find fonts on your system and set this path:
fontPath="/Library/Fonts/arial.ttf"
arial18 = ImageFont.truetype(fontPath,18)
arial28 = ImageFont.truetype(fontPath,28)
arial36 = ImageFont.truetype(fontPath,36)
arial48 = ImageFont.truetype(fontPath,48)
arial64 = ImageFont.truetype(fontPath,64)
font_classes = [arial18, arial28, arial36, arial48, arial64]
words_and_counts = {}
for line in open('geojeff_twitter.txt').readlines():
line = line.strip()
possible_start_times = ['%02d:' % num for num in range(24)]
for possible_start_time in possible_start_times:
if possible_start_time in line:
line = line[:line.find(possible_start_time)]
words = line.split()
for word in words:
word = word.strip(punctuation)
if len(word) > 0:
if word.endswith('\'s'):
word = word[:-2]
exclude = False
try:
number = int(word)
except (ValueError, IndexError):
number = None
if number:
exclude = True
elif 'http' in word:
exclude = True
elif len(word) == 1:
exclude = True
if not exclude:
word = word.lower()
if word not in excluded_words:
words_and_counts[word] \
= words_and_counts.get(word, 0) + 1
tags = words_and_counts.items()
tags.sort()
levels = 5
def ex_weights(l): return [int(w) for (t,w) in l]
max_weight = max(ex_weights(tags))
min_weight = min(ex_weights(tags))
thresholds = [math.pow(max_weight - min_weight + 1,float(i) \
/ float(levels)) for i in range(0,levels)]
def class_from_weight(w,thresholds):
i = 0
for t in thresholds:
i += 1
if w <= t:
return i
return i
im = Image.new("RGB",(width,height),"#ddd")
draw = ImageDraw.Draw(im)
textsizes = [draw.textsize('Boy', font_classes[0]),
draw.textsize('Boy', font_classes[1]),
draw.textsize('Boy', font_classes[2]),
draw.textsize('Boy', font_classes[3]),
draw.textsize('Boy', font_classes[4])]
x = 0
# fix width to at least fit the longest item
for (t,w) in tags:
c = class_from_weight(w,thresholds) - 1
font = font_classes[c]
word_w,word_h = draw.textsize(t, font=font)
if (x + word_w) > width:
width = x + word_w
lines = []
current_line = []
x = 0
y = 0
word_left_margin = 0
max_font_index = 0
for (t,w) in tags:
c = class_from_weight(w,thresholds) - 1
font = font_classes[c]
word_w,word_h = draw.textsize(t, font=font)
# set the x position
if len(current_line) == 0:
x = image_left_margin
word_left_margin = 0
else:
previous_word_w,previous_word_h = current_line[-1][3]
word_left_margin = int((float(previous_word_w) * 0.05 \
+ float(word_w) * 0.05) / 2.0)
x = x + previous_word_w + word_left_margin
if (x + word_w) < width:
current_line.append((t, c, (x,y), (word_w,word_h),
font, word_left_margin))
if c > max_font_index:
max_font_index = c
else:
lines.append(current_line)
y = y + textsizes[max_font_index][1]
max_font_index = 0
current_line = []
x = image_left_margin
word_left_margin = 0
current_line.append((t, c, (x,y), (word_w,word_h),
font, word_left_margin))
if c > max_font_index:
max_font_index = c
image_width = 0
image_height = 0
lines_adjusted = []
for line in lines:
width_for_line = 0
max_height_for_line = 0
for word_data in line:
x,y = word_data[2]
if x > width_for_line:
width_for_line = x
word_left_margin = word_data[5]
word_w,word_h = word_data[3]
width_for_line += word_left_margin + word_w
if word_h > max_height_for_line:
max_height_for_line = word_h
line_adjusted = []
for word_data in line:
word_w,word_h = word_data[3]
if word_h != max_height_for_line:
x,y = word_data[2]
y_adjusted = y + int((float(max_height_for_line) \
- float(word_h)) * 0.5)
line_adjusted.append((word_data[0], word_data[1],
(x,y_adjusted), word_data[3],
word_data[4], word_data[5]))
else:
line_adjusted.append(word_data)
lines_adjusted.append(line_adjusted)
if image_width < width_for_line:
image_width = width_for_line
image_height += max_height_for_line
im = Image.new("RGB",(image_width,image_height),"#ddd")
draw = ImageDraw.Draw(im)
for line in lines_adjusted:
for word_data in line:
word = word_data[0]
x,y = word_data[2]
font = word_data[4]
draw.text((x,y),word,font=font,fill="black")
im.save("tagcloud.png")
for line in lines_adjusted:
for word_data in line:
print word_data[0]
And here is the image that is produced when I apply the program to my first 42 Twitter updates, to summarize the subjects I had been tweeting about (451x2508 pixels!):
my-spore-creation-li..ih..ihh..ihhhves
Want to try your luck at evolution? Want to spend countless hours playing a computer game? Want to have some fun and laughter?
Meet BiSpike
From humble beginnings,
To a terrestrial sphere,
Walked a curious creature,
With sharp beak to fear.
It was scythed, and stood in a linebacker pose,
Then taller and longer, with fangs on the nose,
Then squatty again, in tri-eyed regression,
Came antennae splayed wide,
And a colored expression,
To gawky composure and a very long neck,
came eyes all around,
And a mustache on deck,
Bedazzling at present, with feather and fez,
Ready for anything, going off the rez!
BiSpike thrives in the wild world of Spore ,
a cool game by Maxis you cannot ignore.
Learning evolution is unbelievably virtuous,
Go out now and unabashedly purchase it! :)
posting-map-locations-to-a-plone-web-site
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))
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).