Subversion Repositories Misc

[/] [dslmap/] [forgottenislanderbot/] [fib.py] - Rev 19

Compare with Previous | Blame | View Log

#!/usr/bin/env python
 
# forgottenislanderbot - makes a DSL coverage map from Bell Aliant's website.
# Copyright (c) 2010 Art Ortenburger
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
 
# fib.py
# by Art Ortenburger IV <utrrrongeeb a users , sf , net>
# written 2010-01-25 -
#
# forgottenislanderbot -- unified interface
#
# Provides all forgottenislanderbot functions from
# one command-line interface, using standard-form
# command-line options with flexibility.
#
 
 
import sys # for argument input and stream buffer flushing
import optparse # for command and option parsing
import sqlite3 # for all database operations
import zipfile # for KMZ output
import StringIO # for KML output
import traceback # maybe used once for debugging
import robotparser # Robot Exclusion Standard; user may override
import urllib # 
import httplib # loading web page
import time # sleeping between requests
import random # randomizing sleeping, etc.
 
 
sd = None  # Sqlite Database
sdc = None # Sqlite Database Connection
# names for each DSL status
dslnames = ["ERROR","EMPTY","NODSL","BASIC","ULTRA","TOTAL"]
# collected statistics stored here
statistics = [0, 0, 0, 0, 0, 0]
kmlbuffer = None # KML file to write
kmlIconHighlitScale = 1.1 # coefficient for highlit icon size in KML
GMIconURLs = ["http://maps.google.com/mapfiles/kml/pushpin/purple-pushpin.png",
"http://maps.google.com/mapfiles/kml/pushpin/wht-pushpin.png",
"http://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png",
"http://maps.google.com/mapfiles/kml/pushpin/ltblu-pushpin.png",
"http://maps.google.com/mapfiles/kml/pushpin/grn-pushpin.png"]
GMIconHotspot = [ 20, 2 ]
# hostname of Bell Aliant server
ba_host = "productsandservice.bellaliant.net"
# webpage which so conveniently does a whole address check with one request. Such a shame it won't take the Range: bytes= header....
ba_handler = "/PS/pe/english/productsandservices/qualificationFullAddressCheck.do"
 
 
def main():
	""" main UI function for forgottenislanderbot"""
 
	print sys.argv[0],"""(forgottenislanderbot unified interface)    Version 0.1
Copyright (c) 2010 Art Ortenburger. Licensed under the GNU GPL; no warranty."""
 
	# ease argument parsing. The two parsers make laziness look elegent -- 
	# you can run "fib.py --help" to get a command list,
	# or "fib.py --all --help" to get an option list.
	# usage strings must be set appropriately.
	# later: add --version?
	# command parser -- only parses one arg.
	cmdParser = optparse.OptionParser(usage="")
	# option parser -- parses anything else
	optParser = optparse.OptionParser(usage="")
 
	# commands. At the moment, only one command is allowed, but by using
	# the "append" or "callback" actions I might make it multi-command later.
	# later: single-char options?
	cmdParser.add_option("--all", action="store_true", dest="auto", default=False,
		help="do everything -- get civic addresses, run bot, and produce KMLs")
	cmdParser.add_option("--newdb", action="store_true", dest="newdb", default=False,
		help="download civic addresses and make a new, empty database")
	# later: add commands to only download, only make/merge
	cmdParser.add_option("--crawl", action="store_true", dest="crawl", default=False,
		help="query the Bell Aliant server, finding DSL statuses")
	cmdParser.add_option("--kml", action="store_true", dest="kml", default=False,
		help="generate a KML overlay from a database")
	cmdParser.add_option("--stats", action="store_true", dest="stats", default=False,
		help="print the totals of each DSL status")
	cmdParser.add_option("--list", action="store_true", dest="listdb", default=False,
		help="print every selected address")
	# later: add merge/update, diff, strip commands
 
	# options. All options are...optional.
	# later: single-char options?, better help
	# Global options:
	optParser.add_option("--db", action="store", dest="db", default="database.sqlite",
		help="SQlite database file to work with. Default: database.sqlite", metavar="FILE")
	# there are NOT going to be individual dslmasks, sparsities, maxrows, latlons, 
	# custom SQLs, endofroads, or nocities for --crawl, --stats, and --kml. Period.
	# anyone needing such functionality will have to use a shell script and multiple
	# invocations, or modify the code themselves.
	optParser.add_option("--dslmask", action="store", dest="dslmask", type="string",
		default=None, metavar="MASK", 
		help="DSL types to process; y/n for each of EMPTY, NODSL, BASIC, & ULTRA. Overrides defaults: crawl=ynnn, stats=yyyy, kml=nyyy")
	optParser.add_option("--sparsity", action="store", dest="sparsity", type="int",
		default=None, metavar="INT",
		help="minimum difference in civic numbers along a road to process. Overrides defaults: crawl=1, stats=1, kml=40")
	optParser.add_option("--nocities", action="store_true", dest="nocities", default=False,
	help="don't process addresses in Charlottetown or Summerside")
	optParser.add_option("--max", action="store_true", dest="maxrows", default=False,
		help="process more addresses when using a >1 sparsity.")
	optParser.add_option("--always-get", action="store", dest="alwaysget",
		default="first", metavar="HOUSE",
		help="ignoring sparsity, which address on the end of a road to always get. Values: 'none', 'first' (default), 'last', 'both'.")
	optParser.add_option("--sql-where", action="store", dest="sqlwhere", type="string",
		default=None, metavar="SQL", help="custom SQL SELECT conditions. Inserted directly after WHERE. Be careful!")
	optParser.add_option("--coord-rect", action="store", dest="coordrect", type="string",
		default=None, metavar="latA,longA,latB,longB", help="latitude/longitude coordinates limiting processing to addresses within the rectangle")
	# newdb, merge options:
	optParser.add_option("--tsv", action="append", dest="tsv", type="string",
		default=None, metavar="TSV-FILE", help="TSV database of civic addresses to add to SQlite database. Can appear multiple times.")
	# crawler-bot options:
	optParser.add_option("--override-robot-exclusion-standard", action="store_true",
		dest="ores", default=None, help="override a robots.txt, instead of asking")
	optParser.add_option("--quiet-fail", action="store_false", dest="ores",
		default=None, help="don't ask about a robots.txt -- just exit")
	optParser.add_option("--delay", action="store", type="float", dest="delay",
		default=1.0, metavar="SECONDS", help="time to sleep between single-threaded requests to Bell Aliant server. Can be float. Default: 1.0")
	optParser.add_option("--no-connect", action="store_true", dest="noconnect",
		default=False,help="Don't reconnect HTTP connection between requests. Experimental, will probably fail.")
	optParser.add_option("--useragent", action="store", dest="useragent", default="forgottenislanderbot",
		metavar="NAME", help="user-agent for the bot to represent itself with. Default: %default")
	optParser.add_option("--pause-every", action="store", dest="pauseblock", type="int",
		default=20, metavar="QUERIES", help="number of requests to run between database-commit / kill-opportunity sleep")
	optParser.add_option("--pause-time", action="store", dest="pausetime", type="float",
		default=3, metavar="SECONDS", help="time to sleep at an intermittent pause")
	# stats options:
	# kml options:
	optParser.add_option("--map",action="store",type="string",dest="kmlfile",
		default="dslmap.kml",metavar="KMLFILE",help="file to write KML to. Will be overwritten. Default: dslmap.kml")
	optParser.add_option("--globe",action="store",type="string",dest="kmlglobe",
		default=None, metavar="[GE|NWW]", help="virtual-globe to optimize KML for")
	optParser.add_option("--civic-addresses-in-kml", action="store_true", dest="keepaddrs",
		default=False, help="Label icons in KML by civic address. May raise privacy concerns, file size, lag.")
	optParser.add_option("--no-highlight", action="store_true", dest="nohighlight",
		default="False", help="Disable highlightable icons in KML. Experimental.")
	optParser.add_option("--kmz", action="store_true", dest="kmz", default=False,
		help="Make a compressed KMZ from the KML.")
	optParser.add_option("--iconsize", action="store", type="float", dest="iconsize",
		default="1.0", metavar="FLOAT", help="Size of KML icons. Default: 1.0")
	optParser.add_option("--icontheme", action="store", type="string", dest="icontheme",
		default="GM", metavar="[NAME|ante*post.png]",
		help="Icons to use in KML. Either builtin, like 'GM', or external, like "
		"'dsl-*.png', where '*' comprises 'ERROR', 'EMPTY', 'NODSL', 'BASIC', and 'ULTRA', "
		"in separate files. It can also be a URL.")
	optParser.add_option("--iconhotspot", action="store", type="string", dest="iconhotspot",
		default=None,metavar="x,y",
		help="Pixel coords in an icon where it is aligned over the lat/long coords.")
	optParser.add_option("--iconcolours",action="store", type="string", dest="iconcolours",
		default=None, metavar="LIST",help="Colours to tint KML icons, "
		"as comma-separated list of five colours like 'bf0000ff', in 'AABBGGRR' format.")
	# csv options:
	optParser.add_option("--csvfile", action="store", type="string", dest="csvfile",
		default="coords.csv", metavar="CSVFILE", help="file to write CSV to. Will be overwritten. Default: %default")
	# ui options:
	optParser.add_option("--pipeable", action="store_true", dest="pipeable", default=False,
		help="Changes stdout flushing behavior. May cause problems on some systems. Not implemented.")
	optParser.add_option("--verbose", action="store_true", dest="verbose", default=False,
		help="Prints additional information. Not implemented.")
	optParser.add_option("--debug", action="store_true", dest="debug", default=False,
		help="Prints massive amounts of debug information. Implemented as necessary.")
 
 
	if len(sys.argv) <= 1: # no command, can't do anything
		cmdParser.print_help()
		exit(0)
 
	# parse command
	(cmd,cmdargs) = cmdParser.parse_args([sys.argv[1]])
	# parse options
	(opt, optargs) = optParser.parse_args(sys.argv[2:])
 
	# extract and parse dslmask and sparsity -- the first time
	dslmask = (opt.dslmask and [parseDSLMask(opt.dslmask)] or [None])[0]
	sparsity = (opt.sparsity and [opt.sparsity] or [None])[0]
	# individual mode dslmasks and sparsities
	crawldslmask = (dslmask or [True,False,False,False])
	kmldslmask = (dslmask or [False,True,True,True])
	csvdslmask = (dslmask or [False,True,False,False])
	statsdslmask = (dslmask or [True,True,True,True])
	listdbdslmask = (dslmask or [False,True,False,False])
	crawlsparsity = (sparsity or 1)
	kmlsparsity = (sparsity or 40)
	csvsparsity = (sparsity or 1)
	statssparsity = (sparsity or 1)
	listdbsparsity = (sparsity or 1000)
	# change default name to .kmz if necessary
	outkml = ((opt.kmz and opt.kmlfile == "dslmap.kml") and ["dslmap.kmz"] or [opt.kmlfile])[0]
 
	# take action depending on mode
	try:
		if cmd.auto: # do the whole thing
			initdb(opt.db)
			newdb(sdc, opt)
			crawl(sdc, crawldslmask, crawlsparsity, opt)
			# loop, checking stats, to repeat crawl
			kml(sdc, kmldslmask, kmlsparsity, opt, outkml)
			stats(sdc, statsdslmask, statssparsity, opt, True)
		elif cmd.newdb:
			initdb(opt.db)
			newdb(sdc, opt);
		elif cmd.crawl:
			initdb(opt.db)
			crawl(sdc, crawldslmask, crawlsparsity, opt)
		elif cmd.kml:
			initdb(opt.db)
			kml(sdc, kmldslmask, kmlsparsity, opt, outkml)
		elif cmd.stats:
			initdb(opt.db)
			stats(sdc, statsdslmask, statssparsity, opt, True)
		elif cmd.listdb:
			initdb(opt.db)
			listdb(sdc, listdbdslmask, listdbsparsity, opt)
		else:
			cmdParser.print_help()
	except Exception, errdet:
		print "Failing. Command could not be completed. Data may not have been saved."
		print "Error:",errdet
		traceback.print_exc()
		done(1)
	done()
 
def done(errcode=0):
	""" Exits safely,
		committing, flushing, and closing anything that needs it."""
	try:
		sdc.close()
	except:
		pass
	try:
		sd.commit()
		sd.close()
	except:
		pass
	exit(errcode)
 
 
def initdb(dbn): # opens the database
	""" opens sqlite database for use by FIB UI."""
	try:
		globals()['sd'] = sqlite3.connect(dbn)
		globals()['sdc'] = sd.cursor()
	except Exception, errdet:
		print "Failing: couldn't open database ",dbn,": ", errdet
		print sys.exc_info()[0]
		done(1)
 
def newdb(dc, opt): # downloads civic address databases, and merges them into a new database
	pass
 
def crawl(dc, opt): # real bot part -- queries
	pass
 
def kml(dc, dslmask, sparsity, opt, outkml): # generates KML file
	""" Generates a KML file from a database.
	dc:
		sqlite3.Cursor to FIB database.
	dslmask:
		4 bools in list.
		Whether to include in KML output
		each of EMPTY, NODSL, BASIC, & ULTRA
		categories. Usually [False,True,True,True]
	sparsity:
		int; minimum civic-number distance
		between houses to output. This is
		to avoid slowing Google Earth to a
		crawl with 68023 placemarks.
	opt:
		key-value list of miscellaneous options.
		Documentation is elsewhere.
	outkml:
		string; path to KML file to write.
		Will be overwritten."""
	globals()['kmlbuffer'] = StringIO.StringIO()
	kmlbuffer.write(kmlMakeHeader(outkml,opt))
	processDB(dc,kmlItemAction,dslmask,sparsity,opt)
	kmlbuffer.write(kmlMakeFooter(outkml,opt))
 
	kmlfile = None
	kmzzip = None
	try:
		kmlfile = open(outkml,"wb")
		if opt.kmz: # write a zipfile
			kmzzip = zipfile.ZipFile(kmlfile,'w',zipfile.ZIP_DEFLATED)
			kmzzip.writestr("doc.kml",kmlbuffer.getvalue()) # doc.kml is the special name required by Google Earth
			kmzzip.close()
		else: # write normal KML
			kmlfile.write(kmlbuffer.getvalue())
		kmlfile.flush()
		kmlfile.close()
	except Exception, errdet:
		print "Error writing KML:",errdet
		print sys.exc_info()[0]
		try:
			kmzzip.close()
		except:
			pass
		try:
			kmlfile.flush()
			kmlfile.close()
		except:
			pass
	kmlbuffer.close()
 
def stats(dc, dslmask, sparsity, opt, printtable): # prints totals of each DSL type
	""" Prints statistics from civic address database.
	dc:
		sqlite3.Cursor to FIB database
	dslmask:
		4 bools in list.
		Whether to sum in output
		each of EMPTY, NODSL, BASIC, & ULTRA
		categories. Usually [True,True,True,True]
	sparsity:
		int; minimum civic-number distance
		between houses to output. This is
		to find out what a given sparsity
		will do to the crawler or Google Earth.
		For totals, use 1.
	opt:
		key-value list of miscellaneous options.
		Documentation is elsewhere.
	printtable:
		bool; whether to print table, or just
		return the values."""
 
	# reset statistics totals
	for i in range(len(statistics)):
		statistics[i] = 0
	# use utility function to loop through database
	processDB(dc,statsItemAction,dslmask,sparsity,opt)
	if printtable:
		print # newline needed
		# print totals table
		for i in range(len(dslnames)):
			print dslnames[i],":         ",statistics[i]
	return statistics
 
def listdb(dc, dslmask, sparsity, opt):
	""" Prints addresses from civic address database.
	dc:       sqlite3.Cursor to FIB database
	dslmask:    
	sparsity:   int; minimum civic-number distance
		    between houses to output. This is
		    to find out what a given sparsity
		    will do to the crawler or Google Earth.
		    For totals, use 1.
	opt:"""
	processDB(dc,listItemAction,dslmask,sparsity,opt)
	sys.stdout.flush()
 
def processDB(dc, actionfunc, dslmask, sparsity, opt):
	""" Loops through SQlite database, selecting rows
	based on parameters, and calling the specified function
	for each selected row.
	dc:
		sqlite3.Cursor to civic address database from fib-cadb2sql.py
	actionfunc:
		function to call for selected addresses.
		Must accept these parameters:
			sqlite3.Cursor
			town (string)
			road (string)
			civic number (int)
			dsl status of address (int)
			longitude (float)
			latitude (float)
			option set (key:value list)
	dslmask:
		4 bools in a list.
		Whether to call actionfunc() for rows
		in the EMPTY, NODSL, BASIC, or ULTRA
		categories.
	sparsity:
		int; minimum civic-number difference between houses to process.
		To get every house, enter 1.
		For about one house per road, enter 10000.
	opt:
		key-value list of miscellaneous options.
		Documentation is elsewhere."""
	# parse the lat/lon rectangle, if needed
	coordrect = (opt.coordrect and True)
	if coordrect:
		coordmaskstr = opt.coordrect.split(',')
		coordmask = [0.0, 0.0, 0.0, 0.0]
		# will not allow invalid args to fail this, but will complain
		if len(coordmaskstr) < 4:
			print "invalid coordinate rectangle",coordmaskstr
		try: # same here
			for i in range(4):
				coordmask[i] = float(coordmaskstr[i])
		except:
			print "invalid coordinate rectangle",coordmaskstr
			coordrect = False
 
	# parse whether to always get the first house, the last house, both, or neither on a road
	alwaysfirst = (opt.alwaysget == "first") or (opt.alwaysget == "both")
	alwayslast = (opt.alwaysget == "last") or (opt.alwaysget == "both")
 
	# query the sqlite database for everything. The blob at the end is for custom SQL conditions.
	dc.execute("select town,road,civic,dsl,long,lat from dsl"+(opt.sqlwhere and [" where "+opt.sqlwhere] or [""])[0]+" order by town,road,civic")
 
	# town, road...	:	data for presently-processed address
	# last___	:	data for previous address as pertains to determining sparsity
	# lastok___	:	data for previous selected-in-all-but-sparsity address, for last-on-road feature
	town = road = lasttown = lastroad = lastoktown = lastokroad = "" # strings
	civic = status = lastcivic = laststatus = lastokcivic = lastokstatus = 0 # ints
	lon = lat = lastlon = lastlat = lastoklon = lastoklat = 0.0 # floats
	notgot = lastoknotgot = False # bools -- used exclusively for last-on-road feature. Name translates to 'last okay address -- not been gotten?', meaning 'selected'
	# this block will take a lot of ram due to fetchall() rather than fetchone().
	# Shouldn't be hard to fix if needed, though. Current-db ram req: 6 MB max?
	for cca in dc.fetchall(): # current civic address
		town,road,civic,status,lon,lat,okay = cca[0],cca[1],int(cca[2]),int(cca[3]),float(cca[4]),float(cca[5]),False # casts here are important
 
		# skip cities -- currently only Ch'town and Summerside -- if requested in opt
		if opt.nocities and ( town == "CHARLOTTETOWN" or town == "SUMMERSIDE" ):
			continue
		# skip addresses outside of the latitude-longitude rectangle, if requested in opt
		if coordrect:
			if not ((min(coordmask[0],coordmask[2]) < lat < max(coordmask[0],coordmask[2]))
				and (min(coordmask[1],coordmask[3]) < lon < max(coordmask[1],coordmask[3]))): # check whether address is within coord rectangle
				continue
 
		# apply the dslmask. If status doesn't match, check maxrows
		# on whether to set last___ vars, to determine whether a
		# maximum number of addresses will be gotten, or a regular
		# sparseness will be maintained.
		if not dslmask[((status < 0) and [0] or [status])[0]]: # treat errors (-1) as empty (0).
			# doesn't match up -- ignore
			if not opt.maxrows:
				lasttown,lastroad,lastcivic,laststatus,lastlon,lastlat = town,road,civic,status,lon,lat
			continue;
 
		try: # loop must not crash
			notgot = False # by default, assume the address was selected
			# on the same road, make sure to skip houses for sparseness
			if town == lasttown and road == lastroad:
				if (civic - lastcivic) >= sparsity: # sparseness check
					actionfunc(dc,town,road,civic,status,lon,lat,opt)
				else: # if sparseness check discards an address, put it in the running to be selected for being last on a road:
					notgot = True
					lastoktown,lastokroad,lastokcivic,lastokstatus,lastoklon,lastoklat = town,road,civic,status,lon,lat
			else: # on a new road, decide whether to get the first house, or the last house on the previous road
				if alwaysfirst:
					actionfunc(dc,town,road,civic,status,lon,lat,opt)
				if alwayslast and (lastroad != "") and lastoknotgot:
					actionfunc(dc,lastoktown,lastokroad,lastokcivic,lastokstatus,lastoklon,lastoklat,opt)
		except Exception, errdet:
			print "> error < :",errdet
			print sys.exc_info()[0]
		lasttown,lastroad,lastcivic,laststatus,lastlon,lastlat = town,road,civic,status,lon,lat
		lastoknotgot = notgot
	# end of for loop
	if alwayslast and (lastroad != "") and lastoknotgot: # this ridiculous excess is to get the very last address in the database
		try:
			actionfunc(dc,lastoktown,lastokroad,lastokcivic,lastokstatus,lastoklon,lastoklat,opt)
		except Exception, errdet:
			print "> error < :",errdet
			print sys.exc_info()[0]
 
def crawlItemAction(dc, town, road, civic, status, lon, lat, opt):
	pass
 
def kmlItemAction(dc, town, road, civic, status, lon, lat, opt):
	""" handles an individual selected street address
	Called by processDB(), from kml().
	Does not honour delay param.
	See processDB() for param description."""
	if opt.kmlglobe: # absolutely nothing is done with this for now
		pass
	# placemark text block
	kmlbuffer.write("""
		<Placemark>
			"""+(opt.keepaddrs and [("<name>"+str(civic)+" "+road.replace("&","&amp;")+", "+town+"""</name>
""")] or [""])[0]+"""<styleUrl>#stylemap_"""+dslnames[(status+1)]+"""</styleUrl>
			<Point>
				<coordinates>"""+str(lon)+""","""+str(lat)+""",0</coordinates>
			</Point>
		</Placemark>
""")
	# The only useful bit in this block is the <name> block.
	# However, this makes Google Earth rather cluttered,
	# adds to file size, and raises privacy concerns.
	# The '&' replacement is very important -- Earth chokes easily.
	# The <LookAt> block is useless bloat.
	# This stuff was originally above <styleUrl>.
##			<name>"""+str(civic)+" "+road.replace("&","&amp;")+", "+town+"""</name>
##			<LookAt>
##				<longitude>"""+str(lon)+"""</longitude>
##				<latitude>"""+str(lat)+"""</latitude>
##				<altitude>0</altitude>
##				<range>600</range>
##				<tilt>0</tilt>
##				<heading>0</heading>
##				<altitudeMode>relativeToGround</altitudeMode>
##				<gx:altitudeMode>relativeToSeaFloor</gx:altitudeMode>
##			</LookAt>
	# end of kmlItemAction()
 
def statsItemAction(dc, town, road, civic, status, lon, lat, opt):
	""" handles an individual selected street address.
	    Called by processDB(), from stats().
	    Does not honour delay param. For param details,
	    see docstring for processDB()."""
	statistics[5] = statistics[5] + 1 # increment the total
	if status < 0: # error
	    statistics[0] = statistics[0] + 1
	elif status == 0: # unknown [EMPTY]
	    statistics[1] = statistics[1] + 1
	elif status == 1: # no [NODSL]
	    statistics[2] = statistics[2] + 1
	elif status == 2: # basic [BASIC]
	    statistics[3] = statistics[3] + 1
	elif status == 3: # ultra [ULTRA]
	    statistics[4] = statistics[4] + 1
	else: # error
	    print "invalid status: ",civic, road,",", town,": ",status
 
def listItemAction(dc, town, road, civic, status, lon, lat, opt):
	""" prints an individual selected street address.
	Called by fibmod.processDB, from printList().
	Does not honour delay param. For param details,
	see docstring for fibmod.processDB()."""
	print dslnames[(status+1)],"     ",civic,road+",",town
 
 
def parseDSLMask(instr):
	""" parses four boolean values from string, or bool list. 
	_Please_ don't use the less-than-four mechanism."""
	dslmask = [ False, False, False, False ] # real dslmask for use
	dfl = len(instr) # input length
	if dfl >= 4: # 4 or more? only use first 4.
		for i in range(4):
			dslmask[i] = char2Bool(instr[i])
	elif dfl == 3: # hopefully this won't be used -- EMPTY, NODSL, & (BASIC+ULTRA)
		for i in range(3):
			dslmask[i] = char2Bool(instr[i])
			dslmask[3] = dslmask[2]
	elif dfl == 2: # hopefully to be ignored -- EMPTY, KNOWN
		dslmask[0] = char2Bool(instr[0])
		dslmask[1] = dslmask[2] = dslmask[3] = char2Bool(instr[1])
	elif dfl == 1: # hopefully to be ignored -- EMPTY [KNOWN = True]
		dslmask = [char2Bool(instr[0]), True, True, True]
	else: # easy -- all True
		dslmask = [ True, True, True, True ]
	return dslmask
 
def char2Bool(inchar):
	""" Returns a bool from a y/n char param.
	y:Y: True; anything else: False
	However, if inchar is a bool, it will be returned as is.
 
	param sequence can be any length,
	but only the first char gets evaluated"""
	if inchar == True or inchar == False:
		return inchar
	elif inchar[0] == 'y' or inchar[0] == 'Y':
		return True
	else:
		return False
 
 
def kmlMakeHeader(outkml, opt):
	""" Returns a header for a Google Earth KML file.
	Manipulates some of the styles based on the options.
	outkml:
		name of the KML file -- used as title
	opt:
		Miscellaneous options -- see docs elsewhere
	"""
	if opt.kmlglobe: # not implemented, but don't want it forgotten....
		pass
 
	# determine the icon URLs to use
	iconURL = ["", "", "", "", ""]
	if opt.icontheme == "GM":
		iconURL = GMIconURLs
	else:
		for i in range(len(dslnames)-1):
			iconURL[i] = opt.icontheme.replace("*",dslnames[i])
	# determine the icon hotspots
	iconHotspots = GMIconHotspot
	if opt.iconhotspot:
		ihsc = opt.iconhotspot.split(",") # chunk into x/y coords
		if len(ihsc) < 2: # not a coord
			print "KML icon hotspot invalid: Too few coords. Using default...."
		else:
			for i in range(2):
				iconHotspots[i] = int(ihsc[i])
	# determine icon colors
	useIconColours = False
	iconColours = ["", "", "", "", ""]
	if opt.iconcolours:
		iconColours = opt.iconcolours.split(",") # chunk into icon colours
		if len(iconColours) < (len(dslnames)-1): # bad input
			print "KML icon colours invalid: Too few. Using default...."
			useIconColours = False
		else:
			useIconColours = True
 
	headerstr = """<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom">
<Document>
	<name>"""+outkml+"""</name>"""
	for i in range(len(dslnames)-1): # don't want the last one -- it's TOTAL
		headerstr = headerstr+"""
	<StyleMap id="stylemap_"""+dslnames[i]+"""">
		<Pair>
			<key>normal</key>
			<styleUrl>#stylenormal_"""+dslnames[i]+"""</styleUrl>
		</Pair>
		<Pair>
			<key>highlight</key>
			<styleUrl>#style"""+((opt.nohighlight == True) and ["normal_"] or ["highlit_"])[0]+dslnames[i]+"""</styleUrl>
		</Pair>"""
		headerstr = headerstr+"""
	</StyleMap>
	<Style id="stylenormal_"""+dslnames[i]+"""">
		<IconStyle>"""+(useIconColours and ["""
			<color>"""+iconColours[i]+"""</color>"""] or [""])[0]+"""
			<scale>"""+str(opt.iconsize)+"""</scale>
			<Icon>
				<href>"""+iconURL[i]+"""</href>
			</Icon>
			<hotSpot x="""+"\""+str(iconHotspots[0])+"\" y=\""+str(iconHotspots[1])+"\""+""" xunits="pixels" yunits="pixels"/>
		</IconStyle>
		<ListStyle>
		</ListStyle>
	</Style>"""
		# this line was plagued by a mysterious bug, as was the other nohighlight line above.
		# This works -- please don't just change it for appearance
		if opt.nohighlight != True:
			headerstr = headerstr+"""
	<Style id="stylehighlit_"""+dslnames[i]+"""">
		<IconStyle>"""+(useIconColours and ["""
			<color>"""+iconColours[i]+"""</color>"""] or [""])[0]+"""
			<scale>"""+str(opt.iconsize*kmlIconHighlitScale)+"""</scale>
			<Icon>
				<href>"""+iconURL[i]+"""</href>
			</Icon>
			<hotSpot x="""+"\""+str(iconHotspots[0])+"\" y=\""+str(iconHotspots[1])+"\""+""" xunits="pixels" yunits="pixels"/>
		</IconStyle>
		<ListStyle>
		</ListStyle>
	</Style>
	"""
	headerstr = headerstr+"""
 
	<Folder>
		<name>"""+outkml+"""</name>
		<open>0</open>
 
"""
	return headerstr
	# end of kmlMakeHeader()
 
def kmlMakeFooter(outkml, opt):
	""" Returns a footer for a Google Earth KML file.
	Manipulates some of the styles based on the options.
	outkml:
		name of the KML file -- used as title
	opt:
		Miscellaneous options -- see docs elsewhere
	"""
	return """
 
	</Folder>
</Document>
</kml>
 
"""
	# end of kmlMakeFooter()
 
 
if __name__ == "__main__":
	main()
 

Compare with Previous | Blame | View Log