Rearrange includes so session can act on GET/POST variables
Rearrange includes so session can act on GET/POST variables

file:a/about.php -> file:b/about.php
--- a/about.php
+++ b/about.php
@@ -12,7 +12,7 @@
 href="https://github.com/maxious/ACTBus-data">transit 
 feed</a> and <a href="https://github.com/maxious/ACTBus-ui">this 
 site</a> available from github.<br />
-Uses jQuery Mobile, PHP, Ruby, Python, Google Transit Feed Specification tools, OpenTripPlanner, OpenLayers, OpenStreetMap, Cloudmade Geocoder and Tile Service<br />
+Uses jQuery Mobile, PHP, PostgreSQL, OpenTripPlanner, OpenLayers, OpenStreetMap, Cloudmade Geocoder and Tile Service<br />
 <br />
 Feedback encouraged; contact maxious@lambdacomplex.org<br />
     <br />
@@ -25,7 +25,7 @@
 All offers are not binding and without obligation. The Author expressly reserves the right, in his discretion, to suspend, 
 change, modify, add or remove portions of the Site and to restrict or terminate the use and accessibility of the Site 
 without prior notice. </small>
-<?
+<?php
 include_footer();
 ?>
 

--- a/aws/awsStartup.sh
+++ b/aws/awsStartup.sh
@@ -1,7 +1,8 @@
 #!/bin/bash
-#this script should be run from a fresh git checkout from http://maxious.lambdacomplex.org
+#this script should be run from a fresh git checkout from github
 #ami base must have yum install lighttpd-fastcgi, git, tomcat6 
-#screen php-cli php-gd tomcat6-webapps tomcat6-admin-webapps svn maven2
+#php-cli php-gd tomcat6-webapps tomcat6-admin-webapps svn maven2
+#postgres postgres-server php-pg
 #http://www.how2forge.org/installing-lighttpd-with-php5-and-mysql-support-on-fedora-12
 
 cp /root/aws.php /tmp/
@@ -10,12 +11,22 @@
 chcon -R -h root:object_r:httpd_sys_content_t /var/www/*
 chcon -R -t httpd_sys_content_rw_t /var/www/lib/staticmaplite/cache
 chmod -R 777 /var/www/lib/staticmaplite/cache 
+chcon -R -t httpd_sys_content_rw_t /var/www/labs/tiles
+chmod -R 777 /var/www/labs/tiles
 wget http://s3-ap-southeast-1.amazonaws.com/busresources/cbrfeed.zip \
 -O /var/www/cbrfeed.zip
-easy_install transitfeed
-easy_install simplejson
-screen -S viewsh -X quit
-screen -S viewsh -d -m /var/www/view.sh
+
+createdb transitdata
+createlang -d transitdata plpgsql
+psql -d transitdata -f /var/www/lib/postgis.sql
+# curl https://github.com/maxious/ACTBus-ui/raw/master/transitdata.cbrfeed.sql.gz -o transitdata.cbrfeed.sql.gz 
+#made with pg_dump transitdata | gzip -c >  transitdata.cbrfeed.sql.gz
+gunzip /var/www/transitdata.cbrfeed.sql.gz
+psql -d transitdata -f /var/www/transitdata.cbrfeed.sql
+#createuser transitdata -SDRP
+#password transitdata
+#psql -d transitdata -c \"GRANT SELECT ON TABLE agency,calendar,calendar_dates,routes,stop_times,stops,trips TO transitdata;\"
+php /var/www/updatedb.php
 
 wget http://s3-ap-southeast-1.amazonaws.com/busresources/Graph.obj \
 -O /tmp/Graph.obj
@@ -26,4 +37,3 @@
 -O /usr/share/tomcat6/webapps/opentripplanner-api-webapp.war
 /etc/init.d/tomcat6 restart
 
-

file:b/aws/pg_hba.conf (new)
--- /dev/null
+++ b/aws/pg_hba.conf
@@ -1,1 +1,77 @@
+# PostgreSQL Client Authentication Configuration File
+# ===================================================
+#
+# Refer to the "Client Authentication" section in the
+# PostgreSQL documentation for a complete description
+# of this file.  A short synopsis follows.
+#
+# This file controls: which hosts are allowed to connect, how clients
+# are authenticated, which PostgreSQL user names they can use, which
+# databases they can access.  Records take one of these forms:
+#
+# local      DATABASE  USER  METHOD  [OPTIONS]
+# host       DATABASE  USER  CIDR-ADDRESS  METHOD  [OPTIONS]
+# hostssl    DATABASE  USER  CIDR-ADDRESS  METHOD  [OPTIONS]
+# hostnossl  DATABASE  USER  CIDR-ADDRESS  METHOD  [OPTIONS]
+#
+# (The uppercase items must be replaced by actual values.)
+#
+# The first field is the connection type: "local" is a Unix-domain socket,
+# "host" is either a plain or SSL-encrypted TCP/IP socket, "hostssl" is an
+# SSL-encrypted TCP/IP socket, and "hostnossl" is a plain TCP/IP socket.
+#
+# DATABASE can be "all", "sameuser", "samerole", a database name, or
+# a comma-separated list thereof.
+#
+# USER can be "all", a user name, a group name prefixed with "+", or
+# a comma-separated list thereof.  In both the DATABASE and USER fields
+# you can also write a file name prefixed with "@" to include names from
+# a separate file.
+#
+# CIDR-ADDRESS specifies the set of hosts the record matches.
+# It is made up of an IP address and a CIDR mask that is an integer
+# (between 0 and 32 (IPv4) or 128 (IPv6) inclusive) that specifies
+# the number of significant bits in the mask.  Alternatively, you can write
+# an IP address and netmask in separate columns to specify the set of hosts.
+#
+# METHOD can be "trust", "reject", "md5", "password", "gss", "sspi", "krb5",
+# "ident", "pam", "ldap" or "cert".  Note that "password" sends passwords
+# in clear text; "md5" is preferred since it sends encrypted passwords.
+#
+# OPTIONS are a set of options for the authentication in the format
+# NAME=VALUE. The available options depend on the different authentication
+# methods - refer to the "Client Authentication" section in the documentation
+# for a list of which options are available for which authentication methods.
+#
+# Database and user names containing spaces, commas, quotes and other special
+# characters must be quoted. Quoting one of the keywords "all", "sameuser" or
+# "samerole" makes the name lose its special character, and just match a
+# database or username with that name.
+#
+# This file is read on server startup and when the postmaster receives
+# a SIGHUP signal.  If you edit the file on a running system, you have
+# to SIGHUP the postmaster for the changes to take effect.  You can use
+# "pg_ctl reload" to do that.
 
+# Put your actual configuration here
+# ----------------------------------
+#
+# If you want to allow non-local connections, you need to add more
+# "host" records. In that case you will also need to make PostgreSQL listen
+# on a non-local interface via the listen_addresses configuration parameter,
+# or via the -i or -h command line switches.
+#
+
+
+
+# TYPE  DATABASE    USER        CIDR-ADDRESS          METHOD
+
+# "local" is for Unix domain socket connections only
+local   all         all                               trust
+# IPv4 local connections:
+host    all         all         127.0.0.1/32          trust
+# IPv6 local connections:
+host    all         all         ::1/128               trust
+#Allow any IP to connect, with a password:
+host    all         all         0.0.0.0          0.0.0.0      md5
+

--- /dev/null
+++ b/aws/postgresql.conf
@@ -1,1 +1,502 @@
-
+# -----------------------------
+# PostgreSQL configuration file
+# -----------------------------
+#
+# This file consists of lines of the form:
+#
+#   name = value
+#
+# (The "=" is optional.)  Whitespace may be used.  Comments are introduced with
+# "#" anywhere on a line.  The complete list of parameter names and allowed
+# values can be found in the PostgreSQL documentation.
+#
+# The commented-out settings shown in this file represent the default values.
+# Re-commenting a setting is NOT sufficient to revert it to the default value;
+# you need to reload the server.
+#
+# This file is read on server startup and when the server receives a SIGHUP
+# signal.  If you edit the file on a running system, you have to SIGHUP the
+# server for the changes to take effect, or use "pg_ctl reload".  Some
+# parameters, which are marked below, require a server shutdown and restart to
+# take effect.
+#
+# Any parameter can also be given as a command-line option to the server, e.g.,
+# "postgres -c log_connections=on".  Some parameters can be changed at run time
+# with the "SET" SQL command.
+#
+# Memory units:  kB = kilobytes        Time units:  ms  = milliseconds
+#                MB = megabytes                     s   = seconds
+#                GB = gigabytes                     min = minutes
+#                                                   h   = hours
+#                                                   d   = days
+
+
+#------------------------------------------------------------------------------
+# FILE LOCATIONS
+#------------------------------------------------------------------------------
+
+# The default values of these variables are driven from the -D command-line
+# option or PGDATA environment variable, represented here as ConfigDir.
+
+#data_directory = 'ConfigDir'		# use data in another directory
+					# (change requires restart)
+#hba_file = 'ConfigDir/pg_hba.conf'	# host-based authentication file
+					# (change requires restart)
+#ident_file = 'ConfigDir/pg_ident.conf'	# ident configuration file
+					# (change requires restart)
+
+# If external_pid_file is not explicitly set, no extra PID file is written.
+#external_pid_file = '(none)'		# write an extra PID file
+					# (change requires restart)
+
+
+#------------------------------------------------------------------------------
+# CONNECTIONS AND AUTHENTICATION
+#------------------------------------------------------------------------------
+
+# - Connection Settings -
+
+listen_addresses = '*'		# what IP address(es) to listen on;
+					# comma-separated list of addresses;
+					# defaults to 'localhost', '*' = all
+					# (change requires restart)
+#port = 5432				# (change requires restart)
+max_connections = 100			# (change requires restart)
+# Note:  Increasing max_connections costs ~400 bytes of shared memory per 
+# connection slot, plus lock space (see max_locks_per_transaction).
+#superuser_reserved_connections = 3	# (change requires restart)
+#unix_socket_directory = ''		# (change requires restart)
+#unix_socket_group = ''			# (change requires restart)
+#unix_socket_permissions = 0777		# begin with 0 to use octal notation
+					# (change requires restart)
+#bonjour_name = ''			# defaults to the computer name
+					# (change requires restart)
+
+# - Security and Authentication -
+
+#authentication_timeout = 1min		# 1s-600s
+#ssl = off				# (change requires restart)
+#ssl_ciphers = 'ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH'	# allowed SSL ciphers
+					# (change requires restart)
+#ssl_renegotiation_limit = 512MB	# amount of data between renegotiations
+#password_encryption = on
+#db_user_namespace = off
+
+# Kerberos and GSSAPI
+#krb_server_keyfile = ''
+#krb_srvname = 'postgres'		# (Kerberos only)
+#krb_caseins_users = off
+
+# - TCP Keepalives -
+# see "man 7 tcp" for details
+
+#tcp_keepalives_idle = 0		# TCP_KEEPIDLE, in seconds;
+					# 0 selects the system default
+#tcp_keepalives_interval = 0		# TCP_KEEPINTVL, in seconds;
+					# 0 selects the system default
+#tcp_keepalives_count = 0		# TCP_KEEPCNT;
+					# 0 selects the system default
+
+
+#------------------------------------------------------------------------------
+# RESOURCE USAGE (except WAL)
+#------------------------------------------------------------------------------
+
+# - Memory -
+
+shared_buffers = 32MB			# min 128kB
+					# (change requires restart)
+#temp_buffers = 8MB			# min 800kB
+#max_prepared_transactions = 0		# zero disables the feature
+					# (change requires restart)
+# Note:  Increasing max_prepared_transactions costs ~600 bytes of shared memory
+# per transaction slot, plus lock space (see max_locks_per_transaction).
+# It is not advisable to set max_prepared_transactions nonzero unless you
+# actively intend to use prepared transactions.
+#work_mem = 1MB				# min 64kB
+#maintenance_work_mem = 16MB		# min 1MB
+#max_stack_depth = 2MB			# min 100kB
+
+# - Kernel Resource Usage -
+
+#max_files_per_process = 1000		# min 25
+					# (change requires restart)
+#shared_preload_libraries = ''		# (change requires restart)
+
+# - Cost-Based Vacuum Delay -
+
+#vacuum_cost_delay = 0ms		# 0-100 milliseconds
+#vacuum_cost_page_hit = 1		# 0-10000 credits
+#vacuum_cost_page_miss = 10		# 0-10000 credits
+#vacuum_cost_page_dirty = 20		# 0-10000 credits
+#vacuum_cost_limit = 200		# 1-10000 credits
+
+# - Background Writer -
+
+#bgwriter_delay = 200ms			# 10-10000ms between rounds
+#bgwriter_lru_maxpages = 100		# 0-1000 max buffers written/round
+#bgwriter_lru_multiplier = 2.0		# 0-10.0 multipler on buffers scanned/round
+
+# - Asynchronous Behavior -
+
+#effective_io_concurrency = 1		# 1-1000. 0 disables prefetching
+
+
+#------------------------------------------------------------------------------
+# WRITE AHEAD LOG
+#------------------------------------------------------------------------------
+
+# - Settings -
+
+#fsync = on				# turns forced synchronization on or off
+#synchronous_commit = on		# immediate fsync at commit
+#wal_sync_method = fsync		# the default is the first option 
+					# supported by the operating system:
+					#   open_datasync
+					#   fdatasync
+					#   fsync
+					#   fsync_writethrough
+					#   open_sync
+#full_page_writes = on			# recover from partial page writes
+#wal_buffers = 64kB			# min 32kB
+					# (change requires restart)
+#wal_writer_delay = 200ms		# 1-10000 milliseconds
+
+#commit_delay = 0			# range 0-100000, in microseconds
+#commit_siblings = 5			# range 1-1000
+
+# - Checkpoints -
+
+#checkpoint_segments = 3		# in logfile segments, min 1, 16MB each
+#checkpoint_timeout = 5min		# range 30s-1h
+#checkpoint_completion_target = 0.5	# checkpoint target duration, 0.0 - 1.0
+#checkpoint_warning = 30s		# 0 disables
+
+# - Archiving -
+
+#archive_mode = off		# allows archiving to be done
+				# (change requires restart)
+#archive_command = ''		# command to use to archive a logfile segment
+#archive_timeout = 0		# force a logfile segment switch after this
+				# number of seconds; 0 disables
+
+
+#------------------------------------------------------------------------------
+# QUERY TUNING
+#------------------------------------------------------------------------------
+
+# - Planner Method Configuration -
+
+#enable_bitmapscan = on
+#enable_hashagg = on
+#enable_hashjoin = on
+#enable_indexscan = on
+#enable_mergejoin = on
+#enable_nestloop = on
+#enable_seqscan = on
+#enable_sort = on
+#enable_tidscan = on
+
+# - Planner Cost Constants -
+
+#seq_page_cost = 1.0			# measured on an arbitrary scale
+#random_page_cost = 4.0			# same scale as above
+#cpu_tuple_cost = 0.01			# same scale as above
+#cpu_index_tuple_cost = 0.005		# same scale as above
+#cpu_operator_cost = 0.0025		# same scale as above
+#effective_cache_size = 128MB
+
+# - Genetic Query Optimizer -
+
+#geqo = on
+#geqo_threshold = 12
+#geqo_effort = 5			# range 1-10
+#geqo_pool_size = 0			# selects default based on effort
+#geqo_generations = 0			# selects default based on effort
+#geqo_selection_bias = 2.0		# range 1.5-2.0
+
+# - Other Planner Options -
+
+#default_statistics_target = 100	# range 1-10000
+#constraint_exclusion = partition	# on, off, or partition
+#cursor_tuple_fraction = 0.1		# range 0.0-1.0
+#from_collapse_limit = 8
+#join_collapse_limit = 8		# 1 disables collapsing of explicit 
+					# JOIN clauses
+
+
+#------------------------------------------------------------------------------
+# ERROR REPORTING AND LOGGING
+#------------------------------------------------------------------------------
+
+# - Where to Log -
+
+#log_destination = 'stderr'		# Valid values are combinations of
+					# stderr, csvlog, syslog and eventlog,
+					# depending on platform.  csvlog
+					# requires logging_collector to be on.
+
+# This is used when logging to stderr:
+logging_collector = on			# Enable capturing of stderr and csvlog
+					# into log files. Required to be on for
+					# csvlogs.
+					# (change requires restart)
+
+# These are only used if logging_collector is on:
+log_directory = 'pg_log'		# directory where log files are written,
+					# can be absolute or relative to PGDATA
+log_filename = 'postgresql-%a.log'	# log file name pattern,
+					# can include strftime() escapes
+log_truncate_on_rotation = on		# If on, an existing log file of the
+					# same name as the new log file will be
+					# truncated rather than appended to.
+					# But such truncation only occurs on
+					# time-driven rotation, not on restarts
+					# or size-driven rotation.  Default is
+					# off, meaning append to existing files
+					# in all cases.
+log_rotation_age = 1d			# Automatic rotation of logfiles will
+					# happen after that time.  0 disables.
+log_rotation_size = 0			# Automatic rotation of logfiles will 
+					# happen after that much log output.
+					# 0 disables.
+
+# These are relevant when logging to syslog:
+#syslog_facility = 'LOCAL0'
+#syslog_ident = 'postgres'
+
+#silent_mode = off			# Run server silently.
+					# DO NOT USE without syslog or
+					# logging_collector
+					# (change requires restart)
+
+
+# - When to Log -
+
+#client_min_messages = notice		# values in order of decreasing detail:
+					#   debug5
+					#   debug4
+					#   debug3
+					#   debug2
+					#   debug1
+					#   log
+					#   notice
+					#   warning
+					#   error
+
+#log_min_messages = warning		# values in order of decreasing detail:
+					#   debug5
+					#   debug4
+					#   debug3
+					#   debug2
+					#   debug1
+					#   info
+					#   notice
+					#   warning
+					#   error
+					#   log
+					#   fatal
+					#   panic
+
+#log_error_verbosity = default		# terse, default, or verbose messages
+
+#log_min_error_statement = error	# values in order of decreasing detail:
+				 	#   debug5
+					#   debug4
+					#   debug3
+					#   debug2
+					#   debug1
+				 	#   info
+					#   notice
+					#   warning
+					#   error
+					#   log
+					#   fatal
+					#   panic (effectively off)
+
+#log_min_duration_statement = -1	# -1 is disabled, 0 logs all statements
+					# and their durations, > 0 logs only
+					# statements running at least this number
+					# of milliseconds
+
+
+# - What to Log -
+
+#debug_print_parse = off
+#debug_print_rewritten = off
+#debug_print_plan = off
+#debug_pretty_print = on
+#log_checkpoints = off
+#log_connections = off
+#log_disconnections = off
+#log_duration = off
+#log_hostname = off
+#log_line_prefix = ''			# special values:
+					#   %u = user name
+					#   %d = database name
+					#   %r = remote host and port
+					#   %h = remote host
+					#   %p = process ID
+					#   %t = timestamp without milliseconds
+					#   %m = timestamp with milliseconds
+					#   %i = command tag
+					#   %c = session ID
+					#   %l = session line number
+					#   %s = session start timestamp
+					#   %v = virtual transaction ID
+					#   %x = transaction ID (0 if none)
+					#   %q = stop here in non-session
+					#        processes
+					#   %% = '%'
+					# e.g. '<%u%%%d> '
+#log_lock_waits = off			# log lock waits >= deadlock_timeout
+#log_statement = 'none'			# none, ddl, mod, all
+#log_temp_files = -1			# log temporary files equal or larger
+					# than the specified size in kilobytes;
+					# -1 disables, 0 logs all temp files
+#log_timezone = unknown			# actually, defaults to TZ environment
+					# setting
+
+
+#------------------------------------------------------------------------------
+# RUNTIME STATISTICS
+#------------------------------------------------------------------------------
+
+# - Query/Index Statistics Collector -
+
+#track_activities = on
+#track_counts = on
+#track_functions = none			# none, pl, all
+#track_activity_query_size = 1024
+#update_process_title = on
+#stats_temp_directory = 'pg_stat_tmp'
+
+
+# - Statistics Monitoring -
+
+#log_parser_stats = off
+#log_planner_stats = off
+#log_executor_stats = off
+#log_statement_stats = off
+
+
+#------------------------------------------------------------------------------
+# AUTOVACUUM PARAMETERS
+#------------------------------------------------------------------------------
+
+#autovacuum = on			# Enable autovacuum subprocess?  'on' 
+					# requires track_counts to also be on.
+#log_autovacuum_min_duration = -1	# -1 disables, 0 logs all actions and
+					# their durations, > 0 logs only
+					# actions running at least this number
+					# of milliseconds.
+#autovacuum_max_workers = 3		# max number of autovacuum subprocesses
+#autovacuum_naptime = 1min		# time between autovacuum runs
+#autovacuum_vacuum_threshold = 50	# min number of row updates before
+					# vacuum
+#autovacuum_analyze_threshold = 50	# min number of row updates before 
+					# analyze
+#autovacuum_vacuum_scale_factor = 0.2	# fraction of table size before vacuum
+#autovacuum_analyze_scale_factor = 0.1	# fraction of table size before analyze
+#autovacuum_freeze_max_age = 200000000	# maximum XID age before forced vacuum
+					# (change requires restart)
+#autovacuum_vacuum_cost_delay = 20ms	# default vacuum cost delay for
+					# autovacuum, in milliseconds;
+					# -1 means use vacuum_cost_delay
+#autovacuum_vacuum_cost_limit = -1	# default vacuum cost limit for
+					# autovacuum, -1 means use
+					# vacuum_cost_limit
+
+
+#------------------------------------------------------------------------------
+# CLIENT CONNECTION DEFAULTS
+#------------------------------------------------------------------------------
+
+# - Statement Behavior -
+
+#search_path = '"$user",public'		# schema names
+#default_tablespace = ''		# a tablespace name, '' uses the default
+#temp_tablespaces = ''			# a list of tablespace names, '' uses
+					# only default tablespace
+#check_function_bodies = on
+#default_transaction_isolation = 'read committed'
+#default_transaction_read_only = off
+#session_replication_role = 'origin'
+#statement_timeout = 0			# in milliseconds, 0 is disabled
+#vacuum_freeze_min_age = 50000000
+#vacuum_freeze_table_age = 150000000
+#xmlbinary = 'base64'
+#xmloption = 'content'
+
+# - Locale and Formatting -
+
+datestyle = 'iso, mdy'
+#intervalstyle = 'postgres'
+#timezone = unknown			# actually, defaults to TZ environment
+					# setting
+#timezone_abbreviations = 'Default'     # Select the set of available time zone
+					# abbreviations.  Currently, there are
+					#   Default
+					#   Australia
+					#   India
+					# You can create your own file in
+					# share/timezonesets/.
+#extra_float_digits = 0			# min -15, max 2
+#client_encoding = sql_ascii		# actually, defaults to database
+					# encoding
+
+# These settings are initialized by initdb, but they can be changed.
+lc_messages = 'en_US.UTF-8'			# locale for system error message
+					# strings
+lc_monetary = 'en_US.UTF-8'			# locale for monetary formatting
+lc_numeric = 'en_US.UTF-8'			# locale for number formatting
+lc_time = 'en_US.UTF-8'				# locale for time formatting
+
+# default configuration for text search
+default_text_search_config = 'pg_catalog.english'
+
+# - Other Defaults -
+
+#dynamic_library_path = '$libdir'
+#local_preload_libraries = ''
+
+
+#------------------------------------------------------------------------------
+# LOCK MANAGEMENT
+#------------------------------------------------------------------------------
+
+#deadlock_timeout = 1s
+#max_locks_per_transaction = 64		# min 10
+					# (change requires restart)
+# Note:  Each lock table slot uses ~270 bytes of shared memory, and there are
+# max_locks_per_transaction * (max_connections + max_prepared_transactions)
+# lock table slots.
+
+
+#------------------------------------------------------------------------------
+# VERSION/PLATFORM COMPATIBILITY
+#------------------------------------------------------------------------------
+
+# - Previous PostgreSQL Versions -
+
+#add_missing_from = off
+#array_nulls = on
+#backslash_quote = safe_encoding	# on, off, or safe_encoding
+#default_with_oids = off
+#escape_string_warning = on
+#regex_flavor = advanced		# advanced, extended, or basic
+#sql_inheritance = on
+#standard_conforming_strings = off
+#synchronize_seqscans = on
+
+# - Other Platforms and Clients -
+
+#transform_null_equals = off
+
+
+#------------------------------------------------------------------------------
+# CUSTOMIZED OPTIONS
+#------------------------------------------------------------------------------
+
+#custom_variable_classes = ''		# list of custom variable class names
+

 Binary files a/css/images/01-refresh.png and /dev/null differ
file:a/css/images/02-redo.png (deleted)
 Binary files a/css/images/02-redo.png and /dev/null differ
 Binary files a/css/images/06-magnify.png and /dev/null differ
 Binary files a/css/images/07-map-marker.png and /dev/null differ
 Binary files a/css/images/101-gameplan.png and /dev/null differ
file:a/css/images/102-walk.png (deleted)
 Binary files a/css/images/102-walk.png and /dev/null differ
file:a/css/images/103-map.png (deleted)
 Binary files a/css/images/103-map.png and /dev/null differ
 Binary files a/css/images/121-landscape.png and /dev/null differ
 Binary files a/css/images/13-target.png and /dev/null differ
 Binary files a/css/images/139-flags.png and /dev/null differ
 Binary files a/css/images/145-persondot.png and /dev/null differ
 Binary files a/css/images/184-warning.png and /dev/null differ
 Binary files a/css/images/193-location-arrow.png and /dev/null differ
file:a/css/images/28-star.png (deleted)
 Binary files a/css/images/28-star.png and /dev/null differ
file:a/css/images/53-house.png (deleted)
 Binary files a/css/images/53-house.png and /dev/null differ
 Binary files a/css/images/55-network.png and /dev/null differ
 Binary files a/css/images/57-download.png and /dev/null differ
 Binary files a/css/images/58-bookmark.png and /dev/null differ
file:a/css/images/59-flag.png (deleted)
 Binary files a/css/images/59-flag.png and /dev/null differ
 Binary files a/css/images/60-signpost.png and /dev/null differ
file:a/css/images/73-radar.png (deleted)
 Binary files a/css/images/73-radar.png and /dev/null differ
 Binary files a/css/images/74-location.png and /dev/null differ
 Binary files a/css/images/83-calendar.png and /dev/null differ
 Binary files /dev/null and b/css/images/91-beaker-2.png differ
 Binary files /dev/null and b/css/images/ui-bg_diagonals-thick_18_b81900_40x40.png differ
 Binary files /dev/null and b/css/images/ui-bg_diagonals-thick_20_666666_40x40.png differ
 Binary files /dev/null and b/css/images/ui-bg_flat_10_000000_40x100.png differ
 Binary files /dev/null and b/css/images/ui-bg_glass_100_f6f6f6_1x400.png differ
 Binary files /dev/null and b/css/images/ui-bg_glass_100_fdf5ce_1x400.png differ
 Binary files /dev/null and b/css/images/ui-bg_glass_65_ffffff_1x400.png differ
 Binary files /dev/null and b/css/images/ui-bg_gloss-wave_35_f6a828_500x100.png differ
 Binary files /dev/null and b/css/images/ui-bg_highlight-soft_100_eeeeee_1x100.png differ
 Binary files /dev/null and b/css/images/ui-bg_highlight-soft_75_ffe45c_1x100.png differ
 Binary files /dev/null and b/css/images/ui-icons_222222_256x240.png differ
 Binary files /dev/null and b/css/images/ui-icons_228ef1_256x240.png differ
 Binary files /dev/null and b/css/images/ui-icons_ef8c08_256x240.png differ
 Binary files /dev/null and b/css/images/ui-icons_ffd27a_256x240.png differ
 Binary files /dev/null and b/css/images/ui-icons_ffffff_256x240.png differ
--- /dev/null
+++ b/css/jquery-ui-1.8.12.custom.css
@@ -1,1 +1,348 @@
-
+/*
+ * jQuery UI CSS Framework 1.8.12
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Theming/API
+ */
+
+/* Layout helpers
+----------------------------------*/
+.ui-helper-hidden { display: none; }
+.ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); }
+.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
+.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
+.ui-helper-clearfix { display: inline-block; }
+/* required comment for clearfix to work in Opera \*/
+* html .ui-helper-clearfix { height:1%; }
+.ui-helper-clearfix { display:block; }
+/* end clearfix */
+.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
+
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-disabled { cursor: default !important; }
+
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Overlays */
+.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
+
+
+/*
+ * jQuery UI CSS Framework 1.8.12
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Theming/API
+ *
+ * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Trebuchet%20MS,%20Tahoma,%20Verdana,%20Arial,%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=f6a828&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=35&borderColorHeader=e78f08&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=03_highlight_soft.png&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=1c94c4&iconColorDefault=ef8c08&bgColorHover=fdf5ce&bgTextureHover=02_glass.png&bgImgOpacityHover=100&borderColorHover=fbcb09&fcHover=c77405&iconColorHover=ef8c08&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=fbd850&fcActive=eb8f00&iconColorActive=ef8c08&bgColorHighlight=ffe45c&bgTextureHighlight=03_highlight_soft.png&bgImgOpacityHighlight=75&borderColorHighlight=fed22f&fcHighlight=363636&iconColorHighlight=228ef1&bgColorError=b81900&bgTextureError=08_diagonals_thick.png&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=08_diagonals_thick.png&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=01_flat.png&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px
+ */
+
+
+/* Component containers
+----------------------------------*/
+.ui-widget { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1.1em; }
+.ui-widget .ui-widget { font-size: 1em; }
+.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1em; }
+.ui-widget-content { border: 1px solid #dddddd; background: #eeeeee url(images/ui-bg_highlight-soft_100_eeeeee_1x100.png) 50% top repeat-x; color: #333333; }
+.ui-widget-content a { color: #333333; }
+.ui-widget-header { border: 1px solid #e78f08; background: #f6a828 url(images/ui-bg_gloss-wave_35_f6a828_500x100.png) 50% 50% repeat-x; color: #ffffff; font-weight: bold; }
+.ui-widget-header a { color: #ffffff; }
+
+/* Interaction states
+----------------------------------*/
+.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #cccccc; background: #f6f6f6 url(images/ui-bg_glass_100_f6f6f6_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #1c94c4; }
+.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #1c94c4; text-decoration: none; }
+.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #fbcb09; background: #fdf5ce url(images/ui-bg_glass_100_fdf5ce_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #c77405; }
+.ui-state-hover a, .ui-state-hover a:hover { color: #c77405; text-decoration: none; }
+.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #fbd850; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #eb8f00; }
+.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #eb8f00; text-decoration: none; }
+.ui-widget :active { outline: none; }
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight  {border: 1px solid #fed22f; background: #ffe45c url(images/ui-bg_highlight-soft_75_ffe45c_1x100.png) 50% top repeat-x; color: #363636; }
+.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; }
+.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #b81900 url(images/ui-bg_diagonals-thick_18_b81900_40x40.png) 50% 50% repeat; color: #ffffff; }
+.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #ffffff; }
+.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #ffffff; }
+.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; }
+.ui-priority-secondary, .ui-widget-content .ui-priority-secondary,  .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
+.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-header .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); }
+.ui-state-default .ui-icon { background-image: url(images/ui-icons_ef8c08_256x240.png); }
+.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_ef8c08_256x240.png); }
+.ui-state-active .ui-icon {background-image: url(images/ui-icons_ef8c08_256x240.png); }
+.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_228ef1_256x240.png); }
+.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_ffd27a_256x240.png); }
+
+/* positioning */
+.ui-icon-carat-1-n { background-position: 0 0; }
+.ui-icon-carat-1-ne { background-position: -16px 0; }
+.ui-icon-carat-1-e { background-position: -32px 0; }
+.ui-icon-carat-1-se { background-position: -48px 0; }
+.ui-icon-carat-1-s { background-position: -64px 0; }
+.ui-icon-carat-1-sw { background-position: -80px 0; }
+.ui-icon-carat-1-w { background-position: -96px 0; }
+.ui-icon-carat-1-nw { background-position: -112px 0; }
+.ui-icon-carat-2-n-s { background-position: -128px 0; }
+.ui-icon-carat-2-e-w { background-position: -144px 0; }
+.ui-icon-triangle-1-n { background-position: 0 -16px; }
+.ui-icon-triangle-1-ne { background-position: -16px -16px; }
+.ui-icon-triangle-1-e { background-position: -32px -16px; }
+.ui-icon-triangle-1-se { background-position: -48px -16px; }
+.ui-icon-triangle-1-s { background-position: -64px -16px; }
+.ui-icon-triangle-1-sw { background-position: -80px -16px; }
+.ui-icon-triangle-1-w { background-position: -96px -16px; }
+.ui-icon-triangle-1-nw { background-position: -112px -16px; }
+.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
+.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
+.ui-icon-arrow-1-n { background-position: 0 -32px; }
+.ui-icon-arrow-1-ne { background-position: -16px -32px; }
+.ui-icon-arrow-1-e { background-position: -32px -32px; }
+.ui-icon-arrow-1-se { background-position: -48px -32px; }
+.ui-icon-arrow-1-s { background-position: -64px -32px; }
+.ui-icon-arrow-1-sw { background-position: -80px -32px; }
+.ui-icon-arrow-1-w { background-position: -96px -32px; }
+.ui-icon-arrow-1-nw { background-position: -112px -32px; }
+.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
+.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
+.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
+.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
+.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
+.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
+.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
+.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
+.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
+.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
+.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
+.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
+.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
+.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
+.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
+.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
+.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
+.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
+.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
+.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
+.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
+.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
+.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
+.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
+.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
+.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
+.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
+.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
+.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
+.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
+.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
+.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
+.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
+.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
+.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
+.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
+.ui-icon-arrow-4 { background-position: 0 -80px; }
+.ui-icon-arrow-4-diag { background-position: -16px -80px; }
+.ui-icon-extlink { background-position: -32px -80px; }
+.ui-icon-newwin { background-position: -48px -80px; }
+.ui-icon-refresh { background-position: -64px -80px; }
+.ui-icon-shuffle { background-position: -80px -80px; }
+.ui-icon-transfer-e-w { background-position: -96px -80px; }
+.ui-icon-transferthick-e-w { background-position: -112px -80px; }
+.ui-icon-folder-collapsed { background-position: 0 -96px; }
+.ui-icon-folder-open { background-position: -16px -96px; }
+.ui-icon-document { background-position: -32px -96px; }
+.ui-icon-document-b { background-position: -48px -96px; }
+.ui-icon-note { background-position: -64px -96px; }
+.ui-icon-mail-closed { background-position: -80px -96px; }
+.ui-icon-mail-open { background-position: -96px -96px; }
+.ui-icon-suitcase { background-position: -112px -96px; }
+.ui-icon-comment { background-position: -128px -96px; }
+.ui-icon-person { background-position: -144px -96px; }
+.ui-icon-print { background-position: -160px -96px; }
+.ui-icon-trash { background-position: -176px -96px; }
+.ui-icon-locked { background-position: -192px -96px; }
+.ui-icon-unlocked { background-position: -208px -96px; }
+.ui-icon-bookmark { background-position: -224px -96px; }
+.ui-icon-tag { background-position: -240px -96px; }
+.ui-icon-home { background-position: 0 -112px; }
+.ui-icon-flag { background-position: -16px -112px; }
+.ui-icon-calendar { background-position: -32px -112px; }
+.ui-icon-cart { background-position: -48px -112px; }
+.ui-icon-pencil { background-position: -64px -112px; }
+.ui-icon-clock { background-position: -80px -112px; }
+.ui-icon-disk { background-position: -96px -112px; }
+.ui-icon-calculator { background-position: -112px -112px; }
+.ui-icon-zoomin { background-position: -128px -112px; }
+.ui-icon-zoomout { background-position: -144px -112px; }
+.ui-icon-search { background-position: -160px -112px; }
+.ui-icon-wrench { background-position: -176px -112px; }
+.ui-icon-gear { background-position: -192px -112px; }
+.ui-icon-heart { background-position: -208px -112px; }
+.ui-icon-star { background-position: -224px -112px; }
+.ui-icon-link { background-position: -240px -112px; }
+.ui-icon-cancel { background-position: 0 -128px; }
+.ui-icon-plus { background-position: -16px -128px; }
+.ui-icon-plusthick { background-position: -32px -128px; }
+.ui-icon-minus { background-position: -48px -128px; }
+.ui-icon-minusthick { background-position: -64px -128px; }
+.ui-icon-close { background-position: -80px -128px; }
+.ui-icon-closethick { background-position: -96px -128px; }
+.ui-icon-key { background-position: -112px -128px; }
+.ui-icon-lightbulb { background-position: -128px -128px; }
+.ui-icon-scissors { background-position: -144px -128px; }
+.ui-icon-clipboard { background-position: -160px -128px; }
+.ui-icon-copy { background-position: -176px -128px; }
+.ui-icon-contact { background-position: -192px -128px; }
+.ui-icon-image { background-position: -208px -128px; }
+.ui-icon-video { background-position: -224px -128px; }
+.ui-icon-script { background-position: -240px -128px; }
+.ui-icon-alert { background-position: 0 -144px; }
+.ui-icon-info { background-position: -16px -144px; }
+.ui-icon-notice { background-position: -32px -144px; }
+.ui-icon-help { background-position: -48px -144px; }
+.ui-icon-check { background-position: -64px -144px; }
+.ui-icon-bullet { background-position: -80px -144px; }
+.ui-icon-radio-off { background-position: -96px -144px; }
+.ui-icon-radio-on { background-position: -112px -144px; }
+.ui-icon-pin-w { background-position: -128px -144px; }
+.ui-icon-pin-s { background-position: -144px -144px; }
+.ui-icon-play { background-position: 0 -160px; }
+.ui-icon-pause { background-position: -16px -160px; }
+.ui-icon-seek-next { background-position: -32px -160px; }
+.ui-icon-seek-prev { background-position: -48px -160px; }
+.ui-icon-seek-end { background-position: -64px -160px; }
+.ui-icon-seek-start { background-position: -80px -160px; }
+/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
+.ui-icon-seek-first { background-position: -80px -160px; }
+.ui-icon-stop { background-position: -96px -160px; }
+.ui-icon-eject { background-position: -112px -160px; }
+.ui-icon-volume-off { background-position: -128px -160px; }
+.ui-icon-volume-on { background-position: -144px -160px; }
+.ui-icon-power { background-position: 0 -176px; }
+.ui-icon-signal-diag { background-position: -16px -176px; }
+.ui-icon-signal { background-position: -32px -176px; }
+.ui-icon-battery-0 { background-position: -48px -176px; }
+.ui-icon-battery-1 { background-position: -64px -176px; }
+.ui-icon-battery-2 { background-position: -80px -176px; }
+.ui-icon-battery-3 { background-position: -96px -176px; }
+.ui-icon-circle-plus { background-position: 0 -192px; }
+.ui-icon-circle-minus { background-position: -16px -192px; }
+.ui-icon-circle-close { background-position: -32px -192px; }
+.ui-icon-circle-triangle-e { background-position: -48px -192px; }
+.ui-icon-circle-triangle-s { background-position: -64px -192px; }
+.ui-icon-circle-triangle-w { background-position: -80px -192px; }
+.ui-icon-circle-triangle-n { background-position: -96px -192px; }
+.ui-icon-circle-arrow-e { background-position: -112px -192px; }
+.ui-icon-circle-arrow-s { background-position: -128px -192px; }
+.ui-icon-circle-arrow-w { background-position: -144px -192px; }
+.ui-icon-circle-arrow-n { background-position: -160px -192px; }
+.ui-icon-circle-zoomin { background-position: -176px -192px; }
+.ui-icon-circle-zoomout { background-position: -192px -192px; }
+.ui-icon-circle-check { background-position: -208px -192px; }
+.ui-icon-circlesmall-plus { background-position: 0 -208px; }
+.ui-icon-circlesmall-minus { background-position: -16px -208px; }
+.ui-icon-circlesmall-close { background-position: -32px -208px; }
+.ui-icon-squaresmall-plus { background-position: -48px -208px; }
+.ui-icon-squaresmall-minus { background-position: -64px -208px; }
+.ui-icon-squaresmall-close { background-position: -80px -208px; }
+.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
+.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
+.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
+.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
+.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
+.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Corner radius */
+.ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; }
+.ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; }
+.ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; }
+.ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; }
+.ui-corner-top { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; }
+.ui-corner-bottom { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; }
+.ui-corner-right {  -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; }
+.ui-corner-left { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; }
+.ui-corner-all { -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; }
+
+/* Overlays */
+.ui-widget-overlay { background: #666666 url(images/ui-bg_diagonals-thick_20_666666_40x40.png) 50% 50% repeat; opacity: .50;filter:Alpha(Opacity=50); }
+.ui-widget-shadow { margin: -5px 0 0 -5px; padding: 5px; background: #000000 url(images/ui-bg_flat_10_000000_40x100.png) 50% 50% repeat-x; opacity: .20;filter:Alpha(Opacity=20); -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; }/*
+ * jQuery UI Autocomplete 1.8.12
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Autocomplete#theming
+ */
+.ui-autocomplete { position: absolute; cursor: default; }	
+
+/* workarounds */
+* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */
+
+/*
+ * jQuery UI Menu 1.8.12
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Menu#theming
+ */
+.ui-menu {
+	list-style:none;
+	padding: 2px;
+	margin: 0;
+	display:block;
+	float: left;
+}
+.ui-menu .ui-menu {
+	margin-top: -3px;
+}
+.ui-menu .ui-menu-item {
+	margin:0;
+	padding: 0;
+	zoom: 1;
+	float: left;
+	clear: left;
+	width: 100%;
+}
+.ui-menu .ui-menu-item a {
+	text-decoration:none;
+	display:block;
+	padding:.2em .4em;
+	line-height:1.5;
+	zoom:1;
+}
+.ui-menu .ui-menu-item a.ui-state-hover,
+.ui-menu .ui-menu-item a.ui-state-active {
+	font-weight: normal;
+	margin: -1px;
+}
+

--- a/css/jquery.ui.datepicker.mobile.css
+++ /dev/null
@@ -1,18 +1,1 @@
-div.hasDatepicker{display:block;padding:0;overflow:visible;margin:8px 0;}
-.ui-datepicker{overflow:visible;margin:0;max-width:500px;}
-.ui-datepicker .ui-datepicker-header{position:relative;padding:.4em 0;border-bottom:0;font-weight:bold;}
-.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next{padding:1px 0 1px 2px;position:absolute;top:.5em;margin-top:0;text-indent:-9999px;}
-.ui-datepicker .ui-datepicker-prev{left:6px;}
-.ui-datepicker .ui-datepicker-next{right:6px;}
-.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center;}
-.ui-datepicker .ui-datepicker-title select{font-size:1em;margin:1px 0;}
-.ui-datepicker select.ui-datepicker-month-year{width:100%;}
-.ui-datepicker select.ui-datepicker-month, .ui-datepicker select.ui-datepicker-year{width:49%;}
-.ui-datepicker table{width:100%;border-collapse:collapse;margin:0;}
-.ui-datepicker td{border-width:1px;padding:0;text-align:center;}
-.ui-datepicker td span, .ui-datepicker td a{display:block;padding:.2em 0;font-weight:bold;margin:0;border-width:0;text-align:center;text-decoration:none;}
-.ui-datepicker-calendar th{padding-top:.3em;padding-bottom:.3em;}
-.ui-datepicker-calendar th span, .ui-datepicker-calendar span.ui-state-default{opacity:.3;}
-.ui-datepicker-calendar td a{padding-top:.5em;padding-bottom:.5em;}
-.min-width-480px div.hasDatepicker{width:63%;display:inline-block;margin:0;}
 

--- /dev/null
+++ b/dotcloud/postinstall
@@ -1,1 +1,19 @@
+#!/bin/bash
+#dotcloud postinstall
 
+curl http://s3-ap-southeast-1.amazonaws.com/busresources/cbrfeed.zip \
+-o /home/dotcloud/current/cbrfeed.zip
+wget http://s3-ap-southeast-1.amazonaws.com/busresources/Graph.obj \
+-O /tmp/Graph.obj
+
+#db setup
+#curl https://github.com/maxious/ACTBus-ui/raw/master/transitdata.cbrfeed.sql.gz -o transitdata.cbrfeed.sql.gz
+#curl https://github.com/maxious/ACTBus-ui/raw/master/lib/postgis.sql -o postgis.sql
+#createlang -d transitdata plpgsql
+#psql -d transitdata -f postgis.sql
+#gunzip /var/www/transitdata.cbrfeed.sql.gz
+#psql -d transitdata -f transitdata.cbrfeed.sql
+#createuser transitdata -SDRP
+#password transitdata
+#psql -c \"GRANT SELECT ON TABLE agency,calendar,calendar_dates,routes,stop_times,stops,trips TO transitdata;\"
+

file:b/dotcloud/push.sh (new)
--- /dev/null
+++ b/dotcloud/push.sh
@@ -1,1 +1,7 @@
+#wget http://s3-ap-southeast-1.amazonaws.com/busresources/opentripplanner-webapp.war 
+cp ~/workspace/opentripplanner/maven.1277125291275/opentripplanner-webapp/target/opentripplanner-webapp.war ./
+#wget http://s3-ap-southeast-1.amazonaws.com/busresources/opentripplanner-api-webapp.war 
+cp ~/workspace/opentripplanner/maven.1277125291275/opentripplanner-api-webapp/target/opentripplanner-api-webapp.war ./
 
+dotcloud push actbus.otp ./
+

--- a/feedback.php
+++ b/feedback.php
@@ -26,7 +26,7 @@
 }
 if (isset($_REQUEST['feedback']) || isset($_REQUEST['newlocation'])){
 	sendEmail("bus.lambda feedback",print_r($_REQUEST,true));
-	echo "<center><h2>Thank you for your feedback!</h2></center>";
+	echo "<h2 style='text-align: center;'>Thank you for your feedback!</h2>";
 } else {
 $stopid = "";
 $stopcode = "";
@@ -48,7 +48,7 @@
 <small> if you click on feedback from a stop page, these will get filled in automatically. else describe the location/street of the stop in one of these boxes </small><br>
 
 Suggested Stop Location (lat/long or words):  <input type="text" name="newlocation"/><br>
-<small> if your device supports javascript, you can pick a location from the map above</small><br>
+<!--<small> if your device supports javascript, you can pick a location from the map above</small><br>-->
 
 <input type="submit" value="Submit!"/>
 </form>

--- /dev/null
+++ b/include/common-db.inc.php
@@ -1,1 +1,23 @@
+<?php
+if (php_uname('n') == "actbus-www") {
+	$conn = new PDO("pgsql:dbname=transitdata;user=transitdata;password=transitdata;host=bus-main.lambdacomplex.org");
+}
+else if (isDebugServer()) {
+	$conn = new PDO("pgsql:dbname=transitdata;user=postgres;password=snmc;host=localhost");
+}
+else {
+	$conn = new PDO("pgsql:dbname=transitdata;user=transitdata;password=transitdata;host=localhost");
+}
+if (!$conn) {
+	die("A database error occurred.\n");
+}
+function databaseError($errMsg)
+{
+	die($errMsg);
+}
+include ('db/route-dao.inc.php');
+include ('db/trip-dao.inc.php');
+include ('db/stop-dao.inc.php');
+include ('db/servicealert-dao.inc.php');
+?>
 

--- a/include/common-geo.inc.php
+++ b/include/common-geo.inc.php
@@ -46,9 +46,9 @@
 		$center = $totalLat / sizeof($mapPoints) . "," . $totalLon / sizeof($mapPoints);
 	}
 	$output = "";
-	if ($collapsible) $output.= '<div data-role="collapsible" data-collapsed="true"><h3>Open Map...</h3>';
-	$output.= '<center><img src="' . curPageURL() . '/lib/staticmaplite/staticmap.php?center=' . $center . '&zoom=' . $zoom . '&size=' . $width . 'x' . $height . '&markers=' . 
-$markers . '" width=' . $width . ' height=' . $height . '></center>';
+	if ($collapsible) $output.= '<div class="map" data-role="collapsible" data-collapsed="true"><h3>Open Map...</h3>';
+	$output.= '<img class="map" src="' . curPageURL() . '/lib/staticmaplite/staticmap.php?center=' . $center . '&amp;zoom=' . $zoom . '&amp;size=' . $width . 'x' . $height . '&amp;markers=' . 
+$markers . '" width=' . $width . ' height=' . $height . '>';
 	if ($collapsible) $output.= '</div>';
 	return $output;
 }
@@ -70,6 +70,7 @@
 	  else return round($km,2)."k";
 	} else return floor($km * 1000);
 }
+
 function decodePolylineToArray($encoded)
 {
 	// source: http://latlongeeks.com/forum/viewtopic.php?f=4&t=5

--- a/include/common-net.inc.php
+++ b/include/common-net.inc.php
@@ -5,7 +5,7 @@
 	$ch = curl_init($url);
 	curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
 	curl_setopt($ch, CURLOPT_HEADER, 0);
-	curl_setopt($ch, CURLOPT_TIMEOUT, 30);
+	curl_setopt($ch, CURLOPT_TIMEOUT, 45);
 	$page = curl_exec($ch);
 	if (curl_errno($ch)) {
 		echo "<font color=red> Database temporarily unavailable: ";

--- /dev/null
+++ b/include/common-request.inc.php
@@ -1,1 +1,48 @@
-
+<?php
+if (isset($_REQUEST['firstLetter'])) {
+	$firstLetter = filter_var($_REQUEST['firstLetter'], FILTER_SANITIZE_STRING);
+}
+if (isset($_REQUEST['bysuburbs'])) {
+	$bysuburbs = true;
+}
+if (isset($_REQUEST['bynumber'])) {
+	$bynumber = true;
+}
+if (isset($_REQUEST['allstops'])) {
+	$allstops = true;
+}
+if (isset($_REQUEST['nearby'])) {
+	$nearby = true;
+}
+if (isset($_REQUEST['suburb'])) {
+	$suburb = filter_var($_REQUEST['suburb'], FILTER_SANITIZE_STRING);
+}
+$pageKey = filter_var($_REQUEST['pageKey'], FILTER_SANITIZE_NUMBER_INT);
+$lat = filter_var($_REQUEST['lat'], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
+$lon = filter_var($_REQUEST['lon'], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
+$max_distance = filter_var($_REQUEST['radius'], FILTER_SANITIZE_NUMBER_INT);
+if (isset($_REQUEST['numberSeries'])) {
+	$numberSeries = filter_var($_REQUEST['numberSeries'], FILTER_SANITIZE_NUMBER_INT);
+}
+if (isset($_REQUEST['routeDestination'])) {
+	$routeDestination = urldecode(filter_var($_REQUEST['routeDestination'], FILTER_SANITIZE_ENCODED));
+}
+if (isset($_REQUEST['stopcode'])) {
+	$stopcode = filter_var($_REQUEST['stopcode'], FILTER_SANITIZE_STRING);
+}
+if (isset($_REQUEST['stopids'])) {
+	$stopids = explode(",", filter_var($_REQUEST['stopids'], FILTER_SANITIZE_STRING));
+}
+if (isset($_REQUEST['tripid'])) {
+	$tripid = filter_var($_REQUEST['tripid'], FILTER_SANITIZE_NUMBER_INT);
+}
+if (isset($_REQUEST['stopid'])) {
+	$stopid = filter_var($_REQUEST['stopid'], FILTER_SANITIZE_NUMBER_INT);
+}
+if (isset($_REQUEST['routeid'])) {
+	$routeid = filter_var($_REQUEST['routeid'], FILTER_SANITIZE_NUMBER_INT);
+}
+if (isset($_REQUEST['geolocate'])) {
+$geolocate = filter_var($_REQUEST['geolocate'], FILTER_SANITIZE_URL);
+}
+?>

--- a/include/common-session.inc.php
+++ b/include/common-session.inc.php
@@ -9,17 +9,21 @@
 	$_SESSION['time'] = filter_var($_REQUEST['time'], FILTER_SANITIZE_STRING);
 	sessionUpdated();
 }
-if (isset($_REQUEST['geolocate'])) {
+if (isset($_REQUEST['geolocate']) && $_REQUEST['geolocate'] != "Enter co-ordinates or address here") {
 	$geocoded = false;
 	if (isset($_REQUEST['lat']) && isset($_REQUEST['lon'])) {
 		$_SESSION['lat'] = trim(filter_var($_REQUEST['lat'], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION));
 		$_SESSION['lon'] = trim(filter_var($_REQUEST['lon'], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION));
 	}
 	else {
-		$geolocate = filter_var($_REQUEST['geolocate'], FILTER_SANITIZE_URL);
-		echo $_REQUEST['geolocate'];
 		if (startsWith($geolocate, "-")) {
 			$locateparts = explode(",", $geolocate);
+			$_SESSION['lat'] = $locateparts[0];
+			$_SESSION['lon'] = $locateparts[1];
+		}
+		else if (strpos($geolocate, "(") !== false) {
+			$geoParts = explode("(", $geolocate);
+			$locateparts = explode(",", str_replace(")", "",$geoParts[1]));
 			$_SESSION['lat'] = $locateparts[0];
 			$_SESSION['lon'] = $locateparts[1];
 		}
@@ -38,20 +42,24 @@
 		}
 	}
 	if ($_SESSION['lat'] != "" && isAnalyticsOn()) {
-		trackEvent("Geolocation","Updated Location", "Geocoded - ".($geocoded ? "Yes" : "No"));
+		trackEvent("Geolocation", "Updated Location", "Geocoded - " . ($geocoded ? "Yes" : "No"));
 	}
 	sessionUpdated();
 }
-function sessionUpdated() {
+function sessionUpdated()
+{
 	$_SESSION['lastUpdated'] = time();
 }
 // timeoutSession
-$TIMEOUT_LIMIT = 60*5; // 5 minutes
-if (isset($_SESSION['lastUpdated']) && $_SESSION['lastUpdated']+$TIMEOUT_LIMIT < time()) {
-	debug ("Session timeout ".($_SESSION['lastUpdated']+$TIMEOUT_LIMIT).">".time(),"session");
+$TIMEOUT_LIMIT = 60 * 5; // 5 minutes
+if (isset($_SESSION['lastUpdated']) && $_SESSION['lastUpdated'] + $TIMEOUT_LIMIT < time()) {
+	debug("Session timeout " . ($_SESSION['lastUpdated'] + $TIMEOUT_LIMIT) . ">" . time() , "session");
 	session_destroy();
 	session_start();
 }
-debug(print_r($_SESSION, true) , "session");
-
+//debug(print_r($_SESSION, true) , "session");
+function current_time()
+{
+	return ($_SESSION['time'] ? $_SESSION['time'] : date("H:i:s"));
+}
 ?>

--- a/include/common-template.inc.php
+++ b/include/common-template.inc.php
@@ -1,28 +1,27 @@
 <?php
-  // Copyright 2009 Google Inc. All Rights Reserved.
-  $GA_ACCOUNT = "MO-22173039-1";
-  $GA_PIXEL = "/lib/ga.php";
-
-  function googleAnalyticsGetImageUrl() {
-    global $GA_ACCOUNT, $GA_PIXEL;
-    $url = "";
-    $url .= $GA_PIXEL . "?";
-    $url .= "utmac=" . $GA_ACCOUNT;
-    $url .= "&utmn=" . rand(0, 0x7fffffff);
-    $referer = $_SERVER["HTTP_REFERER"];
-    $query = $_SERVER["QUERY_STRING"];
-    $path = $_SERVER["REQUEST_URI"];
-    if (empty($referer)) {
-      $referer = "-";
-    }
-    $url .= "&utmr=" . urlencode($referer);
-    if (!empty($path)) {
-      $url .= "&utmp=" . urlencode($path);
-    }
-    $url .= "&guid=ON";
-    return str_replace("&", "&amp;", $url);
-  }
-
+// Copyright 2009 Google Inc. All Rights Reserved.
+$GA_ACCOUNT = "MO-22173039-1";
+$GA_PIXEL = "/lib/ga.php";
+function googleAnalyticsGetImageUrl()
+{
+	global $GA_ACCOUNT, $GA_PIXEL;
+	$url = "";
+	$url.= $GA_PIXEL . "?";
+	$url.= "utmac=" . $GA_ACCOUNT;
+	$url.= "&utmn=" . rand(0, 0x7fffffff);
+	$referer = $_SERVER["HTTP_REFERER"];
+	$query = $_SERVER["QUERY_STRING"];
+	$path = $_SERVER["REQUEST_URI"];
+	if (empty($referer)) {
+		$referer = "-";
+	}
+	$url.= "&utmr=" . urlencode($referer);
+	if (!empty($path)) {
+		$url.= "&utmp=" . urlencode($path);
+	}
+	$url.= "&guid=ON";
+	return str_replace("&", "&amp;", $url);
+}
 function include_header($pageTitle, $pageType, $opendiv = true, $geolocate = false, $datepicker = false)
 {
 	echo '
@@ -32,29 +31,49 @@
         <meta charset="UTF-8">
 	<title>' . $pageTitle . '</title>
         <meta name="google-site-verification" 
-content="-53T5Qn4TB_de1NyfR_ZZkEVdUNcNFSaYKSFkWKx-sY" />';
-	if ($datepicker) echo '<link rel="stylesheet"  href="css/jquery.ui.datepicker.mobile.css" />';
-	if (isDebugServer()) echo '<link rel="stylesheet"  href="css/jquery.mobile-1.0a4.css" />
+content="-53T5Qn4TB_de1NyfR_ZZkEVdUNcNFSaYKSFkWKx-sY" />
+	<link rel="stylesheet"  href="css/jquery-ui-1.8.12.custom.css" />';
+	if (isDebugServer()) {
+		echo '<link rel="stylesheet"  href="css/jquery.mobile-1.0a4.css" />
+	
          <script type="text/javascript" src="js/jquery-1.5.js"></script>
 	 <script>$(document).bind("mobileinit", function(){
   $.mobile.ajaxEnabled = false;
 });
 </script>
         <script type="text/javascript" src="js/jquery.mobile-1.0a4.js"></script>';
-	else echo '<link rel="stylesheet"  href="http://code.jquery.com/mobile/1.0a4/jquery.mobile-1.0a4.min.css" />
+	}
+	else {
+		echo '<link rel="stylesheet"  href="http://code.jquery.com/mobile/1.0a4.1/jquery.mobile-1.0a4.1.min.css" />
         <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.2/jquery.min.js"></script>
 	 <script>$(document).bind("mobileinit", function(){
   $.mobile.ajaxEnabled = false;
 });
 </script>
-        <script type="text/javascript" src="http://code.jquery.com/mobile/1.0a4/jquery.mobile-1.0a4.min.js"></script>';
-	if ($datepicker) echo '<script> 
-		//reset type=date inputs to text
-		$( document ).bind( "mobileinit", function(){
-			$.mobile.page.prototype.options.degradeInputs.date = true;
-		});	
-	</script> 
-	<script src="js/jQuery.ui.datepicker.js"></script>';
+        <script type="text/javascript" src="http://code.jquery.com/mobile/1.0a4.1/jquery.mobile-1.0a4.1.min.js"></script>';
+	}
+	echo '
+	<script src="js/jquery.ui.autocomplete.min.js"></script>
+<script src="js/jquery.ui.core.min.js"></script>
+<script src="js/jquery.ui.position.min.js"></script>
+<script src="js/jquery.ui.widget.min.js"></script>
+  <script>
+	$(function() {
+		$( "#geolocate" ).autocomplete({
+			source: "lib/autocomplete.php",
+			minLength: 2
+		});
+		$( "#from" ).autocomplete({
+			source: "lib/autocomplete.php",
+			minLength: 2
+		});
+		$( "#to" ).autocomplete({
+			source: "lib/autocomplete.php",
+			minLength: 2
+		});
+	});
+	</script>
+	';
 	echo '<style type="text/css">
      .ui-navbar {
      width: 100%;
@@ -68,10 +87,14 @@
     .ui-listview-filter {
         margin: 0 !important;
      }
-     .ui-icon-navigation {
+    .ui-icon-navigation {
         background-image: url(css/images/113-navigation.png);
         background-position: 1px 0;
      }
+    .ui-icon-beaker {
+        background-image: url(css/images/91-beaker-2.png);
+        background-position: 1px 0;
+    }
     #footer {
         text-size: 0.75em;
         text-align: center;
@@ -93,6 +116,16 @@
     visibility: hidden;
     display: none;
     }
+    #servicewarning {
+    padding: 1em;
+    margin-bottom: 0.5em;
+    text-size: 0.2em;
+    background-color: #FF9;
+    -moz-border-radius: 15px;
+border-radius: 15px;
+    }
+ 
+
     // source http://webaim.org/techniques/skipnav/
     #skip a, #skip a:hover, #skip a:visited 
 { 
@@ -145,17 +178,17 @@
 $('#here').show();
 });
 ";
-if (!isset($_SESSION['lat']) || $_SESSION['lat'] == "") echo "geolocate();";
-echo "</script> ";
+		if (!isset($_SESSION['lat']) || $_SESSION['lat'] == "") echo "geolocate();";
+		echo "</script> ";
 	}
 	if (isAnalyticsOn()) echo '
-<script type="text/javascript">'."
+<script type="text/javascript">' . "
 
   var _gaq = _gaq || [];
   _gaq.push(['_setAccount', 'UA-22173039-1']);
   _gaq.push(['_trackPageview']);
 </script>";
-echo '</head>
+	echo '</head>
 <body>
     <div id="skip">
     <a href="#maincontent">Skip to content</a>
@@ -163,27 +196,32 @@
  ';
 	if ($opendiv) {
 		echo '<div data-role="page"> 
- <script>
-$(document).ready(function ()
-{
-    document.title = "' . $pageTitle . '";
-});
-</script>
 	<div data-role="header" data-position="inline">
-	<a href="'.$_SERVER["HTTP_REFERER"].'" data-icon="arrow-l" data-rel="back" class="ui-btn-left">Back</a> 
+	<a href="' . (isset($_SERVER["HTTP_REFERER"]) ? $_SERVER["HTTP_REFERER"] : "javascript:history.go(-1)") . '" data-icon="arrow-l" data-rel="back" class="ui-btn-left">Back</a> 
 		<h1>' . $pageTitle . '</h1>
 		<a href="/index.php" data-icon="home" class="ui-btn-right">Home</a>
 	</div><!-- /header -->
         <a name="maincontent" id="maincontent"></a>
         <div data-role="content"> ';
-	}
+		$overrides = getServiceOverride();
+		if ($overrides['service_id']) {
+				if ($overrides['service_id'] == "noservice") {
+					echo '<div id="servicewarning">Buses are <strong>not running today</strong> due to industrial action/public holiday. See <a 
+href="http://www.action.act.gov.au">http://www.action.act.gov.au</a> for details.</div>';
+				}
+				else {
+					echo '<div id="servicewarning">Buses are running on an altered timetable today due to industrial action/public holiday. See <a href="http://www.action.act.gov.au">http://www.action.act.gov.au</a> for details.</div>';
+				}
+			}
+		}
+
 }
 function include_footer()
 {
-	echo '<div id="footer"><a href="about.php">About/Contact Us</a>&nbsp;<a href="feedback.php">Feedback/Bug Report</a></a>';
+	echo '<div id="footer"><a href="about.php">About/Contact Us</a>&nbsp;<a href="feedback.php">Feedback/Bug Report</a>';
 	echo '</div>';
-        if (isAnalyticsOn()) {
-	echo "<script>  (function() {
+	if (isAnalyticsOn()) {
+		echo "<script>  (function() {
     var ga = document.createElement('script'); ga.type = 
 'text/javascript'; ga.async = true;
     ga.src = ('https:' == document.location.protocol ? 
@@ -191,9 +229,11 @@
     var s = document.getElementsByTagName('script')[0]; 
 s.parentNode.insertBefore(ga, s);
   })();</script>";
-         $googleAnalyticsImageUrl = googleAnalyticsGetImageUrl();
-  echo '<noscript><img src="' . $googleAnalyticsImageUrl . '" /></noscript>';
-    }
+		$googleAnalyticsImageUrl = googleAnalyticsGetImageUrl();
+		echo '<noscript><img src="' . $googleAnalyticsImageUrl . '" /></noscript>';
+
+	}
+			echo "\n</div></div></body></html>";
 }
 function timePlaceSettings($geolocate = false)
 {
@@ -202,14 +242,16 @@
 	if ($geolocate == true) {
 		$geoerror = !isset($_SESSION['lat']) || !isset($_SESSION['lat']) || $_SESSION['lat'] == "" || $_SESSION['lon'] == "";
 	}
+	echo '<div id="error">';
 	if ($geoerror) {
-		echo '<div id="error">Sorry, but your location could not currently be detected.
+		echo 'Sorry, but your location could not currently be detected.
         Please allow location permission, wait for your location to be detected,
-        or enter an address/co-ordinates in the box below.</div>';
-	}
+        or enter an address/co-ordinates in the box below.';
+	}
+	echo '</div>';
 	echo '<div data-role="collapsible" data-collapsed="' . !$geoerror . '">
         <h3>Change Time/Place (' . (isset($_SESSION['time']) ? $_SESSION['time'] : "Current Time,") . ' ' . ucwords(service_period()) . ')...</h3>
-        <form action="'.basename($_SERVER['PHP_SELF'])."?".$_SERVER['QUERY_STRING'].'" method="post">
+        <form action="' . basename($_SERVER['PHP_SELF']) . "?" . $_SERVER['QUERY_STRING'] . '" method="post">
         <div class="ui-body"> 
 		<div data-role="fieldcontain">
 	            <label for="geolocate"> Current Location: </label>
@@ -218,7 +260,7 @@
     		<div data-role="fieldcontain">
 		        <label for="time"> Time: </label>
 		    	<input type="time" name="time" id="time" value="' . (isset($_SESSION['time']) ? $_SESSION['time'] : date("H:i")) . '"/>
-			<a href="#" name="currentTime" id="currentTime" onClick="var d = new Date();'. "$('#time').val(d.getHours() +':'+ (d.getMinutes().toString().length = 1 ? '0'+ d.getMinutes():  d.getMinutes()));".'">Current Time?</a>
+			<a href="#" name="currentTime" id="currentTime" onClick="var d = new Date();' . "$('#time').val(d.getHours() +':'+ (d.getMinutes().toString().length == 1 ? '0'+ d.getMinutes():  d.getMinutes()));" . '">Current Time?</a>
 	        </div>
 		<div data-role="fieldcontain">
 		    <label for="service_period"> Service Period:  </label>
@@ -227,17 +269,18 @@
 		echo "<option value=\"$service_period\"" . (service_period() === $service_period ? " SELECTED" : "") . '>' . ucwords($service_period) . '</option>';
 	}
 	echo '</select>
-			<a href="#" style="display:none" name="currentPeriod" id="currentPeriod"/>Current Period?</a>
+			<a href="#" style="display:none" name="currentPeriod" id="currentPeriod">Current Period?</a>
 		</div>
 		
 		<input type="submit" value="Update"/>
-                </form>
-            </div></div>';
-}
-function trackEvent($category, $action, $label = "", $value = -1) {
-  if (isAnalyticsOn()) {
-    echo "\n<script> _gaq.push(['_trackEvent', '$category', '$action'".($label != "" ? ", '$label'" : "").($value != -1 ? ", $value" : "")."]);</script>";
-  }
+                </div></form>
+            </div>';
+}
+function trackEvent($category, $action, $label = "", $value = - 1)
+{
+	if (isAnalyticsOn()) {
+		echo "\n<script> _gaq.push(['_trackEvent', '$category', '$action'" . ($label != "" ? ", '$label'" : "") . ($value != - 1 ? ", $value" : "") . "]);</script>";
+	}
 }
 ?>
 

--- a/include/common-transit.inc.php
+++ b/include/common-transit.inc.php
@@ -4,9 +4,16 @@
 	'saturday',
 	'weekday'
 );
+
 function service_period()
 {
+	
 	if (isset($_SESSION['service_period'])) return $_SESSION['service_period'];
+	$override = getServiceOverride();
+	if ($override['service_id']){
+		return $override['service_id'];
+	}
+
 	switch (date('w')) {
 	case 0:
 		return 'sunday';
@@ -35,39 +42,5 @@
 		return "";
 	}
 }
-function viaPoints($tripid, $stopid, $timingPointsOnly = false)
-{
-	global $APIurl;
-	$url = $APIurl . "/json/tripstoptimes?trip=" . $tripid;
-	$json = json_decode(getPage($url));
-	debug(print_r($json, true));
-	$stops = $json[0];
-	$times = $json[1];
-	$foundStop = false;
-	$viaPoints = Array();
-	foreach ($stops as $key => $row) {
-		if ($foundStop) {
-			if (!$timingPointsOnly || !startsWith($row[5], "Wj")) {
-				$viaPoints[] = Array(
-					"id" => $row[0],
-					"name" => $row[1],
-					"time" => $times[$key]
-				);
-			}
-		}
-		else {
-			if ($row[0] == $stopid) $foundStop = true;
-		}
-	}
-	return $viaPoints;
-}
-function viaPointNames($tripid, $stopid)
-{
-	$points = viaPoints($tripid, $stopid, true);
-	$pointNames = Array();
-	foreach ($points as $point) {
-		$pointNames[] = $point['name'];
-	}
-	return implode(", ", $pointNames);
-}
 ?>
+

--- a/include/common.inc.php
+++ b/include/common.inc.php
@@ -1,24 +1,23 @@
 <?php
 date_default_timezone_set('Australia/ACT');
-$APIurl = "http://localhost:8765";
 $debugOkay = Array(
 	"session",
 	"json",
 	"phperror",
-	"awsgtfs",
 	"awsotp",
 	//"squallotp",
-	//"vanilleotp",
+	"vanilleotp",
+	"database",
 	"other"
 );
-if (isDebug("awsgtfs")) {
-	$APIurl = "http://bus-main.lambdacomplex.org:8765";
-}
 $cloudmadeAPIkey = "daa03470bb8740298d4b10e3f03d63e6";
 $googleMapsAPIkey = "ABQIAAAA95XYXN0cki3Yj_Sb71CFvBTPaLd08ONybQDjcH_VdYtHHLgZvRTw2INzI_m17_IoOUqH3RNNmlTk1Q";
 $otpAPIurl = 'http://localhost:8080/opentripplanner-api-webapp/';
 if (isDebug("awsotp") || php_uname('n') == "maxious.xen.prgmr.com") {
 	$otpAPIurl = 'http://bus-main.lambdacomplex.org:8080/opentripplanner-api-webapp/';
+}
+if (isDebug("dotcloudotp") || php_uname('n') == "actbus-www") {
+	$otpAPIurl = 'http://otp.actbus.dotcloud.com/opentripplanner-api-webapp/';
 }
 if (isDebug("squallotp")) {
 		$otpAPIurl = 'http://10.0.1.108:5080/opentripplanner-api-webapp/';
@@ -31,7 +30,9 @@
 include_once ("common-geo.inc.php");
 include_once ("common-net.inc.php");
 include_once ("common-transit.inc.php");
+include_once ("common-db.inc.php");
 
+include_once ("common-request.inc.php");
 include_once ("common-session.inc.php");
 include_once ("common-template.inc.php");
 
@@ -143,5 +144,42 @@
 	if ($sort_ascending) $array = array_reverse($temp_array);
 	else $array = $temp_array;
 }
+function sktimesort(&$array, $subkey = "id", $sort_ascending = false)
+{
+	if (count($array)) $temp_array[key($array) ] = array_shift($array);
+	foreach ($array as $key => $val) {
+		$offset = 0;
+		$found = false;
+		foreach ($temp_array as $tmp_key => $tmp_val) {
+			if (!$found and strtotime($val[$subkey]) > strtotime($tmp_val[$subkey])) {
+				$temp_array = array_merge((array)array_slice($temp_array, 0, $offset) , array(
+					$key => $val
+				) , array_slice($temp_array, $offset));
+				$found = true;
+			}
+			$offset++;
+		}
+		if (!$found) $temp_array = array_merge($temp_array, array(
+			$key => $val
+		));
+	}
+	if ($sort_ascending) $array = array_reverse($temp_array);
+	else $array = $temp_array;
+}
+function r_implode( $glue, $pieces ) 
+{ 
+  foreach( $pieces as $r_pieces ) 
+  { 
+    if( is_array( $r_pieces ) ) 
+    { 
+      $retVal[] = r_implode( $glue, $r_pieces ); 
+    } 
+    else 
+    { 
+      $retVal[] = $r_pieces; 
+    } 
+  } 
+  return implode( $glue, $retVal ); 
+} 
 ?>
 

--- /dev/null
+++ b/include/db/route-dao.inc.php
@@ -1,1 +1,211 @@
+<?php
+function getRoute($routeID)
+{
+	global $conn;
+	$query = "Select * from routes where route_id = :routeID LIMIT 1";
+	debug($query, "database");
+	$query = $conn->prepare($query);
+	$query->bindParam(":routeID", $routeID);
+	$query->execute();
+	if (!$query) {
+		databaseError($conn->errorInfo());
+		return Array();
+	}
+	return $query->fetch(PDO::FETCH_ASSOC);
+}
+function getRoutes()
+{
+	global $conn;
+	$query = "Select * from routes order by route_short_name;";
+	debug($query, "database");
+	$query = $conn->prepare($query);
+	$query->execute();
+	if (!$query) {
+		databaseError($conn->errorInfo());
+		return Array();
+	}
+	return $query->fetchAll();
+}
+function getRoutesByNumber($routeNumber = "")
+{
+	global $conn;
+	if ($routeNumber != "") {
+		$query = "Select distinct routes.route_id,routes.route_short_name,routes.route_long_name,service_id from routes  join trips on trips.route_id =
+routes.route_id join stop_times on stop_times.trip_id = trips.trip_id where route_short_name = :routeNumber order by route_short_name;";
+	}
+	else {
+		$query = "SELECT DISTINCT route_short_name from routes order by route_short_name";
+	}
+	debug($query, "database");
+	$query = $conn->prepare($query);
+	if ($routeNumber != "") {
+		$query->bindParam(":routeNumber", $routeNumber);
+	}
+	$query->execute();
+	if (!$query) {
+		databaseError($conn->errorInfo());
+		return Array();
+	}
+	return $query->fetchAll();
+}
+function getRoutesByNumberSeries($routeNumberSeries = "")
+{
+	global $conn;
+	if (strlen($routeNumberSeries) == 1) {
+		return getRoutesByNumber($routeNumberSeries);
+	}
+	$seriesMin = substr($routeNumberSeries, 0, -1) . "0";
+	$seriesMax = substr($routeNumberSeries, 0, -1) . "9";
+	$query = "Select distinct routes.route_id,routes.route_short_name,routes.route_long_name,service_id from routes  join trips on trips.route_id =
+routes.route_id join stop_times on stop_times.trip_id = trips.trip_id where to_number(route_short_name, 'FM999') between :seriesMin and :seriesMax OR route_short_name LIKE :routeNumberSeries order by route_short_name;";
+	debug($query, "database");
+	$query = $conn->prepare($query);
+	$query->bindParam(":seriesMin", $seriesMin);
+	$query->bindParam(":seriesMax", $seriesMax);
+        $routeNumberSeries = "% ".substr($routeNumberSeries, 0, -1)."%";
+        $query->bindParam(":routeNumberSeries", $routeNumberSeries);
+	$query->execute();
+	if (!$query) {
+		databaseError($conn->errorInfo());
+		return Array();
+	}
+	return $query->fetchAll();
+}
+function getRouteNextTrip($routeID)
+{
+	global $conn;
+	$query = "select * from routes join trips on trips.route_id = routes.route_id
+join stop_times on stop_times.trip_id = trips.trip_id where
+arrival_time > :currentTime and routes.route_id = :routeID order by
+arrival_time limit 1";
+	debug($query, "database");
+	$query = $conn->prepare($query);
+	$query->bindParam(":currentTime", current_time());
+	$query->bindParam(":routeID", $routeID);
+	$query->execute();
+	if (!$query) {
+		databaseError($conn->errorInfo());
+		return Array();
+	}
+	$r = $query->fetch(PDO::FETCH_ASSOC);
 
+	// past last trip of the day special case
+	if (sizeof($r) < 16) {
+		$query = "select * from routes join trips on trips.route_id = routes.route_id
+join stop_times on stop_times.trip_id = trips.trip_id where routes.route_id = :routeID order by
+arrival_time DESC limit 1";
+		debug($query, "database");
+		$query = $conn->prepare($query);
+		$query->bindParam(":routeID", $routeID);
+		$query->execute();
+		if (!$query) {
+			databaseError($conn->errorInfo());
+			return Array();
+		}
+            
+		$r = $query->fetch(PDO::FETCH_ASSOC);
+	}
+	return $r;
+}
+function getTimeInterpolatedRouteAtStop($routeID, $stop_id)
+{
+	$nextTrip = getRouteNextTrip($routeID);
+	if ($nextTrip['trip_id']) {
+		foreach (getTimeInterpolatedTrip($nextTrip['trip_id']) as $tripStop) {
+			if ($tripStop['stop_id'] == $stop_id) return $tripStop;
+		}
+	}
+	return Array();
+}
+function getRouteTrips($routeID)
+{
+	global $conn;
+	$query = "select routes.route_id,trips.trip_id,service_id,arrival_time, stop_id, stop_sequence from routes join trips on trips.route_id = routes.route_id
+join stop_times on stop_times.trip_id = trips.trip_id where routes.route_id = :routeID and stop_sequence = '1' order by
+arrival_time ";
+	debug($query, "database");
+	$query = $conn->prepare($query);
+	$query->bindParam(":routeID", $routeID);
+	$query->execute();
+	if (!$query) {
+		databaseError($conn->errorInfo());
+		return Array();
+	}
+	return $query->fetchAll();
+}
+function getRoutesByDestination($destination = "", $service_period = "")
+{
+	global $conn;
+	if ($service_period == "") $service_period = service_period();
+	if ($destination != "") {
+		$query = "SELECT DISTINCT trips.route_id,route_short_name,route_long_name, service_id
+FROM stop_times join trips on trips.trip_id =
+stop_times.trip_id join routes on trips.route_id = routes.route_id
+WHERE route_long_name = :destination AND  service_id=:service_period order by route_short_name";
+	}
+	else {
+		$query = "SELECT DISTINCT route_long_name
+FROM stop_times join trips on trips.trip_id =
+stop_times.trip_id join routes on trips.route_id = routes.route_id
+WHERE service_id= :service_period order by route_long_name";
+	}
+	debug($query, "database");
+	$query = $conn->prepare($query);
+	$query->bindParam(":service_period", $service_period);
+	if ($destination != "") $query->bindParam(":destination", $destination);
+	$query->execute();
+	if (!$query) {
+		databaseError($conn->errorInfo());
+		return Array();
+	}
+	return $query->fetchAll();
+}
+function getRoutesBySuburb($suburb, $service_period = "")
+{
+	if ($service_period == "") $service_period = service_period();
+	global $conn;
+	$query = "SELECT DISTINCT service_id,trips.route_id,route_short_name,route_long_name
+FROM stop_times join trips on trips.trip_id = stop_times.trip_id
+join routes on trips.route_id = routes.route_id
+join stops on stops.stop_id = stop_times.stop_id
+WHERE zone_id LIKE ':suburb AND service_id=:service_period ORDER BY route_short_name";
+	debug($query, "database");
+	$query = $conn->prepare($query);
+	$query->bindParam(":service_period", $service_period);
+        $suburb = "%" . $suburb . ";%";
+	$query->bindParam(":suburb", $suburb);
+	$query->execute();
+	if (!$query) {
+		databaseError($conn->errorInfo());
+		return Array();
+	}
+	return $query->fetchAll();
+}
+function getRoutesNearby($lat, $lng, $limit = "", $distance = 500)
+{
+	if ($service_period == "") $service_period = service_period();
+	if ($limit != "") $limitSQL = " LIMIT :limit ";
+	global $conn;
+	$query = "SELECT service_id,trips.route_id,route_short_name,route_long_name,min(stops.stop_id) as stop_id,
+        min(ST_Distance(position, ST_GeographyFromText('SRID=4326;POINT($lng $lat)'), FALSE)) as distance
+FROM stop_times
+join trips on trips.trip_id = stop_times.trip_id
+join routes on trips.route_id = routes.route_id
+join stops on stops.stop_id = stop_times.stop_id
+WHERE service_id=:service_period
+AND ST_DWithin(position, ST_GeographyFromText('SRID=4326;POINT($lng $lat)'), :distance, FALSE)
+        group by service_id,trips.route_id,route_short_name,route_long_name
+        order by distance $limitSQL";
+	debug($query, "database");
+	$query = $conn->prepare($query);
+	$query->bindParam(":service_period", $service_period);
+	$query->bindParam(":distance", $distance);
+	$query->bindParam(":limit", $limit);
+	$query->execute();
+	if (!$query) {
+		databaseError($conn->errorInfo());
+		return Array();
+	}
+	return $query->fetchAll();
+}
+?>

--- /dev/null
+++ b/include/db/servicealert-dao.inc.php
@@ -1,1 +1,15 @@
-
+<?php
+function getServiceOverride() {
+	global $conn;
+	$query = "Select * from calendar_dates where date = :date and exception_type = '1' LIMIT 1";
+	 debug($query,"database");
+	$query = $conn->prepare($query); // Create a prepared statement
+	$query->bindParam(":date", date("Ymd"));
+	$query->execute();
+	if (!$query) {
+		databaseError($conn->errorInfo());
+		return Array();
+	}
+	return $query->fetch(PDO::FETCH_ASSOC);
+}
+?>

--- /dev/null
+++ b/include/db/stop-dao.inc.php
@@ -1,1 +1,159 @@
-
+<?php
+function getStop($stopID)
+{
+	global $conn;
+	$query = "Select * from stops where stop_id = :stopID LIMIT 1";
+	debug($query, "database");
+	$query = $conn->prepare($query);
+	$query->bindParam(":stopID", $stopID);
+	$query->execute();
+	if (!$query) {
+		databaseError($conn->errorInfo());
+		return Array();
+	}
+	return $query->fetch(PDO::FETCH_ASSOC);
+}
+function getStops($timingPointsOnly = false, $firstLetter = "")
+{
+	global $conn;
+	$conditions = Array();
+	if ($timingPointsOnly) $conditions[] = "substr(stop_code,1,2) != 'Wj'";
+	if ($firstLetter != "") $conditions[] = "substr(stop_name,1,1) = :firstLetter";
+	$query = "Select * from stops";
+	if (sizeof($conditions) > 0) {
+		if (sizeof($conditions) > 1) {
+			$query.= " Where " . implode(" AND ", $conditions) . " ";
+		}
+		else {
+			$query.= " Where " . $conditions[0] . " ";
+		}
+	}
+	$query.= " order by stop_name;";
+	$query = $conn->prepare($query);
+        $query->bindParam(":firstLetter", $firstLetter);
+	$query->execute();
+	if (!$query) {
+		databaseError($conn->errorInfo());
+		return Array();
+	}
+	return $query->fetchAll();
+}
+function getNearbyStops($lat, $lng, $limit = "", $distance = 1000)
+{
+	if ($lat == null || $lng == null) return Array();
+	if ($limit != "") $limitSQL = " LIMIT :limit ";
+	global $conn;
+	$query = "Select *, ST_Distance(position, ST_GeographyFromText('SRID=4326;POINT($lng $lat)'), FALSE) as distance
+        from stops WHERE ST_DWithin(position, ST_GeographyFromText('SRID=4326;POINT($lng $lat)'), :distance, FALSE)
+        order by distance $limitSQL;";
+	debug($query, "database");
+        $query = $conn->prepare($query);
+	$query->bindParam(":distance", $distance);
+	$query->bindParam(":limit", $limit);
+	$query->execute();
+	if (!$query) {
+		databaseError($conn->errorInfo());
+		return Array();
+	}
+	return $query->fetchAll();
+}
+function getStopsBySuburb($suburb)
+{
+	global $conn;
+	$query = "Select * from stops where zone_id LIKE :suburb order by stop_name;";
+	debug($query, "database");
+	$query = $conn->prepare($query);
+        $suburb = "%" . $suburb . ";%";
+	$query->bindParam(":suburb", $suburb);
+	$query->execute();
+	if (!$query) {
+		databaseError($conn->errorInfo());
+		return Array();
+	}
+	return $query->fetchAll();
+}
+function getStopRoutes($stopID, $service_period)
+{
+	if ($service_period == "") $service_period = service_period();
+	global $conn;
+	$query = "SELECT service_id,trips.route_id,route_short_name,route_long_name
+FROM stop_times join trips on trips.trip_id =
+stop_times.trip_id join routes on trips.route_id = routes.route_id WHERE stop_id = :stopID AND service_id=:service_period";
+	debug($query, "database");
+	$query = $conn->prepare($query);
+	$query->bindParam(":service_period", $service_period);
+	$query->bindParam(":stopID", $stopID);
+	$query->execute();
+	if (!$query) {
+		databaseError($conn->errorInfo());
+		return Array();
+	}
+	return $query->fetchAll();
+}
+function getStopTrips($stopID, $service_period = "", $afterTime = "")
+{
+	if ($service_period == "") $service_period = service_period();
+	global $conn;
+	if ($afterTime != "") {
+		$query = " SELECT stop_times.trip_id,stop_times.arrival_time,stop_times.stop_id,stop_sequence,service_id,trips.route_id,route_short_name,route_long_name, end_times.arrival_time as end_time
+FROM stop_times
+join trips on trips.trip_id =
+stop_times.trip_id
+join routes on trips.route_id = routes.route_id , (SELECT trip_id,max(arrival_time) as arrival_time from stop_times
+	WHERE stop_times.arrival_time IS NOT NULL group by trip_id) as end_times 
+WHERE stop_times.stop_id = :stopID
+AND stop_times.trip_id = end_times.trip_id
+AND service_id=:service_period
+AND end_times.arrival_time > :afterTime
+ORDER BY end_time";
+	}
+	else {
+		$query = "SELECT stop_times.trip_id,arrival_time,stop_times.stop_id,stop_sequence,service_id,trips.route_id,route_short_name,route_long_name
+FROM stop_times
+join trips on trips.trip_id =
+stop_times.trip_id
+join routes on trips.route_id = routes.route_id
+WHERE stop_times.stop_id = :stopID
+AND service_id=:service_period
+ORDER BY arrival_time";
+	}
+	debug($query, "database");
+	$query = $conn->prepare($query);
+	$query->bindParam(":service_period", $service_period);
+	$query->bindParam(":stopID", $stopID);
+        if ($afterTime != "") $query->bindParam(":afterTime", $afterTime);
+	$query->execute();
+	if (!$query) {
+		databaseError($conn->errorInfo());
+		return Array();
+	}
+	return $query->fetchAll();
+}
+function getStopTripsWithTimes($stopID, $time = "", $service_period = "", $time_range = "", $limit = "")
+{
+	if ($service_period == "") $service_period = service_period();
+	if ($time_range == "") $time_range = (24 * 60 * 60);
+	if ($time == "") $time = current_time();
+	if ($limit == "") $limit = 10;
+	$trips = getStopTrips($stopID, $service_period, $time);
+	$timedTrips = Array();
+	if ($trips && sizeof($trips) > 0) {
+            foreach ($trips as $trip) {
+		if ($trip['arrival_time'] != "") {
+			if (strtotime($trip['arrival_time']) > strtotime($time) and strtotime($trip['arrival_time']) < (strtotime($time) + $time_range)) {
+				$timedTrips[] = $trip;
+			}
+		}
+		else {
+			$timedTrip = getTimeInterpolatedTripAtStop($trip['trip_id'], $trip['stop_sequence']);
+			if ($timedTrip['arrival_time'] > $time and strtotime($timedTrip['arrival_time']) < (strtotime($time) + $time_range)) {
+				$timedTrips[] = $timedTrip;
+			}
+		}
+		if (sizeof($timedTrips) > $limit) break;
+	}
+	sktimesort($timedTrips, "arrival_time", true);
+        }
+	return $timedTrips;
+}
+?>

--- /dev/null
+++ b/include/db/trip-dao.inc.php
@@ -1,1 +1,234 @@
-
+<?php
+function getTrip($tripID)
+{
+	global $conn;
+	$query = "Select * from trips
+	join routes on trips.route_id = routes.route_id
+	where trip_id =	:tripID
+	LIMIT 1";
+	debug($query, "database");
+	$query = $conn->prepare($query);
+	$query->bindParam(":tripID", $tripID);
+	$query->execute();
+	if (!$query) {
+		databaseError($conn->errorInfo());
+		return Array();
+	}
+	return $query->fetch(PDO::FETCH_ASSOC);
+}
+function getTripShape()
+{
+	/* def handle_json_GET_tripstopTimes(self, params):
+	   schedule = self.server.schedule
+	   try:
+	     trip = schedule.GetTrip(params.get('trip'))
+	   except KeyError:
+	      # if a non-existent trip is searched for, the return nothing
+	     return
+	   time_stops = trip.GetTimeInterpolatedStops()
+	   stops = []
+	   times = []
+	   for arr,ts,is_timingpoint in time_stops:
+	     stops.append(StopToTuple(ts.stop))
+	     times.append(arr)
+	   return [stops, times]
+	
+	 def handle_json_GET_tripshape(self, params):
+	   schedule = self.server.schedule
+	   try:
+	     trip = schedule.GetTrip(params.get('trip'))
+	   except KeyError:
+	      # if a non-existent trip is searched for, the return nothing
+	     return
+	   points = []
+	   if trip.shape_id:
+	     shape = schedule.GetShape(trip.shape_id)
+	     for (lat, lon, dist) in shape.points:
+	       points.append((lat, lon))
+	   else:
+	     time_stops = trip.GetTimeStops()
+	     for arr,dep,stop in time_stops:
+	       points.append((stop.stop_lat, stop.stop_lon))
+	   return points*/
+}
+function getTimeInterpolatedTrip($tripID, $range = "")
+{
+	global $conn;
+	$query = "SELECT stop_times.trip_id,arrival_time,stop_times.stop_id,stop_lat,stop_lon,stop_name,stop_code,
+	stop_sequence,service_id,trips.route_id,route_short_name,route_long_name
+FROM stop_times
+join trips on trips.trip_id = stop_times.trip_id
+join routes on trips.route_id = routes.route_id
+join stops on stops.stop_id = stop_times.stop_id
+WHERE trips.trip_id = :tripID $range ORDER BY stop_sequence";
+	debug($query, "database");
+	$query = $conn->prepare($query);
+	$query->bindParam(":tripID", $tripID);
+	$query->execute();
+	if (!$query) {
+		databaseError($conn->errorInfo());
+		return Array();
+	}
+	$stopTimes = $query->fetchAll();
+	$cur_timepoint = Array();
+	$next_timepoint = Array();
+	$distance_between_timepoints = 0.0;
+	$distance_traveled_between_timepoints = 0.0;
+	$rv = Array();
+	foreach ($stopTimes as $i => $stopTime) {
+		if ($stopTime['arrival_time'] != "") {
+			// is timepoint
+			$cur_timepoint = $stopTime;
+			$distance_between_timepoints = 0.0;
+			$distance_traveled_between_timepoints = 0.0;
+			if ($i + 1 < sizeof($stopTimes)) {
+				$k = $i + 1;
+				$distance_between_timepoints+= distance($stopTimes[$k - 1]["stop_lat"], $stopTimes[$k - 1]["stop_lon"], $stopTimes[$k]["stop_lat"], $stopTimes[$k]["stop_lon"]);
+				while ($stopTimes[$k]["arrival_time"] == "" && $k + 1 < sizeof($stopTimes)) {
+					$k+= 1;
+					//echo "k".$k;
+					$distance_between_timepoints+= distance($stopTimes[$k - 1]["stop_lat"], $stopTimes[$k - 1]["stop_lon"], $stopTimes[$k]["stop_lat"], $stopTimes[$k]["stop_lon"]);
+				}
+				$next_timepoint = $stopTimes[$k];
+				$rv[] = $stopTime;
+			}
+		}
+		else {
+			// is untimed point
+			//echo "i".$i;
+			$distance_traveled_between_timepoints+= distance($stopTimes[$i - 1]["stop_lat"], $stopTimes[$i - 1]["stop_lon"], $stopTimes[$i]["stop_lat"], $stopTimes[$i]["stop_lon"]);
+			//echo "$distance_traveled_between_timepoints / $distance_between_timepoints<br>";
+			$distance_percent = $distance_traveled_between_timepoints / $distance_between_timepoints;
+			if ($next_timepoint["arrival_time"] != "") {
+				$total_time = strtotime($next_timepoint["arrival_time"]) - strtotime($cur_timepoint["arrival_time"]);
+				//echo strtotime($next_timepoint["arrival_time"])." - ".strtotime($cur_timepoint["arrival_time"])."<br>";
+				$time_estimate = ($distance_percent * $total_time) + strtotime($cur_timepoint["arrival_time"]);
+				$stopTime["arrival_time"] = date("H:i:s", $time_estimate);
+			}
+			else {
+				$stopTime["arrival_time"] = $cur_timepoint["arrival_time"];
+			}
+			$rv[] = $stopTime;
+			//var_dump($rv);
+			
+		}
+	}
+	return $rv;
+}
+function getTripPreviousTimePoint($tripID, $stop_sequence)
+{
+	global $conn;
+	$query = " SELECT trip_id,stop_id,
+	stop_sequence
+FROM stop_times
+WHERE trip_id = :tripID and stop_sequence < :stop_sequence
+and stop_times.arrival_time IS NOT NULL ORDER BY stop_sequence DESC LIMIT 1";
+	debug($query, "database");
+	$query = $conn->prepare($query);
+	$query->bindParam(":tripID", $tripID);
+	$query->bindParam(":stop_sequence", $stop_sequence);
+	$query->execute();
+	if (!$query) {
+		databaseError($conn->errorInfo());
+		return Array();
+	}
+	return $query->fetch(PDO::FETCH_ASSOC);
+}
+function getTripNextTimePoint($tripID, $stop_sequence)
+{
+	global $conn;
+	$query = " SELECT trip_id,stop_id,
+	stop_sequence
+FROM stop_times
+WHERE trip_id = :tripID and stop_sequence > :stop_sequence
+and stop_times.arrival_time IS NOT NULL ORDER BY stop_sequence LIMIT 1";
+	debug($query, "database");
+	$query = $conn->prepare($query);
+	$query->bindParam(":tripID", $tripID);
+	$query->bindParam(":stop_sequence", $stop_sequence);
+	$query->execute();
+	if (!$query) {
+		databaseError($conn->errorInfo());
+		return Array();
+	}
+	return $query->fetch(PDO::FETCH_ASSOC);
+}
+function getTimeInterpolatedTripAtStop($tripID, $stop_sequence)
+{
+	global $conn;
+	// limit interpolation to between nearest actual points.
+	$prevTimePoint = getTripPreviousTimePoint($tripID, $stop_sequence);
+	$nextTimePoint = getTripNextTimePoint($tripID, $stop_sequence);
+	$range = "AND stop_sequence >= '{$prevTimePoint['stop_sequence']}' AND stop_sequence <= '{$nextTimePoint['stop_sequence']}'";
+	foreach (getTimeInterpolatedTrip($tripID, $range) as $tripStop) {
+		if ($tripStop['stop_sequence'] == $stop_sequence) return $tripStop;
+	}
+	return Array();
+}
+function getTripStartTime($tripID)
+{
+	global $conn;
+	$query = "Select * from stop_times
+	where trip_id = :tripID
+	AND arrival_time IS NOT NULL
+	AND stop_sequence = '1'";
+	debug($query, "database");
+	$query = $conn->prepare($query);
+	$query->bindParam(":tripID", $tripID);
+	$query->execute();
+	if (!$query) {
+		databaseError($conn->errorInfo());
+		return Array();
+	}
+	$r = $query->fetch(PDO::FETCH_ASSOC);
+	return $r['arrival_time'];
+}
+function getActiveTrips($time)
+{
+	global $conn;
+	if ($time == "") $time = current_time();
+	$query = "Select distinct stop_times.trip_id, start_times.arrival_time as start_time, end_times.arrival_time as end_time from stop_times, (SELECT trip_id,arrival_time from stop_times WHERE stop_times.arrival_time IS NOT NULL
+AND stop_sequence = '1') as start_times, (SELECT trip_id,max(arrival_time) as arrival_time from stop_times WHERE stop_times.arrival_time IS NOT NULL group by trip_id) as end_times
+WHERE start_times.trip_id = end_times.trip_id AND stop_times.trip_id = end_times.trip_id AND :time > start_times.arrival_time  AND :time < end_times.arrival_time";
+	debug($query, "database");
+	$query = $conn->prepare($query);
+	$query->bindParam(":time", $time);
+	$query->execute();
+	if (!$query) {
+		databaseError($conn->errorInfo());
+		return Array();
+	}
+	return $query->fetchAll();
+}
+function viaPoints($tripID, $stop_sequence = "")
+{
+	global $conn;
+	$query = "SELECT stops.stop_id, stop_name, arrival_time
+FROM stop_times join stops on stops.stop_id = stop_times.stop_id
+WHERE stop_times.trip_id = :tripID
+" . ($stop_sequence != "" ? " AND stop_sequence > :stop_sequence " : "") . "AND substr(stop_code,1,2) != 'Wj' ORDER BY stop_sequence";
+	debug($query, "database");
+	$query = $conn->prepare($query);
+	if ($stop_sequence != "") $query->bindParam(":stop_sequence", $stop_sequence);
+	$query->bindParam(":tripID", $tripID);
+	$query->execute();
+	if (!$query) {
+		databaseError($conn->errorInfo());
+		return Array();
+	}
+	return $query->fetchAll();
+}
+function viaPointNames($tripid, $stop_sequence = "")
+{
+	$viaPointNames = Array();
+	foreach (viaPoints($tripid, $stop_sequence) as $point) {
+		$viaPointNames[] = $point['stop_name'];
+	}
+	if (sizeof($viaPointNames) > 0) {
+		return r_implode(", ", $viaPointNames);
+	}
+	else {
+		return "";
+	}
+}
+?>

file:a/index.php -> file:b/index.php
--- a/index.php
+++ b/index.php
@@ -13,18 +13,19 @@
                 <li data-role="list-divider">Timetables - Stops</li>
                 <li><a href="stopList.php">Major (Timing Point) Stops</a></li>
 		<li><a href="stopList.php?allstops=yes">All Stops</a></li>
-		<li><a href="stopList.php?suburbs=yes">Stops By Suburb</a></li>
+		<li><a href="stopList.php?bysuburbs=yes">Stops By Suburb</a></li>
 		<li><a class="nearby" href="stopList.php?nearby=yes">Nearby Stops</a></li>
             </ul>
 	    <ul data-role="listview" data-inset="true" data-theme="c" data-dividertheme="b">
                 <li data-role="list-divider">Timetables - Routes</li>
                 <li><a href="routeList.php">Routes By Final Destination</a></li>
 		<li><a href="routeList.php?bynumber=yes">Routes By Number</a></li>
-		<li><a href="routeList.php?bysuburb=yes">Routes By Suburb</a></li>
+		<li><a href="routeList.php?bysuburbs=yes">Routes By Suburb</a></li>
 		<li><a class="nearby" href="routeList.php?nearby=yes">Nearby Routes</a></li>
             </ul>
 <?php
 echo timePlaceSettings();
+echo ' <a href="labs/index.php" data-role="button" data-icon="beaker">Busness R&amp;D</a>';
 include_footer(true)
 ?>
         

--- /dev/null
+++ b/js/flotr/flotr-0.2.0-alpha.js
@@ -1,1 +1,2 @@
-
+//Flotr 0.2.0-alpha Copyright (c) 2009 Bas Wenneker, <http://solutoire.com>, MIT License.
+var Flotr={version:"0.2.0-alpha",author:"Bas Wenneker",website:"http://www.solutoire.com",_registeredTypes:{lines:"drawSeriesLines",points:"drawSeriesPoints",bars:"drawSeriesBars",candles:"drawSeriesCandles",pie:"drawSeriesPie"},register:function(A,B){Flotr._registeredTypes[A]=B+""},draw:function(B,D,A,C){C=C||Flotr.Graph;return new C(B,D,A)},getSeries:function(A){return A.collect(function(C){var B,C=(C.data)?Object.clone(C):{data:C};for(B=C.data.length-1;B>-1;--B){C.data[B][1]=(C.data[B][1]===null?null:parseFloat(C.data[B][1]))}return C})},merge:function(D,B){var A=B||{};for(var C in D){A[C]=(D[C]!=null&&typeof (D[C])=="object"&&!(D[C].constructor==Array||D[C].constructor==RegExp)&&!Object.isElement(D[C]))?Flotr.merge(D[C],B[C]):A[C]=D[C]}return A},getTickSize:function(E,D,A,B){var H=(A-D)/E;var G=Flotr.getMagnitude(H);var C=H/G;var F=10;if(C<1.5){F=1}else{if(C<2.25){F=2}else{if(C<3){F=((B==0)?2:2.5)}else{if(C<7.5){F=5}}}}return F*G},defaultTickFormatter:function(A){return A+""},defaultTrackFormatter:function(A){return"("+A.x+", "+A.y+")"},defaultPieLabelFormatter:function(A){return(A.fraction*100).toFixed(2)+"%"},getMagnitude:function(A){return Math.pow(10,Math.floor(Math.log(A)/Math.LN10))},toPixel:function(A){return Math.floor(A)+0.5},toRad:function(A){return -A*(Math.PI/180)},parseColor:function(D){if(D instanceof Flotr.Color){return D}var A,C=Flotr.Color;if((A=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(D))){return new C(parseInt(A[1]),parseInt(A[2]),parseInt(A[3]))}if((A=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(D))){return new C(parseInt(A[1]),parseInt(A[2]),parseInt(A[3]),parseFloat(A[4]))}if((A=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(D))){return new C(parseFloat(A[1])*2.55,parseFloat(A[2])*2.55,parseFloat(A[3])*2.55)}if((A=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(D))){return new C(parseFloat(A[1])*2.55,parseFloat(A[2])*2.55,parseFloat(A[3])*2.55,parseFloat(A[4]))}if((A=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(D))){return new C(parseInt(A[1],16),parseInt(A[2],16),parseInt(A[3],16))}if((A=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(D))){return new C(parseInt(A[1]+A[1],16),parseInt(A[2]+A[2],16),parseInt(A[3]+A[3],16))}var B=D.strip().toLowerCase();if(B=="transparent"){return new C(255,255,255,0)}return((A=C.lookupColors[B]))?new C(A[0],A[1],A[2]):false},extractColor:function(B){var A;do{A=B.getStyle("background-color").toLowerCase();if(!(A==""||A=="transparent")){break}B=B.up(0)}while(!B.nodeName.match(/^body$/i));return(A=="rgba(0, 0, 0, 0)")?"transparent":A}};Flotr.Graph=Class.create({initialize:function(B,C,A){this.el=$(B);if(!this.el){throw"The target container doesn't exist"}this.data=C;this.series=Flotr.getSeries(C);this.setOptions(A);this.lastMousePos={pageX:null,pageY:null};this.selection={first:{x:-1,y:-1},second:{x:-1,y:-1}};this.prevSelection=null;this.selectionInterval=null;this.ignoreClick=false;this.prevHit=null;this.constructCanvas();this.initEvents();this.findDataRanges();this.calculateTicks(this.axes.x);this.calculateTicks(this.axes.x2);this.calculateTicks(this.axes.y);this.calculateTicks(this.axes.y2);this.calculateSpacing();this.draw();this.insertLegend();if(this.options.spreadsheet.show){this.constructTabs()}},setOptions:function(B){var P={colors:["#00A8F0","#C0D800","#CB4B4B","#4DA74D","#9440ED"],title:null,subtitle:null,legend:{show:true,noColumns:1,labelFormatter:Prototype.K,labelBoxBorderColor:"#CCCCCC",labelBoxWidth:14,labelBoxHeight:10,labelBoxMargin:5,container:null,position:"nw",margin:5,backgroundColor:null,backgroundOpacity:0.85},xaxis:{ticks:null,showLabels:true,labelsAngle:0,title:null,titleAngle:0,noTicks:5,tickFormatter:Flotr.defaultTickFormatter,tickDecimals:null,min:null,max:null,autoscaleMargin:0,color:null},x2axis:{},yaxis:{ticks:null,showLabels:true,labelsAngle:0,title:null,titleAngle:90,noTicks:5,tickFormatter:Flotr.defaultTickFormatter,tickDecimals:null,min:null,max:null,autoscaleMargin:0,color:null},y2axis:{titleAngle:270},points:{show:false,radius:3,lineWidth:2,fill:true,fillColor:"#FFFFFF",fillOpacity:0.4},lines:{show:false,lineWidth:2,fill:false,fillColor:null,fillOpacity:0.4},bars:{show:false,lineWidth:2,barWidth:1,fill:true,fillColor:null,fillOpacity:0.4,horizontal:false,stacked:false},candles:{show:false,lineWidth:1,wickLineWidth:1,candleWidth:0.6,fill:true,upFillColor:"#00A8F0",downFillColor:"#CB4B4B",fillOpacity:0.5,barcharts:false},pie:{show:false,lineWidth:1,fill:true,fillColor:null,fillOpacity:0.6,explode:6,sizeRatio:0.6,startAngle:Math.PI/4,labelFormatter:Flotr.defaultPieLabelFormatter,pie3D:false,pie3DviewAngle:(Math.PI/2*0.8),pie3DspliceThickness:20},grid:{color:"#545454",backgroundColor:null,tickColor:"#DDDDDD",labelMargin:3,verticalLines:true,horizontalLines:true,outlineWidth:2},selection:{mode:null,color:"#B6D9FF",fps:20},mouse:{track:false,position:"se",relative:false,trackFormatter:Flotr.defaultTrackFormatter,margin:5,lineColor:"#FF3F19",trackDecimals:1,sensibility:2,radius:3},shadowSize:4,defaultType:"lines",HtmlText:true,fontSize:7.5,spreadsheet:{show:false,tabGraphLabel:"Graph",tabDataLabel:"Data",toolbarDownload:"Download CSV",toolbarSelectAll:"Select all"}};P.x2axis=Object.extend(Object.clone(P.xaxis),P.x2axis);P.y2axis=Object.extend(Object.clone(P.yaxis),P.y2axis);this.options=Flotr.merge((B||{}),P);this.axes={x:{options:this.options.xaxis,n:1},x2:{options:this.options.x2axis,n:2},y:{options:this.options.yaxis,n:1},y2:{options:this.options.y2axis,n:2}};var H=[],C=[],K=this.series.length,N=this.series.length,D=this.options.colors,A=[],G=0,M,J,I,O,E;for(J=N-1;J>-1;--J){M=this.series[J].color;if(M!=null){--N;if(Object.isNumber(M)){H.push(M)}else{A.push(Flotr.parseColor(M))}}}for(J=H.length-1;J>-1;--J){N=Math.max(N,H[J]+1)}for(J=0;C.length<N;){M=(D.length==J)?new Flotr.Color(100,100,100):Flotr.parseColor(D[J]);var F=G%2==1?-1:1;var L=1+F*Math.ceil(G/2)*0.2;M.scale(L,L,L);C.push(M);if(++J>=D.length){J=0;++G}}for(J=0,I=0;J<K;++J){O=this.series[J];if(O.color==null){O.color=C[I++].toString()}else{if(Object.isNumber(O.color)){O.color=C[O.color].toString()}}if(!O.xaxis){O.xaxis=this.axes.x}if(O.xaxis==1){O.xaxis=this.axes.x}else{if(O.xaxis==2){O.xaxis=this.axes.x2}}if(!O.yaxis){O.yaxis=this.axes.y}if(O.yaxis==1){O.yaxis=this.axes.y}else{if(O.yaxis==2){O.yaxis=this.axes.y2}}O.lines=Object.extend(Object.clone(this.options.lines),O.lines);O.points=Object.extend(Object.clone(this.options.points),O.points);O.bars=Object.extend(Object.clone(this.options.bars),O.bars);O.candles=Object.extend(Object.clone(this.options.candles),O.candles);O.pie=Object.extend(Object.clone(this.options.pie),O.pie);O.mouse=Object.extend(Object.clone(this.options.mouse),O.mouse);if(O.shadowSize==null){O.shadowSize=this.options.shadowSize}}},constructCanvas:function(){var C=this.el,B,D,A;this.canvas=C.select(".flotr-canvas")[0];this.overlay=C.select(".flotr-overlay")[0];C.childElements().invoke("remove");C.setStyle({position:"relative",cursor:"default"});this.canvasWidth=C.getWidth();this.canvasHeight=C.getHeight();B={width:this.canvasWidth,height:this.canvasHeight};if(this.canvasWidth<=0||this.canvasHeight<=0){throw"Invalid dimensions for plot, width = "+this.canvasWidth+", height = "+this.canvasHeight}if(!this.canvas){D=this.canvas=new Element("canvas",B);D.className="flotr-canvas";D=D.writeAttribute("style","position:absolute;left:0px;top:0px;")}else{D=this.canvas.writeAttribute(B)}C.insert(D);if(Prototype.Browser.IE){D=window.G_vmlCanvasManager.initElement(D)}this.ctx=D.getContext("2d");if(!this.overlay){A=this.overlay=new Element("canvas",B);A.className="flotr-overlay";A=A.writeAttribute("style","position:absolute;left:0px;top:0px;")}else{A=this.overlay.writeAttribute(B)}C.insert(A);if(Prototype.Browser.IE){A=window.G_vmlCanvasManager.initElement(A)}this.octx=A.getContext("2d");if(window.CanvasText){CanvasText.enable(this.ctx);CanvasText.enable(this.octx);this.textEnabled=true}},getTextDimensions:function(F,C,B,D){if(!F){return{width:0,height:0}}if(!this.options.HtmlText&&this.textEnabled){var E=this.ctx.getTextBounds(F,C);return{width:E.width+2,height:E.height+6}}else{var A=this.el.insert('<div style="position:absolute;top:-10000px;'+B+'" class="'+D+' flotr-dummy-div">'+F+"</div>").select(".flotr-dummy-div")[0];dim=A.getDimensions();A.remove();return dim}},loadDataGrid:function(){if(this.seriesData){return this.seriesData}var A=this.series;var B=[];for(i=0;i<A.length;++i){A[i].data.each(function(D){var C=D[0],F=D[1];if(r=B.find(function(G){return G[0]==C})){r[i+1]=F}else{var E=[];E[0]=C;E[i+1]=F;B.push(E)}})}B=B.sortBy(function(C){return C[0]});return this.seriesData=B},showTab:function(B,C){var A="canvas, .flotr-labels, .flotr-legend, .flotr-legend-bg, .flotr-title, .flotr-subtitle";switch(B){case"graph":this.datagrid.up().hide();this.el.select(A).invoke("show");this.tabs.data.removeClassName("selected");this.tabs.graph.addClassName("selected");break;case"data":this.constructDataGrid();this.datagrid.up().show();this.el.select(A).invoke("hide");this.tabs.data.addClassName("selected");this.tabs.graph.removeClassName("selected");break}},constructTabs:function(){var A=new Element("div",{className:"flotr-tabs-group",style:"position:absolute;left:0px;top:"+this.canvasHeight+"px;width:"+this.canvasWidth+"px;"});this.el.insert({bottom:A});this.tabs={graph:new Element("div",{className:"flotr-tab selected",style:"float:left;"}).update(this.options.spreadsheet.tabGraphLabel),data:new Element("div",{className:"flotr-tab",style:"float:left;"}).update(this.options.spreadsheet.tabDataLabel)};A.insert(this.tabs.graph).insert(this.tabs.data);this.el.setStyle({height:this.canvasHeight+this.tabs.data.getHeight()+2+"px"});this.tabs.graph.observe("click",(function(){this.showTab("graph")}).bind(this));this.tabs.data.observe("click",(function(){this.showTab("data")}).bind(this))},constructDataGrid:function(){if(this.datagrid){return this.datagrid}var D,B,L=this.series,J=this.loadDataGrid();var K=this.datagrid=new Element("table",{className:"flotr-datagrid",style:"height:100px;"});var C=["<colgroup><col />"];var F=['<tr class="first-row">'];F.push("<th>&nbsp;</th>");for(D=0;D<L.length;++D){F.push('<th scope="col">'+(L[D].label||String.fromCharCode(65+D))+"</th>");C.push("<col />")}F.push("</tr>");for(B=0;B<J.length;++B){F.push("<tr>");for(D=0;D<L.length+1;++D){var M="td";var G=(J[B][D]!=null?Math.round(J[B][D]*100000)/100000:"");if(D==0){M="th";var I;if(this.options.xaxis.ticks){var E=this.options.xaxis.ticks.find(function(N){return N[0]==J[B][D]});if(E){I=E[1]}}else{I=this.options.xaxis.tickFormatter(G)}if(I){G=I}}F.push("<"+M+(M=="th"?' scope="row"':"")+">"+G+"</"+M+">")}F.push("</tr>")}C.push("</colgroup>");K.update(C.join("")+F.join(""));if(!Prototype.Browser.IE){K.select("td").each(function(N){N.observe("mouseover",function(O){N=O.element();var P=N.previousSiblings();K.select("th[scope=col]")[P.length-1].addClassName("hover");K.select("colgroup col")[P.length].addClassName("hover")});N.observe("mouseout",function(){K.select("colgroup col.hover, th.hover").each(function(O){O.removeClassName("hover")})})})}var H=new Element("div",{className:"flotr-datagrid-toolbar"}).insert(new Element("button",{type:"button",className:"flotr-datagrid-toolbar-button"}).update(this.options.spreadsheet.toolbarDownload).observe("click",this.downloadCSV.bind(this))).insert(new Element("button",{type:"button",className:"flotr-datagrid-toolbar-button"}).update(this.options.spreadsheet.toolbarSelectAll).observe("click",this.selectAllData.bind(this)));var A=new Element("div",{className:"flotr-datagrid-container",style:"left:0px;top:0px;width:"+this.canvasWidth+"px;height:"+this.canvasHeight+"px;overflow:auto;"});A.insert(H);K.wrap(A.hide());this.el.insert(A);return K},selectAllData:function(){if(this.tabs){var B,A,E,D,C=this.constructDataGrid();this.showTab("data");(function(){if((E=C.ownerDocument)&&(D=E.defaultView)&&D.getSelection&&E.createRange&&(B=window.getSelection())&&B.removeAllRanges){A=E.createRange();A.selectNode(C);B.removeAllRanges();B.addRange(A)}else{if(document.body&&document.body.createTextRange&&(A=document.body.createTextRange())){A.moveToElementText(C);A.select()}}}).defer();return true}else{return false}},downloadCSV:function(){var D,A='"x"',C=this.series,E=this.loadDataGrid();for(D=0;D<C.length;++D){A+='%09"'+(C[D].label||String.fromCharCode(65+D))+'"'}A+="%0D%0A";for(D=0;D<E.length;++D){if(this.options.xaxis.ticks){var B=this.options.xaxis.ticks.find(function(F){return F[0]==E[D][0]});if(B){E[D][0]=B[1]}}else{E[D][0]=this.options.xaxis.tickFormatter(E[D][0])}A+=E[D].join("%09")+"%0D%0A"}if(Prototype.Browser.IE){A=A.gsub("%09","\t").gsub("%0A","\n").gsub("%0D","\r");window.open().document.write(A)}else{window.open("data:text/csv,"+A)}},initEvents:function(){this.overlay.stopObserving();this.overlay.observe("mousedown",this.mouseDownHandler.bind(this));this.overlay.observe("mousemove",this.mouseMoveHandler.bind(this));this.overlay.observe("click",this.clickHandler.bind(this))},findDataRanges:function(){var J=this.series,G=this.axes;G.x.datamin=0;G.x.datamax=0;G.x2.datamin=0;G.x2.datamax=0;G.y.datamin=0;G.y.datamax=0;G.y2.datamin=0;G.y2.datamax=0;if(J.length>0){var C,A,D,H,F,B,I,E;for(C=0;C<J.length;++C){B=J[C].data,I=J[C].xaxis,E=J[C].yaxis;if(B.length>0&&!J[C].hide){if(!I.used){I.datamin=I.datamax=B[0][0]}if(!E.used){E.datamin=E.datamax=B[0][1]}I.used=true;E.used=true;for(D=B.length-1;D>-1;--D){H=B[D][0];if(H<I.datamin){I.datamin=H}else{if(H>I.datamax){I.datamax=H}}for(A=1;A<B[D].length;A++){F=B[D][A];if(F<E.datamin){E.datamin=F}else{if(F>E.datamax){E.datamax=F}}}}}}}this.findXAxesValues();this.calculateRange(G.x);this.extendXRangeIfNeededByBar(G.x);if(G.x2.used){this.calculateRange(G.x2);this.extendXRangeIfNeededByBar(G.x2)}this.calculateRange(G.y);this.extendYRangeIfNeededByBar(G.y);if(G.y2.used){this.calculateRange(G.y2);this.extendYRangeIfNeededByBar(G.y2)}},calculateRange:function(D){var F=D.options,C=F.min!=null?F.min:D.datamin,A=F.max!=null?F.max:D.datamax,E;if(A-C==0){var B=(A==0)?1:0.01;C-=B;A+=B}D.tickSize=Flotr.getTickSize(F.noTicks,C,A,F.tickDecimals);if(F.min==null){E=F.autoscaleMargin;if(E!=0){C-=D.tickSize*E;if(C<0&&D.datamin>=0){C=0}C=D.tickSize*Math.floor(C/D.tickSize)}}if(F.max==null){E=F.autoscaleMargin;if(E!=0){A+=D.tickSize*E;if(A>0&&D.datamax<=0){A=0}A=D.tickSize*Math.ceil(A/D.tickSize)}}D.min=C;D.max=A},extendXRangeIfNeededByBar:function(A){if(A.options.max==null){var D=A.max,B,I,F,E,H=[],C=null;for(B=0;B<this.series.length;++B){I=this.series[B];F=I.bars;E=I.candles;if(I.axis==A&&(F.show||E.show)){if(!F.horizontal&&(F.barWidth+A.datamax>D)||(E.candleWidth+A.datamax>D)){D=A.max+I.bars.barWidth}if(F.stacked&&F.horizontal){for(j=0;j<I.data.length;j++){if(I.bars.show&&I.bars.stacked){var G=I.data[j][0];H[G]=(H[G]||0)+I.data[j][1];C=I}}for(j=0;j<H.length;j++){D=Math.max(H[j],D)}}}}A.lastSerie=C;A.max=D}},extendYRangeIfNeededByBar:function(A){if(A.options.max==null){var D=A.max,B,I,F,E,H=[],C=null;for(B=0;B<this.series.length;++B){I=this.series[B];F=I.bars;E=I.candles;if(I.yaxis==A&&F.show&&!I.hide){if(F.horizontal&&(F.barWidth+A.datamax>D)||(E.candleWidth+A.datamax>D)){D=A.max+F.barWidth}if(F.stacked&&!F.horizontal){for(j=0;j<I.data.length;j++){if(I.bars.show&&I.bars.stacked){var G=I.data[j][0];H[G]=(H[G]||0)+I.data[j][1];C=I}}for(j=0;j<H.length;j++){D=Math.max(H[j],D)}}}}A.lastSerie=C;A.max=D}},findXAxesValues:function(){for(i=this.series.length-1;i>-1;--i){s=this.series[i];s.xaxis.values=s.xaxis.values||[];for(j=s.data.length-1;j>-1;--j){s.xaxis.values[s.data[j][0]]={}}}},calculateTicks:function(D){var B=D.options,E,H;D.ticks=[];if(B.ticks){var G=B.ticks,I,F;if(Object.isFunction(G)){G=G({min:D.min,max:D.max})}for(E=0;E<G.length;++E){I=G[E];if(typeof (I)=="object"){H=I[0];F=(I.length>1)?I[1]:B.tickFormatter(H)}else{H=I;F=B.tickFormatter(H)}D.ticks[E]={v:H,label:F}}}else{var A=D.tickSize*Math.ceil(D.min/D.tickSize),C;for(E=0;A+E*D.tickSize<=D.max;++E){H=A+E*D.tickSize;C=B.tickDecimals;if(C==null){C=1-Math.floor(Math.log(D.tickSize)/Math.LN10)}if(C<0){C=0}H=H.toFixed(C);D.ticks.push({v:H,label:B.tickFormatter(H)})}}},calculateSpacing:function(){var L=this.axes,N=this.options,H=this.series,D=N.grid.labelMargin,M=L.x,A=L.x2,J=L.y,K=L.y2,F=2,G,E,C,I;[M,A,J,K].each(function(P){var O="";if(P.options.showLabels){for(G=0;G<P.ticks.length;++G){C=P.ticks[G].label.length;if(C>O.length){O=P.ticks[G].label}}}P.maxLabel=this.getTextDimensions(O,{size:N.fontSize,angle:Flotr.toRad(P.options.labelsAngle)},"font-size:smaller;","flotr-grid-label");P.titleSize=this.getTextDimensions(P.options.title,{size:N.fontSize*1.2,angle:Flotr.toRad(P.options.titleAngle)},"font-weight:bold;","flotr-axis-title")},this);I=this.getTextDimensions(N.title,{size:N.fontSize*1.5},"font-size:1em;font-weight:bold;","flotr-title");this.titleHeight=I.height;I=this.getTextDimensions(N.subtitle,{size:N.fontSize},"font-size:smaller;","flotr-subtitle");this.subtitleHeight=I.height;if(N.show){F=Math.max(F,N.points.radius+N.points.lineWidth/2)}for(E=0;E<N.length;++E){if(H[E].points.show){F=Math.max(F,H[E].points.radius+H[E].points.lineWidth/2)}}var B=this.plotOffset={left:0,right:0,top:0,bottom:0};B.left=B.right=B.top=B.bottom=F;B.bottom+=(M.options.showLabels?(M.maxLabel.height+D):0)+(M.options.title?(M.titleSize.height+D):0);B.top+=(A.options.showLabels?(A.maxLabel.height+D):0)+(A.options.title?(A.titleSize.height+D):0)+this.subtitleHeight+this.titleHeight;B.left+=(J.options.showLabels?(J.maxLabel.width+D):0)+(J.options.title?(J.titleSize.width+D):0);B.right+=(K.options.showLabels?(K.maxLabel.width+D):0)+(K.options.title?(K.titleSize.width+D):0);B.top=Math.floor(B.top);this.plotWidth=this.canvasWidth-B.left-B.right;this.plotHeight=this.canvasHeight-B.bottom-B.top;M.scale=this.plotWidth/(M.max-M.min);A.scale=this.plotWidth/(A.max-A.min);J.scale=this.plotHeight/(J.max-J.min);K.scale=this.plotHeight/(K.max-K.min)},draw:function(){this.drawGrid();this.drawLabels();this.drawTitles();if(this.series.length){this.el.fire("flotr:beforedraw",[this.series,this]);for(var A=0;A<this.series.length;A++){if(!this.series[A].hide){this.drawSeries(this.series[A])}}}this.el.fire("flotr:afterdraw",[this.series,this])},tHoz:function(A,B){B=B||this.axes.x;return(A-B.min)*B.scale},tVert:function(B,A){A=A||this.axes.y;return this.plotHeight-(B-A.min)*A.scale},drawGrid:function(){var B,E=this.options,A=this.ctx;if(E.grid.verticalLines||E.grid.horizontalLines){this.el.fire("flotr:beforegrid",[this.axes.x,this.axes.y,E,this])}A.save();A.translate(this.plotOffset.left,this.plotOffset.top);if(E.grid.backgroundColor!=null){A.fillStyle=E.grid.backgroundColor;A.fillRect(0,0,this.plotWidth,this.plotHeight)}A.lineWidth=1;A.strokeStyle=E.grid.tickColor;A.beginPath();if(E.grid.verticalLines){for(var D=0;D<this.axes.x.ticks.length;++D){B=this.axes.x.ticks[D].v;if((B==this.axes.x.min||B==this.axes.x.max)&&E.grid.outlineWidth!=0){continue}A.moveTo(Math.floor(this.tHoz(B))+A.lineWidth/2,0);A.lineTo(Math.floor(this.tHoz(B))+A.lineWidth/2,this.plotHeight)}}if(E.grid.horizontalLines){for(var C=0;C<this.axes.y.ticks.length;++C){B=this.axes.y.ticks[C].v;if((B==this.axes.y.min||B==this.axes.y.max)&&E.grid.outlineWidth!=0){continue}A.moveTo(0,Math.floor(this.tVert(B))+A.lineWidth/2);A.lineTo(this.plotWidth,Math.floor(this.tVert(B))+A.lineWidth/2)}}A.stroke();if(E.grid.outlineWidth!=0){A.lineWidth=E.grid.outlineWidth;A.strokeStyle=E.grid.color;A.lineJoin="round";A.strokeRect(0,0,this.plotWidth,this.plotHeight)}A.restore();if(E.grid.verticalLines||E.grid.horizontalLines){this.el.fire("flotr:aftergrid",[this.axes.x,this.axes.y,E,this])}},drawLabels:function(){var C=0,D,B,E,F,G,J=this.options,I=this.ctx,H=this.axes;for(E=0;E<H.x.ticks.length;++E){if(H.x.ticks[E].label){++C}}B=this.plotWidth/C;if(!J.HtmlText&&this.textEnabled){var A={size:J.fontSize,adjustAlign:true};D=H.x;A.color=D.options.color||J.grid.color;for(E=0;E<D.ticks.length&&D.options.showLabels&&D.used;++E){G=D.ticks[E];if(!G.label||G.label.length==0){continue}A.angle=Flotr.toRad(D.options.labelsAngle);A.halign="c";A.valign="t";I.drawText(G.label,this.plotOffset.left+this.tHoz(G.v,D),this.plotOffset.top+this.plotHeight+J.grid.labelMargin,A)}D=H.x2;A.color=D.options.color||J.grid.color;for(E=0;E<D.ticks.length&&D.options.showLabels&&D.used;++E){G=D.ticks[E];if(!G.label||G.label.length==0){continue}A.angle=Flotr.toRad(D.options.labelsAngle);A.halign="c";A.valign="b";I.drawText(G.label,this.plotOffset.left+this.tHoz(G.v,D),this.plotOffset.top+J.grid.labelMargin,A)}D=H.y;A.color=D.options.color||J.grid.color;for(E=0;E<D.ticks.length&&D.options.showLabels&&D.used;++E){G=D.ticks[E];if(!G.label||G.label.length==0){continue}A.angle=Flotr.toRad(D.options.labelsAngle);A.halign="r";A.valign="m";I.drawText(G.label,this.plotOffset.left-J.grid.labelMargin,this.plotOffset.top+this.tVert(G.v,D),A)}D=H.y2;A.color=D.options.color||J.grid.color;for(E=0;E<D.ticks.length&&D.options.showLabels&&D.used;++E){G=D.ticks[E];if(!G.label||G.label.length==0){continue}A.angle=Flotr.toRad(D.options.labelsAngle);A.halign="l";A.valign="m";I.drawText(G.label,this.plotOffset.left+this.plotWidth+J.grid.labelMargin,this.plotOffset.top+this.tVert(G.v,D),A);I.save();I.strokeStyle=A.color;I.beginPath();I.moveTo(this.plotOffset.left+this.plotWidth-8,this.plotOffset.top+this.tVert(G.v,D));I.lineTo(this.plotOffset.left+this.plotWidth,this.plotOffset.top+this.tVert(G.v,D));I.stroke();I.restore()}}else{if(H.x.options.showLabels||H.x2.options.showLabels||H.y.options.showLabels||H.y2.options.showLabels){F=['<div style="font-size:smaller;color:'+J.grid.color+';" class="flotr-labels">'];D=H.x;if(D.options.showLabels){for(E=0;E<D.ticks.length;++E){G=D.ticks[E];if(!G.label||G.label.length==0){continue}F.push('<div style="position:absolute;top:'+(this.plotOffset.top+this.plotHeight+J.grid.labelMargin)+"px;left:"+(this.plotOffset.left+this.tHoz(G.v,D)-B/2)+"px;width:"+B+"px;text-align:center;"+(D.options.color?("color:"+D.options.color+";"):"")+'" class="flotr-grid-label">'+G.label+"</div>")}}D=H.x2;if(D.options.showLabels&&D.used){for(E=0;E<D.ticks.length;++E){G=D.ticks[E];if(!G.label||G.label.length==0){continue}F.push('<div style="position:absolute;top:'+(this.plotOffset.top-J.grid.labelMargin-D.maxLabel.height)+"px;left:"+(this.plotOffset.left+this.tHoz(G.v,D)-B/2)+"px;width:"+B+"px;text-align:center;"+(D.options.color?("color:"+D.options.color+";"):"")+'" class="flotr-grid-label">'+G.label+"</div>")}}D=H.y;if(D.options.showLabels){for(E=0;E<D.ticks.length;++E){G=D.ticks[E];if(!G.label||G.label.length==0){continue}F.push('<div style="position:absolute;top:'+(this.plotOffset.top+this.tVert(G.v,D)-D.maxLabel.height/2)+"px;left:0;width:"+(this.plotOffset.left-J.grid.labelMargin)+"px;text-align:right;"+(D.options.color?("color:"+D.options.color+";"):"")+'" class="flotr-grid-label">'+G.label+"</div>")}}D=H.y2;if(D.options.showLabels&&D.used){I.save();I.strokeStyle=D.options.color||J.grid.color;I.beginPath();for(E=0;E<D.ticks.length;++E){G=D.ticks[E];if(!G.label||G.label.length==0){continue}F.push('<div style="position:absolute;top:'+(this.plotOffset.top+this.tVert(G.v,D)-D.maxLabel.height/2)+"px;right:0;width:"+(this.plotOffset.right-J.grid.labelMargin)+"px;text-align:left;"+(D.options.color?("color:"+D.options.color+";"):"")+'" class="flotr-grid-label">'+G.label+"</div>");I.moveTo(this.plotOffset.left+this.plotWidth-8,this.plotOffset.top+this.tVert(G.v,D));I.lineTo(this.plotOffset.left+this.plotWidth,this.plotOffset.top+this.tVert(G.v,D))}I.stroke();I.restore()}F.push("</div>");this.el.insert(F.join(""))}}},drawTitles:function(){var D,C=this.options,F=C.grid.labelMargin,B=this.ctx,A=this.axes;if(!C.HtmlText&&this.textEnabled){var E={size:C.fontSize,color:C.grid.color,halign:"c"};if(C.subtitle){B.drawText(C.subtitle,this.plotOffset.left+this.plotWidth/2,this.titleHeight+this.subtitleHeight-2,E)}E.weight=1.5;E.size*=1.5;if(C.title){B.drawText(C.title,this.plotOffset.left+this.plotWidth/2,this.titleHeight-2,E)}E.weight=1.8;E.size*=0.8;E.adjustAlign=true;if(A.x.options.title&&A.x.used){E.halign="c";E.valign="t";E.angle=Flotr.toRad(A.x.options.titleAngle);B.drawText(A.x.options.title,this.plotOffset.left+this.plotWidth/2,this.plotOffset.top+A.x.maxLabel.height+this.plotHeight+2*F,E)}if(A.x2.options.title&&A.x2.used){E.halign="c";E.valign="b";E.angle=Flotr.toRad(A.x2.options.titleAngle);B.drawText(A.x2.options.title,this.plotOffset.left+this.plotWidth/2,this.plotOffset.top-A.x2.maxLabel.height-2*F,E)}if(A.y.options.title&&A.y.used){E.halign="r";E.valign="m";E.angle=Flotr.toRad(A.y.options.titleAngle);B.drawText(A.y.options.title,this.plotOffset.left-A.y.maxLabel.width-2*F,this.plotOffset.top+this.plotHeight/2,E)}if(A.y2.options.title&&A.y2.used){E.halign="l";E.valign="m";E.angle=Flotr.toRad(A.y2.options.titleAngle);B.drawText(A.y2.options.title,this.plotOffset.left+this.plotWidth+A.y2.maxLabel.width+2*F,this.plotOffset.top+this.plotHeight/2,E)}}else{D=['<div style="color:'+C.grid.color+';" class="flotr-titles">'];if(C.title){D.push('<div style="position:absolute;top:0;left:'+this.plotOffset.left+"px;font-size:1em;font-weight:bold;text-align:center;width:"+this.plotWidth+'px;" class="flotr-title">'+C.title+"</div>")}if(C.subtitle){D.push('<div style="position:absolute;top:'+this.titleHeight+"px;left:"+this.plotOffset.left+"px;font-size:smaller;text-align:center;width:"+this.plotWidth+'px;" class="flotr-subtitle">'+C.subtitle+"</div>")}D.push("</div>");D.push('<div class="flotr-axis-title" style="font-weight:bold;">');if(A.x.options.title&&A.x.used){D.push('<div style="position:absolute;top:'+(this.plotOffset.top+this.plotHeight+C.grid.labelMargin+A.x.titleSize.height)+"px;left:"+this.plotOffset.left+"px;width:"+this.plotWidth+'px;text-align:center;" class="flotr-axis-title">'+A.x.options.title+"</div>")}if(A.x2.options.title&&A.x2.used){D.push('<div style="position:absolute;top:0;left:'+this.plotOffset.left+"px;width:"+this.plotWidth+'px;text-align:center;" class="flotr-axis-title">'+A.x2.options.title+"</div>")}if(A.y.options.title&&A.y.used){D.push('<div style="position:absolute;top:'+(this.plotOffset.top+this.plotHeight/2-A.y.titleSize.height/2)+'px;left:0;text-align:right;" class="flotr-axis-title">'+A.y.options.title+"</div>")}if(A.y2.options.title&&A.y2.used){D.push('<div style="position:absolute;top:'+(this.plotOffset.top+this.plotHeight/2-A.y.titleSize.height/2)+'px;right:0;text-align:right;" class="flotr-axis-title">'+A.y2.options.title+"</div>")}D.push("</div>");this.el.insert(D.join(""))}},drawSeries:function(A){A=A||this.series;var C=false;for(var B in Flotr._registeredTypes){if(A[B]&&A[B].show){this[Flotr._registeredTypes[B]](A);C=true}}if(!C){this[Flotr._registeredTypes[this.options.defaultType]](A)}},plotLine:function(I,F){var O=this.ctx,A=I.xaxis,K=I.yaxis,J=this.tHoz.bind(this),M=this.tVert.bind(this),H=I.data;if(H.length<2){return }var E=J(H[0][0],A),D=M(H[0][1],K)+F;O.beginPath();O.moveTo(E,D);for(var G=0;G<H.length-1;++G){var C=H[G][0],N=H[G][1],B=H[G+1][0],L=H[G+1][1];if(N===null||L===null){continue}if(N<=L&&N<K.min){if(L<K.min){continue}C=(K.min-N)/(L-N)*(B-C)+C;N=K.min}else{if(L<=N&&L<K.min){if(N<K.min){continue}B=(K.min-N)/(L-N)*(B-C)+C;L=K.min}}if(N>=L&&N>K.max){if(L>K.max){continue}C=(K.max-N)/(L-N)*(B-C)+C;N=K.max}else{if(L>=N&&L>K.max){if(N>K.max){continue}B=(K.max-N)/(L-N)*(B-C)+C;L=K.max}}if(C<=B&&C<A.min){if(B<A.min){continue}N=(A.min-C)/(B-C)*(L-N)+N;C=A.min}else{if(B<=C&&B<A.min){if(C<A.min){continue}L=(A.min-C)/(B-C)*(L-N)+N;B=A.min}}if(C>=B&&C>A.max){if(B>A.max){continue}N=(A.max-C)/(B-C)*(L-N)+N;C=A.max}else{if(B>=C&&B>A.max){if(C>A.max){continue}L=(A.max-C)/(B-C)*(L-N)+N;B=A.max}}if(E!=J(C,A)||D!=M(N,K)+F){O.moveTo(J(C,A),M(N,K)+F)}E=J(B,A);D=M(L,K)+F;O.lineTo(E,D)}O.stroke()},plotLineArea:function(J,D){var S=J.data;if(S.length<2){return }var L,G=0,N=this.ctx,Q=J.xaxis,B=J.yaxis,E=this.tHoz.bind(this),M=this.tVert.bind(this),H=Math.min(Math.max(0,B.min),B.max),F=true;N.beginPath();for(var O=0;O<S.length-1;++O){var R=S[O][0],C=S[O][1],P=S[O+1][0],A=S[O+1][1];if(R<=P&&R<Q.min){if(P<Q.min){continue}C=(Q.min-R)/(P-R)*(A-C)+C;R=Q.min}else{if(P<=R&&P<Q.min){if(R<Q.min){continue}A=(Q.min-R)/(P-R)*(A-C)+C;P=Q.min}}if(R>=P&&R>Q.max){if(P>Q.max){continue}C=(Q.max-R)/(P-R)*(A-C)+C;R=Q.max}else{if(P>=R&&P>Q.max){if(R>Q.max){continue}A=(Q.max-R)/(P-R)*(A-C)+C;P=Q.max}}if(F){N.moveTo(E(R,Q),M(H,B)+D);F=false}if(C>=B.max&&A>=B.max){N.lineTo(E(R,Q),M(B.max,B)+D);N.lineTo(E(P,Q),M(B.max,B)+D);continue}else{if(C<=B.min&&A<=B.min){N.lineTo(E(R,Q),M(B.min,B)+D);N.lineTo(E(P,Q),M(B.min,B)+D);continue}}var I=R,K=P;if(C<=A&&C<B.min&&A>=B.min){R=(B.min-C)/(A-C)*(P-R)+R;C=B.min}else{if(A<=C&&A<B.min&&C>=B.min){P=(B.min-C)/(A-C)*(P-R)+R;A=B.min}}if(C>=A&&C>B.max&&A<=B.max){R=(B.max-C)/(A-C)*(P-R)+R;C=B.max}else{if(A>=C&&A>B.max&&C<=B.max){P=(B.max-C)/(A-C)*(P-R)+R;A=B.max}}if(R!=I){L=(C<=B.min)?L=B.min:B.max;N.lineTo(E(I,Q),M(L,B)+D);N.lineTo(E(R,Q),M(L,B)+D)}N.lineTo(E(R,Q),M(C,B)+D);N.lineTo(E(P,Q),M(A,B)+D);if(P!=K){L=(A<=B.min)?B.min:B.max;N.lineTo(E(K,Q),M(L,B)+D);N.lineTo(E(P,Q),M(L,B)+D)}G=Math.max(P,K)}N.lineTo(E(G,Q),M(H,B)+D);N.closePath();N.fill()},drawSeriesLines:function(C){C=C||this.series;var B=this.ctx;B.save();B.translate(this.plotOffset.left,this.plotOffset.top);B.lineJoin="round";var D=C.lines.lineWidth;var A=C.shadowSize;if(A>0){B.lineWidth=A/2;var E=D/2+B.lineWidth/2;B.strokeStyle="rgba(0,0,0,0.1)";this.plotLine(C,E+A/2);B.strokeStyle="rgba(0,0,0,0.2)";this.plotLine(C,E);if(C.lines.fill){B.fillStyle="rgba(0,0,0,0.05)";this.plotLineArea(C,E+A/2)}}B.lineWidth=D;B.strokeStyle=C.color;if(C.lines.fill){B.fillStyle=C.lines.fillColor!=null?C.lines.fillColor:Flotr.parseColor(C.color).scale(null,null,null,C.lines.fillOpacity).toString();this.plotLineArea(C,0)}this.plotLine(C,0);B.restore()},drawSeriesPoints:function(C){var B=this.ctx;B.save();B.translate(this.plotOffset.left,this.plotOffset.top);var D=C.lines.lineWidth;var A=C.shadowSize;if(A>0){B.lineWidth=A/2;B.strokeStyle="rgba(0,0,0,0.1)";this.plotPointShadows(C,A/2+B.lineWidth/2,C.points.radius);B.strokeStyle="rgba(0,0,0,0.2)";this.plotPointShadows(C,B.lineWidth/2,C.points.radius)}B.lineWidth=C.points.lineWidth;B.strokeStyle=C.color;B.fillStyle=C.points.fillColor!=null?C.points.fillColor:C.color;this.plotPoints(C,C.points.radius,C.points.fill);B.restore()},plotPoints:function(C,E,I){var A=C.xaxis,F=C.yaxis,J=this.ctx,D,B=C.data;for(D=B.length-1;D>-1;--D){var H=B[D][0],G=B[D][1];if(H<A.min||H>A.max||G<F.min||G>F.max){continue}J.beginPath();J.arc(this.tHoz(H,A),this.tVert(G,F),E,0,2*Math.PI,true);if(I){J.fill()}J.stroke()}},plotPointShadows:function(D,B,F){var A=D.xaxis,G=D.yaxis,J=this.ctx,E,C=D.data;for(E=C.length-1;E>-1;--E){var I=C[E][0],H=C[E][1];if(I<A.min||I>A.max||H<G.min||H>G.max){continue}J.beginPath();J.arc(this.tHoz(I,A),this.tVert(H,G)+B,F,0,Math.PI,false);J.stroke()}},drawSeriesBars:function(B){var A=this.ctx,D=B.bars.barWidth,C=Math.min(B.bars.lineWidth,D);A.save();A.translate(this.plotOffset.left,this.plotOffset.top);A.lineJoin="miter";A.lineWidth=C;A.strokeStyle=B.color;this.plotBarsShadows(B,D,0,B.bars.fill);if(B.bars.fill){A.fillStyle=B.bars.fillColor!=null?B.bars.fillColor:Flotr.parseColor(B.color).scale(null,null,null,B.bars.fillOpacity).toString()}this.plotBars(B,D,0,B.bars.fill);A.restore()},plotBars:function(K,N,D,Q){var U=K.data;if(U.length<1){return }var S=K.xaxis,B=K.yaxis,P=this.ctx,F=this.tHoz.bind(this),O=this.tVert.bind(this);for(var R=0;R<U.length;R++){var J=U[R][0],I=U[R][1];var E=true,L=true,A=true;var H=0;if(K.bars.stacked){S.values.each(function(W,V){if(V==J){H=W.stack||0;W.stack=H+I}})}if(K.bars.horizontal){var C=H,T=J+H,G=I,M=I+N}else{var C=J,T=J+N,G=H,M=I+H}if(T<S.min||C>S.max||M<B.min||G>B.max){continue}if(C<S.min){C=S.min;E=false}if(T>S.max){T=S.max;if(S.lastSerie!=K&&K.bars.horizontal){L=false}}if(G<B.min){G=B.min}if(M>B.max){M=B.max;if(B.lastSerie!=K&&!K.bars.horizontal){L=false}}if(Q){P.beginPath();P.moveTo(F(C,S),O(G,B)+D);P.lineTo(F(C,S),O(M,B)+D);P.lineTo(F(T,S),O(M,B)+D);P.lineTo(F(T,S),O(G,B)+D);P.fill()}if(K.bars.lineWidth!=0&&(E||A||L)){P.beginPath();P.moveTo(F(C,S),O(G,B)+D);P[E?"lineTo":"moveTo"](F(C,S),O(M,B)+D);P[L?"lineTo":"moveTo"](F(T,S),O(M,B)+D);P[A?"lineTo":"moveTo"](F(T,S),O(G,B)+D);P.stroke()}}},plotBarsShadows:function(I,K,C){var T=I.data;if(T.length<1){return }var R=I.xaxis,A=I.yaxis,P=this.ctx,D=this.tHoz.bind(this),M=this.tVert.bind(this),N=this.options.shadowSize;for(var Q=0;Q<T.length;Q++){var H=T[Q][0],G=T[Q][1];var E=0;if(I.bars.stacked){R.values.each(function(V,U){if(U==H){E=V.stackShadow||0;V.stackShadow=E+G}})}if(I.bars.horizontal){var B=E,S=H+E,F=G,J=G+K}else{var B=H,S=H+K,F=E,J=G+E}if(S<R.min||B>R.max||J<A.min||F>A.max){continue}if(B<R.min){B=R.min}if(S>R.max){S=R.max}if(F<A.min){F=A.min}if(J>A.max){J=A.max}var O=D(S,R)-D(B,R)-((D(S,R)+N<=this.plotWidth)?0:N);var L=Math.max(0,M(F,A)-M(J,A)-((M(F,A)+N<=this.plotHeight)?0:N));P.fillStyle="rgba(0,0,0,0.05)";P.fillRect(Math.min(D(B,R)+N,this.plotWidth),Math.min(M(J,A)+N,this.plotWidth),O,L)}},drawSeriesCandles:function(B){var A=this.ctx,C=B.candles.candleWidth;A.save();A.translate(this.plotOffset.left,this.plotOffset.top);A.lineJoin="miter";A.lineWidth=B.candles.lineWidth;this.plotCandlesShadows(B,C/2);this.plotCandles(B,C/2);A.restore()},plotCandles:function(K,D){var W=K.data;if(W.length<1){return }var T=K.xaxis,B=K.yaxis,P=this.ctx,E=this.tHoz.bind(this),O=this.tVert.bind(this);for(var S=0;S<W.length;S++){var U=W[S],J=U[0],L=U[1],I=U[2],X=U[3],N=U[4];var C=J,V=J+K.candles.candleWidth,G=Math.max(B.min,X),M=Math.min(B.max,I),A=Math.max(B.min,Math.min(L,N)),R=Math.min(B.max,Math.max(L,N));if(V<T.min||C>T.max||M<B.min||G>B.max){continue}var Q=K.candles[L>N?"downFillColor":"upFillColor"];if(K.candles.fill&&!K.candles.barcharts){P.fillStyle=Flotr.parseColor(Q).scale(null,null,null,K.candles.fillOpacity).toString();P.fillRect(E(C,T),O(R,B)+D,E(V,T)-E(C,T),O(A,B)-O(R,B))}if(K.candles.lineWidth||K.candles.wickLineWidth){var J,H,F=(K.candles.wickLineWidth%2)/2;J=Math.floor(E((C+V)/2),T)+F;P.save();P.strokeStyle=Q;P.lineWidth=K.candles.wickLineWidth;P.lineCap="butt";if(K.candles.barcharts){P.beginPath();P.moveTo(J,Math.floor(O(M,B)+D));P.lineTo(J,Math.floor(O(G,B)+D));H=Math.floor(O(L,B)+D)+0.5;P.moveTo(Math.floor(E(C,T))+F,H);P.lineTo(J,H);H=Math.floor(O(N,B)+D)+0.5;P.moveTo(Math.floor(E(V,T))+F,H);P.lineTo(J,H)}else{P.strokeRect(E(C,T),O(R,B)+D,E(V,T)-E(C,T),O(A,B)-O(R,B));P.beginPath();P.moveTo(J,Math.floor(O(R,B)+D));P.lineTo(J,Math.floor(O(M,B)+D));P.moveTo(J,Math.floor(O(A,B)+D));P.lineTo(J,Math.floor(O(G,B)+D))}P.stroke();P.restore()}}},plotCandlesShadows:function(H,C){var T=H.data;if(T.length<1||H.candles.barcharts){return }var Q=H.xaxis,A=H.yaxis,D=this.tHoz.bind(this),M=this.tVert.bind(this),N=this.options.shadowSize;for(var P=0;P<T.length;P++){var R=T[P],G=R[0],I=R[1],F=R[2],U=R[3],K=R[4];var B=G,S=G+H.candles.candleWidth,E=Math.max(A.min,Math.min(I,K)),J=Math.min(A.max,Math.max(I,K));if(S<Q.min||B>Q.max||J<A.min||E>A.max){continue}var O=D(S,Q)-D(B,Q)-((D(S,Q)+N<=this.plotWidth)?0:N);var L=Math.max(0,M(E,A)-M(J,A)-((M(E,A)+N<=this.plotHeight)?0:N));this.ctx.fillStyle="rgba(0,0,0,0.05)";this.ctx.fillRect(Math.min(D(B,Q)+N,this.plotWidth),Math.min(M(J,A)+N,this.plotWidth),O,L)}},drawSeriesPie:function(G){if(!this.options.pie.drawn){var K=this.ctx,C=this.options,E=G.pie.lineWidth,I=G.shadowSize,R=G.data,D=(Math.min(this.canvasWidth,this.canvasHeight)*G.pie.sizeRatio)/2,H=[];var L=1;var P=Math.sin(G.pie.viewAngle)*G.pie.spliceThickness/L;var M={size:C.fontSize*1.2,color:C.grid.color,weight:1.5};var Q={x:(this.canvasWidth+this.plotOffset.left)/2,y:(this.canvasHeight-this.plotOffset.bottom)/2};var O=this.series.collect(function(T,S){if(T.pie.show){return{name:(T.label||T.data[0][1]),value:[S,T.data[0][1]],explode:T.pie.explode}}});var B=O.pluck("value").pluck(1).inject(0,function(S,T){return S+T});var F=0,N=G.pie.startAngle,J=0;var A=O.collect(function(S){N+=F;J=parseFloat(S.value[1]);F=J/B;return{name:S.name,fraction:F,x:S.value[0],y:J,explode:S.explode,startAngle:2*N*Math.PI,endAngle:2*(N+F)*Math.PI}});K.save();if(I>0){A.each(function(V){var S=(V.startAngle+V.endAngle)/2;var T=Q.x+Math.cos(S)*V.explode+I;var U=Q.y+Math.sin(S)*V.explode+I;this.plotSlice(T,U,D,V.startAngle,V.endAngle,false,L);K.fillStyle="rgba(0,0,0,0.1)";K.fill()},this)}if(C.HtmlText){H=['<div style="color:'+this.options.grid.color+'" class="flotr-labels">']}A.each(function(c,X){var W=(c.startAngle+c.endAngle)/2;var V=C.colors[X];var Y=Q.x+Math.cos(W)*c.explode;var U=Q.y+Math.sin(W)*c.explode;this.plotSlice(Y,U,D,c.startAngle,c.endAngle,false,L);if(G.pie.fill){K.fillStyle=Flotr.parseColor(V).scale(null,null,null,G.pie.fillOpacity).toString();K.fill()}K.lineWidth=E;K.strokeStyle=V;K.stroke();var b=C.pie.labelFormatter(c);var S=(Math.cos(W)<0);var a=Y+Math.cos(W)*(G.pie.explode+D);var Z=U+Math.sin(W)*(G.pie.explode+D);if(c.fraction&&b){if(C.HtmlText){var T="position:absolute;top:"+(Z-5)+"px;";if(S){T+="right:"+(this.canvasWidth-a)+"px;text-align:right;"}else{T+="left:"+a+"px;text-align:left;"}H.push('<div style="'+T+'" class="flotr-grid-label">'+b+"</div>")}else{M.halign=S?"r":"l";K.drawText(b,a,Z+M.size/2,M)}}},this);if(C.HtmlText){H.push("</div>");this.el.insert(H.join(""))}K.restore();C.pie.drawn=true}},plotSlice:function(B,H,A,E,D,F,G){var C=this.ctx;G=G||1;C.save();C.scale(1,G);C.beginPath();C.moveTo(B,H);C.arc(B,H,A,E,D,F);C.lineTo(B,H);C.closePath();C.restore()},plotPie:function(){},insertLegend:function(){if(!this.options.legend.show){return }var H=this.series,I=this.plotOffset,B=this.options,b=[],A=false,O=this.ctx,R;var Q=H.findAll(function(c){return(c.label&&!c.hide)}).size();if(Q){if(!B.HtmlText&&this.textEnabled){var T={size:B.fontSize*1.1,color:B.grid.color};var M=B.legend.position,N=B.legend.margin,L=B.legend.labelBoxWidth,Z=B.legend.labelBoxHeight,S=B.legend.labelBoxMargin,W=I.left+N,U=I.top+N;var a=0;for(R=H.length-1;R>-1;--R){if(!H[R].label||H[R].hide){continue}var E=B.legend.labelFormatter(H[R].label);a=Math.max(a,O.measureText(E,T))}var K=Math.round(L+S*3+a),C=Math.round(Q*(S+Z)+S);if(M.charAt(0)=="s"){U=I.top+this.plotHeight-(N+C)}if(M.charAt(1)=="e"){W=I.left+this.plotWidth-(N+K)}var P=Flotr.parseColor(B.legend.backgroundColor||"rgb(240,240,240)").scale(null,null,null,B.legend.backgroundOpacity||0.1).toString();O.fillStyle=P;O.fillRect(W,U,K,C);O.strokeStyle=B.legend.labelBoxBorderColor;O.strokeRect(Flotr.toPixel(W),Flotr.toPixel(U),K,C);var G=W+S;var F=U+S;for(R=0;R<H.length;R++){if(!H[R].label||H[R].hide){continue}var E=B.legend.labelFormatter(H[R].label);O.fillStyle=H[R].color;O.fillRect(G,F,L-1,Z-1);O.strokeStyle=B.legend.labelBoxBorderColor;O.lineWidth=1;O.strokeRect(Math.ceil(G)-1.5,Math.ceil(F)-1.5,L+2,Z+2);O.drawText(E,G+L+S,F+(Z+T.size-O.fontDescent(T))/2,T);F+=Z+S}}else{for(R=0;R<H.length;++R){if(!H[R].label||H[R].hide){continue}if(R%B.legend.noColumns==0){b.push(A?"</tr><tr>":"<tr>");A=true}var E=B.legend.labelFormatter(H[R].label);b.push('<td class="flotr-legend-color-box"><div style="border:1px solid '+B.legend.labelBoxBorderColor+';padding:1px"><div style="width:'+B.legend.labelBoxWidth+"px;height:"+B.legend.labelBoxHeight+"px;background-color:"+H[R].color+'"></div></div></td><td class="flotr-legend-label">'+E+"</td>")}if(A){b.push("</tr>")}if(b.length>0){var V='<table style="font-size:smaller;color:'+B.grid.color+'">'+b.join("")+"</table>";if(B.legend.container!=null){$(B.legend.container).update(V)}else{var D="";var M=B.legend.position,N=B.legend.margin;if(M.charAt(0)=="n"){D+="top:"+(N+I.top)+"px;"}else{if(M.charAt(0)=="s"){D+="bottom:"+(N+I.bottom)+"px;"}}if(M.charAt(1)=="e"){D+="right:"+(N+I.right)+"px;"}else{if(M.charAt(1)=="w"){D+="left:"+(N+I.left)+"px;"}}var J=this.el.insert('<div class="flotr-legend" style="position:absolute;z-index:2;'+D+'">'+V+"</div>").select("div.flotr-legend").first();if(B.legend.backgroundOpacity!=0){var Y=B.legend.backgroundColor;if(Y==null){var X=(B.grid.backgroundColor!=null)?B.grid.backgroundColor:Flotr.extractColor(J);Y=Flotr.parseColor(X).adjust(null,null,null,1).toString()}this.el.insert('<div class="flotr-legend-bg" style="position:absolute;width:'+J.getWidth()+"px;height:"+J.getHeight()+"px;"+D+"background-color:"+Y+';"> </div>').select("div.flotr-legend-bg").first().setStyle({opacity:B.legend.backgroundOpacity})}}}}}},getEventPosition:function(C){var G=this.overlay.cumulativeOffset(),F=(C.pageX-G.left-this.plotOffset.left),E=(C.pageY-G.top-this.plotOffset.top),D=0,B=0;if(C.pageX==null&&C.clientX!=null){var H=document.documentElement,A=document.body;D=C.clientX+(H&&H.scrollLeft||A.scrollLeft||0);B=C.clientY+(H&&H.scrollTop||A.scrollTop||0)}else{D=C.pageX;B=C.pageY}return{x:this.axes.x.min+F/this.axes.x.scale,x2:this.axes.x2.min+F/this.axes.x2.scale,y:this.axes.y.max-E/this.axes.y.scale,y2:this.axes.y2.max-E/this.axes.y2.scale,relX:F,relY:E,absX:D,absY:B}},clickHandler:function(A){if(this.ignoreClick){this.ignoreClick=false;return }this.el.fire("flotr:click",[this.getEventPosition(A),this])},mouseMoveHandler:function(A){var B=this.getEventPosition(A);this.lastMousePos.pageX=B.absX;this.lastMousePos.pageY=B.absY;if(this.selectionInterval==null&&(this.options.mouse.track||this.series.any(function(C){return C.mouse&&C.mouse.track}))){this.hit(B)}this.el.fire("flotr:mousemove",[A,B,this])},mouseDownHandler:function(C){if(C.isRightClick()){C.stop();var B=this.overlay;B.hide();function A(){B.show();$(document).stopObserving("mousemove",A)}$(document).observe("mousemove",A);return }if(!this.options.selection.mode||!C.isLeftClick()){return }this.setSelectionPos(this.selection.first,C);if(this.selectionInterval!=null){clearInterval(this.selectionInterval)}this.lastMousePos.pageX=null;this.selectionInterval=setInterval(this.updateSelection.bind(this),1000/this.options.selection.fps);this.mouseUpHandler=this.mouseUpHandler.bind(this);$(document).observe("mouseup",this.mouseUpHandler)},fireSelectEvent:function(){var A=this.axes,F=this.selection,C=(F.first.x<=F.second.x)?F.first.x:F.second.x,B=(F.first.x<=F.second.x)?F.second.x:F.first.x,E=(F.first.y>=F.second.y)?F.first.y:F.second.y,D=(F.first.y>=F.second.y)?F.second.y:F.first.y;C=A.x.min+C/A.x.scale;B=A.x.min+B/A.x.scale;E=A.y.max-E/A.y.scale;D=A.y.max-D/A.y.scale;this.el.fire("flotr:select",[{x1:C,y1:E,x2:B,y2:D},this])},mouseUpHandler:function(A){$(document).stopObserving("mouseup",this.mouseUpHandler);A.stop();if(this.selectionInterval!=null){clearInterval(this.selectionInterval);this.selectionInterval=null}this.setSelectionPos(this.selection.second,A);this.clearSelection();if(this.selectionIsSane()){this.drawSelection();this.fireSelectEvent();this.ignoreClick=true}},setSelectionPos:function(D,B){var A=this.options,C=$(this.overlay).cumulativeOffset();if(A.selection.mode.indexOf("x")==-1){D.x=(D==this.selection.first)?0:this.plotWidth}else{D.x=B.pageX-C.left-this.plotOffset.left;D.x=Math.min(Math.max(0,D.x),this.plotWidth)}if(A.selection.mode.indexOf("y")==-1){D.y=(D==this.selection.first)?0:this.plotHeight}else{D.y=B.pageY-C.top-this.plotOffset.top;D.y=Math.min(Math.max(0,D.y),this.plotHeight)}},updateSelection:function(){if(this.lastMousePos.pageX==null){return }this.setSelectionPos(this.selection.second,this.lastMousePos);this.clearSelection();if(this.selectionIsSane()){this.drawSelection()}},clearSelection:function(){if(this.prevSelection==null){return }var G=this.prevSelection,E=this.octx,C=this.plotOffset,A=Math.min(G.first.x,G.second.x),F=Math.min(G.first.y,G.second.y),B=Math.abs(G.second.x-G.first.x),D=Math.abs(G.second.y-G.first.y);E.clearRect(A+C.left-E.lineWidth,F+C.top-E.lineWidth,B+E.lineWidth*2,D+E.lineWidth*2);this.prevSelection=null},setSelection:function(G){var B=this.options,H=this.axes.x,A=this.axes.y,F=yaxis.scale,D=xaxis.scale,E=B.selection.mode.indexOf("x")!=-1,C=B.selection.mode.indexOf("y")!=-1;this.clearSelection();this.selection.first.y=E?0:(A.max-G.y1)*F;this.selection.second.y=E?this.plotHeight:(A.max-G.y2)*F;this.selection.first.x=C?0:(G.x1-H.min)*D;this.selection.second.x=C?this.plotWidth:(G.x2-H.min)*D;this.drawSelection();this.fireSelectEvent()},drawSelection:function(){var C=this.prevSelection,F=this.selection,H=this.octx,I=this.options,A=this.plotOffset;if(C!=null&&F.first.x==C.first.x&&F.first.y==C.first.y&&F.second.x==C.second.x&&F.second.y==C.second.y){return }H.strokeStyle=Flotr.parseColor(I.selection.color).scale(null,null,null,0.8).toString();H.lineWidth=1;H.lineJoin="round";H.fillStyle=Flotr.parseColor(I.selection.color).scale(null,null,null,0.4).toString();this.prevSelection={first:{x:F.first.x,y:F.first.y},second:{x:F.second.x,y:F.second.y}};var E=Math.min(F.first.x,F.second.x),D=Math.min(F.first.y,F.second.y),G=Math.abs(F.second.x-F.first.x),B=Math.abs(F.second.y-F.first.y);H.fillRect(E+A.left,D+A.top,G,B);H.strokeRect(E+A.left,D+A.top,G,B)},selectionIsSane:function(){var A=this.selection;return Math.abs(A.second.x-A.first.x)>=5&&Math.abs(A.second.y-A.first.y)>=5},clearHit:function(){if(this.prevHit){var B=this.options,A=this.plotOffset,C=this.prevHit;this.octx.clearRect(this.tHoz(C.x)+A.left-B.points.radius*2,this.tVert(C.y)+A.top-B.points.radius*2,B.points.radius*3+B.points.lineWidth*3,B.points.radius*3+B.points.lineWidth*3);this.prevHit=null}},hit:function(I){var G=this.series,C=this.options,R=this.prevHit,H=this.plotOffset,D=this.octx,S,A,M,Q,L={dist:Number.MAX_VALUE,x:null,y:null,relX:I.relX,relY:I.relY,absX:I.absX,absY:I.absY,mouse:null};for(Q=0;Q<G.length;Q++){s=G[Q];if(!s.mouse.track){continue}S=s.data;A=(s.xaxis.scale*s.mouse.sensibility);M=(s.yaxis.scale*s.mouse.sensibility);for(var P=0,B,E;P<S.length;P++){if(S[P][1]===null){continue}B=Math.pow(s.xaxis.scale*(S[P][0]-I.x),2);E=Math.pow(s.yaxis.scale*(S[P][1]-I.y),2);if(B<A&&E<M&&Math.sqrt(B+E)<L.dist){L.dist=Math.sqrt(B+E);L.x=S[P][0];L.y=S[P][1];L.mouse=s.mouse}}}if(L.mouse&&L.mouse.track&&!R||(R&&(L.x!=R.x||L.y!=R.y))){var K=this.mouseTrack||this.el.select(".flotr-mouse-value")[0],F="",J=C.mouse.position,N=C.mouse.margin,O="opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;";if(!C.mouse.relative){if(J.charAt(0)=="n"){F+="top:"+(N+H.top)+"px;"}else{if(J.charAt(0)=="s"){F+="bottom:"+(N+H.bottom)+"px;"}}if(J.charAt(1)=="e"){F+="right:"+(N+H.right)+"px;"}else{if(J.charAt(1)=="w"){F+="left:"+(N+H.left)+"px;"}}}else{if(J.charAt(0)=="n"){F+="bottom:"+(N-H.top-this.tVert(L.y)+this.canvasHeight)+"px;"}else{if(J.charAt(0)=="s"){F+="top:"+(N+H.top+this.tVert(L.y))+"px;"}}if(J.charAt(1)=="e"){F+="left:"+(N+H.left+this.tHoz(L.x))+"px;"}else{if(J.charAt(1)=="w"){F+="right:"+(N-H.left-this.tHoz(L.x)+this.canvasWidth)+"px;"}}}O+=F;if(!K){this.el.insert('<div class="flotr-mouse-value" style="'+O+'"></div>');K=this.mouseTrack=this.el.select(".flotr-mouse-value").first()}else{this.mouseTrack=K.setStyle(O)}if(L.x!==null&&L.y!==null){K.show();this.clearHit();if(L.mouse.lineColor!=null){D.save();D.translate(H.left,H.top);D.lineWidth=C.points.lineWidth;D.strokeStyle=L.mouse.lineColor;D.fillStyle="#ffffff";D.beginPath();D.arc(this.tHoz(L.x),this.tVert(L.y),C.mouse.radius,0,2*Math.PI,true);D.fill();D.stroke();D.restore()}this.prevHit=L;var T=L.mouse.trackDecimals;if(T==null||T<0){T=0}K.innerHTML=L.mouse.trackFormatter({x:L.x.toFixed(T),y:L.y.toFixed(T)});K.fire("flotr:hit",[L,this])}else{if(R){K.hide();this.clearHit()}}}},saveImage:function(D,C,A,B){var E=null;switch(D){case"jpeg":case"jpg":E=Canvas2Image.saveAsJPEG(this.canvas,B,C,A);break;default:case"png":E=Canvas2Image.saveAsPNG(this.canvas,B,C,A);break;case"bmp":E=Canvas2Image.saveAsBMP(this.canvas,B,C,A);break}if(Object.isElement(E)&&B){this.restoreCanvas();this.canvas.hide();this.overlay.hide();this.el.insert(E.setStyle({position:"absolute"}))}},restoreCanvas:function(){this.canvas.show();this.overlay.show();this.el.select("img").invoke("remove")}});Flotr.Color=Class.create({initialize:function(E,D,B,C){this.rgba=["r","g","b","a"];var A=4;while(-1<--A){this[this.rgba[A]]=arguments[A]||((A==3)?1:0)}this.normalize()},adjust:function(D,C,E,B){var A=4;while(-1<--A){if(arguments[A]!=null){this[this.rgba[A]]+=arguments[A]}}return this.normalize()},clone:function(){return new Flotr.Color(this.r,this.b,this.g,this.a)},limit:function(B,A,C){return Math.max(Math.min(B,C),A)},normalize:function(){var A=this.limit;this.r=A(parseInt(this.r),0,255);this.g=A(parseInt(this.g),0,255);this.b=A(parseInt(this.b),0,255);this.a=A(this.a,0,1);return this},scale:function(D,C,E,B){var A=4;while(-1<--A){if(arguments[A]!=null){this[this.rgba[A]]*=arguments[A]}}return this.normalize()},distance:function(B){if(!B){return }B=new Flotr.parseColor(B);var C=0;var A=3;while(-1<--A){C+=Math.abs(this[this.rgba[A]]-B[this.rgba[A]])}return C},toString:function(){return(this.a>=1)?"rgb("+[this.r,this.g,this.b].join(",")+")":"rgba("+[this.r,this.g,this.b,this.a].join(",")+")"}});Flotr.Color.lookupColors={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]};Flotr.Date={format:function(F,E){if(!F){return }var A=function(H){H=H.toString();return H.length==1?"0"+H:H};var D=[];var C=false;for(var B=0;B<E.length;++B){var G=E.charAt(B);if(C){switch(G){case"h":G=F.getUTCHours().toString();break;case"H":G=A(F.getUTCHours());break;case"M":G=A(F.getUTCMinutes());break;case"S":G=A(F.getUTCSeconds());break;case"d":G=F.getUTCDate().toString();break;case"m":G=(F.getUTCMonth()+1).toString();break;case"y":G=F.getUTCFullYear().toString();break;case"b":G=Flotr.Date.monthNames[F.getUTCMonth()];break}D.push(G);C=false}else{if(G=="%"){C=true}else{D.push(G)}}}return D.join("")},timeUnits:{second:1000,minute:60*1000,hour:60*60*1000,day:24*60*60*1000,month:30*24*60*60*1000,year:365.2425*24*60*60*1000},spec:[[1,"second"],[2,"second"],[5,"second"],[10,"second"],[30,"second"],[1,"minute"],[2,"minute"],[5,"minute"],[10,"minute"],[30,"minute"],[1,"hour"],[2,"hour"],[4,"hour"],[8,"hour"],[12,"hour"],[1,"day"],[2,"day"],[3,"day"],[0.25,"month"],[0.5,"month"],[1,"month"],[2,"month"],[3,"month"],[6,"month"],[1,"year"]],monthNames:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]};

--- /dev/null
+++ b/js/flotr/flotr.debug-0.2.0-alpha_radar1.js
@@ -1,1 +1,3349 @@
+//Flotr 0.2.0-alpha Copyright (c) 2009 Bas Wenneker, <http://solutoire.com>, MIT License.

+//

+//Radar chart added by Ryan Simmons

+//
+/* $Id: flotr.js 82 2009-01-12 19:19:31Z fabien.menager $ */

+

+var Flotr = {

+	version: '0.2.0-alpha',

+	author: 'Bas Wenneker',

+	website: 'http://www.solutoire.com',

+	/**

+	 * An object of the default registered graph types. Use Flotr.register(type, functionName)

+	 * to add your own type.

+	 */

+	_registeredTypes:{

+		'lines': 'drawSeriesLines',

+		'points': 'drawSeriesPoints',

+		'bars': 'drawSeriesBars',

+		'candles': 'drawSeriesCandles',

+		'pie': 'drawSeriesPie',

+		'radar':'drawSeriesRadar'

+	},

+	/**

+	 * Can be used to register your own chart type. Default types are 'lines', 'points' and 'bars'.

+	 * This is still experimental.

+	 * @todo Test and confirm.

+	 * @param {String} type - type of chart, like 'pies', 'bars' etc.

+	 * @param {String} functionName - Name of the draw function, like 'drawSeriesPies', 'drawSeriesBars' etc.

+	 */

+	register: function(type, functionName){

+		Flotr._registeredTypes[type] = functionName+'';	

+	},

+	/**

+	 * Draws the graph. This function is here for backwards compatibility with Flotr version 0.1.0alpha.

+	 * You could also draw graphs by directly calling Flotr.Graph(element, data, options).

+	 * @param {Element} el - element to insert the graph into

+	 * @param {Object} data - an array or object of dataseries

+	 * @param {Object} options - an object containing options

+	 * @param {Class} _GraphKlass_ - (optional) Class to pass the arguments to, defaults to Flotr.Graph

+	 * @return {Class} returns a new graph object and of course draws the graph.

+	 */

+	draw: function(el, data, options, _GraphKlass_){	

+		_GraphKlass_ = _GraphKlass_ || Flotr.Graph;

+		return new _GraphKlass_(el, data, options);

+	},

+	/**

+	 * Collects dataseries from input and parses the series into the right format. It returns an Array 

+	 * of Objects each having at least the 'data' key set.

+	 * @param {Array/Object} data - Object or array of dataseries

+	 * @return {Array} Array of Objects parsed into the right format ({(...,) data: [[x1,y1], [x2,y2], ...] (, ...)})

+	 */

+	getSeries: function(data){

+		return data.collect(function(serie){

+			var i, serie = (serie.data) ? Object.clone(serie) : {'data': serie};

+			for (i = serie.data.length-1; i > -1; --i) {

+				serie.data[i][1] = (serie.data[i][1] === null ? null : parseFloat(serie.data[i][1])); 

+			}

+			return serie;

+		});

+	},

+	/**

+	 * Recursively merges two objects.

+	 * @param {Object} src - source object (likely the object with the least properties)

+	 * @param {Object} dest - destination object (optional, object with the most properties)

+	 * @return {Object} recursively merged Object

+	 */

+	merge: function(src, dest){

+		var result = dest || {};

+		for(var i in src){

+			result[i] = (src[i] != null && typeof(src[i]) == 'object' && !(src[i].constructor == Array || src[i].constructor == RegExp) && !Object.isElement(src[i])) ? Flotr.merge(src[i], dest[i]) : result[i] = src[i];		

+		}

+		return result;

+	},	

+	/**

+	 * Function calculates the ticksize and returns it.

+	 * @param {Integer} noTicks - number of ticks

+	 * @param {Integer} min - lower bound integer value for the current axis

+	 * @param {Integer} max - upper bound integer value for the current axis

+	 * @param {Integer} decimals - number of decimals for the ticks

+	 * @return {Integer} returns the ticksize in pixels

+	 */

+	getTickSize: function(noTicks, min, max, decimals){

+		var delta = (max - min) / noTicks;	

+		var magn = Flotr.getMagnitude(delta);

+		

+		// Norm is between 1.0 and 10.0.

+		var norm = delta / magn;

+		

+		var tickSize = 10;

+		if(norm < 1.5) tickSize = 1;

+		else if(norm < 2.25) tickSize = 2;

+		else if(norm < 3) tickSize = ((decimals == 0) ? 2 : 2.5);

+		else if(norm < 7.5) tickSize = 5;

+		

+		return tickSize * magn;

+	},

+	/**

+	 * Default tick formatter.

+	 * @param {String/Integer} val - tick value integer

+	 * @return {String} formatted tick string

+	 */

+	defaultTickFormatter: function(val){

+		return val+'';

+	},

+	/**

+	 * Formats the mouse tracker values.

+	 * @param {Object} obj - Track value Object {x:..,y:..}

+	 * @return {String} Formatted track string

+	 */

+	defaultTrackFormatter: function(obj){

+		return '('+obj.x+', '+obj.y+')';

+	}, 

+	defaultPieLabelFormatter: function(slice) {

+	  return (slice.fraction*100).toFixed(2)+'%';

+	},

+	/**

+	 * Returns the magnitude of the input value.

+	 * @param {Integer/Float} x - integer or float value

+	 * @return {Integer/Float} returns the magnitude of the input value

+	 */

+	getMagnitude: function(x){

+		return Math.pow(10, Math.floor(Math.log(x) / Math.LN10));

+	},

+	toPixel: function(val){

+		return Math.floor(val)+0.5;//((val-Math.round(val) < 0.4) ? (Math.floor(val)-0.5) : val);

+	},

+	toRad: function(angle){

+		return -angle * (Math.PI/180);

+	},

+	/**

+	 * Parses a color string and returns a corresponding Color.

+	 * @param {String} str - string thats representing a color

+	 * @return {Color} returns a Color object or false

+	 */

+	parseColor: function(str){

+		if (str instanceof Flotr.Color) return str;

+		

+		var result, Color = Flotr.Color;

+

+		// rgb(num,num,num)

+		if((result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str)))

+			return new Color(parseInt(result[1]), parseInt(result[2]), parseInt(result[3]));

+	

+		// rgba(num,num,num,num)

+		if((result = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str)))

+			return new Color(parseInt(result[1]), parseInt(result[2]), parseInt(result[3]), parseFloat(result[4]));

+			

+		// rgb(num%,num%,num%)

+		if((result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str)))

+			return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55);

+	

+		// rgba(num%,num%,num%,num)

+		if((result = /rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str)))

+			return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55, parseFloat(result[4]));

+			

+		// #a0b1c2

+		if((result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str)))

+			return new Color(parseInt(result[1],16), parseInt(result[2],16), parseInt(result[3],16));

+	

+		// #fff

+		if((result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str)))

+			return new Color(parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16));

+

+		// Otherwise, we're most likely dealing with a named color.

+		var name = str.strip().toLowerCase();

+		if(name == 'transparent'){

+			return new Color(255, 255, 255, 0);

+		}

+		return ((result = Color.lookupColors[name])) ? new Color(result[0], result[1], result[2]) : false;

+	},

+	/**

+	 * Extracts the background-color of the passed element.

+	 * @param {Element} element

+	 * @return {String} color string

+	 */

+	extractColor: function(element){

+		var color;

+		// Loop until we find an element with a background color and stop when we hit the body element. 

+		do {

+			color = element.getStyle('background-color').toLowerCase();

+			if(!(color == '' || color == 'transparent')) break;

+			element = element.up(0);

+		} while(!element.nodeName.match(/^body$/i));

+

+		// Catch Safari's way of signaling transparent.

+		return (color == 'rgba(0, 0, 0, 0)') ? 'transparent' : color;

+	}

+};

+/**

+ * Flotr Graph class that plots a graph on creation.

+

+ */

+Flotr.Graph = Class.create({

+	/**

+	 * Flotr Graph constructor.

+	 * @param {Element} el - element to insert the graph into

+	 * @param {Object} data - an array or object of dataseries

+ 	 * @param {Object} options - an object containing options

+	 */

+	initialize: function(el, data, options){

+		this.el = $(el);

+		

+		if (!this.el) throw 'The target container doesn\'t exist';

+		

+		this.data = data;

+		this.series = Flotr.getSeries(data);

+		this.setOptions(options);

+		

+		// Initialize some variables

+		this.lastMousePos = { pageX: null, pageY: null };

+		this.selection = { first: { x: -1, y: -1}, second: { x: -1, y: -1} };

+		this.prevSelection = null;

+		this.selectionInterval = null;

+		this.ignoreClick = false;   

+		this.prevHit = null;

+    

+		// Create and prepare canvas.

+		this.constructCanvas();

+		

+		// Add event handlers for mouse tracking, clicking and selection

+		this.initEvents();

+		

+		this.findDataRanges();

+		this.calculateTicks(this.axes.x);

+		this.calculateTicks(this.axes.x2);

+		this.calculateTicks(this.axes.y);

+		this.calculateTicks(this.axes.y2);

+		

+		this.calculateSpacing();

+		this.draw();

+		this.insertLegend();

+    

+		// Graph and Data tabs

+		if (this.options.spreadsheet.show) 

+		this.constructTabs();

+	},

+	/**

+	 * Sets options and initializes some variables and color specific values, used by the constructor. 

+	 * @param {Object} opts - options object

+	 */

+  setOptions: function(opts){

+    var options = {

+      colors: ['#00A8F0', '#C0D800', '#CB4B4B', '#4DA74D', '#9440ED'], //=> The default colorscheme. When there are > 5 series, additional colors are generated.

+      title: null,

+      subtitle: null,

+      legend: {

+        show: true,            // => setting to true will show the legend, hide otherwise

+        noColumns: 1,          // => number of colums in legend table // @todo: doesn't work for HtmlText = false

+        labelFormatter: Prototype.K, // => fn: string -> string

+        labelBoxBorderColor: '#CCCCCC', // => border color for the little label boxes

+        labelBoxWidth: 14,

+        labelBoxHeight: 10,

+        labelBoxMargin: 5,

+        container: null,       // => container (as jQuery object) to put legend in, null means default on top of graph

+        position: 'nw',        // => position of default legend container within plot

+        margin: 5,             // => distance from grid edge to default legend container within plot

+        backgroundColor: null, // => null means auto-detect

+        backgroundOpacity: 0.85// => set to 0 to avoid background, set to 1 for a solid background

+      },

+      xaxis: {

+        ticks: null,           // => format: either [1, 3] or [[1, 'a'], 3]

+        showLabels: true,      // => setting to true will show the axis ticks labels, hide otherwise

+        labelsAngle: 0,        // => Labels' angle, in degrees

+        title: null,           // => axis title

+        titleAngle: 0,         // => axis title's angle, in degrees

+        noTicks: 5,            // => number of ticks for automagically generated ticks

+        tickFormatter: Flotr.defaultTickFormatter, // => fn: number -> string

+        tickDecimals: null,    // => no. of decimals, null means auto

+        min: null,             // => min. value to show, null means set automatically

+        max: null,             // => max. value to show, null means set automatically

+        autoscaleMargin: 0,    // => margin in % to add if auto-setting min/max

+        color: null

+      },

+      x2axis: {},

+      yaxis: {

+        ticks: null,           // => format: either [1, 3] or [[1, 'a'], 3]

+        showLabels: true,      // => setting to true will show the axis ticks labels, hide otherwise

+        labelsAngle: 0,        // => Labels' angle, in degrees

+        title: null,           // => axis title

+        titleAngle: 90,        // => axis title's angle, in degrees

+        noTicks: 5,            // => number of ticks for automagically generated ticks

+        tickFormatter: Flotr.defaultTickFormatter, // => fn: number -> string

+        tickDecimals: null,    // => no. of decimals, null means auto

+        min: null,             // => min. value to show, null means set automatically

+        max: null,             // => max. value to show, null means set automatically

+        autoscaleMargin: 0,    // => margin in % to add if auto-setting min/max

+        color: null

+      },

+      y2axis: {

+      	titleAngle: 270

+      },

+      points: {

+        show: false,           // => setting to true will show points, false will hide

+        radius: 3,             // => point radius (pixels)

+        lineWidth: 2,          // => line width in pixels

+        fill: true,            // => true to fill the points with a color, false for (transparent) no fill

+        fillColor: '#FFFFFF',  // => fill color

+        fillOpacity: 0.4

+      },

+      lines: {

+        show: false,           // => setting to true will show lines, false will hide

+        lineWidth: 2,          // => line width in pixels

+        fill: false,           // => true to fill the area from the line to the x axis, false for (transparent) no fill

+        fillColor: null,       // => fill color

+        fillOpacity: 0.4       // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill

+      },

+      radar: {

+        show: false,           // => setting to true will show radar chart, false will hide

+        lineWidth: 2,          // => line width in pixels

+        fill: false,           // => true to fill the area from the line to the x axis, false for (transparent) no fill

+        fillColor: null,       // => fill color

+        fillOpacity: 0.4       // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill

+      },

+      bars: {

+        show: false,           // => setting to true will show bars, false will hide

+        lineWidth: 2,          // => in pixels

+        barWidth: 1,           // => in units of the x axis

+        fill: true,            // => true to fill the area from the line to the x axis, false for (transparent) no fill

+        fillColor: null,       // => fill color

+        fillOpacity: 0.4,      // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill

+        horizontal: false,

+        stacked: false

+      },

+      candles: {

+        show: false,           // => setting to true will show candle sticks, false will hide

+        lineWidth: 1,          // => in pixels

+        wickLineWidth: 1,      // => in pixels

+        candleWidth: 0.6,      // => in units of the x axis

+        fill: true,            // => true to fill the area from the line to the x axis, false for (transparent) no fill

+        upFillColor: '#00A8F0',// => up sticks fill color

+        downFillColor: '#CB4B4B',// => down sticks fill color

+        fillOpacity: 0.5,      // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill

+        barcharts: false       // => draw as barcharts (not standard bars but financial barcharts)

+      },

+      pie: {

+        show: false,           // => setting to true will show bars, false will hide

+        lineWidth: 1,          // => in pixels

+        fill: true,            // => true to fill the area from the line to the x axis, false for (transparent) no fill

+        fillColor: null,       // => fill color

+        fillOpacity: 0.6,      // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill

+        explode: 6,

+        sizeRatio: 0.6,

+        startAngle: Math.PI/4,

+        labelFormatter: Flotr.defaultPieLabelFormatter,

+        pie3D: false,

+        pie3DviewAngle: (Math.PI/2 * 0.8),

+        pie3DspliceThickness: 20

+      },

+      grid: {

+        color: '#545454',      // => primary color used for outline and labels

+        backgroundColor: null, // => null for transparent, else color

+        tickColor: '#DDDDDD',  // => color used for the ticks

+        labelMargin: 3,        // => margin in pixels

+        verticalLines: true,   // => whether to show gridlines in vertical direction

+        horizontalLines: true, // => whether to show gridlines in horizontal direction

+        outlineWidth: 2        // => width of the grid outline/border in pixels

+      },

+      selection: {

+        mode: null,            // => one of null, 'x', 'y' or 'xy'

+        color: '#B6D9FF',      // => selection box color

+        fps: 20                // => frames-per-second

+      },

+      mouse: {

+        track: false,          // => true to track the mouse, no tracking otherwise

+        position: 'se',        // => position of the value box (default south-east)

+        relative: false,       // => next to the mouse cursor

+        trackFormatter: Flotr.defaultTrackFormatter, // => formats the values in the value box

+        margin: 5,             // => margin in pixels of the valuebox

+        lineColor: '#FF3F19',  // => line color of points that are drawn when mouse comes near a value of a series

+        trackDecimals: 1,      // => decimals for the track values

+        sensibility: 2,        // => the lower this number, the more precise you have to aim to show a value

+        radius: 3              // => radius of the track point

+      },

+      radarChartMode: false, // => true to render radar grid / and setup scaling for radar chart

+      shadowSize: 4,           // => size of the 'fake' shadow

+      defaultType: 'lines',    // => default series type

+      HtmlText: true,          // => wether to draw the text using HTML or on the canvas

+      fontSize: 7.5,             // => canvas' text font size

+      spreadsheet: {

+      	show: false,           // => show the data grid using two tabs

+      	tabGraphLabel: 'Graph',

+      	tabDataLabel: 'Data',

+      	toolbarDownload: 'Download CSV', // @todo: add language support

+      	toolbarSelectAll: 'Select all'

+      }

+    }

+    

+    options.x2axis = Object.extend(Object.clone(options.xaxis), options.x2axis);

+    options.y2axis = Object.extend(Object.clone(options.yaxis), options.y2axis);

+    this.options = Flotr.merge((opts || {}), options);

+    

+    this.axes = {

+      x:  {options: this.options.xaxis,  n: 1}, 

+      x2: {options: this.options.x2axis, n: 2}, 

+      y:  {options: this.options.yaxis,  n: 1}, 

+      y2: {options: this.options.y2axis, n: 2}

+    };

+		

+		// Initialize some variables used throughout this function.

+		var assignedColors = [],

+		    colors = [],

+		    ln = this.series.length,

+		    neededColors = this.series.length,

+		    oc = this.options.colors, 

+		    usedColors = [],

+		    variation = 0,

+		    c, i, j, s, tooClose;

+

+		// Collect user-defined colors from series.

+		for(i = neededColors - 1; i > -1; --i){

+			c = this.series[i].color;

+			if(c != null){

+				--neededColors;

+				if(Object.isNumber(c)) assignedColors.push(c);

+				else usedColors.push(Flotr.parseColor(c));

+			}

+		}

+		

+		// Calculate the number of colors that need to be generated.

+		for(i = assignedColors.length - 1; i > -1; --i)

+			neededColors = Math.max(neededColors, assignedColors[i] + 1);

+

+		// Generate needed number of colors.

+		for(i = 0; colors.length < neededColors;){

+			c = (oc.length == i) ? new Flotr.Color(100, 100, 100) : Flotr.parseColor(oc[i]);

+			

+			// Make sure each serie gets a different color.

+			var sign = variation % 2 == 1 ? -1 : 1;

+			var factor = 1 + sign * Math.ceil(variation / 2) * 0.2;

+			c.scale(factor, factor, factor);

+

+			/**

+			 * @todo if we're getting too close to something else, we should probably skip this one

+			 */

+			colors.push(c);

+			

+			if(++i >= oc.length){

+				i = 0;

+				++variation;

+			}

+		}

+	

+		// Fill the options with the generated colors.

+		for(i = 0, j = 0; i < ln; ++i){

+			s = this.series[i];

+

+			// Assign the color.

+			if(s.color == null){

+				s.color = colors[j++].toString();

+			}else if(Object.isNumber(s.color)){

+				s.color = colors[s.color].toString();

+			}

+			

+      if (!s.xaxis) s.xaxis = this.axes.x;

+           if (s.xaxis == 1) s.xaxis = this.axes.x;

+      else if (s.xaxis == 2) s.xaxis = this.axes.x2;

+  

+      if (!s.yaxis) s.yaxis = this.axes.y;

+           if (s.yaxis == 1) s.yaxis = this.axes.y;

+      else if (s.yaxis == 2) s.yaxis = this.axes.y2;

+			

+			// Apply missing options to the series.

+			s.lines   = Object.extend(Object.clone(this.options.lines), s.lines);

+			s.points  = Object.extend(Object.clone(this.options.points), s.points);

+			s.bars    = Object.extend(Object.clone(this.options.bars), s.bars);

+			s.candles = Object.extend(Object.clone(this.options.candles), s.candles);

+			s.pie     = Object.extend(Object.clone(this.options.pie), s.pie);

+			s.radar   = Object.extend(Object.clone(this.options.radar), s.radar);

+			s.mouse   = Object.extend(Object.clone(this.options.mouse), s.mouse);

+			

+			if(s.shadowSize == null) s.shadowSize = this.options.shadowSize;

+		}

+	},

+	/**

+	 * Initializes the canvas and it's overlay canvas element. When the browser is IE, this makes use 

+	 * of excanvas. The overlay canvas is inserted for displaying interactions. After the canvas elements

+	 * are created, the elements are inserted into the container element.

+	 */

+	constructCanvas: function(){

+		var el = this.el,

+			size, c, oc;

+		

+  	this.canvas = el.select('.flotr-canvas')[0];

+		this.overlay = el.select('.flotr-overlay')[0];

+		

+		el.childElements().invoke('remove');

+

+		// For positioning labels and overlay.

+		el.setStyle({position:'relative', cursor:'default'});

+

+		this.canvasWidth = el.getWidth();

+		this.canvasHeight = el.getHeight();

+		size = {'width': this.canvasWidth, 'height': this.canvasHeight};

+

+		if(this.canvasWidth <= 0 || this.canvasHeight <= 0){

+			throw 'Invalid dimensions for plot, width = ' + this.canvasWidth + ', height = ' + this.canvasHeight;

+		}

+

+		// Insert main canvas.

+		if (!this.canvas) {

+			c = this.canvas = new Element('canvas', size);

+			c.className = 'flotr-canvas';

+			c = c.writeAttribute('style', 'position:absolute;left:0px;top:0px;');

+		} else {

+			c = this.canvas.writeAttribute(size);

+		}

+		el.insert(c);

+		

+		if(Prototype.Browser.IE){

+			c = window.G_vmlCanvasManager.initElement(c);

+		}

+		this.ctx = c.getContext('2d');

+    

+		// Insert overlay canvas for interactive features.

+		if (!this.overlay) {

+			oc = this.overlay = new Element('canvas', size);

+			oc.className = 'flotr-overlay';

+			oc = oc.writeAttribute('style', 'position:absolute;left:0px;top:0px;');

+		} else {

+			oc = this.overlay.writeAttribute(size);

+		}

+		el.insert(oc);

+		

+		if(Prototype.Browser.IE){

+			oc = window.G_vmlCanvasManager.initElement(oc);

+		}

+		this.octx = oc.getContext('2d');

+

+		// Enable text functions

+		if (window.CanvasText) {

+		  CanvasText.enable(this.ctx);

+		  CanvasText.enable(this.octx);

+		  this.textEnabled = true;

+		}

+	},

+  getTextDimensions: function(text, canvasStyle, HtmlStyle, className) {

+    if (!text) return {width:0, height:0};

+    

+    if (!this.options.HtmlText && this.textEnabled) {

+      var bounds = this.ctx.getTextBounds(text, canvasStyle);

+      return {

+        width: bounds.width+2, 

+        height: bounds.height+6

+      };

+    }

+    else {

+      var dummyDiv = this.el.insert('<div style="position:absolute;top:-10000px;'+HtmlStyle+'" class="'+className+' flotr-dummy-div">' + text + '</div>').select(".flotr-dummy-div")[0];

+      dim = dummyDiv.getDimensions();

+      dummyDiv.remove();

+      return dim;

+    }

+  },

+	loadDataGrid: function(){

+    if (this.seriesData) return this.seriesData;

+

+		var s = this.series;

+		var dg = [];

+

+    /* The data grid is a 2 dimensions array. There is a row for each X value.

+     * Each row contains the x value and the corresponding y value for each serie ('undefined' if there isn't one)

+    **/

+		for(i = 0; i < s.length; ++i){

+			s[i].data.each(function(v) {

+				var x = v[0],

+				    y = v[1];

+				if (r = dg.find(function(row) {return row[0] == x})) {

+					r[i+1] = y;

+				}

+				else {

+					var newRow = [];

+					newRow[0] = x;

+					newRow[i+1] = y

+					dg.push(newRow);

+				}

+			});

+		}

+		

+    // The data grid is sorted by x value

+		dg = dg.sortBy(function(v) {

+			return v[0];

+		});

+		return this.seriesData = dg;

+	},

+	

+	// @todo: make a tab manager (Flotr.Tabs)

+  showTab: function(tabName, onComplete){

+    var elementsClassNames = 'canvas, .flotr-labels, .flotr-legend, .flotr-legend-bg, .flotr-title, .flotr-subtitle';

+    switch(tabName) {

+      case 'graph':

+        this.datagrid.up().hide();

+        this.el.select(elementsClassNames).invoke('show');

+        this.tabs.data.removeClassName('selected');

+        this.tabs.graph.addClassName('selected');

+      break;

+      case 'data':

+        this.constructDataGrid();

+        this.datagrid.up().show();

+        this.el.select(elementsClassNames).invoke('hide');

+        this.tabs.data.addClassName('selected');

+        this.tabs.graph.removeClassName('selected');

+      break;

+    }

+  },

+  constructTabs: function(){

+    var tabsContainer = new Element('div', {className:'flotr-tabs-group', style:'position:absolute;left:0px;top:'+this.canvasHeight+'px;width:'+this.canvasWidth+'px;'});

+    this.el.insert({bottom: tabsContainer});

+    this.tabs = {

+    	graph: new Element('div', {className:'flotr-tab selected', style:'float:left;'}).update(this.options.spreadsheet.tabGraphLabel),

+    	data: new Element('div', {className:'flotr-tab', style:'float:left;'}).update(this.options.spreadsheet.tabDataLabel)

+    }

+    

+    tabsContainer.insert(this.tabs.graph).insert(this.tabs.data);

+    

+    this.el.setStyle({height: this.canvasHeight+this.tabs.data.getHeight()+2+'px'});

+

+    this.tabs.graph.observe('click', (function() {this.showTab('graph')}).bind(this));

+    this.tabs.data.observe('click', (function() {this.showTab('data')}).bind(this));

+  },

+  

+  // @todo: make a spreadsheet manager (Flotr.Spreadsheet)

+	constructDataGrid: function(){

+    // If the data grid has already been built, nothing to do here

+    if (this.datagrid) return this.datagrid;

+    

+		var i, j, 

+        s = this.series,

+        datagrid = this.loadDataGrid();

+

+		var t = this.datagrid = new Element('table', {className:'flotr-datagrid', style:'height:100px;'});

+		var colgroup = ['<colgroup><col />'];

+		

+		// First row : series' labels

+		var html = ['<tr class="first-row">'];

+		html.push('<th>&nbsp;</th>');

+		for (i = 0; i < s.length; ++i) {

+			html.push('<th scope="col">'+(s[i].label || String.fromCharCode(65+i))+'</th>');

+			colgroup.push('<col />');

+		}

+		html.push('</tr>');

+		

+		// Data rows

+		for (j = 0; j < datagrid.length; ++j) {

+			html.push('<tr>');

+			for (i = 0; i < s.length+1; ++i) {

+        var tag = 'td';

+        var content = (datagrid[j][i] != null ? Math.round(datagrid[j][i]*100000)/100000 : '');

+        

+        if (i == 0) {

+          tag = 'th';

+          var label;

+          if(this.options.xaxis.ticks) {

+            var tick = this.options.xaxis.ticks.find(function (x) { return x[0] == datagrid[j][i] });

+            if (tick) label = tick[1];

+          } 

+          else {

+            label = this.options.xaxis.tickFormatter(content);

+          }

+          

+          if (label) content = label;

+        }

+

+				html.push('<'+tag+(tag=='th'?' scope="row"':'')+'>'+content+'</'+tag+'>');

+			}

+			html.push('</tr>');

+		}

+		colgroup.push('</colgroup>');

+    t.update(colgroup.join('')+html.join(''));

+    

+    if (!Prototype.Browser.IE) {

+      t.select('td').each(function(td) {

+      	td.observe('mouseover', function(e){

+      		td = e.element();

+      		var siblings = td.previousSiblings();

+      		

+      		t.select('th[scope=col]')[siblings.length-1].addClassName('hover');

+      		t.select('colgroup col')[siblings.length].addClassName('hover');

+      	});

+      	

+      	td.observe('mouseout', function(){

+      		t.select('colgroup col.hover, th.hover').each(function(e){e.removeClassName('hover')});

+      	});

+      });

+    }

+    

+		var toolbar = new Element('div', {className: 'flotr-datagrid-toolbar'}).

+	    insert(new Element('button', {type:'button', className:'flotr-datagrid-toolbar-button'}).update(this.options.spreadsheet.toolbarDownload).observe('click', this.downloadCSV.bind(this))).

+	    insert(new Element('button', {type:'button', className:'flotr-datagrid-toolbar-button'}).update(this.options.spreadsheet.toolbarSelectAll).observe('click', this.selectAllData.bind(this)));

+		

+		var container = new Element('div', {className:'flotr-datagrid-container', style:'left:0px;top:0px;width:'+this.canvasWidth+'px;height:'+this.canvasHeight+'px;overflow:auto;'});

+		container.insert(toolbar);

+		t.wrap(container.hide());

+		

+		this.el.insert(container);

+    return t;

+  },

+  selectAllData: function(){

+    if (this.tabs) {

+      var selection, range, doc, win, node = this.constructDataGrid();

+  

+      this.showTab('data');

+      

+      // deferred to be able to select the table

+      (function () {

+        if ((doc = node.ownerDocument) && (win = doc.defaultView) && 

+          win.getSelection && doc.createRange && 

+          (selection = window.getSelection()) && 

+          selection.removeAllRanges) {

+           range = doc.createRange();

+           range.selectNode(node);

+           selection.removeAllRanges();

+           selection.addRange(range);

+        }

+        else if (document.body && document.body.createTextRange && 

+          (range = document.body.createTextRange())) {

+           range.moveToElementText(node);

+           range.select();

+        }

+      }).defer();

+      return true;

+    }

+    else return false;

+  },

+  downloadCSV: function(){

+    var i, csv = '"x"',

+        series = this.series,

+        dg = this.loadDataGrid();

+    

+    for (i = 0; i < series.length; ++i) {

+      csv += '%09"'+(series[i].label || String.fromCharCode(65+i))+'"'; // \t

+    }

+    csv += "%0D%0A"; // \r\n

+    

+    for (i = 0; i < dg.length; ++i) {

+      if (this.options.xaxis.ticks) {

+        var tick = this.options.xaxis.ticks.find(function (x) { return x[0] == dg[i][0] });

+        if (tick) dg[i][0] = tick[1];

+      } else {

+        dg[i][0] = this.options.xaxis.tickFormatter(dg[i][0]);

+      }

+      csv += dg[i].join('%09')+"%0D%0A"; // \t and \r\n

+    }

+    if (Prototype.Browser.IE) {

+      csv = csv.gsub('%09', '\t').gsub('%0A', '\n').gsub('%0D', '\r');

+      window.open().document.write(csv);

+    }

+    else {

+      window.open('data:text/csv,'+csv);

+    }

+  },

+	/**

+	 * Initializes event some handlers.

+	 */

+	initEvents: function () {

+  	//@todo: maybe stopObserving with only flotr functions

+  	this.overlay.stopObserving();

+  	this.overlay.observe('mousedown', this.mouseDownHandler.bind(this));

+		this.overlay.observe('mousemove', this.mouseMoveHandler.bind(this));

+		this.overlay.observe('click', this.clickHandler.bind(this));

+	},

+	/**

+	 * Function determines the min and max values for the xaxis and yaxis.

+	 */

+	findDataRanges: function(){

+		var s = this.series, 

+		    a = this.axes;

+		

+		a.x.datamin = 0;  a.x.datamax  = 0;

+		a.x2.datamin = 0; a.x2.datamax = 0;

+		a.y.datamin = 0;  a.y.datamax  = 0;

+		a.y2.datamin = 0; a.y2.datamax = 0;

+		

+		if(s.length > 0){

+			var i, j, h, x, y, data, xaxis, yaxis;

+		

+			// Get datamin, datamax start values 

+			for(i = 0; i < s.length; ++i) {

+				data = s[i].data, 

+				xaxis = s[i].xaxis, 

+				yaxis = s[i].yaxis;

+				

+				if (data.length > 0 && !s[i].hide) {

+					if (!xaxis.used) xaxis.datamin = xaxis.datamax = data[0][0];

+					if (!yaxis.used) yaxis.datamin = yaxis.datamax = data[0][1];

+					xaxis.used = true;

+					yaxis.used = true;

+

+					for(h = data.length - 1; h > -1; --h){

+  					x = data[h][0];

+  			         if(x < xaxis.datamin) xaxis.datamin = x;

+   					else if(x > xaxis.datamax) xaxis.datamax = x;

+  			    

+  					for(j = 1; j < data[h].length; j++){

+  						y = data[h][j];

+  				         if(y < yaxis.datamin) yaxis.datamin = y;

+  	  				else if(y > yaxis.datamax) yaxis.datamax = y;

+  					}

+					}

+				}

+				if (this.options.radarChartMode) {

+					xaxis.datamin = yaxis.datamin = - yaxis.datamax;

+					xaxis.datamax = yaxis.datamax;

+					if (!this.options.radarChartSides) this.options.radarChartSides = data.length;

+				}

+			}

+		}

+		

+		this.findXAxesValues();

+		

+		this.calculateRange(a.x);

+		this.extendXRangeIfNeededByBar(a.x);

+		

+		if (a.x2.used) {

+			this.calculateRange(a.x2);

+		  this.extendXRangeIfNeededByBar(a.x2);

+		}

+		

+		this.calculateRange(a.y);

+		this.extendYRangeIfNeededByBar(a.y);

+		

+		if (a.y2.used) {

+  		this.calculateRange(a.y2);

+  		this.extendYRangeIfNeededByBar(a.y2);

+		}

+	},

+	/**

+	 * Calculates the range of an axis to apply autoscaling.

+	 */

+	calculateRange: function(axis){

+		var o = axis.options,

+		  min = o.min != null ? o.min : axis.datamin,

+			max = o.max != null ? o.max : axis.datamax,

+			margin;

+

+		if(max - min == 0.0){

+			var widen = (max == 0.0) ? 1.0 : 0.01;

+			min -= widen;

+			max += widen;

+		}

+		axis.tickSize = Flotr.getTickSize(o.noTicks, ((this.options.radarChartMode) ? 0 : min), max, o.tickDecimals);

+

+		// Autoscaling.

+		if(o.min == null){

+			// Add a margin.

+			margin = o.autoscaleMargin;

+			if(margin != 0){

+				min -= axis.tickSize * margin;

+				

+				// Make sure we don't go below zero if all values are positive.

+				if(min < 0 && axis.datamin >= 0) min = 0;

+				min = axis.tickSize * Math.floor(min / axis.tickSize);

+			}

+		}

+		if(o.max == null){

+			margin = o.autoscaleMargin;

+			if(margin != 0){

+				max += axis.tickSize * margin;

+				if(max > 0 && axis.datamax <= 0) max = 0;				

+				max = axis.tickSize * Math.ceil(max / axis.tickSize);

+			}

+		}

+		axis.min = min;

+		axis.max = max;

+	},

+	/**

+	 * Bar series autoscaling in x direction.

+	 */

+	extendXRangeIfNeededByBar: function(axis){

+		if(axis.options.max == null){

+			var newmax = axis.max,

+			    i, s, b, c,

+			    stackedSums = [], 

+			    lastSerie = null;

+

+			for(i = 0; i < this.series.length; ++i){

+				s = this.series[i];

+				b = s.bars;

+				c = s.candles;

+				if(s.axis == axis && (b.show || c.show)) {

+					if (!b.horizontal && (b.barWidth + axis.datamax > newmax) || (c.candleWidth + axis.datamax > newmax)){

+						newmax = axis.max + s.bars.barWidth;

+					}

+					if(b.stacked && b.horizontal){

+						for (j = 0; j < s.data.length; j++) {

+							if (s.bars.show && s.bars.stacked) {

+								var x = s.data[j][0];

+								stackedSums[x] = (stackedSums[x] || 0) + s.data[j][1];

+								lastSerie = s;

+							}

+						}

+				    

+						for (j = 0; j < stackedSums.length; j++) {

+				    	newmax = Math.max(stackedSums[j], newmax);

+						}

+					}

+				}

+			}

+			axis.lastSerie = lastSerie;

+			axis.max = newmax;

+		}

+	},

+	/**

+	 * Bar series autoscaling in y direction.

+	 */

+	extendYRangeIfNeededByBar: function(axis){

+		if(axis.options.max == null){

+			var newmax = axis.max,

+				  i, s, b, c,

+				  stackedSums = [],

+				  lastSerie = null;

+									

+			for(i = 0; i < this.series.length; ++i){

+				s = this.series[i];

+				b = s.bars;

+				c = s.candles;

+				if (s.yaxis == axis && b.show && !s.hide) {

+					if (b.horizontal && (b.barWidth + axis.datamax > newmax) || (c.candleWidth + axis.datamax > newmax)){

+						newmax = axis.max + b.barWidth;

+					}

+					if(b.stacked && !b.horizontal){

+						for (j = 0; j < s.data.length; j++) {

+							if (s.bars.show && s.bars.stacked) {

+								var x = s.data[j][0];

+								stackedSums[x] = (stackedSums[x] || 0) + s.data[j][1];

+								lastSerie = s;

+							}

+						}

+						

+						for (j = 0; j < stackedSums.length; j++) {

+							newmax = Math.max(stackedSums[j], newmax);

+						}

+					}

+				}

+			}

+			axis.lastSerie = lastSerie;

+			axis.max = newmax;

+		}

+	},

+	/** 

+	 * Find every values of the x axes

+	 */

+	findXAxesValues: function(){

+		for(i = this.series.length-1; i > -1 ; --i){

+			s = this.series[i];

+			s.xaxis.values = s.xaxis.values || [];

+			for (j = s.data.length-1; j > -1 ; --j){

+				s.xaxis.values[s.data[j][0]] = {};

+			}

+		}

+	},

+	/**

+	 * Calculate axis ticks.

+	 * @param {Object} axis - axis object

+	 * @param {Object} o - axis options

+	 */

+	calculateTicks: function(axis){

+		var o = axis.options, i, v;

+		

+		axis.ticks = [];	

+		if(o.ticks){

+			var ticks = o.ticks, t, label;

+

+			if(Object.isFunction(ticks)){

+				ticks = ticks({min: axis.min, max: axis.max});

+			}

+			

+			// Clean up the user-supplied ticks, copy them over.

+			for(i = 0; i < ticks.length; ++i){

+				t = ticks[i];

+				if(typeof(t) == 'object'){

+					v = t[0];

+					label = (t.length > 1) ? t[1] : o.tickFormatter(v);

+				}else{

+					v = t;

+					label = o.tickFormatter(v);

+				}

+				axis.ticks[i] = { v: v, label: label };

+			}

+		}

+    else {

+			// Round to nearest multiple of tick size.

+			var start = axis.tickSize * Math.ceil(axis.min / axis.tickSize),

+				  decimals;

+			

+			// Then store all possible ticks.

+			for(i = 0; start + i * axis.tickSize <= axis.max; ++i){

+				v = start + i * axis.tickSize;

+				

+				// Round (this is always needed to fix numerical instability).

+				decimals = o.tickDecimals;

+				if(decimals == null) decimals = 1 - Math.floor(Math.log(axis.tickSize) / Math.LN10);

+				if(decimals < 0) decimals = 0;

+				

+				v = v.toFixed(decimals);

+				axis.ticks.push({ v: v, label: o.tickFormatter(v) });

+			}

+		}

+	},

+	/**

+	 * Calculates axis label sizes.

+	 */

+	calculateSpacing: function(){

+		var a = this.axes,

+  			options = this.options,

+  			series = this.series,

+  			margin = options.grid.labelMargin,

+  			x = a.x,

+  			x2 = a.x2,

+  			y = a.y,

+  			y2 = a.y2,

+  			maxOutset = 2,

+  			i, j, l, dim;

+		

+		// Labels width and height

+		[x, x2, y, y2].each(function(axis) {

+			var maxLabel = '';

+			

+		  if (axis.options.showLabels) {

+				for(i = 0; i < axis.ticks.length; ++i){

+					l = axis.ticks[i].label.length;

+					if(l > maxLabel.length){

+						maxLabel = axis.ticks[i].label;

+					}

+				}

+	    }

+		  axis.maxLabel  = this.getTextDimensions(maxLabel, {size:options.fontSize, angle: Flotr.toRad(axis.options.labelsAngle)}, 'font-size:smaller;', 'flotr-grid-label');

+		  axis.titleSize = this.getTextDimensions(axis.options.title, {size: options.fontSize*1.2, angle: Flotr.toRad(axis.options.titleAngle)}, 'font-weight:bold;', 'flotr-axis-title');

+		}, this);

+

+    // Title height

+    dim = this.getTextDimensions(options.title, {size: options.fontSize*1.5}, 'font-size:1em;font-weight:bold;', 'flotr-title');

+    this.titleHeight = dim.height;

+    

+    // Subtitle height

+    dim = this.getTextDimensions(options.subtitle, {size: options.fontSize}, 'font-size:smaller;', 'flotr-subtitle');

+    this.subtitleHeight = dim.height;

+

+		// Grid outline line width.

+		if(options.show){

+			maxOutset = Math.max(maxOutset, options.points.radius + options.points.lineWidth/2);

+		}

+		for(j = 0; j < options.length; ++j){

+			if (series[j].points.show){

+				maxOutset = Math.max(maxOutset, series[j].points.radius + series[j].points.lineWidth/2);

+			}

+		}

+		

+		var p = this.plotOffset = {left: 0, right: 0, top: 0, bottom: 0};

+		p.left = p.right = p.top = p.bottom = maxOutset;

+		

+		p.bottom += (x.options.showLabels ?  (x.maxLabel.height  + margin) : 0) + 

+		            (x.options.title ?       (x.titleSize.height + margin) : 0);

+		

+    p.top    += (x2.options.showLabels ? (x2.maxLabel.height  + margin) : 0) + 

+                (x2.options.title ?      (x2.titleSize.height + margin) : 0) + this.subtitleHeight + this.titleHeight + 

+		this.options.radarChartMode ? (y.options.showLabels ?  (y.maxLabel.height  + margin) : 0) : 0;

+    

+		p.left   += (y.options.showLabels ?  (y.maxLabel.width  + margin) : 0) + 

+                (y.options.title ?       (y.titleSize.width + margin) : 0);

+		

+		p.right  += (y2.options.showLabels ? (y2.maxLabel.width  + margin) : 0) + 

+                (y2.options.title ?      (y2.titleSize.width + margin) : 0) + 

+		this.options.radarChartMode ? (x.options.showLabels ?  (x.maxLabel.width  + margin) : 0) : 0;

+    

+    p.top = Math.floor(p.top); // In order the outline not to be blured

+    

+		this.plotWidth  = this.canvasWidth - p.left - p.right;

+		this.plotHeight = this.canvasHeight - p.bottom - p.top;

+		

+		x.scale  = this.plotWidth / (x.max - x.min);

+		x2.scale = this.plotWidth / (x2.max - x2.min);

+		y.scale  = this.plotHeight / (y.max - y.min);

+		y2.scale = this.plotHeight / (y2.max - y2.min);

+	},

+	/**

+	 * Draws grid, labels and series.

+	 */

+	draw: function() {

+		this.drawGrid();

+		this.drawLabels();

+    this.drawTitles();

+    

+		if(this.series.length){

+			this.el.fire('flotr:beforedraw', [this.series, this]);

+			for(var i = 0; i < this.series.length; i++){

+				if (!this.series[i].hide)

+					this.drawSeries(this.series[i]);

+			}

+		}

+		this.el.fire('flotr:afterdraw', [this.series, this]);

+	},

+	/**

+	 * Translates absolute horizontal x coordinates to relative coordinates.

+	 * @param {Integer} x - absolute integer x coordinate

+	 * @return {Integer} translated relative x coordinate

+	 */

+	tHoz: function(x, axis){

+		axis = axis || this.axes.x;

+		return (x - axis.min) * axis.scale;

+	},

+	/**

+	 * Translates absolute vertical x coordinates to relative coordinates.

+	 * @param {Integer} y - absolute integer y coordinate

+	 * @return {Integer} translated relative y coordinate

+	 */

+	tVert: function(y, axis){

+		axis = axis || this.axes.y;

+		return this.plotHeight - (y - axis.min) * axis.scale;

+	},

+	/**

+	 * Draws a grid for the graph.

+	 */

+	drawGrid: function(){

+		if (this.options.radarChartMode) { // If we are in radar chart mode call drawRadarGrid instead and exit

+			this.drawRadarGrid();

+			return;

+		}

+		var v, o = this.options,

+		    ctx = this.ctx;

+		if(o.grid.verticalLines || o.grid.horizontalLines){

+			this.el.fire('flotr:beforegrid', [this.axes.x, this.axes.y, o, this]);

+		}

+		ctx.save();

+		ctx.translate(this.plotOffset.left, this.plotOffset.top);

+

+		// Draw grid background, if present in options.

+		if(o.grid.backgroundColor != null){

+			ctx.fillStyle = o.grid.backgroundColor;

+			ctx.fillRect(0, 0, this.plotWidth, this.plotHeight);

+		}

+		

+		// Draw grid lines in vertical direction.

+		ctx.lineWidth = 1;

+		ctx.strokeStyle = o.grid.tickColor;

+		ctx.beginPath();

+		if(o.grid.verticalLines){

+			for(var i = 0; i < this.axes.x.ticks.length; ++i){

+				v = this.axes.x.ticks[i].v;

+				// Don't show lines on upper and lower bounds.

+				if ((v == this.axes.x.min || v == this.axes.x.max) && o.grid.outlineWidth != 0)

+					continue;

+	

+				ctx.moveTo(Math.floor(this.tHoz(v)) + ctx.lineWidth/2, 0);

+				ctx.lineTo(Math.floor(this.tHoz(v)) + ctx.lineWidth/2, this.plotHeight);

+			}

+		}

+		

+		// Draw grid lines in horizontal direction.

+		if(o.grid.horizontalLines){

+			for(var j = 0; j < this.axes.y.ticks.length; ++j){

+				v = this.axes.y.ticks[j].v;

+				// Don't show lines on upper and lower bounds.

+				if ((v == this.axes.y.min || v == this.axes.y.max) && o.grid.outlineWidth != 0)

+					continue;

+	

+				ctx.moveTo(0, Math.floor(this.tVert(v)) + ctx.lineWidth/2);

+				ctx.lineTo(this.plotWidth, Math.floor(this.tVert(v)) + ctx.lineWidth/2);

+			}

+		}

+		ctx.stroke();

+		

+		// Draw axis/grid border.

+		if(o.grid.outlineWidth != 0) {

+			ctx.lineWidth = o.grid.outlineWidth;

+			ctx.strokeStyle = o.grid.color;

+			ctx.lineJoin = 'round';

+			ctx.strokeRect(0, 0, this.plotWidth, this.plotHeight);

+		}

+		ctx.restore();

+		if(o.grid.verticalLines || o.grid.horizontalLines){

+			this.el.fire('flotr:aftergrid', [this.axes.x, this.axes.y, o, this]);

+		}

+	},

+	/**

+	 * Draws a grid for the graph.

+	 */

+	drawRadarGrid: function(){

+		

+		var v, o = this.options,

+		    ctx = this.ctx;

+		    

+		var sides = this.options.radarChartSides,

+		    degreesInRadiansForAngle = Math.PI * 2 / sides,

+		    nintyDegrees = Math.PI / 2;

+		

+		if(o.grid.verticalLines || o.grid.horizontalLines){

+			this.el.fire('flotr:beforegrid', [this.axes.x, this.axes.y, o, this]);

+		}

+		ctx.save();

+		ctx.translate(this.plotOffset.left, this.plotOffset.top);

+		ctx.lineJoin = 'round';

+

+		// Draw grid background, if present in options.

+		if(o.grid.backgroundColor != null){

+			ctx.fillStyle = o.grid.backgroundColor;

+			ctx.fillRect(0, 0, this.plotWidth, this.plotHeight);

+		}

+		

+		// Draw grid lines

+		var regPoly = {};

+		regPoly.xaxis = {};

+		regPoly.yaxis = {};

+		regPoly.xaxis.min = regPoly.yaxis.min = this.axes.x.min;

+		regPoly.xaxis.max = regPoly.yaxis.max = this.axes.x.max;

+		regPoly.xaxis.scale = this.plotWidth / (this.axes.x.max - this.axes.x.min);

+		regPoly.yaxis.scale = this.plotHeight / (this.axes.x.max - this.axes.x.min);

+		

+		ctx.lineWidth = 1;

+		ctx.strokeStyle = o.grid.tickColor;

+		

+		if(o.grid.horizontalLines){

+			for(var j = 0; j < this.axes.y.ticks.length; ++j){

+				v = this.axes.y.ticks[j].v;

+				if (v < 0) continue;

+				// Don't show lines on upper and lower bounds.

+				if ((v == this.axes.y.min || v == this.axes.y.max) && o.grid.outlineWidth != 0)

+					continue;

+				regPoly.data = new Array();

+				for (i = 0; i < sides; i++) {

+					angle = nintyDegrees + (degreesInRadiansForAngle * i);

+					regPoly.data[i] = [v * Math.cos(angle), v * Math.sin(angle)]

+				}

+				regPoly.data[sides] = regPoly.data[0];

+				this.plotLine(regPoly,0);

+			}

+		}

+		

+		// Draw axis/grid border.

+		if(o.grid.outlineWidth != 0) {

+			ctx.lineWidth = o.grid.outlineWidth;

+			ctx.strokeStyle = o.grid.color;

+			regPoly.data = new Array();

+			var radius = this.axes.x.max;

+			for (i = 0; i < sides; i++) {

+				angle = nintyDegrees + (degreesInRadiansForAngle * i);

+				regPoly.data[i] = [radius * Math.cos(angle), radius * Math.sin(angle)]

+				}

+				regPoly.data[sides] = regPoly.data[0];

+				this.plotLine(regPoly,0);

+		}

+		

+		ctx.lineWidth = 1;

+		ctx.strokeStyle = o.grid.tickColor;

+		ctx.beginPath();

+		

+		if(o.grid.verticalLines){

+			for(var i = 0; i < sides; ++i){

+				ctx.moveTo(Math.floor(this.tHoz(0)) + ctx.lineWidth/2, 

+						Math.floor(this.tVert(0)) + ctx.lineWidth/2);

+				ctx.lineTo(Math.floor(this.tHoz(regPoly.data[i][0])) + ctx.lineWidth/2, 

+						Math.floor(this.tVert(regPoly.data[i][1])) + ctx.lineWidth/2);

+			}

+		}

+		

+		ctx.stroke();

+		

+		ctx.restore();

+		if(o.grid.verticalLines || o.grid.horizontalLines){

+			this.el.fire('flotr:aftergrid', [this.axes.x, this.axes.y, o, this]);

+		}

+	},

+	/**

+	* Draws labels aroung radar chart

+	*/

+	drawRadarLabels:function(){

+		var ctx = this.ctx,

+			options = this.options,

+			axis = this.axes.x,

+			tick, minY = 0, maxY = 0,

+			xOffset, yOffset;

+		var style = {

+		    size: options.fontSize,

+		    adjustAlign: true

+		  };

+		style.color = axis.options.color || options.grid.color;

+		style.angle = Flotr.toRad(axis.options.labelsAngle);

+		var radius = axis.max * 1,

+		      closeTo = axis.max * 0.1,

+		      sides = this.options.radarChartSides,

+		      degreesInRadiansForAngle = Math.PI * 2 / sides,

+		      nintyDegrees = Math.PI / 2,

+		      posdata = new Array();

+		for (i = 0; i < sides; i++) {

+				angle = nintyDegrees + (degreesInRadiansForAngle * i);

+				posdata[i] = [radius * Math.cos(angle), radius * Math.sin(angle)];

+				if (minY > posdata[i][1]) minY = posdata[i][1];

+				if (maxY < posdata[i][1]) maxY = posdata[i][1];

+				}

+		for (i = 0; i < sides; i++) {

+				tick = axis.ticks[i];

+				if(!tick.label || tick.label.length == 0) continue;

+				yOffset = 0;

+				if (posdata[i][0] > 0) {

+					style.halign = 'l';

+					xOffset = options.grid.labelMargin;

+				} else {

+					style.halign = 'r';

+					xOffset = - options.grid.labelMargin;

+				}

+				style.valign = 'm';

+				

+				if ((posdata[i][1] + closeTo) >= minY && (posdata[i][1] - closeTo) <= minY) {

+					style.valign = 't' ; 

+					style.halign = 'c';

+					yOffset = options.grid.labelMargin; 

+				};

+				if (posdata[i][1] == maxY) {

+					style.valign = 'b' ; 

+					style.halign = 'c';

+					yOffset = - options.grid.labelMargin; 

+				}

+				ctx.drawText(

+					tick.label,

+					this.plotOffset.left + this.tHoz(posdata[i][0]) + xOffset, 

+					this.plotOffset.top + this.tVert(posdata[i][1]) + yOffset,

+					style

+				);

+				}

+		

+	},

+	/**

+	 * Draws labels for x and y axis.

+	 */   

+	drawLabels: function(){		

+		// Construct fixed width label boxes, which can be styled easily. 

+		var noLabels = 0, axis,

+			xBoxWidth, i, html, tick,

+			options = this.options,

+      ctx = this.ctx,

+      a = this.axes;

+		

+		for(i = 0; i < a.x.ticks.length; ++i){

+			if (a.x.ticks[i].label) {

+				++noLabels;

+			}

+		}

+		xBoxWidth = this.plotWidth / noLabels;

+    

+		if (!options.HtmlText && this.textEnabled) {

+		  var style = {

+		    size: options.fontSize,

+        adjustAlign: true

+		  };

+

+		  // Add x labels.

+		  if (options.radarChartMode) {

+			this.drawRadarLabels();} else {

+		  axis = a.x;

+		  style.color = axis.options.color || options.grid.color;

+		  for(i = 0; i < axis.ticks.length && axis.options.showLabels && axis.used; ++i){

+		    tick = axis.ticks[i];

+		    if(!tick.label || tick.label.length == 0) continue;

+        

+        style.angle = Flotr.toRad(axis.options.labelsAngle);

+        style.halign = 'c';

+        style.valign = 't';

+        

+		    ctx.drawText(

+		      tick.label,

+		      this.plotOffset.left + this.tHoz(tick.v, axis), 

+		      this.plotOffset.top + this.plotHeight + options.grid.labelMargin,

+		      style

+		    );

+		  }}

+		  

+		  // Add x2 labels.

+		  axis = a.x2;

+		  style.color = axis.options.color || options.grid.color;

+		  for(i = 0; i < axis.ticks.length && axis.options.showLabels && axis.used; ++i){

+		    tick = axis.ticks[i];

+		    if(!tick.label || tick.label.length == 0) continue;

+        

+        style.angle = Flotr.toRad(axis.options.labelsAngle);

+        style.halign = 'c';

+        style.valign = 'b';

+        

+		    ctx.drawText(

+		      tick.label,

+		      this.plotOffset.left + this.tHoz(tick.v, axis), 

+		      this.plotOffset.top + options.grid.labelMargin,

+		      style

+		    );

+		  }

+		  

+		  // Add y labels.

+		  axis = a.y;

+		  style.color = axis.options.color || options.grid.color;

+		  for(i = 0; i < axis.ticks.length && axis.options.showLabels && axis.used; ++i){

+		    tick = axis.ticks[i];

+		    if (!tick.label || tick.label.length == 0 || (tick.v < 0 && this.options.radarChartMode)) continue;

+        

+        style.angle = Flotr.toRad(axis.options.labelsAngle);

+        style.halign = 'r';

+        style.valign = 'm';

+        

+		    ctx.drawText(

+		      tick.label,

+		      this.plotOffset.left + (this.options.radarChartMode ? this.tHoz(0) : 0) - options.grid.labelMargin, 

+		      this.plotOffset.top + this.tVert(tick.v, axis),

+		      style

+		    );

+		  }

+		  

+		  // Add y2 labels.

+		  axis = a.y2;

+		  style.color = axis.options.color || options.grid.color;

+		  for(i = 0; i < axis.ticks.length && axis.options.showLabels && axis.used; ++i){

+		    tick = axis.ticks[i];

+		    if (!tick.label || tick.label.length == 0) continue;

+        

+        style.angle = Flotr.toRad(axis.options.labelsAngle);

+        style.halign = 'l';

+        style.valign = 'm';

+        

+		    ctx.drawText(

+		      tick.label,

+		      this.plotOffset.left + this.plotWidth + options.grid.labelMargin, 

+		      this.plotOffset.top + this.tVert(tick.v, axis),

+		      style

+		    );

+		    

+				ctx.save();

+				ctx.strokeStyle = style.color;

+				ctx.beginPath();

+				ctx.moveTo(this.plotOffset.left + this.plotWidth - 8, this.plotOffset.top + this.tVert(tick.v, axis));

+				ctx.lineTo(this.plotOffset.left + this.plotWidth,     this.plotOffset.top + this.tVert(tick.v, axis));

+				ctx.stroke();

+				ctx.restore();

+		  }

+		} 

+		else if (a.x.options.showLabels || 

+				     a.x2.options.showLabels || 

+				     a.y.options.showLabels || 

+				     a.y2.options.showLabels) {

+			html = ['<div style="font-size:smaller;color:' + options.grid.color + ';" class="flotr-labels">'];

+			

+			// Add x labels.

+			axis = a.x;

+			if (axis.options.showLabels){

+				for(i = 0; i < axis.ticks.length; ++i){

+					tick = axis.ticks[i];

+					if(!tick.label || tick.label.length == 0) continue;

+					html.push('<div style="position:absolute;top:' + (this.plotOffset.top + this.plotHeight + options.grid.labelMargin) + 'px;left:' + (this.plotOffset.left + this.tHoz(tick.v, axis) - xBoxWidth/2) + 'px;width:' + xBoxWidth + 'px;text-align:center;'+(axis.options.color?('color:'+axis.options.color+';'):'')+'" class="flotr-grid-label">' + tick.label + '</div>');

+				}

+			}

+			

+			// Add x2 labels.

+			axis = a.x2;

+			if (axis.options.showLabels && axis.used){

+				for(i = 0; i < axis.ticks.length; ++i){

+					tick = axis.ticks[i];

+					if(!tick.label || tick.label.length == 0) continue;

+					html.push('<div style="position:absolute;top:' + (this.plotOffset.top - options.grid.labelMargin - axis.maxLabel.height) + 'px;left:' + (this.plotOffset.left + this.tHoz(tick.v, axis) - xBoxWidth/2) + 'px;width:' + xBoxWidth + 'px;text-align:center;'+(axis.options.color?('color:'+axis.options.color+';'):'')+'" class="flotr-grid-label">' + tick.label + '</div>');

+				}

+			}

+			

+			// Add y labels.

+			axis = a.y;

+			if (axis.options.showLabels){

+				for(i = 0; i < axis.ticks.length; ++i){

+					tick = axis.ticks[i];

+					if (!tick.label || tick.label.length == 0) continue;

+					html.push('<div style="position:absolute;top:' + (this.plotOffset.top + this.tVert(tick.v, axis) - axis.maxLabel.height/2) + 'px;left:0;width:' + (this.plotOffset.left - options.grid.labelMargin) + 'px;text-align:right;'+(axis.options.color?('color:'+axis.options.color+';'):'')+'" class="flotr-grid-label">' + tick.label + '</div>');

+				}

+			}

+			

+			// Add y2 labels.

+			axis = a.y2;

+			if (axis.options.showLabels && axis.used){

+				ctx.save();

+				ctx.strokeStyle = axis.options.color || options.grid.color;

+				ctx.beginPath();

+				

+				for(i = 0; i < axis.ticks.length; ++i){

+					tick = axis.ticks[i];

+					if (!tick.label || tick.label.length == 0) continue;

+					html.push('<div style="position:absolute;top:' + (this.plotOffset.top + this.tVert(tick.v, axis) - axis.maxLabel.height/2) + 'px;right:0;width:' + (this.plotOffset.right - options.grid.labelMargin) + 'px;text-align:left;'+(axis.options.color?('color:'+axis.options.color+';'):'')+'" class="flotr-grid-label">' + tick.label + '</div>');

+

+					ctx.moveTo(this.plotOffset.left + this.plotWidth - 8, this.plotOffset.top + this.tVert(tick.v, axis));

+					ctx.lineTo(this.plotOffset.left + this.plotWidth,     this.plotOffset.top + this.tVert(tick.v, axis));

+				}

+				ctx.stroke();

+				ctx.restore();

+			}

+			

+			html.push('</div>');

+			this.el.insert(html.join(''));

+		}

+	},

+  /**

+   * Draws the title and the subtitle

+   */   

+  drawTitles: function(){

+    var html,

+        options = this.options,

+        margin = options.grid.labelMargin,

+        ctx = this.ctx,

+        a = this.axes;

+      

+    if (!options.HtmlText && this.textEnabled) {

+      var style = {

+        size: options.fontSize,

+        color: options.grid.color,

+        halign: 'c'

+      };

+

+      // Add subtitle

+      if (options.subtitle){

+        ctx.drawText(

+          options.subtitle,

+          this.plotOffset.left + this.plotWidth/2, 

+          this.titleHeight + this.subtitleHeight - 2,

+          style

+        );

+      }

+      

+			style.weight = 1.5;

+      style.size *= 1.5;

+      

+      // Add title

+      if (options.title){

+        ctx.drawText(

+          options.title,

+          this.plotOffset.left + this.plotWidth/2, 

+          this.titleHeight - 2,

+          style

+        );

+      }

+      

+      style.weight = 1.8;

+      style.size *= 0.8;

+      style.adjustAlign = true;

+      

+			// Add x axis title

+			if (a.x.options.title && a.x.used){

+				style.halign = 'c';

+				style.valign = 't';

+				style.angle = Flotr.toRad(a.x.options.titleAngle);

+        ctx.drawText(

+          a.x.options.title,

+          this.plotOffset.left + this.plotWidth/2, 

+          this.plotOffset.top + a.x.maxLabel.height + this.plotHeight + 2 * margin,

+          style

+        );

+      }

+			

+			// Add x2 axis title

+			if (a.x2.options.title && a.x2.used){

+				style.halign = 'c';

+				style.valign = 'b';

+				style.angle = Flotr.toRad(a.x2.options.titleAngle);

+        ctx.drawText(

+          a.x2.options.title,

+          this.plotOffset.left + this.plotWidth/2, 

+          this.plotOffset.top - a.x2.maxLabel.height - 2 * margin,

+          style

+        );

+      }

+			

+			// Add y axis title

+			if (a.y.options.title && a.y.used){

+				style.halign = 'r';

+				style.valign = 'm';

+				style.angle = Flotr.toRad(a.y.options.titleAngle);

+        ctx.drawText(

+          a.y.options.title,

+          this.plotOffset.left - a.y.maxLabel.width - 2 * margin, 

+          this.plotOffset.top + this.plotHeight / 2,

+          style

+        );

+      }

+			

+			// Add y2 axis title

+			if (a.y2.options.title && a.y2.used){

+				style.halign = 'l';

+				style.valign = 'm';

+				style.angle = Flotr.toRad(a.y2.options.titleAngle);

+        ctx.drawText(

+          a.y2.options.title,

+          this.plotOffset.left + this.plotWidth + a.y2.maxLabel.width + 2 * margin, 

+          this.plotOffset.top + this.plotHeight / 2,

+          style

+        );

+      }

+    } 

+    else {

+      html = ['<div style="color:'+options.grid.color+';" class="flotr-titles">'];

+      

+      // Add title

+      if (options.title){

+        html.push('<div style="position:absolute;top:0;left:'+this.plotOffset.left+'px;font-size:1em;font-weight:bold;text-align:center;width:'+this.plotWidth+'px;" class="flotr-title">'+options.title+'</div>');

+      }

+      

+      // Add subtitle

+      if (options.subtitle){

+        html.push('<div style="position:absolute;top:'+this.titleHeight+'px;left:'+this.plotOffset.left+'px;font-size:smaller;text-align:center;width:'+this.plotWidth+'px;" class="flotr-subtitle">'+options.subtitle+'</div>');

+      }

+      html.push('</div>');

+      

+      

+      html.push('<div class="flotr-axis-title" style="font-weight:bold;">');

+			// Add x axis title

+			if (a.x.options.title && a.x.used){

+				html.push('<div style="position:absolute;top:' + (this.plotOffset.top + this.plotHeight + options.grid.labelMargin + a.x.titleSize.height) + 'px;left:' + this.plotOffset.left + 'px;width:' + this.plotWidth + 'px;text-align:center;" class="flotr-axis-title">' + a.x.options.title + '</div>');

+			}

+			

+			// Add x2 axis title

+			if (a.x2.options.title && a.x2.used){

+				html.push('<div style="position:absolute;top:0;left:' + this.plotOffset.left + 'px;width:' + this.plotWidth + 'px;text-align:center;" class="flotr-axis-title">' + a.x2.options.title + '</div>');

+			}

+			

+			// Add y axis title

+			if (a.y.options.title && a.y.used){

+				html.push('<div style="position:absolute;top:' + (this.plotOffset.top + this.plotHeight/2 - a.y.titleSize.height/2) + 'px;left:0;text-align:right;" class="flotr-axis-title">' + a.y.options.title + '</div>');

+			}

+			

+			// Add y2 axis title

+			if (a.y2.options.title && a.y2.used){

+				html.push('<div style="position:absolute;top:' + (this.plotOffset.top + this.plotHeight/2 - a.y.titleSize.height/2) + 'px;right:0;text-align:right;" class="flotr-axis-title">' + a.y2.options.title + '</div>');

+			}

+			html.push('</div>');

+      

+      this.el.insert(html.join(''));

+    }

+  },

+	/**

+	 * Actually draws the graph.

+	 * @param {Object} series - series to draw

+	 */

+	drawSeries: function(series){

+		series = series || this.series;

+		

+		var drawn = false;

+		for(var type in Flotr._registeredTypes){

+			if(series[type] && series[type].show){

+				this[Flotr._registeredTypes[type]](series);

+				drawn = true;

+			}

+		}

+		

+		if(!drawn){

+			this[Flotr._registeredTypes[this.options.defaultType]](series);

+		}

+	},

+	

+	plotLine: function(series, offset){

+		var ctx = this.ctx,

+		    xa = series.xaxis,

+		    ya = series.yaxis,

+  			tHoz = this.tHoz.bind(this),

+  			tVert = this.tVert.bind(this),

+  			data = series.data;

+			

+		if(data.length < 2) return;

+

+		var prevx = tHoz(data[0][0], xa),

+		    prevy = tVert(data[0][1], ya) + offset;

+		ctx.beginPath();

+		ctx.moveTo(prevx, prevy);

+		for(var i = 0; i < data.length - 1; ++i){

+			var x1 = data[i][0],   y1 = data[i][1],

+			    x2 = data[i+1][0], y2 = data[i+1][1];

+

+      // To allow empty values

+      if (y1 === null || y2 === null) continue;

+      

+			/**

+			 * Clip with ymin.

+			 */

+			if(y1 <= y2 && y1 < ya.min){

+				/**

+				 * Line segment is outside the drawing area.

+				 */

+				if(y2 < ya.min) continue;

+				

+				/**

+				 * Compute new intersection point.

+				 */

+				x1 = (ya.min - y1) / (y2 - y1) * (x2 - x1) + x1;

+				y1 = ya.min;

+			}else if(y2 <= y1 && y2 < ya.min){

+				if(y1 < ya.min) continue;

+				x2 = (ya.min - y1) / (y2 - y1) * (x2 - x1) + x1;

+				y2 = ya.min;

+			}

+

+			/**

+			 * Clip with ymax.

+			 */ 

+			if(y1 >= y2 && y1 > ya.max) {

+				if(y2 > ya.max) continue;

+				x1 = (ya.max - y1) / (y2 - y1) * (x2 - x1) + x1;

+				y1 = ya.max;

+			}

+			else if(y2 >= y1 && y2 > ya.max){

+				if(y1 > ya.max) continue;

+				x2 = (ya.max - y1) / (y2 - y1) * (x2 - x1) + x1;

+				y2 = ya.max;

+			}

+

+			/**

+			 * Clip with xmin.

+			 */

+			if(x1 <= x2 && x1 < xa.min){

+				if(x2 < xa.min) continue;

+				y1 = (xa.min - x1) / (x2 - x1) * (y2 - y1) + y1;

+				x1 = xa.min;

+			}else if(x2 <= x1 && x2 < xa.min){

+				if(x1 < xa.min) continue;

+				y2 = (xa.min - x1) / (x2 - x1) * (y2 - y1) + y1;

+				x2 = xa.min;

+			}

+

+			/**

+			 * Clip with xmax.

+			 */

+			if(x1 >= x2 && x1 > xa.max){

+				if (x2 > xa.max) continue;

+				y1 = (xa.max - x1) / (x2 - x1) * (y2 - y1) + y1;

+				x1 = xa.max;

+			}else if(x2 >= x1 && x2 > xa.max){

+				if(x1 > xa.max) continue;

+				y2 = (xa.max - x1) / (x2 - x1) * (y2 - y1) + y1;

+				x2 = xa.max;

+			}

+

+			if(prevx != tHoz(x1, xa) || prevy != tVert(y1, ya) + offset)

+				ctx.moveTo(tHoz(x1, xa), tVert(y1, ya) + offset);

+			

+			prevx = tHoz(x2, xa);

+			prevy = tVert(y2, ya) + offset;

+			ctx.lineTo(prevx, prevy);

+		}

+		ctx.stroke();

+	},

+	/**

+	 * Function used to fill

+	 * @param {Object} data

+	 */

+	plotLineArea: function(series, offset){

+		var data = series.data;

+		if(data.length < 2) return;

+

+		var top, lastX = 0,

+			ctx = this.ctx,

+	    xa = series.xaxis,

+	    ya = series.yaxis,

+			tHoz = this.tHoz.bind(this),

+			tVert = this.tVert.bind(this),

+			bottom = Math.min(Math.max(0, ya.min), ya.max),

+			first = true;

+		

+		ctx.beginPath();

+		for(var i = 0; i < data.length - 1; ++i){

+			

+			var x1 = data[i][0], y1 = data[i][1],

+			    x2 = data[i+1][0], y2 = data[i+1][1];

+			

+			if(x1 <= x2 && x1 < xa.min){

+				if(x2 < xa.min) continue;

+				y1 = (xa.min - x1) / (x2 - x1) * (y2 - y1) + y1;

+				x1 = xa.min;

+			}else if(x2 <= x1 && x2 < xa.min){

+				if(x1 < xa.min) continue;

+				y2 = (xa.min - x1) / (x2 - x1) * (y2 - y1) + y1;

+				x2 = xa.min;

+			}

+								

+			if(x1 >= x2 && x1 > xa.max){

+				if(x2 > xa.max) continue;

+				y1 = (xa.max - x1) / (x2 - x1) * (y2 - y1) + y1;

+				x1 = xa.max;

+			}else if(x2 >= x1 && x2 > xa.max){

+				if (x1 > xa.max) continue;

+				y2 = (xa.max - x1) / (x2 - x1) * (y2 - y1) + y1;

+				x2 = xa.max;

+			}

+

+			if(first){

+				ctx.moveTo(tHoz(x1, xa), tVert(bottom, ya) + offset);

+				first = false;

+			}

+			

+			/**

+			 * Now check the case where both is outside.

+			 */

+			if(y1 >= ya.max && y2 >= ya.max){

+				ctx.lineTo(tHoz(x1, xa), tVert(ya.max, ya) + offset);

+				ctx.lineTo(tHoz(x2, xa), tVert(ya.max, ya) + offset);

+				continue;

+			}else if(y1 <= ya.min && y2 <= ya.min){

+				ctx.lineTo(tHoz(x1, xa), tVert(ya.min, ya) + offset);

+				ctx.lineTo(tHoz(x2, xa), tVert(ya.min, ya) + offset);

+				continue;

+			}

+			

+			/**

+			 * Else it's a bit more complicated, there might

+			 * be two rectangles and two triangles we need to fill

+			 * in; to find these keep track of the current x values.

+			 */

+			var x1old = x1, x2old = x2;

+			

+			/**

+			 * And clip the y values, without shortcutting.

+			 * Clip with ymin.

+			 */

+			if(y1 <= y2 && y1 < ya.min && y2 >= ya.min){

+				x1 = (ya.min - y1) / (y2 - y1) * (x2 - x1) + x1;

+				y1 = ya.min;

+			}else if(y2 <= y1 && y2 < ya.min && y1 >= ya.min){

+				x2 = (ya.min - y1) / (y2 - y1) * (x2 - x1) + x1;

+				y2 = ya.min;

+			}

+

+			/**

+			 * Clip with ymax.

+			 */

+			if(y1 >= y2 && y1 > ya.max && y2 <= ya.max){

+				x1 = (ya.max - y1) / (y2 - y1) * (x2 - x1) + x1;

+				y1 = ya.max;

+			}else if(y2 >= y1 && y2 > ya.max && y1 <= ya.max){

+				x2 = (ya.max - y1) / (y2 - y1) * (x2 - x1) + x1;

+				y2 = ya.max;

+			}

+

+			/**

+			 * If the x value was changed we got a rectangle to fill.

+			 */

+			if(x1 != x1old){

+				top = (y1 <= ya.min) ? top = ya.min : ya.max;

+				ctx.lineTo(tHoz(x1old, xa), tVert(top, ya) + offset);

+				ctx.lineTo(tHoz(x1, xa), tVert(top, ya) + offset);

+			}

+		   	

+			/**

+			 * Fill the triangles.

+			 */

+			ctx.lineTo(tHoz(x1, xa), tVert(y1, ya) + offset);

+			ctx.lineTo(tHoz(x2, xa), tVert(y2, ya) + offset);

+

+			/**

+			 * Fill the other rectangle if it's there.

+			 */

+			if(x2 != x2old){

+				top = (y2 <= ya.min) ? ya.min : ya.max;

+				ctx.lineTo(tHoz(x2old, xa), tVert(top, ya) + offset);

+				ctx.lineTo(tHoz(x2, xa), tVert(top, ya) + offset);

+			}

+

+			lastX = Math.max(x2, x2old);

+		}

+		

+		ctx.lineTo(tHoz(lastX, xa), tVert(bottom, ya) + offset);

+		ctx.closePath();

+		ctx.fill();

+	},

+	/**

+	 * Function: (private) drawSeriesLines

+	 * 

+	 * Function draws lines series in the canvas element.

+	 * 

+	 * Parameters:

+	 * 		series - Series with options.lines.show = true.

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	drawSeriesLines: function(series){

+		series = series || this.series;

+		var ctx = this.ctx;

+		ctx.save();

+		ctx.translate(this.plotOffset.left, this.plotOffset.top);

+		ctx.lineJoin = 'round';

+

+		var lw = series.lines.lineWidth;

+		var sw = series.shadowSize;

+

+		if(sw > 0){

+			ctx.lineWidth = sw / 2;

+

+			var offset = lw/2 + ctx.lineWidth/2;

+			

+			ctx.strokeStyle = "rgba(0,0,0,0.1)";

+			this.plotLine(series, offset + sw/2);

+

+			ctx.strokeStyle = "rgba(0,0,0,0.2)";

+			this.plotLine(series, offset);

+

+			if(series.lines.fill) {

+				ctx.fillStyle = "rgba(0,0,0,0.05)";

+				this.plotLineArea(series, offset + sw/2);

+			}

+		}

+

+		ctx.lineWidth = lw;

+		ctx.strokeStyle = series.color;

+		if(series.lines.fill){

+			ctx.fillStyle = series.lines.fillColor != null ? series.lines.fillColor : Flotr.parseColor(series.color).scale(null, null, null, series.lines.fillOpacity).toString();

+			this.plotLineArea(series, 0);

+		}

+

+		this.plotLine(series, 0);

+		ctx.restore();

+	},

+	/**

+	 * Function: drawSeriesPoints

+	 * 

+	 * Function draws point series in the canvas element.

+	 * 

+	 * Parameters:

+	 * 		series - Series with options.points.show = true.

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	drawSeriesPoints: function(series) {

+		var ctx = this.ctx;

+		

+		ctx.save();

+		ctx.translate(this.plotOffset.left, this.plotOffset.top);

+

+		var lw = series.lines.lineWidth;

+		var sw = series.shadowSize;

+		

+		if(sw > 0){

+			ctx.lineWidth = sw / 2;

+      

+			ctx.strokeStyle = 'rgba(0,0,0,0.1)';

+			this.plotPointShadows(series, sw/2 + ctx.lineWidth/2, series.points.radius);

+

+			ctx.strokeStyle = 'rgba(0,0,0,0.2)';

+			this.plotPointShadows(series, ctx.lineWidth/2, series.points.radius);

+		}

+

+		ctx.lineWidth = series.points.lineWidth;

+		ctx.strokeStyle = series.color;

+		ctx.fillStyle = series.points.fillColor != null ? series.points.fillColor : series.color;

+		this.plotPoints(series, series.points.radius, series.points.fill);

+		ctx.restore();

+	},

+	plotPoints: function (series, radius, fill) {

+    var xa = series.xaxis,

+        ya = series.yaxis,

+		    ctx = this.ctx, i,

+		    data = series.data;

+			

+		for(i = data.length - 1; i > -1; --i){

+			var x = data[i][0], y = data[i][1];

+			if(x < xa.min || x > xa.max || y < ya.min || y > ya.max)

+				continue;

+			

+			ctx.beginPath();

+			ctx.arc(this.tHoz(x, xa), this.tVert(y, ya), radius, 0, 2 * Math.PI, true);

+			if(fill) ctx.fill();

+			ctx.stroke();

+		}

+	},

+	plotPointShadows: function(series, offset, radius){

+    var xa = series.xaxis,

+        ya = series.yaxis,

+		    ctx = this.ctx, i,

+		    data = series.data;

+			

+		for(i = data.length - 1; i > -1; --i){

+			var x = data[i][0], y = data[i][1];

+			if (x < xa.min || x > xa.max || y < ya.min || y > ya.max)

+				continue;

+			ctx.beginPath();

+			ctx.arc(this.tHoz(x, xa), this.tVert(y, ya) + offset, radius, 0, Math.PI, false);

+			ctx.stroke();

+		}

+	},

+	/**

+	 * Function: drawSeriesBars

+	 * 

+	 * Function draws bar series in the canvas element.

+	 * 

+	 * Parameters:

+	 * 		series - Series with options.bars.show = true.

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	drawSeriesBars: function(series) {

+		var ctx = this.ctx,

+			bw = series.bars.barWidth,

+			lw = Math.min(series.bars.lineWidth, bw);

+		

+		ctx.save();

+		ctx.translate(this.plotOffset.left, this.plotOffset.top);

+		ctx.lineJoin = 'miter';

+

+		/**

+		 * @todo linewidth not interpreted the right way.

+		 */

+		ctx.lineWidth = lw;

+		ctx.strokeStyle = series.color;

+    

+		this.plotBarsShadows(series, bw, 0, series.bars.fill);

+

+		if(series.bars.fill){

+			ctx.fillStyle = series.bars.fillColor != null ? series.bars.fillColor : Flotr.parseColor(series.color).scale(null, null, null, series.bars.fillOpacity).toString();

+		}

+    

+		this.plotBars(series, bw, 0, series.bars.fill);

+		ctx.restore();

+	},

+	plotBars: function(series, barWidth, offset, fill){

+		var data = series.data;

+		if(data.length < 1) return;

+		

+    var xa = series.xaxis,

+        ya = series.yaxis,

+  			ctx = this.ctx,

+  			tHoz = this.tHoz.bind(this),

+  			tVert = this.tVert.bind(this);

+

+		for(var i = 0; i < data.length; i++){

+			var x = data[i][0],

+			    y = data[i][1];

+			var drawLeft = true, drawTop = true, drawRight = true;

+			

+			// Stacked bars

+			var stackOffset = 0;

+			if(series.bars.stacked) {

+			  xa.values.each(function(o, v) {

+			    if (v == x) {

+			      stackOffset = o.stack || 0;

+			      o.stack = stackOffset + y;

+			    }

+			  });

+			}

+

+			// @todo: fix horizontal bars support

+			// Horizontal bars

+			if(series.bars.horizontal)

+				var left = stackOffset, right = x + stackOffset, bottom = y, top = y + barWidth;

+			else 

+				var left = x, right = x + barWidth, bottom = stackOffset, top = y + stackOffset;

+

+			if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max)

+				continue;

+

+			if(left < xa.min){

+				left = xa.min;

+				drawLeft = false;

+			}

+

+			if(right > xa.max){

+				right = xa.max;

+				if (xa.lastSerie != series && series.bars.horizontal)

+					drawTop = false;

+			}

+

+			if(bottom < ya.min)

+				bottom = ya.min;

+

+			if(top > ya.max){

+				top = ya.max;

+				if (ya.lastSerie != series && !series.bars.horizontal)

+					drawTop = false;

+			}

+      

+			/**

+			 * Fill the bar.

+			 */

+			if(fill){

+				ctx.beginPath();

+				ctx.moveTo(tHoz(left, xa), tVert(bottom, ya) + offset);

+				ctx.lineTo(tHoz(left, xa), tVert(top, ya) + offset);

+				ctx.lineTo(tHoz(right, xa), tVert(top, ya) + offset);

+				ctx.lineTo(tHoz(right, xa), tVert(bottom, ya) + offset);

+				ctx.fill();

+			}

+

+			/**

+			 * Draw bar outline/border.

+			 */

+			if(series.bars.lineWidth != 0 && (drawLeft || drawRight || drawTop)){

+				ctx.beginPath();

+				ctx.moveTo(tHoz(left, xa), tVert(bottom, ya) + offset);

+				

+				ctx[drawLeft ?'lineTo':'moveTo'](tHoz(left, xa), tVert(top, ya) + offset);

+				ctx[drawTop  ?'lineTo':'moveTo'](tHoz(right, xa), tVert(top, ya) + offset);

+				ctx[drawRight?'lineTo':'moveTo'](tHoz(right, xa), tVert(bottom, ya) + offset);

+				         

+				ctx.stroke();

+			}

+		}

+	},

+  plotBarsShadows: function(series, barWidth, offset){

+		var data = series.data;

+    if(data.length < 1) return;

+    

+    var xa = series.xaxis,

+        ya = series.yaxis,

+        ctx = this.ctx,

+        tHoz = this.tHoz.bind(this),

+        tVert = this.tVert.bind(this),

+        sw = this.options.shadowSize;

+

+    for(var i = 0; i < data.length; i++){

+      var x = data[i][0],

+          y = data[i][1];

+      

+      // Stacked bars

+      var stackOffset = 0;

+			if(series.bars.stacked) {

+			  xa.values.each(function(o, v) {

+			    if (v == x) {

+			      stackOffset = o.stackShadow || 0;

+			      o.stackShadow = stackOffset + y;

+			    }

+			  });

+			}

+      

+      // Horizontal bars

+      if(series.bars.horizontal) 

+        var left = stackOffset, right = x + stackOffset, bottom = y, top = y + barWidth;

+      else 

+        var left = x, right = x + barWidth, bottom = stackOffset, top = y + stackOffset;

+

+      if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max)

+        continue;

+

+      if(left < xa.min)   left = xa.min;

+      if(right > xa.max)  right = xa.max;

+      if(bottom < ya.min) bottom = ya.min;

+      if(top > ya.max)    top = ya.max;

+      

+      var width =  tHoz(right, xa)-tHoz(left, xa)-((tHoz(right, xa)+sw <= this.plotWidth) ? 0 : sw);

+      var height = Math.max(0, tVert(bottom, ya)-tVert(top, ya)-((tVert(bottom, ya)+sw <= this.plotHeight) ? 0 : sw));

+

+      ctx.fillStyle = 'rgba(0,0,0,0.05)';

+      ctx.fillRect(Math.min(tHoz(left, xa)+sw, this.plotWidth), Math.min(tVert(top, ya)+sw, this.plotWidth), width, height);

+    }

+  },

+	/**

+	 * Function: drawSeriesCandles

+	 * 

+	 * Function draws candles series in the canvas element.

+	 * 

+	 * Parameters:

+	 * 		series - Series with options.candles.show = true.

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	drawSeriesCandles: function(series) {

+		var ctx = this.ctx,

+			  bw = series.candles.candleWidth;

+		

+		ctx.save();

+		ctx.translate(this.plotOffset.left, this.plotOffset.top);

+		ctx.lineJoin = 'miter';

+

+		/**

+		 * @todo linewidth not interpreted the right way.

+		 */

+		ctx.lineWidth = series.candles.lineWidth;

+		this.plotCandlesShadows(series, bw/2);

+		this.plotCandles(series, bw/2);

+		

+		ctx.restore();

+	},

+	plotCandles: function(series, offset){

+		var data = series.data;

+		if(data.length < 1) return;

+		

+    var xa = series.xaxis,

+        ya = series.yaxis,

+  			ctx = this.ctx,

+  			tHoz = this.tHoz.bind(this),

+  			tVert = this.tVert.bind(this);

+

+		for(var i = 0; i < data.length; i++){

+      var d     = data[i],

+  		    x     = d[0],

+  		    open  = d[1],

+  		    high  = d[2],

+  		    low   = d[3],

+  		    close = d[4];

+

+			var left    = x,

+			    right   = x + series.candles.candleWidth,

+          bottom  = Math.max(ya.min, low),

+	        top     = Math.min(ya.max, high),

+          bottom2 = Math.max(ya.min, Math.min(open, close)),

+	        top2    = Math.min(ya.max, Math.max(open, close));

+

+			if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max)

+				continue;

+

+			var color = series.candles[open>close?'downFillColor':'upFillColor'];

+			/**

+			 * Fill the candle.

+			 */

+			if(series.candles.fill && !series.candles.barcharts){

+				ctx.fillStyle = Flotr.parseColor(color).scale(null, null, null, series.candles.fillOpacity).toString();

+				ctx.fillRect(tHoz(left, xa), tVert(top2, ya) + offset, tHoz(right, xa) - tHoz(left, xa), tVert(bottom2, ya) - tVert(top2, ya));

+			}

+

+			/**

+			 * Draw candle outline/border, high, low.

+			 */

+			if(series.candles.lineWidth || series.candles.wickLineWidth){

+				var x, y, pixelOffset = (series.candles.wickLineWidth % 2) / 2;

+

+				x = Math.floor(tHoz((left + right) / 2), xa) + pixelOffset;

+				

+			  ctx.save();

+			  ctx.strokeStyle = color;

+			  ctx.lineWidth = series.candles.wickLineWidth;

+			  ctx.lineCap = 'butt';

+			  

+				if (series.candles.barcharts) {

+					ctx.beginPath();

+					

+					ctx.moveTo(x, Math.floor(tVert(top, ya) + offset));

+					ctx.lineTo(x, Math.floor(tVert(bottom, ya) + offset));

+					

+					y = Math.floor(tVert(open, ya) + offset)+0.5;

+					ctx.moveTo(Math.floor(tHoz(left, xa))+pixelOffset, y);

+					ctx.lineTo(x, y);

+					

+					y = Math.floor(tVert(close, ya) + offset)+0.5;

+					ctx.moveTo(Math.floor(tHoz(right, xa))+pixelOffset, y);

+					ctx.lineTo(x, y);

+				} 

+				else {

+  				ctx.strokeRect(tHoz(left, xa), tVert(top2, ya) + offset, tHoz(right, xa) - tHoz(left, xa), tVert(bottom2, ya) - tVert(top2, ya));

+

+  				ctx.beginPath();

+  				ctx.moveTo(x, Math.floor(tVert(top2,    ya) + offset));

+  				ctx.lineTo(x, Math.floor(tVert(top,     ya) + offset));

+  				ctx.moveTo(x, Math.floor(tVert(bottom2, ya) + offset));

+  				ctx.lineTo(x, Math.floor(tVert(bottom,  ya) + offset));

+				}

+				

+				ctx.stroke();

+				ctx.restore();

+			}

+		}

+	},

+  plotCandlesShadows: function(series, offset){

+		var data = series.data;

+    if(data.length < 1 || series.candles.barcharts) return;

+    

+    var xa = series.xaxis,

+        ya = series.yaxis,

+        tHoz = this.tHoz.bind(this),

+        tVert = this.tVert.bind(this),

+        sw = this.options.shadowSize;

+

+    for(var i = 0; i < data.length; i++){

+      var d     = data[i],

+      		x     = d[0],

+	        open  = d[1],

+	        high  = d[2],

+	        low   = d[3],

+	        close = d[4];

+      

+			var left   = x,

+	        right  = x + series.candles.candleWidth,

+          bottom = Math.max(ya.min, Math.min(open, close)),

+	        top    = Math.min(ya.max, Math.max(open, close));

+

+      if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max)

+        continue;

+

+      var width =  tHoz(right, xa)-tHoz(left, xa)-((tHoz(right, xa)+sw <= this.plotWidth) ? 0 : sw);

+      var height = Math.max(0, tVert(bottom, ya)-tVert(top, ya)-((tVert(bottom, ya)+sw <= this.plotHeight) ? 0 : sw));

+

+      this.ctx.fillStyle = 'rgba(0,0,0,0.05)';

+      this.ctx.fillRect(Math.min(tHoz(left, xa)+sw, this.plotWidth), Math.min(tVert(top, ya)+sw, this.plotWidth), width, height);

+    }

+  },

+  /**

+   * Function: drawSeriesRadar

+   * 

+   * Function draws a radar chart on the canvas element.

+   * 

+   * Parameters:

+   *    series - Series with options.radar.show = true.

+   * 

+   * Returns:

+   *    void

+   */

+  drawSeriesRadar: function(series) {

+	var ctx = this.ctx,

+		options = this.options, sides= series.data.length;

+		

+	var degreesInRadiansForAngle = Math.PI * 2 / sides,

+	      nintyDegrees = Math.PI / 2;

+	

+	var poly = {};

+	

+	/* 

+	Draw radar grid

+	

+	poly.xaxis = series.xaxis;

+	poly.yaxis = series.yaxis;

+	ctx.save();

+	ctx.translate(this.plotOffset.left, this.plotOffset.top);

+	ctx.lineJoin = 'round';

+	for (radius = 20; radius <= 100; radius += 20) {

+	poly.data = new Array();

+	for (i = 0; i < sides; i++) {

+		angle = nintyDegrees + (degreesInRadiansForAngle * i);

+		poly.data[i] = [radius * Math.cos(angle), radius * Math.sin(angle)]

+	}

+	poly.data[sides] = poly.data[0];

+	this.plotLine(poly,0);}

+	

+	var outside = poly.data;

+	for (i = 0; i < sides; i++) {

+		poly.data = new Array();

+		poly.data[0] = [0,0];

+		poly.data[1] = outside[i];

+		this.plotLine(poly,0);

+	}

+	*/

+	

+	/*

+	Convert Series data into X, Y co-ordinates

+	*/

+	if (!series.dataInRadarFormat) {

+	poly.data = new Array();

+	for (i = 0; i < sides; i++) {

+		angle = nintyDegrees + (degreesInRadiansForAngle * i);

+		poly.data[i] = [series.data[i][1] * Math.cos(angle), series.data[i][1] * Math.sin(angle), series.data[i][0], series.data[i][1]]

+	}

+	poly.data[sides] = poly.data[0];

+	series.data = poly.data;

+	series.lines = series.radar;

+	series.lines.show = false;

+	series.dataInRadarFormat = true;

+	}

+	

+	this.drawSeriesLines(series);

+	

+},

+  

+  

+  /**

+   * Function: drawSeriesPie

+   * 

+   * Function draws a pie in the canvas element.

+   * 

+   * Parameters:

+   *    series - Series with options.pie.show = true.

+   * 

+   * Returns:

+   *    void

+   */

+  drawSeriesPie: function(series) {

+    if (!this.options.pie.drawn) {

+    var ctx = this.ctx,

+        options = this.options,

+        lw = series.pie.lineWidth,

+        sw = series.shadowSize,

+        data = series.data,

+        radius = (Math.min(this.canvasWidth, this.canvasHeight) * series.pie.sizeRatio) / 2,

+        html = [];

+    

+    var vScale = 1;//Math.cos(series.pie.viewAngle);

+    var plotTickness = Math.sin(series.pie.viewAngle)*series.pie.spliceThickness / vScale;

+    

+    var style = {

+      size: options.fontSize*1.2,

+      color: options.grid.color,

+      weight: 1.5

+    };

+    

+    var center = {

+      x: (this.canvasWidth+this.plotOffset.left)/2,

+      y: (this.canvasHeight-this.plotOffset.bottom)/2

+    };

+    

+    // Pie portions

+    var portions = this.series.collect(function(hash, index){

+    	if (hash.pie.show)

+      return {

+        name: (hash.label || hash.data[0][1]),

+        value: [index, hash.data[0][1]],

+        explode: hash.pie.explode

+      };

+    });

+    

+    // Sum of the portions' angles

+    var sum = portions.pluck('value').pluck(1).inject(0, function(acc, n) { return acc + n; });

+    

+    var fraction = 0.0,

+        angle = series.pie.startAngle,

+        value = 0.0;

+    

+    var slices = portions.collect(function(slice){

+      angle += fraction;

+      value = parseFloat(slice.value[1]); // @warning : won't support null values !!

+      fraction = value/sum;

+      return {

+        name:     slice.name,

+        fraction: fraction,

+        x:        slice.value[0],

+        y:        value,

+        explode:  slice.explode,

+        startAngle: 2 * angle * Math.PI,

+        endAngle:   2 * (angle + fraction) * Math.PI

+      };

+    });

+    

+    ctx.save();

+

+    if(sw > 0){

+	    slices.each(function (slice) {

+        var bisection = (slice.startAngle + slice.endAngle) / 2;

+        

+        var xOffset = center.x + Math.cos(bisection) * slice.explode + sw;

+        var yOffset = center.y + Math.sin(bisection) * slice.explode + sw;

+        

+		    this.plotSlice(xOffset, yOffset, radius, slice.startAngle, slice.endAngle, false, vScale);

+

+        ctx.fillStyle = 'rgba(0,0,0,0.1)';

+        ctx.fill();

+      }, this);

+    }

+    

+    if (options.HtmlText) {

+      html = ['<div style="color:' + this.options.grid.color + '" class="flotr-labels">'];

+    }

+    

+    slices.each(function (slice, index) {

+      var bisection = (slice.startAngle + slice.endAngle) / 2;

+      var color = options.colors[index];

+      

+      var xOffset = center.x + Math.cos(bisection) * slice.explode;

+      var yOffset = center.y + Math.sin(bisection) * slice.explode;

+      

+      this.plotSlice(xOffset, yOffset, radius, slice.startAngle, slice.endAngle, false, vScale);

+      

+      if(series.pie.fill){

+        ctx.fillStyle = Flotr.parseColor(color).scale(null, null, null, series.pie.fillOpacity).toString();

+        ctx.fill();

+      }

+      ctx.lineWidth = lw;

+      ctx.strokeStyle = color;

+      ctx.stroke();

+      

+      /*ctx.save();

+      ctx.scale(1, vScale);

+      

+      ctx.moveTo(xOffset, yOffset);

+      ctx.beginPath();

+      ctx.lineTo(xOffset, yOffset+plotTickness);

+      ctx.lineTo(xOffset+Math.cos(slice.startAngle)*radius, yOffset+Math.sin(slice.startAngle)*radius+plotTickness);

+      ctx.lineTo(xOffset+Math.cos(slice.startAngle)*radius, yOffset+Math.sin(slice.startAngle)*radius);

+      ctx.lineTo(xOffset, yOffset);

+      ctx.closePath();

+      ctx.fill();ctx.stroke();

+      

+      ctx.moveTo(xOffset, yOffset);

+      ctx.beginPath();

+      ctx.lineTo(xOffset, yOffset+plotTickness);

+      ctx.lineTo(xOffset+Math.cos(slice.endAngle)*radius, yOffset+Math.sin(slice.endAngle)*radius+plotTickness);

+      ctx.lineTo(xOffset+Math.cos(slice.endAngle)*radius, yOffset+Math.sin(slice.endAngle)*radius);

+      ctx.lineTo(xOffset, yOffset);

+      ctx.closePath();

+      ctx.fill();ctx.stroke();

+      

+      ctx.moveTo(xOffset+Math.cos(slice.startAngle)*radius, yOffset+Math.sin(slice.startAngle)*radius);

+      ctx.beginPath();

+      ctx.lineTo(xOffset+Math.cos(slice.startAngle)*radius, yOffset+Math.sin(slice.startAngle)*radius+plotTickness);

+      ctx.arc(xOffset, yOffset+plotTickness, radius, slice.startAngle, slice.endAngle, false);

+      ctx.lineTo(xOffset+Math.cos(slice.endAngle)*radius, yOffset+Math.sin(slice.endAngle)*radius);

+      ctx.arc(xOffset, yOffset, radius, slice.endAngle, slice.startAngle, true);

+      ctx.closePath();

+      ctx.fill();ctx.stroke();

+      

+      ctx.scale(1, 1/vScale);

+      this.plotSlice(xOffset, yOffset+plotTickness, radius, slice.startAngle, slice.endAngle, false, vScale);

+      ctx.stroke();

+      if(series.pie.fill){

+        ctx.fillStyle = Flotr.parseColor(color).scale(null, null, null, series.pie.fillOpacity).toString();

+        ctx.fill();

+      }

+      

+      ctx.restore();*/

+      

+      var label = options.pie.labelFormatter(slice);

+      

+      var textAlignRight = (Math.cos(bisection) < 0);

+      var distX = xOffset + Math.cos(bisection) * (series.pie.explode + radius);

+      var distY = yOffset + Math.sin(bisection) * (series.pie.explode + radius);

+      

+      if (slice.fraction && label) {

+        if (options.HtmlText) {

+          var divStyle = 'position:absolute;top:' + (distY - 5) + 'px;'; //@todo: change

+          if (textAlignRight) {

+            divStyle += 'right:'+(this.canvasWidth - distX)+'px;text-align:right;';

+          }

+          else {

+            divStyle += 'left:'+distX+'px;text-align:left;';

+          }

+          html.push('<div style="' + divStyle + '" class="flotr-grid-label">' + label + '</div>');

+        }

+        else {

+          style.halign = textAlignRight ? 'r' : 'l';

+          ctx.drawText(

+            label, 

+            distX, 

+            distY + style.size / 2, 

+            style

+          );

+        }

+      }

+    }, this);

+

+    if (options.HtmlText) {

+      html.push('</div>');    

+      this.el.insert(html.join(''));

+    }

+    

+    ctx.restore();

+    options.pie.drawn = true;

+    }

+  },

+  plotSlice: function(x, y, radius, startAngle, endAngle, fill, vScale) {

+    var ctx = this.ctx;

+    vScale = vScale || 1;

+    

+    ctx.save();

+    ctx.scale(1, vScale);

+    ctx.beginPath();

+    ctx.moveTo(x, y);

+    ctx.arc   (x, y, radius, startAngle, endAngle, fill);

+    ctx.lineTo(x, y);

+    ctx.closePath();

+    ctx.restore();

+  },

+  plotPie: function() {}, 

+	/**

+	 * Function: insertLegend

+	 * 

+	 * Function adds a legend div to the canvas container or draws it on the canvas.

+	 * 

+	 * Parameters:

+	 * 		none

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	insertLegend: function(){

+		if(!this.options.legend.show)

+			return;

+			

+		var series = this.series,

+			plotOffset = this.plotOffset,

+			options = this.options,

+			fragments = [],

+			rowStarted = false, 

+			ctx = this.ctx,

+			i;

+			

+		var noLegendItems = series.findAll(function(s) {return (s.label && !s.hide)}).size();

+

+    if (noLegendItems) {

+	    if (!options.HtmlText && this.textEnabled) {

+	      var style = {

+	        size: options.fontSize*1.1,

+	        color: options.grid.color

+	      };

+	      

+	      // @todo: take css into account

+	      //var dummyDiv = this.el.insert('<div class="flotr-legend" style="position:absolute;top:-10000px;"></div>');

+	      

+	      var p = options.legend.position, 

+	          m = options.legend.margin,

+	          lbw = options.legend.labelBoxWidth,

+	          lbh = options.legend.labelBoxHeight,

+	          lbm = options.legend.labelBoxMargin,

+	          offsetX = plotOffset.left + m,

+	          offsetY = plotOffset.top + m;

+	      

+	      // We calculate the labels' max width

+	      var labelMaxWidth = 0;

+	      for(i = series.length - 1; i > -1; --i){

+	        if(!series[i].label || series[i].hide) continue;

+	        var label = options.legend.labelFormatter(series[i].label);	

+	        labelMaxWidth = Math.max(labelMaxWidth, ctx.measureText(label, style));

+	      }

+	      

+	      var legendWidth  = Math.round(lbw + lbm*3 + labelMaxWidth),

+	          legendHeight = Math.round(noLegendItems*(lbm+lbh) + lbm);

+	

+	      if(p.charAt(0) == 's') offsetY = plotOffset.top + this.plotHeight - (m + legendHeight);

+	      if(p.charAt(1) == 'e') offsetX = plotOffset.left + this.plotWidth - (m + legendWidth);

+

+	      // Legend box

+	      var color = Flotr.parseColor(options.legend.backgroundColor || 'rgb(240,240,240)').scale(null, null, null, options.legend.backgroundOpacity || 0.1).toString();

+	      

+	      ctx.fillStyle = color;

+	      ctx.fillRect(offsetX, offsetY, legendWidth, legendHeight);

+	      ctx.strokeStyle = options.legend.labelBoxBorderColor;

+	      ctx.strokeRect(Flotr.toPixel(offsetX), Flotr.toPixel(offsetY), legendWidth, legendHeight);

+	      

+	      // Legend labels

+	      var x = offsetX + lbm;

+	      var y = offsetY + lbm;

+	      for(i = 0; i < series.length; i++){

+	        if(!series[i].label || series[i].hide) continue;

+	        var label = options.legend.labelFormatter(series[i].label);

+

+	        ctx.fillStyle = series[i].color;

+	        ctx.fillRect(x, y, lbw-1, lbh-1);

+	        

+	        ctx.strokeStyle = options.legend.labelBoxBorderColor;

+	        ctx.lineWidth = 1;

+	        ctx.strokeRect(Math.ceil(x)-1.5, Math.ceil(y)-1.5, lbw+2, lbh+2);

+	        

+	        // Legend text

+	        ctx.drawText(

+	          label,

+	          x + lbw + lbm,

+	          y + (lbh + style.size - ctx.fontDescent(style))/2,

+	          style

+	        );

+	        

+	        y += lbh + lbm;

+	      }

+	    }

+	    else {

+	  		for(i = 0; i < series.length; ++i){

+	  			if(!series[i].label || series[i].hide) continue;

+	  			

+	  			if(i % options.legend.noColumns == 0){

+	  				fragments.push(rowStarted ? '</tr><tr>' : '<tr>');

+	  				rowStarted = true;

+	  			}

+	  

+	  			var label = options.legend.labelFormatter(series[i].label);

+	  			

+	  			fragments.push('<td class="flotr-legend-color-box"><div style="border:1px solid ' + options.legend.labelBoxBorderColor + ';padding:1px"><div style="width:' + options.legend.labelBoxWidth + 'px;height:' + options.legend.labelBoxHeight + 'px;background-color:' + series[i].color + '"></div></div></td>' +

+	  				'<td class="flotr-legend-label">' + label + '</td>');

+	  		}

+	  		if(rowStarted) fragments.push('</tr>');

+	  		

+	  		if(fragments.length > 0){

+	  			var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join("") + '</table>';

+	  			if(options.legend.container != null){

+	  				$(options.legend.container).update(table);

+	  			}else{

+	  				var pos = '';

+	  				var p = options.legend.position, m = options.legend.margin;

+	  				

+	  				     if(p.charAt(0) == 'n') pos += 'top:' + (m + plotOffset.top) + 'px;';

+	  				else if(p.charAt(0) == 's') pos += 'bottom:' + (m + plotOffset.bottom) + 'px;';					

+	  				     if(p.charAt(1) == 'e') pos += 'right:' + (m + plotOffset.right) + 'px;';

+	  				else if(p.charAt(1) == 'w') pos += 'left:' + (m + plotOffset.left) + 'px;';

+	  				     

+	  				var div = this.el.insert('<div class="flotr-legend" style="position:absolute;z-index:2;' + pos +'">' + table + '</div>').select('div.flotr-legend').first();

+	  				

+	  				if(options.legend.backgroundOpacity != 0.0){

+	  					/**

+	  					 * Put in the transparent background separately to avoid blended labels and

+	  					 * label boxes.

+	  					 */

+	  					var c = options.legend.backgroundColor;

+	  					if(c == null){

+	  						var tmp = (options.grid.backgroundColor != null) ? options.grid.backgroundColor : Flotr.extractColor(div);

+	  						c = Flotr.parseColor(tmp).adjust(null, null, null, 1).toString();

+	  					}

+	  					this.el.insert('<div class="flotr-legend-bg" style="position:absolute;width:' + div.getWidth() + 'px;height:' + div.getHeight() + 'px;' + pos +'background-color:' + c + ';"> </div>').select('div.flotr-legend-bg').first().setStyle({

+	  						'opacity': options.legend.backgroundOpacity

+	  					});						

+	  				}

+	  			}

+	  		}

+	    }

+    }

+	},

+	/**

+	 * Function: getEventPosition

+	 * 

+	 * Calculates the coordinates from a mouse event object.

+	 * 

+	 * Parameters:

+	 * 		event - Mouse Event object.

+	 * 

+	 * Returns:

+	 * 		Object with x and y coordinates of the mouse.

+	 */

+	getEventPosition: function (event){

+		var offset = this.overlay.cumulativeOffset(),

+			rx = (event.pageX - offset.left - this.plotOffset.left),

+			ry = (event.pageY - offset.top - this.plotOffset.top),

+			ax = 0, ay = 0

+			

+		if(event.pageX == null && event.clientX != null){

+			var de = document.documentElement, b = document.body;

+			ax = event.clientX + (de && de.scrollLeft || b.scrollLeft || 0);

+			ay = event.clientY + (de && de.scrollTop || b.scrollTop || 0);

+		}else{

+			ax = event.pageX;

+			ay = event.pageY;

+		}

+		

+		return {

+			x:  this.axes.x.min  + rx / this.axes.x.scale,

+			x2: this.axes.x2.min + rx / this.axes.x2.scale,

+			y:  this.axes.y.max  - ry / this.axes.y.scale,

+			y2: this.axes.y2.max - ry / this.axes.y2.scale,

+			relX: rx,

+			relY: ry,

+			absX: ax,

+			absY: ay

+		};

+	},

+	/**

+	 * Function: clickHandler

+	 * 

+	 * Handler observes the 'click' event and fires the 'flotr:click' event.

+	 * 

+	 * Parameters:

+	 * 		event - 'click' Event object.

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	clickHandler: function(event){

+		if(this.ignoreClick){

+			this.ignoreClick = false;

+			return;

+		}

+		this.el.fire('flotr:click', [this.getEventPosition(event), this]);

+	},

+	/**

+	 * Function: mouseMoveHandler

+	 * 

+	 * Handler observes mouse movement over the graph area. Fires the 

+	 * 'flotr:mousemove' event.

+	 * 

+	 * Parameters:

+	 * 		event - 'mousemove' Event object.

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	mouseMoveHandler: function(event){

+ 		var pos = this.getEventPosition(event);

+    

+		this.lastMousePos.pageX = pos.absX;

+		this.lastMousePos.pageY = pos.absY;	

+		if(this.selectionInterval == null && (this.options.mouse.track || this.series.any(function(s){return s.mouse && s.mouse.track;}))){	

+			this.hit(pos);

+		}

+    

+		this.el.fire('flotr:mousemove', [event, pos, this]);

+	},

+	/**

+	 * Function: mouseDownHandler

+	 * 

+	 * Handler observes the 'mousedown' event.

+	 * 

+	 * Parameters:

+	 * 		event - 'mousedown' Event object.

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	mouseDownHandler: function (event){

+    if(event.isRightClick()) {

+      event.stop();

+      var overlay = this.overlay;

+      overlay.hide();

+      

+      function cancelContextMenu () {

+        overlay.show();

+        $(document).stopObserving('mousemove', cancelContextMenu);

+      }

+      $(document).observe('mousemove', cancelContextMenu);

+      return;

+    }

+    

+		if(!this.options.selection.mode || !event.isLeftClick()) return;

+		

+		this.setSelectionPos(this.selection.first, event);				

+		if(this.selectionInterval != null){

+			clearInterval(this.selectionInterval);

+		}

+		this.lastMousePos.pageX = null;

+		this.selectionInterval = setInterval(this.updateSelection.bind(this), 1000/this.options.selection.fps);

+		

+		this.mouseUpHandler = this.mouseUpHandler.bind(this);

+		$(document).observe('mouseup', this.mouseUpHandler);

+	},

+	/**

+	 * Function: (private) fireSelectEvent

+	 * 

+	 * Fires the 'flotr:select' event when the user made a selection.

+	 * 

+	 * Parameters:

+	 * 		none

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	fireSelectEvent: function(){

+		var a = this.axes, selection = this.selection,

+			x1 = (selection.first.x <= selection.second.x) ? selection.first.x : selection.second.x,

+			x2 = (selection.first.x <= selection.second.x) ? selection.second.x : selection.first.x,

+			y1 = (selection.first.y >= selection.second.y) ? selection.first.y : selection.second.y,

+			y2 = (selection.first.y >= selection.second.y) ? selection.second.y : selection.first.y;

+		

+		x1 = a.x.min + x1 / a.x.scale;

+		x2 = a.x.min + x2 / a.x.scale;

+		y1 = a.y.max - y1 / a.y.scale;

+		y2 = a.y.max - y2 / a.y.scale;

+

+		this.el.fire('flotr:select', [{x1:x1, y1:y1, x2:x2, y2:y2}, this]);

+	},

+	/**

+	 * Function: (private) mouseUpHandler

+	 * 

+	 * Handler observes the mouseup event for the document. 

+	 * 

+	 * Parameters:

+	 * 		event - 'mouseup' Event object.

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	mouseUpHandler: function(event){

+    $(document).stopObserving('mouseup', this.mouseUpHandler);

+    event.stop();

+    

+		if(this.selectionInterval != null){

+			clearInterval(this.selectionInterval);

+			this.selectionInterval = null;

+		}

+

+		this.setSelectionPos(this.selection.second, event);

+		this.clearSelection();

+		

+		if(this.selectionIsSane()){

+			this.drawSelection();

+			this.fireSelectEvent();

+			this.ignoreClick = true;

+		}

+	},

+	/**

+	 * Function: setSelectionPos

+	 * 

+	 * Calculates the position of the selection.

+	 * 

+	 * Parameters:

+	 * 		pos - Position object.

+	 * 		event - Event object.

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	setSelectionPos: function(pos, event) {

+		var options = this.options,

+		    offset = $(this.overlay).cumulativeOffset();

+		

+		if(options.selection.mode.indexOf('x') == -1){

+			pos.x = (pos == this.selection.first) ? 0 : this.plotWidth;			   

+		}else{

+			pos.x = event.pageX - offset.left - this.plotOffset.left;

+			pos.x = Math.min(Math.max(0, pos.x), this.plotWidth);

+		}

+

+		if (options.selection.mode.indexOf('y') == -1){

+			pos.y = (pos == this.selection.first) ? 0 : this.plotHeight;

+		}else{

+			pos.y = event.pageY - offset.top - this.plotOffset.top;

+			pos.y = Math.min(Math.max(0, pos.y), this.plotHeight);

+		}

+	},

+	/**

+	 * Function: updateSelection

+	 * 

+	 * Updates (draws) the selection box.

+	 * 

+	 * Parameters:

+	 * 		none

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	updateSelection: function(){

+		if(this.lastMousePos.pageX == null) return;

+		

+		this.setSelectionPos(this.selection.second, this.lastMousePos);

+		this.clearSelection();

+		

+		if(this.selectionIsSane()) this.drawSelection();

+	},

+	/**

+	 * Function: clearSelection

+	 * 

+	 * Removes the selection box from the overlay canvas.

+	 * 

+	 * Parameters:

+	 * 		none

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	clearSelection: function() {

+		if(this.prevSelection == null) return;

+			

+		var prevSelection = this.prevSelection,

+			octx = this.octx,

+			plotOffset = this.plotOffset,

+			x = Math.min(prevSelection.first.x, prevSelection.second.x),

+			y = Math.min(prevSelection.first.y, prevSelection.second.y),

+			w = Math.abs(prevSelection.second.x - prevSelection.first.x),

+			h = Math.abs(prevSelection.second.y - prevSelection.first.y);

+		

+		octx.clearRect(x + plotOffset.left - octx.lineWidth,

+		               y + plotOffset.top - octx.lineWidth,

+		               w + octx.lineWidth*2,

+		               h + octx.lineWidth*2);

+		

+		this.prevSelection = null;

+	},

+	/**

+	 * Function: setSelection

+	 * 

+	 * Allows the user the manually select an area.

+	 * 

+	 * Parameters:

+	 * 		area - Object with coordinates to select.

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	setSelection: function(area){

+		var options = this.options,

+			xa = this.axes.x,

+			ya = this.axes.y,

+			vertScale = yaxis.scale,

+			hozScale = xaxis.scale,

+			selX = options.selection.mode.indexOf('x') != -1,

+			selY = options.selection.mode.indexOf('y') != -1;

+		

+		this.clearSelection();

+

+		this.selection.first.y  = selX ? 0 : (ya.max - area.y1) * vertScale;

+		this.selection.second.y = selX ? this.plotHeight : (ya.max - area.y2) * vertScale;			

+		this.selection.first.x  = selY ? 0 : (area.x1 - xa.min) * hozScale;

+		this.selection.second.x = selY ? this.plotWidth : (area.x2 - xa.min) * hozScale;

+		

+		this.drawSelection();

+		this.fireSelectEvent();

+	},

+	/**

+	 * Function: (private) drawSelection

+	 * 

+	 * Draws the selection box.

+	 * 

+	 * Parameters:

+	 * 		none

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	drawSelection: function() {

+		var prevSelection = this.prevSelection,

+			selection = this.selection,

+			octx = this.octx,

+			options = this.options,

+			plotOffset = this.plotOffset;

+		

+		if(prevSelection != null &&

+			selection.first.x == prevSelection.first.x &&

+			selection.first.y == prevSelection.first.y && 

+			selection.second.x == prevSelection.second.x &&

+			selection.second.y == prevSelection.second.y)

+			return;

+		

+		octx.strokeStyle = Flotr.parseColor(options.selection.color).scale(null, null, null, 0.8).toString();

+		octx.lineWidth = 1;

+		octx.lineJoin = 'round';

+		octx.fillStyle = Flotr.parseColor(options.selection.color).scale(null, null, null, 0.4).toString();

+

+		this.prevSelection = {

+			first: { x: selection.first.x, y: selection.first.y },

+			second: { x: selection.second.x, y: selection.second.y }

+		};

+

+		var x = Math.min(selection.first.x, selection.second.x),

+		    y = Math.min(selection.first.y, selection.second.y),

+		    w = Math.abs(selection.second.x - selection.first.x),

+		    h = Math.abs(selection.second.y - selection.first.y);

+		

+		octx.fillRect(x + plotOffset.left, y + plotOffset.top, w, h);

+		octx.strokeRect(x + plotOffset.left, y + plotOffset.top, w, h);

+	},

+	/**

+	 * Function: (private) selectionIsSane

+	 * 

+	 * Determines whether or not the selection is sane and should be drawn.

+	 * 

+	 * Parameters:

+	 * 		none

+	 * 

+	 * Returns:

+	 * 		boolean - True when sane, false otherwise.

+	 */

+	selectionIsSane: function(){

+		var selection = this.selection;

+		return Math.abs(selection.second.x - selection.first.x) >= 5 &&

+		       Math.abs(selection.second.y - selection.first.y) >= 5;

+	},

+	/**

+	 * Function: clearHit

+	 * 

+	 * Removes the mouse tracking point from the overlay.

+	 * 

+	 * Parameters:

+	 * 		none

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	clearHit: function(){

+		if(this.prevHit){

+			var options = this.options,

+			    plotOffset = this.plotOffset,

+			    prevHit = this.prevHit;

+					

+			this.octx.clearRect(

+				this.tHoz(prevHit.x) + plotOffset.left - options.points.radius*2,

+				this.tVert(prevHit.y) + plotOffset.top - options.points.radius*2,

+				options.points.radius*3 + options.points.lineWidth*3, 

+				options.points.radius*3 + options.points.lineWidth*3

+			);

+			this.prevHit = null;

+		}		

+	},

+	/**

+	 * Function: hit

+	 * 

+	 * Retrieves the nearest data point from the mouse cursor. If it's within

+	 * a certain range, draw a point on the overlay canvas and display the x and y

+	 * value of the data.

+	 * 

+	 * Parameters:

+	 * 		mouse - Object that holds the relative x and y coordinates of the cursor.

+	 * 

+	 * Returns:

+	 * 		void

+	 */

+	hit: function(mouse){

+		var series = this.series,

+			options = this.options,

+			prevHit = this.prevHit,

+			plotOffset = this.plotOffset,

+			octx = this.octx, 

+			data, xsens, ysens,

+			/**

+			 * Nearest data element.

+			 */

+			i, n = {

+				dist:Number.MAX_VALUE,

+				x:null,

+				y:null,

+				relX:mouse.relX,

+				relY:mouse.relY,

+				absX:mouse.absX,

+				absY:mouse.absY,

+				mouse:null,

+				radarData:null

+			};

+		

+		for(i = 0; i < series.length; i++){

+			s = series[i];

+			if(!s.mouse.track) continue;

+			data = s.data;

+			xsens = (s.xaxis.scale*s.mouse.sensibility);

+			ysens = (s.yaxis.scale*s.mouse.sensibility);

+

+			for(var j = 0, xpow, ypow; j < data.length; j++){

+				if (data[j][1] === null) continue;

+				xpow = Math.pow(s.xaxis.scale*(data[j][0] - mouse.x), 2);

+				ypow = Math.pow(s.yaxis.scale*(data[j][1] - mouse.y), 2);

+				if(xpow < xsens && ypow < ysens && Math.sqrt(xpow+ypow) < n.dist){

+					n.dist = Math.sqrt(xpow+ypow);

+					n.x = data[j][0];

+					n.y = data[j][1];

+					n.radarLabel = data[j][2];

+					n.radarData = data[j][3];

+					n.mouse = s.mouse;

+				}

+			}

+		}

+		

+		if(n.mouse && n.mouse.track && !prevHit || (prevHit && (n.x != prevHit.x || n.y != prevHit.y))){

+			var mt = this.mouseTrack || this.el.select(".flotr-mouse-value")[0],

+			    pos = '', 

+			    p = options.mouse.position, 

+			    m = options.mouse.margin,

+			    elStyle = 'opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;';

+

+			if (!options.mouse.relative) { // absolute to the canvas

+						 if(p.charAt(0) == 'n') pos += 'top:' + (m + plotOffset.top) + 'px;';

+				else if(p.charAt(0) == 's') pos += 'bottom:' + (m + plotOffset.bottom) + 'px;';					

+				     if(p.charAt(1) == 'e') pos += 'right:' + (m + plotOffset.right) + 'px;';

+				else if(p.charAt(1) == 'w') pos += 'left:' + (m + plotOffset.left) + 'px;';

+			}

+			else { // relative to the mouse

+			       if(p.charAt(0) == 'n') pos += 'bottom:' + (m - plotOffset.top - this.tVert(n.y) + this.canvasHeight) + 'px;';

+				else if(p.charAt(0) == 's') pos += 'top:' + (m + plotOffset.top + this.tVert(n.y)) + 'px;';

+				     if(p.charAt(1) == 'e') pos += 'left:' + (m + plotOffset.left + this.tHoz(n.x)) + 'px;';

+				else if(p.charAt(1) == 'w') pos += 'right:' + (m - plotOffset.left - this.tHoz(n.x) + this.canvasWidth) + 'px;';

+			}

+			

+			elStyle += pos;

+				     

+			if(!mt){

+				this.el.insert('<div class="flotr-mouse-value" style="'+elStyle+'"></div>');

+				mt = this.mouseTrack = this.el.select('.flotr-mouse-value').first();

+			}

+			else {

+				this.mouseTrack = mt.setStyle(elStyle);

+			}

+			

+			if(n.x !== null && n.y !== null){

+				mt.show();

+				

+				this.clearHit();

+				if(n.mouse.lineColor != null){

+					octx.save();

+					octx.translate(plotOffset.left, plotOffset.top);

+					octx.lineWidth = options.points.lineWidth;

+					octx.strokeStyle = n.mouse.lineColor;

+					octx.fillStyle = '#ffffff';

+					octx.beginPath();

+					octx.arc(this.tHoz(n.x), this.tVert(n.y), options.mouse.radius, 0, 2 * Math.PI, true);

+					octx.fill();

+					octx.stroke();

+					octx.restore();

+				}

+				this.prevHit = n;

+				

+				var decimals = n.mouse.trackDecimals;

+				if(decimals == null || decimals < 0) decimals = 0;

+				

+				mt.innerHTML = n.mouse.trackFormatter({x: n.x.toFixed(decimals), y: n.y.toFixed(decimals), 

+												radarLabel: n.radarLabel, radarData: n.radarData.toFixed(decimals)});

+				mt.fire('flotr:hit', [n, this]);

+			}

+			else if(prevHit){

+				mt.hide();

+				this.clearHit();

+			}

+		}

+	},

+	saveImage: function (type, width, height, replaceCanvas) {

+		var image = null;

+	  switch (type) {

+	  	case 'jpeg':

+	    case 'jpg': image = Canvas2Image.saveAsJPEG(this.canvas, replaceCanvas, width, height); break;

+      default:

+      case 'png': image = Canvas2Image.saveAsPNG(this.canvas, replaceCanvas, width, height); break;

+      case 'bmp': image = Canvas2Image.saveAsBMP(this.canvas, replaceCanvas, width, height); break;

+	  }

+	  if (Object.isElement(image) && replaceCanvas) {

+	    this.restoreCanvas();

+	    this.canvas.hide();

+	    this.overlay.hide();

+	  	this.el.insert(image.setStyle({position: 'absolute'}));

+	  }

+	},

+	restoreCanvas: function() {

+    this.canvas.show();

+    this.overlay.show();

+    this.el.select('img').invoke('remove');

+	}

+});

+

+Flotr.Color = Class.create({

+	initialize: function(r, g, b, a){

+		this.rgba = ['r','g','b','a'];

+		var x = 4;

+		while(-1<--x){

+			this[this.rgba[x]] = arguments[x] || ((x==3) ? 1.0 : 0);

+		}

+		this.normalize();

+	},

+	

+	adjust: function(rd, gd, bd, ad) {

+		var x = 4;

+		while(-1<--x){

+			if(arguments[x] != null)

+				this[this.rgba[x]] += arguments[x];

+		}

+		return this.normalize();

+	},

+	

+	clone: function(){

+		return new Flotr.Color(this.r, this.b, this.g, this.a);

+	},

+	

+	limit: function(val,minVal,maxVal){

+		return Math.max(Math.min(val, maxVal), minVal);

+	},

+	

+	normalize: function(){

+		var limit = this.limit;

+		this.r = limit(parseInt(this.r), 0, 255);

+		this.g = limit(parseInt(this.g), 0, 255);

+		this.b = limit(parseInt(this.b), 0, 255);

+		this.a = limit(this.a, 0, 1);

+		return this;

+	},

+	

+	scale: function(rf, gf, bf, af){

+		var x = 4;

+		while(-1<--x){

+			if(arguments[x] != null)

+				this[this.rgba[x]] *= arguments[x];

+		}

+		return this.normalize();

+	},

+	

+	distance: function(color){

+		if (!color) return;

+		color = new Flotr.parseColor(color);

+	  var dist = 0;

+		var x = 3;

+		while(-1<--x){

+			dist += Math.abs(this[this.rgba[x]] - color[this.rgba[x]]);

+		}

+		return dist;

+	},

+	

+	toString: function(){

+		return (this.a >= 1.0) ? 'rgb('+[this.r,this.g,this.b].join(',')+')' : 'rgba('+[this.r,this.g,this.b,this.a].join(',')+')';

+	}

+});

+

+Flotr.Color.lookupColors = {

+	aqua:[0,255,255],

+	azure:[240,255,255],

+	beige:[245,245,220],

+	black:[0,0,0],

+	blue:[0,0,255],

+	brown:[165,42,42],

+	cyan:[0,255,255],

+	darkblue:[0,0,139],

+	darkcyan:[0,139,139],

+	darkgrey:[169,169,169],

+	darkgreen:[0,100,0],

+	darkkhaki:[189,183,107],

+	darkmagenta:[139,0,139],

+	darkolivegreen:[85,107,47],

+	darkorange:[255,140,0],

+	darkorchid:[153,50,204],

+	darkred:[139,0,0],

+	darksalmon:[233,150,122],

+	darkviolet:[148,0,211],

+	fuchsia:[255,0,255],

+	gold:[255,215,0],

+	green:[0,128,0],

+	indigo:[75,0,130],

+	khaki:[240,230,140],

+	lightblue:[173,216,230],

+	lightcyan:[224,255,255],

+	lightgreen:[144,238,144],

+	lightgrey:[211,211,211],

+	lightpink:[255,182,193],

+	lightyellow:[255,255,224],

+	lime:[0,255,0],

+	magenta:[255,0,255],

+	maroon:[128,0,0],

+	navy:[0,0,128],

+	olive:[128,128,0],

+	orange:[255,165,0],

+	pink:[255,192,203],

+	purple:[128,0,128],

+	violet:[128,0,128],

+	red:[255,0,0],

+	silver:[192,192,192],

+	white:[255,255,255],

+	yellow:[255,255,0]

+};

+

+// not used yet

+Flotr.Date = {

+  format: function(d, format) {

+		if (!d) return;

+

+    var leftPad = function(n) {

+      n = n.toString();

+      return n.length == 1 ? "0" + n : n;

+    };

+    

+    var r = [];

+    var escape = false;

+    

+    for (var i = 0; i < format.length; ++i) {

+      var c = format.charAt(i);

+      

+      if (escape) {

+        switch (c) {

+	        case 'h': c = d.getUTCHours().toString(); break;

+	        case 'H': c = leftPad(d.getUTCHours()); break;

+	        case 'M': c = leftPad(d.getUTCMinutes()); break;

+	        case 'S': c = leftPad(d.getUTCSeconds()); break;

+	        case 'd': c = d.getUTCDate().toString(); break;

+	        case 'm': c = (d.getUTCMonth() + 1).toString(); break;

+	        case 'y': c = d.getUTCFullYear().toString(); break;

+	        case 'b': c = Flotr.Date.monthNames[d.getUTCMonth()]; break;

+        }

+        r.push(c);

+        escape = false;

+      }

+      else {

+        if (c == "%")

+          escape = true;

+        else

+          r.push(c);

+      }

+    }

+    return r.join("");

+  },

+  timeUnits: {

+    "second": 1000,

+    "minute": 60 * 1000,

+    "hour": 60 * 60 * 1000,

+    "day": 24 * 60 * 60 * 1000,

+    "month": 30 * 24 * 60 * 60 * 1000,

+    "year": 365.2425 * 24 * 60 * 60 * 1000

+  },

+  // the allowed tick sizes, after 1 year we use an integer algorithm

+  spec: [

+    [1, "second"], [2, "second"], [5, "second"], [10, "second"], [30, "second"], 

+    [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"], [30, "minute"], 

+    [1, "hour"], [2, "hour"], [4, "hour"], [8, "hour"], [12, "hour"],

+    [1, "day"], [2, "day"], [3, "day"],

+    [0.25, "month"], [0.5, "month"], [1, "month"], [2, "month"], [3, "month"], [6, "month"],

+    [1, "year"]

+  ],

+  monthNames: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]

+};

 

--- /dev/null
+++ b/js/flotr/lib/base64.js
@@ -1,1 +1,113 @@
-
+/* Copyright (C) 1999 Masanao Izumo <iz@onicos.co.jp>

+ * Version: 1.0

+ * LastModified: Dec 25 1999

+ * This library is free.  You can redistribute it and/or modify it.

+ */

+

+/*

+ * Interfaces:

+ * b64 = base64encode(data);

+ * data = base64decode(b64);

+ */

+

+(function() {

+

+var base64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

+var base64DecodeChars = [

+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,

+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,

+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,

+    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,

+    -1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,

+    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,

+    -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,

+    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1];

+

+function base64encode(str) {

+    var out, i, len;

+    var c1, c2, c3;

+

+    len = str.length;

+    i = 0;

+    out = "";

+    while(i < len) {

+	c1 = str.charCodeAt(i++) & 0xff;

+	if(i == len)

+	{

+	    out += base64EncodeChars.charAt(c1 >> 2);

+	    out += base64EncodeChars.charAt((c1 & 0x3) << 4);

+	    out += "==";

+	    break;

+	}

+	c2 = str.charCodeAt(i++);

+	if(i == len)

+	{

+	    out += base64EncodeChars.charAt(c1 >> 2);

+	    out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4));

+	    out += base64EncodeChars.charAt((c2 & 0xF) << 2);

+	    out += "=";

+	    break;

+	}

+	c3 = str.charCodeAt(i++);

+	out += base64EncodeChars.charAt(c1 >> 2);

+	out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4));

+	out += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6));

+	out += base64EncodeChars.charAt(c3 & 0x3F);

+    }

+    return out;

+}

+

+function base64decode(str) {

+    var c1, c2, c3, c4;

+    var i, len, out;

+

+    len = str.length;

+    i = 0;

+    out = "";

+    while(i < len) {

+	/* c1 */

+	do {

+	    c1 = base64DecodeChars[str.charCodeAt(i++) & 0xff];

+	} while(i < len && c1 == -1);

+	if(c1 == -1)

+	    break;

+

+	/* c2 */

+	do {

+	    c2 = base64DecodeChars[str.charCodeAt(i++) & 0xff];

+	} while(i < len && c2 == -1);

+	if(c2 == -1)

+	    break;

+

+	out += String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4));

+

+	/* c3 */

+	do {

+	    c3 = str.charCodeAt(i++) & 0xff;

+	    if(c3 == 61)

+		return out;

+	    c3 = base64DecodeChars[c3];

+	} while(i < len && c3 == -1);

+	if(c3 == -1)

+	    break;

+

+	out += String.fromCharCode(((c2 & 0XF) << 4) | ((c3 & 0x3C) >> 2));

+

+	/* c4 */

+	do {

+	    c4 = str.charCodeAt(i++) & 0xff;

+	    if(c4 == 61)

+		return out;

+	    c4 = base64DecodeChars[c4];

+	} while(i < len && c4 == -1);

+	if(c4 == -1)

+	    break;

+	out += String.fromCharCode(((c3 & 0x03) << 6) | c4);

+    }

+    return out;

+}

+

+if (!window.btoa) window.btoa = base64encode;

+if (!window.atob) window.atob = base64decode;

+

+})();

--- /dev/null
+++ b/js/flotr/lib/canvas2image.js
@@ -1,1 +1,230 @@
-
+/*

+ * Canvas2Image v0.1

+ * Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com

+ * MIT License [http://www.opensource.org/licenses/mit-license.php]

+ */

+

+var Canvas2Image = (function() {

+	// check if we have canvas support

+	var oCanvas = document.createElement("canvas");

+  

+	// no canvas, bail out.

+	if (!oCanvas.getContext) {

+		return {

+			saveAsBMP : function(){},

+			saveAsPNG : function(){},

+			saveAsJPEG : function(){}

+		}

+	}

+

+	var bHasImageData = !!(oCanvas.getContext("2d").getImageData);

+	var bHasDataURL = !!(oCanvas.toDataURL);

+	var bHasBase64 = !!(window.btoa);

+

+	var strDownloadMime = "image/octet-stream";

+

+	// ok, we're good

+	var readCanvasData = function(oCanvas) {

+		var iWidth = parseInt(oCanvas.width);

+		var iHeight = parseInt(oCanvas.height);

+		return oCanvas.getContext("2d").getImageData(0,0,iWidth,iHeight);

+	}

+

+	// base64 encodes either a string or an array of charcodes

+	var encodeData = function(data) {

+		var strData = "";

+		if (typeof data == "string") {

+			strData = data;

+		} else {

+			var aData = data;

+			for (var i = 0; i < aData.length; i++) {

+				strData += String.fromCharCode(aData[i]);

+			}

+		}

+		return btoa(strData);

+	}

+

+	// creates a base64 encoded string containing BMP data

+	// takes an imagedata object as argument

+	var createBMP = function(oData) {

+		var aHeader = [];

+	

+		var iWidth = oData.width;

+		var iHeight = oData.height;

+

+		aHeader.push(0x42); // magic 1

+		aHeader.push(0x4D); 

+	

+		var iFileSize = iWidth*iHeight*3 + 54; // total header size = 54 bytes

+		aHeader.push(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256);

+		aHeader.push(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256);

+		aHeader.push(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256);

+		aHeader.push(iFileSize % 256);

+

+		aHeader.push(0); // reserved

+		aHeader.push(0);

+		aHeader.push(0); // reserved

+		aHeader.push(0);

+

+		aHeader.push(54); // data offset

+		aHeader.push(0);

+		aHeader.push(0);

+		aHeader.push(0);

+

+		var aInfoHeader = [];

+		aInfoHeader.push(40); // info header size

+		aInfoHeader.push(0);

+		aInfoHeader.push(0);

+		aInfoHeader.push(0);

+

+		var iImageWidth = iWidth;

+		aInfoHeader.push(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256);

+		aInfoHeader.push(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256);

+		aInfoHeader.push(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256);

+		aInfoHeader.push(iImageWidth % 256);

+	

+		var iImageHeight = iHeight;

+		aInfoHeader.push(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256);

+		aInfoHeader.push(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256);

+		aInfoHeader.push(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256);

+		aInfoHeader.push(iImageHeight % 256);

+	

+		aInfoHeader.push(1); // num of planes

+		aInfoHeader.push(0);

+	

+		aInfoHeader.push(24); // num of bits per pixel

+		aInfoHeader.push(0);

+	

+		aInfoHeader.push(0); // compression = none

+		aInfoHeader.push(0);

+		aInfoHeader.push(0);

+		aInfoHeader.push(0);

+	

+		var iDataSize = iWidth*iHeight*3; 

+		aInfoHeader.push(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256);

+		aInfoHeader.push(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256);

+		aInfoHeader.push(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256);

+		aInfoHeader.push(iDataSize % 256); 

+	

+		for (var i = 0; i < 16; i++) {

+			aInfoHeader.push(0);	// these bytes not used

+		}

+	

+		var iPadding = (4 - ((iWidth * 3) % 4)) % 4;

+

+		var aImgData = oData.data;

+

+		var strPixelData = "";

+		var y = iHeight;

+		do {

+			var iOffsetY = iWidth*(y-1)*4;

+			var strPixelRow = "";

+			for (var x=0;x<iWidth;x++) {

+				var iOffsetX = 4*x;

+

+				strPixelRow += String.fromCharCode(aImgData[iOffsetY+iOffsetX+2]);

+				strPixelRow += String.fromCharCode(aImgData[iOffsetY+iOffsetX+1]);

+				strPixelRow += String.fromCharCode(aImgData[iOffsetY+iOffsetX]);

+			}

+			for (var c=0;c<iPadding;c++) {

+				strPixelRow += String.fromCharCode(0);

+			}

+			strPixelData += strPixelRow;

+		} while (--y);

+

+		return encodeData(aHeader.concat(aInfoHeader)) + encodeData(strPixelData);

+	}

+

+	// sends the generated file to the client

+	var saveFile = function(strData) {

+    if (!window.open(strData)) {

+      document.location.href = strData;

+    }

+	}

+

+	var makeDataURI = function(strData, strMime) {

+		return "data:" + strMime + ";base64," + strData;

+	}

+

+	// generates a <img> object containing the imagedata

+	var makeImageObject = function(strSource) {

+		var oImgElement = document.createElement("img");

+		oImgElement.src = strSource;

+		return oImgElement;

+	}

+

+	var scaleCanvas = function(oCanvas, iWidth, iHeight) {

+		if (iWidth && iHeight) {

+			var oSaveCanvas = document.createElement("canvas");

+			

+			oSaveCanvas.width = iWidth;

+			oSaveCanvas.height = iHeight;

+			oSaveCanvas.style.width = iWidth+"px";

+			oSaveCanvas.style.height = iHeight+"px";

+

+			var oSaveCtx = oSaveCanvas.getContext("2d");

+

+			oSaveCtx.drawImage(oCanvas, 0, 0, oCanvas.width, oCanvas.height, 0, 0, iWidth, iWidth);

+			

+			return oSaveCanvas;

+		}

+		return oCanvas;

+	}

+

+	return {

+		saveAsPNG : function(oCanvas, bReturnImg, iWidth, iHeight) {

+			if (!bHasDataURL) {

+				return false;

+			}

+			var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight);

+			var strData = oScaledCanvas.toDataURL("image/png");

+			if (bReturnImg) {

+				return makeImageObject(strData);

+			} else {

+				saveFile(strData.replace("image/png", strDownloadMime));

+			}

+			return true;

+		},

+

+		saveAsJPEG : function(oCanvas, bReturnImg, iWidth, iHeight) {

+			if (!bHasDataURL) {

+				return false;

+			}

+

+			var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight);

+			var strMime = "image/jpeg";

+			var strData = oScaledCanvas.toDataURL(strMime);

+	

+			// check if browser actually supports jpeg by looking for the mime type in the data uri.

+			// if not, return false

+			if (strData.indexOf(strMime) != 5) {

+				return false;

+			}

+

+			if (bReturnImg) {

+				return makeImageObject(strData);

+			} else {

+				saveFile(strData.replace(strMime, strDownloadMime));

+			}

+			return true;

+		},

+

+		saveAsBMP : function(oCanvas, bReturnImg, iWidth, iHeight) {

+			if (!(bHasImageData && bHasBase64)) {

+				return false;

+			}

+

+			var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight);

+

+			var oData = readCanvasData(oScaledCanvas);

+			var strImgData = createBMP(oData);

+			if (bReturnImg) {

+				return makeImageObject(makeDataURI(strImgData, "image/bmp"));

+			} else {

+				saveFile(makeDataURI(strImgData, strDownloadMime));

+			}

+			return true;

+		}

+	};

+

+})();

--- /dev/null
+++ b/js/flotr/lib/canvastext.js
@@ -1,1 +1,397 @@
-
+/**

+ * This code is released to the public domain by Jim Studt, 2007.

+ * He may keep some sort of up to date copy at http://www.federated.com/~jim/canvastext/


+ * A partial support for accentuated letters as been added too.

+ */

+var CanvasText = {

+	/** The letters definition. It is a list of letters, 

+	 * with their width, and the coordinates of points compositing them.

+	 * The syntax for the points is : [x, y], null value means "pen up"

+	 */

+  letters: {

+		'\n':{ width: -1, points: [] },

+    ' ': { width: 10, points: [] },

+    '!': { width: 10, points: [[5,21],[5,7],null,[5,2],[4,1],[5,0],[6,1],[5,2]] },

+    '"': { width: 16, points: [[4,21],[4,14],null,[12,21],[12,14]] },

+    '#': { width: 21, points: [[11,25],[4,-7],null,[17,25],[10,-7],null,[4,12],[18,12],null,[3,6],[17,6]] },

+    '$': { width: 20, points: [[8,25],[8,-4],null,[12,25],[12,-4],null,[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[4,14],[5,13],[7,12],[13,10],[15,9],[16,8],[17,6],[17,3],[15,1],[12,0],[8,0],[5,1],[3,3]] },

+    '%': { width: 24, points: [[21,21],[3,0],null,[8,21],[10,19],[10,17],[9,15],[7,14],[5,14],[3,16],[3,18],[4,20],[6,21],[8,21],null,[17,7],[15,6],[14,4],[14,2],[16,0],[18,0],[20,1],[21,3],[21,5],[19,7],[17,7]] },

+    '&': { width: 26, points: [[23,12],[23,13],[22,14],[21,14],[20,13],[19,11],[17,6],[15,3],[13,1],[11,0],[7,0],[5,1],[4,2],[3,4],[3,6],[4,8],[5,9],[12,13],[13,14],[14,16],[14,18],[13,20],[11,21],[9,20],[8,18],[8,16],[9,13],[11,10],[16,3],[18,1],[20,0],[22,0],[23,1],[23,2]] },

+    '\'':{ width: 10, points: [[5,19],[4,20],[5,21],[6,20],[6,18],[5,16],[4,15]] },

+    '(': { width: 14, points: [[11,25],[9,23],[7,20],[5,16],[4,11],[4,7],[5,2],[7,-2],[9,-5],[11,-7]] },

+    ')': { width: 14, points: [[3,25],[5,23],[7,20],[9,16],[10,11],[10,7],[9,2],[7,-2],[5,-5],[3,-7]] },

+    '*': { width: 16, points: [[8,21],[8,9],null,[3,18],[13,12],null,[13,18],[3,12]] },

+    '+': { width: 26, points: [[13,18],[13,0],null,[4,9],[22,9]] },

+    ',': { width: 10, points: [[6,1],[5,0],[4,1],[5,2],[6,1],[6,-1],[5,-3],[4,-4]] },

+    '-': { width: 26, points: [[4,9],[22,9]] },

+    '.': { width: 10, points: [[5,2],[4,1],[5,0],[6,1],[5,2]] },

+    '/': { width: 22, points: [[20,25],[2,-7]] },

+    '0': { width: 20, points: [[9,21],[6,20],[4,17],[3,12],[3,9],[4,4],[6,1],[9,0],[11,0],[14,1],[16,4],[17,9],[17,12],[16,17],[14,20],[11,21],[9,21]] },

+    '1': { width: 20, points: [[6,17],[8,18],[11,21],[11,0]] },

+    '2': { width: 20, points: [[4,16],[4,17],[5,19],[6,20],[8,21],[12,21],[14,20],[15,19],[16,17],[16,15],[15,13],[13,10],[3,0],[17,0]] },

+    '3': { width: 20, points: [[5,21],[16,21],[10,13],[13,13],[15,12],[16,11],[17,8],[17,6],[16,3],[14,1],[11,0],[8,0],[5,1],[4,2],[3,4]] },

+    '4': { width: 20, points: [[13,21],[3,7],[18,7],null,[13,21],[13,0]] },

+    '5': { width: 20, points: [[15,21],[5,21],[4,12],[5,13],[8,14],[11,14],[14,13],[16,11],[17,8],[17,6],[16,3],[14,1],[11,0],[8,0],[5,1],[4,2],[3,4]] },

+    '6': { width: 20, points: [[16,18],[15,20],[12,21],[10,21],[7,20],[5,17],[4,12],[4,7],[5,3],[7,1],[10,0],[11,0],[14,1],[16,3],[17,6],[17,7],[16,10],[14,12],[11,13],[10,13],[7,12],[5,10],[4,7]] },

+    '7': { width: 20, points: [[17,21],[7,0],null,[3,21],[17,21]] },

+    '8': { width: 20, points: [[8,21],[5,20],[4,18],[4,16],[5,14],[7,13],[11,12],[14,11],[16,9],[17,7],[17,4],[16,2],[15,1],[12,0],[8,0],[5,1],[4,2],[3,4],[3,7],[4,9],[6,11],[9,12],[13,13],[15,14],[16,16],[16,18],[15,20],[12,21],[8,21]] },

+    '9': { width: 20, points: [[16,14],[15,11],[13,9],[10,8],[9,8],[6,9],[4,11],[3,14],[3,15],[4,18],[6,20],[9,21],[10,21],[13,20],[15,18],[16,14],[16,9],[15,4],[13,1],[10,0],[8,0],[5,1],[4,3]] },

+    ':': { width: 10, points: [[5,14],[4,13],[5,12],[6,13],[5,14],null,[5,2],[4,1],[5,0],[6,1],[5,2]] },

+    ';': { width: 10, points: [[5,14],[4,13],[5,12],[6,13],[5,14],null,[6,1],[5,0],[4,1],[5,2],[6,1],[6,-1],[5,-3],[4,-4]] },

+    '<': { width: 24, points: [[20,18],[4,9],[20,0]] },

+    '=': { width: 26, points: [[4,12],[22,12],null,[4,6],[22,6]] },

+    '>': { width: 24, points: [[4,18],[20,9],[4,0]] },

+    '?': { width: 18, points: [[3,16],[3,17],[4,19],[5,20],[7,21],[11,21],[13,20],[14,19],[15,17],[15,15],[14,13],[13,12],[9,10],[9,7],null,[9,2],[8,1],[9,0],[10,1],[9,2]] },

+    '@': { width: 27, points: [[18,13],[17,15],[15,16],[12,16],[10,15],[9,14],[8,11],[8,8],[9,6],[11,5],[14,5],[16,6],[17,8],null,[12,16],[10,14],[9,11],[9,8],[10,6],[11,5],null,[18,16],[17,8],[17,6],[19,5],[21,5],[23,7],[24,10],[24,12],[23,15],[22,17],[20,19],[18,20],[15,21],[12,21],[9,20],[7,19],[5,17],[4,15],[3,12],[3,9],[4,6],[5,4],[7,2],[9,1],[12,0],[15,0],[18,1],[20,2],[21,3],null,[19,16],[18,8],[18,6],[19,5]] },

+    'A': { width: 18, points: [[9,21],[1,0],null,[9,21],[17,0],null,[4,7],[14,7]] },

+    'B': { width: 21, points: [[4,21],[4,0],null,[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],null,[4,11],[13,11],[16,10],[17,9],[18,7],[18,4],[17,2],[16,1],[13,0],[4,0]] },

+    'C': { width: 21, points: [[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5]] },

+    'D': { width: 21, points: [[4,21],[4,0],null,[4,21],[11,21],[14,20],[16,18],[17,16],[18,13],[18,8],[17,5],[16,3],[14,1],[11,0],[4,0]] },

+    'E': { width: 19, points: [[4,21],[4,0],null,[4,21],[17,21],null,[4,11],[12,11],null,[4,0],[17,0]] },

+    'F': { width: 18, points: [[4,21],[4,0],null,[4,21],[17,21],null,[4,11],[12,11]] },

+    'G': { width: 21, points: [[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[18,8],null,[13,8],[18,8]] },

+    'H': { width: 22, points: [[4,21],[4,0],null,[18,21],[18,0],null,[4,11],[18,11]] },

+    'I': { width: 8,  points: [[4,21],[4,0]] },

+    'J': { width: 16, points: [[12,21],[12,5],[11,2],[10,1],[8,0],[6,0],[4,1],[3,2],[2,5],[2,7]] },

+    'K': { width: 21, points: [[4,21],[4,0],null,[18,21],[4,7],null,[9,12],[18,0]] },

+    'L': { width: 17, points: [[4,21],[4,0],null,[4,0],[16,0]] },

+    'M': { width: 24, points: [[4,21],[4,0],null,[4,21],[12,0],null,[20,21],[12,0],null,[20,21],[20,0]] },

+    'N': { width: 22, points: [[4,21],[4,0],null,[4,21],[18,0],null,[18,21],[18,0]] },

+    'O': { width: 22, points: [[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21]] },

+    'P': { width: 21, points: [[4,21],[4,0],null,[4,21],[13,21],[16,20],[17,19],[18,17],[18,14],[17,12],[16,11],[13,10],[4,10]] },

+    'Q': { width: 22, points: [[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21],null,[12,4],[18,-2]] },

+    'R': { width: 21, points: [[4,21],[4,0],null,[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],[4,11],null,[11,11],[18,0]] },

+    'S': { width: 20, points: [[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[4,14],[5,13],[7,12],[13,10],[15,9],[16,8],[17,6],[17,3],[15,1],[12,0],[8,0],[5,1],[3,3]] },

+    'T': { width: 16, points: [[8,21],[8,0],null,[1,21],[15,21]] },

+    'U': { width: 22, points: [[4,21],[4,6],[5,3],[7,1],[10,0],[12,0],[15,1],[17,3],[18,6],[18,21]] },

+    'V': { width: 18, points: [[1,21],[9,0],null,[17,21],[9,0]] },

+    'W': { width: 24, points: [[2,21],[7,0],null,[12,21],[7,0],null,[12,21],[17,0],null,[22,21],[17,0]] },

+    'X': { width: 20, points: [[3,21],[17,0],null,[17,21],[3,0]] },

+    'Y': { width: 18, points: [[1,21],[9,11],[9,0],null,[17,21],[9,11]] },

+    'Z': { width: 20, points: [[17,21],[3,0],null,[3,21],[17,21],null,[3,0],[17,0]] },

+    '[': { width: 14, points: [[4,25],[4,-7],null,[5,25],[5,-7],null,[4,25],[11,25],null,[4,-7],[11,-7]] },

+    '\\':{ width: 14, points: [[0,21],[14,-3]] },

+    ']': { width: 14, points: [[9,25],[9,-7],null,[10,25],[10,-7],null,[3,25],[10,25],null,[3,-7],[10,-7]] },

+    '^': { width: 14, points: [[3,10],[8,18],[13,10]] },

+    '_': { width: 16, points: [[0,-2],[16,-2]] },

+    '`': { width: 10, points: [[6,21],[5,20],[4,18],[4,16],[5,15],[6,16],[5,17]] },

+    'a': { width: 19, points: [[15,14],[15,0],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },

+    'b': { width: 19, points: [[4,21],[4,0],null,[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]] },

+    'c': { width: 18, points: [[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },

+    'd': { width: 19, points: [[15,21],[15,0],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },

+    'e': { width: 18, points: [[3,8],[15,8],[15,10],[14,12],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },

+    'f': { width: 12, points: [[10,21],[8,21],[6,20],[5,17],[5,0],null,[2,14],[9,14]] },

+    'g': { width: 19, points: [[15,14],[15,-2],[14,-5],[13,-6],[11,-7],[8,-7],[6,-6],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },

+    'h': { width: 19, points: [[4,21],[4,0],null,[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]] },

+    'i': { width: 8,  points: [[3,21],[4,20],[5,21],[4,22],[3,21],null,[4,14],[4,0]] },

+    'j': { width: 10, points: [[5,21],[6,20],[7,21],[6,22],[5,21],null,[6,14],[6,-3],[5,-6],[3,-7],[1,-7]] },

+    'k': { width: 17, points: [[4,21],[4,0],null,[14,14],[4,4],null,[8,8],[15,0]] },

+    'l': { width: 8,  points: [[4,21],[4,0]] },

+    'm': { width: 30, points: [[4,14],[4,0],null,[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0],null,[15,10],[18,13],[20,14],[23,14],[25,13],[26,10],[26,0]] },

+    'n': { width: 19, points: [[4,14],[4,0],null,[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]] },

+    'o': { width: 19, points: [[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3],[16,6],[16,8],[15,11],[13,13],[11,14],[8,14]] },

+    'p': { width: 19, points: [[4,14],[4,-7],null,[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]] },

+    'q': { width: 19, points: [[15,14],[15,-7],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },

+    'r': { width: 13, points: [[4,14],[4,0],null,[4,8],[5,11],[7,13],[9,14],[12,14]] },

+    's': { width: 17, points: [[14,11],[13,13],[10,14],[7,14],[4,13],[3,11],[4,9],[6,8],[11,7],[13,6],[14,4],[14,3],[13,1],[10,0],[7,0],[4,1],[3,3]] },

+    't': { width: 12, points: [[5,21],[5,4],[6,1],[8,0],[10,0],null,[2,14],[9,14]] },

+    'u': { width: 19, points: [[4,14],[4,4],[5,1],[7,0],[10,0],[12,1],[15,4],null,[15,14],[15,0]] },

+    'v': { width: 16, points: [[2,14],[8,0],null,[14,14],[8,0]] },

+    'w': { width: 22, points: [[3,14],[7,0],null,[11,14],[7,0],null,[11,14],[15,0],null,[19,14],[15,0]] },

+    'x': { width: 17, points: [[3,14],[14,0],null,[14,14],[3,0]] },

+    'y': { width: 16, points: [[2,14],[8,0],null,[14,14],[8,0],[6,-4],[4,-6],[2,-7],[1,-7]] },

+    'z': { width: 17, points: [[14,14],[3,0],null,[3,14],[14,14],null,[3,0],[14,0]] },

+    '{': { width: 14, points: [[9,25],[7,24],[6,23],[5,21],[5,19],[6,17],[7,16],[8,14],[8,12],[6,10],null,[7,24],[6,22],[6,20],[7,18],[8,17],[9,15],[9,13],[8,11],[4,9],[8,7],[9,5],[9,3],[8,1],[7,0],[6,-2],[6,-4],[7,-6],null,[6,8],[8,6],[8,4],[7,2],[6,1],[5,-1],[5,-3],[6,-5],[7,-6],[9,-7]] },

+    '|': { width: 8,  points: [[4,25],[4,-7]] },

+    '}': { width: 14, points: [[5,25],[7,24],[8,23],[9,21],[9,19],[8,17],[7,16],[6,14],[6,12],[8,10],null,[7,24],[8,22],[8,20],[7,18],[6,17],[5,15],[5,13],[6,11],[10,9],[6,7],[5,5],[5,3],[6,1],[7,0],[8,-2],[8,-4],[7,-6],null,[8,8],[6,6],[6,4],[7,2],[8,1],[9,-1],[9,-3],[8,-5],[7,-6],[5,-7]] },

+    '~': { width: 24, points: [[3,6],[3,8],[4,11],[6,12],[8,12],[10,11],[14,8],[16,7],[18,7],[20,8],[21,10],null,[3,8],[4,10],[6,11],[8,11],[10,10],[14,7],[16,6],[18,6],[20,7],[21,10],[21,12]] },

















+  },

+  

+  specialchars: {

+  	'pi': { width: 19, points: [[6,14],[6,0],null,[14,14],[14,0],null,[2,13],[6,16],[13,13],[17,16]] }

+  },

+  

+  /** Diacritics, used to draw accentuated letters */

+  diacritics: {



+    '`': { entity: 'grave', points: [[7,22],[12,19]] },

+    '^': { entity: 'circ',  points: [[5.5,19],[9.5,23],[12.5,19]] },


+    '~': { entity: 'tilde', points: [[4,18],[7,22],[10,18],[13,22]] }

+  },

+  

+  /** The default font styling */

+  style: {

+    size: 8,          // font height in pixels

+    font: null,       // not yet implemented

+    color: '#000000', // 

+    weight: 1,        // float, 1 for 'normal'

+    halign: 'l',      // l: left, r: right, c: center

+    valign: 'b',      // t: top, m: middle, b: bottom 

+    adjustAlign: false, // modifies the alignments if the angle is different from 0 to make the spin point always at the good position

+    angle: 0,         // in radians, anticlockwise

+    tracking: 1,      // space between the letters, float, 1 for 'normal'

+    boundingBoxColor: '#ff0000', //null // color of the bounding box (null to hide), can be used for debug and font drawing

+    originPointColor: '#000000' //null // color of the bounding box (null to hide), can be used for debug and font drawing

+  },

+  

+  debug: false,

+  _bufferLexemes: {},

+  

+  /** Get the letter data corresponding to a char

+   * @param {String} ch - The char

+   */

+  letter: function(ch) {

+    return CanvasText.letters[ch];

+  },

+  

+  parseLexemes: function(str) {

+    if (CanvasText._bufferLexemes[str]) 

+      return CanvasText._bufferLexemes[str];

+    

+  	var i, c, matches = str.match(/&[A-Za-z]{2,5};|\s|./g);

+  	var result = [], chars = [];

+  	for (i = 0; i < matches.length; i++) {

+  		c = matches[i];

+  		if (c.length == 1) 

+  			chars.push(c);

+  		else {

+  			var entity = c.substring(1, c.length-1);

+  			if (CanvasText.specialchars[entity]) 

+  				chars.push(entity);

+  			else

+  				chars = chars.concat(c.toArray());

+  		}

+  	}

+  	for (i = 0; i < chars.length; i++) {

+  		c = chars[i];

+  		if (c = CanvasText.letters[c] || CanvasText.specialchars[c])

+  		  result.push(c);

+  	}

+  	return CanvasText._bufferLexemes[str] = result.compact();

+  },

+

+  /** Get the font ascent for a given style

+   * @param {Object} style - The reference style

+   */

+  ascent: function(style) {

+  	style = style || {};

+    return (style.size || CanvasText.style.size);

+  },

+  

+  /** Get the font descent for a given style 

+   * @param {Object} style - The reference style

+   * */

+  descent: function(style) {

+  	style = style || {};

+    return 7.0*(style.size || CanvasText.style.size)/25.0;

+  },

+  

+  /** Measure the text horizontal size 

+   * @param {String} str - The text

+   * @param {Object} style - Text style

+   * */

+  measure: function(str, style) {

+    if (!str) return;

+    style = style || {};

+    

+    var i, width, lexemes = CanvasText.parseLexemes(str),

+        total = 0;

+

+    for (i = lexemes.length-1; i > -1; --i) {

+    	c = lexemes[i];

+    	width = (c.diacritic) ? CanvasText.letter(c.letter).width : c.width;

+      total += width * (style.tracking || CanvasText.style.tracking) * (style.size || CanvasText.style.size) / 25.0;

+    }

+    return total;

+  },

+  

+  getDimensions: function(str, style) {

+    var width = CanvasText.measure(str, style),

+        height = style.size || CanvasText.style.size,

+        angle = style.angle || CanvasText.style.angle;

+

+    if (style.angle == 0) return {width: width, height: height};

+    return {

+      width:  Math.abs(Math.cos(angle) * width) + Math.abs(Math.sin(angle) * height),

+      height: Math.abs(Math.sin(angle) * width) + Math.abs(Math.cos(angle) * height)

+    }

+  },

+  

+  getBestAlign: function(angle, style) {

+    angle += CanvasText.getAngleFromAlign(style.halign, style.valign);

+    var a = {h:'c', v:'m'};

+    if (Math.round(Math.cos(angle)*1000)/1000 != 0) 

+      a.h = (Math.cos(angle) > 0 ? 'r' : 'l');

+    

+    if (Math.round(Math.sin(angle)*1000)/1000 != 0) 

+      a.v = (Math.sin(angle) > 0 ? 't' : 'b');

+    return a;

+  },

+  

+  getAngleFromAlign: function(halign, valign) {

+    var pi = Math.PI, table = {

+      'rm': 0,

+      'rt': pi/4,

+      'ct': pi/2,

+      'lt': 3*(pi/4),

+      'lm': pi,

+      'lb': -3*(pi/4),

+      'cb': -pi/2,

+      'rb': -pi/4,

+      'cm': 0

+    }

+    return table[halign+valign];

+  },

+  

+  /** Draws serie of points at given coordinates 

+   * @param {Canvas context} ctx - The canvas context

+   * @param {Array} points - The points to draw

+   * @param {Number} x - The X coordinate

+   * @param {Number} y - The Y coordinate

+   * @param {Number} mag - The scale 

+   */

+  drawPoints: function (ctx, points, x, y, mag, offset) {

+    var i, a, penUp = true, needStroke = 0;

+    offset = offset || {x:0, y:0};

+    

+    ctx.beginPath();

+    for (i = 0; i < points.length; i++) {

+      a = points[i];

+      if (!a) {

+        penUp = true;

+        continue;

+      }

+      if (penUp) {

+        ctx.moveTo(x + a[0]*mag + offset.x, y - a[1]*mag + offset.y);

+        penUp = false;

+      }

+      else {

+        ctx.lineTo(x + a[0]*mag + offset.x, y - a[1]*mag + offset.y);

+      }

+    }

+    ctx.stroke();

+  },

+  

+  /** Draws a text at given coordinates and with a given style

+   * @param {Canvas context} ctx - The canvas context

+   * @param {String} str - The text to draw

+   * @param {Number} xOrig - The X coordinate

+   * @param {Number} yOrig - The Y coordinate

+   * @param {Object} style - The font style

+   */

+  draw: function(ctx, str, xOrig, yOrig, style) {

+    if (!str) return;

+    style = style || CanvasText.style;

+    style.halign = style.halign || CanvasText.style.halign;

+    style.valign = style.valign || CanvasText.style.valign;

+    style.angle = style.angle || CanvasText.style.angle;

+    style.size = style.size || CanvasText.style.size;

+    style.adjustAlign = style.adjustAlign || CanvasText.style.adjustAlign;

+    

+    var i, c, total = 0,

+        mag = style.size / 25.0,

+        x = 0, y = 0,

+        lexemes = CanvasText.parseLexemes(str);

+    

+    var offset = {x:0, y:0}, 

+        measure = CanvasText.measure(str, style),

+        align;

+        

+    if (style.adjustAlign) {

+      align = CanvasText.getBestAlign(style.angle, style);

+      style.halign = align.h;

+      style.valign = align.v;

+    }

+        

+    switch (style.halign) {

+      case 'l': break;

+      case 'c': offset.x = -measure / 2; break;

+      case 'r': offset.x = -measure; break;

+    }

+    

+    switch (style.valign) {

+      case 'b': break;

+      case 'm': offset.y = style.size / 2; break;

+      case 't': offset.y = style.size; break;

+    }

+    

+    ctx.save();

+    ctx.translate(xOrig, yOrig);

+    ctx.rotate(style.angle);

+    ctx.lineCap = "round";

+    ctx.lineWidth = 2.0 * mag * (style.weight || CanvasText.style.weight);

+    ctx.strokeStyle = style.color || CanvasText.style.color;

+    

+    for (i = 0; i < lexemes.length; i++) {

+    	c = lexemes[i];

+      if (c.width == -1) {

+        x = 0;

+        y = style.size * 1.4;

+        continue;

+      }

+    

+      var points = c.points,

+          width = c.width;

+          

+      if (c.diacritic) {

+        var dia = CanvasText.diacritics[c.diacritic];

+        var char = CanvasText.letter(c.letter);

+

+        CanvasText.drawPoints(ctx, dia.points, x, y - (c.letter.toUpperCase() == c.letter ? 3 : 0), mag, offset);

+        points = char.points;

+        width = char.width;

+      }

+

+      CanvasText.drawPoints(ctx, points, x, y, mag, offset);

+      

+      if (CanvasText.debug) {

+      	ctx.save();

+        ctx.lineJoin = "miter";

+        ctx.lineWidth = 0.5;

+        ctx.strokeStyle = (style.boundingBoxColor || CanvasText.style.boundingBoxColor);

+      	ctx.strokeRect(x+offset.x, y+offset.y, width*mag, -style.size);

+        

+        ctx.fillStyle = (style.originPointColor || CanvasText.style.originPointColor);

+        ctx.beginPath();

+        ctx.arc(0, 0, 1.5, 0, Math.PI*2, true);

+        ctx.fill();

+        

+      	ctx.restore();

+      }

+      

+      x += width*mag*(style.tracking || CanvasText.style.tracking);

+    }

+    ctx.restore();

+    return total;

+  },

+  

+  /** Enables the text function for a Canvas context

+   * @param {Canvas context} ctx - The canvas context

+   */

+  enable: function(ctx) {

+    ctx.drawText    = function(text, x, y, style) { return CanvasText.draw(ctx, text, x, y, style); };

+    ctx.measureText = function(text, style) { return CanvasText.measure(text, style); };

+    ctx.getTextBounds = function(text, style) { return CanvasText.getDimensions(text, style); };

+    ctx.fontAscent  = function(style) { return CanvasText.ascent(style); };

+    ctx.fontDescent = function(style) { return CanvasText.descent(style); };

+  }

+};

--- /dev/null
+++ b/js/flotr/lib/excanvas.js
@@ -1,1 +1,885 @@
-
+// Copyright 2006 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+// Known Issues:
+//
+// * Patterns are not implemented.
+// * Radial gradient are not implemented. The VML version of these look very
+//   different from the canvas one.
+// * Clipping paths are not implemented.
+// * Coordsize. The width and height attribute have higher priority than the
+//   width and height style values which isn't correct.
+// * Painting mode isn't implemented.
+// * Canvas width/height should is using content-box by default. IE in
+//   Quirks mode will draw the canvas using border-box. Either change your
+//   doctype to HTML5
+//   (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
+//   or use Box Sizing Behavior from WebFX
+//   (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
+// * Non uniform scaling does not correctly scale strokes.
+// * Optimize. There is always room for speed improvements.
+
+// Only add this code if we do not already have a canvas implementation
+if (!document.createElement('canvas').getContext) {
+
+(function() {
+
+  // alias some functions to make (compiled) code shorter
+  var m = Math;
+  var mr = m.round;
+  var ms = m.sin;
+  var mc = m.cos;
+  var abs = m.abs;
+  var sqrt = m.sqrt;
+
+  // this is used for sub pixel precision
+  var Z = 10;
+  var Z2 = Z / 2;
+
+  /**
+   * This funtion is assigned to the <canvas> elements as element.getContext().
+   * @this {HTMLElement}
+   * @return {CanvasRenderingContext2D_}
+   */
+  function getContext() {
+    return this.context_ ||
+        (this.context_ = new CanvasRenderingContext2D_(this));
+  }
+
+  var slice = Array.prototype.slice;
+
+  /**
+   * Binds a function to an object. The returned function will always use the
+   * passed in {@code obj} as {@code this}.
+   *
+   * Example:
+   *
+   *   g = bind(f, obj, a, b)
+   *   g(c, d) // will do f.call(obj, a, b, c, d)
+   *
+   * @param {Function} f The function to bind the object to
+   * @param {Object} obj The object that should act as this when the function
+   *     is called
+   * @param {*} var_args Rest arguments that will be used as the initial
+   *     arguments when the function is called
+   * @return {Function} A new function that has bound this
+   */
+  function bind(f, obj, var_args) {
+    var a = slice.call(arguments, 2);
+    return function() {
+      return f.apply(obj, a.concat(slice.call(arguments)));
+    };
+  }
+
+  var G_vmlCanvasManager_ = {
+    init: function(opt_doc) {
+      if (/MSIE/.test(navigator.userAgent) && !window.opera) {
+        var doc = opt_doc || document;
+        // Create a dummy element so that IE will allow canvas elements to be
+        // recognized.
+        doc.createElement('canvas');
+        doc.attachEvent('onreadystatechange', bind(this.init_, this, doc));
+      }
+    },
+
+    init_: function(doc) {
+      // create xmlns
+      if (!doc.namespaces['g_vml_']) {
+        doc.namespaces.add('g_vml_', 'urn:schemas-microsoft-com:vml',
+                           '#default#VML');
+
+      }
+      if (!doc.namespaces['g_o_']) {
+        doc.namespaces.add('g_o_', 'urn:schemas-microsoft-com:office:office',
+                           '#default#VML');
+      }
+
+      // Setup default CSS.  Only add one style sheet per document
+      if (!doc.styleSheets['ex_canvas_']) {
+        var ss = doc.createStyleSheet();
+        ss.owningElement.id = 'ex_canvas_';
+        ss.cssText = 'canvas{display:inline-block;overflow:hidden;' +
+            // default size is 300x150 in Gecko and Opera
+            'text-align:left;width:300px;height:150px}' +
+            'g_vml_\\:*{behavior:url(#default#VML)}' +
+            'g_o_\\:*{behavior:url(#default#VML)}';
+
+      }
+
+      // find all canvas elements
+      var els = doc.getElementsByTagName('canvas');
+      for (var i = 0; i < els.length; i++) {
+        this.initElement(els[i]);
+      }
+    },
+
+    /**
+     * Public initializes a canvas element so that it can be used as canvas
+     * element from now on. This is called automatically before the page is
+     * loaded but if you are creating elements using createElement you need to
+     * make sure this is called on the element.
+     * @param {HTMLElement} el The canvas element to initialize.
+     * @return {HTMLElement} the element that was created.
+     */
+    initElement: function(el) {
+      if (!el.getContext) {
+
+        el.getContext = getContext;
+
+        // Remove fallback content. There is no way to hide text nodes so we
+        // just remove all childNodes. We could hide all elements and remove
+        // text nodes but who really cares about the fallback content.
+        el.innerHTML = '';
+
+        // do not use inline function because that will leak memory
+        el.attachEvent('onpropertychange', onPropertyChange);
+        el.attachEvent('onresize', onResize);
+
+        var attrs = el.attributes;
+        if (attrs.width && attrs.width.specified) {
+          // TODO: use runtimeStyle and coordsize
+          // el.getContext().setWidth_(attrs.width.nodeValue);
+          el.style.width = attrs.width.nodeValue + 'px';
+        } else {
+          el.width = el.clientWidth;
+        }
+        if (attrs.height && attrs.height.specified) {
+          // TODO: use runtimeStyle and coordsize
+          // el.getContext().setHeight_(attrs.height.nodeValue);
+          el.style.height = attrs.height.nodeValue + 'px';
+        } else {
+          el.height = el.clientHeight;
+        }
+        //el.getContext().setCoordsize_()
+      }
+      return el;
+    }
+  };
+
+  function onPropertyChange(e) {
+    var el = e.srcElement;
+
+    switch (e.propertyName) {
+      case 'width':
+        el.style.width = el.attributes.width.nodeValue + 'px';
+        el.getContext().clearRect();
+        break;
+      case 'height':
+        el.style.height = el.attributes.height.nodeValue + 'px';
+        el.getContext().clearRect();
+        break;
+    }
+  }
+
+  function onResize(e) {
+    var el = e.srcElement;
+    if (el.firstChild) {
+      el.firstChild.style.width =  el.clientWidth + 'px';
+      el.firstChild.style.height = el.clientHeight + 'px';
+    }
+  }
+
+  G_vmlCanvasManager_.init();
+
+  // precompute "00" to "FF"
+  var dec2hex = [];
+  for (var i = 0; i < 16; i++) {
+    for (var j = 0; j < 16; j++) {
+      dec2hex[i * 16 + j] = i.toString(16) + j.toString(16);
+    }
+  }
+
+  function createMatrixIdentity() {
+    return [
+      [1, 0, 0],
+      [0, 1, 0],
+      [0, 0, 1]
+    ];
+  }
+
+  function matrixMultiply(m1, m2) {
+    var result = createMatrixIdentity();
+
+    for (var x = 0; x < 3; x++) {
+      for (var y = 0; y < 3; y++) {
+        var sum = 0;
+
+        for (var z = 0; z < 3; z++) {
+          sum += m1[x][z] * m2[z][y];
+        }
+
+        result[x][y] = sum;
+      }
+    }
+    return result;
+  }
+
+  function copyState(o1, o2) {
+    o2.fillStyle     = o1.fillStyle;
+    o2.lineCap       = o1.lineCap;
+    o2.lineJoin      = o1.lineJoin;
+    o2.lineWidth     = o1.lineWidth;
+    o2.miterLimit    = o1.miterLimit;
+    o2.shadowBlur    = o1.shadowBlur;
+    o2.shadowColor   = o1.shadowColor;
+    o2.shadowOffsetX = o1.shadowOffsetX;
+    o2.shadowOffsetY = o1.shadowOffsetY;
+    o2.strokeStyle   = o1.strokeStyle;
+    o2.globalAlpha   = o1.globalAlpha;
+    o2.arcScaleX_    = o1.arcScaleX_;
+    o2.arcScaleY_    = o1.arcScaleY_;
+    o2.lineScale_    = o1.lineScale_;
+  }
+
+  function processStyle(styleString) {
+    var str, alpha = 1;
+
+    styleString = String(styleString);
+    if (styleString.substring(0, 3) == 'rgb') {
+      var start = styleString.indexOf('(', 3);
+      var end = styleString.indexOf(')', start + 1);
+      var guts = styleString.substring(start + 1, end).split(',');
+
+      str = '#';
+      for (var i = 0; i < 3; i++) {
+        str += dec2hex[Number(guts[i])];
+      }
+
+      if (guts.length == 4 && styleString.substr(3, 1) == 'a') {
+        alpha = guts[3];
+      }
+    } else {
+      str = styleString;
+    }
+
+    return {color: str, alpha: alpha};
+  }
+
+  function processLineCap(lineCap) {
+    switch (lineCap) {
+      case 'butt':
+        return 'flat';
+      case 'round':
+        return 'round';
+      case 'square':
+      default:
+        return 'square';
+    }
+  }
+
+  /**
+   * This class implements CanvasRenderingContext2D interface as described by
+   * the WHATWG.
+   * @param {HTMLElement} surfaceElement The element that the 2D context should
+   * be associated with
+   */
+  function CanvasRenderingContext2D_(surfaceElement) {
+    this.m_ = createMatrixIdentity();
+
+    this.mStack_ = [];
+    this.aStack_ = [];
+    this.currentPath_ = [];
+
+    // Canvas context properties
+    this.strokeStyle = '#000';
+    this.fillStyle = '#000';
+
+    this.lineWidth = 1;
+    this.lineJoin = 'miter';
+    this.lineCap = 'butt';
+    this.miterLimit = Z * 1;
+    this.globalAlpha = 1;
+    this.canvas = surfaceElement;
+
+    var el = surfaceElement.ownerDocument.createElement('div');
+    el.style.width =  surfaceElement.clientWidth + 'px';
+    el.style.height = surfaceElement.clientHeight + 'px';
+    el.style.overflow = 'hidden';
+    el.style.position = 'absolute';
+    surfaceElement.appendChild(el);
+
+    this.element_ = el;
+    this.arcScaleX_ = 1;
+    this.arcScaleY_ = 1;
+    this.lineScale_ = 1;
+  }
+
+  var contextPrototype = CanvasRenderingContext2D_.prototype;
+  contextPrototype.clearRect = function() {
+    this.element_.innerHTML = '';
+  };
+
+  contextPrototype.beginPath = function() {
+    // TODO: Branch current matrix so that save/restore has no effect
+    //       as per safari docs.
+    this.currentPath_ = [];
+  };
+
+  contextPrototype.moveTo = function(aX, aY) {
+    var p = this.getCoords_(aX, aY);
+    this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y});
+    this.currentX_ = p.x;
+    this.currentY_ = p.y;
+  };
+
+  contextPrototype.lineTo = function(aX, aY) {
+    var p = this.getCoords_(aX, aY);
+    this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y});
+
+    this.currentX_ = p.x;
+    this.currentY_ = p.y;
+  };
+
+  contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
+                                            aCP2x, aCP2y,
+                                            aX, aY) {
+    var p = this.getCoords_(aX, aY);
+    var cp1 = this.getCoords_(aCP1x, aCP1y);
+    var cp2 = this.getCoords_(aCP2x, aCP2y);
+    bezierCurveTo(this, cp1, cp2, p);
+  };
+
+  // Helper function that takes the already fixed cordinates.
+  function bezierCurveTo(self, cp1, cp2, p) {
+    self.currentPath_.push({
+      type: 'bezierCurveTo',
+      cp1x: cp1.x,
+      cp1y: cp1.y,
+      cp2x: cp2.x,
+      cp2y: cp2.y,
+      x: p.x,
+      y: p.y
+    });
+    self.currentX_ = p.x;
+    self.currentY_ = p.y;
+  }
+
+  contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
+    // the following is lifted almost directly from
+    // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
+
+    var cp = this.getCoords_(aCPx, aCPy);
+    var p = this.getCoords_(aX, aY);
+
+    var cp1 = {
+      x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_),
+      y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_)
+    };
+    var cp2 = {
+      x: cp1.x + (p.x - this.currentX_) / 3.0,
+      y: cp1.y + (p.y - this.currentY_) / 3.0
+    };
+
+    bezierCurveTo(this, cp1, cp2, p);
+  };
+
+  contextPrototype.arc = function(aX, aY, aRadius,
+                                  aStartAngle, aEndAngle, aClockwise) {
+    aRadius *= Z;
+    var arcType = aClockwise ? 'at' : 'wa';
+
+    var xStart = aX + mc(aStartAngle) * aRadius - Z2;
+    var yStart = aY + ms(aStartAngle) * aRadius - Z2;
+
+    var xEnd = aX + mc(aEndAngle) * aRadius - Z2;
+    var yEnd = aY + ms(aEndAngle) * aRadius - Z2;
+
+    // IE won't render arches drawn counter clockwise if xStart == xEnd.
+    if (xStart == xEnd && !aClockwise) {
+      xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something
+                       // that can be represented in binary
+    }
+
+    var p = this.getCoords_(aX, aY);
+    var pStart = this.getCoords_(xStart, yStart);
+    var pEnd = this.getCoords_(xEnd, yEnd);
+
+    this.currentPath_.push({type: arcType,
+                           x: p.x,
+                           y: p.y,
+                           radius: aRadius,
+                           xStart: pStart.x,
+                           yStart: pStart.y,
+                           xEnd: pEnd.x,
+                           yEnd: pEnd.y});
+
+  };
+
+  contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
+    this.moveTo(aX, aY);
+    this.lineTo(aX + aWidth, aY);
+    this.lineTo(aX + aWidth, aY + aHeight);
+    this.lineTo(aX, aY + aHeight);
+    this.closePath();
+  };
+
+  contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
+    var oldPath = this.currentPath_;
+    this.beginPath();
+
+    this.moveTo(aX, aY);
+    this.lineTo(aX + aWidth, aY);
+    this.lineTo(aX + aWidth, aY + aHeight);
+    this.lineTo(aX, aY + aHeight);
+    this.closePath();
+    this.stroke();
+
+    this.currentPath_ = oldPath;
+  };
+
+  contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
+    var oldPath = this.currentPath_;
+    this.beginPath();
+
+    this.moveTo(aX, aY);
+    this.lineTo(aX + aWidth, aY);
+    this.lineTo(aX + aWidth, aY + aHeight);
+    this.lineTo(aX, aY + aHeight);
+    this.closePath();
+    this.fill();
+
+    this.currentPath_ = oldPath;
+  };
+
+  contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
+    var gradient = new CanvasGradient_('gradient');
+    gradient.x0_ = aX0;
+    gradient.y0_ = aY0;
+    gradient.x1_ = aX1;
+    gradient.y1_ = aY1;
+    return gradient;
+  };
+
+  contextPrototype.createRadialGradient = function(aX0, aY0, aR0,
+                                                   aX1, aY1, aR1) {
+    var gradient = new CanvasGradient_('gradientradial');
+    gradient.x0_ = aX0;
+    gradient.y0_ = aY0;
+    gradient.r0_ = aR0;
+    gradient.x1_ = aX1;
+    gradient.y1_ = aY1;
+    gradient.r1_ = aR1;
+    return gradient;
+  };
+
+  contextPrototype.drawImage = function(image, var_args) {
+    var dx, dy, dw, dh, sx, sy, sw, sh;
+
+    // to find the original width we overide the width and height
+    var oldRuntimeWidth = image.runtimeStyle.width;
+    var oldRuntimeHeight = image.runtimeStyle.height;
+    image.runtimeStyle.width = 'auto';
+    image.runtimeStyle.height = 'auto';
+
+    // get the original size
+    var w = image.width;
+    var h = image.height;
+
+    // and remove overides
+    image.runtimeStyle.width = oldRuntimeWidth;
+    image.runtimeStyle.height = oldRuntimeHeight;
+
+    if (arguments.length == 3) {
+      dx = arguments[1];
+      dy = arguments[2];
+      sx = sy = 0;
+      sw = dw = w;
+      sh = dh = h;
+    } else if (arguments.length == 5) {
+      dx = arguments[1];
+      dy = arguments[2];
+      dw = arguments[3];
+      dh = arguments[4];
+      sx = sy = 0;
+      sw = w;
+      sh = h;
+    } else if (arguments.length == 9) {
+      sx = arguments[1];
+      sy = arguments[2];
+      sw = arguments[3];
+      sh = arguments[4];
+      dx = arguments[5];
+      dy = arguments[6];
+      dw = arguments[7];
+      dh = arguments[8];
+    } else {
+      throw Error('Invalid number of arguments');
+    }
+
+    var d = this.getCoords_(dx, dy);
+
+    var w2 = sw / 2;
+    var h2 = sh / 2;
+
+    var vmlStr = [];
+
+    var W = 10;
+    var H = 10;
+
+    // For some reason that I've now forgotten, using divs didn't work
+    vmlStr.push(' <g_vml_:group',
+                ' coordsize="', Z * W, ',', Z * H, '"',
+                ' coordorigin="0,0"' ,
+                ' style="width:', W, 'px;height:', H, 'px;position:absolute;');
+
+    // If filters are necessary (rotation exists), create them
+    // filters are bog-slow, so only create them if abbsolutely necessary
+    // The following check doesn't account for skews (which don't exist
+    // in the canvas spec (yet) anyway.
+
+    if (this.m_[0][0] != 1 || this.m_[0][1]) {
+      var filter = [];
+
+      // Note the 12/21 reversal
+      filter.push('M11=', this.m_[0][0], ',',
+                  'M12=', this.m_[1][0], ',',
+                  'M21=', this.m_[0][1], ',',
+                  'M22=', this.m_[1][1], ',',
+                  'Dx=', mr(d.x / Z), ',',
+                  'Dy=', mr(d.y / Z), '');
+
+      // Bounding box calculation (need to minimize displayed area so that
+      // filters don't waste time on unused pixels.
+      var max = d;
+      var c2 = this.getCoords_(dx + dw, dy);
+      var c3 = this.getCoords_(dx, dy + dh);
+      var c4 = this.getCoords_(dx + dw, dy + dh);
+
+      max.x = m.max(max.x, c2.x, c3.x, c4.x);
+      max.y = m.max(max.y, c2.y, c3.y, c4.y);
+
+      vmlStr.push('padding:0 ', mr(max.x / Z), 'px ', mr(max.y / Z),
+                  'px 0;filter:progid:DXImageTransform.Microsoft.Matrix(',
+                  filter.join(''), ", sizingmethod='clip');")
+    } else {
+      vmlStr.push('top:', mr(d.y / Z), 'px;left:', mr(d.x / Z), 'px;');
+    }
+
+    vmlStr.push(' ">' ,
+                '<g_vml_:image src="', image.src, '"',
+                ' style="width:', Z * dw, 'px;',
+                ' height:', Z * dh, 'px;"',
+                ' cropleft="', sx / w, '"',
+                ' croptop="', sy / h, '"',
+                ' cropright="', (w - sx - sw) / w, '"',
+                ' cropbottom="', (h - sy - sh) / h, '"',
+                ' />',
+                '</g_vml_:group>');
+
+    this.element_.insertAdjacentHTML('BeforeEnd',
+                                    vmlStr.join(''));
+  };
+
+  contextPrototype.stroke = function(aFill) {
+    var lineStr = [];
+    var lineOpen = false;
+    var a = processStyle(aFill ? this.fillStyle : this.strokeStyle);
+    var color = a.color;
+    var opacity = a.alpha * this.globalAlpha;
+
+    var W = 10;
+    var H = 10;
+
+    lineStr.push('<g_vml_:shape',
+                 ' filled="', !!aFill, '"',
+                 ' style="position:absolute;width:', W, 'px;height:', H, 'px;"',
+                 ' coordorigin="0 0" coordsize="', Z * W, ' ', Z * H, '"',
+                 ' stroked="', !aFill, '"',
+                 ' path="');
+
+    var newSeq = false;
+    var min = {x: null, y: null};
+    var max = {x: null, y: null};
+
+    for (var i = 0; i < this.currentPath_.length; i++) {
+      var p = this.currentPath_[i];
+      var c;
+
+      switch (p.type) {
+        case 'moveTo':
+          c = p;
+          lineStr.push(' m ', mr(p.x), ',', mr(p.y));
+          break;
+        case 'lineTo':
+          lineStr.push(' l ', mr(p.x), ',', mr(p.y));
+          break;
+        case 'close':
+          lineStr.push(' x ');
+          p = null;
+          break;
+        case 'bezierCurveTo':
+          lineStr.push(' c ',
+                       mr(p.cp1x), ',', mr(p.cp1y), ',',
+                       mr(p.cp2x), ',', mr(p.cp2y), ',',
+                       mr(p.x), ',', mr(p.y));
+          break;
+        case 'at':
+        case 'wa':
+          lineStr.push(' ', p.type, ' ',
+                       mr(p.x - this.arcScaleX_ * p.radius), ',',
+                       mr(p.y - this.arcScaleY_ * p.radius), ' ',
+                       mr(p.x + this.arcScaleX_ * p.radius), ',',
+                       mr(p.y + this.arcScaleY_ * p.radius), ' ',
+                       mr(p.xStart), ',', mr(p.yStart), ' ',
+                       mr(p.xEnd), ',', mr(p.yEnd));
+          break;
+      }
+
+
+      // TODO: Following is broken for curves due to
+      //       move to proper paths.
+
+      // Figure out dimensions so we can do gradient fills
+      // properly
+      if (p) {
+        if (min.x == null || p.x < min.x) {
+          min.x = p.x;
+        }
+        if (max.x == null || p.x > max.x) {
+          max.x = p.x;
+        }
+        if (min.y == null || p.y < min.y) {
+          min.y = p.y;
+        }
+        if (max.y == null || p.y > max.y) {
+          max.y = p.y;
+        }
+      }
+    }
+    lineStr.push(' ">');
+
+    if (!aFill) {
+      var lineWidth = this.lineScale_ * this.lineWidth;
+
+      // VML cannot correctly render a line if the width is less than 1px.
+      // In that case, we dilute the color to make the line look thinner.
+      if (lineWidth < 1) {
+        opacity *= lineWidth;
+      }
+
+      lineStr.push(
+        '<g_vml_:stroke',
+        ' opacity="', opacity, '"',
+        ' joinstyle="', this.lineJoin, '"',
+        ' miterlimit="', this.miterLimit, '"',
+        ' endcap="', processLineCap(this.lineCap), '"',
+        ' weight="', lineWidth, 'px"',
+        ' color="', color, '" />'
+      );
+    } else if (typeof this.fillStyle == 'object') {
+      var fillStyle = this.fillStyle;
+      var angle = 0;
+      var focus = {x: 0, y: 0};
+
+      // additional offset
+      var shift = 0;
+      // scale factor for offset
+      var expansion = 1;
+
+      if (fillStyle.type_ == 'gradient') {
+        var x0 = fillStyle.x0_ / this.arcScaleX_;
+        var y0 = fillStyle.y0_ / this.arcScaleY_;
+        var x1 = fillStyle.x1_ / this.arcScaleX_;
+        var y1 = fillStyle.y1_ / this.arcScaleY_;
+        var p0 = this.getCoords_(x0, y0);
+        var p1 = this.getCoords_(x1, y1);
+        var dx = p1.x - p0.x;
+        var dy = p1.y - p0.y;
+        angle = Math.atan2(dx, dy) * 180 / Math.PI;
+
+        // The angle should be a non-negative number.
+        if (angle < 0) {
+          angle += 360;
+        }
+
+        // Very small angles produce an unexpected result because they are
+        // converted to a scientific notation string.
+        if (angle < 1e-6) {
+          angle = 0;
+        }
+      } else {
+        var p0 = this.getCoords_(fillStyle.x0_, fillStyle.y0_);
+        var width  = max.x - min.x;
+        var height = max.y - min.y;
+        focus = {
+          x: (p0.x - min.x) / width,
+          y: (p0.y - min.y) / height
+        };
+
+        width  /= this.arcScaleX_ * Z;
+        height /= this.arcScaleY_ * Z;
+        var dimension = m.max(width, height);
+        shift = 2 * fillStyle.r0_ / dimension;
+        expansion = 2 * fillStyle.r1_ / dimension - shift;
+      }
+
+      // We need to sort the color stops in ascending order by offset,
+      // otherwise IE won't interpret it correctly.
+      var stops = fillStyle.colors_;
+      stops.sort(function(cs1, cs2) {
+        return cs1.offset - cs2.offset;
+      });
+
+      var length = stops.length;
+      var color1 = stops[0].color;
+      var color2 = stops[length - 1].color;
+      var opacity1 = stops[0].alpha * this.globalAlpha;
+      var opacity2 = stops[length - 1].alpha * this.globalAlpha;
+
+      var colors = [];
+      for (var i = 0; i < length; i++) {
+        var stop = stops[i];
+        colors.push(stop.offset * expansion + shift + ' ' + stop.color);
+      }
+
+      // When colors attribute is used, the meanings of opacity and o:opacity2
+      // are reversed.
+      lineStr.push('<g_vml_:fill type="', fillStyle.type_, '"',
+                   ' method="none" focus="100%"',
+                   ' color="', color1, '"',
+                   ' color2="', color2, '"',
+                   ' colors="', colors.join(','), '"',
+                   ' opacity="', opacity2, '"',
+                   ' g_o_:opacity2="', opacity1, '"',
+                   ' angle="', angle, '"',
+                   ' focusposition="', focus.x, ',', focus.y, '" />');
+    } else {
+      lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity,
+                   '" />');
+    }
+
+    lineStr.push('</g_vml_:shape>');
+
+    this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
+  };
+
+  contextPrototype.fill = function() {
+    this.stroke(true);
+  }
+
+  contextPrototype.closePath = function() {
+    this.currentPath_.push({type: 'close'});
+  };
+
+  /**
+   * @private
+   */
+  contextPrototype.getCoords_ = function(aX, aY) {
+    var m = this.m_;
+    return {
+      x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2,
+      y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2
+    }
+  };
+
+  contextPrototype.save = function() {
+    var o = {};
+    copyState(this, o);
+    this.aStack_.push(o);
+    this.mStack_.push(this.m_);
+    this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
+  };
+
+  contextPrototype.restore = function() {
+    copyState(this.aStack_.pop(), this);
+    this.m_ = this.mStack_.pop();
+  };
+
+  contextPrototype.translate = function(aX, aY) {
+    var m1 = [
+      [1,  0,  0],
+      [0,  1,  0],
+      [aX, aY, 1]
+    ];
+
+    this.m_ = matrixMultiply(m1, this.m_);
+  };
+
+  contextPrototype.rotate = function(aRot) {
+    var c = mc(aRot);
+    var s = ms(aRot);
+
+    var m1 = [
+      [c,  s, 0],
+      [-s, c, 0],
+      [0,  0, 1]
+    ];
+
+    this.m_ = matrixMultiply(m1, this.m_);
+  };
+
+  contextPrototype.scale = function(aX, aY) {
+    this.arcScaleX_ *= aX;
+    this.arcScaleY_ *= aY;
+    var m1 = [
+      [aX, 0,  0],
+      [0,  aY, 0],
+      [0,  0,  1]
+    ];
+
+    var m = this.m_ = matrixMultiply(m1, this.m_);
+
+    // Get the line scale.
+    // Determinant of this.m_ means how much the area is enlarged by the
+    // transformation. So its square root can be used as a scale factor
+    // for width.
+    var det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
+    this.lineScale_ = sqrt(abs(det));
+  };
+
+  /******** STUBS ********/
+  contextPrototype.clip = function() {
+    // TODO: Implement
+  };
+
+  contextPrototype.arcTo = function() {
+    // TODO: Implement
+  };
+
+  contextPrototype.createPattern = function() {
+    return new CanvasPattern_;
+  };
+
+  // Gradient / Pattern Stubs
+  function CanvasGradient_(aType) {
+    this.type_ = aType;
+    this.x0_ = 0;
+    this.y0_ = 0;
+    this.r0_ = 0;
+    this.x1_ = 0;
+    this.y1_ = 0;
+    this.r1_ = 0;
+    this.colors_ = [];
+  }
+
+  CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
+    aColor = processStyle(aColor);
+    this.colors_.push({offset: aOffset,
+                       color: aColor.color,
+                       alpha: aColor.alpha});
+  };
+
+  function CanvasPattern_() {}
+
+  // set up externs
+  G_vmlCanvasManager = G_vmlCanvasManager_;
+  CanvasRenderingContext2D = CanvasRenderingContext2D_;
+  CanvasGradient = CanvasGradient_;
+  CanvasPattern = CanvasPattern_;
+
+})();
+
+} // if
+

--- /dev/null
+++ b/js/flotr/lib/prototype-1.6.0.2.js
@@ -1,1 +1,4221 @@
-
+/*  Prototype JavaScript framework, version 1.6.0.2

+ *  (c) 2005-2008 Sam Stephenson

+ *

+ *  Prototype is freely distributable under the terms of an MIT-style license.

+ *  For details, see the Prototype web site: http://www.prototypejs.org/

+ *

+ *--------------------------------------------------------------------------*/

+

+var Prototype = {

+  Version: '1.6.0.2',

+

+  Browser: {

+    IE:     !!(window.attachEvent && !window.opera),

+    Opera:  !!window.opera,

+    WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,

+    Gecko:  navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1,

+    MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)

+  },

+

+  BrowserFeatures: {

+    XPath: !!document.evaluate,

+    ElementExtensions: !!window.HTMLElement,

+    SpecificElementExtensions:

+      document.createElement('div').__proto__ &&

+      document.createElement('div').__proto__ !==

+        document.createElement('form').__proto__

+  },

+

+  ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',

+  JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,

+

+  emptyFunction: function() { },

+  K: function(x) { return x }

+};

+

+if (Prototype.Browser.MobileSafari)

+  Prototype.BrowserFeatures.SpecificElementExtensions = false;

+

+

+/* Based on Alex Arnell's inheritance implementation. */

+var Class = {

+  create: function() {

+    var parent = null, properties = $A(arguments);

+    if (Object.isFunction(properties[0]))

+      parent = properties.shift();

+

+    function klass() {

+      this.initialize.apply(this, arguments);

+    }

+

+    Object.extend(klass, Class.Methods);

+    klass.superclass = parent;

+    klass.subclasses = [];

+

+    if (parent) {

+      var subclass = function() { };

+      subclass.prototype = parent.prototype;

+      klass.prototype = new subclass;

+      parent.subclasses.push(klass);

+    }

+

+    for (var i = 0; i < properties.length; i++)

+      klass.addMethods(properties[i]);

+

+    if (!klass.prototype.initialize)

+      klass.prototype.initialize = Prototype.emptyFunction;

+

+    klass.prototype.constructor = klass;

+

+    return klass;

+  }

+};

+

+Class.Methods = {

+  addMethods: function(source) {

+    var ancestor   = this.superclass && this.superclass.prototype;

+    var properties = Object.keys(source);

+

+    if (!Object.keys({ toString: true }).length)

+      properties.push("toString", "valueOf");

+

+    for (var i = 0, length = properties.length; i < length; i++) {

+      var property = properties[i], value = source[property];

+      if (ancestor && Object.isFunction(value) &&

+          value.argumentNames().first() == "$super") {

+        var method = value, value = Object.extend((function(m) {

+          return function() { return ancestor[m].apply(this, arguments) };

+        })(property).wrap(method), {

+          valueOf:  function() { return method },

+          toString: function() { return method.toString() }

+        });

+      }

+      this.prototype[property] = value;

+    }

+

+    return this;

+  }

+};

+

+var Abstract = { };

+

+Object.extend = function(destination, source) {

+  for (var property in source)

+    destination[property] = source[property];

+  return destination;

+};

+

+Object.extend(Object, {

+  inspect: function(object) {

+    try {

+      if (Object.isUndefined(object)) return 'undefined';

+      if (object === null) return 'null';

+      return object.inspect ? object.inspect() : String(object);

+    } catch (e) {

+      if (e instanceof RangeError) return '...';

+      throw e;

+    }

+  },

+

+  toJSON: function(object) {

+    var type = typeof object;

+    switch (type) {

+      case 'undefined':

+      case 'function':

+      case 'unknown': return;

+      case 'boolean': return object.toString();

+    }

+

+    if (object === null) return 'null';

+    if (object.toJSON) return object.toJSON();

+    if (Object.isElement(object)) return;

+

+    var results = [];

+    for (var property in object) {

+      var value = Object.toJSON(object[property]);

+      if (!Object.isUndefined(value))

+        results.push(property.toJSON() + ': ' + value);

+    }

+

+    return '{' + results.join(', ') + '}';

+  },

+

+  toQueryString: function(object) {

+    return $H(object).toQueryString();

+  },

+

+  toHTML: function(object) {

+    return object && object.toHTML ? object.toHTML() : String.interpret(object);

+  },

+

+  keys: function(object) {

+    var keys = [];

+    for (var property in object)

+      keys.push(property);

+    return keys;

+  },

+

+  values: function(object) {

+    var values = [];

+    for (var property in object)

+      values.push(object[property]);

+    return values;

+  },

+

+  clone: function(object) {

+    return Object.extend({ }, object);

+  },

+

+  isElement: function(object) {

+    return object && object.nodeType == 1;

+  },

+

+  isArray: function(object) {

+    return object != null && typeof object == "object" &&

+      'splice' in object && 'join' in object;

+  },

+

+  isHash: function(object) {

+    return object instanceof Hash;

+  },

+

+  isFunction: function(object) {

+    return typeof object == "function";

+  },

+

+  isString: function(object) {

+    return typeof object == "string";

+  },

+

+  isNumber: function(object) {

+    return typeof object == "number";

+  },

+

+  isUndefined: function(object) {

+    return typeof object == "undefined";

+  }

+});

+

+Object.extend(Function.prototype, {

+  argumentNames: function() {

+    var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip");

+    return names.length == 1 && !names[0] ? [] : names;

+  },

+

+  bind: function() {

+    if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;

+    var __method = this, args = $A(arguments), object = args.shift();

+    return function() {

+      return __method.apply(object, args.concat($A(arguments)));

+    }

+  },

+

+  bindAsEventListener: function() {

+    var __method = this, args = $A(arguments), object = args.shift();

+    return function(event) {

+      return __method.apply(object, [event || window.event].concat(args));

+    }

+  },

+

+  curry: function() {

+    if (!arguments.length) return this;

+    var __method = this, args = $A(arguments);

+    return function() {

+      return __method.apply(this, args.concat($A(arguments)));

+    }

+  },

+

+  delay: function() {

+    var __method = this, args = $A(arguments), timeout = args.shift() * 1000;

+    return window.setTimeout(function() {

+      return __method.apply(__method, args);

+    }, timeout);

+  },

+

+  wrap: function(wrapper) {

+    var __method = this;

+    return function() {

+      return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));

+    }

+  },

+

+  methodize: function() {

+    if (this._methodized) return this._methodized;

+    var __method = this;

+    return this._methodized = function() {

+      return __method.apply(null, [this].concat($A(arguments)));

+    };

+  }

+});

+

+Function.prototype.defer = Function.prototype.delay.curry(0.01);

+

+Date.prototype.toJSON = function() {

+  return '"' + this.getUTCFullYear() + '-' +

+    (this.getUTCMonth() + 1).toPaddedString(2) + '-' +

+    this.getUTCDate().toPaddedString(2) + 'T' +

+    this.getUTCHours().toPaddedString(2) + ':' +

+    this.getUTCMinutes().toPaddedString(2) + ':' +

+    this.getUTCSeconds().toPaddedString(2) + 'Z"';

+};

+

+var Try = {

+  these: function() {

+    var returnValue;

+

+    for (var i = 0, length = arguments.length; i < length; i++) {

+      var lambda = arguments[i];

+      try {

+        returnValue = lambda();

+        break;

+      } catch (e) { }

+    }

+

+    return returnValue;

+  }

+};

+

+RegExp.prototype.match = RegExp.prototype.test;

+

+RegExp.escape = function(str) {

+  return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');

+};

+

+/*--------------------------------------------------------------------------*/

+

+var PeriodicalExecuter = Class.create({

+  initialize: function(callback, frequency) {

+    this.callback = callback;

+    this.frequency = frequency;

+    this.currentlyExecuting = false;

+

+    this.registerCallback();

+  },

+

+  registerCallback: function() {

+    this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);

+  },

+

+  execute: function() {

+    this.callback(this);

+  },

+

+  stop: function() {

+    if (!this.timer) return;

+    clearInterval(this.timer);

+    this.timer = null;

+  },

+

+  onTimerEvent: function() {

+    if (!this.currentlyExecuting) {

+      try {

+        this.currentlyExecuting = true;

+        this.execute();

+      } finally {

+        this.currentlyExecuting = false;

+      }

+    }

+  }

+});

+Object.extend(String, {

+  interpret: function(value) {

+    return value == null ? '' : String(value);

+  },

+  specialChar: {

+    '\b': '\\b',

+    '\t': '\\t',

+    '\n': '\\n',

+    '\f': '\\f',

+    '\r': '\\r',

+    '\\': '\\\\'

+  }

+});

+

+Object.extend(String.prototype, {

+  gsub: function(pattern, replacement) {

+    var result = '', source = this, match;

+    replacement = arguments.callee.prepareReplacement(replacement);

+

+    while (source.length > 0) {

+      if (match = source.match(pattern)) {

+        result += source.slice(0, match.index);

+        result += String.interpret(replacement(match));

+        source  = source.slice(match.index + match[0].length);

+      } else {

+        result += source, source = '';

+      }

+    }

+    return result;

+  },

+

+  sub: function(pattern, replacement, count) {

+    replacement = this.gsub.prepareReplacement(replacement);

+    count = Object.isUndefined(count) ? 1 : count;

+

+    return this.gsub(pattern, function(match) {

+      if (--count < 0) return match[0];

+      return replacement(match);

+    });

+  },

+

+  scan: function(pattern, iterator) {

+    this.gsub(pattern, iterator);

+    return String(this);

+  },

+

+  truncate: function(length, truncation) {

+    length = length || 30;

+    truncation = Object.isUndefined(truncation) ? '...' : truncation;

+    return this.length > length ?

+      this.slice(0, length - truncation.length) + truncation : String(this);

+  },

+

+  strip: function() {

+    return this.replace(/^\s+/, '').replace(/\s+$/, '');

+  },

+

+  stripTags: function() {

+    return this.replace(/<\/?[^>]+>/gi, '');

+  },

+

+  stripScripts: function() {

+    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');

+  },

+

+  extractScripts: function() {

+    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');

+    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');

+    return (this.match(matchAll) || []).map(function(scriptTag) {

+      return (scriptTag.match(matchOne) || ['', ''])[1];

+    });

+  },

+

+  evalScripts: function() {

+    return this.extractScripts().map(function(script) { return eval(script) });

+  },

+

+  escapeHTML: function() {

+    var self = arguments.callee;

+    self.text.data = this;

+    return self.div.innerHTML;

+  },

+

+  unescapeHTML: function() {

+    var div = new Element('div');

+    div.innerHTML = this.stripTags();

+    return div.childNodes[0] ? (div.childNodes.length > 1 ?

+      $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :

+      div.childNodes[0].nodeValue) : '';

+  },

+

+  toQueryParams: function(separator) {

+    var match = this.strip().match(/([^?#]*)(#.*)?$/);

+    if (!match) return { };

+

+    return match[1].split(separator || '&').inject({ }, function(hash, pair) {

+      if ((pair = pair.split('='))[0]) {

+        var key = decodeURIComponent(pair.shift());

+        var value = pair.length > 1 ? pair.join('=') : pair[0];

+        if (value != undefined) value = decodeURIComponent(value);

+

+        if (key in hash) {

+          if (!Object.isArray(hash[key])) hash[key] = [hash[key]];

+          hash[key].push(value);

+        }

+        else hash[key] = value;

+      }

+      return hash;

+    });

+  },

+

+  toArray: function() {

+    return this.split('');

+  },

+

+  succ: function() {

+    return this.slice(0, this.length - 1) +

+      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);

+  },

+

+  times: function(count) {

+    return count < 1 ? '' : new Array(count + 1).join(this);

+  },

+

+  camelize: function() {

+    var parts = this.split('-'), len = parts.length;

+    if (len == 1) return parts[0];

+

+    var camelized = this.charAt(0) == '-'

+      ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)

+      : parts[0];

+

+    for (var i = 1; i < len; i++)

+      camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);

+

+    return camelized;

+  },

+

+  capitalize: function() {

+    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();

+  },

+

+  underscore: function() {

+    return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();

+  },

+

+  dasherize: function() {

+    return this.gsub(/_/,'-');

+  },

+

+  inspect: function(useDoubleQuotes) {

+    var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {

+      var character = String.specialChar[match[0]];

+      return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);

+    });

+    if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';

+    return "'" + escapedString.replace(/'/g, '\\\'') + "'";

+  },

+

+  toJSON: function() {

+    return this.inspect(true);

+  },

+

+  unfilterJSON: function(filter) {

+    return this.sub(filter || Prototype.JSONFilter, '#{1}');

+  },

+

+  isJSON: function() {

+    var str = this;

+    if (str.blank()) return false;

+    str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');

+    return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);

+  },

+

+  evalJSON: function(sanitize) {

+    var json = this.unfilterJSON();

+    try {

+      if (!sanitize || json.isJSON()) return eval('(' + json + ')');

+    } catch (e) { }

+    throw new SyntaxError('Badly formed JSON string: ' + this.inspect());

+  },

+

+  include: function(pattern) {

+    return this.indexOf(pattern) > -1;

+  },

+

+  startsWith: function(pattern) {

+    return this.indexOf(pattern) === 0;

+  },

+

+  endsWith: function(pattern) {

+    var d = this.length - pattern.length;

+    return d >= 0 && this.lastIndexOf(pattern) === d;

+  },

+

+  empty: function() {

+    return this == '';

+  },

+

+  blank: function() {

+    return /^\s*$/.test(this);

+  },

+

+  interpolate: function(object, pattern) {

+    return new Template(this, pattern).evaluate(object);

+  }

+});

+

+if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {

+  escapeHTML: function() {

+    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');

+  },

+  unescapeHTML: function() {

+    return this.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');

+  }

+});

+

+String.prototype.gsub.prepareReplacement = function(replacement) {

+  if (Object.isFunction(replacement)) return replacement;

+  var template = new Template(replacement);

+  return function(match) { return template.evaluate(match) };

+};

+

+String.prototype.parseQuery = String.prototype.toQueryParams;

+

+Object.extend(String.prototype.escapeHTML, {

+  div:  document.createElement('div'),

+  text: document.createTextNode('')

+});

+

+with (String.prototype.escapeHTML) div.appendChild(text);

+

+var Template = Class.create({

+  initialize: function(template, pattern) {

+    this.template = template.toString();

+    this.pattern = pattern || Template.Pattern;

+  },

+

+  evaluate: function(object) {

+    if (Object.isFunction(object.toTemplateReplacements))

+      object = object.toTemplateReplacements();

+

+    return this.template.gsub(this.pattern, function(match) {

+      if (object == null) return '';

+

+      var before = match[1] || '';

+      if (before == '\\') return match[2];

+

+      var ctx = object, expr = match[3];

+      var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;

+      match = pattern.exec(expr);

+      if (match == null) return before;

+

+      while (match != null) {

+        var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];

+        ctx = ctx[comp];

+        if (null == ctx || '' == match[3]) break;

+        expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);

+        match = pattern.exec(expr);

+      }

+

+      return before + String.interpret(ctx);

+    });

+  }

+});

+Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;

+

+var $break = { };

+

+var Enumerable = {

+  each: function(iterator, context) {

+    var index = 0;

+    iterator = iterator.bind(context);

+    try {

+      this._each(function(value) {

+        iterator(value, index++);

+      });

+    } catch (e) {

+      if (e != $break) throw e;

+    }

+    return this;

+  },

+

+  eachSlice: function(number, iterator, context) {

+    iterator = iterator ? iterator.bind(context) : Prototype.K;

+    var index = -number, slices = [], array = this.toArray();

+    while ((index += number) < array.length)

+      slices.push(array.slice(index, index+number));

+    return slices.collect(iterator, context);

+  },

+

+  all: function(iterator, context) {

+    iterator = iterator ? iterator.bind(context) : Prototype.K;

+    var result = true;

+    this.each(function(value, index) {

+      result = result && !!iterator(value, index);

+      if (!result) throw $break;

+    });

+    return result;

+  },

+

+  any: function(iterator, context) {

+    iterator = iterator ? iterator.bind(context) : Prototype.K;

+    var result = false;

+    this.each(function(value, index) {

+      if (result = !!iterator(value, index))

+        throw $break;

+    });

+    return result;

+  },

+

+  collect: function(iterator, context) {

+    iterator = iterator ? iterator.bind(context) : Prototype.K;

+    var results = [];

+    this.each(function(value, index) {

+      results.push(iterator(value, index));

+    });

+    return results;

+  },

+

+  detect: function(iterator, context) {

+    iterator = iterator.bind(context);

+    var result;

+    this.each(function(value, index) {

+      if (iterator(value, index)) {

+        result = value;

+        throw $break;

+      }

+    });

+    return result;

+  },

+

+  findAll: function(iterator, context) {

+    iterator = iterator.bind(context);

+    var results = [];

+    this.each(function(value, index) {

+      if (iterator(value, index))

+        results.push(value);

+    });

+    return results;

+  },

+

+  grep: function(filter, iterator, context) {

+    iterator = iterator ? iterator.bind(context) : Prototype.K;

+    var results = [];

+

+    if (Object.isString(filter))

+      filter = new RegExp(filter);

+

+    this.each(function(value, index) {

+      if (filter.match(value))

+        results.push(iterator(value, index));

+    });

+    return results;

+  },

+

+  include: function(object) {

+    if (Object.isFunction(this.indexOf))

+      if (this.indexOf(object) != -1) return true;

+

+    var found = false;

+    this.each(function(value) {

+      if (value == object) {

+        found = true;

+        throw $break;

+      }

+    });

+    return found;

+  },

+

+  inGroupsOf: function(number, fillWith) {

+    fillWith = Object.isUndefined(fillWith) ? null : fillWith;

+    return this.eachSlice(number, function(slice) {

+      while(slice.length < number) slice.push(fillWith);

+      return slice;

+    });

+  },

+

+  inject: function(memo, iterator, context) {

+    iterator = iterator.bind(context);

+    this.each(function(value, index) {

+      memo = iterator(memo, value, index);

+    });

+    return memo;

+  },

+

+  invoke: function(method) {

+    var args = $A(arguments).slice(1);

+    return this.map(function(value) {

+      return value[method].apply(value, args);

+    });

+  },

+

+  max: function(iterator, context) {

+    iterator = iterator ? iterator.bind(context) : Prototype.K;

+    var result;

+    this.each(function(value, index) {

+      value = iterator(value, index);

+      if (result == null || value >= result)

+        result = value;

+    });

+    return result;

+  },

+

+  min: function(iterator, context) {

+    iterator = iterator ? iterator.bind(context) : Prototype.K;

+    var result;

+    this.each(function(value, index) {

+      value = iterator(value, index);

+      if (result == null || value < result)

+        result = value;

+    });

+    return result;

+  },

+

+  partition: function(iterator, context) {

+    iterator = iterator ? iterator.bind(context) : Prototype.K;

+    var trues = [], falses = [];

+    this.each(function(value, index) {

+      (iterator(value, index) ?

+        trues : falses).push(value);

+    });

+    return [trues, falses];

+  },

+

+  pluck: function(property) {

+    var results = [];

+    this.each(function(value) {

+      results.push(value[property]);

+    });

+    return results;

+  },

+

+  reject: function(iterator, context) {

+    iterator = iterator.bind(context);

+    var results = [];

+    this.each(function(value, index) {

+      if (!iterator(value, index))

+        results.push(value);

+    });

+    return results;

+  },

+

+  sortBy: function(iterator, context) {

+    iterator = iterator.bind(context);

+    return this.map(function(value, index) {

+      return {value: value, criteria: iterator(value, index)};

+    }).sort(function(left, right) {

+      var a = left.criteria, b = right.criteria;

+      return a < b ? -1 : a > b ? 1 : 0;

+    }).pluck('value');

+  },

+

+  toArray: function() {

+    return this.map();

+  },

+

+  zip: function() {

+    var iterator = Prototype.K, args = $A(arguments);

+    if (Object.isFunction(args.last()))

+      iterator = args.pop();

+

+    var collections = [this].concat(args).map($A);

+    return this.map(function(value, index) {

+      return iterator(collections.pluck(index));

+    });

+  },

+

+  size: function() {

+    return this.toArray().length;

+  },

+

+  inspect: function() {

+    return '#<Enumerable:' + this.toArray().inspect() + '>';

+  }

+};

+

+Object.extend(Enumerable, {

+  map:     Enumerable.collect,

+  find:    Enumerable.detect,

+  select:  Enumerable.findAll,

+  filter:  Enumerable.findAll,

+  member:  Enumerable.include,

+  entries: Enumerable.toArray,

+  every:   Enumerable.all,

+  some:    Enumerable.any

+});

+function $A(iterable) {

+  if (!iterable) return [];

+  if (iterable.toArray) return iterable.toArray();

+  var length = iterable.length || 0, results = new Array(length);

+  while (length--) results[length] = iterable[length];

+  return results;

+}

+

+if (Prototype.Browser.WebKit) {

+  $A = function(iterable) {

+    if (!iterable) return [];

+    if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') &&

+        iterable.toArray) return iterable.toArray();

+    var length = iterable.length || 0, results = new Array(length);

+    while (length--) results[length] = iterable[length];

+    return results;

+  };

+}

+

+Array.from = $A;

+

+Object.extend(Array.prototype, Enumerable);

+

+if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;

+

+Object.extend(Array.prototype, {

+  _each: function(iterator) {

+    for (var i = 0, length = this.length; i < length; i++)

+      iterator(this[i]);

+  },

+

+  clear: function() {

+    this.length = 0;

+    return this;

+  },

+

+  first: function() {

+    return this[0];

+  },

+

+  last: function() {

+    return this[this.length - 1];

+  },

+

+  compact: function() {

+    return this.select(function(value) {

+      return value != null;

+    });

+  },

+

+  flatten: function() {

+    return this.inject([], function(array, value) {

+      return array.concat(Object.isArray(value) ?

+        value.flatten() : [value]);

+    });

+  },

+

+  without: function() {

+    var values = $A(arguments);

+    return this.select(function(value) {

+      return !values.include(value);

+    });

+  },

+

+  reverse: function(inline) {

+    return (inline !== false ? this : this.toArray())._reverse();

+  },

+

+  reduce: function() {

+    return this.length > 1 ? this : this[0];

+  },

+

+  uniq: function(sorted) {

+    return this.inject([], function(array, value, index) {

+      if (0 == index || (sorted ? array.last() != value : !array.include(value)))

+        array.push(value);

+      return array;

+    });

+  },

+

+  intersect: function(array) {

+    return this.uniq().findAll(function(item) {

+      return array.detect(function(value) { return item === value });

+    });

+  },

+

+  clone: function() {

+    return [].concat(this);

+  },

+

+  size: function() {

+    return this.length;

+  },

+

+  inspect: function() {

+    return '[' + this.map(Object.inspect).join(', ') + ']';

+  },

+

+  toJSON: function() {

+    var results = [];

+    this.each(function(object) {

+      var value = Object.toJSON(object);

+      if (!Object.isUndefined(value)) results.push(value);

+    });

+    return '[' + results.join(', ') + ']';

+  }

+});

+

+// use native browser JS 1.6 implementation if available

+if (Object.isFunction(Array.prototype.forEach))

+  Array.prototype._each = Array.prototype.forEach;

+

+if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {

+  i || (i = 0);

+  var length = this.length;

+  if (i < 0) i = length + i;

+  for (; i < length; i++)

+    if (this[i] === item) return i;

+  return -1;

+};

+

+if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) {

+  i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;

+  var n = this.slice(0, i).reverse().indexOf(item);

+  return (n < 0) ? n : i - n - 1;

+};

+

+Array.prototype.toArray = Array.prototype.clone;

+

+function $w(string) {

+  if (!Object.isString(string)) return [];

+  string = string.strip();

+  return string ? string.split(/\s+/) : [];

+}

+

+if (Prototype.Browser.Opera){

+  Array.prototype.concat = function() {

+    var array = [];

+    for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);

+    for (var i = 0, length = arguments.length; i < length; i++) {

+      if (Object.isArray(arguments[i])) {

+        for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)

+          array.push(arguments[i][j]);

+      } else {

+        array.push(arguments[i]);

+      }

+    }

+    return array;

+  };

+}

+Object.extend(Number.prototype, {

+  toColorPart: function() {

+    return this.toPaddedString(2, 16);

+  },

+

+  succ: function() {

+    return this + 1;

+  },

+

+  times: function(iterator) {

+    $R(0, this, true).each(iterator);

+    return this;

+  },

+

+  toPaddedString: function(length, radix) {

+    var string = this.toString(radix || 10);

+    return '0'.times(length - string.length) + string;

+  },

+

+  toJSON: function() {

+    return isFinite(this) ? this.toString() : 'null';

+  }

+});

+

+$w('abs round ceil floor').each(function(method){

+  Number.prototype[method] = Math[method].methodize();

+});

+function $H(object) {

+  return new Hash(object);

+};

+

+var Hash = Class.create(Enumerable, (function() {

+

+  function toQueryPair(key, value) {

+    if (Object.isUndefined(value)) return key;

+    return key + '=' + encodeURIComponent(String.interpret(value));

+  }

+

+  return {

+    initialize: function(object) {

+      this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);

+    },

+

+    _each: function(iterator) {

+      for (var key in this._object) {

+        var value = this._object[key], pair = [key, value];

+        pair.key = key;

+        pair.value = value;

+        iterator(pair);

+      }

+    },

+

+    set: function(key, value) {

+      return this._object[key] = value;

+    },

+

+    get: function(key) {

+      return this._object[key];

+    },

+

+    unset: function(key) {

+      var value = this._object[key];

+      delete this._object[key];

+      return value;

+    },

+

+    toObject: function() {

+      return Object.clone(this._object);

+    },

+

+    keys: function() {

+      return this.pluck('key');

+    },

+

+    values: function() {

+      return this.pluck('value');

+    },

+

+    index: function(value) {

+      var match = this.detect(function(pair) {

+        return pair.value === value;

+      });

+      return match && match.key;

+    },

+

+    merge: function(object) {

+      return this.clone().update(object);

+    },

+

+    update: function(object) {

+      return new Hash(object).inject(this, function(result, pair) {

+        result.set(pair.key, pair.value);

+        return result;

+      });

+    },

+

+    toQueryString: function() {

+      return this.map(function(pair) {

+        var key = encodeURIComponent(pair.key), values = pair.value;

+

+        if (values && typeof values == 'object') {

+          if (Object.isArray(values))

+            return values.map(toQueryPair.curry(key)).join('&');

+        }

+        return toQueryPair(key, values);

+      }).join('&');

+    },

+

+    inspect: function() {

+      return '#<Hash:{' + this.map(function(pair) {

+        return pair.map(Object.inspect).join(': ');

+      }).join(', ') + '}>';

+    },

+

+    toJSON: function() {

+      return Object.toJSON(this.toObject());

+    },

+

+    clone: function() {

+      return new Hash(this);

+    }

+  }

+})());

+

+Hash.prototype.toTemplateReplacements = Hash.prototype.toObject;

+Hash.from = $H;

+var ObjectRange = Class.create(Enumerable, {

+  initialize: function(start, end, exclusive) {

+    this.start = start;

+    this.end = end;

+    this.exclusive = exclusive;

+  },

+

+  _each: function(iterator) {

+    var value = this.start;

+    while (this.include(value)) {

+      iterator(value);

+      value = value.succ();

+    }

+  },

+

+  include: function(value) {

+    if (value < this.start)

+      return false;

+    if (this.exclusive)

+      return value < this.end;

+    return value <= this.end;

+  }

+});

+

+var $R = function(start, end, exclusive) {

+  return new ObjectRange(start, end, exclusive);

+};

+

+var Ajax = {

+  getTransport: function() {

+    return Try.these(

+      function() {return new XMLHttpRequest()},

+      function() {return new ActiveXObject('Msxml2.XMLHTTP')},

+      function() {return new ActiveXObject('Microsoft.XMLHTTP')}

+    ) || false;

+  },

+

+  activeRequestCount: 0

+};

+

+Ajax.Responders = {

+  responders: [],

+

+  _each: function(iterator) {

+    this.responders._each(iterator);

+  },

+

+  register: function(responder) {

+    if (!this.include(responder))

+      this.responders.push(responder);

+  },

+

+  unregister: function(responder) {

+    this.responders = this.responders.without(responder);

+  },

+

+  dispatch: function(callback, request, transport, json) {

+    this.each(function(responder) {

+      if (Object.isFunction(responder[callback])) {

+        try {

+          responder[callback].apply(responder, [request, transport, json]);

+        } catch (e) { }

+      }

+    });

+  }

+};

+

+Object.extend(Ajax.Responders, Enumerable);

+

+Ajax.Responders.register({

+  onCreate:   function() { Ajax.activeRequestCount++ },

+  onComplete: function() { Ajax.activeRequestCount-- }

+});

+

+Ajax.Base = Class.create({

+  initialize: function(options) {

+    this.options = {

+      method:       'post',

+      asynchronous: true,

+      contentType:  'application/x-www-form-urlencoded',

+      encoding:     'UTF-8',

+      parameters:   '',

+      evalJSON:     true,

+      evalJS:       true

+    };

+    Object.extend(this.options, options || { });

+

+    this.options.method = this.options.method.toLowerCase();

+

+    if (Object.isString(this.options.parameters))

+      this.options.parameters = this.options.parameters.toQueryParams();

+    else if (Object.isHash(this.options.parameters))

+      this.options.parameters = this.options.parameters.toObject();

+  }

+});

+

+Ajax.Request = Class.create(Ajax.Base, {

+  _complete: false,

+

+  initialize: function($super, url, options) {

+    $super(options);

+    this.transport = Ajax.getTransport();

+    this.request(url);

+  },

+

+  request: function(url) {

+    this.url = url;

+    this.method = this.options.method;

+    var params = Object.clone(this.options.parameters);

+

+    if (!['get', 'post'].include(this.method)) {

+      // simulate other verbs over post

+      params['_method'] = this.method;

+      this.method = 'post';

+    }

+

+    this.parameters = params;

+

+    if (params = Object.toQueryString(params)) {

+      // when GET, append parameters to URL

+      if (this.method == 'get')

+        this.url += (this.url.include('?') ? '&' : '?') + params;

+      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))

+        params += '&_=';

+    }

+

+    try {

+      var response = new Ajax.Response(this);

+      if (this.options.onCreate) this.options.onCreate(response);

+      Ajax.Responders.dispatch('onCreate', this, response);

+

+      this.transport.open(this.method.toUpperCase(), this.url,

+        this.options.asynchronous);

+

+      if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);

+

+      this.transport.onreadystatechange = this.onStateChange.bind(this);

+      this.setRequestHeaders();

+

+      this.body = this.method == 'post' ? (this.options.postBody || params) : null;

+      this.transport.send(this.body);

+

+      /* Force Firefox to handle ready state 4 for synchronous requests */

+      if (!this.options.asynchronous && this.transport.overrideMimeType)

+        this.onStateChange();

+

+    }

+    catch (e) {

+      this.dispatchException(e);

+    }

+  },

+

+  onStateChange: function() {

+    var readyState = this.transport.readyState;

+    if (readyState > 1 && !((readyState == 4) && this._complete))

+      this.respondToReadyState(this.transport.readyState);

+  },

+

+  setRequestHeaders: function() {

+    var headers = {

+      'X-Requested-With': 'XMLHttpRequest',

+      'X-Prototype-Version': Prototype.Version,

+      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'

+    };

+

+    if (this.method == 'post') {

+      headers['Content-type'] = this.options.contentType +

+        (this.options.encoding ? '; charset=' + this.options.encoding : '');

+

+      /* Force "Connection: close" for older Mozilla browsers to work

+       * around a bug where XMLHttpRequest sends an incorrect

+       * Content-length header. See Mozilla Bugzilla #246651.

+       */

+      if (this.transport.overrideMimeType &&

+          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)

+            headers['Connection'] = 'close';

+    }

+

+    // user-defined headers

+    if (typeof this.options.requestHeaders == 'object') {

+      var extras = this.options.requestHeaders;

+

+      if (Object.isFunction(extras.push))

+        for (var i = 0, length = extras.length; i < length; i += 2)

+          headers[extras[i]] = extras[i+1];

+      else

+        $H(extras).each(function(pair) { headers[pair.key] = pair.value });

+    }

+

+    for (var name in headers)

+      this.transport.setRequestHeader(name, headers[name]);

+  },

+

+  success: function() {

+    var status = this.getStatus();

+    return !status || (status >= 200 && status < 300);

+  },

+

+  getStatus: function() {

+    try {

+      return this.transport.status || 0;

+    } catch (e) { return 0 }

+  },

+

+  respondToReadyState: function(readyState) {

+    var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);

+

+    if (state == 'Complete') {

+      try {

+        this._complete = true;

+        (this.options['on' + response.status]

+         || this.options['on' + (this.success() ? 'Success' : 'Failure')]

+         || Prototype.emptyFunction)(response, response.headerJSON);

+      } catch (e) {

+        this.dispatchException(e);

+      }

+

+      var contentType = response.getHeader('Content-type');

+      if (this.options.evalJS == 'force'

+          || (this.options.evalJS && this.isSameOrigin() && contentType

+          && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))

+        this.evalResponse();

+    }

+

+    try {

+      (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);

+      Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);

+    } catch (e) {

+      this.dispatchException(e);

+    }

+

+    if (state == 'Complete') {

+      // avoid memory leak in MSIE: clean up

+      this.transport.onreadystatechange = Prototype.emptyFunction;

+    }

+  },

+

+  isSameOrigin: function() {

+    var m = this.url.match(/^\s*https?:\/\/[^\/]*/);

+    return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({

+      protocol: location.protocol,

+      domain: document.domain,

+      port: location.port ? ':' + location.port : ''

+    }));

+  },

+

+  getHeader: function(name) {

+    try {

+      return this.transport.getResponseHeader(name) || null;

+    } catch (e) { return null }

+  },

+

+  evalResponse: function() {

+    try {

+      return eval((this.transport.responseText || '').unfilterJSON());

+    } catch (e) {

+      this.dispatchException(e);

+    }

+  },

+

+  dispatchException: function(exception) {

+    (this.options.onException || Prototype.emptyFunction)(this, exception);

+    Ajax.Responders.dispatch('onException', this, exception);

+  }

+});

+

+Ajax.Request.Events =

+  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];

+

+Ajax.Response = Class.create({

+  initialize: function(request){

+    this.request = request;

+    var transport  = this.transport  = request.transport,

+        readyState = this.readyState = transport.readyState;

+

+    if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {

+      this.status       = this.getStatus();

+      this.statusText   = this.getStatusText();

+      this.responseText = String.interpret(transport.responseText);

+      this.headerJSON   = this._getHeaderJSON();

+    }

+

+    if(readyState == 4) {

+      var xml = transport.responseXML;

+      this.responseXML  = Object.isUndefined(xml) ? null : xml;

+      this.responseJSON = this._getResponseJSON();

+    }

+  },

+

+  status:      0,

+  statusText: '',

+

+  getStatus: Ajax.Request.prototype.getStatus,

+

+  getStatusText: function() {

+    try {

+      return this.transport.statusText || '';

+    } catch (e) { return '' }

+  },

+

+  getHeader: Ajax.Request.prototype.getHeader,

+

+  getAllHeaders: function() {

+    try {

+      return this.getAllResponseHeaders();

+    } catch (e) { return null }

+  },

+

+  getResponseHeader: function(name) {

+    return this.transport.getResponseHeader(name);

+  },

+

+  getAllResponseHeaders: function() {

+    return this.transport.getAllResponseHeaders();

+  },

+

+  _getHeaderJSON: function() {

+    var json = this.getHeader('X-JSON');

+    if (!json) return null;

+    json = decodeURIComponent(escape(json));

+    try {

+      return json.evalJSON(this.request.options.sanitizeJSON ||

+        !this.request.isSameOrigin());

+    } catch (e) {

+      this.request.dispatchException(e);

+    }

+  },

+

+  _getResponseJSON: function() {

+    var options = this.request.options;

+    if (!options.evalJSON || (options.evalJSON != 'force' &&

+      !(this.getHeader('Content-type') || '').include('application/json')) ||

+        this.responseText.blank())

+          return null;

+    try {

+      return this.responseText.evalJSON(options.sanitizeJSON ||

+        !this.request.isSameOrigin());

+    } catch (e) {

+      this.request.dispatchException(e);

+    }

+  }

+});

+

+Ajax.Updater = Class.create(Ajax.Request, {

+  initialize: function($super, container, url, options) {

+    this.container = {

+      success: (container.success || container),

+      failure: (container.failure || (container.success ? null : container))

+    };

+

+    options = Object.clone(options);

+    var onComplete = options.onComplete;

+    options.onComplete = (function(response, json) {

+      this.updateContent(response.responseText);

+      if (Object.isFunction(onComplete)) onComplete(response, json);

+    }).bind(this);

+

+    $super(url, options);

+  },

+

+  updateContent: function(responseText) {

+    var receiver = this.container[this.success() ? 'success' : 'failure'],

+        options = this.options;

+

+    if (!options.evalScripts) responseText = responseText.stripScripts();

+

+    if (receiver = $(receiver)) {

+      if (options.insertion) {

+        if (Object.isString(options.insertion)) {

+          var insertion = { }; insertion[options.insertion] = responseText;

+          receiver.insert(insertion);

+        }

+        else options.insertion(receiver, responseText);

+      }

+      else receiver.update(responseText);

+    }

+  }

+});

+

+Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {

+  initialize: function($super, container, url, options) {

+    $super(options);

+    this.onComplete = this.options.onComplete;

+

+    this.frequency = (this.options.frequency || 2);

+    this.decay = (this.options.decay || 1);

+

+    this.updater = { };

+    this.container = container;

+    this.url = url;

+

+    this.start();

+  },

+

+  start: function() {

+    this.options.onComplete = this.updateComplete.bind(this);

+    this.onTimerEvent();

+  },

+

+  stop: function() {

+    this.updater.options.onComplete = undefined;

+    clearTimeout(this.timer);

+    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);

+  },

+

+  updateComplete: function(response) {

+    if (this.options.decay) {

+      this.decay = (response.responseText == this.lastText ?

+        this.decay * this.options.decay : 1);

+

+      this.lastText = response.responseText;

+    }

+    this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);

+  },

+

+  onTimerEvent: function() {

+    this.updater = new Ajax.Updater(this.container, this.url, this.options);

+  }

+});

+function $(element) {

+  if (arguments.length > 1) {

+    for (var i = 0, elements = [], length = arguments.length; i < length; i++)

+      elements.push($(arguments[i]));

+    return elements;

+  }

+  if (Object.isString(element))

+    element = document.getElementById(element);

+  return Element.extend(element);

+}

+

+if (Prototype.BrowserFeatures.XPath) {

+  document._getElementsByXPath = function(expression, parentElement) {

+    var results = [];

+    var query = document.evaluate(expression, $(parentElement) || document,

+      null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);

+    for (var i = 0, length = query.snapshotLength; i < length; i++)

+      results.push(Element.extend(query.snapshotItem(i)));

+    return results;

+  };

+}

+

+/*--------------------------------------------------------------------------*/

+

+if (!window.Node) var Node = { };

+

+if (!Node.ELEMENT_NODE) {

+  // DOM level 2 ECMAScript Language Binding

+  Object.extend(Node, {

+    ELEMENT_NODE: 1,

+    ATTRIBUTE_NODE: 2,

+    TEXT_NODE: 3,

+    CDATA_SECTION_NODE: 4,

+    ENTITY_REFERENCE_NODE: 5,

+    ENTITY_NODE: 6,

+    PROCESSING_INSTRUCTION_NODE: 7,

+    COMMENT_NODE: 8,

+    DOCUMENT_NODE: 9,

+    DOCUMENT_TYPE_NODE: 10,

+    DOCUMENT_FRAGMENT_NODE: 11,

+    NOTATION_NODE: 12

+  });

+}

+

+(function() {

+  var element = this.Element;

+  this.Element = function(tagName, attributes) {

+    attributes = attributes || { };

+    tagName = tagName.toLowerCase();

+    var cache = Element.cache;

+    if (Prototype.Browser.IE && attributes.name) {

+      tagName = '<' + tagName + ' name="' + attributes.name + '">';

+      delete attributes.name;

+      return Element.writeAttribute(document.createElement(tagName), attributes);

+    }

+    if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));

+    return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);

+  };

+  Object.extend(this.Element, element || { });

+}).call(window);

+

+Element.cache = { };

+

+Element.Methods = {

+  visible: function(element) {

+    return $(element).style.display != 'none';

+  },

+

+  toggle: function(element) {

+    element = $(element);

+    Element[Element.visible(element) ? 'hide' : 'show'](element);

+    return element;

+  },

+

+  hide: function(element) {

+    $(element).style.display = 'none';

+    return element;

+  },

+

+  show: function(element) {

+    $(element).style.display = '';

+    return element;

+  },

+

+  remove: function(element) {

+    element = $(element);

+    element.parentNode.removeChild(element);

+    return element;

+  },

+

+  update: function(element, content) {

+    element = $(element);

+    if (content && content.toElement) content = content.toElement();

+    if (Object.isElement(content)) return element.update().insert(content);

+    content = Object.toHTML(content);

+    element.innerHTML = content.stripScripts();

+    content.evalScripts.bind(content).defer();

+    return element;

+  },

+

+  replace: function(element, content) {

+    element = $(element);

+    if (content && content.toElement) content = content.toElement();

+    else if (!Object.isElement(content)) {

+      content = Object.toHTML(content);

+      var range = element.ownerDocument.createRange();

+      range.selectNode(element);

+      content.evalScripts.bind(content).defer();

+      content = range.createContextualFragment(content.stripScripts());

+    }

+    element.parentNode.replaceChild(content, element);

+    return element;

+  },

+

+  insert: function(element, insertions) {

+    element = $(element);

+

+    if (Object.isString(insertions) || Object.isNumber(insertions) ||

+        Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))

+          insertions = {bottom:insertions};

+

+    var content, insert, tagName, childNodes;

+

+    for (var position in insertions) {

+      content  = insertions[position];

+      position = position.toLowerCase();

+      insert = Element._insertionTranslations[position];

+

+      if (content && content.toElement) content = content.toElement();

+      if (Object.isElement(content)) {

+        insert(element, content);

+        continue;

+      }

+

+      content = Object.toHTML(content);

+

+      tagName = ((position == 'before' || position == 'after')

+        ? element.parentNode : element).tagName.toUpperCase();

+

+      childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());

+

+      if (position == 'top' || position == 'after') childNodes.reverse();

+      childNodes.each(insert.curry(element));

+

+      content.evalScripts.bind(content).defer();

+    }

+

+    return element;

+  },

+

+  wrap: function(element, wrapper, attributes) {

+    element = $(element);

+    if (Object.isElement(wrapper))

+      $(wrapper).writeAttribute(attributes || { });

+    else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);

+    else wrapper = new Element('div', wrapper);

+    if (element.parentNode)

+      element.parentNode.replaceChild(wrapper, element);

+    wrapper.appendChild(element);

+    return wrapper;

+  },

+

+  inspect: function(element) {

+    element = $(element);

+    var result = '<' + element.tagName.toLowerCase();

+    $H({'id': 'id', 'className': 'class'}).each(function(pair) {

+      var property = pair.first(), attribute = pair.last();

+      var value = (element[property] || '').toString();

+      if (value) result += ' ' + attribute + '=' + value.inspect(true);

+    });

+    return result + '>';

+  },

+

+  recursivelyCollect: function(element, property) {

+    element = $(element);

+    var elements = [];

+    while (element = element[property])

+      if (element.nodeType == 1)

+        elements.push(Element.extend(element));

+    return elements;

+  },

+

+  ancestors: function(element) {

+    return $(element).recursivelyCollect('parentNode');

+  },

+

+  descendants: function(element) {

+    return $(element).select("*");

+  },

+

+  firstDescendant: function(element) {

+    element = $(element).firstChild;

+    while (element && element.nodeType != 1) element = element.nextSibling;

+    return $(element);

+  },

+

+  immediateDescendants: function(element) {

+    if (!(element = $(element).firstChild)) return [];

+    while (element && element.nodeType != 1) element = element.nextSibling;

+    if (element) return [element].concat($(element).nextSiblings());

+    return [];

+  },

+

+  previousSiblings: function(element) {

+    return $(element).recursivelyCollect('previousSibling');

+  },

+

+  nextSiblings: function(element) {

+    return $(element).recursivelyCollect('nextSibling');

+  },

+

+  siblings: function(element) {

+    element = $(element);

+    return element.previousSiblings().reverse().concat(element.nextSiblings());

+  },

+

+  match: function(element, selector) {

+    if (Object.isString(selector))

+      selector = new Selector(selector);

+    return selector.match($(element));

+  },

+

+  up: function(element, expression, index) {

+    element = $(element);

+    if (arguments.length == 1) return $(element.parentNode);

+    var ancestors = element.ancestors();

+    return Object.isNumber(expression) ? ancestors[expression] :

+      Selector.findElement(ancestors, expression, index);

+  },

+

+  down: function(element, expression, index) {

+    element = $(element);

+    if (arguments.length == 1) return element.firstDescendant();

+    return Object.isNumber(expression) ? element.descendants()[expression] :

+      element.select(expression)[index || 0];

+  },

+

+  previous: function(element, expression, index) {

+    element = $(element);

+    if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));

+    var previousSiblings = element.previousSiblings();

+    return Object.isNumber(expression) ? previousSiblings[expression] :

+      Selector.findElement(previousSiblings, expression, index);

+  },

+

+  next: function(element, expression, index) {

+    element = $(element);

+    if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));

+    var nextSiblings = element.nextSiblings();

+    return Object.isNumber(expression) ? nextSiblings[expression] :

+      Selector.findElement(nextSiblings, expression, index);

+  },

+

+  select: function() {

+    var args = $A(arguments), element = $(args.shift());

+    return Selector.findChildElements(element, args);

+  },

+

+  adjacent: function() {

+    var args = $A(arguments), element = $(args.shift());

+    return Selector.findChildElements(element.parentNode, args).without(element);

+  },

+

+  identify: function(element) {

+    element = $(element);

+    var id = element.readAttribute('id'), self = arguments.callee;

+    if (id) return id;

+    do { id = 'anonymous_element_' + self.counter++ } while ($(id));

+    element.writeAttribute('id', id);

+    return id;

+  },

+

+  readAttribute: function(element, name) {

+    element = $(element);

+    if (Prototype.Browser.IE) {

+      var t = Element._attributeTranslations.read;

+      if (t.values[name]) return t.values[name](element, name);

+      if (t.names[name]) name = t.names[name];

+      if (name.include(':')) {

+        return (!element.attributes || !element.attributes[name]) ? null :

+         element.attributes[name].value;

+      }

+    }

+    return element.getAttribute(name);

+  },

+

+  writeAttribute: function(element, name, value) {

+    element = $(element);

+    var attributes = { }, t = Element._attributeTranslations.write;

+

+    if (typeof name == 'object') attributes = name;

+    else attributes[name] = Object.isUndefined(value) ? true : value;

+

+    for (var attr in attributes) {

+      name = t.names[attr] || attr;

+      value = attributes[attr];

+      if (t.values[attr]) name = t.values[attr](element, value);

+      if (value === false || value === null)

+        element.removeAttribute(name);

+      else if (value === true)

+        element.setAttribute(name, name);

+      else element.setAttribute(name, value);

+    }

+    return element;

+  },

+

+  getHeight: function(element) {

+    return $(element).getDimensions().height;

+  },

+

+  getWidth: function(element) {

+    return $(element).getDimensions().width;

+  },

+

+  classNames: function(element) {

+    return new Element.ClassNames(element);

+  },

+

+  hasClassName: function(element, className) {

+    if (!(element = $(element))) return;

+    var elementClassName = element.className;

+    return (elementClassName.length > 0 && (elementClassName == className ||

+      new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));

+  },

+

+  addClassName: function(element, className) {

+    if (!(element = $(element))) return;

+    if (!element.hasClassName(className))

+      element.className += (element.className ? ' ' : '') + className;

+    return element;

+  },

+

+  removeClassName: function(element, className) {

+    if (!(element = $(element))) return;

+    element.className = element.className.replace(

+      new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();

+    return element;

+  },

+

+  toggleClassName: function(element, className) {

+    if (!(element = $(element))) return;

+    return element[element.hasClassName(className) ?

+      'removeClassName' : 'addClassName'](className);

+  },

+

+  // removes whitespace-only text node children

+  cleanWhitespace: function(element) {

+    element = $(element);

+    var node = element.firstChild;

+    while (node) {

+      var nextNode = node.nextSibling;

+      if (node.nodeType == 3 && !/\S/.test(node.nodeValue))

+        element.removeChild(node);

+      node = nextNode;

+    }

+    return element;

+  },

+

+  empty: function(element) {

+    return $(element).innerHTML.blank();

+  },

+

+  descendantOf: function(element, ancestor) {

+    element = $(element), ancestor = $(ancestor);

+    var originalAncestor = ancestor;

+

+    if (element.compareDocumentPosition)

+      return (element.compareDocumentPosition(ancestor) & 8) === 8;

+

+    if (element.sourceIndex && !Prototype.Browser.Opera) {

+      var e = element.sourceIndex, a = ancestor.sourceIndex,

+       nextAncestor = ancestor.nextSibling;

+      if (!nextAncestor) {

+        do { ancestor = ancestor.parentNode; }

+        while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode);

+      }

+      if (nextAncestor && nextAncestor.sourceIndex)

+       return (e > a && e < nextAncestor.sourceIndex);

+    }

+

+    while (element = element.parentNode)

+      if (element == originalAncestor) return true;

+    return false;

+  },

+

+  scrollTo: function(element) {

+    element = $(element);

+    var pos = element.cumulativeOffset();

+    window.scrollTo(pos[0], pos[1]);

+    return element;

+  },

+

+  getStyle: function(element, style) {

+    element = $(element);

+    style = style == 'float' ? 'cssFloat' : style.camelize();

+    var value = element.style[style];

+    if (!value) {

+      var css = document.defaultView.getComputedStyle(element, null);

+      value = css ? css[style] : null;

+    }

+    if (style == 'opacity') return value ? parseFloat(value) : 1.0;

+    return value == 'auto' ? null : value;

+  },

+

+  getOpacity: function(element) {

+    return $(element).getStyle('opacity');

+  },

+

+  setStyle: function(element, styles) {

+    element = $(element);

+    var elementStyle = element.style, match;

+    if (Object.isString(styles)) {

+      element.style.cssText += ';' + styles;

+      return styles.include('opacity') ?

+        element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;

+    }

+    for (var property in styles)

+      if (property == 'opacity') element.setOpacity(styles[property]);

+      else

+        elementStyle[(property == 'float' || property == 'cssFloat') ?

+          (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :

+            property] = styles[property];

+

+    return element;

+  },

+

+  setOpacity: function(element, value) {

+    element = $(element);

+    element.style.opacity = (value == 1 || value === '') ? '' :

+      (value < 0.00001) ? 0 : value;

+    return element;

+  },

+

+  getDimensions: function(element) {

+    element = $(element);

+    var display = $(element).getStyle('display');

+    if (display != 'none' && display != null) // Safari bug

+      return {width: element.offsetWidth, height: element.offsetHeight};

+

+    // All *Width and *Height properties give 0 on elements with display none,

+    // so enable the element temporarily

+    var els = element.style;

+    var originalVisibility = els.visibility;

+    var originalPosition = els.position;

+    var originalDisplay = els.display;

+    els.visibility = 'hidden';

+    els.position = 'absolute';

+    els.display = 'block';

+    var originalWidth = element.clientWidth;

+    var originalHeight = element.clientHeight;

+    els.display = originalDisplay;

+    els.position = originalPosition;

+    els.visibility = originalVisibility;

+    return {width: originalWidth, height: originalHeight};

+  },

+

+  makePositioned: function(element) {

+    element = $(element);

+    var pos = Element.getStyle(element, 'position');

+    if (pos == 'static' || !pos) {

+      element._madePositioned = true;

+      element.style.position = 'relative';

+      // Opera returns the offset relative to the positioning context, when an

+      // element is position relative but top and left have not been defined

+      if (window.opera) {

+        element.style.top = 0;

+        element.style.left = 0;

+      }

+    }

+    return element;

+  },

+

+  undoPositioned: function(element) {

+    element = $(element);

+    if (element._madePositioned) {

+      element._madePositioned = undefined;

+      element.style.position =

+        element.style.top =

+        element.style.left =

+        element.style.bottom =

+        element.style.right = '';

+    }

+    return element;

+  },

+

+  makeClipping: function(element) {

+    element = $(element);

+    if (element._overflow) return element;

+    element._overflow = Element.getStyle(element, 'overflow') || 'auto';

+    if (element._overflow !== 'hidden')

+      element.style.overflow = 'hidden';

+    return element;

+  },

+

+  undoClipping: function(element) {

+    element = $(element);

+    if (!element._overflow) return element;

+    element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;

+    element._overflow = null;

+    return element;

+  },

+

+  cumulativeOffset: function(element) {

+    var valueT = 0, valueL = 0;

+    do {

+      valueT += element.offsetTop  || 0;

+      valueL += element.offsetLeft || 0;

+      element = element.offsetParent;

+    } while (element);

+    return Element._returnOffset(valueL, valueT);

+  },

+

+  positionedOffset: function(element) {

+    var valueT = 0, valueL = 0;

+    do {

+      valueT += element.offsetTop  || 0;

+      valueL += element.offsetLeft || 0;

+      element = element.offsetParent;

+      if (element) {

+        if (element.tagName == 'BODY') break;

+        var p = Element.getStyle(element, 'position');

+        if (p !== 'static') break;

+      }

+    } while (element);

+    return Element._returnOffset(valueL, valueT);

+  },

+

+  absolutize: function(element) {

+    element = $(element);

+    if (element.getStyle('position') == 'absolute') return;

+    // Position.prepare(); // To be done manually by Scripty when it needs it.

+

+    var offsets = element.positionedOffset();

+    var top     = offsets[1];

+    var left    = offsets[0];

+    var width   = element.clientWidth;

+    var height  = element.clientHeight;

+

+    element._originalLeft   = left - parseFloat(element.style.left  || 0);

+    element._originalTop    = top  - parseFloat(element.style.top || 0);

+    element._originalWidth  = element.style.width;

+    element._originalHeight = element.style.height;

+

+    element.style.position = 'absolute';

+    element.style.top    = top + 'px';

+    element.style.left   = left + 'px';

+    element.style.width  = width + 'px';

+    element.style.height = height + 'px';

+    return element;

+  },

+

+  relativize: function(element) {

+    element = $(element);

+    if (element.getStyle('position') == 'relative') return;

+    // Position.prepare(); // To be done manually by Scripty when it needs it.

+

+    element.style.position = 'relative';

+    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);

+    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);

+

+    element.style.top    = top + 'px';

+    element.style.left   = left + 'px';

+    element.style.height = element._originalHeight;

+    element.style.width  = element._originalWidth;

+    return element;

+  },

+

+  cumulativeScrollOffset: function(element) {

+    var valueT = 0, valueL = 0;

+    do {

+      valueT += element.scrollTop  || 0;

+      valueL += element.scrollLeft || 0;

+      element = element.parentNode;

+    } while (element);

+    return Element._returnOffset(valueL, valueT);

+  },

+

+  getOffsetParent: function(element) {

+    if (element.offsetParent) return $(element.offsetParent);

+    if (element == document.body) return $(element);

+

+    while ((element = element.parentNode) && element != document.body)

+      if (Element.getStyle(element, 'position') != 'static')

+        return $(element);

+

+    return $(document.body);

+  },

+

+  viewportOffset: function(forElement) {

+    var valueT = 0, valueL = 0;

+

+    var element = forElement;

+    do {

+      valueT += element.offsetTop  || 0;

+      valueL += element.offsetLeft || 0;

+

+      // Safari fix

+      if (element.offsetParent == document.body &&

+        Element.getStyle(element, 'position') == 'absolute') break;

+

+    } while (element = element.offsetParent);

+

+    element = forElement;

+    do {

+      if (!Prototype.Browser.Opera || element.tagName == 'BODY') {

+        valueT -= element.scrollTop  || 0;

+        valueL -= element.scrollLeft || 0;

+      }

+    } while (element = element.parentNode);

+

+    return Element._returnOffset(valueL, valueT);

+  },

+

+  clonePosition: function(element, source) {

+    var options = Object.extend({

+      setLeft:    true,

+      setTop:     true,

+      setWidth:   true,

+      setHeight:  true,

+      offsetTop:  0,

+      offsetLeft: 0

+    }, arguments[2] || { });

+

+    // find page position of source

+    source = $(source);

+    var p = source.viewportOffset();

+

+    // find coordinate system to use

+    element = $(element);

+    var delta = [0, 0];

+    var parent = null;

+    // delta [0,0] will do fine with position: fixed elements,

+    // position:absolute needs offsetParent deltas

+    if (Element.getStyle(element, 'position') == 'absolute') {

+      parent = element.getOffsetParent();

+      delta = parent.viewportOffset();

+    }

+

+    // correct by body offsets (fixes Safari)

+    if (parent == document.body) {

+      delta[0] -= document.body.offsetLeft;

+      delta[1] -= document.body.offsetTop;

+    }

+

+    // set position

+    if (options.setLeft)   element.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';

+    if (options.setTop)    element.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';

+    if (options.setWidth)  element.style.width = source.offsetWidth + 'px';

+    if (options.setHeight) element.style.height = source.offsetHeight + 'px';

+    return element;

+  }

+};

+

+Element.Methods.identify.counter = 1;

+

+Object.extend(Element.Methods, {

+  getElementsBySelector: Element.Methods.select,

+  childElements: Element.Methods.immediateDescendants

+});

+

+Element._attributeTranslations = {

+  write: {

+    names: {

+      className: 'class',

+      htmlFor:   'for'

+    },

+    values: { }

+  }

+};

+

+if (Prototype.Browser.Opera) {

+  Element.Methods.getStyle = Element.Methods.getStyle.wrap(

+    function(proceed, element, style) {

+      switch (style) {

+        case 'left': case 'top': case 'right': case 'bottom':

+          if (proceed(element, 'position') === 'static') return null;

+        case 'height': case 'width':

+          // returns '0px' for hidden elements; we want it to return null

+          if (!Element.visible(element)) return null;

+

+          // returns the border-box dimensions rather than the content-box

+          // dimensions, so we subtract padding and borders from the value

+          var dim = parseInt(proceed(element, style), 10);

+

+          if (dim !== element['offset' + style.capitalize()])

+            return dim + 'px';

+

+          var properties;

+          if (style === 'height') {

+            properties = ['border-top-width', 'padding-top',

+             'padding-bottom', 'border-bottom-width'];

+          }

+          else {

+            properties = ['border-left-width', 'padding-left',

+             'padding-right', 'border-right-width'];

+          }

+          return properties.inject(dim, function(memo, property) {

+            var val = proceed(element, property);

+            return val === null ? memo : memo - parseInt(val, 10);

+          }) + 'px';

+        default: return proceed(element, style);

+      }

+    }

+  );

+

+  Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(

+    function(proceed, element, attribute) {

+      if (attribute === 'title') return element.title;

+      return proceed(element, attribute);

+    }

+  );

+}

+

+else if (Prototype.Browser.IE) {

+  // IE doesn't report offsets correctly for static elements, so we change them

+  // to "relative" to get the values, then change them back.

+  Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(

+    function(proceed, element) {

+      element = $(element);

+      var position = element.getStyle('position');

+      if (position !== 'static') return proceed(element);

+      element.setStyle({ position: 'relative' });

+      var value = proceed(element);

+      element.setStyle({ position: position });

+      return value;

+    }

+  );

+

+  $w('positionedOffset viewportOffset').each(function(method) {

+    Element.Methods[method] = Element.Methods[method].wrap(

+      function(proceed, element) {

+        element = $(element);

+        var position = element.getStyle('position');

+        if (position !== 'static') return proceed(element);

+        // Trigger hasLayout on the offset parent so that IE6 reports

+        // accurate offsetTop and offsetLeft values for position: fixed.

+        var offsetParent = element.getOffsetParent();

+        if (offsetParent && offsetParent.getStyle('position') === 'fixed')

+          offsetParent.setStyle({ zoom: 1 });

+        element.setStyle({ position: 'relative' });

+        var value = proceed(element);

+        element.setStyle({ position: position });

+        return value;

+      }

+    );

+  });

+

+  Element.Methods.getStyle = function(element, style) {

+    element = $(element);

+    style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();

+    var value = element.style[style];

+    if (!value && element.currentStyle) value = element.currentStyle[style];

+

+    if (style == 'opacity') {

+      if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))

+        if (value[1]) return parseFloat(value[1]) / 100;

+      return 1.0;

+    }

+

+    if (value == 'auto') {

+      if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))

+        return element['offset' + style.capitalize()] + 'px';

+      return null;

+    }

+    return value;

+  };

+

+  Element.Methods.setOpacity = function(element, value) {

+    function stripAlpha(filter){

+      return filter.replace(/alpha\([^\)]*\)/gi,'');

+    }

+    element = $(element);

+    var currentStyle = element.currentStyle;

+    if ((currentStyle && !currentStyle.hasLayout) ||

+      (!currentStyle && element.style.zoom == 'normal'))

+        element.style.zoom = 1;

+

+    var filter = element.getStyle('filter'), style = element.style;

+    if (value == 1 || value === '') {

+      (filter = stripAlpha(filter)) ?

+        style.filter = filter : style.removeAttribute('filter');

+      return element;

+    } else if (value < 0.00001) value = 0;

+    style.filter = stripAlpha(filter) +

+      'alpha(opacity=' + (value * 100) + ')';

+    return element;

+  };

+

+  Element._attributeTranslations = {

+    read: {

+      names: {

+        'class': 'className',

+        'for':   'htmlFor'

+      },

+      values: {

+        _getAttr: function(element, attribute) {

+          return element.getAttribute(attribute, 2);

+        },

+        _getAttrNode: function(element, attribute) {

+          var node = element.getAttributeNode(attribute);

+          return node ? node.value : "";

+        },

+        _getEv: function(element, attribute) {

+          attribute = element.getAttribute(attribute);

+          return attribute ? attribute.toString().slice(23, -2) : null;

+        },

+        _flag: function(element, attribute) {

+          return $(element).hasAttribute(attribute) ? attribute : null;

+        },

+        style: function(element) {

+          return element.style.cssText.toLowerCase();

+        },

+        title: function(element) {

+          return element.title;

+        }

+      }

+    }

+  };

+

+  Element._attributeTranslations.write = {

+    names: Object.extend({

+      cellpadding: 'cellPadding',

+      cellspacing: 'cellSpacing'

+    }, Element._attributeTranslations.read.names),

+    values: {

+      checked: function(element, value) {

+        element.checked = !!value;

+      },

+

+      style: function(element, value) {

+        element.style.cssText = value ? value : '';

+      }

+    }

+  };

+

+  Element._attributeTranslations.has = {};

+

+  $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +

+      'encType maxLength readOnly longDesc').each(function(attr) {

+    Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;

+    Element._attributeTranslations.has[attr.toLowerCase()] = attr;

+  });

+

+  (function(v) {

+    Object.extend(v, {

+      href:        v._getAttr,

+      src:         v._getAttr,

+      type:        v._getAttr,

+      action:      v._getAttrNode,

+      disabled:    v._flag,

+      checked:     v._flag,

+      readonly:    v._flag,

+      multiple:    v._flag,

+      onload:      v._getEv,

+      onunload:    v._getEv,

+      onclick:     v._getEv,

+      ondblclick:  v._getEv,

+      onmousedown: v._getEv,

+      onmouseup:   v._getEv,

+      onmouseover: v._getEv,

+      onmousemove: v._getEv,

+      onmouseout:  v._getEv,

+      onfocus:     v._getEv,

+      onblur:      v._getEv,

+      onkeypress:  v._getEv,

+      onkeydown:   v._getEv,

+      onkeyup:     v._getEv,

+      onsubmit:    v._getEv,

+      onreset:     v._getEv,

+      onselect:    v._getEv,

+      onchange:    v._getEv

+    });

+  })(Element._attributeTranslations.read.values);

+}

+

+else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {

+  Element.Methods.setOpacity = function(element, value) {

+    element = $(element);

+    element.style.opacity = (value == 1) ? 0.999999 :

+      (value === '') ? '' : (value < 0.00001) ? 0 : value;

+    return element;

+  };

+}

+

+else if (Prototype.Browser.WebKit) {

+  Element.Methods.setOpacity = function(element, value) {

+    element = $(element);

+    element.style.opacity = (value == 1 || value === '') ? '' :

+      (value < 0.00001) ? 0 : value;

+

+    if (value == 1)

+      if(element.tagName == 'IMG' && element.width) {

+        element.width++; element.width--;

+      } else try {

+        var n = document.createTextNode(' ');

+        element.appendChild(n);

+        element.removeChild(n);

+      } catch (e) { }

+

+    return element;

+  };

+

+  // Safari returns margins on body which is incorrect if the child is absolutely

+  // positioned.  For performance reasons, redefine Element#cumulativeOffset for

+  // KHTML/WebKit only.

+  Element.Methods.cumulativeOffset = function(element) {

+    var valueT = 0, valueL = 0;

+    do {

+      valueT += element.offsetTop  || 0;

+      valueL += element.offsetLeft || 0;

+      if (element.offsetParent == document.body)

+        if (Element.getStyle(element, 'position') == 'absolute') break;

+

+      element = element.offsetParent;

+    } while (element);

+

+    return Element._returnOffset(valueL, valueT);

+  };

+}

+

+if (Prototype.Browser.IE || Prototype.Browser.Opera) {

+  // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements

+  Element.Methods.update = function(element, content) {

+    element = $(element);

+

+    if (content && content.toElement) content = content.toElement();

+    if (Object.isElement(content)) return element.update().insert(content);

+

+    content = Object.toHTML(content);

+    var tagName = element.tagName.toUpperCase();

+

+    if (tagName in Element._insertionTranslations.tags) {

+      $A(element.childNodes).each(function(node) { element.removeChild(node) });

+      Element._getContentFromAnonymousElement(tagName, content.stripScripts())

+        .each(function(node) { element.appendChild(node) });

+    }

+    else element.innerHTML = content.stripScripts();

+

+    content.evalScripts.bind(content).defer();

+    return element;

+  };

+}

+

+if ('outerHTML' in document.createElement('div')) {

+  Element.Methods.replace = function(element, content) {

+    element = $(element);

+

+    if (content && content.toElement) content = content.toElement();

+    if (Object.isElement(content)) {

+      element.parentNode.replaceChild(content, element);

+      return element;

+    }

+

+    content = Object.toHTML(content);

+    var parent = element.parentNode, tagName = parent.tagName.toUpperCase();

+

+    if (Element._insertionTranslations.tags[tagName]) {

+      var nextSibling = element.next();

+      var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());

+      parent.removeChild(element);

+      if (nextSibling)

+        fragments.each(function(node) { parent.insertBefore(node, nextSibling) });

+      else

+        fragments.each(function(node) { parent.appendChild(node) });

+    }

+    else element.outerHTML = content.stripScripts();

+

+    content.evalScripts.bind(content).defer();

+    return element;

+  };

+}

+

+Element._returnOffset = function(l, t) {

+  var result = [l, t];

+  result.left = l;

+  result.top = t;

+  return result;

+};

+

+Element._getContentFromAnonymousElement = function(tagName, html) {

+  var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];

+  if (t) {

+    div.innerHTML = t[0] + html + t[1];

+    t[2].times(function() { div = div.firstChild });

+  } else div.innerHTML = html;

+  return $A(div.childNodes);

+};

+

+Element._insertionTranslations = {

+  before: function(element, node) {

+    element.parentNode.insertBefore(node, element);

+  },

+  top: function(element, node) {

+    element.insertBefore(node, element.firstChild);

+  },

+  bottom: function(element, node) {

+    element.appendChild(node);

+  },

+  after: function(element, node) {

+    element.parentNode.insertBefore(node, element.nextSibling);

+  },

+  tags: {

+    TABLE:  ['<table>',                '</table>',                   1],

+    TBODY:  ['<table><tbody>',         '</tbody></table>',           2],

+    TR:     ['<table><tbody><tr>',     '</tr></tbody></table>',      3],

+    TD:     ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],

+    SELECT: ['<select>',               '</select>',                  1]

+  }

+};

+

+(function() {

+  Object.extend(this.tags, {

+    THEAD: this.tags.TBODY,

+    TFOOT: this.tags.TBODY,

+    TH:    this.tags.TD

+  });

+}).call(Element._insertionTranslations);

+

+Element.Methods.Simulated = {

+  hasAttribute: function(element, attribute) {

+    attribute = Element._attributeTranslations.has[attribute] || attribute;

+    var node = $(element).getAttributeNode(attribute);

+    return node && node.specified;

+  }

+};

+

+Element.Methods.ByTag = { };

+

+Object.extend(Element, Element.Methods);

+

+if (!Prototype.BrowserFeatures.ElementExtensions &&

+    document.createElement('div').__proto__) {

+  window.HTMLElement = { };

+  window.HTMLElement.prototype = document.createElement('div').__proto__;

+  Prototype.BrowserFeatures.ElementExtensions = true;

+}

+

+Element.extend = (function() {

+  if (Prototype.BrowserFeatures.SpecificElementExtensions)

+    return Prototype.K;

+

+  var Methods = { }, ByTag = Element.Methods.ByTag;

+

+  var extend = Object.extend(function(element) {

+    if (!element || element._extendedByPrototype ||

+        element.nodeType != 1 || element == window) return element;

+

+    var methods = Object.clone(Methods),

+      tagName = element.tagName, property, value;

+

+    // extend methods for specific tags

+    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);

+

+    for (property in methods) {

+      value = methods[property];

+      if (Object.isFunction(value) && !(property in element))

+        element[property] = value.methodize();

+    }

+

+    element._extendedByPrototype = Prototype.emptyFunction;

+    return element;

+

+  }, {

+    refresh: function() {

+      // extend methods for all tags (Safari doesn't need this)

+      if (!Prototype.BrowserFeatures.ElementExtensions) {

+        Object.extend(Methods, Element.Methods);

+        Object.extend(Methods, Element.Methods.Simulated);

+      }

+    }

+  });

+

+  extend.refresh();

+  return extend;

+})();

+

+Element.hasAttribute = function(element, attribute) {

+  if (element.hasAttribute) return element.hasAttribute(attribute);

+  return Element.Methods.Simulated.hasAttribute(element, attribute);

+};

+

+Element.addMethods = function(methods) {

+  var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;

+

+  if (!methods) {

+    Object.extend(Form, Form.Methods);

+    Object.extend(Form.Element, Form.Element.Methods);

+    Object.extend(Element.Methods.ByTag, {

+      "FORM":     Object.clone(Form.Methods),

+      "INPUT":    Object.clone(Form.Element.Methods),

+      "SELECT":   Object.clone(Form.Element.Methods),

+      "TEXTAREA": Object.clone(Form.Element.Methods)

+    });

+  }

+

+  if (arguments.length == 2) {

+    var tagName = methods;

+    methods = arguments[1];

+  }

+

+  if (!tagName) Object.extend(Element.Methods, methods || { });

+  else {

+    if (Object.isArray(tagName)) tagName.each(extend);

+    else extend(tagName);

+  }

+

+  function extend(tagName) {

+    tagName = tagName.toUpperCase();

+    if (!Element.Methods.ByTag[tagName])

+      Element.Methods.ByTag[tagName] = { };

+    Object.extend(Element.Methods.ByTag[tagName], methods);

+  }

+

+  function copy(methods, destination, onlyIfAbsent) {

+    onlyIfAbsent = onlyIfAbsent || false;

+    for (var property in methods) {

+      var value = methods[property];

+      if (!Object.isFunction(value)) continue;

+      if (!onlyIfAbsent || !(property in destination))

+        destination[property] = value.methodize();

+    }

+  }

+

+  function findDOMClass(tagName) {

+    var klass;

+    var trans = {

+      "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",

+      "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",

+      "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",

+      "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",

+      "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":

+      "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":

+      "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":

+      "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":

+      "FrameSet", "IFRAME": "IFrame"

+    };

+    if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';

+    if (window[klass]) return window[klass];

+    klass = 'HTML' + tagName + 'Element';

+    if (window[klass]) return window[klass];

+    klass = 'HTML' + tagName.capitalize() + 'Element';

+    if (window[klass]) return window[klass];

+

+    window[klass] = { };

+    window[klass].prototype = document.createElement(tagName).__proto__;

+    return window[klass];

+  }

+

+  if (F.ElementExtensions) {

+    copy(Element.Methods, HTMLElement.prototype);

+    copy(Element.Methods.Simulated, HTMLElement.prototype, true);

+  }

+

+  if (F.SpecificElementExtensions) {

+    for (var tag in Element.Methods.ByTag) {

+      var klass = findDOMClass(tag);

+      if (Object.isUndefined(klass)) continue;

+      copy(T[tag], klass.prototype);

+    }

+  }

+

+  Object.extend(Element, Element.Methods);

+  delete Element.ByTag;

+

+  if (Element.extend.refresh) Element.extend.refresh();

+  Element.cache = { };

+};

+

+document.viewport = {

+  getDimensions: function() {

+    var dimensions = { };

+    var B = Prototype.Browser;

+    $w('width height').each(function(d) {

+      var D = d.capitalize();

+      dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] :

+        (B.Opera) ? document.body['client' + D] : document.documentElement['client' + D];

+    });

+    return dimensions;

+  },

+

+  getWidth: function() {

+    return this.getDimensions().width;

+  },

+

+  getHeight: function() {

+    return this.getDimensions().height;

+  },

+

+  getScrollOffsets: function() {

+    return Element._returnOffset(

+      window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,

+      window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);

+  }

+};

+/* Portions of the Selector class are derived from Jack Slocum’s DomQuery,

+ * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style

+ * license.  Please see http://www.yui-ext.com/ for more information. */

+

+var Selector = Class.create({

+  initialize: function(expression) {

+    this.expression = expression.strip();

+    this.compileMatcher();

+  },

+

+  shouldUseXPath: function() {

+    if (!Prototype.BrowserFeatures.XPath) return false;

+

+    var e = this.expression;

+

+    // Safari 3 chokes on :*-of-type and :empty

+    if (Prototype.Browser.WebKit &&

+     (e.include("-of-type") || e.include(":empty")))

+      return false;

+

+    // XPath can't do namespaced attributes, nor can it read

+    // the "checked" property from DOM nodes

+    if ((/(\[[\w-]*?:|:checked)/).test(this.expression))

+      return false;

+

+    return true;

+  },

+

+  compileMatcher: function() {

+    if (this.shouldUseXPath())

+      return this.compileXPathMatcher();

+

+    var e = this.expression, ps = Selector.patterns, h = Selector.handlers,

+        c = Selector.criteria, le, p, m;

+

+    if (Selector._cache[e]) {

+      this.matcher = Selector._cache[e];

+      return;

+    }

+

+    this.matcher = ["this.matcher = function(root) {",

+                    "var r = root, h = Selector.handlers, c = false, n;"];

+

+    while (e && le != e && (/\S/).test(e)) {

+      le = e;

+      for (var i in ps) {

+        p = ps[i];

+        if (m = e.match(p)) {

+          this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :

+    	      new Template(c[i]).evaluate(m));

+          e = e.replace(m[0], '');

+          break;

+        }

+      }

+    }

+

+    this.matcher.push("return h.unique(n);\n}");

+    eval(this.matcher.join('\n'));

+    Selector._cache[this.expression] = this.matcher;

+  },

+

+  compileXPathMatcher: function() {

+    var e = this.expression, ps = Selector.patterns,

+        x = Selector.xpath, le, m;

+

+    if (Selector._cache[e]) {

+      this.xpath = Selector._cache[e]; return;

+    }

+

+    this.matcher = ['.//*'];

+    while (e && le != e && (/\S/).test(e)) {

+      le = e;

+      for (var i in ps) {

+        if (m = e.match(ps[i])) {

+          this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :

+            new Template(x[i]).evaluate(m));

+          e = e.replace(m[0], '');

+          break;

+        }

+      }

+    }

+

+    this.xpath = this.matcher.join('');

+    Selector._cache[this.expression] = this.xpath;

+  },

+

+  findElements: function(root) {

+    root = root || document;

+    if (this.xpath) return document._getElementsByXPath(this.xpath, root);

+    return this.matcher(root);

+  },

+

+  match: function(element) {

+    this.tokens = [];

+

+    var e = this.expression, ps = Selector.patterns, as = Selector.assertions;

+    var le, p, m;

+

+    while (e && le !== e && (/\S/).test(e)) {

+      le = e;

+      for (var i in ps) {

+        p = ps[i];

+        if (m = e.match(p)) {

+          // use the Selector.assertions methods unless the selector

+          // is too complex.

+          if (as[i]) {

+            this.tokens.push([i, Object.clone(m)]);

+            e = e.replace(m[0], '');

+          } else {

+            // reluctantly do a document-wide search

+            // and look for a match in the array

+            return this.findElements(document).include(element);

+          }

+        }

+      }

+    }

+

+    var match = true, name, matches;

+    for (var i = 0, token; token = this.tokens[i]; i++) {

+      name = token[0], matches = token[1];

+      if (!Selector.assertions[name](element, matches)) {

+        match = false; break;

+      }

+    }

+

+    return match;

+  },

+

+  toString: function() {

+    return this.expression;

+  },

+

+  inspect: function() {

+    return "#<Selector:" + this.expression.inspect() + ">";

+  }

+});

+

+Object.extend(Selector, {

+  _cache: { },

+

+  xpath: {

+    descendant:   "//*",

+    child:        "/*",

+    adjacent:     "/following-sibling::*[1]",

+    laterSibling: '/following-sibling::*',

+    tagName:      function(m) {

+      if (m[1] == '*') return '';

+      return "[local-name()='" + m[1].toLowerCase() +

+             "' or local-name()='" + m[1].toUpperCase() + "']";

+    },

+    className:    "[contains(concat(' ', @class, ' '), ' #{1} ')]",

+    id:           "[@id='#{1}']",

+    attrPresence: function(m) {

+      m[1] = m[1].toLowerCase();

+      return new Template("[@#{1}]").evaluate(m);

+    },

+    attr: function(m) {

+      m[1] = m[1].toLowerCase();

+      m[3] = m[5] || m[6];

+      return new Template(Selector.xpath.operators[m[2]]).evaluate(m);

+    },

+    pseudo: function(m) {

+      var h = Selector.xpath.pseudos[m[1]];

+      if (!h) return '';

+      if (Object.isFunction(h)) return h(m);

+      return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);

+    },

+    operators: {

+      '=':  "[@#{1}='#{3}']",

+      '!=': "[@#{1}!='#{3}']",

+      '^=': "[starts-with(@#{1}, '#{3}')]",

+      '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",

+      '*=': "[contains(@#{1}, '#{3}')]",

+      '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",

+      '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"

+    },

+    pseudos: {

+      'first-child': '[not(preceding-sibling::*)]',

+      'last-child':  '[not(following-sibling::*)]',

+      'only-child':  '[not(preceding-sibling::* or following-sibling::*)]',

+      'empty':       "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]",

+      'checked':     "[@checked]",

+      'disabled':    "[@disabled]",

+      'enabled':     "[not(@disabled)]",

+      'not': function(m) {

+        var e = m[6], p = Selector.patterns,

+            x = Selector.xpath, le, v;

+

+        var exclusion = [];

+        while (e && le != e && (/\S/).test(e)) {

+          le = e;

+          for (var i in p) {

+            if (m = e.match(p[i])) {

+              v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m);

+              exclusion.push("(" + v.substring(1, v.length - 1) + ")");

+              e = e.replace(m[0], '');

+              break;

+            }

+          }

+        }

+        return "[not(" + exclusion.join(" and ") + ")]";

+      },

+      'nth-child':      function(m) {

+        return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);

+      },

+      'nth-last-child': function(m) {

+        return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);

+      },

+      'nth-of-type':    function(m) {

+        return Selector.xpath.pseudos.nth("position() ", m);

+      },

+      'nth-last-of-type': function(m) {

+        return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);

+      },

+      'first-of-type':  function(m) {

+        m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);

+      },

+      'last-of-type':   function(m) {

+        m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);

+      },

+      'only-of-type':   function(m) {

+        var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);

+      },

+      nth: function(fragment, m) {

+        var mm, formula = m[6], predicate;

+        if (formula == 'even') formula = '2n+0';

+        if (formula == 'odd')  formula = '2n+1';

+        if (mm = formula.match(/^(\d+)$/)) // digit only

+          return '[' + fragment + "= " + mm[1] + ']';

+        if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b

+          if (mm[1] == "-") mm[1] = -1;

+          var a = mm[1] ? Number(mm[1]) : 1;

+          var b = mm[2] ? Number(mm[2]) : 0;

+          predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +

+          "((#{fragment} - #{b}) div #{a} >= 0)]";

+          return new Template(predicate).evaluate({

+            fragment: fragment, a: a, b: b });

+        }

+      }

+    }

+  },

+

+  criteria: {

+    tagName:      'n = h.tagName(n, r, "#{1}", c);      c = false;',

+    className:    'n = h.className(n, r, "#{1}", c);    c = false;',

+    id:           'n = h.id(n, r, "#{1}", c);           c = false;',

+    attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',

+    attr: function(m) {

+      m[3] = (m[5] || m[6]);

+      return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);

+    },

+    pseudo: function(m) {

+      if (m[6]) m[6] = m[6].replace(/"/g, '\\"');

+      return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);

+    },

+    descendant:   'c = "descendant";',

+    child:        'c = "child";',

+    adjacent:     'c = "adjacent";',

+    laterSibling: 'c = "laterSibling";'

+  },

+

+  patterns: {

+    // combinators must be listed first

+    // (and descendant needs to be last combinator)

+    laterSibling: /^\s*~\s*/,

+    child:        /^\s*>\s*/,

+    adjacent:     /^\s*\+\s*/,

+    descendant:   /^\s/,

+

+    // selectors follow

+    tagName:      /^\s*(\*|[\w\-]+)(\b|$)?/,

+    id:           /^#([\w\-\*]+)(\b|$)/,

+    className:    /^\.([\w\-\*]+)(\b|$)/,

+    pseudo:

+/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,

+    attrPresence: /^\[([\w]+)\]/,

+    attr:         /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/

+  },

+

+  // for Selector.match and Element#match

+  assertions: {

+    tagName: function(element, matches) {

+      return matches[1].toUpperCase() == element.tagName.toUpperCase();

+    },

+

+    className: function(element, matches) {

+      return Element.hasClassName(element, matches[1]);

+    },

+

+    id: function(element, matches) {

+      return element.id === matches[1];

+    },

+

+    attrPresence: function(element, matches) {

+      return Element.hasAttribute(element, matches[1]);

+    },

+

+    attr: function(element, matches) {

+      var nodeValue = Element.readAttribute(element, matches[1]);

+      return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);

+    }

+  },

+

+  handlers: {

+    // UTILITY FUNCTIONS

+    // joins two collections

+    concat: function(a, b) {

+      for (var i = 0, node; node = b[i]; i++)

+        a.push(node);

+      return a;

+    },

+

+    // marks an array of nodes for counting

+    mark: function(nodes) {

+      var _true = Prototype.emptyFunction;

+      for (var i = 0, node; node = nodes[i]; i++)

+        node._countedByPrototype = _true;

+      return nodes;

+    },

+

+    unmark: function(nodes) {

+      for (var i = 0, node; node = nodes[i]; i++)

+        node._countedByPrototype = undefined;

+      return nodes;

+    },

+

+    // mark each child node with its position (for nth calls)

+    // "ofType" flag indicates whether we're indexing for nth-of-type

+    // rather than nth-child

+    index: function(parentNode, reverse, ofType) {

+      parentNode._countedByPrototype = Prototype.emptyFunction;

+      if (reverse) {

+        for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {

+          var node = nodes[i];

+          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;

+        }

+      } else {

+        for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)

+          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;

+      }

+    },

+

+    // filters out duplicates and extends all nodes

+    unique: function(nodes) {

+      if (nodes.length == 0) return nodes;

+      var results = [], n;

+      for (var i = 0, l = nodes.length; i < l; i++)

+        if (!(n = nodes[i])._countedByPrototype) {

+          n._countedByPrototype = Prototype.emptyFunction;

+          results.push(Element.extend(n));

+        }

+      return Selector.handlers.unmark(results);

+    },

+

+    // COMBINATOR FUNCTIONS

+    descendant: function(nodes) {

+      var h = Selector.handlers;

+      for (var i = 0, results = [], node; node = nodes[i]; i++)

+        h.concat(results, node.getElementsByTagName('*'));

+      return results;

+    },

+

+    child: function(nodes) {

+      var h = Selector.handlers;

+      for (var i = 0, results = [], node; node = nodes[i]; i++) {

+        for (var j = 0, child; child = node.childNodes[j]; j++)

+          if (child.nodeType == 1 && child.tagName != '!') results.push(child);

+      }

+      return results;

+    },

+

+    adjacent: function(nodes) {

+      for (var i = 0, results = [], node; node = nodes[i]; i++) {

+        var next = this.nextElementSibling(node);

+        if (next) results.push(next);

+      }

+      return results;

+    },

+

+    laterSibling: function(nodes) {

+      var h = Selector.handlers;

+      for (var i = 0, results = [], node; node = nodes[i]; i++)

+        h.concat(results, Element.nextSiblings(node));

+      return results;

+    },

+

+    nextElementSibling: function(node) {

+      while (node = node.nextSibling)

+	      if (node.nodeType == 1) return node;

+      return null;

+    },

+

+    previousElementSibling: function(node) {

+      while (node = node.previousSibling)

+        if (node.nodeType == 1) return node;

+      return null;

+    },

+

+    // TOKEN FUNCTIONS

+    tagName: function(nodes, root, tagName, combinator) {

+      var uTagName = tagName.toUpperCase();

+      var results = [], h = Selector.handlers;

+      if (nodes) {

+        if (combinator) {

+          // fastlane for ordinary descendant combinators

+          if (combinator == "descendant") {

+            for (var i = 0, node; node = nodes[i]; i++)

+              h.concat(results, node.getElementsByTagName(tagName));

+            return results;

+          } else nodes = this[combinator](nodes);

+          if (tagName == "*") return nodes;

+        }

+        for (var i = 0, node; node = nodes[i]; i++)

+          if (node.tagName.toUpperCase() === uTagName) results.push(node);

+        return results;

+      } else return root.getElementsByTagName(tagName);

+    },

+

+    id: function(nodes, root, id, combinator) {

+      var targetNode = $(id), h = Selector.handlers;

+      if (!targetNode) return [];

+      if (!nodes && root == document) return [targetNode];

+      if (nodes) {

+        if (combinator) {

+          if (combinator == 'child') {

+            for (var i = 0, node; node = nodes[i]; i++)

+              if (targetNode.parentNode == node) return [targetNode];

+          } else if (combinator == 'descendant') {

+            for (var i = 0, node; node = nodes[i]; i++)

+              if (Element.descendantOf(targetNode, node)) return [targetNode];

+          } else if (combinator == 'adjacent') {

+            for (var i = 0, node; node = nodes[i]; i++)

+              if (Selector.handlers.previousElementSibling(targetNode) == node)

+                return [targetNode];

+          } else nodes = h[combinator](nodes);

+        }

+        for (var i = 0, node; node = nodes[i]; i++)

+          if (node == targetNode) return [targetNode];

+        return [];

+      }

+      return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];

+    },

+

+    className: function(nodes, root, className, combinator) {

+      if (nodes && combinator) nodes = this[combinator](nodes);

+      return Selector.handlers.byClassName(nodes, root, className);

+    },

+

+    byClassName: function(nodes, root, className) {

+      if (!nodes) nodes = Selector.handlers.descendant([root]);

+      var needle = ' ' + className + ' ';

+      for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {

+        nodeClassName = node.className;

+        if (nodeClassName.length == 0) continue;

+        if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))

+          results.push(node);

+      }

+      return results;

+    },

+

+    attrPresence: function(nodes, root, attr, combinator) {

+      if (!nodes) nodes = root.getElementsByTagName("*");

+      if (nodes && combinator) nodes = this[combinator](nodes);

+      var results = [];

+      for (var i = 0, node; node = nodes[i]; i++)

+        if (Element.hasAttribute(node, attr)) results.push(node);

+      return results;

+    },

+

+    attr: function(nodes, root, attr, value, operator, combinator) {

+      if (!nodes) nodes = root.getElementsByTagName("*");

+      if (nodes && combinator) nodes = this[combinator](nodes);

+      var handler = Selector.operators[operator], results = [];

+      for (var i = 0, node; node = nodes[i]; i++) {

+        var nodeValue = Element.readAttribute(node, attr);

+        if (nodeValue === null) continue;

+        if (handler(nodeValue, value)) results.push(node);

+      }

+      return results;

+    },

+

+    pseudo: function(nodes, name, value, root, combinator) {

+      if (nodes && combinator) nodes = this[combinator](nodes);

+      if (!nodes) nodes = root.getElementsByTagName("*");

+      return Selector.pseudos[name](nodes, value, root);

+    }

+  },

+

+  pseudos: {

+    'first-child': function(nodes, value, root) {

+      for (var i = 0, results = [], node; node = nodes[i]; i++) {

+        if (Selector.handlers.previousElementSibling(node)) continue;

+          results.push(node);

+      }

+      return results;

+    },

+    'last-child': function(nodes, value, root) {

+      for (var i = 0, results = [], node; node = nodes[i]; i++) {

+        if (Selector.handlers.nextElementSibling(node)) continue;

+          results.push(node);

+      }

+      return results;

+    },

+    'only-child': function(nodes, value, root) {

+      var h = Selector.handlers;

+      for (var i = 0, results = [], node; node = nodes[i]; i++)

+        if (!h.previousElementSibling(node) && !h.nextElementSibling(node))

+          results.push(node);

+      return results;

+    },

+    'nth-child':        function(nodes, formula, root) {

+      return Selector.pseudos.nth(nodes, formula, root);

+    },

+    'nth-last-child':   function(nodes, formula, root) {

+      return Selector.pseudos.nth(nodes, formula, root, true);

+    },

+    'nth-of-type':      function(nodes, formula, root) {

+      return Selector.pseudos.nth(nodes, formula, root, false, true);

+    },

+    'nth-last-of-type': function(nodes, formula, root) {

+      return Selector.pseudos.nth(nodes, formula, root, true, true);

+    },

+    'first-of-type':    function(nodes, formula, root) {

+      return Selector.pseudos.nth(nodes, "1", root, false, true);

+    },

+    'last-of-type':     function(nodes, formula, root) {

+      return Selector.pseudos.nth(nodes, "1", root, true, true);

+    },

+    'only-of-type':     function(nodes, formula, root) {

+      var p = Selector.pseudos;

+      return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);

+    },

+

+    // handles the an+b logic

+    getIndices: function(a, b, total) {

+      if (a == 0) return b > 0 ? [b] : [];

+      return $R(1, total).inject([], function(memo, i) {

+        if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);

+        return memo;

+      });

+    },

+

+    // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type

+    nth: function(nodes, formula, root, reverse, ofType) {

+      if (nodes.length == 0) return [];

+      if (formula == 'even') formula = '2n+0';

+      if (formula == 'odd')  formula = '2n+1';

+      var h = Selector.handlers, results = [], indexed = [], m;

+      h.mark(nodes);

+      for (var i = 0, node; node = nodes[i]; i++) {

+        if (!node.parentNode._countedByPrototype) {

+          h.index(node.parentNode, reverse, ofType);

+          indexed.push(node.parentNode);

+        }

+      }

+      if (formula.match(/^\d+$/)) { // just a number

+        formula = Number(formula);

+        for (var i = 0, node; node = nodes[i]; i++)

+          if (node.nodeIndex == formula) results.push(node);

+      } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b

+        if (m[1] == "-") m[1] = -1;

+        var a = m[1] ? Number(m[1]) : 1;

+        var b = m[2] ? Number(m[2]) : 0;

+        var indices = Selector.pseudos.getIndices(a, b, nodes.length);

+        for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {

+          for (var j = 0; j < l; j++)

+            if (node.nodeIndex == indices[j]) results.push(node);

+        }

+      }

+      h.unmark(nodes);

+      h.unmark(indexed);

+      return results;

+    },

+

+    'empty': function(nodes, value, root) {

+      for (var i = 0, results = [], node; node = nodes[i]; i++) {

+        // IE treats comments as element nodes

+        if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue;

+        results.push(node);

+      }

+      return results;

+    },

+

+    'not': function(nodes, selector, root) {

+      var h = Selector.handlers, selectorType, m;

+      var exclusions = new Selector(selector).findElements(root);

+      h.mark(exclusions);

+      for (var i = 0, results = [], node; node = nodes[i]; i++)

+        if (!node._countedByPrototype) results.push(node);

+      h.unmark(exclusions);

+      return results;

+    },

+

+    'enabled': function(nodes, value, root) {

+      for (var i = 0, results = [], node; node = nodes[i]; i++)

+        if (!node.disabled) results.push(node);

+      return results;

+    },

+

+    'disabled': function(nodes, value, root) {

+      for (var i = 0, results = [], node; node = nodes[i]; i++)

+        if (node.disabled) results.push(node);

+      return results;

+    },

+

+    'checked': function(nodes, value, root) {

+      for (var i = 0, results = [], node; node = nodes[i]; i++)

+        if (node.checked) results.push(node);

+      return results;

+    }

+  },

+

+  operators: {

+    '=':  function(nv, v) { return nv == v; },

+    '!=': function(nv, v) { return nv != v; },

+    '^=': function(nv, v) { return nv.startsWith(v); },

+    '$=': function(nv, v) { return nv.endsWith(v); },

+    '*=': function(nv, v) { return nv.include(v); },

+    '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },

+    '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }

+  },

+

+  split: function(expression) {

+    var expressions = [];

+    expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {

+      expressions.push(m[1].strip());

+    });

+    return expressions;

+  },

+

+  matchElements: function(elements, expression) {

+    var matches = $$(expression), h = Selector.handlers;

+    h.mark(matches);

+    for (var i = 0, results = [], element; element = elements[i]; i++)

+      if (element._countedByPrototype) results.push(element);

+    h.unmark(matches);

+    return results;

+  },

+

+  findElement: function(elements, expression, index) {

+    if (Object.isNumber(expression)) {

+      index = expression; expression = false;

+    }

+    return Selector.matchElements(elements, expression || '*')[index || 0];

+  },

+

+  findChildElements: function(element, expressions) {

+    expressions = Selector.split(expressions.join(','));

+    var results = [], h = Selector.handlers;

+    for (var i = 0, l = expressions.length, selector; i < l; i++) {

+      selector = new Selector(expressions[i].strip());

+      h.concat(results, selector.findElements(element));

+    }

+    return (l > 1) ? h.unique(results) : results;

+  }

+});

+

+if (Prototype.Browser.IE) {

+  Object.extend(Selector.handlers, {

+    // IE returns comment nodes on getElementsByTagName("*").

+    // Filter them out.

+    concat: function(a, b) {

+      for (var i = 0, node; node = b[i]; i++)

+        if (node.tagName !== "!") a.push(node);

+      return a;

+    },

+

+    // IE improperly serializes _countedByPrototype in (inner|outer)HTML.

+    unmark: function(nodes) {

+      for (var i = 0, node; node = nodes[i]; i++)

+        node.removeAttribute('_countedByPrototype');

+      return nodes;

+    }

+  });

+}

+

+function $$() {

+  return Selector.findChildElements(document, $A(arguments));

+}

+var Form = {

+  reset: function(form) {

+    $(form).reset();

+    return form;

+  },

+

+  serializeElements: function(elements, options) {

+    if (typeof options != 'object') options = { hash: !!options };

+    else if (Object.isUndefined(options.hash)) options.hash = true;

+    var key, value, submitted = false, submit = options.submit;

+

+    var data = elements.inject({ }, function(result, element) {

+      if (!element.disabled && element.name) {

+        key = element.name; value = $(element).getValue();

+        if (value != null && (element.type != 'submit' || (!submitted &&

+            submit !== false && (!submit || key == submit) && (submitted = true)))) {

+          if (key in result) {

+            // a key is already present; construct an array of values

+            if (!Object.isArray(result[key])) result[key] = [result[key]];

+            result[key].push(value);

+          }

+          else result[key] = value;

+        }

+      }

+      return result;

+    });

+

+    return options.hash ? data : Object.toQueryString(data);

+  }

+};

+

+Form.Methods = {

+  serialize: function(form, options) {

+    return Form.serializeElements(Form.getElements(form), options);

+  },

+

+  getElements: function(form) {

+    return $A($(form).getElementsByTagName('*')).inject([],

+      function(elements, child) {

+        if (Form.Element.Serializers[child.tagName.toLowerCase()])

+          elements.push(Element.extend(child));

+        return elements;

+      }

+    );

+  },

+

+  getInputs: function(form, typeName, name) {

+    form = $(form);

+    var inputs = form.getElementsByTagName('input');

+

+    if (!typeName && !name) return $A(inputs).map(Element.extend);

+

+    for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {

+      var input = inputs[i];

+      if ((typeName && input.type != typeName) || (name && input.name != name))

+        continue;

+      matchingInputs.push(Element.extend(input));

+    }

+

+    return matchingInputs;

+  },

+

+  disable: function(form) {

+    form = $(form);

+    Form.getElements(form).invoke('disable');

+    return form;

+  },

+

+  enable: function(form) {

+    form = $(form);

+    Form.getElements(form).invoke('enable');

+    return form;

+  },

+

+  findFirstElement: function(form) {

+    var elements = $(form).getElements().findAll(function(element) {

+      return 'hidden' != element.type && !element.disabled;

+    });

+    var firstByIndex = elements.findAll(function(element) {

+      return element.hasAttribute('tabIndex') && element.tabIndex >= 0;

+    }).sortBy(function(element) { return element.tabIndex }).first();

+

+    return firstByIndex ? firstByIndex : elements.find(function(element) {

+      return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());

+    });

+  },

+

+  focusFirstElement: function(form) {

+    form = $(form);

+    form.findFirstElement().activate();

+    return form;

+  },

+

+  request: function(form, options) {

+    form = $(form), options = Object.clone(options || { });

+

+    var params = options.parameters, action = form.readAttribute('action') || '';

+    if (action.blank()) action = window.location.href;

+    options.parameters = form.serialize(true);

+

+    if (params) {

+      if (Object.isString(params)) params = params.toQueryParams();

+      Object.extend(options.parameters, params);

+    }

+

+    if (form.hasAttribute('method') && !options.method)

+      options.method = form.method;

+

+    return new Ajax.Request(action, options);

+  }

+};

+

+/*--------------------------------------------------------------------------*/

+

+Form.Element = {

+  focus: function(element) {

+    $(element).focus();

+    return element;

+  },

+

+  select: function(element) {

+    $(element).select();

+    return element;

+  }

+};

+

+Form.Element.Methods = {

+  serialize: function(element) {

+    element = $(element);

+    if (!element.disabled && element.name) {

+      var value = element.getValue();

+      if (value != undefined) {

+        var pair = { };

+        pair[element.name] = value;

+        return Object.toQueryString(pair);

+      }

+    }

+    return '';

+  },

+

+  getValue: function(element) {

+    element = $(element);

+    var method = element.tagName.toLowerCase();

+    return Form.Element.Serializers[method](element);

+  },

+

+  setValue: function(element, value) {

+    element = $(element);

+    var method = element.tagName.toLowerCase();

+    Form.Element.Serializers[method](element, value);

+    return element;

+  },

+

+  clear: function(element) {

+    $(element).value = '';

+    return element;

+  },

+

+  present: function(element) {

+    return $(element).value != '';

+  },

+

+  activate: function(element) {

+    element = $(element);

+    try {

+      element.focus();

+      if (element.select && (element.tagName.toLowerCase() != 'input' ||

+          !['button', 'reset', 'submit'].include(element.type)))

+        element.select();

+    } catch (e) { }

+    return element;

+  },

+

+  disable: function(element) {

+    element = $(element);

+    element.blur();

+    element.disabled = true;

+    return element;

+  },

+

+  enable: function(element) {

+    element = $(element);

+    element.disabled = false;

+    return element;

+  }

+};

+

+/*--------------------------------------------------------------------------*/

+

+var Field = Form.Element;

+var $F = Form.Element.Methods.getValue;

+

+/*--------------------------------------------------------------------------*/

+

+Form.Element.Serializers = {

+  input: function(element, value) {

+    switch (element.type.toLowerCase()) {

+      case 'checkbox':

+      case 'radio':

+        return Form.Element.Serializers.inputSelector(element, value);

+      default:

+        return Form.Element.Serializers.textarea(element, value);

+    }

+  },

+

+  inputSelector: function(element, value) {

+    if (Object.isUndefined(value)) return element.checked ? element.value : null;

+    else element.checked = !!value;

+  },

+

+  textarea: function(element, value) {

+    if (Object.isUndefined(value)) return element.value;

+    else element.value = value;

+  },

+

+  select: function(element, index) {

+    if (Object.isUndefined(index))

+      return this[element.type == 'select-one' ?

+        'selectOne' : 'selectMany'](element);

+    else {

+      var opt, value, single = !Object.isArray(index);

+      for (var i = 0, length = element.length; i < length; i++) {

+        opt = element.options[i];

+        value = this.optionValue(opt);

+        if (single) {

+          if (value == index) {

+            opt.selected = true;

+            return;

+          }

+        }

+        else opt.selected = index.include(value);

+      }

+    }

+  },

+

+  selectOne: function(element) {

+    var index = element.selectedIndex;

+    return index >= 0 ? this.optionValue(element.options[index]) : null;

+  },

+

+  selectMany: function(element) {

+    var values, length = element.length;

+    if (!length) return null;

+

+    for (var i = 0, values = []; i < length; i++) {

+      var opt = element.options[i];

+      if (opt.selected) values.push(this.optionValue(opt));

+    }

+    return values;

+  },

+

+  optionValue: function(opt) {

+    // extend element because hasAttribute may not be native

+    return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;

+  }

+};

+

+/*--------------------------------------------------------------------------*/

+

+Abstract.TimedObserver = Class.create(PeriodicalExecuter, {

+  initialize: function($super, element, frequency, callback) {

+    $super(callback, frequency);

+    this.element   = $(element);

+    this.lastValue = this.getValue();

+  },

+

+  execute: function() {

+    var value = this.getValue();

+    if (Object.isString(this.lastValue) && Object.isString(value) ?

+        this.lastValue != value : String(this.lastValue) != String(value)) {

+      this.callback(this.element, value);

+      this.lastValue = value;

+    }

+  }

+});

+

+Form.Element.Observer = Class.create(Abstract.TimedObserver, {

+  getValue: function() {

+    return Form.Element.getValue(this.element);

+  }

+});

+

+Form.Observer = Class.create(Abstract.TimedObserver, {

+  getValue: function() {

+    return Form.serialize(this.element);

+  }

+});

+

+/*--------------------------------------------------------------------------*/

+

+Abstract.EventObserver = Class.create({

+  initialize: function(element, callback) {

+    this.element  = $(element);

+    this.callback = callback;

+

+    this.lastValue = this.getValue();

+    if (this.element.tagName.toLowerCase() == 'form')

+      this.registerFormCallbacks();

+    else

+      this.registerCallback(this.element);

+  },

+

+  onElementEvent: function() {

+    var value = this.getValue();

+    if (this.lastValue != value) {

+      this.callback(this.element, value);

+      this.lastValue = value;

+    }

+  },

+

+  registerFormCallbacks: function() {

+    Form.getElements(this.element).each(this.registerCallback, this);

+  },

+

+  registerCallback: function(element) {

+    if (element.type) {

+      switch (element.type.toLowerCase()) {

+        case 'checkbox':

+        case 'radio':

+          Event.observe(element, 'click', this.onElementEvent.bind(this));

+          break;

+        default:

+          Event.observe(element, 'change', this.onElementEvent.bind(this));

+          break;

+      }

+    }

+  }

+});

+

+Form.Element.EventObserver = Class.create(Abstract.EventObserver, {

+  getValue: function() {

+    return Form.Element.getValue(this.element);

+  }

+});

+

+Form.EventObserver = Class.create(Abstract.EventObserver, {

+  getValue: function() {

+    return Form.serialize(this.element);

+  }

+});

+if (!window.Event) var Event = { };

+

+Object.extend(Event, {

+  KEY_BACKSPACE: 8,

+  KEY_TAB:       9,

+  KEY_RETURN:   13,

+  KEY_ESC:      27,

+  KEY_LEFT:     37,

+  KEY_UP:       38,

+  KEY_RIGHT:    39,

+  KEY_DOWN:     40,

+  KEY_DELETE:   46,

+  KEY_HOME:     36,

+  KEY_END:      35,

+  KEY_PAGEUP:   33,

+  KEY_PAGEDOWN: 34,

+  KEY_INSERT:   45,

+

+  cache: { },

+

+  relatedTarget: function(event) {

+    var element;

+    switch(event.type) {

+      case 'mouseover': element = event.fromElement; break;

+      case 'mouseout':  element = event.toElement;   break;

+      default: return null;

+    }

+    return Element.extend(element);

+  }

+});

+

+Event.Methods = (function() {

+  var isButton;

+

+  if (Prototype.Browser.IE) {

+    var buttonMap = { 0: 1, 1: 4, 2: 2 };

+    isButton = function(event, code) {

+      return event.button == buttonMap[code];

+    };

+

+  } else if (Prototype.Browser.WebKit) {

+    isButton = function(event, code) {

+      switch (code) {

+        case 0: return event.which == 1 && !event.metaKey;

+        case 1: return event.which == 1 && event.metaKey;

+        default: return false;

+      }

+    };

+

+  } else {

+    isButton = function(event, code) {

+      return event.which ? (event.which === code + 1) : (event.button === code);

+    };

+  }

+

+  return {

+    isLeftClick:   function(event) { return isButton(event, 0) },

+    isMiddleClick: function(event) { return isButton(event, 1) },

+    isRightClick:  function(event) { return isButton(event, 2) },

+

+    element: function(event) {

+      var node = Event.extend(event).target;

+      return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node);

+    },

+

+    findElement: function(event, expression) {

+      var element = Event.element(event);

+      if (!expression) return element;

+      var elements = [element].concat(element.ancestors());

+      return Selector.findElement(elements, expression, 0);

+    },

+

+    pointer: function(event) {

+      return {

+        x: event.pageX || (event.clientX +

+          (document.documentElement.scrollLeft || document.body.scrollLeft)),

+        y: event.pageY || (event.clientY +

+          (document.documentElement.scrollTop || document.body.scrollTop))

+      };

+    },

+

+    pointerX: function(event) { return Event.pointer(event).x },

+    pointerY: function(event) { return Event.pointer(event).y },

+

+    stop: function(event) {

+      Event.extend(event);

+      event.preventDefault();

+      event.stopPropagation();

+      event.stopped = true;

+    }

+  };

+})();

+

+Event.extend = (function() {

+  var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {

+    m[name] = Event.Methods[name].methodize();

+    return m;

+  });

+

+  if (Prototype.Browser.IE) {

+    Object.extend(methods, {

+      stopPropagation: function() { this.cancelBubble = true },

+      preventDefault:  function() { this.returnValue = false },

+      inspect: function() { return "[object Event]" }

+    });

+

+    return function(event) {

+      if (!event) return false;

+      if (event._extendedByPrototype) return event;

+

+      event._extendedByPrototype = Prototype.emptyFunction;

+      var pointer = Event.pointer(event);

+      Object.extend(event, {

+        target: event.srcElement,

+        relatedTarget: Event.relatedTarget(event),

+        pageX:  pointer.x,

+        pageY:  pointer.y

+      });

+      return Object.extend(event, methods);

+    };

+

+  } else {

+    Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__;

+    Object.extend(Event.prototype, methods);

+    return Prototype.K;

+  }

+})();

+

+Object.extend(Event, (function() {

+  var cache = Event.cache;

+

+  function getEventID(element) {

+    if (element._prototypeEventID) return element._prototypeEventID[0];

+    arguments.callee.id = arguments.callee.id || 1;

+    return element._prototypeEventID = [++arguments.callee.id];

+  }

+

+  function getDOMEventName(eventName) {

+    if (eventName && eventName.include(':')) return "dataavailable";

+    return eventName;

+  }

+

+  function getCacheForID(id) {

+    return cache[id] = cache[id] || { };

+  }

+

+  function getWrappersForEventName(id, eventName) {

+    var c = getCacheForID(id);

+    return c[eventName] = c[eventName] || [];

+  }

+

+  function createWrapper(element, eventName, handler) {

+    var id = getEventID(element);

+    var c = getWrappersForEventName(id, eventName);

+    if (c.pluck("handler").include(handler)) return false;

+

+    var wrapper = function(event) {

+      if (!Event || !Event.extend ||

+        (event.eventName && event.eventName != eventName))

+          return false;

+

+      Event.extend(event);

+      handler.call(element, event);

+    };

+

+    wrapper.handler = handler;

+    c.push(wrapper);

+    return wrapper;

+  }

+

+  function findWrapper(id, eventName, handler) {

+    var c = getWrappersForEventName(id, eventName);

+    return c.find(function(wrapper) { return wrapper.handler == handler });

+  }

+

+  function destroyWrapper(id, eventName, handler) {

+    var c = getCacheForID(id);

+    if (!c[eventName]) return false;

+    c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));

+  }

+

+  function destroyCache() {

+    for (var id in cache)

+      for (var eventName in cache[id])

+        cache[id][eventName] = null;

+  }

+

+  if (window.attachEvent) {

+    window.attachEvent("onunload", destroyCache);

+  }

+

+  return {

+    observe: function(element, eventName, handler) {

+      element = $(element);

+      var name = getDOMEventName(eventName);

+

+      var wrapper = createWrapper(element, eventName, handler);

+      if (!wrapper) return element;

+

+      if (element.addEventListener) {

+        element.addEventListener(name, wrapper, false);

+      } else {

+        element.attachEvent("on" + name, wrapper);

+      }

+

+      return element;

+    },

+

+    stopObserving: function(element, eventName, handler) {

+      element = $(element);

+      var id = getEventID(element), name = getDOMEventName(eventName);

+

+      if (!handler && eventName) {

+        getWrappersForEventName(id, eventName).each(function(wrapper) {

+          element.stopObserving(eventName, wrapper.handler);

+        });

+        return element;

+

+      } else if (!eventName) {

+        Object.keys(getCacheForID(id)).each(function(eventName) {

+          element.stopObserving(eventName);

+        });

+        return element;

+      }

+

+      var wrapper = findWrapper(id, eventName, handler);

+      if (!wrapper) return element;

+

+      if (element.removeEventListener) {

+        element.removeEventListener(name, wrapper, false);

+      } else {

+        element.detachEvent("on" + name, wrapper);

+      }

+

+      destroyWrapper(id, eventName, handler);

+

+      return element;

+    },

+

+    fire: function(element, eventName, memo) {

+      element = $(element);

+      if (element == document && document.createEvent && !element.dispatchEvent)

+        element = document.documentElement;

+

+      var event;

+      if (document.createEvent) {

+        event = document.createEvent("HTMLEvents");

+        event.initEvent("dataavailable", true, true);

+      } else {

+        event = document.createEventObject();

+        event.eventType = "ondataavailable";

+      }

+

+      event.eventName = eventName;

+      event.memo = memo || { };

+

+      if (document.createEvent) {

+        element.dispatchEvent(event);

+      } else {

+        element.fireEvent(event.eventType, event);

+      }

+

+      return Event.extend(event);

+    }

+  };

+})());

+

+Object.extend(Event, Event.Methods);

+

+Element.addMethods({

+  fire:          Event.fire,

+  observe:       Event.observe,

+  stopObserving: Event.stopObserving

+});

+

+Object.extend(document, {

+  fire:          Element.Methods.fire.methodize(),

+  observe:       Element.Methods.observe.methodize(),

+  stopObserving: Element.Methods.stopObserving.methodize(),

+  loaded:        false

+});

+

+(function() {

+  /* Support for the DOMContentLoaded event is based on work by Dan Webb,

+     Matthias Miller, Dean Edwards and John Resig. */

+

+  var timer;

+

+  function fireContentLoadedEvent() {

+    if (document.loaded) return;

+    if (timer) window.clearInterval(timer);

+    document.fire("dom:loaded");

+    document.loaded = true;

+  }

+

+  if (document.addEventListener) {

+    if (Prototype.Browser.WebKit) {

+      timer = window.setInterval(function() {

+        if (/loaded|complete/.test(document.readyState))

+          fireContentLoadedEvent();

+      }, 0);

+

+      Event.observe(window, "load", fireContentLoadedEvent);

+

+    } else {

+      document.addEventListener("DOMContentLoaded",

+        fireContentLoadedEvent, false);

+    }

+

+  } else {

+    document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");

+    $("__onDOMContentLoaded").onreadystatechange = function() {

+      if (this.readyState == "complete") {

+        this.onreadystatechange = null;

+        fireContentLoadedEvent();

+      }

+    };

+  }

+})();

+/*------------------------------- DEPRECATED -------------------------------*/

+

+Hash.toQueryString = Object.toQueryString;

+

+var Toggle = { display: Element.toggle };

+

+Element.Methods.childOf = Element.Methods.descendantOf;

+

+var Insertion = {

+  Before: function(element, content) {

+    return Element.insert(element, {before:content});

+  },

+

+  Top: function(element, content) {

+    return Element.insert(element, {top:content});

+  },

+

+  Bottom: function(element, content) {

+    return Element.insert(element, {bottom:content});

+  },

+

+  After: function(element, content) {

+    return Element.insert(element, {after:content});

+  }

+};

+

+var $continue = new Error('"throw $continue" is deprecated, use "return" instead');

+

+// This should be moved to script.aculo.us; notice the deprecated methods

+// further below, that map to the newer Element methods.

+var Position = {

+  // set to true if needed, warning: firefox performance problems

+  // NOT neeeded for page scrolling, only if draggable contained in

+  // scrollable elements

+  includeScrollOffsets: false,

+

+  // must be called before calling withinIncludingScrolloffset, every time the

+  // page is scrolled

+  prepare: function() {

+    this.deltaX =  window.pageXOffset

+                || document.documentElement.scrollLeft

+                || document.body.scrollLeft

+                || 0;

+    this.deltaY =  window.pageYOffset

+                || document.documentElement.scrollTop

+                || document.body.scrollTop

+                || 0;

+  },

+

+  // caches x/y coordinate pair to use with overlap

+  within: function(element, x, y) {

+    if (this.includeScrollOffsets)

+      return this.withinIncludingScrolloffsets(element, x, y);

+    this.xcomp = x;

+    this.ycomp = y;

+    this.offset = Element.cumulativeOffset(element);

+

+    return (y >= this.offset[1] &&

+            y <  this.offset[1] + element.offsetHeight &&

+            x >= this.offset[0] &&

+            x <  this.offset[0] + element.offsetWidth);

+  },

+

+  withinIncludingScrolloffsets: function(element, x, y) {

+    var offsetcache = Element.cumulativeScrollOffset(element);

+

+    this.xcomp = x + offsetcache[0] - this.deltaX;

+    this.ycomp = y + offsetcache[1] - this.deltaY;

+    this.offset = Element.cumulativeOffset(element);

+

+    return (this.ycomp >= this.offset[1] &&

+            this.ycomp <  this.offset[1] + element.offsetHeight &&

+            this.xcomp >= this.offset[0] &&

+            this.xcomp <  this.offset[0] + element.offsetWidth);

+  },

+

+  // within must be called directly before

+  overlap: function(mode, element) {

+    if (!mode) return 0;

+    if (mode == 'vertical')

+      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /

+        element.offsetHeight;

+    if (mode == 'horizontal')

+      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /

+        element.offsetWidth;

+  },

+

+  // Deprecation layer -- use newer Element methods now (1.5.2).

+

+  cumulativeOffset: Element.Methods.cumulativeOffset,

+

+  positionedOffset: Element.Methods.positionedOffset,

+

+  absolutize: function(element) {

+    Position.prepare();

+    return Element.absolutize(element);

+  },

+

+  relativize: function(element) {

+    Position.prepare();

+    return Element.relativize(element);

+  },

+

+  realOffset: Element.Methods.cumulativeScrollOffset,

+

+  offsetParent: Element.Methods.getOffsetParent,

+

+  page: Element.Methods.viewportOffset,

+

+  clone: function(source, target, options) {

+    options = options || { };

+    return Element.clonePosition(target, source, options);

+  }

+};

+

+/*--------------------------------------------------------------------------*/

+

+if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){

+  function iter(name) {

+    return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";

+  }

+

+  instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?

+  function(element, className) {

+    className = className.toString().strip();

+    var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);

+    return cond ? document._getElementsByXPath('.//*' + cond, element) : [];

+  } : function(element, className) {

+    className = className.toString().strip();

+    var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);

+    if (!classNames && !className) return elements;

+

+    var nodes = $(element).getElementsByTagName('*');

+    className = ' ' + className + ' ';

+

+    for (var i = 0, child, cn; child = nodes[i]; i++) {

+      if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||

+          (classNames && classNames.all(function(name) {

+            return !name.toString().blank() && cn.include(' ' + name + ' ');

+          }))))

+        elements.push(Element.extend(child));

+    }

+    return elements;

+  };

+

+  return function(className, parentElement) {

+    return $(parentElement || document.body).getElementsByClassName(className);

+  };

+}(Element.Methods);

+

+/*--------------------------------------------------------------------------*/

+

+Element.ClassNames = Class.create();

+Element.ClassNames.prototype = {

+  initialize: function(element) {

+    this.element = $(element);

+  },

+

+  _each: function(iterator) {

+    this.element.className.split(/\s+/).select(function(name) {

+      return name.length > 0;

+    })._each(iterator);

+  },

+

+  set: function(className) {

+    this.element.className = className;

+  },

+

+  add: function(classNameToAdd) {

+    if (this.include(classNameToAdd)) return;

+    this.set($A(this).concat(classNameToAdd).join(' '));

+  },

+

+  remove: function(classNameToRemove) {

+    if (!this.include(classNameToRemove)) return;

+    this.set($A(this).without(classNameToRemove).join(' '));

+  },

+

+  toString: function() {

+    return $A(this).join(' ');

+  }

+};

+

+Object.extend(Element.ClassNames.prototype, Enumerable);

+

+/*--------------------------------------------------------------------------*/

+

+Element.addMethods();

--- a/js/jQuery.ui.datepicker.js
+++ /dev/null
@@ -1,156 +1,1 @@
-/*!
- * jQuery UI 1.8.5
- *
- * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT or GPL Version 2 licenses.
- * http://jquery.org/license
- *
- * http://docs.jquery.com/UI
- */
-(function(c,j){function k(a){return!c(a).parents().andSelf().filter(function(){return c.curCSS(this,"visibility")==="hidden"||c.expr.filters.hidden(this)}).length}c.ui=c.ui||{};if(!c.ui.version){c.extend(c.ui,{version:"1.8.5",keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,
-NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});c.fn.extend({_focus:c.fn.focus,focus:function(a,b){return typeof a==="number"?this.each(function(){var d=this;setTimeout(function(){c(d).focus();b&&b.call(d)},a)}):this._focus.apply(this,arguments)},scrollParent:function(){var a;a=c.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(c.curCSS(this,
-"position",1))&&/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0);return/fixed/.test(this.css("position"))||!a.length?c(document):a},zIndex:function(a){if(a!==j)return this.css("zIndex",a);if(this.length){a=c(this[0]);for(var b;a.length&&a[0]!==document;){b=a.css("position");
-if(b==="absolute"||b==="relative"||b==="fixed"){b=parseInt(a.css("zIndex"));if(!isNaN(b)&&b!=0)return b}a=a.parent()}}return 0},disableSelection:function(){return this.bind("mousedown.ui-disableSelection selectstart.ui-disableSelection",function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});c.each(["Width","Height"],function(a,b){function d(f,g,l,m){c.each(e,function(){g-=parseFloat(c.curCSS(f,"padding"+this,true))||0;if(l)g-=parseFloat(c.curCSS(f,
-"border"+this+"Width",true))||0;if(m)g-=parseFloat(c.curCSS(f,"margin"+this,true))||0});return g}var e=b==="Width"?["Left","Right"]:["Top","Bottom"],h=b.toLowerCase(),i={innerWidth:c.fn.innerWidth,innerHeight:c.fn.innerHeight,outerWidth:c.fn.outerWidth,outerHeight:c.fn.outerHeight};c.fn["inner"+b]=function(f){if(f===j)return i["inner"+b].call(this);return this.each(function(){c.style(this,h,d(this,f)+"px")})};c.fn["outer"+b]=function(f,g){if(typeof f!=="number")return i["outer"+b].call(this,f);return this.each(function(){c.style(this,
-h,d(this,f,true,g)+"px")})}});c.extend(c.expr[":"],{data:function(a,b,d){return!!c.data(a,d[3])},focusable:function(a){var b=a.nodeName.toLowerCase(),d=c.attr(a,"tabindex");if("area"===b){b=a.parentNode;d=b.name;if(!a.href||!d||b.nodeName.toLowerCase()!=="map")return false;a=c("img[usemap=#"+d+"]")[0];return!!a&&k(a)}return(/input|select|textarea|button|object/.test(b)?!a.disabled:"a"==b?a.href||!isNaN(d):!isNaN(d))&&k(a)},tabbable:function(a){var b=c.attr(a,"tabindex");return(isNaN(b)||b>=0)&&c(a).is(":focusable")}});
-c(function(){var a=document.createElement("div"),b=document.body;c.extend(a.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0});c.support.minHeight=b.appendChild(a).offsetHeight===100;b.removeChild(a).style.display="none"});c.extend(c.ui,{plugin:{add:function(a,b,d){a=c.ui[a].prototype;for(var e in d){a.plugins[e]=a.plugins[e]||[];a.plugins[e].push([b,d[e]])}},call:function(a,b,d){if((b=a.plugins[b])&&a.element[0].parentNode)for(var e=0;e<b.length;e++)a.options[b[e][0]]&&b[e][1].apply(a.element,
-d)}},contains:function(a,b){return document.compareDocumentPosition?a.compareDocumentPosition(b)&16:a!==b&&a.contains(b)},hasScroll:function(a,b){if(c(a).css("overflow")==="hidden")return false;b=b&&b==="left"?"scrollLeft":"scrollTop";var d=false;if(a[b]>0)return true;a[b]=1;d=a[b]>0;a[b]=0;return d},isOverAxis:function(a,b,d){return a>b&&a<b+d},isOver:function(a,b,d,e,h,i){return c.ui.isOverAxis(a,d,h)&&c.ui.isOverAxis(b,e,i)}})}})(jQuery);
-;/*
- * jQuery UI Datepicker 1.8.5
- *
- * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT or GPL Version 2 licenses.
- * http://jquery.org/license
- *
- * http://docs.jquery.com/UI/Datepicker
- *
- * Depends:
- *	jquery.ui.core.js
- */
-(function(d,G){function L(){this.debug=false;this._curInst=null;this._keyEvent=false;this._disabledInputs=[];this._inDialog=this._datepickerShowing=false;this._mainDivId="ui-datepicker-div";this._inlineClass="ui-datepicker-inline";this._appendClass="ui-datepicker-append";this._triggerClass="ui-datepicker-trigger";this._dialogClass="ui-datepicker-dialog";this._disableClass="ui-datepicker-disabled";this._unselectableClass="ui-datepicker-unselectable";this._currentClass="ui-datepicker-current-day";this._dayOverClass=
-"ui-datepicker-days-cell-over";this.regional=[];this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su",
-"Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:false,showMonthAfterYear:false,yearSuffix:""};this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:false,hideIfNoPrevNext:false,navigationAsDateFormat:false,gotoCurrent:false,changeMonth:false,changeYear:false,yearRange:"c-10:c+10",showOtherMonths:false,selectOtherMonths:false,showWeek:false,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",
-minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:true,showButtonPanel:false,autoSize:false};d.extend(this._defaults,this.regional[""]);this.dpDiv=d('<div id="'+this._mainDivId+'" class="ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all ui-helper-hidden-accessible"></div>')}function E(a,b){d.extend(a,
-b);for(var c in b)if(b[c]==null||b[c]==G)a[c]=b[c];return a}d.extend(d.ui,{datepicker:{version:"1.8.5"}});var y=(new Date).getTime();d.extend(L.prototype,{markerClassName:"hasDatepicker",log:function(){this.debug&&console.log.apply("",arguments)},_widgetDatepicker:function(){return this.dpDiv},setDefaults:function(a){E(this._defaults,a||{});return this},_attachDatepicker:function(a,b){var c=null;for(var e in this._defaults){var f=a.getAttribute("date:"+e);if(f){c=c||{};try{c[e]=eval(f)}catch(h){c[e]=
-f}}}e=a.nodeName.toLowerCase();f=e=="div"||e=="span";if(!a.id){this.uuid+=1;a.id="dp"+this.uuid}var i=this._newInst(d(a),f);i.settings=d.extend({},b||{},c||{});if(e=="input")this._connectDatepicker(a,i);else f&&this._inlineDatepicker(a,i)},_newInst:function(a,b){return{id:a[0].id.replace(/([^A-Za-z0-9_])/g,"\\\\$1"),input:a,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:b,dpDiv:!b?this.dpDiv:d('<div class="'+this._inlineClass+' ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>')}},
-_connectDatepicker:function(a,b){var c=d(a);b.append=d([]);b.trigger=d([]);if(!c.hasClass(this.markerClassName)){this._attachments(c,b);c.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).keyup(this._doKeyUp).bind("setData.datepicker",function(e,f,h){b.settings[f]=h}).bind("getData.datepicker",function(e,f){return this._get(b,f)});this._autoSize(b);d.data(a,"datepicker",b)}},_attachments:function(a,b){var c=this._get(b,"appendText"),e=this._get(b,"isRTL");b.append&&
-b.append.remove();if(c){b.append=d('<span class="'+this._appendClass+'">'+c+"</span>");a[e?"before":"after"](b.append)}a.unbind("focus",this._showDatepicker);b.trigger&&b.trigger.remove();c=this._get(b,"showOn");if(c=="focus"||c=="both")a.focus(this._showDatepicker);if(c=="button"||c=="both"){c=this._get(b,"buttonText");var f=this._get(b,"buttonImage");b.trigger=d(this._get(b,"buttonImageOnly")?d("<img/>").addClass(this._triggerClass).attr({src:f,alt:c,title:c}):d('<button type="button"></button>').addClass(this._triggerClass).html(f==
-""?c:d("<img/>").attr({src:f,alt:c,title:c})));a[e?"before":"after"](b.trigger);b.trigger.click(function(){d.datepicker._datepickerShowing&&d.datepicker._lastInput==a[0]?d.datepicker._hideDatepicker():d.datepicker._showDatepicker(a[0]);return false})}},_autoSize:function(a){if(this._get(a,"autoSize")&&!a.inline){var b=new Date(2009,11,20),c=this._get(a,"dateFormat");if(c.match(/[DM]/)){var e=function(f){for(var h=0,i=0,g=0;g<f.length;g++)if(f[g].length>h){h=f[g].length;i=g}return i};b.setMonth(e(this._get(a,
-c.match(/MM/)?"monthNames":"monthNamesShort")));b.setDate(e(this._get(a,c.match(/DD/)?"dayNames":"dayNamesShort"))+20-b.getDay())}a.input.attr("size",this._formatDate(a,b).length)}},_inlineDatepicker:function(a,b){var c=d(a);if(!c.hasClass(this.markerClassName)){c.addClass(this.markerClassName).append(b.dpDiv).bind("setData.datepicker",function(e,f,h){b.settings[f]=h}).bind("getData.datepicker",function(e,f){return this._get(b,f)});d.data(a,"datepicker",b);this._setDate(b,this._getDefaultDate(b),
-true);this._updateDatepicker(b);this._updateAlternate(b)}},_dialogDatepicker:function(a,b,c,e,f){a=this._dialogInst;if(!a){this.uuid+=1;this._dialogInput=d('<input type="text" id="'+("dp"+this.uuid)+'" style="position: absolute; top: -100px; width: 0px; z-index: -10;"/>');this._dialogInput.keydown(this._doKeyDown);d("body").append(this._dialogInput);a=this._dialogInst=this._newInst(this._dialogInput,false);a.settings={};d.data(this._dialogInput[0],"datepicker",a)}E(a.settings,e||{});b=b&&b.constructor==
-Date?this._formatDate(a,b):b;this._dialogInput.val(b);this._pos=f?f.length?f:[f.pageX,f.pageY]:null;if(!this._pos)this._pos=[document.documentElement.clientWidth/2-100+(document.documentElement.scrollLeft||document.body.scrollLeft),document.documentElement.clientHeight/2-150+(document.documentElement.scrollTop||document.body.scrollTop)];this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px");a.settings.onSelect=c;this._inDialog=true;this.dpDiv.addClass(this._dialogClass);this._showDatepicker(this._dialogInput[0]);
-d.blockUI&&d.blockUI(this.dpDiv);d.data(this._dialogInput[0],"datepicker",a);return this},_destroyDatepicker:function(a){var b=d(a),c=d.data(a,"datepicker");if(b.hasClass(this.markerClassName)){var e=a.nodeName.toLowerCase();d.removeData(a,"datepicker");if(e=="input"){c.append.remove();c.trigger.remove();b.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)}else if(e=="div"||e=="span")b.removeClass(this.markerClassName).empty()}},
-_enableDatepicker:function(a){var b=d(a),c=d.data(a,"datepicker");if(b.hasClass(this.markerClassName)){var e=a.nodeName.toLowerCase();if(e=="input"){a.disabled=false;c.trigger.filter("button").each(function(){this.disabled=false}).end().filter("img").css({opacity:"1.0",cursor:""})}else if(e=="div"||e=="span")b.children("."+this._inlineClass).children().removeClass("ui-state-disabled");this._disabledInputs=d.map(this._disabledInputs,function(f){return f==a?null:f})}},_disableDatepicker:function(a){var b=
-d(a),c=d.data(a,"datepicker");if(b.hasClass(this.markerClassName)){var e=a.nodeName.toLowerCase();if(e=="input"){a.disabled=true;c.trigger.filter("button").each(function(){this.disabled=true}).end().filter("img").css({opacity:"0.5",cursor:"default"})}else if(e=="div"||e=="span")b.children("."+this._inlineClass).children().addClass("ui-state-disabled");this._disabledInputs=d.map(this._disabledInputs,function(f){return f==a?null:f});this._disabledInputs[this._disabledInputs.length]=a}},_isDisabledDatepicker:function(a){if(!a)return false;
-for(var b=0;b<this._disabledInputs.length;b++)if(this._disabledInputs[b]==a)return true;return false},_getInst:function(a){try{return d.data(a,"datepicker")}catch(b){throw"Missing instance data for this datepicker";}},_optionDatepicker:function(a,b,c){var e=this._getInst(a);if(arguments.length==2&&typeof b=="string")return b=="defaults"?d.extend({},d.datepicker._defaults):e?b=="all"?d.extend({},e.settings):this._get(e,b):null;var f=b||{};if(typeof b=="string"){f={};f[b]=c}if(e){this._curInst==e&&
-this._hideDatepicker();var h=this._getDateDatepicker(a,true);E(e.settings,f);this._attachments(d(a),e);this._autoSize(e);this._setDateDatepicker(a,h);this._updateDatepicker(e)}},_changeDatepicker:function(a,b,c){this._optionDatepicker(a,b,c)},_refreshDatepicker:function(a){(a=this._getInst(a))&&this._updateDatepicker(a)},_setDateDatepicker:function(a,b){if(a=this._getInst(a)){this._setDate(a,b);this._updateDatepicker(a);this._updateAlternate(a)}},_getDateDatepicker:function(a,b){(a=this._getInst(a))&&
-!a.inline&&this._setDateFromField(a,b);return a?this._getDate(a):null},_doKeyDown:function(a){var b=d.datepicker._getInst(a.target),c=true,e=b.dpDiv.is(".ui-datepicker-rtl");b._keyEvent=true;if(d.datepicker._datepickerShowing)switch(a.keyCode){case 9:d.datepicker._hideDatepicker();c=false;break;case 13:c=d("td."+d.datepicker._dayOverClass,b.dpDiv).add(d("td."+d.datepicker._currentClass,b.dpDiv));c[0]?d.datepicker._selectDay(a.target,b.selectedMonth,b.selectedYear,c[0]):d.datepicker._hideDatepicker();
-return false;case 27:d.datepicker._hideDatepicker();break;case 33:d.datepicker._adjustDate(a.target,a.ctrlKey?-d.datepicker._get(b,"stepBigMonths"):-d.datepicker._get(b,"stepMonths"),"M");break;case 34:d.datepicker._adjustDate(a.target,a.ctrlKey?+d.datepicker._get(b,"stepBigMonths"):+d.datepicker._get(b,"stepMonths"),"M");break;case 35:if(a.ctrlKey||a.metaKey)d.datepicker._clearDate(a.target);c=a.ctrlKey||a.metaKey;break;case 36:if(a.ctrlKey||a.metaKey)d.datepicker._gotoToday(a.target);c=a.ctrlKey||
-a.metaKey;break;case 37:if(a.ctrlKey||a.metaKey)d.datepicker._adjustDate(a.target,e?+1:-1,"D");c=a.ctrlKey||a.metaKey;if(a.originalEvent.altKey)d.datepicker._adjustDate(a.target,a.ctrlKey?-d.datepicker._get(b,"stepBigMonths"):-d.datepicker._get(b,"stepMonths"),"M");break;case 38:if(a.ctrlKey||a.metaKey)d.datepicker._adjustDate(a.target,-7,"D");c=a.ctrlKey||a.metaKey;break;case 39:if(a.ctrlKey||a.metaKey)d.datepicker._adjustDate(a.target,e?-1:+1,"D");c=a.ctrlKey||a.metaKey;if(a.originalEvent.altKey)d.datepicker._adjustDate(a.target,
-a.ctrlKey?+d.datepicker._get(b,"stepBigMonths"):+d.datepicker._get(b,"stepMonths"),"M");break;case 40:if(a.ctrlKey||a.metaKey)d.datepicker._adjustDate(a.target,+7,"D");c=a.ctrlKey||a.metaKey;break;default:c=false}else if(a.keyCode==36&&a.ctrlKey)d.datepicker._showDatepicker(this);else c=false;if(c){a.preventDefault();a.stopPropagation()}},_doKeyPress:function(a){var b=d.datepicker._getInst(a.target);if(d.datepicker._get(b,"constrainInput")){b=d.datepicker._possibleChars(d.datepicker._get(b,"dateFormat"));
-var c=String.fromCharCode(a.charCode==G?a.keyCode:a.charCode);return a.ctrlKey||c<" "||!b||b.indexOf(c)>-1}},_doKeyUp:function(a){a=d.datepicker._getInst(a.target);if(a.input.val()!=a.lastVal)try{if(d.datepicker.parseDate(d.datepicker._get(a,"dateFormat"),a.input?a.input.val():null,d.datepicker._getFormatConfig(a))){d.datepicker._setDateFromField(a);d.datepicker._updateAlternate(a);d.datepicker._updateDatepicker(a)}}catch(b){d.datepicker.log(b)}return true},_showDatepicker:function(a){a=a.target||
-a;if(a.nodeName.toLowerCase()!="input")a=d("input",a.parentNode)[0];if(!(d.datepicker._isDisabledDatepicker(a)||d.datepicker._lastInput==a)){var b=d.datepicker._getInst(a);d.datepicker._curInst&&d.datepicker._curInst!=b&&d.datepicker._curInst.dpDiv.stop(true,true);var c=d.datepicker._get(b,"beforeShow");E(b.settings,c?c.apply(a,[a,b]):{});b.lastVal=null;d.datepicker._lastInput=a;d.datepicker._setDateFromField(b);if(d.datepicker._inDialog)a.value="";if(!d.datepicker._pos){d.datepicker._pos=d.datepicker._findPos(a);
-d.datepicker._pos[1]+=a.offsetHeight}var e=false;d(a).parents().each(function(){e|=d(this).css("position")=="fixed";return!e});if(e&&d.browser.opera){d.datepicker._pos[0]-=document.documentElement.scrollLeft;d.datepicker._pos[1]-=document.documentElement.scrollTop}c={left:d.datepicker._pos[0],top:d.datepicker._pos[1]};d.datepicker._pos=null;b.dpDiv.css({position:"absolute",display:"block",top:"-1000px"});d.datepicker._updateDatepicker(b);c=d.datepicker._checkOffset(b,c,e);b.dpDiv.css({position:d.datepicker._inDialog&&
-d.blockUI?"static":e?"fixed":"absolute",display:"none",left:c.left+"px",top:c.top+"px"});if(!b.inline){c=d.datepicker._get(b,"showAnim");var f=d.datepicker._get(b,"duration"),h=function(){d.datepicker._datepickerShowing=true;var i=d.datepicker._getBorders(b.dpDiv);b.dpDiv.find("iframe.ui-datepicker-cover").css({left:-i[0],top:-i[1],width:b.dpDiv.outerWidth(),height:b.dpDiv.outerHeight()})};b.dpDiv.zIndex(d(a).zIndex()+1);d.effects&&d.effects[c]?b.dpDiv.show(c,d.datepicker._get(b,"showOptions"),f,
-h):b.dpDiv[c||"show"](c?f:null,h);if(!c||!f)h();b.input.is(":visible")&&!b.input.is(":disabled")&&b.input.focus();d.datepicker._curInst=b}}},_updateDatepicker:function(a){var b=this,c=d.datepicker._getBorders(a.dpDiv);a.dpDiv.empty().append(this._generateHTML(a)).find("iframe.ui-datepicker-cover").css({left:-c[0],top:-c[1],width:a.dpDiv.outerWidth(),height:a.dpDiv.outerHeight()}).end().find("button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a").bind("mouseout",function(){d(this).removeClass("ui-state-hover");
-this.className.indexOf("ui-datepicker-prev")!=-1&&d(this).removeClass("ui-datepicker-prev-hover");this.className.indexOf("ui-datepicker-next")!=-1&&d(this).removeClass("ui-datepicker-next-hover")}).bind("mouseover",function(){if(!b._isDisabledDatepicker(a.inline?a.dpDiv.parent()[0]:a.input[0])){d(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover");d(this).addClass("ui-state-hover");this.className.indexOf("ui-datepicker-prev")!=-1&&d(this).addClass("ui-datepicker-prev-hover");
-this.className.indexOf("ui-datepicker-next")!=-1&&d(this).addClass("ui-datepicker-next-hover")}}).end().find("."+this._dayOverClass+" a").trigger("mouseover").end();c=this._getNumberOfMonths(a);var e=c[1];e>1?a.dpDiv.addClass("ui-datepicker-multi-"+e).css("width",17*e+"em"):a.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width("");a.dpDiv[(c[0]!=1||c[1]!=1?"add":"remove")+"Class"]("ui-datepicker-multi");a.dpDiv[(this._get(a,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl");
-a==d.datepicker._curInst&&d.datepicker._datepickerShowing&&a.input&&a.input.is(":visible")&&!a.input.is(":disabled")&&a.input.focus()},_getBorders:function(a){var b=function(c){return{thin:1,medium:2,thick:3}[c]||c};return[parseFloat(b(a.css("border-left-width"))),parseFloat(b(a.css("border-top-width")))]},_checkOffset:function(a,b,c){var e=a.dpDiv.outerWidth(),f=a.dpDiv.outerHeight(),h=a.input?a.input.outerWidth():0,i=a.input?a.input.outerHeight():0,g=document.documentElement.clientWidth+d(document).scrollLeft(),
-k=document.documentElement.clientHeight+d(document).scrollTop();b.left-=this._get(a,"isRTL")?e-h:0;b.left-=c&&b.left==a.input.offset().left?d(document).scrollLeft():0;b.top-=c&&b.top==a.input.offset().top+i?d(document).scrollTop():0;b.left-=Math.min(b.left,b.left+e>g&&g>e?Math.abs(b.left+e-g):0);b.top-=Math.min(b.top,b.top+f>k&&k>f?Math.abs(f+i):0);return b},_findPos:function(a){for(var b=this._get(this._getInst(a),"isRTL");a&&(a.type=="hidden"||a.nodeType!=1);)a=a[b?"previousSibling":"nextSibling"];
-a=d(a).offset();return[a.left,a.top]},_hideDatepicker:function(a){var b=this._curInst;if(!(!b||a&&b!=d.data(a,"datepicker")))if(this._datepickerShowing){a=this._get(b,"showAnim");var c=this._get(b,"duration"),e=function(){d.datepicker._tidyDialog(b);this._curInst=null};d.effects&&d.effects[a]?b.dpDiv.hide(a,d.datepicker._get(b,"showOptions"),c,e):b.dpDiv[a=="slideDown"?"slideUp":a=="fadeIn"?"fadeOut":"hide"](a?c:null,e);a||e();if(a=this._get(b,"onClose"))a.apply(b.input?b.input[0]:null,[b.input?b.input.val():
-"",b]);this._datepickerShowing=false;this._lastInput=null;if(this._inDialog){this._dialogInput.css({position:"absolute",left:"0",top:"-100px"});if(d.blockUI){d.unblockUI();d("body").append(this.dpDiv)}}this._inDialog=false}},_tidyDialog:function(a){a.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(a){if(d.datepicker._curInst){a=d(a.target);a[0].id!=d.datepicker._mainDivId&&a.parents("#"+d.datepicker._mainDivId).length==0&&!a.hasClass(d.datepicker.markerClassName)&&
-!a.hasClass(d.datepicker._triggerClass)&&d.datepicker._datepickerShowing&&!(d.datepicker._inDialog&&d.blockUI)&&d.datepicker._hideDatepicker()}},_adjustDate:function(a,b,c){a=d(a);var e=this._getInst(a[0]);if(!this._isDisabledDatepicker(a[0])){this._adjustInstDate(e,b+(c=="M"?this._get(e,"showCurrentAtPos"):0),c);this._updateDatepicker(e)}},_gotoToday:function(a){a=d(a);var b=this._getInst(a[0]);if(this._get(b,"gotoCurrent")&&b.currentDay){b.selectedDay=b.currentDay;b.drawMonth=b.selectedMonth=b.currentMonth;
-b.drawYear=b.selectedYear=b.currentYear}else{var c=new Date;b.selectedDay=c.getDate();b.drawMonth=b.selectedMonth=c.getMonth();b.drawYear=b.selectedYear=c.getFullYear()}this._notifyChange(b);this._adjustDate(a)},_selectMonthYear:function(a,b,c){a=d(a);var e=this._getInst(a[0]);e._selectingMonthYear=false;e["selected"+(c=="M"?"Month":"Year")]=e["draw"+(c=="M"?"Month":"Year")]=parseInt(b.options[b.selectedIndex].value,10);this._notifyChange(e);this._adjustDate(a)},_clickMonthYear:function(a){var b=
-this._getInst(d(a)[0]);b.input&&b._selectingMonthYear&&setTimeout(function(){b.input.focus()},0);b._selectingMonthYear=!b._selectingMonthYear},_selectDay:function(a,b,c,e){var f=d(a);if(!(d(e).hasClass(this._unselectableClass)||this._isDisabledDatepicker(f[0]))){f=this._getInst(f[0]);f.selectedDay=f.currentDay=d("a",e).html();f.selectedMonth=f.currentMonth=b;f.selectedYear=f.currentYear=c;this._selectDate(a,this._formatDate(f,f.currentDay,f.currentMonth,f.currentYear))}},_clearDate:function(a){a=
-d(a);this._getInst(a[0]);this._selectDate(a,"")},_selectDate:function(a,b){a=this._getInst(d(a)[0]);b=b!=null?b:this._formatDate(a);a.input&&a.input.val(b);this._updateAlternate(a);var c=this._get(a,"onSelect");if(c)c.apply(a.input?a.input[0]:null,[b,a]);else a.input&&a.input.trigger("change");if(a.inline)this._updateDatepicker(a);else{this._hideDatepicker();this._lastInput=a.input[0];typeof a.input[0]!="object"&&a.input.focus();this._lastInput=null}},_updateAlternate:function(a){var b=this._get(a,
-"altField");if(b){var c=this._get(a,"altFormat")||this._get(a,"dateFormat"),e=this._getDate(a),f=this.formatDate(c,e,this._getFormatConfig(a));d(b).each(function(){d(this).val(f)})}},noWeekends:function(a){a=a.getDay();return[a>0&&a<6,""]},iso8601Week:function(a){a=new Date(a.getTime());a.setDate(a.getDate()+4-(a.getDay()||7));var b=a.getTime();a.setMonth(0);a.setDate(1);return Math.floor(Math.round((b-a)/864E5)/7)+1},parseDate:function(a,b,c){if(a==null||b==null)throw"Invalid arguments";b=typeof b==
-"object"?b.toString():b+"";if(b=="")return null;for(var e=(c?c.shortYearCutoff:null)||this._defaults.shortYearCutoff,f=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,h=(c?c.dayNames:null)||this._defaults.dayNames,i=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,g=(c?c.monthNames:null)||this._defaults.monthNames,k=c=-1,l=-1,u=-1,j=false,o=function(p){(p=z+1<a.length&&a.charAt(z+1)==p)&&z++;return p},m=function(p){o(p);p=new RegExp("^\\d{1,"+(p=="@"?14:p=="!"?20:p=="y"?4:p=="o"?
-3:2)+"}");p=b.substring(s).match(p);if(!p)throw"Missing number at position "+s;s+=p[0].length;return parseInt(p[0],10)},n=function(p,w,H){p=o(p)?H:w;for(w=0;w<p.length;w++)if(b.substr(s,p[w].length).toLowerCase()==p[w].toLowerCase()){s+=p[w].length;return w+1}throw"Unknown name at position "+s;},r=function(){if(b.charAt(s)!=a.charAt(z))throw"Unexpected literal at position "+s;s++},s=0,z=0;z<a.length;z++)if(j)if(a.charAt(z)=="'"&&!o("'"))j=false;else r();else switch(a.charAt(z)){case "d":l=m("d");
-break;case "D":n("D",f,h);break;case "o":u=m("o");break;case "m":k=m("m");break;case "M":k=n("M",i,g);break;case "y":c=m("y");break;case "@":var v=new Date(m("@"));c=v.getFullYear();k=v.getMonth()+1;l=v.getDate();break;case "!":v=new Date((m("!")-this._ticksTo1970)/1E4);c=v.getFullYear();k=v.getMonth()+1;l=v.getDate();break;case "'":if(o("'"))r();else j=true;break;default:r()}if(c==-1)c=(new Date).getFullYear();else if(c<100)c+=(new Date).getFullYear()-(new Date).getFullYear()%100+(c<=e?0:-100);if(u>
--1){k=1;l=u;do{e=this._getDaysInMonth(c,k-1);if(l<=e)break;k++;l-=e}while(1)}v=this._daylightSavingAdjust(new Date(c,k-1,l));if(v.getFullYear()!=c||v.getMonth()+1!=k||v.getDate()!=l)throw"Invalid date";return v},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925))*24*
-60*60*1E7,formatDate:function(a,b,c){if(!b)return"";var e=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,f=(c?c.dayNames:null)||this._defaults.dayNames,h=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort;c=(c?c.monthNames:null)||this._defaults.monthNames;var i=function(o){(o=j+1<a.length&&a.charAt(j+1)==o)&&j++;return o},g=function(o,m,n){m=""+m;if(i(o))for(;m.length<n;)m="0"+m;return m},k=function(o,m,n,r){return i(o)?r[m]:n[m]},l="",u=false;if(b)for(var j=0;j<a.length;j++)if(u)if(a.charAt(j)==
-"'"&&!i("'"))u=false;else l+=a.charAt(j);else switch(a.charAt(j)){case "d":l+=g("d",b.getDate(),2);break;case "D":l+=k("D",b.getDay(),e,f);break;case "o":l+=g("o",(b.getTime()-(new Date(b.getFullYear(),0,0)).getTime())/864E5,3);break;case "m":l+=g("m",b.getMonth()+1,2);break;case "M":l+=k("M",b.getMonth(),h,c);break;case "y":l+=i("y")?b.getFullYear():(b.getYear()%100<10?"0":"")+b.getYear()%100;break;case "@":l+=b.getTime();break;case "!":l+=b.getTime()*1E4+this._ticksTo1970;break;case "'":if(i("'"))l+=
-"'";else u=true;break;default:l+=a.charAt(j)}return l},_possibleChars:function(a){for(var b="",c=false,e=function(h){(h=f+1<a.length&&a.charAt(f+1)==h)&&f++;return h},f=0;f<a.length;f++)if(c)if(a.charAt(f)=="'"&&!e("'"))c=false;else b+=a.charAt(f);else switch(a.charAt(f)){case "d":case "m":case "y":case "@":b+="0123456789";break;case "D":case "M":return null;case "'":if(e("'"))b+="'";else c=true;break;default:b+=a.charAt(f)}return b},_get:function(a,b){return a.settings[b]!==G?a.settings[b]:this._defaults[b]},
-_setDateFromField:function(a,b){if(a.input.val()!=a.lastVal){var c=this._get(a,"dateFormat"),e=a.lastVal=a.input?a.input.val():null,f,h;f=h=this._getDefaultDate(a);var i=this._getFormatConfig(a);try{f=this.parseDate(c,e,i)||h}catch(g){this.log(g);e=b?"":e}a.selectedDay=f.getDate();a.drawMonth=a.selectedMonth=f.getMonth();a.drawYear=a.selectedYear=f.getFullYear();a.currentDay=e?f.getDate():0;a.currentMonth=e?f.getMonth():0;a.currentYear=e?f.getFullYear():0;this._adjustInstDate(a)}},_getDefaultDate:function(a){return this._restrictMinMax(a,
-this._determineDate(a,this._get(a,"defaultDate"),new Date))},_determineDate:function(a,b,c){var e=function(h){var i=new Date;i.setDate(i.getDate()+h);return i},f=function(h){try{return d.datepicker.parseDate(d.datepicker._get(a,"dateFormat"),h,d.datepicker._getFormatConfig(a))}catch(i){}var g=(h.toLowerCase().match(/^c/)?d.datepicker._getDate(a):null)||new Date,k=g.getFullYear(),l=g.getMonth();g=g.getDate();for(var u=/([+-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g,j=u.exec(h);j;){switch(j[2]||"d"){case "d":case "D":g+=
-parseInt(j[1],10);break;case "w":case "W":g+=parseInt(j[1],10)*7;break;case "m":case "M":l+=parseInt(j[1],10);g=Math.min(g,d.datepicker._getDaysInMonth(k,l));break;case "y":case "Y":k+=parseInt(j[1],10);g=Math.min(g,d.datepicker._getDaysInMonth(k,l));break}j=u.exec(h)}return new Date(k,l,g)};if(b=(b=b==null?c:typeof b=="string"?f(b):typeof b=="number"?isNaN(b)?c:e(b):b)&&b.toString()=="Invalid Date"?c:b){b.setHours(0);b.setMinutes(0);b.setSeconds(0);b.setMilliseconds(0)}return this._daylightSavingAdjust(b)},
-_daylightSavingAdjust:function(a){if(!a)return null;a.setHours(a.getHours()>12?a.getHours()+2:0);return a},_setDate:function(a,b,c){var e=!b,f=a.selectedMonth,h=a.selectedYear;b=this._restrictMinMax(a,this._determineDate(a,b,new Date));a.selectedDay=a.currentDay=b.getDate();a.drawMonth=a.selectedMonth=a.currentMonth=b.getMonth();a.drawYear=a.selectedYear=a.currentYear=b.getFullYear();if((f!=a.selectedMonth||h!=a.selectedYear)&&!c)this._notifyChange(a);this._adjustInstDate(a);if(a.input)a.input.val(e?
-"":this._formatDate(a))},_getDate:function(a){return!a.currentYear||a.input&&a.input.val()==""?null:this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay))},_generateHTML:function(a){var b=new Date;b=this._daylightSavingAdjust(new Date(b.getFullYear(),b.getMonth(),b.getDate()));var c=this._get(a,"isRTL"),e=this._get(a,"showButtonPanel"),f=this._get(a,"hideIfNoPrevNext"),h=this._get(a,"navigationAsDateFormat"),i=this._getNumberOfMonths(a),g=this._get(a,"showCurrentAtPos"),k=
-this._get(a,"stepMonths"),l=i[0]!=1||i[1]!=1,u=this._daylightSavingAdjust(!a.currentDay?new Date(9999,9,9):new Date(a.currentYear,a.currentMonth,a.currentDay)),j=this._getMinMaxDate(a,"min"),o=this._getMinMaxDate(a,"max");g=a.drawMonth-g;var m=a.drawYear;if(g<0){g+=12;m--}if(o){var n=this._daylightSavingAdjust(new Date(o.getFullYear(),o.getMonth()-i[0]*i[1]+1,o.getDate()));for(n=j&&n<j?j:n;this._daylightSavingAdjust(new Date(m,g,1))>n;){g--;if(g<0){g=11;m--}}}a.drawMonth=g;a.drawYear=m;n=this._get(a,
-"prevText");n=!h?n:this.formatDate(n,this._daylightSavingAdjust(new Date(m,g-k,1)),this._getFormatConfig(a));n=this._canAdjustMonth(a,-1,m,g)?'<a class="ui-datepicker-prev ui-corner-all" onclick="DP_jQuery_'+y+".datepicker._adjustDate('#"+a.id+"', -"+k+", 'M');\" title=\""+n+'"><span class="ui-icon ui-icon-circle-triangle-'+(c?"e":"w")+'">'+n+"</span></a>":f?"":'<a class="ui-datepicker-prev ui-corner-all ui-state-disabled" title="'+n+'"><span class="ui-icon ui-icon-circle-triangle-'+(c?"e":"w")+'">'+
-n+"</span></a>";var r=this._get(a,"nextText");r=!h?r:this.formatDate(r,this._daylightSavingAdjust(new Date(m,g+k,1)),this._getFormatConfig(a));f=this._canAdjustMonth(a,+1,m,g)?'<a class="ui-datepicker-next ui-corner-all" onclick="DP_jQuery_'+y+".datepicker._adjustDate('#"+a.id+"', +"+k+", 'M');\" title=\""+r+'"><span class="ui-icon ui-icon-circle-triangle-'+(c?"w":"e")+'">'+r+"</span></a>":f?"":'<a class="ui-datepicker-next ui-corner-all ui-state-disabled" title="'+r+'"><span class="ui-icon ui-icon-circle-triangle-'+
-(c?"w":"e")+'">'+r+"</span></a>";k=this._get(a,"currentText");r=this._get(a,"gotoCurrent")&&a.currentDay?u:b;k=!h?k:this.formatDate(k,r,this._getFormatConfig(a));h=!a.inline?'<button type="button" class="ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all" onclick="DP_jQuery_'+y+'.datepicker._hideDatepicker();">'+this._get(a,"closeText")+"</button>":"";e=e?'<div class="ui-datepicker-buttonpane ui-widget-content">'+(c?h:"")+(this._isInRange(a,r)?'<button type="button" class="ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all" onclick="DP_jQuery_'+
-y+".datepicker._gotoToday('#"+a.id+"');\">"+k+"</button>":"")+(c?"":h)+"</div>":"";h=parseInt(this._get(a,"firstDay"),10);h=isNaN(h)?0:h;k=this._get(a,"showWeek");r=this._get(a,"dayNames");this._get(a,"dayNamesShort");var s=this._get(a,"dayNamesMin"),z=this._get(a,"monthNames"),v=this._get(a,"monthNamesShort"),p=this._get(a,"beforeShowDay"),w=this._get(a,"showOtherMonths"),H=this._get(a,"selectOtherMonths");this._get(a,"calculateWeek");for(var M=this._getDefaultDate(a),I="",C=0;C<i[0];C++){for(var N=
-"",D=0;D<i[1];D++){var J=this._daylightSavingAdjust(new Date(m,g,a.selectedDay)),t=" ui-corner-all",x="";if(l){x+='<div class="ui-datepicker-group';if(i[1]>1)switch(D){case 0:x+=" ui-datepicker-group-first";t=" ui-corner-"+(c?"right":"left");break;case i[1]-1:x+=" ui-datepicker-group-last";t=" ui-corner-"+(c?"left":"right");break;default:x+=" ui-datepicker-group-middle";t="";break}x+='">'}x+='<div class="ui-datepicker-header ui-widget-header ui-helper-clearfix'+t+'">'+(/all|left/.test(t)&&C==0?c?
-f:n:"")+(/all|right/.test(t)&&C==0?c?n:f:"")+this._generateMonthYearHeader(a,g,m,j,o,C>0||D>0,z,v)+'</div><table class="ui-datepicker-calendar"><thead><tr>';var A=k?'<th class="ui-datepicker-week-col">'+this._get(a,"weekHeader")+"</th>":"";for(t=0;t<7;t++){var q=(t+h)%7;A+="<th"+((t+h+6)%7>=5?' class="ui-datepicker-week-end"':"")+'><span title="'+r[q]+'">'+s[q]+"</span></th>"}x+=A+"</tr></thead><tbody>";A=this._getDaysInMonth(m,g);if(m==a.selectedYear&&g==a.selectedMonth)a.selectedDay=Math.min(a.selectedDay,
-A);t=(this._getFirstDayOfMonth(m,g)-h+7)%7;A=l?6:Math.ceil((t+A)/7);q=this._daylightSavingAdjust(new Date(m,g,1-t));for(var O=0;O<A;O++){x+="<tr>";var P=!k?"":'<td class="ui-datepicker-week-col">'+this._get(a,"calculateWeek")(q)+"</td>";for(t=0;t<7;t++){var F=p?p.apply(a.input?a.input[0]:null,[q]):[true,""],B=q.getMonth()!=g,K=B&&!H||!F[0]||j&&q<j||o&&q>o;P+='<td class="'+((t+h+6)%7>=5?" ui-datepicker-week-end":"")+(B?" ui-datepicker-other-month":"")+(q.getTime()==J.getTime()&&g==a.selectedMonth&&
-a._keyEvent||M.getTime()==q.getTime()&&M.getTime()==J.getTime()?" "+this._dayOverClass:"")+(K?" "+this._unselectableClass+" ui-state-disabled":"")+(B&&!w?"":" "+F[1]+(q.getTime()==u.getTime()?" "+this._currentClass:"")+(q.getTime()==b.getTime()?" ui-datepicker-today":""))+'"'+((!B||w)&&F[2]?' title="'+F[2]+'"':"")+(K?"":' onclick="DP_jQuery_'+y+".datepicker._selectDay('#"+a.id+"',"+q.getMonth()+","+q.getFullYear()+', this);return false;"')+">"+(B&&!w?"&#xa0;":K?'<span class="ui-state-default">'+q.getDate()+
-"</span>":'<a class="ui-state-default'+(q.getTime()==b.getTime()?" ui-state-highlight":"")+(q.getTime()==J.getTime()?" ui-state-active":"")+(B?" ui-priority-secondary":"")+'" href="#">'+q.getDate()+"</a>")+"</td>";q.setDate(q.getDate()+1);q=this._daylightSavingAdjust(q)}x+=P+"</tr>"}g++;if(g>11){g=0;m++}x+="</tbody></table>"+(l?"</div>"+(i[0]>0&&D==i[1]-1?'<div class="ui-datepicker-row-break"></div>':""):"");N+=x}I+=N}I+=e+(d.browser.msie&&parseInt(d.browser.version,10)<7&&!a.inline?'<iframe src="javascript:false;" class="ui-datepicker-cover" frameborder="0"></iframe>':
-"");a._keyEvent=false;return I},_generateMonthYearHeader:function(a,b,c,e,f,h,i,g){var k=this._get(a,"changeMonth"),l=this._get(a,"changeYear"),u=this._get(a,"showMonthAfterYear"),j='<div class="ui-datepicker-title">',o="";if(h||!k)o+='<span class="ui-datepicker-month">'+i[b]+"</span>";else{i=e&&e.getFullYear()==c;var m=f&&f.getFullYear()==c;o+='<select class="ui-datepicker-month" onchange="DP_jQuery_'+y+".datepicker._selectMonthYear('#"+a.id+"', this, 'M');\" onclick=\"DP_jQuery_"+y+".datepicker._clickMonthYear('#"+
-a.id+"');\">";for(var n=0;n<12;n++)if((!i||n>=e.getMonth())&&(!m||n<=f.getMonth()))o+='<option value="'+n+'"'+(n==b?' selected="selected"':"")+">"+g[n]+"</option>";o+="</select>"}u||(j+=o+(h||!(k&&l)?"&#xa0;":""));if(h||!l)j+='<span class="ui-datepicker-year">'+c+"</span>";else{g=this._get(a,"yearRange").split(":");var r=(new Date).getFullYear();i=function(s){s=s.match(/c[+-].*/)?c+parseInt(s.substring(1),10):s.match(/[+-].*/)?r+parseInt(s,10):parseInt(s,10);return isNaN(s)?r:s};b=i(g[0]);g=Math.max(b,
-i(g[1]||""));b=e?Math.max(b,e.getFullYear()):b;g=f?Math.min(g,f.getFullYear()):g;for(j+='<select class="ui-datepicker-year" onchange="DP_jQuery_'+y+".datepicker._selectMonthYear('#"+a.id+"', this, 'Y');\" onclick=\"DP_jQuery_"+y+".datepicker._clickMonthYear('#"+a.id+"');\">";b<=g;b++)j+='<option value="'+b+'"'+(b==c?' selected="selected"':"")+">"+b+"</option>";j+="</select>"}j+=this._get(a,"yearSuffix");if(u)j+=(h||!(k&&l)?"&#xa0;":"")+o;j+="</div>";return j},_adjustInstDate:function(a,b,c){var e=
-a.drawYear+(c=="Y"?b:0),f=a.drawMonth+(c=="M"?b:0);b=Math.min(a.selectedDay,this._getDaysInMonth(e,f))+(c=="D"?b:0);e=this._restrictMinMax(a,this._daylightSavingAdjust(new Date(e,f,b)));a.selectedDay=e.getDate();a.drawMonth=a.selectedMonth=e.getMonth();a.drawYear=a.selectedYear=e.getFullYear();if(c=="M"||c=="Y")this._notifyChange(a)},_restrictMinMax:function(a,b){var c=this._getMinMaxDate(a,"min");a=this._getMinMaxDate(a,"max");b=c&&b<c?c:b;return b=a&&b>a?a:b},_notifyChange:function(a){var b=this._get(a,
-"onChangeMonthYear");if(b)b.apply(a.input?a.input[0]:null,[a.selectedYear,a.selectedMonth+1,a])},_getNumberOfMonths:function(a){a=this._get(a,"numberOfMonths");return a==null?[1,1]:typeof a=="number"?[1,a]:a},_getMinMaxDate:function(a,b){return this._determineDate(a,this._get(a,b+"Date"),null)},_getDaysInMonth:function(a,b){return 32-(new Date(a,b,32)).getDate()},_getFirstDayOfMonth:function(a,b){return(new Date(a,b,1)).getDay()},_canAdjustMonth:function(a,b,c,e){var f=this._getNumberOfMonths(a);
-c=this._daylightSavingAdjust(new Date(c,e+(b<0?b:f[0]*f[1]),1));b<0&&c.setDate(this._getDaysInMonth(c.getFullYear(),c.getMonth()));return this._isInRange(a,c)},_isInRange:function(a,b){var c=this._getMinMaxDate(a,"min");a=this._getMinMaxDate(a,"max");return(!c||b.getTime()>=c.getTime())&&(!a||b.getTime()<=a.getTime())},_getFormatConfig:function(a){var b=this._get(a,"shortYearCutoff");b=typeof b!="string"?b:(new Date).getFullYear()%100+parseInt(b,10);return{shortYearCutoff:b,dayNamesShort:this._get(a,
-"dayNamesShort"),dayNames:this._get(a,"dayNames"),monthNamesShort:this._get(a,"monthNamesShort"),monthNames:this._get(a,"monthNames")}},_formatDate:function(a,b,c,e){if(!b){a.currentDay=a.selectedDay;a.currentMonth=a.selectedMonth;a.currentYear=a.selectedYear}b=b?typeof b=="object"?b:this._daylightSavingAdjust(new Date(e,c,b)):this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return this.formatDate(this._get(a,"dateFormat"),b,this._getFormatConfig(a))}});d.fn.datepicker=
-function(a){if(!d.datepicker.initialized){d(document).mousedown(d.datepicker._checkExternalClick).find("body").append(d.datepicker.dpDiv);d.datepicker.initialized=true}var b=Array.prototype.slice.call(arguments,1);if(typeof a=="string"&&(a=="isDisabled"||a=="getDate"||a=="widget"))return d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this[0]].concat(b));if(a=="option"&&arguments.length==2&&typeof arguments[1]=="string")return d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this[0]].concat(b));
-return this.each(function(){typeof a=="string"?d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this].concat(b)):d.datepicker._attachDatepicker(this,a)})};d.datepicker=new L;d.datepicker.initialized=false;d.datepicker.uuid=(new Date).getTime();d.datepicker.version="1.8.5";window["DP_jQuery_"+y]=d})(jQuery);
-;/*
-* jQuery Mobile Framework : temporary extension to port jQuery UI's datepicker for mobile
-* Copyright (c) jQuery Project
-* Dual licensed under the MIT or GPL Version 2 licenses.
-* http://jquery.org/license
-*/
-(function($, undefined ) {
 
-	//cache previous datepicker ui method
-	var prevDp = $.fn.datepicker;
-	
-	//rewrite datepicker
-	$.fn.datepicker = function( options ){
-		
-		var dp = this;
-	
-		//call cached datepicker plugin
-		prevDp.call( this, options );
-		
-		//extend with some dom manipulation to update the markup for jQM
-		//call immediately
-		function updateDatepicker(){
-			$( ".ui-datepicker-header", dp ).addClass("ui-body-c ui-corner-top").removeClass("ui-corner-all");
-			$( ".ui-datepicker-prev, .ui-datepicker-next", dp ).attr("href", "#");
-			$( ".ui-datepicker-prev", dp ).buttonMarkup({iconpos: "notext", icon: "arrow-l", shadow: true, corners: true});
-			$( ".ui-datepicker-next", dp ).buttonMarkup({iconpos: "notext", icon: "arrow-r", shadow: true, corners: true});
-			$( ".ui-datepicker-calendar th", dp ).addClass("ui-bar-c");
-			$( ".ui-datepicker-calendar td", dp ).addClass("ui-body-c");
-			$( ".ui-datepicker-calendar a", dp ).buttonMarkup({corners: false, shadow: false}); 
-			$( ".ui-datepicker-calendar a.ui-state-active", dp ).addClass("ui-btn-active"); // selected date
-			$( ".ui-datepicker-calendar a.ui-state-highlight", dp ).addClass("ui-btn-up-e"); // today"s date
-	        $( ".ui-datepicker-calendar .ui-btn", dp ).each(function(){
-				var el = $(this);
-				// remove extra button markup - necessary for date value to be interpreted correctly
-				el.html( el.find( ".ui-btn-text" ).text() ); 
-	        });
-		};
-		
-		//update now
-		updateDatepicker();
-		
-		// and on click
-		$( dp ).click( updateDatepicker );
-		
-		//return jqm obj 
-		return this;
-	};
-		
-	//bind to pagecreate to automatically enhance date inputs	
-	$( ".ui-page" ).live( "pagecreate", function(){     
-		$( "#date, input[type='date'], input[data-type='date']" ).each(function(){
-		    if ($(this).hasClass("hasDatepicker") == false) {
-			$(this).after( $( "<div />" ).datepicker({ altField: "#" + $(this).attr( "id" ), showOtherMonths: true }) );
-			$(this).addClass("hasDatepicker");
-		    }
-		}); 
-    });
-})( jQuery );
-

--- a/js/jquery.effects.core.js
+++ /dev/null
@@ -1,748 +1,1 @@
-/*
- * jQuery UI Effects @VERSION
- *
- * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT or GPL Version 2 licenses.
- * http://jquery.org/license
- *
- * http://docs.jquery.com/UI/Effects/
- */
-;jQuery.effects || (function($, undefined) {
 
-$.effects = {};
-
-
-
-/******************************************************************************/
-/****************************** COLOR ANIMATIONS ******************************/
-/******************************************************************************/
-
-// override the animation for color styles
-$.each(['backgroundColor', 'borderBottomColor', 'borderLeftColor',
-	'borderRightColor', 'borderTopColor', 'borderColor', 'color', 'outlineColor'],
-function(i, attr) {
-	$.fx.step[attr] = function(fx) {
-		if (!fx.colorInit) {
-			fx.start = getColor(fx.elem, attr);
-			fx.end = getRGB(fx.end);
-			fx.colorInit = true;
-		}
-
-		fx.elem.style[attr] = 'rgb(' +
-			Math.max(Math.min(parseInt((fx.pos * (fx.end[0] - fx.start[0])) + fx.start[0], 10), 255), 0) + ',' +
-			Math.max(Math.min(parseInt((fx.pos * (fx.end[1] - fx.start[1])) + fx.start[1], 10), 255), 0) + ',' +
-			Math.max(Math.min(parseInt((fx.pos * (fx.end[2] - fx.start[2])) + fx.start[2], 10), 255), 0) + ')';
-	};
-});
-
-// Color Conversion functions from highlightFade
-// By Blair Mitchelmore
-// http://jquery.offput.ca/highlightFade/
-
-// Parse strings looking for color tuples [255,255,255]
-function getRGB(color) {
-		var result;
-
-		// Check if we're already dealing with an array of colors
-		if ( color && color.constructor == Array && color.length == 3 )
-				return color;
-
-		// Look for rgb(num,num,num)
-		if (result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color))
-				return [parseInt(result[1],10), parseInt(result[2],10), parseInt(result[3],10)];
-
-		// Look for rgb(num%,num%,num%)
-		if (result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(color))
-				return [parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55];
-
-		// Look for #a0b1c2
-		if (result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color))
-				return [parseInt(result[1],16), parseInt(result[2],16), parseInt(result[3],16)];
-
-		// Look for #fff
-		if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color))
-				return [parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16)];
-
-		// Look for rgba(0, 0, 0, 0) == transparent in Safari 3
-		if (result = /rgba\(0, 0, 0, 0\)/.exec(color))
-				return colors['transparent'];
-
-		// Otherwise, we're most likely dealing with a named color
-		return colors[$.trim(color).toLowerCase()];
-}
-
-function getColor(elem, attr) {
-		var color;
-
-		do {
-				color = $.curCSS(elem, attr);
-
-				// Keep going until we find an element that has color, or we hit the body
-				if ( color != '' && color != 'transparent' || $.nodeName(elem, "body") )
-						break;
-
-				attr = "backgroundColor";
-		} while ( elem = elem.parentNode );
-
-		return getRGB(color);
-};
-
-// Some named colors to work with
-// From Interface by Stefan Petre
-// http://interface.eyecon.ro/
-
-var colors = {
-	aqua:[0,255,255],
-	azure:[240,255,255],
-	beige:[245,245,220],
-	black:[0,0,0],
-	blue:[0,0,255],
-	brown:[165,42,42],
-	cyan:[0,255,255],
-	darkblue:[0,0,139],
-	darkcyan:[0,139,139],
-	darkgrey:[169,169,169],
-	darkgreen:[0,100,0],
-	darkkhaki:[189,183,107],
-	darkmagenta:[139,0,139],
-	darkolivegreen:[85,107,47],
-	darkorange:[255,140,0],
-	darkorchid:[153,50,204],
-	darkred:[139,0,0],
-	darksalmon:[233,150,122],
-	darkviolet:[148,0,211],
-	fuchsia:[255,0,255],
-	gold:[255,215,0],
-	green:[0,128,0],
-	indigo:[75,0,130],
-	khaki:[240,230,140],
-	lightblue:[173,216,230],
-	lightcyan:[224,255,255],
-	lightgreen:[144,238,144],
-	lightgrey:[211,211,211],
-	lightpink:[255,182,193],
-	lightyellow:[255,255,224],
-	lime:[0,255,0],
-	magenta:[255,0,255],
-	maroon:[128,0,0],
-	navy:[0,0,128],
-	olive:[128,128,0],
-	orange:[255,165,0],
-	pink:[255,192,203],
-	purple:[128,0,128],
-	violet:[128,0,128],
-	red:[255,0,0],
-	silver:[192,192,192],
-	white:[255,255,255],
-	yellow:[255,255,0],
-	transparent: [255,255,255]
-};
-
-
-
-/******************************************************************************/
-/****************************** CLASS ANIMATIONS ******************************/
-/******************************************************************************/
-
-var classAnimationActions = ['add', 'remove', 'toggle'],
-	shorthandStyles = {
-		border: 1,
-		borderBottom: 1,
-		borderColor: 1,
-		borderLeft: 1,
-		borderRight: 1,
-		borderTop: 1,
-		borderWidth: 1,
-		margin: 1,
-		padding: 1
-	};
-
-function getElementStyles() {
-	var style = document.defaultView
-			? document.defaultView.getComputedStyle(this, null)
-			: this.currentStyle,
-		newStyle = {},
-		key,
-		camelCase;
-
-	// webkit enumerates style porperties
-	if (style && style.length && style[0] && style[style[0]]) {
-		var len = style.length;
-		while (len--) {
-			key = style[len];
-			if (typeof style[key] == 'string') {
-				camelCase = key.replace(/\-(\w)/g, function(all, letter){
-					return letter.toUpperCase();
-				});
-				newStyle[camelCase] = style[key];
-			}
-		}
-	} else {
-		for (key in style) {
-			if (typeof style[key] === 'string') {
-				newStyle[key] = style[key];
-			}
-		}
-	}
-	
-	return newStyle;
-}
-
-function filterStyles(styles) {
-	var name, value;
-	for (name in styles) {
-		value = styles[name];
-		if (
-			// ignore null and undefined values
-			value == null ||
-			// ignore functions (when does this occur?)
-			$.isFunction(value) ||
-			// shorthand styles that need to be expanded
-			name in shorthandStyles ||
-			// ignore scrollbars (break in IE)
-			(/scrollbar/).test(name) ||
-
-			// only colors or values that can be converted to numbers
-			(!(/color/i).test(name) && isNaN(parseFloat(value)))
-		) {
-			delete styles[name];
-		}
-	}
-	
-	return styles;
-}
-
-function styleDifference(oldStyle, newStyle) {
-	var diff = { _: 0 }, // http://dev.jquery.com/ticket/5459
-		name;
-
-	for (name in newStyle) {
-		if (oldStyle[name] != newStyle[name]) {
-			diff[name] = newStyle[name];
-		}
-	}
-
-	return diff;
-}
-
-$.effects.animateClass = function(value, duration, easing, callback) {
-	if ($.isFunction(easing)) {
-		callback = easing;
-		easing = null;
-	}
-
-	return this.queue('fx', function() {
-		var that = $(this),
-			originalStyleAttr = that.attr('style') || ' ',
-			originalStyle = filterStyles(getElementStyles.call(this)),
-			newStyle,
-			className = that.attr('className');
-
-		$.each(classAnimationActions, function(i, action) {
-			if (value[action]) {
-				that[action + 'Class'](value[action]);
-			}
-		});
-		newStyle = filterStyles(getElementStyles.call(this));
-		that.attr('className', className);
-
-		that.animate(styleDifference(originalStyle, newStyle), duration, easing, function() {
-			$.each(classAnimationActions, function(i, action) {
-				if (value[action]) { that[action + 'Class'](value[action]); }
-			});
-			// work around bug in IE by clearing the cssText before setting it
-			if (typeof that.attr('style') == 'object') {
-				that.attr('style').cssText = '';
-				that.attr('style').cssText = originalStyleAttr;
-			} else {
-				that.attr('style', originalStyleAttr);
-			}
-			if (callback) { callback.apply(this, arguments); }
-		});
-
-		// $.animate adds a function to the end of the queue
-		// but we want it at the front
-		var queue = $.queue(this),
-			anim = queue.splice(queue.length - 1, 1)[0];
-		queue.splice(1, 0, anim);
-		$.dequeue(this);
-	});
-};
-
-$.fn.extend({
-	_addClass: $.fn.addClass,
-	addClass: function(classNames, speed, easing, callback) {
-		return speed ? $.effects.animateClass.apply(this, [{ add: classNames },speed,easing,callback]) : this._addClass(classNames);
-	},
-
-	_removeClass: $.fn.removeClass,
-	removeClass: function(classNames,speed,easing,callback) {
-		return speed ? $.effects.animateClass.apply(this, [{ remove: classNames },speed,easing,callback]) : this._removeClass(classNames);
-	},
-
-	_toggleClass: $.fn.toggleClass,
-	toggleClass: function(classNames, force, speed, easing, callback) {
-		if ( typeof force == "boolean" || force === undefined ) {
-			if ( !speed ) {
-				// without speed parameter;
-				return this._toggleClass(classNames, force);
-			} else {
-				return $.effects.animateClass.apply(this, [(force?{add:classNames}:{remove:classNames}),speed,easing,callback]);
-			}
-		} else {
-			// without switch parameter;
-			return $.effects.animateClass.apply(this, [{ toggle: classNames },force,speed,easing]);
-		}
-	},
-
-	switchClass: function(remove,add,speed,easing,callback) {
-		return $.effects.animateClass.apply(this, [{ add: add, remove: remove },speed,easing,callback]);
-	}
-});
-
-
-
-/******************************************************************************/
-/*********************************** EFFECTS **********************************/
-/******************************************************************************/
-
-$.extend($.effects, {
-	version: "@VERSION",
-
-	// Saves a set of properties in a data storage
-	save: function(element, set) {
-		for(var i=0; i < set.length; i++) {
-			if(set[i] !== null) element.data("ec.storage."+set[i], element[0].style[set[i]]);
-		}
-	},
-
-	// Restores a set of previously saved properties from a data storage
-	restore: function(element, set) {
-		for(var i=0; i < set.length; i++) {
-			if(set[i] !== null) element.css(set[i], element.data("ec.storage."+set[i]));
-		}
-	},
-
-	setMode: function(el, mode) {
-		if (mode == 'toggle') mode = el.is(':hidden') ? 'show' : 'hide'; // Set for toggle
-		return mode;
-	},
-
-	getBaseline: function(origin, original) { // Translates a [top,left] array into a baseline value
-		// this should be a little more flexible in the future to handle a string & hash
-		var y, x;
-		switch (origin[0]) {
-			case 'top': y = 0; break;
-			case 'middle': y = 0.5; break;
-			case 'bottom': y = 1; break;
-			default: y = origin[0] / original.height;
-		};
-		switch (origin[1]) {
-			case 'left': x = 0; break;
-			case 'center': x = 0.5; break;
-			case 'right': x = 1; break;
-			default: x = origin[1] / original.width;
-		};
-		return {x: x, y: y};
-	},
-
-	// Wraps the element around a wrapper that copies position properties
-	createWrapper: function(element) {
-
-		// if the element is already wrapped, return it
-		if (element.parent().is('.ui-effects-wrapper')) {
-			return element.parent();
-		}
-
-		// wrap the element
-		var props = {
-				width: element.outerWidth(true),
-				height: element.outerHeight(true),
-				'float': element.css('float')
-			},
-			wrapper = $('<div></div>')
-				.addClass('ui-effects-wrapper')
-				.css({
-					fontSize: '100%',
-					background: 'transparent',
-					border: 'none',
-					margin: 0,
-					padding: 0
-				});
-
-		element.wrap(wrapper);
-		wrapper = element.parent(); //Hotfix for jQuery 1.4 since some change in wrap() seems to actually loose the reference to the wrapped element
-
-		// transfer positioning properties to the wrapper
-		if (element.css('position') == 'static') {
-			wrapper.css({ position: 'relative' });
-			element.css({ position: 'relative' });
-		} else {
-			$.extend(props, {
-				position: element.css('position'),
-				zIndex: element.css('z-index')
-			});
-			$.each(['top', 'left', 'bottom', 'right'], function(i, pos) {
-				props[pos] = element.css(pos);
-				if (isNaN(parseInt(props[pos], 10))) {
-					props[pos] = 'auto';
-				}
-			});
-			element.css({position: 'relative', top: 0, left: 0, right: 'auto', bottom: 'auto' });
-		}
-
-		return wrapper.css(props).show();
-	},
-
-	removeWrapper: function(element) {
-		if (element.parent().is('.ui-effects-wrapper'))
-			return element.parent().replaceWith(element);
-		return element;
-	},
-
-	setTransition: function(element, list, factor, value) {
-		value = value || {};
-		$.each(list, function(i, x){
-			unit = element.cssUnit(x);
-			if (unit[0] > 0) value[x] = unit[0] * factor + unit[1];
-		});
-		return value;
-	}
-});
-
-
-function _normalizeArguments(effect, options, speed, callback) {
-	// shift params for method overloading
-	if (typeof effect == 'object') {
-		callback = options;
-		speed = null;
-		options = effect;
-		effect = options.effect;
-	}
-	if ($.isFunction(options)) {
-		callback = options;
-		speed = null;
-		options = {};
-	}
-        if (typeof options == 'number' || $.fx.speeds[options]) {
-		callback = speed;
-		speed = options;
-		options = {};
-	}
-	if ($.isFunction(speed)) {
-		callback = speed;
-		speed = null;
-	}
-
-	options = options || {};
-
-	speed = speed || options.duration;
-	speed = $.fx.off ? 0 : typeof speed == 'number'
-		? speed : speed in $.fx.speeds ? $.fx.speeds[speed] : $.fx.speeds._default;
-
-	callback = callback || options.complete;
-
-	return [effect, options, speed, callback];
-}
-
-function standardSpeed( speed ) {
-	// valid standard speeds
-	if ( !speed || typeof speed === "number" || $.fx.speeds[ speed ] ) {
-		return true;
-	}
-	
-	// invalid strings - treat as "normal" speed
-	if ( typeof speed === "string" && !$.effects[ speed ] ) {
-		return true;
-	}
-	
-	return false;
-}
-
-$.fn.extend({
-	effect: function(effect, options, speed, callback) {
-		var args = _normalizeArguments.apply(this, arguments),
-			// TODO: make effects take actual parameters instead of a hash
-			args2 = {
-				options: args[1],
-				duration: args[2],
-				callback: args[3]
-			},
-			mode = args2.options.mode,
-			effectMethod = $.effects[effect];
-		
-		if ( $.fx.off || !effectMethod ) {
-			// delegate to the original method (e.g., .show()) if possible
-			if ( mode ) {
-				return this[ mode ]( args2.duration, args2.callback );
-			} else {
-				return this.each(function() {
-					if ( args2.callback ) {
-						args2.callback.call( this );
-					}
-				});
-			}
-		}
-		
-		return effectMethod.call(this, args2);
-	},
-
-	_show: $.fn.show,
-	show: function(speed) {
-		if ( standardSpeed( speed ) ) {
-			return this._show.apply(this, arguments);
-		} else {
-			var args = _normalizeArguments.apply(this, arguments);
-			args[1].mode = 'show';
-			return this.effect.apply(this, args);
-		}
-	},
-
-	_hide: $.fn.hide,
-	hide: function(speed) {
-		if ( standardSpeed( speed ) ) {
-			return this._hide.apply(this, arguments);
-		} else {
-			var args = _normalizeArguments.apply(this, arguments);
-			args[1].mode = 'hide';
-			return this.effect.apply(this, args);
-		}
-	},
-
-	// jQuery core overloads toggle and creates _toggle
-	__toggle: $.fn.toggle,
-	toggle: function(speed) {
-		if ( standardSpeed( speed ) || typeof speed === "boolean" || $.isFunction( speed ) ) {
-			return this.__toggle.apply(this, arguments);
-		} else {
-			var args = _normalizeArguments.apply(this, arguments);
-			args[1].mode = 'toggle';
-			return this.effect.apply(this, args);
-		}
-	},
-
-	// helper functions
-	cssUnit: function(key) {
-		var style = this.css(key), val = [];
-		$.each( ['em','px','%','pt'], function(i, unit){
-			if(style.indexOf(unit) > 0)
-				val = [parseFloat(style), unit];
-		});
-		return val;
-	}
-});
-
-
-
-/******************************************************************************/
-/*********************************** EASING ***********************************/
-/******************************************************************************/
-
-/*
- * jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/
- *
- * Uses the built in easing capabilities added In jQuery 1.1
- * to offer multiple easing options
- *
- * TERMS OF USE - jQuery Easing
- *
- * Open source under the BSD License.
- *
- * Copyright 2008 George McGinley Smith
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without modification,
- * are permitted provided that the following conditions are met:
- *
- * Redistributions of source code must retain the above copyright notice, this list of
- * conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright notice, this list
- * of conditions and the following disclaimer in the documentation and/or other materials
- * provided with the distribution.
- *
- * Neither the name of the author nor the names of contributors may be used to endorse
- * or promote products derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
- * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
- * OF THE POSSIBILITY OF SUCH DAMAGE.
- *
-*/
-
-// t: current time, b: begInnIng value, c: change In value, d: duration
-$.easing.jswing = $.easing.swing;
-
-$.extend($.easing,
-{
-	def: 'easeOutQuad',
-	swing: function (x, t, b, c, d) {
-		//alert($.easing.default);
-		return $.easing[$.easing.def](x, t, b, c, d);
-	},
-	easeInQuad: function (x, t, b, c, d) {
-		return c*(t/=d)*t + b;
-	},
-	easeOutQuad: function (x, t, b, c, d) {
-		return -c *(t/=d)*(t-2) + b;
-	},
-	easeInOutQuad: function (x, t, b, c, d) {
-		if ((t/=d/2) < 1) return c/2*t*t + b;
-		return -c/2 * ((--t)*(t-2) - 1) + b;
-	},
-	easeInCubic: function (x, t, b, c, d) {
-		return c*(t/=d)*t*t + b;
-	},
-	easeOutCubic: function (x, t, b, c, d) {
-		return c*((t=t/d-1)*t*t + 1) + b;
-	},
-	easeInOutCubic: function (x, t, b, c, d) {
-		if ((t/=d/2) < 1) return c/2*t*t*t + b;
-		return c/2*((t-=2)*t*t + 2) + b;
-	},
-	easeInQuart: function (x, t, b, c, d) {
-		return c*(t/=d)*t*t*t + b;
-	},
-	easeOutQuart: function (x, t, b, c, d) {
-		return -c * ((t=t/d-1)*t*t*t - 1) + b;
-	},
-	easeInOutQuart: function (x, t, b, c, d) {
-		if ((t/=d/2) < 1) return c/2*t*t*t*t + b;
-		return -c/2 * ((t-=2)*t*t*t - 2) + b;
-	},
-	easeInQuint: function (x, t, b, c, d) {
-		return c*(t/=d)*t*t*t*t + b;
-	},
-	easeOutQuint: function (x, t, b, c, d) {
-		return c*((t=t/d-1)*t*t*t*t + 1) + b;
-	},
-	easeInOutQuint: function (x, t, b, c, d) {
-		if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b;
-		return c/2*((t-=2)*t*t*t*t + 2) + b;
-	},
-	easeInSine: function (x, t, b, c, d) {
-		return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
-	},
-	easeOutSine: function (x, t, b, c, d) {
-		return c * Math.sin(t/d * (Math.PI/2)) + b;
-	},
-	easeInOutSine: function (x, t, b, c, d) {
-		return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
-	},
-	easeInExpo: function (x, t, b, c, d) {
-		return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
-	},
-	easeOutExpo: function (x, t, b, c, d) {
-		return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
-	},
-	easeInOutExpo: function (x, t, b, c, d) {
-		if (t==0) return b;
-		if (t==d) return b+c;
-		if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
-		return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
-	},
-	easeInCirc: function (x, t, b, c, d) {
-		return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
-	},
-	easeOutCirc: function (x, t, b, c, d) {
-		return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
-	},
-	easeInOutCirc: function (x, t, b, c, d) {
-		if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b;
-		return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b;
-	},
-	easeInElastic: function (x, t, b, c, d) {
-		var s=1.70158;var p=0;var a=c;
-		if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
-		if (a < Math.abs(c)) { a=c; var s=p/4; }
-		else var s = p/(2*Math.PI) * Math.asin (c/a);
-		return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
-	},
-	easeOutElastic: function (x, t, b, c, d) {
-		var s=1.70158;var p=0;var a=c;
-		if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
-		if (a < Math.abs(c)) { a=c; var s=p/4; }
-		else var s = p/(2*Math.PI) * Math.asin (c/a);
-		return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b;
-	},
-	easeInOutElastic: function (x, t, b, c, d) {
-		var s=1.70158;var p=0;var a=c;
-		if (t==0) return b;  if ((t/=d/2)==2) return b+c;  if (!p) p=d*(.3*1.5);
-		if (a < Math.abs(c)) { a=c; var s=p/4; }
-		else var s = p/(2*Math.PI) * Math.asin (c/a);
-		if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
-		return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b;
-	},
-	easeInBack: function (x, t, b, c, d, s) {
-		if (s == undefined) s = 1.70158;
-		return c*(t/=d)*t*((s+1)*t - s) + b;
-	},
-	easeOutBack: function (x, t, b, c, d, s) {
-		if (s == undefined) s = 1.70158;
-		return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
-	},
-	easeInOutBack: function (x, t, b, c, d, s) {
-		if (s == undefined) s = 1.70158;
-		if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
-		return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
-	},
-	easeInBounce: function (x, t, b, c, d) {
-		return c - $.easing.easeOutBounce (x, d-t, 0, c, d) + b;
-	},
-	easeOutBounce: function (x, t, b, c, d) {
-		if ((t/=d) < (1/2.75)) {
-			return c*(7.5625*t*t) + b;
-		} else if (t < (2/2.75)) {
-			return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;
-		} else if (t < (2.5/2.75)) {
-			return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
-		} else {
-			return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
-		}
-	},
-	easeInOutBounce: function (x, t, b, c, d) {
-		if (t < d/2) return $.easing.easeInBounce (x, t*2, 0, c, d) * .5 + b;
-		return $.easing.easeOutBounce (x, t*2-d, 0, c, d) * .5 + c*.5 + b;
-	}
-});
-
-/*
- *
- * TERMS OF USE - EASING EQUATIONS
- *
- * Open source under the BSD License.
- *
- * Copyright 2001 Robert Penner
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without modification,
- * are permitted provided that the following conditions are met:
- *
- * Redistributions of source code must retain the above copyright notice, this list of
- * conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright notice, this list
- * of conditions and the following disclaimer in the documentation and/or other materials
- * provided with the distribution.
- *
- * Neither the name of the author nor the names of contributors may be used to endorse
- * or promote products derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
- * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
- * OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- */
-
-})(jQuery);
-

--- a/js/jquery.ui.autocomplete.js
+++ /dev/null
@@ -1,426 +1,1 @@
-/*
- * jQuery UI Autocomplete @VERSION
- *
- * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT or GPL Version 2 licenses.
- * http://jquery.org/license
- *
- * http://docs.jquery.com/UI/Autocomplete
- *
- * Depends:
- *	jquery.ui.core.js
- *	jquery.ui.widget.js
- *	jquery.ui.position.js
- *	jquery.ui.menu.js
- */
-(function( $, undefined ) {
 
-// used to prevent race conditions with remote data sources
-var requestIndex = 0;
-
-$.widget( "ui.autocomplete", {
-	defaultElement: "<input>",
-	options: {
-		appendTo: "body",
-		delay: 300,
-		minLength: 1,
-		position: {
-			my: "left top",
-			at: "left bottom",
-			collision: "none"
-		},
-		source: null
-	},
-
-	pending: 0,
-
-	_create: function() {
-		var self = this,
-			doc = this.element[ 0 ].ownerDocument,
-			suppressKeyPress;
-
-		this.element
-			.addClass( "ui-autocomplete-input" )
-			.attr( "autocomplete", "off" )
-			// TODO verify these actually work as intended
-			.attr({
-				role: "textbox",
-				"aria-autocomplete": "list",
-				"aria-haspopup": "true"
-			})
-			.bind( "keydown.autocomplete", function( event ) {
-				if ( self.options.disabled || self.element.attr( "readonly" ) ) {
-					return;
-				}
-
-				suppressKeyPress = false;
-				var keyCode = $.ui.keyCode;
-				switch( event.keyCode ) {
-				case keyCode.PAGE_UP:
-					self._move( "previousPage", event );
-					break;
-				case keyCode.PAGE_DOWN:
-					self._move( "nextPage", event );
-					break;
-				case keyCode.UP:
-					self._move( "previous", event );
-					// prevent moving cursor to beginning of text field in some browsers
-					event.preventDefault();
-					break;
-				case keyCode.DOWN:
-					self._move( "next", event );
-					// prevent moving cursor to end of text field in some browsers
-					event.preventDefault();
-					break;
-				case keyCode.ENTER:
-				case keyCode.NUMPAD_ENTER:
-					// when menu is open and has focus
-					if ( self.menu.active ) {
-						// #6055 - Opera still allows the keypress to occur
-						// which causes forms to submit
-						suppressKeyPress = true;
-						event.preventDefault();
-					}
-					//passthrough - ENTER and TAB both select the current element
-				case keyCode.TAB:
-					if ( !self.menu.active ) {
-						return;
-					}
-					self.menu.select( event );
-					break;
-				case keyCode.ESCAPE:
-					self.element.val( self.term );
-					self.close( event );
-					break;
-				default:
-					// keypress is triggered before the input value is changed
-					clearTimeout( self.searching );
-					self.searching = setTimeout(function() {
-						// only search if the value has changed
-						if ( self.term != self.element.val() ) {
-							self.selectedItem = null;
-							self.search( null, event );
-						}
-					}, self.options.delay );
-					break;
-				}
-			})
-			.bind( "keypress.autocomplete", function( event ) {
-				if ( suppressKeyPress ) {
-					suppressKeyPress = false;
-					event.preventDefault();
-				}
-			})
-			.bind( "focus.autocomplete", function() {
-				if ( self.options.disabled ) {
-					return;
-				}
-
-				self.selectedItem = null;
-				self.previous = self.element.val();
-			})
-			.bind( "blur.autocomplete", function( event ) {
-				if ( self.options.disabled ) {
-					return;
-				}
-
-				clearTimeout( self.searching );
-				// clicks on the menu (or a button to trigger a search) will cause a blur event
-				self.closing = setTimeout(function() {
-					self.close( event );
-					self._change( event );
-				}, 150 );
-			});
-		this._initSource();
-		this.response = function() {
-			return self._response.apply( self, arguments );
-		};
-		this.menu = $( "<ul></ul>" )
-			.addClass( "ui-autocomplete" )
-			.appendTo( $( this.options.appendTo || "body", doc )[0] )
-			// prevent the close-on-blur in case of a "slow" click on the menu (long mousedown)
-			.mousedown(function( event ) {
-				// clicking on the scrollbar causes focus to shift to the body
-				// but we can't detect a mouseup or a click immediately afterward
-				// so we have to track the next mousedown and close the menu if
-				// the user clicks somewhere outside of the autocomplete
-				var menuElement = self.menu.element[ 0 ];
-				if ( !$( event.target ).closest( ".ui-menu-item" ).length ) {
-					setTimeout(function() {
-						$( document ).one( 'mousedown', function( event ) {
-							if ( event.target !== self.element[ 0 ] &&
-								event.target !== menuElement &&
-								!$.contains( menuElement, event.target ) ) {
-								self.close();
-							}
-						});
-					}, 1 );
-				}
-
-				// use another timeout to make sure the blur-event-handler on the input was already triggered
-				setTimeout(function() {
-					clearTimeout( self.closing );
-				}, 13);
-			})
-			.menu({
-				// custom key handling for now
-				input: $(),
-				focus: function( event, ui ) {
-					var item = ui.item.data( "item.autocomplete" );
-					if ( false !== self._trigger( "focus", event, { item: item } ) ) {
-						// use value to match what will end up in the input, if it was a key event
-						if ( /^key/.test(event.originalEvent.type) ) {
-							self.element.val( item.value );
-						}
-					}
-				},
-				select: function( event, ui ) {
-					var item = ui.item.data( "item.autocomplete" ),
-						previous = self.previous;
-
-					// only trigger when focus was lost (click on menu)
-					if ( self.element[0] !== doc.activeElement ) {
-						self.element.focus();
-						self.previous = previous;
-						// #6109 - IE triggers two focus events and the second
-						// is asynchronous, so we need to reset the previous
-						// term synchronously and asynchronously :-(
-						setTimeout(function() {
-							self.previous = previous;
-							self.selectedItem = item;
-						}, 1);
-					}
-
-					if ( false !== self._trigger( "select", event, { item: item } ) ) {
-						self.element.val( item.value );
-					}
-					// reset the term after the select event
-					// this allows custom select handling to work properly
-					self.term = self.element.val();
-
-					self.close( event );
-					self.selectedItem = item;
-				},
-				blur: function( event, ui ) {
-					// don't set the value of the text field if it's already correct
-					// this prevents moving the cursor unnecessarily
-					if ( self.menu.element.is(":visible") &&
-						( self.element.val() !== self.term ) ) {
-						self.element.val( self.term );
-					}
-				}
-			})
-			.zIndex( this.element.zIndex() + 1 )
-			.hide()
-			.data( "menu" );
-		if ( $.fn.bgiframe ) {
-			 this.menu.element.bgiframe();
-		}
-	},
-
-	_destroy: function() {
-		this.element
-			.removeClass( "ui-autocomplete-input" )
-			.removeAttr( "autocomplete" )
-			.removeAttr( "role" )
-			.removeAttr( "aria-autocomplete" )
-			.removeAttr( "aria-haspopup" );
-		this.menu.element.remove();
-	},
-
-	_setOption: function( key, value ) {
-		this._super( "_setOption", key, value );
-		if ( key === "source" ) {
-			this._initSource();
-		}
-		if ( key === "appendTo" ) {
-			this.menu.element.appendTo( $( value || "body", this.element[0].ownerDocument )[0] )
-		}
-		if ( key === "disabled" && value && this.xhr ) {
-			this.xhr.abort();
-		}
-	},
-
-	_initSource: function() {
-		var self = this,
-			array,
-			url;
-		if ( $.isArray(this.options.source) ) {
-			array = this.options.source;
-			this.source = function( request, response ) {
-				response( $.ui.autocomplete.filter(array, request.term) );
-			};
-		} else if ( typeof this.options.source === "string" ) {
-			url = this.options.source;
-			this.source = function( request, response ) {
-				if ( self.xhr ) {
-					self.xhr.abort();
-				}
-				self.xhr = $.ajax({
-					url: url,
-					data: request,
-					dataType: "json",
-					autocompleteRequest: ++requestIndex,
-					success: function( data, status ) {
-						if ( this.autocompleteRequest === requestIndex ) {
-							response( data );
-						}
-					},
-					error: function() {
-						if ( this.autocompleteRequest === requestIndex ) {
-							response( [] );
-						}
-					}
-				});
-			};
-		} else {
-			this.source = this.options.source;
-		}
-	},
-
-	search: function( value, event ) {
-		value = value != null ? value : this.element.val();
-
-		// always save the actual value, not the one passed as an argument
-		this.term = this.element.val();
-
-		if ( value.length < this.options.minLength ) {
-			return this.close( event );
-		}
-
-		clearTimeout( this.closing );
-		if ( this._trigger( "search", event ) === false ) {
-			return;
-		}
-
-		return this._search( value );
-	},
-
-	_search: function( value ) {
-		this.pending++;
-		this.element.addClass( "ui-autocomplete-loading" );
-
-		this.source( { term: value }, this.response );
-	},
-
-	_response: function( content ) {
-		if ( !this.options.disabled && content && content.length ) {
-			content = this._normalize( content );
-			this._suggest( content );
-			this._trigger( "open" );
-		} else {
-			this.close();
-		}
-		this.pending--;
-		if ( !this.pending ) {
-			this.element.removeClass( "ui-autocomplete-loading" );
-		}
-	},
-
-	close: function( event ) {
-		clearTimeout( this.closing );
-		if ( this.menu.element.is(":visible") ) {
-			this.menu.element.hide();
-			this.menu.deactivate();
-			this._trigger( "close", event );
-		}
-	},
-	
-	_change: function( event ) {
-		if ( this.previous !== this.element.val() ) {
-			this._trigger( "change", event, { item: this.selectedItem } );
-		}
-	},
-
-	_normalize: function( items ) {
-		// assume all items have the right format when the first item is complete
-		if ( items.length && items[0].label && items[0].value ) {
-			return items;
-		}
-		return $.map( items, function(item) {
-			if ( typeof item === "string" ) {
-				return {
-					label: item,
-					value: item
-				};
-			}
-			return $.extend({
-				label: item.label || item.value,
-				value: item.value || item.label
-			}, item );
-		});
-	},
-
-	_suggest: function( items ) {
-		var ul = this.menu.element
-			.empty()
-			.zIndex( this.element.zIndex() + 1 );
-		this._renderMenu( ul, items );
-		// TODO refresh should check if the active item is still in the dom, removing the need for a manual deactivate
-		this.menu.deactivate();
-		this.menu.refresh();
-
-		// size and position menu
-		ul.show();
-		this._resizeMenu();
-		ul.position( $.extend({
-			of: this.element
-		}, this.options.position ));
-	},
-
-	_resizeMenu: function() {
-		var ul = this.menu.element;
-		ul.outerWidth( Math.max(
-			ul.width( "" ).outerWidth(),
-			this.element.outerWidth()
-		) );
-	},
-
-	_renderMenu: function( ul, items ) {
-		var self = this;
-		$.each( items, function( index, item ) {
-			self._renderItem( ul, item );
-		});
-	},
-
-	_renderItem: function( ul, item) {
-		return $( "<li></li>" )
-			.data( "item.autocomplete", item )
-			.append( $( "<a></a>" ).text( item.label ) )
-			.appendTo( ul );
-	},
-
-	_move: function( direction, event ) {
-		if ( !this.menu.element.is(":visible") ) {
-			this.search( null, event );
-			return;
-		}
-		if ( this.menu.first() && /^previous/.test(direction) ||
-				this.menu.last() && /^next/.test(direction) ) {
-			this.element.val( this.term );
-			this.menu.deactivate();
-			return;
-		}
-		this.menu[ direction ]( event );
-	},
-
-	widget: function() {
-		return this.menu.element;
-	}
-});
-
-$.extend( $.ui.autocomplete, {
-	version: "@VERSION",
-	escapeRegex: function( value ) {
-		return value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
-	},
-	filter: function(array, term) {
-		var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );
-		return $.grep( array, function(value) {
-			return matcher.test( value.label || value.value || value );
-		});
-	}
-});
-
-}( jQuery ));
-

--- /dev/null
+++ b/js/jquery.ui.autocomplete.min.js
@@ -1,1 +1,33 @@
+/*
+ * jQuery UI Autocomplete 1.8.12
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Autocomplete
+ *
+ * Depends:
+ *	jquery.ui.core.js
+ *	jquery.ui.widget.js
+ *	jquery.ui.position.js
+ */
+(function(d){var e=0;d.widget("ui.autocomplete",{options:{appendTo:"body",autoFocus:false,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null},pending:0,_create:function(){var a=this,b=this.element[0].ownerDocument,g;this.element.addClass("ui-autocomplete-input").attr("autocomplete","off").attr({role:"textbox","aria-autocomplete":"list","aria-haspopup":"true"}).bind("keydown.autocomplete",function(c){if(!(a.options.disabled||a.element.attr("readonly"))){g=
+false;var f=d.ui.keyCode;switch(c.keyCode){case f.PAGE_UP:a._move("previousPage",c);break;case f.PAGE_DOWN:a._move("nextPage",c);break;case f.UP:a._move("previous",c);c.preventDefault();break;case f.DOWN:a._move("next",c);c.preventDefault();break;case f.ENTER:case f.NUMPAD_ENTER:if(a.menu.active){g=true;c.preventDefault()}case f.TAB:if(!a.menu.active)return;a.menu.select(c);break;case f.ESCAPE:a.element.val(a.term);a.close(c);break;default:clearTimeout(a.searching);a.searching=setTimeout(function(){if(a.term!=
+a.element.val()){a.selectedItem=null;a.search(null,c)}},a.options.delay);break}}}).bind("keypress.autocomplete",function(c){if(g){g=false;c.preventDefault()}}).bind("focus.autocomplete",function(){if(!a.options.disabled){a.selectedItem=null;a.previous=a.element.val()}}).bind("blur.autocomplete",function(c){if(!a.options.disabled){clearTimeout(a.searching);a.closing=setTimeout(function(){a.close(c);a._change(c)},150)}});this._initSource();this.response=function(){return a._response.apply(a,arguments)};
+this.menu=d("<ul></ul>").addClass("ui-autocomplete").appendTo(d(this.options.appendTo||"body",b)[0]).mousedown(function(c){var f=a.menu.element[0];d(c.target).closest(".ui-menu-item").length||setTimeout(function(){d(document).one("mousedown",function(h){h.target!==a.element[0]&&h.target!==f&&!d.ui.contains(f,h.target)&&a.close()})},1);setTimeout(function(){clearTimeout(a.closing)},13)}).menu({focus:function(c,f){f=f.item.data("item.autocomplete");false!==a._trigger("focus",c,{item:f})&&/^key/.test(c.originalEvent.type)&&
+a.element.val(f.value)},selected:function(c,f){var h=f.item.data("item.autocomplete"),i=a.previous;if(a.element[0]!==b.activeElement){a.element.focus();a.previous=i;setTimeout(function(){a.previous=i;a.selectedItem=h},1)}false!==a._trigger("select",c,{item:h})&&a.element.val(h.value);a.term=a.element.val();a.close(c);a.selectedItem=h},blur:function(){a.menu.element.is(":visible")&&a.element.val()!==a.term&&a.element.val(a.term)}}).zIndex(this.element.zIndex()+1).css({top:0,left:0}).hide().data("menu");
+d.fn.bgiframe&&this.menu.element.bgiframe()},destroy:function(){this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete").removeAttr("role").removeAttr("aria-autocomplete").removeAttr("aria-haspopup");this.menu.element.remove();d.Widget.prototype.destroy.call(this)},_setOption:function(a,b){d.Widget.prototype._setOption.apply(this,arguments);a==="source"&&this._initSource();if(a==="appendTo")this.menu.element.appendTo(d(b||"body",this.element[0].ownerDocument)[0]);a==="disabled"&&
+b&&this.xhr&&this.xhr.abort()},_initSource:function(){var a=this,b,g;if(d.isArray(this.options.source)){b=this.options.source;this.source=function(c,f){f(d.ui.autocomplete.filter(b,c.term))}}else if(typeof this.options.source==="string"){g=this.options.source;this.source=function(c,f){a.xhr&&a.xhr.abort();a.xhr=d.ajax({url:g,data:c,dataType:"json",autocompleteRequest:++e,success:function(h){this.autocompleteRequest===e&&f(h)},error:function(){this.autocompleteRequest===e&&f([])}})}}else this.source=
+this.options.source},search:function(a,b){a=a!=null?a:this.element.val();this.term=this.element.val();if(a.length<this.options.minLength)return this.close(b);clearTimeout(this.closing);if(this._trigger("search",b)!==false)return this._search(a)},_search:function(a){this.pending++;this.element.addClass("ui-autocomplete-loading");this.source({term:a},this.response)},_response:function(a){if(!this.options.disabled&&a&&a.length){a=this._normalize(a);this._suggest(a);this._trigger("open")}else this.close();
+this.pending--;this.pending||this.element.removeClass("ui-autocomplete-loading")},close:function(a){clearTimeout(this.closing);if(this.menu.element.is(":visible")){this.menu.element.hide();this.menu.deactivate();this._trigger("close",a)}},_change:function(a){this.previous!==this.element.val()&&this._trigger("change",a,{item:this.selectedItem})},_normalize:function(a){if(a.length&&a[0].label&&a[0].value)return a;return d.map(a,function(b){if(typeof b==="string")return{label:b,value:b};return d.extend({label:b.label||
+b.value,value:b.value||b.label},b)})},_suggest:function(a){var b=this.menu.element.empty().zIndex(this.element.zIndex()+1);this._renderMenu(b,a);this.menu.deactivate();this.menu.refresh();b.show();this._resizeMenu();b.position(d.extend({of:this.element},this.options.position));this.options.autoFocus&&this.menu.next(new d.Event("mouseover"))},_resizeMenu:function(){var a=this.menu.element;a.outerWidth(Math.max(a.width("").outerWidth(),this.element.outerWidth()))},_renderMenu:function(a,b){var g=this;
+d.each(b,function(c,f){g._renderItem(a,f)})},_renderItem:function(a,b){return d("<li></li>").data("item.autocomplete",b).append(d("<a></a>").text(b.label)).appendTo(a)},_move:function(a,b){if(this.menu.element.is(":visible"))if(this.menu.first()&&/^previous/.test(a)||this.menu.last()&&/^next/.test(a)){this.element.val(this.term);this.menu.deactivate()}else this.menu[a](b);else this.search(null,b)},widget:function(){return this.menu.element}});d.extend(d.ui.autocomplete,{escapeRegex:function(a){return a.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,
+"\\$&")},filter:function(a,b){var g=new RegExp(d.ui.autocomplete.escapeRegex(b),"i");return d.grep(a,function(c){return g.test(c.label||c.value||c)})}})})(jQuery);
+(function(d){d.widget("ui.menu",{_create:function(){var e=this;this.element.addClass("ui-menu ui-widget ui-widget-content ui-corner-all").attr({role:"listbox","aria-activedescendant":"ui-active-menuitem"}).click(function(a){if(d(a.target).closest(".ui-menu-item a").length){a.preventDefault();e.select(a)}});this.refresh()},refresh:function(){var e=this;this.element.children("li:not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","menuitem").children("a").addClass("ui-corner-all").attr("tabindex",
+-1).mouseenter(function(a){e.activate(a,d(this).parent())}).mouseleave(function(){e.deactivate()})},activate:function(e,a){this.deactivate();if(this.hasScroll()){var b=a.offset().top-this.element.offset().top,g=this.element.attr("scrollTop"),c=this.element.height();if(b<0)this.element.attr("scrollTop",g+b);else b>=c&&this.element.attr("scrollTop",g+b-c+a.height())}this.active=a.eq(0).children("a").addClass("ui-state-hover").attr("id","ui-active-menuitem").end();this._trigger("focus",e,{item:a})},
+deactivate:function(){if(this.active){this.active.children("a").removeClass("ui-state-hover").removeAttr("id");this._trigger("blur");this.active=null}},next:function(e){this.move("next",".ui-menu-item:first",e)},previous:function(e){this.move("prev",".ui-menu-item:last",e)},first:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},last:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},move:function(e,a,b){if(this.active){e=this.active[e+"All"](".ui-menu-item").eq(0);
+e.length?this.activate(b,e):this.activate(b,this.element.children(a))}else this.activate(b,this.element.children(a))},nextPage:function(e){if(this.hasScroll())if(!this.active||this.last())this.activate(e,this.element.children(".ui-menu-item:first"));else{var a=this.active.offset().top,b=this.element.height(),g=this.element.children(".ui-menu-item").filter(function(){var c=d(this).offset().top-a-b+d(this).height();return c<10&&c>-10});g.length||(g=this.element.children(".ui-menu-item:last"));this.activate(e,
+g)}else this.activate(e,this.element.children(".ui-menu-item").filter(!this.active||this.last()?":first":":last"))},previousPage:function(e){if(this.hasScroll())if(!this.active||this.first())this.activate(e,this.element.children(".ui-menu-item:last"));else{var a=this.active.offset().top,b=this.element.height();result=this.element.children(".ui-menu-item").filter(function(){var g=d(this).offset().top-a+b-d(this).height();return g<10&&g>-10});result.length||(result=this.element.children(".ui-menu-item:first"));
+this.activate(e,result)}else this.activate(e,this.element.children(".ui-menu-item").filter(!this.active||this.first()?":last":":first"))},hasScroll:function(){return this.element.height()<this.element.attr("scrollHeight")},select:function(e){this._trigger("selected",e,{item:this.active})}})})(jQuery);
 

--- /dev/null
+++ b/js/jquery.ui.core.min.js
@@ -1,1 +1,18 @@
+/*!
+ * jQuery UI 1.8.12
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI
+ */
+(function(c,j){function k(a){return!c(a).parents().andSelf().filter(function(){return c.curCSS(this,"visibility")==="hidden"||c.expr.filters.hidden(this)}).length}c.ui=c.ui||{};if(!c.ui.version){c.extend(c.ui,{version:"1.8.12",keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,
+NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});c.fn.extend({_focus:c.fn.focus,focus:function(a,b){return typeof a==="number"?this.each(function(){var d=this;setTimeout(function(){c(d).focus();b&&b.call(d)},a)}):this._focus.apply(this,arguments)},scrollParent:function(){var a;a=c.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(c.curCSS(this,
+"position",1))&&/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0);return/fixed/.test(this.css("position"))||!a.length?c(document):a},zIndex:function(a){if(a!==j)return this.css("zIndex",a);if(this.length){a=c(this[0]);for(var b;a.length&&a[0]!==document;){b=a.css("position");
+if(b==="absolute"||b==="relative"||b==="fixed"){b=parseInt(a.css("zIndex"),10);if(!isNaN(b)&&b!==0)return b}a=a.parent()}}return 0},disableSelection:function(){return this.bind((c.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});c.each(["Width","Height"],function(a,b){function d(f,g,l,m){c.each(e,function(){g-=parseFloat(c.curCSS(f,"padding"+this,true))||0;if(l)g-=parseFloat(c.curCSS(f,
+"border"+this+"Width",true))||0;if(m)g-=parseFloat(c.curCSS(f,"margin"+this,true))||0});return g}var e=b==="Width"?["Left","Right"]:["Top","Bottom"],h=b.toLowerCase(),i={innerWidth:c.fn.innerWidth,innerHeight:c.fn.innerHeight,outerWidth:c.fn.outerWidth,outerHeight:c.fn.outerHeight};c.fn["inner"+b]=function(f){if(f===j)return i["inner"+b].call(this);return this.each(function(){c(this).css(h,d(this,f)+"px")})};c.fn["outer"+b]=function(f,g){if(typeof f!=="number")return i["outer"+b].call(this,f);return this.each(function(){c(this).css(h,
+d(this,f,true,g)+"px")})}});c.extend(c.expr[":"],{data:function(a,b,d){return!!c.data(a,d[3])},focusable:function(a){var b=a.nodeName.toLowerCase(),d=c.attr(a,"tabindex");if("area"===b){b=a.parentNode;d=b.name;if(!a.href||!d||b.nodeName.toLowerCase()!=="map")return false;a=c("img[usemap=#"+d+"]")[0];return!!a&&k(a)}return(/input|select|textarea|button|object/.test(b)?!a.disabled:"a"==b?a.href||!isNaN(d):!isNaN(d))&&k(a)},tabbable:function(a){var b=c.attr(a,"tabindex");return(isNaN(b)||b>=0)&&c(a).is(":focusable")}});
+c(function(){var a=document.body,b=a.appendChild(b=document.createElement("div"));c.extend(b.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0});c.support.minHeight=b.offsetHeight===100;c.support.selectstart="onselectstart"in b;a.removeChild(b).style.display="none"});c.extend(c.ui,{plugin:{add:function(a,b,d){a=c.ui[a].prototype;for(var e in d){a.plugins[e]=a.plugins[e]||[];a.plugins[e].push([b,d[e]])}},call:function(a,b,d){if((b=a.plugins[b])&&a.element[0].parentNode)for(var e=0;e<b.length;e++)a.options[b[e][0]]&&
+b[e][1].apply(a.element,d)}},contains:function(a,b){return document.compareDocumentPosition?a.compareDocumentPosition(b)&16:a!==b&&a.contains(b)},hasScroll:function(a,b){if(c(a).css("overflow")==="hidden")return false;b=b&&b==="left"?"scrollLeft":"scrollTop";var d=false;if(a[b]>0)return true;a[b]=1;d=a[b]>0;a[b]=0;return d},isOverAxis:function(a,b,d){return a>b&&a<b+d},isOver:function(a,b,d,e,h,i){return c.ui.isOverAxis(a,d,h)&&c.ui.isOverAxis(b,e,i)}})}})(jQuery);
 

--- a/js/jquery.ui.position.js
+++ /dev/null
@@ -1,252 +1,1 @@
-/*
- * jQuery UI Position @VERSION
- *
- * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT or GPL Version 2 licenses.
- * http://jquery.org/license
- *
- * http://docs.jquery.com/UI/Position
- */
-(function( $, undefined ) {
 
-$.ui = $.ui || {};
-
-var horizontalPositions = /left|center|right/,
-	verticalPositions = /top|center|bottom/,
-	center = "center",
-	_position = $.fn.position,
-	_offset = $.fn.offset;
-
-$.fn.position = function( options ) {
-	if ( !options || !options.of ) {
-		return _position.apply( this, arguments );
-	}
-
-	// make a copy, we don't want to modify arguments
-	options = $.extend( {}, options );
-
-	var target = $( options.of ),
-		targetElem = target[0],
-		collision = ( options.collision || "flip" ).split( " " ),
-		offset = options.offset ? options.offset.split( " " ) : [ 0, 0 ],
-		targetWidth,
-		targetHeight,
-		basePosition;
-
-	if ( targetElem.nodeType === 9 ) {
-		targetWidth = target.width();
-		targetHeight = target.height();
-		basePosition = { top: 0, left: 0 };
-	} else if ( $.isWindow( targetElem ) ) {
-		targetWidth = target.width();
-		targetHeight = target.height();
-		basePosition = { top: target.scrollTop(), left: target.scrollLeft() };
-	} else if ( targetElem.preventDefault ) {
-		// force left top to allow flipping
-		options.at = "left top";
-		targetWidth = targetHeight = 0;
-		basePosition = { top: options.of.pageY, left: options.of.pageX };
-	} else {
-		targetWidth = target.outerWidth();
-		targetHeight = target.outerHeight();
-		basePosition = target.offset();
-	}
-
-	// force my and at to have valid horizontal and veritcal positions
-	// if a value is missing or invalid, it will be converted to center 
-	$.each( [ "my", "at" ], function() {
-		var pos = ( options[this] || "" ).split( " " );
-		if ( pos.length === 1) {
-			pos = horizontalPositions.test( pos[0] ) ?
-				pos.concat( [center] ) :
-				verticalPositions.test( pos[0] ) ?
-					[ center ].concat( pos ) :
-					[ center, center ];
-		}
-		pos[ 0 ] = horizontalPositions.test( pos[0] ) ? pos[ 0 ] : center;
-		pos[ 1 ] = verticalPositions.test( pos[1] ) ? pos[ 1 ] : center;
-		options[ this ] = pos;
-	});
-
-	// normalize collision option
-	if ( collision.length === 1 ) {
-		collision[ 1 ] = collision[ 0 ];
-	}
-
-	// normalize offset option
-	offset[ 0 ] = parseInt( offset[0], 10 ) || 0;
-	if ( offset.length === 1 ) {
-		offset[ 1 ] = offset[ 0 ];
-	}
-	offset[ 1 ] = parseInt( offset[1], 10 ) || 0;
-
-	if ( options.at[0] === "right" ) {
-		basePosition.left += targetWidth;
-	} else if ( options.at[0] === center ) {
-		basePosition.left += targetWidth / 2;
-	}
-
-	if ( options.at[1] === "bottom" ) {
-		basePosition.top += targetHeight;
-	} else if ( options.at[1] === center ) {
-		basePosition.top += targetHeight / 2;
-	}
-
-	basePosition.left += offset[ 0 ];
-	basePosition.top += offset[ 1 ];
-
-	return this.each(function() {
-		var elem = $( this ),
-			elemWidth = elem.outerWidth(),
-			elemHeight = elem.outerHeight(),
-			marginLeft = parseInt( $.curCSS( this, "marginLeft", true ) ) || 0,
-			marginTop = parseInt( $.curCSS( this, "marginTop", true ) ) || 0,
-			collisionWidth = elemWidth + marginLeft +
-				( parseInt( $.curCSS( this, "marginRight", true ) ) || 0 ),
-			collisionHeight = elemHeight + marginTop +
-				( parseInt( $.curCSS( this, "marginBottom", true ) ) || 0 ),
-			position = $.extend( {}, basePosition ),
-			collisionPosition;
-
-		if ( options.my[0] === "right" ) {
-			position.left -= elemWidth;
-		} else if ( options.my[0] === center ) {
-			position.left -= elemWidth / 2;
-		}
-
-		if ( options.my[1] === "bottom" ) {
-			position.top -= elemHeight;
-		} else if ( options.my[1] === center ) {
-			position.top -= elemHeight / 2;
-		}
-
-		// prevent fractions (see #5280)
-		position.left = Math.round( position.left );
-		position.top = Math.round( position.top );
-
-		collisionPosition = {
-			left: position.left - marginLeft,
-			top: position.top - marginTop
-		};
-
-		$.each( [ "left", "top" ], function( i, dir ) {
-			if ( $.ui.position[ collision[i] ] ) {
-				$.ui.position[ collision[i] ][ dir ]( position, {
-					targetWidth: targetWidth,
-					targetHeight: targetHeight,
-					elemWidth: elemWidth,
-					elemHeight: elemHeight,
-					collisionPosition: collisionPosition,
-					collisionWidth: collisionWidth,
-					collisionHeight: collisionHeight,
-					offset: offset,
-					my: options.my,
-					at: options.at
-				});
-			}
-		});
-
-		if ( $.fn.bgiframe ) {
-			elem.bgiframe();
-		}
-		elem.offset( $.extend( position, { using: options.using } ) );
-	});
-};
-
-$.ui.position = {
-	fit: {
-		left: function( position, data ) {
-			var win = $( window ),
-				over = data.collisionPosition.left + data.collisionWidth - win.width() - win.scrollLeft();
-			position.left = over > 0 ? position.left - over : Math.max( position.left - data.collisionPosition.left, position.left );
-		},
-		top: function( position, data ) {
-			var win = $( window ),
-				over = data.collisionPosition.top + data.collisionHeight - win.height() - win.scrollTop();
-			position.top = over > 0 ? position.top - over : Math.max( position.top - data.collisionPosition.top, position.top );
-		}
-	},
-
-	flip: {
-		left: function( position, data ) {
-			if ( data.at[0] === center ) {
-				return;
-			}
-			var win = $( window ),
-				over = data.collisionPosition.left + data.collisionWidth - win.width() - win.scrollLeft(),
-				myOffset = data.my[ 0 ] === "left" ?
-					-data.elemWidth :
-					data.my[ 0 ] === "right" ?
-						data.elemWidth :
-						0,
-				atOffset = data.at[ 0 ] === "left" ?
-					data.targetWidth :
-					-data.targetWidth,
-				offset = -2 * data.offset[ 0 ];
-			position.left += data.collisionPosition.left < 0 ?
-				myOffset + atOffset + offset :
-				over > 0 ?
-					myOffset + atOffset + offset :
-					0;
-		},
-		top: function( position, data ) {
-			if ( data.at[1] === center ) {
-				return;
-			}
-			var win = $( window ),
-				over = data.collisionPosition.top + data.collisionHeight - win.height() - win.scrollTop(),
-				myOffset = data.my[ 1 ] === "top" ?
-					-data.elemHeight :
-					data.my[ 1 ] === "bottom" ?
-						data.elemHeight :
-						0,
-				atOffset = data.at[ 1 ] === "top" ?
-					data.targetHeight :
-					-data.targetHeight,
-				offset = -2 * data.offset[ 1 ];
-			position.top += data.collisionPosition.top < 0 ?
-				myOffset + atOffset + offset :
-				over > 0 ?
-					myOffset + atOffset + offset :
-					0;
-		}
-	}
-};
-
-// offset setter from jQuery 1.4
-if ( !$.offset.setOffset ) {
-	$.offset.setOffset = function( elem, options ) {
-		// set position first, in-case top/left are set even on static elem
-		if ( /static/.test( $.curCSS( elem, "position" ) ) ) {
-			elem.style.position = "relative";
-		}
-		var curElem   = $( elem ),
-			curOffset = curElem.offset(),
-			curTop    = parseInt( $.curCSS( elem, "top",  true ), 10 ) || 0,
-			curLeft   = parseInt( $.curCSS( elem, "left", true ), 10)  || 0,
-			props     = {
-				top:  (options.top  - curOffset.top)  + curTop,
-				left: (options.left - curOffset.left) + curLeft
-			};
-		
-		if ( 'using' in options ) {
-			options.using.call( elem, props );
-		} else {
-			curElem.css( props );
-		}
-	};
-
-	$.fn.offset = function( options ) {
-		var elem = this[ 0 ];
-		if ( !elem || !elem.ownerDocument ) { return null; }
-		if ( options ) { 
-			return this.each(function() {
-				$.offset.setOffset( this, options );
-			});
-		}
-		return _offset.call( this );
-	};
-}
-
-}( jQuery ));
-

--- /dev/null
+++ b/js/jquery.ui.position.min.js
@@ -1,1 +1,17 @@
+/*
+ * jQuery UI Position 1.8.12
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Position
+ */
+(function(c){c.ui=c.ui||{};var n=/left|center|right/,o=/top|center|bottom/,t=c.fn.position,u=c.fn.offset;c.fn.position=function(b){if(!b||!b.of)return t.apply(this,arguments);b=c.extend({},b);var a=c(b.of),d=a[0],g=(b.collision||"flip").split(" "),e=b.offset?b.offset.split(" "):[0,0],h,k,j;if(d.nodeType===9){h=a.width();k=a.height();j={top:0,left:0}}else if(d.setTimeout){h=a.width();k=a.height();j={top:a.scrollTop(),left:a.scrollLeft()}}else if(d.preventDefault){b.at="left top";h=k=0;j={top:b.of.pageY,
+left:b.of.pageX}}else{h=a.outerWidth();k=a.outerHeight();j=a.offset()}c.each(["my","at"],function(){var f=(b[this]||"").split(" ");if(f.length===1)f=n.test(f[0])?f.concat(["center"]):o.test(f[0])?["center"].concat(f):["center","center"];f[0]=n.test(f[0])?f[0]:"center";f[1]=o.test(f[1])?f[1]:"center";b[this]=f});if(g.length===1)g[1]=g[0];e[0]=parseInt(e[0],10)||0;if(e.length===1)e[1]=e[0];e[1]=parseInt(e[1],10)||0;if(b.at[0]==="right")j.left+=h;else if(b.at[0]==="center")j.left+=h/2;if(b.at[1]==="bottom")j.top+=
+k;else if(b.at[1]==="center")j.top+=k/2;j.left+=e[0];j.top+=e[1];return this.each(function(){var f=c(this),l=f.outerWidth(),m=f.outerHeight(),p=parseInt(c.curCSS(this,"marginLeft",true))||0,q=parseInt(c.curCSS(this,"marginTop",true))||0,v=l+p+(parseInt(c.curCSS(this,"marginRight",true))||0),w=m+q+(parseInt(c.curCSS(this,"marginBottom",true))||0),i=c.extend({},j),r;if(b.my[0]==="right")i.left-=l;else if(b.my[0]==="center")i.left-=l/2;if(b.my[1]==="bottom")i.top-=m;else if(b.my[1]==="center")i.top-=
+m/2;i.left=Math.round(i.left);i.top=Math.round(i.top);r={left:i.left-p,top:i.top-q};c.each(["left","top"],function(s,x){c.ui.position[g[s]]&&c.ui.position[g[s]][x](i,{targetWidth:h,targetHeight:k,elemWidth:l,elemHeight:m,collisionPosition:r,collisionWidth:v,collisionHeight:w,offset:e,my:b.my,at:b.at})});c.fn.bgiframe&&f.bgiframe();f.offset(c.extend(i,{using:b.using}))})};c.ui.position={fit:{left:function(b,a){var d=c(window);d=a.collisionPosition.left+a.collisionWidth-d.width()-d.scrollLeft();b.left=
+d>0?b.left-d:Math.max(b.left-a.collisionPosition.left,b.left)},top:function(b,a){var d=c(window);d=a.collisionPosition.top+a.collisionHeight-d.height()-d.scrollTop();b.top=d>0?b.top-d:Math.max(b.top-a.collisionPosition.top,b.top)}},flip:{left:function(b,a){if(a.at[0]!=="center"){var d=c(window);d=a.collisionPosition.left+a.collisionWidth-d.width()-d.scrollLeft();var g=a.my[0]==="left"?-a.elemWidth:a.my[0]==="right"?a.elemWidth:0,e=a.at[0]==="left"?a.targetWidth:-a.targetWidth,h=-2*a.offset[0];b.left+=
+a.collisionPosition.left<0?g+e+h:d>0?g+e+h:0}},top:function(b,a){if(a.at[1]!=="center"){var d=c(window);d=a.collisionPosition.top+a.collisionHeight-d.height()-d.scrollTop();var g=a.my[1]==="top"?-a.elemHeight:a.my[1]==="bottom"?a.elemHeight:0,e=a.at[1]==="top"?a.targetHeight:-a.targetHeight,h=-2*a.offset[1];b.top+=a.collisionPosition.top<0?g+e+h:d>0?g+e+h:0}}}};if(!c.offset.setOffset){c.offset.setOffset=function(b,a){if(/static/.test(c.curCSS(b,"position")))b.style.position="relative";var d=c(b),
+g=d.offset(),e=parseInt(c.curCSS(b,"top",true),10)||0,h=parseInt(c.curCSS(b,"left",true),10)||0;g={top:a.top-g.top+e,left:a.left-g.left+h};"using"in a?a.using.call(b,g):d.css(g)};c.fn.offset=function(b){var a=this[0];if(!a||!a.ownerDocument)return null;if(b)return this.each(function(){c.offset.setOffset(this,b)});return u.call(this)}}})(jQuery);
 

file:a/js/jquery.ui.widget.js (deleted)
--- a/js/jquery.ui.widget.js
+++ /dev/null
@@ -1,324 +1,1 @@
-/*!
- * jQuery UI Widget @VERSION
- *
- * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT or GPL Version 2 licenses.
- * http://jquery.org/license
- *
- * http://docs.jquery.com/UI/Widget
- */
-(function( $, undefined ) {
 
-var slice = Array.prototype.slice;
-
-var _cleanData = $.cleanData;
-$.cleanData = function( elems ) {
-	for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
-		$( elem ).triggerHandler( "remove" );
-	}
-	_cleanData( elems );
-};
-
-$.widget = function( name, base, prototype ) {
-	var namespace = name.split( "." )[ 0 ],
-		fullName;
-	name = name.split( "." )[ 1 ];
-	fullName = namespace + "-" + name;
-
-	if ( !prototype ) {
-		prototype = base;
-		base = $.Widget;
-	}
-
-	// create selector for plugin
-	$.expr[ ":" ][ fullName ] = function( elem ) {
-		return !!$.data( elem, name );
-	};
-
-	$[ namespace ] = $[ namespace ] || {};
-	$[ namespace ][ name ] = $[ namespace ][ name ] || function( options, element ) {
-		// allow instantiation without "new" keyword
-		if ( !this._createWidget ) {
-			return new $[ namespace ][ name ]( options, element );
-		}
-
-		// allow instantiation without initializing for simple inheritance
-		// must use "new" keyword (the code above always passes args)
-		if ( arguments.length ) {
-			this._createWidget( options, element );
-		}
-	};
-
-	var basePrototype = new base();
-	// we need to make the options hash a property directly on the new instance
-	// otherwise we'll modify the options hash on the prototype that we're
-	// inheriting from
-	basePrototype.options = $.extend( true, {}, basePrototype.options );
-	$[ namespace ][ name ].prototype = $.extend( true, basePrototype, {
-		namespace: namespace,
-		widgetName: name,
-		widgetEventPrefix: name,
-		widgetBaseClass: fullName,
-		base: base.prototype
-	}, prototype );
-
-	$.widget.bridge( name, $[ namespace ][ name ] );
-};
-
-$.widget.bridge = function( name, object ) {
-	$.fn[ name ] = function( options ) {
-		var isMethodCall = typeof options === "string",
-			args = slice.call( arguments, 1 ),
-			returnValue = this;
-
-		// allow multiple hashes to be passed on init
-		options = !isMethodCall && args.length ?
-			$.extend.apply( null, [ true, options ].concat(args) ) :
-			options;
-
-		// prevent calls to internal methods
-		if ( isMethodCall && options.charAt( 0 ) === "_" ) {
-			return returnValue;
-		}
-
-		if ( isMethodCall ) {
-			this.each(function() {
-				var instance = $.data( this, name );
-				if ( !instance ) {
-					return $.error( "cannot call methods on " + name + " prior to initialization; " +
-						"attempted to call method '" + options + "'" );
-				}
-				if ( !$.isFunction( instance[options] ) ) {
-					return $.error( "no such method '" + options + "' for " + name + " widget instance" );
-				}
-				var methodValue = instance[ options ].apply( instance, args );
-				if ( methodValue !== instance && methodValue !== undefined ) {
-					returnValue = methodValue;
-					return false;
-				}
-			});
-		} else {
-			this.each(function() {
-				var instance = $.data( this, name );
-				if ( instance ) {
-					instance.option( options || {} )._init();
-				} else {
-					object( options, this );
-				}
-			});
-		}
-
-		return returnValue;
-	};
-};
-
-$.Widget = function( options, element ) {
-	// allow instantiation without "new" keyword
-	if ( !this._createWidget ) {
-		return new $[ namespace ][ name ]( options, element );
-	}
-
-	// allow instantiation without initializing for simple inheritance
-	// must use "new" keyword (the code above always passes args)
-	if ( arguments.length ) {
-		this._createWidget( options, element );
-	}
-};
-
-$.Widget.prototype = {
-	widgetName: "widget",
-	widgetEventPrefix: "",
-	defaultElement: "<div>",
-	options: {
-		disabled: false
-	},
-	_createWidget: function( options, element ) {
-		element = $( element || this.defaultElement || this )[ 0 ];
-		this.element = $( element );
-		this.options = $.extend( true, {},
-			this.options,
-			this._getCreateOptions(),
-			options );
-
-		this.bindings = $();
-		this.hoverable = $();
-		this.focusable = $();
-
-		if ( element !== this ) {
-			$.data( element, this.widgetName, this );
-			this._bind({ remove: "destroy" });
-		}
-
-		this._create();
-		this._trigger( "create" );
-		this._init();
-	},
-	_getCreateOptions: function() {
-		return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ];
-	},
-	_create: $.noop,
-	_init: $.noop,
-
-	_super: function( method ) {
-		return this.base[ method ].apply( this, slice.call( arguments, 1 ) );
-	},
-	_superApply: function( method, args ) {
-		return this.base[ method ].apply( this, args );
-	},
-
-	destroy: function() {
-		this._destroy();
-		// we can probably remove the unbind calls in 2.0
-		// all event bindings should go through this._bind()
-		this.element
-			.unbind( "." + this.widgetName )
-			.removeData( this.widgetName );
-		this.widget()
-			.unbind( "." + this.widgetName )
-			.removeAttr( "aria-disabled" )
-			.removeClass(
-				this.widgetBaseClass + "-disabled " +
-				"ui-state-disabled" );
-
-		// clean up events and states
-		this.bindings.unbind( "." + this.widgetName );
-		this.hoverable.removeClass( "ui-state-hover" );
-		this.focusable.removeClass( "ui-state-focus" );
-	},
-	_destroy: $.noop,
-
-	widget: function() {
-		return this.element;
-	},
-
-	option: function( key, value ) {
-		var options = key;
-
-		if ( arguments.length === 0 ) {
-			// don't return a reference to the internal hash
-			return $.extend( {}, this.options );
-		}
-
-		if  (typeof key === "string" ) {
-			if ( value === undefined ) {
-				return this.options[ key ];
-			}
-			options = {};
-			options[ key ] = value;
-		}
-
-		this._setOptions( options );
-
-		return this;
-	},
-	_setOptions: function( options ) {
-		var self = this;
-		$.each( options, function( key, value ) {
-			self._setOption( key, value );
-		});
-
-		return this;
-	},
-	_setOption: function( key, value ) {
-		this.options[ key ] = value;
-
-		if ( key === "disabled" ) {
-			this.widget()
-				.toggleClass( this.widgetBaseClass + "-disabled ui-state-disabled", !!value )
-				.attr( "aria-disabled", value );
-			this.hoverable.removeClass( "ui-state-hover" );
-			this.focusable.removeClass( "ui-state-focus" );
-		}
-
-		return this;
-	},
-
-	enable: function() {
-		return this._setOption( "disabled", false );
-	},
-	disable: function() {
-		return this._setOption( "disabled", true );
-	},
-
-	_bind: function( element, handlers ) {
-		// no element argument, shuffle and use this.element
-		if ( !handlers ) {
-			handlers = element;
-			element = this.element;
-		} else {
-			this.bindings = this.bindings.add( element );
-		}
-		var instance = this;
-		$.each( handlers, function( event, handler ) {
-			element.bind( event + "." + instance.widgetName, function() {
-				// allow widgets to customize the disabled handling
-				// - disabled as an array instead of boolean
-				// - disabled class as method for disabling individual parts
-				if ( instance.options.disabled === true ||
-						$( this ).hasClass( "ui-state-disabled" ) ) {
-					return;
-				}
-				return ( typeof handler === "string" ? instance[ handler ] : handler )
-					.apply( instance, arguments );
-			});
-		});
-	},
-
-	_hoverable: function( element ) {
-		this.hoverable = this.hoverable.add( element );
-		this._bind( element, {
-			mouseenter: function( event ) {
-				$( event.currentTarget ).addClass( "ui-state-hover" );
-			},
-			mouseleave: function( event ) {
-				$( event.currentTarget ).removeClass( "ui-state-hover" );
-			}
-		});
-	},
-
-	_focusable: function( element ) {
-		this.focusable = this.focusable.add( element );
-		this._bind( element, {
-			focusin: function( event ) {
-				$( event.currentTarget ).addClass( "ui-state-focus" );
-			},
-			focusout: function( event ) {
-				$( event.currentTarget ).removeClass( "ui-state-focus" );
-			}
-		});
-	},
-
-	_trigger: function( type, event, data ) {
-		var callback = this.options[ type ],
-			args;
-
-		event = $.Event( event );
-		event.type = ( type === this.widgetEventPrefix ?
-			type :
-			this.widgetEventPrefix + type ).toLowerCase();
-		data = data || {};
-
-		// copy original event properties over to the new event
-		// this would happen if we could call $.event.fix instead of $.Event
-		// but we don't have a way to force an event to be fixed multiple times
-		if ( event.originalEvent ) {
-			for ( var i = $.event.props.length, prop; i; ) {
-				prop = $.event.props[ --i ];
-				event[ prop ] = event.originalEvent[ prop ];
-			}
-		}
-
-		this.element.trigger( event, data );
-
-		args = $.isArray( data ) ?
-			[ event ].concat( data ) :
-			[ event, data ];
-
-		return !( $.isFunction( callback ) &&
-			callback.apply( this.element[0], args ) === false ||
-			event.isDefaultPrevented() );
-	}
-};
-
-})( jQuery );
-

--- /dev/null
+++ b/js/jquery.ui.widget.min.js
@@ -1,1 +1,16 @@
+/*!
+ * jQuery UI Widget 1.8.12
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Widget
+ */
+(function(b,j){if(b.cleanData){var k=b.cleanData;b.cleanData=function(a){for(var c=0,d;(d=a[c])!=null;c++)b(d).triggerHandler("remove");k(a)}}else{var l=b.fn.remove;b.fn.remove=function(a,c){return this.each(function(){if(!c)if(!a||b.filter(a,[this]).length)b("*",this).add([this]).each(function(){b(this).triggerHandler("remove")});return l.call(b(this),a,c)})}}b.widget=function(a,c,d){var e=a.split(".")[0],f;a=a.split(".")[1];f=e+"-"+a;if(!d){d=c;c=b.Widget}b.expr[":"][f]=function(h){return!!b.data(h,
+a)};b[e]=b[e]||{};b[e][a]=function(h,g){arguments.length&&this._createWidget(h,g)};c=new c;c.options=b.extend(true,{},c.options);b[e][a].prototype=b.extend(true,c,{namespace:e,widgetName:a,widgetEventPrefix:b[e][a].prototype.widgetEventPrefix||a,widgetBaseClass:f},d);b.widget.bridge(a,b[e][a])};b.widget.bridge=function(a,c){b.fn[a]=function(d){var e=typeof d==="string",f=Array.prototype.slice.call(arguments,1),h=this;d=!e&&f.length?b.extend.apply(null,[true,d].concat(f)):d;if(e&&d.charAt(0)==="_")return h;
+e?this.each(function(){var g=b.data(this,a),i=g&&b.isFunction(g[d])?g[d].apply(g,f):g;if(i!==g&&i!==j){h=i;return false}}):this.each(function(){var g=b.data(this,a);g?g.option(d||{})._init():b.data(this,a,new c(d,this))});return h}};b.Widget=function(a,c){arguments.length&&this._createWidget(a,c)};b.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",options:{disabled:false},_createWidget:function(a,c){b.data(c,this.widgetName,this);this.element=b(c);this.options=b.extend(true,{},this.options,
+this._getCreateOptions(),a);var d=this;this.element.bind("remove."+this.widgetName,function(){d.destroy()});this._create();this._trigger("create");this._init()},_getCreateOptions:function(){return b.metadata&&b.metadata.get(this.element[0])[this.widgetName]},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName);this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+"-disabled ui-state-disabled")},
+widget:function(){return this.element},option:function(a,c){var d=a;if(arguments.length===0)return b.extend({},this.options);if(typeof a==="string"){if(c===j)return this.options[a];d={};d[a]=c}this._setOptions(d);return this},_setOptions:function(a){var c=this;b.each(a,function(d,e){c._setOption(d,e)});return this},_setOption:function(a,c){this.options[a]=c;if(a==="disabled")this.widget()[c?"addClass":"removeClass"](this.widgetBaseClass+"-disabled ui-state-disabled").attr("aria-disabled",c);return this},
+enable:function(){return this._setOption("disabled",false)},disable:function(){return this._setOption("disabled",true)},_trigger:function(a,c,d){var e=this.options[a];c=b.Event(c);c.type=(a===this.widgetEventPrefix?a:this.widgetEventPrefix+a).toLowerCase();d=d||{};if(c.originalEvent){a=b.event.props.length;for(var f;a;){f=b.event.props[--a];c[f]=c.originalEvent[f]}}this.element.trigger(c,d);return!(b.isFunction(e)&&e.call(this.element[0],c,d)===false||c.isDefaultPrevented())}}})(jQuery);
 

symlink:b/labs/about.php (new)
--- /dev/null
+++ b/labs/about.php
@@ -1,1 +1,1 @@
-
+../about.php

--- /dev/null
+++ b/labs/busstopdensity.php
@@ -1,1 +1,78 @@
+<?php
+include ('../include/common.inc.php');
+//include_header("Bus Stop Density", "busstopdensity")
+?>
 
+<style type="text/css">
+
+			#map_container{
+				width:100%;
+				height:100%;
+			}
+			#status, #legend {
+				background-color: #eeeeee;
+				padding: 3px 5px 3px 5px;
+				opacity: .9;
+			}
+		</style>
+	
+		<div id="map_container"></div>
+			<div id="status">
+			Status:&nbsp; <span id="log"> <img alt="progess bar" src="progress_bar.gif" width="150" height="16"/></span>
+		</div>
+		<script type="text/javascript" src="http://www.google.com/jsapi?autoload={%22modules%22:[{%22name%22:%22maps%22,version:3,other_params:%22sensor=false%22},{%22name%22:%22jquery%22,%22version%22:%221.4.2%22}]}"></script>
+		<script type="text/javascript">
+		//<![CDATA[
+		//Google Map API v3
+		
+		var googleMap = null;
+		var previousPos = null;
+
+		$(function($){//Called when page is loaded
+			googleMap = new google.maps.Map(document.getElementById("map_container"), {
+				zoom: 17, 
+				minZoom: 12, 
+				center: new google.maps.LatLng(-35.25,149.125), 
+				mapTypeId: google.maps.MapTypeId.SATELLITE});
+			//Set status bar
+			googleMap.controls[google.maps.ControlPosition.TOP_LEFT].push($("#status").get(0));
+			//Set legend
+			googleMap.controls[google.maps.ControlPosition.BOTTOM_LEFT].push($("#legend").get(0));
+
+			google.maps.event.addListener(googleMap, "zoom_changed", function(){
+				google.maps.event.trigger(googleMap, "mousemove", previousPos);
+			});//onzoomend
+				
+			//Add a listener when mouse moves
+			google.maps.event.addListener(googleMap, "mousemove", function(event){
+				var latLng = event.latLng;
+				var xy = googleMap.getProjection().fromLatLngToPoint(latLng);
+				var ratio = Math.pow(2,googleMap.getZoom());
+				$("#log").html("Zoom:" + googleMap.getZoom() + " WGS84:(" + latLng.lat().toFixed(5) + ", " + latLng.lng().toFixed(5) + ") Px:(" + Math.floor(xy.x * ratio)  + "," + Math.floor(xy.y *ratio) + ")");
+				previousPos = event;
+			});//onmouseover
+
+			//Add a listener when mouse leaves the map area
+			google.maps.event.addListener(googleMap, "mouseout", function(event){
+				$("#log").html("");
+			});//onmouseout
+
+			//Add tile overlay
+			var myOverlay = new google.maps.ImageMapType({
+				getTileUrl: function(coord, zoom) {
+					return 'busstopdensity.tile.php?x=' + coord.x + '&y=' + coord.y + '&zoom=' +zoom;
+				},
+				tileSize: new google.maps.Size(256, 256),
+				isPng: true,
+				opacity:1.0
+			});
+			googleMap.overlayMapTypes.insertAt(0, myOverlay);
+			$("#log").html("Map loaded!");
+		});//onload
+		//]]>
+		</script>
+<?php
+include_footer()
+?>
+        
+

--- /dev/null
+++ b/labs/busstopdensity.tile.php
@@ -1,1 +1,124 @@
+<?php
+include ('../include/common.inc.php');
+$debugOkay = Array();
 
+/*
+*DISCLAIMER
+*  http://blog.gmapify.fr/create-beautiful-tiled-heat-maps-with-php-and-gd
+*THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES *OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, *INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF *USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT *(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*
+*	@author: Olivier G. <olbibigo_AT_gmail_DOT_com>
+*	@version: 1.0
+*	@history:
+*		1.0	creation
+*/
+	set_time_limit(120);//2mn
+	ini_set('memory_limit', '256M');
+error_reporting(E_ALL ^ E_DEPRECATED);
+	require_once ('lib/GoogleMapUtility.php');
+	require_once ('lib/HeatMap.php');
+
+	//Root folder to store generated tiles
+	define('TILE_DIR', 'tiles/');
+	//Covered geographic areas
+	define('MIN_LAT', -35.48);
+	define('MAX_LAT', -35.15);
+	define('MIN_LNG', 148.98);
+	define('MAX_LNG', 149.25);
+	define('TILE_SIZE_FACTOR', 0.5);
+	define('SPOT_RADIUS', 30);
+	define('SPOT_DIMMING_LEVEL', 50);
+	
+	//Input parameters
+	if(isset($_GET['x']))
+		$X = (int)$_GET['x'];
+	else
+		exit("x missing");
+	if(isset($_GET['y']))
+		$Y = (int)$_GET['y'];
+	else
+		exit("y missing");
+	if(isset($_GET['zoom']))
+		$zoom = (int)$_GET['zoom'];
+	else
+		exit("zoom missing");
+if ($zoom < 12) { //enforce minimum zoom
+			header('Content-type: image/png');
+			echo file_get_contents(TILE_DIR.'empty.png');
+}
+	$dir = TILE_DIR.$zoom;
+	$tilename = $dir.'/'.$X.'_'.$Y.'.png';
+	//HTTP headers  (data type and caching rule)
+	header("Cache-Control: must-revalidate");
+	header("Expires: " . gmdate("D, d M Y H:i:s", time() + 86400) . " GMT");
+	if(!file_exists($tilename)){
+		$rect = GoogleMapUtility::getTileRect($X, $Y, $zoom);
+		//A tile can contain part of a spot with center in an adjacent tile (overlaps).
+		//Knowing the spot radius (in pixels) and zoom level, a smart way to process tiles would be to compute the box (in decimal degrees) containing only spots that can be drawn on current tile. We choose a simpler solution by increeasing  geo bounds by 2*TILE_SIZE_FACTOR whatever the zoom level and spot radius.
+		$extend_X = $rect->width * TILE_SIZE_FACTOR;//in decimal degrees
+		$extend_Y = $rect->height * TILE_SIZE_FACTOR;//in decimal degrees
+		$swlat = $rect->y - $extend_Y;
+		$swlng = $rect->x - $extend_X;
+		$nelat = $swlat + $rect->height + 2 * $extend_Y;
+		$nelng = $swlng + $rect->width + 2 * $extend_X;
+
+		if( ($nelat <= MIN_LAT) || ($swlat >= MAX_LAT) || ($nelng <= MIN_LNG) || ($swlng >= MAX_LNG)){
+			//No geodata so return generic empty tile
+			echo file_get_contents(TILE_DIR.'empty.png');
+			exit();
+		}
+
+		//Get McDonald's spots
+		$spots = fGetPOI('Select * from stops where
+				(stop_lon > '.$swlng.' AND stop_lon < '.$nelng.')
+			AND (stop_lat < '.$nelat.' AND stop_lat > '.$swlat.')', $im, $X, $Y, $zoom, SPOT_RADIUS);
+
+		
+		if(empty($spots)){
+			//No geodata so return generic empty tile
+			header('Content-type: image/png');
+			echo file_get_contents(TILE_DIR.'empty.png');
+		}else{
+			if(!file_exists($dir)){
+				mkdir($dir, 0705);
+			}
+			//All the magics is in HeatMap class :)
+			$im = HeatMap::createImage($spots, GoogleMapUtility::TILE_SIZE, GoogleMapUtility::TILE_SIZE, heatMap::$WITH_ALPHA, SPOT_RADIUS, SPOT_DIMMING_LEVEL, HeatMap::$GRADIENT_FIRE);
+			//Store tile for reuse and output it
+			header('content-type:image/png;');
+			imagepng($im, $tilename);
+			echo file_get_contents($tilename);
+			imagedestroy($im);
+			unset($im);
+		}
+	}else{
+		//Output stored tile
+		header('content-type:image/png;');
+		echo file_get_contents($tilename);
+	}
+	/////////////
+	//Functions//
+	/////////////
+	function fGetPOI($query, &$im, $X, $Y, $zoom, $offset){
+            global $conn;
+		$nbPOIInsideTile = 0;
+
+	$result = pg_query($conn, $query);
+        $spots = Array();
+	if (!$result) {
+		databaseError(pg_result_error($result));
+		return Array();
+	}
+	foreach( pg_fetch_all($result) as $row){
+				$point = GoogleMapUtility::getOffsetPixelCoords($row['stop_lat'], $row['stop_lon'], $zoom, $X, $Y);
+				//Count result only in the tile
+				if( ($point->x > -$offset) && ($point->x < (GoogleMapUtility::TILE_SIZE+$offset)) && ($point->y > -$offset) && ($point->y < (GoogleMapUtility::TILE_SIZE+$offset))){
+					$spots[] = new HeatMapPoint($point->x, $point->y);
+				}
+				
+			}//while
+		return $spots;
+	}//fAddPOI
+?>
+
+

symlink:b/labs/css (new)
--- /dev/null
+++ b/labs/css
@@ -1,1 +1,1 @@
-
+../css/

symlink:b/labs/feedback.php (new)
--- /dev/null
+++ b/labs/feedback.php
@@ -1,1 +1,1 @@
-
+../feedback.php

 Binary files /dev/null and b/labs/gradient/classic.png differ
 Binary files /dev/null and b/labs/gradient/fire.png differ
 Binary files /dev/null and b/labs/gradient/pgaitch.png differ
file:b/labs/index.php (new)
--- /dev/null
+++ b/labs/index.php
@@ -1,1 +1,20 @@
+<?php
+include ('../include/common.inc.php');
+include_header("Busness R&amp;D", "index")
+?>
+	    <ul data-role="listview" data-theme="e" data-groupingtheme="e">
+		<li data-role="list-divider" > Experimental Features </li>
+		<li><a href="mywaybalance.php"><h3>MyWay Balance for mobile</h3>
+		<p>Mobile viewer for MyWay balance. Warning! No HTTPS security.</p></a></li>
+		<li><a href="networkstats.php"><h3>Route Statistics</h3>
+		<p>Analysis of route timing points</p></a></li>
+		<li><a href="busstopdensity.php"><h3>Bus Stop Density Map</h3>
+		<p>Analysis of bus stop coverage</p></a></li>
+		<li>More coming soon!</li>
+            </ul>
+	    </div>
+<?php
+include_footer()
+?>
+        
 

symlink:b/labs/js (new)
--- /dev/null
+++ b/labs/js
@@ -1,1 +1,1 @@
-
+../js

symlink:b/labs/lib (new)
--- /dev/null
+++ b/labs/lib
@@ -1,1 +1,1 @@
-
+../lib

--- a/labs/myway_api.json.php
+++ b/labs/myway_api.json.php
@@ -74,7 +74,7 @@
 }
 
 if (!isset($return['error'])) {
-	include_once ('simple_html_dom.php');
+	include_once ('lib/simple_html_dom.php');
 	$page = str_get_html($pageHTML);
 	$pageAlerts = $page->find(".smartCardAlert");
 	if (sizeof($pageAlerts) > 0) {

--- a/labs/mywaybalance.php
+++ b/labs/mywaybalance.php
@@ -1,22 +1,40 @@
 <?php
 include ('../include/common.inc.php');
-include_header("MyWay Balance", "mywayBalance", true, false, true);
+include_header("MyWay Balance", "mywayBalance", false, false, true);
+		echo '<div data-role="page"> 
+	<div data-role="header" data-position="inline">
+	<a href="' . $_SERVER["HTTP_REFERER"] . '" data-icon="arrow-l" data-rel="back" class="ui-btn-left">Back</a> 
+		<h1>MyWay Balance</h1>
+		<a href="mywaybalance.php?logout=yes" data-icon="delete" class="ui-btn-right">Logout</a>
+	</div><!-- /header -->
+        <a name="maincontent" id="maincontent"></a>
+        <div data-role="content"> ';
+	
 $return = Array();
+function logout() {
+	setcookie("card_number", "", time() - 60 * 60 * 24 * 100, "/");
+	setcookie("date", "", time() - 60 * 60 * 24 * 100, "/");
+	setcookie("secret_answer", "", time() - 60 * 60 * 24 * 100, "/");
+}
 function printBalance($cardNumber, $date, $pwrd)
 {
 	global $return;
-	$return = json_decode(getPage(curPageURL() . "/myway_api.json.php?card_number=$cardNumber&DOBday={$date[0]}&DOBmonth={$date[1]}&DOByear={$date[2]}&secret_answer=$pwrd"), true);
-    
-        if (isset($return['error'])) {
-            echo "<font color=red>" . var_dump($return['error']) . "</font>";
-        } else {
+	$return = json_decode(getPage(curPageURL() . "/myway_api.json.php?card_number=$cardNumber&DOBday={$date[0]}&DOBmonth={$date[1]}&DOByear={$date[2]}&secret_answer=$pwrd") , true);
+	if (isset($return['error'])) {
+		logout();
+		echo '<h3><font color="red">' . $return['error'][0] . "</font></h3>";
+	}
+	else {
 		echo "<h2>Balance: " . $return['myway_carddetails']['Card Balance'] . "</h2>";
 		echo '<ul data-role="listview" data-inset="true"><li data-role="list-divider"> Recent Transactions </li>';
+		$txCount=0;
 		foreach ($return['myway_transactions'] as $transaction) {
 			echo "<li><b>" . $transaction["Date / Time"] . "</b>";
-                        echo "<br><small>" . $transaction["TX Reference No / Type"]. "</small>";
-                        echo '<p class="ui-li-aside">'.$transaction["TX Amount"].'</p>';
+			echo "<br><small>" . $transaction["TX Reference No / Type"] . "</small>";
+			echo '<p class="ui-li-aside">' . $transaction["TX Amount"] . '</p>';
 			echo "</li>";
+			$txCount++;
+			if ($txCount > 10) break;
 		}
 		echo "</ul>";
 	}
@@ -25,12 +43,15 @@
 	$cardNumber = $_REQUEST['card_number'];
 	$date = explode("/", $_REQUEST['date']);
 	$pwrd = $_REQUEST['secret_answer'];
-	if ($_REQUEST['remember'] == true) {
-		$_COOKIE['card_number'] = $cardNumber;
-		$_COOKIE['date'] = $date;
-		$_COOKIE['secret_answer'] = $pwrd;
+	if ($_REQUEST['remember'] == "on") {
+		setcookie("card_number", $cardNumber, time() + 60 * 60 * 24 * 100, "/");
+		setcookie("date", $_REQUEST['date'], time() + 60 * 60 * 24 * 100, "/");
+		setcookie("secret_answer", $pwrd, time() + 60 * 60 * 24 * 100, "/");
 	}
 	printBalance($cardNumber, $date, $pwrd);
+}
+else if (isset($_REQUEST['logout'])) {
+	echo '<center><h3> Logged out of MyWay balance </h3><a href="/index.php">Back to main menu...</a><center>';
 }
 else if (isset($_COOKIE['card_number']) && isset($_COOKIE['date']) && isset($_COOKIE['secret_answer'])) {
 	$cardNumber = $_COOKIE['card_number'];

--- /dev/null
+++ b/labs/networkstats.php
@@ -1,1 +1,147 @@
+<?php
+include ('../include/common.inc.php');
+include_header("Route Statistics", "networkstats")
+?>
+<script type="text/javascript" src="js/flotr/lib/prototype-1.6.0.2.js"></script>
 
+		<!--[if IE]>
+
+			<script type="text/javascript" src="js/flotr/lib/excanvas.js"></script>
+
+			<script type="text/javascript" src="js/flotr/lib/base64.js"></script>
+
+		<![endif]-->
+
+		<script type="text/javascript" src="js/flotr/lib/canvas2image.js"></script>
+
+		<script type="text/javascript" src="js/flotr/lib/canvastext.js"></script>
+
+		<script type="text/javascript" src="js/flotr/flotr.debug-0.2.0-alpha_radar1.js"></script>
+		<form method="get" action="networkstats.php">
+			<select id="routeid" name="routeid">
+				<?php
+				foreach (getRoutes() as $route) {
+				echo "<option value=\"{$route['route_id']}\">{$route['route_short_name']} {$route['route_long_name']}</option>";
+				}
+				?>
+			</select>
+			<input type="submit" value="View"/>
+		</form>
+
+<?php
+// middle of graph = 6am
+$adjustFactor = 0;
+$route = getRoute($routeid);
+echo "<h1>{$route['route_short_name']} {$route['route_long_name']}</h1>";
+foreach (getRouteTrips($routeid) as $key => $trip) {
+	$dLabel[$key] = $trip['arrival_time'];
+	if ($key == 0) {
+		$time = strtotime($trip['arrival_time']);
+		$adjustFactor = (date("G", $time) * 3600);
+	}
+	$tripStops = viaPoints($trip['trip_id']);
+	foreach ($tripStops as $i => $stop) {
+		if ($key == 0) {
+			$dTicks[$i] = $stop['stop_name'];
+		}
+		$time = strtotime($stop['arrival_time']);
+		$d[$key][$i] = 	(date("G", $time) * 3600) + (date("i", $time) * 60) + date("s", $time) - $adjustFactor;
+
+	}
+}
+
+?>
+<div id="container" style="width:100%;height:900px;"></div>
+<script type="text/javascript">
+
+			/**
+
+			 * Wait till dom's finished loading.
+
+			 */
+
+			document.observe('dom:loaded', function(){
+
+				/**
+
+				 * Fill series d1 and d2.
+
+				 */
+<?php
+foreach ($d as $key => $dataseries) {
+	
+	echo "var d$key =[";
+	foreach ($dataseries as $i => $datapoint) {
+		echo "[$i, $datapoint],";
+	}
+	echo "];\n";
+}
+
+?>
+
+			    
+
+			    var f = Flotr.draw($('container'), 
+
+					[
+						<?php
+foreach ($d as $key => $dataseries) {
+	
+	echo '{data:d'.$key.", label:'{$dLabel[$key]}'".', radar:{fill:false}},'."\n";
+	
+}
+
+?>
+					 ],
+
+					{defaultType: 'radar',
+
+					 radarChartMode: true,
+
+					 HtmlText: false,
+
+					 fontSize: 9,
+
+					 xaxis:{
+
+						ticks: [
+							<?php
+foreach ($dTicks as $key => $tickName) {
+		echo '['.$key.', "'.$tickName.'"],';
+}
+
+?>
+							
+							]},
+
+					 mouse:{ // Setup point tracking
+
+						track: true,
+
+						lineColor: 'black',
+
+						relative: true,
+
+						sensibility: 70,
+
+						trackFormatter: function(obj){
+						var d = new Date();
+						d.setMinutes(0);
+						d.setHours(0);
+d.setTime(d.getTime() + Math.floor(obj.radarData*1000) + <?php echo $adjustFactor*1000 ?>);
+return d.getHours() +':'+ (d.getMinutes().toString().length == 1 ? '0'+ d.getMinutes():  d.getMinutes());
+}}});
+
+			});
+
+		</script>
+
+	    </div>
+	    
+
+
+<?php
+include_footer()
+?>
+        
+

 Binary files /dev/null and b/labs/tiles/empty.png differ
--- a/labs/tripPlannerTester.kml.php
+++ b/labs/tripPlannerTester.kml.php
@@ -8,6 +8,50 @@
 	}
 	else {
 		return (($pBegin - $pEnd) * (1 - ($pStep / $pMax))) + $pEnd;
+	}
+}
+require ("../lib/rolling-curl/RollingCurl.php");
+function processResult_cb($response, $info, $request)
+{
+	global $testRegions, $regionTimes,$csv,$kml, $latdeltasize,$londeltasize;
+	$md = $request->metadata;
+	$tripplan = json_decode($response);
+	$plans = Array();
+	//var_dump(Array($info, $request));
+	if (is_array($tripplan->plan->itineraries->itinerary)) {
+		foreach ($tripplan->plan->itineraries->itinerary as $itineraryNumber => $itinerary) {
+			$plans[floor($itinerary->duration / 60000) ] = $itinerary;
+		}
+	}
+	else {
+		$plans[floor($tripplan->plan->itineraries->itinerary->duration / 60000) ] = $tripplan->plan->itineraries->itinerary;
+	}
+	if ($csv) echo "{$md['i']},{$md['j']}," . min(array_keys($plans)) . ",$latdeltasize, $londeltasize,{$md['key']}\n";
+	if ($kml) {
+		$time = min(array_keys($plans));
+		$plan = "";
+		if (is_array($plans[min(array_keys($plans)) ]->legs->leg)) {
+			foreach ($plans[min(array_keys($plans)) ]->legs->leg as $legNumber => $leg) {
+				$plan.= processLeg($legNumber, $leg) . ",";
+			}
+		}
+		else {
+			$plan.= processLeg(0, $plans[min(array_keys($plans)) ]->legs->leg);
+		}
+		if (isset($tripplan->error) && $tripplan->error->id == 404) {
+			$time = 999;
+			$plan = "Trip not possible without excessive walking from nearest bus stop";
+		}
+		$testRegions[] = Array(
+			"lat" => $md['i'],
+			"lon" => $md['j'],
+			"time" => $time,
+			"latdeltasize" => $latdeltasize,
+			"londeltasize" => $londeltasize,
+			"regionname" => $md['key'],
+			"plan" => $plan . '<br/><a href="' . htmlspecialchars($md['url']) . '">original plan</a>'
+		);
+		$regionTimes[] = $time;
 	}
 }
 function Gradient($HexFrom, $HexTo, $ColorSteps)
@@ -49,18 +93,18 @@
 		//}
 		//$walkingstep.= floor($step->distance) . "m";
 		//return $walkingstep;
+		
 	}
 }
 $csv = false;
 $kml = true;
+$gearthcolors = false;
 if ($kml) {
-	//header('Content-Type: application/vnd.google-earth.kml+xml');
+	header('Content-Type: application/vnd.google-earth.kml+xml');
 	echo '<?xml version="1.0" encoding="UTF-8"?>
 <kml xmlns="http://www.opengis.net/kml/2.2"><Document>';
 }
 include ('../include/common.inc.php');
-//Test code to grab transit times
-// make sure to sleep(10);
 $boundingBoxes = Array(
 	"belconnen" => Array(
 		"startlat" => - 35.1928,
@@ -93,8 +137,8 @@
 		"finishlon" => 149.1243,
 	)
 );
-$latdeltasize = 0.01;
-$londeltasize = 0.01;
+$latdeltasize = 0.005;
+$londeltasize = 0.005;
 $from = "Wattle Street";
 $fromPlace = (startsWith($from, "-") ? $from : geocode($from, false));
 $startTime = "9:00 am";
@@ -103,82 +147,40 @@
 $regionTimes = Array();
 $testRegions = Array();
 $useragent = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1";
+if ($kml) echo "<name> $from at $startTime on $startDate </name>";
 if ($csv) echo "<pre>";
 if ($csv) echo "lat,lon,time,latdeltasize, londeltasize, region key name\n";
+$rc = new RollingCurl("processResult_cb");
+$rc->window_size = 2;
 foreach ($boundingBoxes as $key => $boundingBox) {
 	for ($i = $boundingBox['startlat']; $i >= $boundingBox['finishlat']; $i-= $latdeltasize) {
 		for ($j = $boundingBox['startlon']; $j <= $boundingBox['finishlon']; $j+= $londeltasize) {
 			$url = $otpAPIurl . "ws/plan?date=" . urlencode($startDate) . "&time=" . urlencode($startTime) . "&mode=TRANSIT%2CWALK&optimize=QUICK&maxWalkDistance=440&wheelchair=false&toPlace=" . $i . "," . $j . "&fromPlace=$fromPlace";
-			$ch = curl_init($url);
-			curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
-			curl_setopt($ch, CURLOPT_HEADER, 0);
-			curl_setopt($ch, CURLOPT_HTTPHEADER, array(
+			//debug($url);
+			$request = new RollingCurlRequest($url);
+			$request->headers = Array(
 				"Accept: application/json"
-			));
-			curl_setopt($ch, CURLOPT_TIMEOUT, 5);
-			$page = curl_exec($ch);
-			if (curl_errno($ch)) {
-				if ($csv) echo "Trip planner temporarily unavailable: " . curl_errno($ch) . " " . curl_error($ch);
-			}
-			else {
-				$tripplan = json_decode($page); 
-				$plans = Array();
-				if (is_array($tripplan->plan->itineraries->itinerary)) {
-					foreach ($tripplan->plan->itineraries->itinerary as $itineraryNumber => $itinerary) {
-						$plans[floor($itinerary->duration / 60000) ] = $itinerary;
-					}
-				}
-				else {
-					$plans[floor($tripplan->plan->itineraries->itinerary->duration / 60000) ] = $tripplan->plan->itineraries->itinerary;
-				}
-				if ($csv) echo "$i,$j," . min(array_keys($plans)) . ",$latdeltasize, $londeltasize,$key\n";
-				if ($kml) {
-					$time = min(array_keys($plans));
-					$plan = "";
-					if (is_array($plans[min(array_keys($plans)) ]->legs->leg)) {
-						foreach ($plans[min(array_keys($plans)) ]->legs->leg as $legNumber => $leg) {
-							$plan .= processLeg($legNumber, $leg).",";
-						}
-					}
-					else {
-						$plan .= processLeg(0, $plans[min(array_keys($plans)) ]->legs->leg);
-					}
-						if (isset($tripplan->error) && $tripplan->error->id == 404) {
-							$time = 999;
-							$plan = "Trip not possible without excessive walking from nearest bus stop";
-						}
-					$testRegions[] = Array(
-						"lat" => $i,
-						"lon" => $j,
-						"time" => $time,
-						"latdeltasize" => $latdeltasize,
-						"londeltasize" => $londeltasize,
-						"regionname" => $key,
-						"plan" => $plan . "<br/><a href='". htmlspecialchars($url)."'>original plan</a>"
-					);
-					$regionTimes[] = $time;
-				}
-			}
-			flush();
-			ob_flush();
-			curl_close($ch);
-		}
-	}
-}
+			);
+			$request->metadata = Array( "i" => $i, "j" => $j, "key" => $key, "url" => $url);
+			$rc->add($request);
+		}
+	}
+}
+$rc->execute();
 if ($kml) {
 	$colorSteps = 9;
 	//$minTime = min($regionTimes);
 	//$maxTime = max($regionTimes);
 	//$rangeTime = $maxTime - $minTime;
 	//$deltaTime = $rangeTime / $colorSteps;
-	$Gradients = Gradient(strrev("66FF00"), strrev("FF0000"), $colorSteps); // KML is BGR not RGB so strrev
+	$Gradients = Gradient(strrev("66FF00") , strrev("FF0000") , $colorSteps); // KML is BGR not RGB so strrev
 	foreach ($testRegions as $testRegion) {
 		//$band = (floor(($testRegion[time] - $minTime) / $deltaTime));
 		$band = (floor($testRegion[time] / 10));
 		if ($band > $colorSteps) $band = $colorSteps;
 		echo "<Placemark>
   <name>" . $testRegion['regionname'] . " time {$testRegion['time']} band $band</name>
-  <description> {$testRegion['plan']} </description>
+  <description> <![CDATA[ {$testRegion['plan']}  ]]> </description>
     <Style>
         <PolyStyle>
             <color>c7" . $Gradients[$band] . "</color>" . // 7f = 50% alpha, c7=78%

--- a/labs/tripPlannerTester.php
+++ b/labs/tripPlannerTester.php
@@ -3,7 +3,9 @@
     <script src="openlayers/OpenLayers.js"></script>
  <SCRIPT TYPE="text/javascript" SRC="OpenStreetMap.js"></SCRIPT> 
     <script type="text/javascript">
-
+        var map,select;
+       
+	
 function init()
 {
     var extent = new OpenLayers.Bounds(148.98, -35.48, 149.25, -35.15);
@@ -16,13 +18,13 @@
 		}; 
  
 		// create the ol map object
-		var map = new OpenLayers.Map('map', options);
+		map = new OpenLayers.Map('map', options);
     
 var osmtiles = new OpenLayers.Layer.OSM("OSM");
 
 var nearmap = new OpenLayers.Layer.OSM.NearMap("NearMap");
 
-    var tripplantest = new OpenLayers.Layer.GML("tripplantest", "tripPlannerTester.kml.php", {
+    var tripplantest = new OpenLayers.Layer.GML("tripplantest", "tripPlannerTester.kml", {
         format: OpenLayers.Format.KML,
         formatOptions: {
             extractStyles: true,
@@ -44,9 +46,45 @@
     {
         displayProjection: new OpenLayers.Projection("EPSG:900913")
     }));
+    
+  select = new OpenLayers.Control.SelectFeature(tripplantest);
+            
+            tripplantest.events.on({
+                "featureselected": onFeatureSelect,
+                "featureunselected": onFeatureUnselect
+            });
+ 
+            map.addControl(select);
+            select.activate();   
 
 }
- 
+ function onPopupClose(evt) {
+            select.unselectAll();
+        }
+        function onFeatureSelect(event) {
+            var feature = event.feature;
+            // Since KML is user-generated, do naive protection against
+            // Javascript.
+            var content = "<h2>"+feature.attributes.name + "</h2>" + feature.attributes.description;
+            if (content.search("<script") != -1) {
+                content = "Content contained Javascript! Escaped content below.<br />" + content.replace(/</g, "&lt;");
+            }
+            popup = new OpenLayers.Popup.FramedCloud("chicken", 
+                                     feature.geometry.getBounds().getCenterLonLat(),
+                                     new OpenLayers.Size(100,100),
+                                     content,
+                                     null, true, onPopupClose);
+            feature.popup = popup;
+            map.addPopup(popup);
+        }
+        function onFeatureUnselect(event) {
+            var feature = event.feature;
+            if(feature.popup) {
+                map.removePopup(feature.popup);
+                feature.popup.destroy();
+                delete feature.popup;
+            }
+        }
     </script>
 
   </head>

--- a/layar_api.php
+++ b/layar_api.php
@@ -5,36 +5,31 @@
 $output['layer'] = "canberrabusstops";
 $max_page = 10;
 $max_results = 50;
-$page_start = 0 + filter_var($_REQUEST['pageKey'], FILTER_SANITIZE_NUMBER_INT);
-$page_end = $max_page + filter_var($_REQUEST['pageKey'], FILTER_SANITIZE_NUMBER_INT);
-$lat = filter_var($_REQUEST['lat'], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
-$lon = filter_var($_REQUEST['lon'], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
-$url = $APIurl . "/json/neareststops?lat=$lat&lon=$lon&limit=50";
-$contents = json_decode(getPage($url));
-debug(print_r($contents, true));
+$page_start = 0 + $pageKey;
+$page_end = $max_page + $pageKey;
+$contents = getNearbyStops($lat, $lon, 50, $max_distance);
 $stopNum = 0;
-foreach ($contents as $row) {
+foreach ($contents as $stop) {
 	$stopNum++;
 	if ($stopNum > $page_start && $stopNum <= $page_end) {
 		$hotspot = Array();
-		$hotspot['id'] = $row[0];
-		$hotspot['title'] = $row[1];
+		$hotspot['id'] = $stop['stop_id'];
+		$hotspot['title'] = $stop['stop_name'];
 		$hotspot['type'] = 0;
-		$hotspot['lat'] = floor($row[2] * 1000000);
-		$hotspot['lon'] = floor($row[3] * 1000000);
-		$hotspot['distance'] = distance($row[2], $row[3], $_REQUEST['lat'], $_REQUEST['lon']);
+		$hotspot['lat'] = floor($stop['stop_lat'] * 1000000);
+		$hotspot['lon'] = floor($stop['stop_lon'] * 1000000);
+		$hotspot['distance'] = floor($stop['distance']);
+		$hotspot['attribution'] = "ACTION Buses";
 		$hotspot['actions'] = Array(
 			Array(
 				"label" => 'View more trips/information',
-				'uri' => 'http://bus.lambdacomplex.org/' . 'stop.php?stopid=' . $row[0]
+				'uri' => 'http://bus.lambdacomplex.org/' . 'stop.php?stopid=' . $stop['stop_id']
 			)
 		);
-		$url = $APIurl . "/json/stoptrips?stop=" . $row[0] . "&time=" . midnight_seconds() . "&service_period=" . service_period() . "&limit=4&time_range=" . strval(90 * 60);
-		$trips = json_decode(getPage($url));
-		debug(print_r($trips, true));
+		$trips = getStopTripsWithTimes($stop['stop_id'], "", "", "", 3);
 		foreach ($trips as $key => $row) {
 			if ($key < 3) {
-				$hotspot['line' . strval($key + 2) ] = $row[1][1] . ' @ ' . midnight_seconds_to_time($row[0]);
+				$hotspot['line' . strval($key + 2) ] = $row['route_short_name'] . ' ' . $row['route_long_name'] . ' @ ' . $row['arrival_time'];
 			}
 		}
 		if (sizeof($trips) == 0) $hotspot['line2'] = 'No trips in the near future.';
@@ -49,7 +44,7 @@
 	$output['errorString'] = 'no results, try increasing range';
 	$output['errorCode'] = 21;
 }
-if ($page_end >= $max_results || sizeof($hotspot) < $max_page) {
+if ($page_end >= $max_results || sizeof($contents) < $page_start+$max_page) {
 	$output["morePages"] = false;
 	$output["nextPageKey"] = null;
 }

--- /dev/null
+++ b/lib/GoogleMapUtility.php
@@ -1,1 +1,97 @@
+<?php
+/*
+*DISCLAIMER
+* 
+*THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES *OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, *INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF *USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT *(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*
+*	@author: Olivier G. <olbibigo_AT_gmail_DOT_com>
+*	@version: 1.1
+*	@history:
+*		1.0	creation
+		1.1	disclaimer added
+*/
+class GoogleMapUtility {
+	const TILE_SIZE = 256;
+	
+	//(lat, lng, z) -> parent tile (X,Y)
+	public static function getTileXY($lat, $lng, $zoom) {
+		$normalised = GoogleMapUtility::_toNormalisedMercatorCoords(GoogleMapUtility::_toMercatorCoords($lat, $lng));
+		$scale = 1 << ($zoom);
+		return new Point(
+			(int)($normalised->x * $scale), 
+			(int)($normalised->y * $scale)
+		);
+	}//toTileXY
+	
+	//(lat, lng, z) -> (x,y) with (0,0) in the upper left corner of the MAP
+	public static function getPixelCoords($lat, $lng, $zoom) {
+		$normalised = GoogleMapUtility::_toNormalisedMercatorCoords(GoogleMapUtility::_toMercatorCoords($lat, $lng));
+		$scale = (1 << ($zoom)) * GoogleMapUtility::TILE_SIZE;
+		return new Point(
+			(int)($normalised->x * $scale), 
+			(int)($normalised->y * $scale)
+		);
+	}//getPixelCoords
 
+	//(lat, lng, z) -> (x,y) in the upper left corner of the TILE ($X, $Y)
+	public static function getOffsetPixelCoords($lat,$lng,$zoom, $X, $Y) {
+		$pixelCoords = GoogleMapUtility::getPixelCoords($lat, $lng, $zoom);
+		return new Point(
+			$pixelCoords->x - $X * GoogleMapUtility::TILE_SIZE, 
+			$pixelCoords->y - $Y * GoogleMapUtility::TILE_SIZE
+		);
+	}//getPixelOffsetInTile
+	
+	public static function getTileRect($X,$Y,$zoom) {
+		$tilesAtThisZoom = 1 << $zoom;
+		$lngWidth = 360.0 / $tilesAtThisZoom;
+		$lng = -180 + ($X * $lngWidth);	
+		$latHeightMerc = 1.0 / $tilesAtThisZoom;
+		$topLatMerc = $Y * $latHeightMerc;
+		$bottomLatMerc = $topLatMerc + $latHeightMerc;
+		$bottomLat = (180 / M_PI) * ((2 * atan(exp(M_PI * (1 - (2 * $bottomLatMerc))))) - (M_PI / 2));
+		$topLat = (180 / M_PI) * ((2 * atan(exp(M_PI * (1 - (2 * $topLatMerc))))) - (M_PI / 2));
+		$latHeight = $topLat - $bottomLat;
+		return new Boundary($lng, $bottomLat, $lngWidth, $latHeight);
+	}//getTileRect	
+
+	private static function _toMercatorCoords($lat, $lng) {
+		if ($lng > 180) {
+			$lng -= 360;
+		}
+		$lng /= 360;
+		$lat = asinh(tan(deg2rad($lat)))/M_PI/2;
+		return new Point($lng, $lat);
+	}//_toMercatorCoords
+
+	private static function _toNormalisedMercatorCoords($point) {
+		$point->x += 0.5;
+		$point->y = abs($point->y-0.5);
+		return $point;
+	}//_toNormalisedMercatorCoords
+}//GoogleMapUtility
+
+class Point {
+	public $x,$y;
+	function __construct($x,$y) {
+		$this->x = $x;
+		$this->y = $y;
+	}
+	function __toString() {
+		return "({$this->x},{$this->y})";
+	}
+}//Point
+
+class Boundary {
+	public $x,$y,$width,$height;
+	function __construct($x,$y,$width,$height) {
+		$this->x = $x;
+		$this->y = $y;
+		$this->width = $width;
+		$this->height = $height;
+	}
+	function __toString() {
+		return "({$this->x} x {$this->y},{$this->width},{$this->height})";
+	}
+}//Boundary
+?>

file:b/lib/HeatMap.php (new)
--- /dev/null
+++ b/lib/HeatMap.php
@@ -1,1 +1,275 @@
-
+<?php
+/*
+*DISCLAIMER
+* http://blog.gmapify.fr/create-beautiful-tiled-heat-maps-with-php-and-gd
+*THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES *OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, *INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF *USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT *(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*
+*	@author: Olivier G. <olbibigo_AT_gmail_DOT_com>
+*	@version: 1.0
+*	@history:
+*		1.0	creation
+*/
+	define('PI2', 2*M_PI);
+
+	class HeatMapPoint{
+		public $x,$y;
+		function __construct($x,$y) {
+			$this->x = $x;
+			$this->y = $y;
+		}
+		function __toString() {
+			return "({$this->x},{$this->y})";
+		}
+	}//Point
+
+	class HeatMap{
+		//TRANSPARENCY
+		public static $WITH_ALPHA = 0;
+		public static $WITH_TRANSPARENCY = 1;
+		//GRADIENT STYLE
+		public static $GRADIENT_CLASSIC = 'classic';
+		public static $GRADIENT_FIRE = 'fire';
+		public static $GRADIENT_PGAITCH = 'pgaitch';
+		//GRADIENT MODE (for heatImage)
+		public static $GRADIENT_NO_NEGATE_NO_INTERPOLATE = 0;
+		public static $GRADIENT_NO_NEGATE_INTERPOLATE = 1;
+		public static $GRADIENT_NEGATE_NO_INTERPOLATE = 2;
+		public static $GRADIENT_NEGATE_INTERPOLATE = 3;
+		//NOT PROCESSED PIXEL (for heatImage)
+		public static $KEEP_VALUE = 0;
+		public static $NO_KEEP_VALUE = 1;
+		//CONSTRAINTS
+		private static $MIN_RADIUS = 2;//in px
+		private static $MAX_RADIUS = 50;//in px
+		private static $MAX_IMAGE_SIZE = 10000;//in px
+		
+		//generate an $image_width by $image_height pixels heatmap image of $points
+		public static function createImage($data, $image_width, $image_height, $mode=0, $spot_radius = 30, $dimming = 75, $gradient_name = 'classic'){
+			$_gradient_name = $gradient_name;
+			if(($_gradient_name != self::$GRADIENT_CLASSIC) && ($_gradient_name != self::$GRADIENT_FIRE) && ($_gradient_name != self::$GRADIENT_PGAITCH)){
+				$_gradient_name = self::$GRADIENT_CLASSIC;
+			}
+			$_image_width = min(self::$MAX_IMAGE_SIZE, max(0, intval($image_width)));
+			$_image_height = min(self::$MAX_IMAGE_SIZE, max(0, intval($image_height)));
+			$_spot_radius = min(self::$MAX_RADIUS, max(self::$MIN_RADIUS, intval($spot_radius)));
+			$_dimming = min(255, max(0, intval($dimming)));
+			if(!is_array($data)){
+				return false;
+			}
+			$im = imagecreatetruecolor($_image_width, $_image_height);
+			$white = imagecolorallocate($im, 255, 255, 255);
+			imagefill($im, 0, 0, $white);
+			if(self::$WITH_ALPHA == $mode){
+				imagealphablending($im, false);
+				imagesavealpha($im,true);
+			}
+			//Step 1: create grayscale image
+			foreach($data as $datum){
+				if( (is_array($datum) && (count($datum)==1)) || (!is_array($datum) && ('HeatMapPoint' == get_class($datum)))){//Plot points
+					if('HeatMapPoint' != get_class($datum)){
+						$datum = $datum[0];
+					}
+					self::_drawCircularGradient($im, $datum->x, $datum->y, $_spot_radius, $_dimming);
+				}else if(is_array($datum)){//Draw lines
+					$length = count($datum)-1;
+					for($i=0; $i < $length; ++$i){//Loop through points
+						//Bresenham's algorithm to plot from from $datum[$i] to $datum[$i+1];
+						self::_drawBilinearGradient($im, $datum[$i], $datum[$i+1], $_spot_radius, $_dimming);
+					}
+				}
+			}
+			//Gaussian filter
+			if($_spot_radius >= 30){
+				imagefilter($im, IMG_FILTER_GAUSSIAN_BLUR);
+			}
+			//Step 2: create colored image
+			if(FALSE === ($grad_rgba = self::_createGradient($im, $mode, $_gradient_name))){
+				return FALSE;
+			}
+			$grad_size = count($grad_rgba);
+			for($x=0; $x <$_image_width; ++$x){
+				for($y=0; $y <$_image_height; ++$y){
+					$level = imagecolorat($im, $x, $y) & 0xFF;
+					if( ($level >= 0) && ($level < $grad_size) ){
+						imagesetpixel($im, $x, $y, $grad_rgba[imagecolorat($im, $x, $y) & 0xFF]);
+					}
+				}
+			}
+			if(self::$WITH_TRANSPARENCY == $mode){
+				imagecolortransparent($im, $grad_rgba[count($grad_rgba)-1]);
+			}
+			return $im;
+		}//createImage
+
+		//Heat an image
+		public static function heatImage($filepath, $gradient_name = 'classic', $mode= 0, $min_level=0, $max_level=255, $gradient_interpolate=0, $keep_value=0){
+			$_gradient_name = $gradient_name;
+			if(($_gradient_name != self::$GRADIENT_CLASSIC) && ($_gradient_name != self::$GRADIENT_FIRE) && ($_gradient_name != self::$GRADIENT_PGAITCH)){
+				$_gradient_name = self::$GRADIENT_CLASSIC;
+			}
+			$_min_level = min(255, max(0, intval($min_level)));
+			$_max_level = min(255, max(0, intval($max_level)));
+
+			//try opening jpg first then png then gif format
+			if(FALSE === ($im = @imagecreatefromjpeg($filepath))){
+				if(FALSE === ($im = @imagecreatefrompng($filepath))){
+					if(FALSE === ($im = @imagecreatefromgif($filepath))){
+						return FALSE;
+					}
+				}
+			}
+			if(self::$WITH_ALPHA == $mode){
+				imagealphablending($im, false);
+				imagesavealpha($im,true);
+			}
+			$width = imagesx($im);
+			$height = imagesy($im);	
+			if(FALSE === ($grad_rgba = self::_createGradient($im, $mode, $_gradient_name))){
+				return FALSE;
+			}
+			//Convert to grayscale
+			$grad_size = count($grad_rgba);
+			$level_range = $_max_level - $_min_level;
+			for($x=0; $x <$width; ++$x){
+				for($y=0; $y <$height; ++$y){
+					$rgb = imagecolorat($im, $x, $y);
+					$r = ($rgb >> 16) & 0xFF;
+					$g = ($rgb >> 8) & 0xFF;
+					$b = $rgb & 0xFF;
+					$gray_level = Min(255, Max(0, floor(0.33 * $r + 0.5 * $g + 0.16 * $b)));//between 0 and 255				
+					if( ($gray_level >= $_min_level) && ($gray_level <= $_max_level) ){
+						switch($gradient_interpolate){
+							case self::$GRADIENT_NO_NEGATE_NO_INTERPOLATE:
+								//$_max_level takes related lowest gradient color
+								//$_min_level takes related highest gradient color
+								$value = 255 - $gray_level;
+								break;
+							case self::$GRADIENT_NEGATE_NO_INTERPOLATE:
+								//$_max_level takes related highest gradient color
+								//$_min_level takes related lowest gradient color
+								$value = $gray_level;
+								break;
+							case self::$GRADIENT_NO_NEGATE_INTERPOLATE:
+								//$_max_level takes lowest gradient color
+								//$_min_level takes highest gradient color
+								$value = 255- floor(($gray_level - $_min_level) * $grad_size / $level_range);
+								break;
+							case self::$GRADIENT_NEGATE_INTERPOLATE:
+								//$_max_level takes highest gradient color
+								//$_min_level takes lowest gradient color
+								$value = floor(($gray_level - $_min_level) * $grad_size / $level_range);
+								break;
+							default:
+						}
+						imagesetpixel($im, $x, $y, $grad_rgba[$value]);
+					}else{
+						if(self::$KEEP_VALUE == $keep_value){
+							//Do nothing
+						}else{//self::$NO_KEEP_VALUE
+							imagesetpixel($im, $x, $y, imagecolorallocatealpha($im,0,0,0,0));
+						}
+					}
+				}
+			}			
+			if(self::$WITH_TRANSPARENCY == $mode){
+				imagecolortransparent($im, $grad_rgba[count($grad_rgba)-1]);
+			}
+			return $im;
+		}//heatImage
+		
+		private static function _drawCircularGradient(&$im, $center_x, $center_y, $spot_radius, $dimming){
+			$dirty = array();
+			$ratio = (255 - $dimming) / $spot_radius;
+			for($r=$spot_radius; $r > 0; --$r){
+				$channel = $dimming + $r * $ratio;
+				$angle_step = 0.45/$r; //0.01;
+				//Process pixel by pixel to draw a radial grayscale radient
+				for($angle=0; $angle <= PI2; $angle += $angle_step){
+					$x = floor($center_x + $r*cos($angle));
+					$y = floor($center_y + $r*sin($angle));
+					if(!isset($dirty[$x][$y])){
+						$previous_channel = @imagecolorat($im, $x, $y) & 0xFF;//grayscale so same value
+						$new_channel = Max(0, Min(255,($previous_channel * $channel)/255));
+						imagesetpixel($im, $x, $y, imagecolorallocate($im, $new_channel, $new_channel, $new_channel));
+						$dirty[$x][$y] = 0;
+					}
+				}
+			}
+		}//_drawCircularGradient
+		
+		private static function _drawBilinearGradient(&$im, $point0, $point1, $spot_radius, $dimming){
+			if($point0->x < $point1->x){
+				$x0 = $point0->x;
+				$y0 = $point0->y;
+				$x1 = $point1->x;
+				$y1 = $point1->y;
+			}else{
+				$x0 = $point1->x;
+				$y0 = $point1->y;
+				$x1 = $point0->x;
+				$y1 = $point0->y;
+			}
+
+			if( ($x0==$x1) && ($y0==$y1)){//check if same coordinates
+				return false;
+			}
+			$steep = (abs($y1 - $y0) > abs($x1 - $x0))? true: false;
+			if($steep){
+				list($x0, $y0) = array($y0, $x0);//swap
+				list($x1, $y1) = array($y1, $x1);//swap
+			}
+			if($x0>$x1){
+				list($x0, $x1) = array($x1, $x0);//swap
+				list($y0, $y1) = array($y1, $y0);//swap
+			}
+			$deltax = $x1 - $x0;
+			$deltay = abs($y1 - $y0);
+			$error = $deltax / 2;
+			$y = $y0;
+			if( $y0 < $y1){
+				$ystep = 1; 
+			}else{
+				$ystep = -1;
+			}
+			$step = max(1, floor($spot_radius/ 3));
+			for($x=$x0; $x<=$x1; ++$x){//Loop through x value
+				if(0==(($x-$x0) % $step)){
+					if($steep){
+						self::_drawCircularGradient(&$im, $y, $x, $spot_radius, $dimming);
+					}else{ 
+						self::_drawCircularGradient(&$im, $x, $y, $spot_radius, $dimming);
+					}
+				}
+				$error -= $deltay;
+				if($error<0){
+						$y = $y + $ystep;
+						$error = $error + $deltax;
+				}
+			}		
+		}//_drawBilinearGradient
+		
+		private static function _createGradient($im, $mode, $gradient_name){
+			//create the gradient from an image
+			if(FALSE === ($grad_im = imagecreatefrompng('gradient/'.$gradient_name.'.png'))){
+				return FALSE;
+			}
+			$width_g = imagesx($grad_im);
+			$height_g = imagesy($grad_im);
+			//Get colors along the longest dimension
+			//Max density is for lower channel value
+			for($y=$height_g-1; $y >= 0 ; --$y){
+					$rgb = imagecolorat($grad_im, 1, $y);
+					//Linear function
+					$alpha = Min(127, Max(0, floor(127 - $y/2)));
+					if(self::$WITH_ALPHA == $mode){
+						$grad_rgba[] = imagecolorallocatealpha($im, ($rgb >> 16) & 0xFF, ($rgb >> 8) & 0xFF, $rgb & 0xFF, $alpha);
+					}else{
+						$grad_rgba[] = imagecolorallocate($im, ($rgb >> 16) & 0xFF, ($rgb >> 8) & 0xFF, $rgb & 0xFF);
+					}
+			}
+			imagedestroy($grad_im);
+			unset($grad_im);
+			return($grad_rgba);
+		}//_createGradient
+	}//Heatmap
+?>

--- /dev/null
+++ b/lib/autocomplete.php
@@ -1,1 +1,25 @@
-
+<?php
+include ("../include/common.inc.php");
+$result = Array();
+if (isset($_REQUEST['term'])) {
+	$term = filter_var($_REQUEST['term'], FILTER_SANITIZE_STRING);
+	$query = "Select stop_name,min(stop_lat) as stop_lat,min(stop_lon) as stop_lon from stops where stop_name LIKE :term group by stop_name";
+	$query = $conn->prepare($query);
+	$term = "$term%";
+	$query->bindParam(":term", $term);
+	$query->execute();
+	if (!$query) {
+		databaseError($conn->errorInfo());
+		return Array();
+	}
+	foreach ($query->fetchAll() as $row) {
+		$name = $row['stop_name'] . " (" . $row['stop_lat'] . "," . $row['stop_lon'] . ")";
+		$result[] = Array(
+			"id" => $name,
+			"label" => $name,
+			"value" => $name
+		);
+	}
+}
+echo json_encode($result);
+?>

file:b/lib/postgis.sh (new)
--- /dev/null
+++ b/lib/postgis.sh
@@ -1,1 +1,3 @@
+createlang -d dbname plpgsql
+psql -d transitdata -f postgis.sql
 

file:b/lib/postgis.sql (new)
--- /dev/null
+++ b/lib/postgis.sql
@@ -1,1 +1,7788 @@
-
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+--
+-- $Id: postgis.sql.in.c 5385 2010-03-09 00:22:41Z pramsey $
+--
+-- PostGIS - Spatial Types for PostgreSQL
+-- http://postgis.refractions.net
+-- Copyright 2001-2003 Refractions Research Inc.
+--
+-- This is free software; you can redistribute and/or modify it under
+-- the terms of the GNU General Public Licence. See the COPYING file.
+--
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+--
+-- WARNING: Any change in this file must be evaluated for compatibility.
+--          Changes cleanly handled by postgis_upgrade.sql are fine,
+--	    other changes will require a bump in Major version.
+--	    Currently only function replaceble by CREATE OR REPLACE
+--	    are cleanly handled.
+--
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+-- INSTALL VERSION: 1.5.1
+
+SET client_min_messages TO warning;
+
+BEGIN;
+
+-------------------------------------------------------------------
+--  SPHEROID TYPE
+-------------------------------------------------------------------
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_spheroid_in(cstring)
+	RETURNS spheroid
+	AS '$libdir/postgis-1.5','ellipsoid_in'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_spheroid_out(spheroid)
+	RETURNS cstring
+	AS '$libdir/postgis-1.5','ellipsoid_out'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION spheroid_in(cstring)
+	RETURNS spheroid
+	AS '$libdir/postgis-1.5','ellipsoid_in'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION spheroid_out(spheroid)
+	RETURNS cstring
+	AS '$libdir/postgis-1.5','ellipsoid_out'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE TYPE spheroid (
+	alignment = double,
+	internallength = 65,
+	input = spheroid_in,
+	output = spheroid_out
+);
+
+-------------------------------------------------------------------
+--  GEOMETRY TYPE (lwgeom)
+-------------------------------------------------------------------
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_in(cstring)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_in'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_out(geometry)
+	RETURNS cstring
+	AS '$libdir/postgis-1.5','LWGEOM_out'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_analyze(internal)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_analyze'
+	LANGUAGE 'C' VOLATILE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_recv(internal)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_recv'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_send(geometry)
+	RETURNS bytea
+	AS '$libdir/postgis-1.5','LWGEOM_send'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_in(cstring)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_in'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_out(geometry)
+	RETURNS cstring
+	AS '$libdir/postgis-1.5','LWGEOM_out'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_analyze(internal)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_analyze'
+	LANGUAGE 'C' VOLATILE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_recv(internal)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_recv'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_send(geometry)
+	RETURNS bytea
+	AS '$libdir/postgis-1.5','LWGEOM_send'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE TYPE geometry (
+	internallength = variable,
+	input = geometry_in,
+	output = geometry_out,
+	send = geometry_send,
+	receive = geometry_recv,
+	delimiter = ':',
+	analyze = geometry_analyze,
+	storage = main
+);
+
+-------------------------------------------
+-- Affine transforms
+-------------------------------------------
+
+-- Availability: 1.1.2
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION Affine(geometry,float8,float8,float8,float8,float8,float8,float8,float8,float8,float8,float8,float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_affine'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_Affine(geometry,float8,float8,float8,float8,float8,float8,float8,float8,float8,float8,float8,float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_affine'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.1.2
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION Affine(geometry,float8,float8,float8,float8,float8,float8)
+	RETURNS geometry
+	AS 'SELECT affine($1,  $2, $3, 0,  $4, $5, 0,  0, 0, 1,  $6, $7, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_Affine(geometry,float8,float8,float8,float8,float8,float8)
+	RETURNS geometry
+	AS 'SELECT affine($1,  $2, $3, 0,  $4, $5, 0,  0, 0, 1,  $6, $7, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.1.2
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION RotateZ(geometry,float8)
+	RETURNS geometry
+	AS 'SELECT affine($1,  cos($2), -sin($2), 0,  sin($2), cos($2), 0,  0, 0, 1,  0, 0, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_RotateZ(geometry,float8)
+	RETURNS geometry
+	AS 'SELECT affine($1,  cos($2), -sin($2), 0,  sin($2), cos($2), 0,  0, 0, 1,  0, 0, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.1.2
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION Rotate(geometry,float8)
+	RETURNS geometry
+	AS 'SELECT rotateZ($1, $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_Rotate(geometry,float8)
+	RETURNS geometry
+	AS 'SELECT rotateZ($1, $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.1.2
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION RotateX(geometry,float8)
+	RETURNS geometry
+	AS 'SELECT affine($1, 1, 0, 0, 0, cos($2), -sin($2), 0, sin($2), cos($2), 0, 0, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_RotateX(geometry,float8)
+	RETURNS geometry
+	AS 'SELECT affine($1, 1, 0, 0, 0, cos($2), -sin($2), 0, sin($2), cos($2), 0, 0, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.1.2
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION RotateY(geometry,float8)
+	RETURNS geometry
+	AS 'SELECT affine($1,  cos($2), 0, sin($2),  0, 1, 0,  -sin($2), 0, cos($2), 0,  0, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_RotateY(geometry,float8)
+	RETURNS geometry
+	AS 'SELECT affine($1,  cos($2), 0, sin($2),  0, 1, 0,  -sin($2), 0, cos($2), 0,  0, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION Translate(geometry,float8,float8,float8)
+	RETURNS geometry
+	AS 'SELECT affine($1, 1, 0, 0, 0, 1, 0, 0, 0, 1, $2, $3, $4)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_Translate(geometry,float8,float8,float8)
+	RETURNS geometry
+	AS 'SELECT affine($1, 1, 0, 0, 0, 1, 0, 0, 0, 1, $2, $3, $4)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION Translate(geometry,float8,float8)
+	RETURNS geometry
+	AS 'SELECT translate($1, $2, $3, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_Translate(geometry,float8,float8)
+	RETURNS geometry
+	AS 'SELECT translate($1, $2, $3, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.1.0
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION Scale(geometry,float8,float8,float8)
+	RETURNS geometry
+	AS 'SELECT affine($1,  $2, 0, 0,  0, $3, 0,  0, 0, $4,  0, 0, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_Scale(geometry,float8,float8,float8)
+	RETURNS geometry
+	AS 'SELECT affine($1,  $2, 0, 0,  0, $3, 0,  0, 0, $4,  0, 0, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.1.0
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION Scale(geometry,float8,float8)
+	RETURNS geometry
+	AS 'SELECT scale($1, $2, $3, 1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_Scale(geometry,float8,float8)
+	RETURNS geometry
+	AS 'SELECT scale($1, $2, $3, 1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.1.0
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION transscale(geometry,float8,float8,float8,float8)
+	RETURNS geometry
+	AS 'SELECT affine($1,  $4, 0, 0,  0, $5, 0,
+		0, 0, 1,  $2 * $4, $3 * $5, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_transscale(geometry,float8,float8,float8,float8)
+	RETURNS geometry
+	AS 'SELECT affine($1,  $4, 0, 0,  0, $5, 0,
+		0, 0, 1,  $2 * $4, $3 * $5, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.1.0
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION shift_longitude(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_longitude_shift'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_shift_longitude(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_longitude_shift'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-------------------------------------------------------------------
+--  BOX3D TYPE
+-------------------------------------------------------------------
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_box3d_in(cstring)
+	RETURNS box3d
+	AS '$libdir/postgis-1.5', 'BOX3D_in'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_box3d_out(box3d)
+	RETURNS cstring
+	AS '$libdir/postgis-1.5', 'BOX3D_out'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION box3d_in(cstring)
+	RETURNS box3d
+	AS '$libdir/postgis-1.5', 'BOX3D_in'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION box3d_out(box3d)
+	RETURNS cstring
+	AS '$libdir/postgis-1.5', 'BOX3D_out'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE TYPE box3d (
+	alignment = double,
+	internallength = 48,
+	input = box3d_in,
+	output = box3d_out
+);
+
+-- Temporary box3d aggregate type to retain full double precision
+-- for ST_Extent(). Should be removed when we change the output
+-- type of ST_Extent() to return something other than BOX2DFLOAT4.
+CREATE OR REPLACE FUNCTION box3d_extent_in(cstring)
+	RETURNS box3d_extent
+	AS '$libdir/postgis-1.5', 'BOX3D_in'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION box3d_extent_out(box3d_extent)
+	RETURNS cstring
+	AS '$libdir/postgis-1.5', 'BOX3D_extent_out'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE TYPE box3d_extent (
+	alignment = double,
+	internallength = 48,
+	input = box3d_extent_in,
+	output = box3d_extent_out
+);
+
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION box3d_extent(box3d_extent)
+	RETURNS box3d
+	AS '$libdir/postgis-1.5', 'BOX3D_extent_to_BOX3D'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION box2d(box3d_extent)
+	RETURNS box2d
+	AS '$libdir/postgis-1.5', 'BOX3D_to_BOX2DFLOAT4'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry(box3d_extent)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','BOX3D_to_LWGEOM'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- End of temporary hack
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION xmin(box3d)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','BOX3D_xmin'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_XMin(box3d)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','BOX3D_xmin'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION ymin(box3d)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','BOX3D_ymin'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_YMin(box3d)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','BOX3D_ymin'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION zmin(box3d)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','BOX3D_zmin'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_ZMin(box3d)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','BOX3D_zmin'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION xmax(box3d)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','BOX3D_xmax'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_XMax(box3d)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','BOX3D_xmax'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION ymax(box3d)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','BOX3D_ymax'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_YMax(box3d)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','BOX3D_ymax'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION zmax(box3d)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','BOX3D_zmax'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_ZMax(box3d)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','BOX3D_zmax'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-------------------------------------------------------------------
+--  CHIP TYPE
+-------------------------------------------------------------------
+
+CREATE OR REPLACE FUNCTION chip_in(cstring)
+	RETURNS chip
+	AS '$libdir/postgis-1.5','CHIP_in'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION chip_out(chip)
+	RETURNS cstring
+	AS '$libdir/postgis-1.5','CHIP_out'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION ST_chip_in(cstring)
+	RETURNS chip
+	AS '$libdir/postgis-1.5','CHIP_in'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION ST_chip_out(chip)
+	RETURNS cstring
+	AS '$libdir/postgis-1.5','CHIP_out'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE TYPE chip (
+	alignment = double,
+	internallength = variable,
+	input = chip_in,
+	output = chip_out,
+	storage = extended
+);
+
+-----------------------------------------------------------------------
+-- BOX2D
+-----------------------------------------------------------------------
+
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_box2d_in(cstring)
+	RETURNS box2d
+	AS '$libdir/postgis-1.5','BOX2DFLOAT4_in'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_box2d_out(box2d)
+	RETURNS cstring
+	AS '$libdir/postgis-1.5','BOX2DFLOAT4_out'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+	
+CREATE OR REPLACE FUNCTION box2d_in(cstring)
+	RETURNS box2d
+	AS '$libdir/postgis-1.5','BOX2DFLOAT4_in'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION box2d_out(box2d)
+	RETURNS cstring
+	AS '$libdir/postgis-1.5','BOX2DFLOAT4_out'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE TYPE box2d (
+	internallength = 16,
+	input = box2d_in,
+	output = box2d_out,
+	storage = plain
+);
+
+
+-------------------------------------------------------------------
+-- BTREE indexes
+-------------------------------------------------------------------
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_lt(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'lwgeom_lt'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_le(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'lwgeom_le'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_gt(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'lwgeom_gt'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_ge(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'lwgeom_ge'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_eq(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'lwgeom_eq'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_cmp(geometry, geometry)
+	RETURNS integer
+	AS '$libdir/postgis-1.5', 'lwgeom_cmp'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_lt(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'lwgeom_lt'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_le(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'lwgeom_le'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_gt(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'lwgeom_gt'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_ge(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'lwgeom_ge'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_eq(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'lwgeom_eq'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_cmp(geometry, geometry)
+	RETURNS integer
+	AS '$libdir/postgis-1.5', 'lwgeom_cmp'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+--
+-- Sorting operators for Btree
+--
+
+CREATE OPERATOR < (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_lt,
+	COMMUTATOR = '>', NEGATOR = '>=',
+	RESTRICT = contsel, JOIN = contjoinsel
+);
+
+CREATE OPERATOR <= (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_le,
+	COMMUTATOR = '>=', NEGATOR = '>',
+	RESTRICT = contsel, JOIN = contjoinsel
+);
+
+CREATE OPERATOR = (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_eq,
+	COMMUTATOR = '=', -- we might implement a faster negator here
+	RESTRICT = contsel, JOIN = contjoinsel
+);
+
+CREATE OPERATOR >= (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_ge,
+	COMMUTATOR = '<=', NEGATOR = '<',
+	RESTRICT = contsel, JOIN = contjoinsel
+);
+CREATE OPERATOR > (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_gt,
+	COMMUTATOR = '<', NEGATOR = '<=',
+	RESTRICT = contsel, JOIN = contjoinsel
+);
+
+
+CREATE OPERATOR CLASS btree_geometry_ops
+	DEFAULT FOR TYPE geometry USING btree AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	geometry_cmp (geometry, geometry);
+
+
+
+-------------------------------------------------------------------
+-- GiST indexes
+-------------------------------------------------------------------
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION postgis_gist_sel (internal, oid, internal, int4)
+	RETURNS float8
+	AS '$libdir/postgis-1.5', 'LWGEOM_gist_sel'
+	LANGUAGE 'C';
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION postgis_gist_joinsel(internal, oid, internal, smallint)
+	RETURNS float8
+	AS '$libdir/postgis-1.5', 'LWGEOM_gist_joinsel'
+	LANGUAGE 'C';
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_postgis_gist_sel (internal, oid, internal, int4)
+	RETURNS float8
+	AS '$libdir/postgis-1.5', 'LWGEOM_gist_sel'
+	LANGUAGE 'C';
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_postgis_gist_joinsel(internal, oid, internal, smallint)
+	RETURNS float8
+	AS '$libdir/postgis-1.5', 'LWGEOM_gist_joinsel'
+	LANGUAGE 'C';
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_overleft(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_overleft'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_overright(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_overright'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_overabove(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_overabove'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_overbelow(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_overbelow'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_left(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_left'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_right(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_right'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_above(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_above'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_below(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_below'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_contain(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_contain'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_contained(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_contained'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_overlap(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_overlap'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry_same(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_samebox'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION geometry_same(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_samebox'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_gist_sel (internal, oid, internal, int4)
+	RETURNS float8
+	AS '$libdir/postgis-1.5', 'LWGEOM_gist_sel'
+	LANGUAGE 'C';
+
+CREATE OR REPLACE FUNCTION geometry_gist_joinsel(internal, oid, internal, smallint)
+	RETURNS float8
+	AS '$libdir/postgis-1.5', 'LWGEOM_gist_joinsel'
+	LANGUAGE 'C';
+
+CREATE OR REPLACE FUNCTION geometry_overleft(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_overleft'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_overright(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_overright'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_overabove(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_overabove'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_overbelow(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_overbelow'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_left(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_left'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_right(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_right'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_above(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_above'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_below(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_below'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_contain(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_contain'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_contained(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_contained'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_overlap(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_overlap'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry_samebox(geometry, geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_samebox'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OPERATOR << (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_left,
+	COMMUTATOR = '>>',
+	RESTRICT = positionsel, JOIN = positionjoinsel
+);
+
+CREATE OPERATOR &< (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_overleft,
+	COMMUTATOR = '&>',
+	RESTRICT = positionsel, JOIN = positionjoinsel
+);
+
+CREATE OPERATOR <<| (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_below,
+	COMMUTATOR = '|>>',
+	RESTRICT = positionsel, JOIN = positionjoinsel
+);
+
+CREATE OPERATOR &<| (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_overbelow,
+	COMMUTATOR = '|&>',
+	RESTRICT = positionsel, JOIN = positionjoinsel
+);
+
+CREATE OPERATOR && (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_overlap,
+	COMMUTATOR = '&&',
+	RESTRICT = geometry_gist_sel, JOIN = geometry_gist_joinsel
+);
+
+CREATE OPERATOR &> (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_overright,
+	COMMUTATOR = '&<',
+	RESTRICT = positionsel, JOIN = positionjoinsel
+);
+
+CREATE OPERATOR >> (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_right,
+	COMMUTATOR = '<<',
+	RESTRICT = positionsel, JOIN = positionjoinsel
+);
+
+CREATE OPERATOR |&> (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_overabove,
+	COMMUTATOR = '&<|',
+	RESTRICT = positionsel, JOIN = positionjoinsel
+);
+
+CREATE OPERATOR |>> (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_above,
+	COMMUTATOR = '<<|',
+	RESTRICT = positionsel, JOIN = positionjoinsel
+);
+
+CREATE OPERATOR ~= (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_samebox,
+	COMMUTATOR = '~=',
+	RESTRICT = eqsel, JOIN = eqjoinsel
+);
+
+CREATE OPERATOR @ (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_contained,
+	COMMUTATOR = '~',
+	RESTRICT = contsel, JOIN = contjoinsel
+);
+
+CREATE OPERATOR ~ (
+	LEFTARG = geometry, RIGHTARG = geometry, PROCEDURE = geometry_contain,
+	COMMUTATOR = '@',
+	RESTRICT = contsel, JOIN = contjoinsel
+);
+
+-- gist support functions
+
+CREATE OR REPLACE FUNCTION LWGEOM_gist_consistent(internal,geometry,int4)
+	RETURNS bool
+	AS '$libdir/postgis-1.5' ,'LWGEOM_gist_consistent'
+	LANGUAGE 'C';
+
+CREATE OR REPLACE FUNCTION LWGEOM_gist_compress(internal)
+	RETURNS internal
+	AS '$libdir/postgis-1.5','LWGEOM_gist_compress'
+	LANGUAGE 'C';
+
+CREATE OR REPLACE FUNCTION LWGEOM_gist_penalty(internal,internal,internal)
+	RETURNS internal
+	AS '$libdir/postgis-1.5' ,'LWGEOM_gist_penalty'
+	LANGUAGE 'C';
+
+CREATE OR REPLACE FUNCTION LWGEOM_gist_picksplit(internal, internal)
+	RETURNS internal
+	AS '$libdir/postgis-1.5' ,'LWGEOM_gist_picksplit'
+	LANGUAGE 'C';
+
+CREATE OR REPLACE FUNCTION LWGEOM_gist_union(bytea, internal)
+	RETURNS internal
+	AS '$libdir/postgis-1.5' ,'LWGEOM_gist_union'
+	LANGUAGE 'C';
+
+CREATE OR REPLACE FUNCTION LWGEOM_gist_same(box2d, box2d, internal)
+	RETURNS internal
+	AS '$libdir/postgis-1.5' ,'LWGEOM_gist_same'
+	LANGUAGE 'C';
+
+CREATE OR REPLACE FUNCTION LWGEOM_gist_decompress(internal)
+	RETURNS internal
+	AS '$libdir/postgis-1.5' ,'LWGEOM_gist_decompress'
+	LANGUAGE 'C';
+
+-------------------------------------------
+-- GIST opclass index binding entries.
+-------------------------------------------
+--
+-- Create opclass index bindings for PG>=73
+--
+
+CREATE OPERATOR CLASS gist_geometry_ops
+	DEFAULT FOR TYPE geometry USING gist AS
+	STORAGE 	box2d,
+	OPERATOR        1        << 	,
+	OPERATOR        2        &<	,
+	OPERATOR        3        &&	,
+	OPERATOR        4        &>	,
+	OPERATOR        5        >>	,
+	OPERATOR        6        ~=	,
+	OPERATOR        7        ~	,
+	OPERATOR        8        @	,
+	OPERATOR	9	 &<|	,
+	OPERATOR	10	 <<|	,
+	OPERATOR	11	 |>>	,
+	OPERATOR	12	 |&>	,
+	FUNCTION        1        LWGEOM_gist_consistent (internal, geometry, int4),
+	FUNCTION        2        LWGEOM_gist_union (bytea, internal),
+	FUNCTION        3        LWGEOM_gist_compress (internal),
+	FUNCTION        4        LWGEOM_gist_decompress (internal),
+	FUNCTION        5        LWGEOM_gist_penalty (internal, internal, internal),
+	FUNCTION        6        LWGEOM_gist_picksplit (internal, internal),
+	FUNCTION        7        LWGEOM_gist_same (box2d, box2d, internal);
+
+-------------------------------------------
+-- other lwgeom functions
+-------------------------------------------
+
+CREATE OR REPLACE FUNCTION addbbox(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_addBBOX'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION postgis_addbbox(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_addBBOX'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION dropbbox(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_dropBBOX'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION postgis_dropbbox(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_dropBBOX'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION getsrid(geometry)
+	RETURNS int4
+	AS '$libdir/postgis-1.5','LWGEOM_getSRID'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION getbbox(geometry)
+	RETURNS box2d
+	AS '$libdir/postgis-1.5','LWGEOM_to_BOX2DFLOAT4'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION postgis_getbbox(geometry)
+	RETURNS box2d
+	AS '$libdir/postgis-1.5','LWGEOM_to_BOX2DFLOAT4'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION hasbbox(geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_hasBBOX'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION postgis_hasbbox(geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_hasBBOX'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-------------------------------------------
+--- CHIP functions
+-------------------------------------------
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION srid(chip)
+	RETURNS int4
+	AS '$libdir/postgis-1.5','CHIP_getSRID'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_srid(chip)
+	RETURNS int4
+	AS '$libdir/postgis-1.5','CHIP_getSRID'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION height(chip)
+	RETURNS int4
+	AS '$libdir/postgis-1.5','CHIP_getHeight'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_height(chip)
+	RETURNS int4
+	AS '$libdir/postgis-1.5','CHIP_getHeight'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION factor(chip)
+	RETURNS FLOAT4
+	AS '$libdir/postgis-1.5','CHIP_getFactor'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_factor(chip)
+	RETURNS FLOAT4
+	AS '$libdir/postgis-1.5','CHIP_getFactor'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION width(chip)
+	RETURNS int4
+	AS '$libdir/postgis-1.5','CHIP_getWidth'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_width(chip)
+	RETURNS int4
+	AS '$libdir/postgis-1.5','CHIP_getWidth'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION datatype(chip)
+	RETURNS int4
+	AS '$libdir/postgis-1.5','CHIP_getDatatype'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_datatype(chip)
+	RETURNS int4
+	AS '$libdir/postgis-1.5','CHIP_getDatatype'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION compression(chip)
+	RETURNS int4
+	AS '$libdir/postgis-1.5','CHIP_getCompression'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_compression(chip)
+	RETURNS int4
+	AS '$libdir/postgis-1.5','CHIP_getCompression'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION setSRID(chip,int4)
+	RETURNS chip
+	AS '$libdir/postgis-1.5','CHIP_setSRID'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION setFactor(chip,float4)
+	RETURNS chip
+	AS '$libdir/postgis-1.5','CHIP_setFactor'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_setFactor(chip,float4)
+	RETURNS chip
+	AS '$libdir/postgis-1.5','CHIP_setFactor'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+------------------------------------------------------------------------
+-- DEBUG
+------------------------------------------------------------------------
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION mem_size(geometry)
+	RETURNS int4
+	AS '$libdir/postgis-1.5', 'LWGEOM_mem_size'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_mem_size(geometry)
+	RETURNS int4
+	AS '$libdir/postgis-1.5', 'LWGEOM_mem_size'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION summary(geometry)
+	RETURNS text
+	AS '$libdir/postgis-1.5', 'LWGEOM_summary'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_summary(geometry)
+	RETURNS text
+	AS '$libdir/postgis-1.5', 'LWGEOM_summary'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION npoints(geometry)
+	RETURNS int4
+	AS '$libdir/postgis-1.5', 'LWGEOM_npoints'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_npoints(geometry)
+	RETURNS int4
+	AS '$libdir/postgis-1.5', 'LWGEOM_npoints'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION nrings(geometry)
+	RETURNS int4
+	AS '$libdir/postgis-1.5', 'LWGEOM_nrings'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_nrings(geometry)
+	RETURNS int4
+	AS '$libdir/postgis-1.5', 'LWGEOM_nrings'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+------------------------------------------------------------------------
+-- Misures
+------------------------------------------------------------------------
+
+-- this is a fake (for back-compatibility)
+-- uses 3d if 3d is available, 2d otherwise
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION length3d(geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5', 'LWGEOM_length_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_length3d(geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5', 'LWGEOM_length_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION length2d(geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5', 'LWGEOM_length2d_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_length2d(geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5', 'LWGEOM_length2d_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION length(geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5', 'LWGEOM_length_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: length2d(geometry)
+CREATE OR REPLACE FUNCTION ST_Length(geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5', 'LWGEOM_length2d_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- this is a fake (for back-compatibility)
+-- uses 3d if 3d is available, 2d otherwise
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION length3d_spheroid(geometry, spheroid)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','LWGEOM_length_ellipsoid_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_length3d_spheroid(geometry, spheroid)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','LWGEOM_length_ellipsoid_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION length_spheroid(geometry, spheroid)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','LWGEOM_length_ellipsoid_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_length_spheroid(geometry, spheroid)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','LWGEOM_length_ellipsoid_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION length2d_spheroid(geometry, spheroid)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','LWGEOM_length2d_ellipsoid'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_length2d_spheroid(geometry, spheroid)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','LWGEOM_length2d_ellipsoid'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- this is a fake (for back-compatibility)
+-- uses 3d if 3d is available, 2d otherwise
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION perimeter3d(geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5', 'LWGEOM_perimeter_poly'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_perimeter3d(geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5', 'LWGEOM_perimeter_poly'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION perimeter2d(geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5', 'LWGEOM_perimeter2d_poly'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_perimeter2d(geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5', 'LWGEOM_perimeter2d_poly'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION perimeter(geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5', 'LWGEOM_perimeter_poly'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: perimeter2d(geometry)
+CREATE OR REPLACE FUNCTION ST_Perimeter(geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5', 'LWGEOM_perimeter2d_poly'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- this is an alias for 'area(geometry)'
+-- there is nothing such an 'area3d'...
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION area2d(geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5', 'LWGEOM_area_polygon'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+-- Deprecation in 1.3.4
+CREATE OR REPLACE FUNCTION ST_area2d(geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5', 'LWGEOM_area_polygon'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION area(geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','LWGEOM_area_polygon'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: area(geometry)
+CREATE OR REPLACE FUNCTION ST_Area(geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','LWGEOM_area_polygon'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION distance_spheroid(geometry,geometry,spheroid)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','LWGEOM_distance_ellipsoid'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_distance_spheroid(geometry,geometry,spheroid)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','LWGEOM_distance_ellipsoid'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION distance_sphere(geometry,geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','LWGEOM_distance_sphere'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_distance_sphere(geometry,geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5','LWGEOM_distance_sphere'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Minimum distance. 2d only.
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION distance(geometry,geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5', 'LWGEOM_mindistance2d'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- PostGIS equivalent function: distance(geometry,geometry)
+CREATE OR REPLACE FUNCTION ST_Distance(geometry,geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5', 'LWGEOM_mindistance2d'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION point_inside_circle(geometry,float8,float8,float8)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_inside_circle_point'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_point_inside_circle(geometry,float8,float8,float8)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_inside_circle_point'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION azimuth(geometry,geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5', 'LWGEOM_azimuth'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_azimuth(geometry,geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5', 'LWGEOM_azimuth'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+------------------------------------------------------------------------
+-- MISC
+------------------------------------------------------------------------
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION force_2d(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_force_2d'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_force_2d(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_force_2d'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION force_3dz(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_force_3dz'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_force_3dz(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_force_3dz'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- an alias for force_3dz
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION force_3d(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_force_3dz'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_force_3d(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_force_3dz'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION force_3dm(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_force_3dm'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_force_3dm(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_force_3dm'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION force_4d(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_force_4d'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_force_4d(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_force_4d'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION force_collection(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_force_collection'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_force_collection(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_force_collection'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_CollectionExtract(geometry, integer)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'ST_CollectionExtract'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION multi(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_force_multi'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_multi(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_force_multi'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION expand(box3d,float8)
+	RETURNS box3d
+	AS '$libdir/postgis-1.5', 'BOX3D_expand'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_Expand(box3d,float8)
+	RETURNS box3d
+	AS '$libdir/postgis-1.5', 'BOX3D_expand'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION expand(box2d,float8)
+	RETURNS box2d
+	AS '$libdir/postgis-1.5', 'BOX2DFLOAT4_expand'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_expand(box2d,float8)
+	RETURNS box2d
+	AS '$libdir/postgis-1.5', 'BOX2DFLOAT4_expand'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION expand(geometry,float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_expand'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_expand(geometry,float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_expand'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION envelope(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_envelope'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: envelope(geometry)
+CREATE OR REPLACE FUNCTION ST_Envelope(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_envelope'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION reverse(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_reverse'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_Reverse(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_reverse'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION ForceRHR(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_forceRHR_poly'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_ForceRHR(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_forceRHR_poly'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION noop(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_noop'
+	LANGUAGE 'C' VOLATILE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION postgis_noop(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_noop'
+	LANGUAGE 'C' VOLATILE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION zmflag(geometry)
+	RETURNS smallint
+	AS '$libdir/postgis-1.5', 'LWGEOM_zmflag'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION ST_zmflag(geometry)
+	RETURNS smallint
+	AS '$libdir/postgis-1.5', 'LWGEOM_zmflag'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION ndims(geometry)
+	RETURNS smallint
+	AS '$libdir/postgis-1.5', 'LWGEOM_ndims'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_NDims(geometry)
+	RETURNS smallint
+	AS '$libdir/postgis-1.5', 'LWGEOM_ndims'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AsEWKT(geometry)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','LWGEOM_asEWKT'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_AsEWKT(geometry)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','LWGEOM_asEWKT'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AsEWKB(geometry)
+	RETURNS BYTEA
+	AS '$libdir/postgis-1.5','WKBFromLWGEOM'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_AsEWKB(geometry)
+	RETURNS BYTEA
+	AS '$libdir/postgis-1.5','WKBFromLWGEOM'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AsHEXEWKB(geometry)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','LWGEOM_asHEXEWKB'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_AsHEXEWKB(geometry)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','LWGEOM_asHEXEWKB'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AsHEXEWKB(geometry, text)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','LWGEOM_asHEXEWKB'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_AsHEXEWKB(geometry, text)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','LWGEOM_asHEXEWKB'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AsEWKB(geometry,text)
+	RETURNS bytea
+	AS '$libdir/postgis-1.5','WKBFromLWGEOM'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_AsEWKB(geometry,text)
+	RETURNS bytea
+	AS '$libdir/postgis-1.5','WKBFromLWGEOM'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION GeomFromEWKB(bytea)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOMFromWKB'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_GeomFromEWKB(bytea)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOMFromWKB'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION GeomFromEWKT(text)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','parse_WKT_lwgeom'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_GeomFromEWKT(text)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','parse_WKT_lwgeom'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION postgis_cache_bbox()
+	RETURNS trigger
+	AS '$libdir/postgis-1.5', 'cache_bbox'
+	LANGUAGE 'C';
+
+------------------------------------------------------------------------
+-- CONSTRUCTORS
+------------------------------------------------------------------------
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MakePoint(float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makepoint'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MakePoint(float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makepoint'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MakePoint(float8, float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makepoint'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MakePoint(float8, float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makepoint'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MakePoint(float8, float8, float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makepoint'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MakePoint(float8, float8, float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makepoint'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MakePointM(float8, float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makepoint3dm'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.3.4
+CREATE OR REPLACE FUNCTION ST_MakePointM(float8, float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makepoint3dm'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MakeBox2d(geometry, geometry)
+	RETURNS box2d
+	AS '$libdir/postgis-1.5', 'BOX2DFLOAT4_construct'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MakeBox2d(geometry, geometry)
+	RETURNS box2d
+	AS '$libdir/postgis-1.5', 'BOX2DFLOAT4_construct'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MakeBox3d(geometry, geometry)
+	RETURNS box3d
+	AS '$libdir/postgis-1.5', 'BOX3D_construct'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MakeBox3d(geometry, geometry)
+	RETURNS box3d
+	AS '$libdir/postgis-1.5', 'BOX3D_construct'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION makeline_garray (geometry[])
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makeline_garray'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MakeLine_GArray (geometry[])
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makeline_garray'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION ST_MakeLine (geometry[])
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makeline_garray'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION LineFromMultiPoint(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_line_from_mpoint'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_LineFromMultiPoint(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_line_from_mpoint'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MakeLine(geometry, geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makeline'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MakeLine(geometry, geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makeline'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AddPoint(geometry, geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_addpoint'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_AddPoint(geometry, geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_addpoint'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AddPoint(geometry, geometry, integer)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_addpoint'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_AddPoint(geometry, geometry, integer)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_addpoint'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION RemovePoint(geometry, integer)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_removepoint'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_RemovePoint(geometry, integer)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_removepoint'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION SetPoint(geometry, integer, geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_setpoint_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_SetPoint(geometry, integer, geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_setpoint_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_MakeEnvelope(float8, float8, float8, float8, integer)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'ST_MakeEnvelope'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MakePolygon(geometry, geometry[])
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makepoly'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MakePolygon(geometry, geometry[])
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makepoly'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MakePolygon(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makepoly'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MakePolygon(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makepoly'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION BuildArea(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_buildarea'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_BuildArea(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_buildarea'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION Polygonize_GArray (geometry[])
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'polygonize_garray'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION ST_Polygonize_GArray (geometry[])
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'polygonize_garray'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION ST_Polygonize (geometry[])
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'polygonize_garray'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION LineMerge(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'linemerge'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_LineMerge(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'linemerge'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+
+CREATE TYPE geometry_dump AS (path integer[], geom geometry);
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION Dump(geometry)
+	RETURNS SETOF geometry_dump
+	AS '$libdir/postgis-1.5', 'LWGEOM_dump'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_Dump(geometry)
+	RETURNS SETOF geometry_dump
+	AS '$libdir/postgis-1.5', 'LWGEOM_dump'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION DumpRings(geometry)
+	RETURNS SETOF geometry_dump
+	AS '$libdir/postgis-1.5', 'LWGEOM_dump_rings'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_DumpRings(geometry)
+	RETURNS SETOF geometry_dump
+	AS '$libdir/postgis-1.5', 'LWGEOM_dump_rings'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-----------------------------------------------------------------------
+-- _ST_DumpPoints()
+-----------------------------------------------------------------------
+-- A helper function for ST_DumpPoints(geom)
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION _ST_DumpPoints(the_geom geometry, cur_path integer[]) RETURNS SETOF geometry_dump AS $$
+DECLARE
+  tmp geometry_dump;
+  tmp2 geometry_dump;
+  nb_points integer;
+  nb_geom integer;
+  i integer;
+  j integer;
+  g geometry;
+  
+BEGIN
+  
+  RAISE DEBUG '%,%', cur_path, ST_GeometryType(the_geom);
+
+  -- Special case (MULTI* OR GEOMETRYCOLLECTION) : iterate and return the DumpPoints of the geometries
+  SELECT ST_NumGeometries(the_geom) INTO nb_geom;
+
+  IF (nb_geom IS NOT NULL) THEN
+    
+    i = 1;
+    FOR tmp2 IN SELECT (ST_Dump(the_geom)).* LOOP
+
+      FOR tmp IN SELECT * FROM _ST_DumpPoints(tmp2.geom, cur_path || tmp2.path) LOOP
+	    RETURN NEXT tmp;
+      END LOOP;
+      i = i + 1;
+      
+    END LOOP;
+
+    RETURN;
+  END IF;
+  
+
+  -- Special case (POLYGON) : return the points of the rings of a polygon
+  IF (ST_GeometryType(the_geom) = 'ST_Polygon') THEN
+
+    FOR tmp IN SELECT * FROM _ST_DumpPoints(ST_ExteriorRing(the_geom), cur_path || ARRAY[1]) LOOP
+      RETURN NEXT tmp;
+    END LOOP;
+    
+    j := ST_NumInteriorRings(the_geom);
+    FOR i IN 1..j LOOP
+        FOR tmp IN SELECT * FROM _ST_DumpPoints(ST_InteriorRingN(the_geom, i), cur_path || ARRAY[i+1]) LOOP
+          RETURN NEXT tmp;
+        END LOOP;
+    END LOOP;
+    
+    RETURN;
+  END IF;
+
+    
+  -- Special case (POINT) : return the point
+  IF (ST_GeometryType(the_geom) = 'ST_Point') THEN
+
+    tmp.path = cur_path || ARRAY[1];
+    tmp.geom = the_geom;
+
+    RETURN NEXT tmp;
+    RETURN;
+
+  END IF;
+
+
+  -- Use ST_NumPoints rather than ST_NPoints to have a NULL value if the_geom isn't
+  -- a LINESTRING or CIRCULARSTRING.
+  SELECT ST_NumPoints(the_geom) INTO nb_points;
+
+  -- This should never happen
+  IF (nb_points IS NULL) THEN
+    RAISE EXCEPTION 'Unexpected error while dumping geometry %', ST_AsText(the_geom);
+  END IF;
+
+  FOR i IN 1..nb_points LOOP
+    tmp.path = cur_path || ARRAY[i];
+    tmp.geom := ST_PointN(the_geom, i);
+    RETURN NEXT tmp;
+  END LOOP;
+   
+END
+$$ LANGUAGE plpgsql;
+
+-----------------------------------------------------------------------
+-- ST_DumpPoints()
+-----------------------------------------------------------------------
+-- This function mimicks that of ST_Dump for collections, but this function 
+-- that returns a path and all the points that make up a particular geometry.
+-- This current implementation in plpgsql does not scale very well at all.
+-- and should be ported to C at some point.
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_DumpPoints(geometry) RETURNS SETOF geometry_dump AS $$
+  SELECT * FROM _ST_DumpPoints($1, NULL);
+$$ LANGUAGE SQL;
+
+
+------------------------------------------------------------------------
+
+--
+-- Aggregate functions
+--
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION combine_bbox(box2d,geometry)
+	RETURNS box2d
+	AS '$libdir/postgis-1.5', 'BOX2DFLOAT4_combine'
+	LANGUAGE 'C' IMMUTABLE;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_Combine_BBox(box2d,geometry)
+	RETURNS box2d
+	AS '$libdir/postgis-1.5', 'BOX2DFLOAT4_combine'
+	LANGUAGE 'C' IMMUTABLE;
+
+-- Temporary hack function
+CREATE OR REPLACE FUNCTION combine_bbox(box3d_extent,geometry)
+	RETURNS box3d_extent
+	AS '$libdir/postgis-1.5', 'BOX3D_combine'
+	LANGUAGE 'C' IMMUTABLE;
+
+-- Temporary hack function
+CREATE OR REPLACE FUNCTION ST_Combine_BBox(box3d_extent,geometry)
+	RETURNS box3d_extent
+	AS '$libdir/postgis-1.5', 'BOX3D_combine'
+	LANGUAGE 'C' IMMUTABLE;
+
+-- Deprecation in 1.2.3
+CREATE AGGREGATE Extent(
+	sfunc = ST_combine_bbox,
+	basetype = geometry,
+	stype = box3d_extent
+	);
+
+-- Availability: 1.2.2
+CREATE AGGREGATE ST_Extent(
+	sfunc = ST_combine_bbox,
+	basetype = geometry,
+	stype = box3d_extent
+	);
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION combine_bbox(box3d,geometry)
+	RETURNS box3d
+	AS '$libdir/postgis-1.5', 'BOX3D_combine'
+	LANGUAGE 'C' IMMUTABLE;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_Combine_BBox(box3d,geometry)
+	RETURNS box3d
+	AS '$libdir/postgis-1.5', 'BOX3D_combine'
+	LANGUAGE 'C' IMMUTABLE;
+
+-- Deprecation in 1.2.3
+CREATE AGGREGATE Extent3d(
+	sfunc = combine_bbox,
+	basetype = geometry,
+	stype = box3d
+	);
+
+-- Availability: 1.2.2
+CREATE AGGREGATE ST_Extent3d(
+	sfunc = ST_combine_bbox,
+	basetype = geometry,
+	stype = box3d
+	);
+
+-----------------------------------------------------------------------
+-- ESTIMATED_EXTENT( <schema name>, <table name>, <column name> )
+-----------------------------------------------------------------------
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION estimated_extent(text,text,text) RETURNS box2d AS
+	'$libdir/postgis-1.5', 'LWGEOM_estimated_extent'
+	LANGUAGE 'C' IMMUTABLE STRICT SECURITY DEFINER;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_estimated_extent(text,text,text) RETURNS box2d AS
+	'$libdir/postgis-1.5', 'LWGEOM_estimated_extent'
+	LANGUAGE 'C' IMMUTABLE STRICT SECURITY DEFINER;
+
+-----------------------------------------------------------------------
+-- ESTIMATED_EXTENT( <table name>, <column name> )
+-----------------------------------------------------------------------
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION estimated_extent(text,text) RETURNS box2d AS
+	'$libdir/postgis-1.5', 'LWGEOM_estimated_extent'
+	LANGUAGE 'C' IMMUTABLE STRICT SECURITY DEFINER;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_estimated_extent(text,text) RETURNS box2d AS
+	'$libdir/postgis-1.5', 'LWGEOM_estimated_extent'
+	LANGUAGE 'C' IMMUTABLE STRICT SECURITY DEFINER;
+
+-----------------------------------------------------------------------
+-- FIND_EXTENT( <schema name>, <table name>, <column name> )
+-----------------------------------------------------------------------
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION find_extent(text,text,text) RETURNS box2d AS
+$$
+DECLARE
+	schemaname alias for $1;
+	tablename alias for $2;
+	columnname alias for $3;
+	myrec RECORD;
+
+BEGIN
+	FOR myrec IN EXECUTE 'SELECT extent("' || columnname || '") FROM "' || schemaname || '"."' || tablename || '"' LOOP
+		return myrec.extent;
+	END LOOP;
+END;
+$$
+LANGUAGE 'plpgsql' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_find_extent(text,text,text) RETURNS box2d AS
+$$
+DECLARE
+	schemaname alias for $1;
+	tablename alias for $2;
+	columnname alias for $3;
+	myrec RECORD;
+
+BEGIN
+	FOR myrec IN EXECUTE 'SELECT extent("' || columnname || '") FROM "' || schemaname || '"."' || tablename || '"' LOOP
+		return myrec.extent;
+	END LOOP;
+END;
+$$
+LANGUAGE 'plpgsql' IMMUTABLE STRICT;
+
+
+-----------------------------------------------------------------------
+-- FIND_EXTENT( <table name>, <column name> )
+-----------------------------------------------------------------------
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION find_extent(text,text) RETURNS box2d AS
+$$
+DECLARE
+	tablename alias for $1;
+	columnname alias for $2;
+	myrec RECORD;
+
+BEGIN
+	FOR myrec IN EXECUTE 'SELECT extent("' || columnname || '") FROM "' || tablename || '"' LOOP
+		return myrec.extent;
+	END LOOP;
+END;
+$$
+LANGUAGE 'plpgsql' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_find_extent(text,text) RETURNS box2d AS
+$$
+DECLARE
+	tablename alias for $1;
+	columnname alias for $2;
+	myrec RECORD;
+
+BEGIN
+	FOR myrec IN EXECUTE 'SELECT extent("' || columnname || '") FROM "' || tablename || '"' LOOP
+		return myrec.extent;
+	END LOOP;
+END;
+$$
+LANGUAGE 'plpgsql' IMMUTABLE STRICT;
+
+-------------------------------------------------------------------
+-- SPATIAL_REF_SYS
+-------------------------------------------------------------------
+CREATE TABLE spatial_ref_sys (
+	 srid integer not null primary key,
+	 auth_name varchar(256),
+	 auth_srid integer,
+	 srtext varchar(2048),
+	 proj4text varchar(2048)
+);
+
+-------------------------------------------------------------------
+-- GEOMETRY_COLUMNS
+-------------------------------------------------------------------
+CREATE TABLE geometry_columns (
+	f_table_catalog varchar(256) not null,
+	f_table_schema varchar(256) not null,
+	f_table_name varchar(256) not null,
+	f_geometry_column varchar(256) not null,
+	coord_dimension integer not null,
+	srid integer not null,
+	type varchar(30) not null,
+	CONSTRAINT geometry_columns_pk primary key (
+		f_table_catalog,
+		f_table_schema,
+		f_table_name,
+		f_geometry_column )
+) WITH OIDS;
+
+-----------------------------------------------------------------------
+-- RENAME_GEOMETRY_TABLE_CONSTRAINTS()
+-----------------------------------------------------------------------
+-- This function has been obsoleted for the difficulty in
+-- finding attribute on which the constraint is applied.
+-- AddGeometryColumn will name the constraints in a meaningful
+-- way, but nobody can rely on it since old postgis versions did
+-- not do that.
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION rename_geometry_table_constraints() RETURNS text
+AS
+$$
+SELECT 'rename_geometry_table_constraint() is obsoleted'::text
+$$
+LANGUAGE 'SQL' IMMUTABLE;
+
+-----------------------------------------------------------------------
+-- FIX_GEOMETRY_COLUMNS()
+-----------------------------------------------------------------------
+-- This function will:
+--
+--	o try to fix the schema of records with an integer one
+--		(for PG>=73)
+--
+--	o link records to system tables through attrelid and varattnum
+--		(for PG<75)
+--
+--	o delete all records for which no linking was possible
+--		(for PG<75)
+--
+--
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION fix_geometry_columns() RETURNS text
+AS
+$$
+DECLARE
+	mislinked record;
+	result text;
+	linked integer;
+	deleted integer;
+	foundschema integer;
+BEGIN
+
+	-- Since 7.3 schema support has been added.
+	-- Previous postgis versions used to put the database name in
+	-- the schema column. This needs to be fixed, so we try to
+	-- set the correct schema for each geometry_colums record
+	-- looking at table, column, type and srid.
+	UPDATE geometry_columns SET f_table_schema = n.nspname
+		FROM pg_namespace n, pg_class c, pg_attribute a,
+			pg_constraint sridcheck, pg_constraint typecheck
+			WHERE ( f_table_schema is NULL
+		OR f_table_schema = ''
+			OR f_table_schema NOT IN (
+					SELECT nspname::varchar
+					FROM pg_namespace nn, pg_class cc, pg_attribute aa
+					WHERE cc.relnamespace = nn.oid
+					AND cc.relname = f_table_name::name
+					AND aa.attrelid = cc.oid
+					AND aa.attname = f_geometry_column::name))
+			AND f_table_name::name = c.relname
+			AND c.oid = a.attrelid
+			AND c.relnamespace = n.oid
+			AND f_geometry_column::name = a.attname
+
+			AND sridcheck.conrelid = c.oid
+		AND sridcheck.consrc LIKE '(srid(% = %)'
+			AND sridcheck.consrc ~ textcat(' = ', srid::text)
+
+			AND typecheck.conrelid = c.oid
+		AND typecheck.consrc LIKE
+		'((geometrytype(%) = ''%''::text) OR (% IS NULL))'
+			AND typecheck.consrc ~ textcat(' = ''', type::text)
+
+			AND NOT EXISTS (
+					SELECT oid FROM geometry_columns gc
+					WHERE c.relname::varchar = gc.f_table_name
+					AND n.nspname::varchar = gc.f_table_schema
+					AND a.attname::varchar = gc.f_geometry_column
+			);
+
+	GET DIAGNOSTICS foundschema = ROW_COUNT;
+
+	-- no linkage to system table needed
+	return 'fixed:'||foundschema::text;
+
+END;
+$$
+LANGUAGE 'plpgsql' VOLATILE;
+
+-----------------------------------------------------------------------
+-- POPULATE_GEOMETRY_COLUMNS()
+-----------------------------------------------------------------------
+-- Truncates and refills the geometry_columns table from all tables and
+-- views in the database that contain geometry columns. This function
+-- is a simple wrapper for populate_geometry_columns(oid).  In essence,
+-- this function ensures every geometry column in the database has the
+-- appropriate spatial contraints (for tables) and exists in the
+-- geometry_columns table.
+-- Availability: 1.4.0
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION populate_geometry_columns()
+	RETURNS text AS
+$$
+DECLARE
+	inserted    integer;
+	oldcount    integer;
+	probed      integer;
+	stale       integer;
+	gcs         RECORD;
+	gc          RECORD;
+	gsrid       integer;
+	gndims      integer;
+	gtype       text;
+	query       text;
+	gc_is_valid boolean;
+
+BEGIN
+	SELECT count(*) INTO oldcount FROM geometry_columns;
+	inserted := 0;
+
+	EXECUTE 'TRUNCATE geometry_columns';
+
+	-- Count the number of geometry columns in all tables and views
+	SELECT count(DISTINCT c.oid) INTO probed
+	FROM pg_class c,
+		 pg_attribute a,
+		 pg_type t,
+		 pg_namespace n
+	WHERE (c.relkind = 'r' OR c.relkind = 'v')
+	AND t.typname = 'geometry'
+	AND a.attisdropped = false
+	AND a.atttypid = t.oid
+	AND a.attrelid = c.oid
+	AND c.relnamespace = n.oid
+	AND n.nspname NOT ILIKE 'pg_temp%';
+
+	-- Iterate through all non-dropped geometry columns
+	RAISE DEBUG 'Processing Tables.....';
+
+	FOR gcs IN
+	SELECT DISTINCT ON (c.oid) c.oid, n.nspname, c.relname
+		FROM pg_class c,
+			 pg_attribute a,
+			 pg_type t,
+			 pg_namespace n
+		WHERE c.relkind = 'r'
+		AND t.typname = 'geometry'
+		AND a.attisdropped = false
+		AND a.atttypid = t.oid
+		AND a.attrelid = c.oid
+		AND c.relnamespace = n.oid
+		AND n.nspname NOT ILIKE 'pg_temp%'
+	LOOP
+
+	inserted := inserted + populate_geometry_columns(gcs.oid);
+	END LOOP;
+
+	-- Add views to geometry columns table
+	RAISE DEBUG 'Processing Views.....';
+	FOR gcs IN
+	SELECT DISTINCT ON (c.oid) c.oid, n.nspname, c.relname
+		FROM pg_class c,
+			 pg_attribute a,
+			 pg_type t,
+			 pg_namespace n
+		WHERE c.relkind = 'v'
+		AND t.typname = 'geometry'
+		AND a.attisdropped = false
+		AND a.atttypid = t.oid
+		AND a.attrelid = c.oid
+		AND c.relnamespace = n.oid
+	LOOP
+
+	inserted := inserted + populate_geometry_columns(gcs.oid);
+	END LOOP;
+
+	IF oldcount > inserted THEN
+	stale = oldcount-inserted;
+	ELSE
+	stale = 0;
+	END IF;
+
+	RETURN 'probed:' ||probed|| ' inserted:'||inserted|| ' conflicts:'||probed-inserted|| ' deleted:'||stale;
+END
+
+$$
+LANGUAGE 'plpgsql' VOLATILE;
+
+-----------------------------------------------------------------------
+-- POPULATE_GEOMETRY_COLUMNS(tbl_oid oid)
+-----------------------------------------------------------------------
+-- DELETEs from and reINSERTs into the geometry_columns table all entries
+-- associated with the oid of a particular table or view.
+--
+-- If the provided oid is for a table, this function tries to determine
+-- the srid, dimension, and geometry type of the all geometries
+-- in the table, adding contraints as necessary to the table.  If
+-- successful, an appropriate row is inserted into the geometry_columns
+-- table, otherwise, the exception is caught and an error notice is
+-- raised describing the problem. (This is so the wrapper function
+-- populate_geometry_columns() can apply spatial constraints to all
+-- geometry columns across an entire database at once without erroring
+-- out)
+--
+-- If the provided oid is for a view, as with a table oid, this function
+-- tries to determine the srid, dimension, and type of all the geometries
+-- in the view, inserting appropriate entries into the geometry_columns
+-- table.
+-- Availability: 1.4.0
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION populate_geometry_columns(tbl_oid oid)
+	RETURNS integer AS
+$$
+DECLARE
+	gcs         RECORD;
+	gc          RECORD;
+	gsrid       integer;
+	gndims      integer;
+	gtype       text;
+	query       text;
+	gc_is_valid boolean;
+	inserted    integer;
+
+BEGIN
+	inserted := 0;
+
+	-- Iterate through all geometry columns in this table
+	FOR gcs IN
+	SELECT n.nspname, c.relname, a.attname
+		FROM pg_class c,
+			 pg_attribute a,
+			 pg_type t,
+			 pg_namespace n
+		WHERE c.relkind = 'r'
+		AND t.typname = 'geometry'
+		AND a.attisdropped = false
+		AND a.atttypid = t.oid
+		AND a.attrelid = c.oid
+		AND c.relnamespace = n.oid
+		AND n.nspname NOT ILIKE 'pg_temp%'
+		AND c.oid = tbl_oid
+	LOOP
+
+	RAISE DEBUG 'Processing table %.%.%', gcs.nspname, gcs.relname, gcs.attname;
+
+	DELETE FROM geometry_columns
+	  WHERE f_table_schema = quote_ident(gcs.nspname)
+	  AND f_table_name = quote_ident(gcs.relname)
+	  AND f_geometry_column = quote_ident(gcs.attname);
+
+	gc_is_valid := true;
+
+	-- Try to find srid check from system tables (pg_constraint)
+	gsrid :=
+		(SELECT replace(replace(split_part(s.consrc, ' = ', 2), ')', ''), '(', '')
+		 FROM pg_class c, pg_namespace n, pg_attribute a, pg_constraint s
+		 WHERE n.nspname = gcs.nspname
+		 AND c.relname = gcs.relname
+		 AND a.attname = gcs.attname
+		 AND a.attrelid = c.oid
+		 AND s.connamespace = n.oid
+		 AND s.conrelid = c.oid
+		 AND a.attnum = ANY (s.conkey)
+		 AND s.consrc LIKE '%srid(% = %');
+	IF (gsrid IS NULL) THEN
+		-- Try to find srid from the geometry itself
+		EXECUTE 'SELECT srid(' || quote_ident(gcs.attname) || ')
+				 FROM ONLY ' || quote_ident(gcs.nspname) || '.' || quote_ident(gcs.relname) || '
+				 WHERE ' || quote_ident(gcs.attname) || ' IS NOT NULL LIMIT 1'
+			INTO gc;
+		gsrid := gc.srid;
+
+		-- Try to apply srid check to column
+		IF (gsrid IS NOT NULL) THEN
+			BEGIN
+				EXECUTE 'ALTER TABLE ONLY ' || quote_ident(gcs.nspname) || '.' || quote_ident(gcs.relname) || '
+						 ADD CONSTRAINT ' || quote_ident('enforce_srid_' || gcs.attname) || '
+						 CHECK (srid(' || quote_ident(gcs.attname) || ') = ' || gsrid || ')';
+			EXCEPTION
+				WHEN check_violation THEN
+					RAISE WARNING 'Not inserting ''%'' in ''%.%'' into geometry_columns: could not apply constraint CHECK (srid(%) = %)', quote_ident(gcs.attname), quote_ident(gcs.nspname), quote_ident(gcs.relname), quote_ident(gcs.attname), gsrid;
+					gc_is_valid := false;
+			END;
+		END IF;
+	END IF;
+
+	-- Try to find ndims check from system tables (pg_constraint)
+	gndims :=
+		(SELECT replace(split_part(s.consrc, ' = ', 2), ')', '')
+		 FROM pg_class c, pg_namespace n, pg_attribute a, pg_constraint s
+		 WHERE n.nspname = gcs.nspname
+		 AND c.relname = gcs.relname
+		 AND a.attname = gcs.attname
+		 AND a.attrelid = c.oid
+		 AND s.connamespace = n.oid
+		 AND s.conrelid = c.oid
+		 AND a.attnum = ANY (s.conkey)
+		 AND s.consrc LIKE '%ndims(% = %');
+	IF (gndims IS NULL) THEN
+		-- Try to find ndims from the geometry itself
+		EXECUTE 'SELECT ndims(' || quote_ident(gcs.attname) || ')
+				 FROM ONLY ' || quote_ident(gcs.nspname) || '.' || quote_ident(gcs.relname) || '
+				 WHERE ' || quote_ident(gcs.attname) || ' IS NOT NULL LIMIT 1'
+			INTO gc;
+		gndims := gc.ndims;
+
+		-- Try to apply ndims check to column
+		IF (gndims IS NOT NULL) THEN
+			BEGIN
+				EXECUTE 'ALTER TABLE ONLY ' || quote_ident(gcs.nspname) || '.' || quote_ident(gcs.relname) || '
+						 ADD CONSTRAINT ' || quote_ident('enforce_dims_' || gcs.attname) || '
+						 CHECK (ndims(' || quote_ident(gcs.attname) || ') = '||gndims||')';
+			EXCEPTION
+				WHEN check_violation THEN
+					RAISE WARNING 'Not inserting ''%'' in ''%.%'' into geometry_columns: could not apply constraint CHECK (ndims(%) = %)', quote_ident(gcs.attname), quote_ident(gcs.nspname), quote_ident(gcs.relname), quote_ident(gcs.attname), gndims;
+					gc_is_valid := false;
+			END;
+		END IF;
+	END IF;
+
+	-- Try to find geotype check from system tables (pg_constraint)
+	gtype :=
+		(SELECT replace(split_part(s.consrc, '''', 2), ')', '')
+		 FROM pg_class c, pg_namespace n, pg_attribute a, pg_constraint s
+		 WHERE n.nspname = gcs.nspname
+		 AND c.relname = gcs.relname
+		 AND a.attname = gcs.attname
+		 AND a.attrelid = c.oid
+		 AND s.connamespace = n.oid
+		 AND s.conrelid = c.oid
+		 AND a.attnum = ANY (s.conkey)
+		 AND s.consrc LIKE '%geometrytype(% = %');
+	IF (gtype IS NULL) THEN
+		-- Try to find geotype from the geometry itself
+		EXECUTE 'SELECT geometrytype(' || quote_ident(gcs.attname) || ')
+				 FROM ONLY ' || quote_ident(gcs.nspname) || '.' || quote_ident(gcs.relname) || '
+				 WHERE ' || quote_ident(gcs.attname) || ' IS NOT NULL LIMIT 1'
+			INTO gc;
+		gtype := gc.geometrytype;
+		--IF (gtype IS NULL) THEN
+		--    gtype := 'GEOMETRY';
+		--END IF;
+
+		-- Try to apply geometrytype check to column
+		IF (gtype IS NOT NULL) THEN
+			BEGIN
+				EXECUTE 'ALTER TABLE ONLY ' || quote_ident(gcs.nspname) || '.' || quote_ident(gcs.relname) || '
+				ADD CONSTRAINT ' || quote_ident('enforce_geotype_' || gcs.attname) || '
+				CHECK ((geometrytype(' || quote_ident(gcs.attname) || ') = ' || quote_literal(gtype) || ') OR (' || quote_ident(gcs.attname) || ' IS NULL))';
+			EXCEPTION
+				WHEN check_violation THEN
+					-- No geometry check can be applied. This column contains a number of geometry types.
+					RAISE WARNING 'Could not add geometry type check (%) to table column: %.%.%', gtype, quote_ident(gcs.nspname),quote_ident(gcs.relname),quote_ident(gcs.attname);
+			END;
+		END IF;
+	END IF;
+
+	IF (gsrid IS NULL) THEN
+		RAISE WARNING 'Not inserting ''%'' in ''%.%'' into geometry_columns: could not determine the srid', quote_ident(gcs.attname), quote_ident(gcs.nspname), quote_ident(gcs.relname);
+	ELSIF (gndims IS NULL) THEN
+		RAISE WARNING 'Not inserting ''%'' in ''%.%'' into geometry_columns: could not determine the number of dimensions', quote_ident(gcs.attname), quote_ident(gcs.nspname), quote_ident(gcs.relname);
+	ELSIF (gtype IS NULL) THEN
+		RAISE WARNING 'Not inserting ''%'' in ''%.%'' into geometry_columns: could not determine the geometry type', quote_ident(gcs.attname), quote_ident(gcs.nspname), quote_ident(gcs.relname);
+	ELSE
+		-- Only insert into geometry_columns if table constraints could be applied.
+		IF (gc_is_valid) THEN
+			INSERT INTO geometry_columns (f_table_catalog,f_table_schema, f_table_name, f_geometry_column, coord_dimension, srid, type)
+			VALUES ('', gcs.nspname, gcs.relname, gcs.attname, gndims, gsrid, gtype);
+			inserted := inserted + 1;
+		END IF;
+	END IF;
+	END LOOP;
+
+	-- Add views to geometry columns table
+	FOR gcs IN
+	SELECT n.nspname, c.relname, a.attname
+		FROM pg_class c,
+			 pg_attribute a,
+			 pg_type t,
+			 pg_namespace n
+		WHERE c.relkind = 'v'
+		AND t.typname = 'geometry'
+		AND a.attisdropped = false
+		AND a.atttypid = t.oid
+		AND a.attrelid = c.oid
+		AND c.relnamespace = n.oid
+		AND n.nspname NOT ILIKE 'pg_temp%'
+		AND c.oid = tbl_oid
+	LOOP
+		RAISE DEBUG 'Processing view %.%.%', gcs.nspname, gcs.relname, gcs.attname;
+
+		EXECUTE 'SELECT ndims(' || quote_ident(gcs.attname) || ')
+				 FROM ' || quote_ident(gcs.nspname) || '.' || quote_ident(gcs.relname) || '
+				 WHERE ' || quote_ident(gcs.attname) || ' IS NOT NULL LIMIT 1'
+			INTO gc;
+		gndims := gc.ndims;
+
+		EXECUTE 'SELECT srid(' || quote_ident(gcs.attname) || ')
+				 FROM ' || quote_ident(gcs.nspname) || '.' || quote_ident(gcs.relname) || '
+				 WHERE ' || quote_ident(gcs.attname) || ' IS NOT NULL LIMIT 1'
+			INTO gc;
+		gsrid := gc.srid;
+
+		EXECUTE 'SELECT geometrytype(' || quote_ident(gcs.attname) || ')
+				 FROM ' || quote_ident(gcs.nspname) || '.' || quote_ident(gcs.relname) || '
+				 WHERE ' || quote_ident(gcs.attname) || ' IS NOT NULL LIMIT 1'
+			INTO gc;
+		gtype := gc.geometrytype;
+
+		IF (gndims IS NULL) THEN
+			RAISE WARNING 'Not inserting ''%'' in ''%.%'' into geometry_columns: could not determine ndims', quote_ident(gcs.attname), quote_ident(gcs.nspname), quote_ident(gcs.relname);
+		ELSIF (gsrid IS NULL) THEN
+			RAISE WARNING 'Not inserting ''%'' in ''%.%'' into geometry_columns: could not determine srid', quote_ident(gcs.attname), quote_ident(gcs.nspname), quote_ident(gcs.relname);
+		ELSIF (gtype IS NULL) THEN
+			RAISE WARNING 'Not inserting ''%'' in ''%.%'' into geometry_columns: could not determine gtype', quote_ident(gcs.attname), quote_ident(gcs.nspname), quote_ident(gcs.relname);
+		ELSE
+			query := 'INSERT INTO geometry_columns (f_table_catalog,f_table_schema, f_table_name, f_geometry_column, coord_dimension, srid, type) ' ||
+					 'VALUES ('''', ' || quote_literal(gcs.nspname) || ',' || quote_literal(gcs.relname) || ',' || quote_literal(gcs.attname) || ',' || gndims || ',' || gsrid || ',' || quote_literal(gtype) || ')';
+			EXECUTE query;
+			inserted := inserted + 1;
+		END IF;
+	END LOOP;
+
+	RETURN inserted;
+END
+
+$$
+LANGUAGE 'plpgsql' VOLATILE;
+
+
+-----------------------------------------------------------------------
+-- PROBE_GEOMETRY_COLUMNS()
+-----------------------------------------------------------------------
+-- Fill the geometry_columns table with values probed from the system
+-- catalogues. This is done by simply looking up constraints previously
+-- added to a geometry column. If geometry constraints are missing, no
+-- attempt is made to add the necessary constraints to the geometry
+-- column, nor is it recorded in the geometry_columns table.
+-- 3d flag cannot be probed, it defaults to 2
+--
+-- Note that bogus records already in geometry_columns are not
+-- overridden (a check for schema.table.column is performed), so
+-- to have a fresh probe backup your geometry_columns, delete from
+-- it and probe.
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION probe_geometry_columns() RETURNS text AS
+$$
+DECLARE
+	inserted integer;
+	oldcount integer;
+	probed integer;
+	stale integer;
+BEGIN
+
+	SELECT count(*) INTO oldcount FROM geometry_columns;
+
+	SELECT count(*) INTO probed
+		FROM pg_class c, pg_attribute a, pg_type t,
+			pg_namespace n,
+			pg_constraint sridcheck, pg_constraint typecheck
+
+		WHERE t.typname = 'geometry'
+		AND a.atttypid = t.oid
+		AND a.attrelid = c.oid
+		AND c.relnamespace = n.oid
+		AND sridcheck.connamespace = n.oid
+		AND typecheck.connamespace = n.oid
+		AND sridcheck.conrelid = c.oid
+		AND sridcheck.consrc LIKE '(srid('||a.attname||') = %)'
+		AND typecheck.conrelid = c.oid
+		AND typecheck.consrc LIKE
+		'((geometrytype('||a.attname||') = ''%''::text) OR (% IS NULL))'
+		;
+
+	INSERT INTO geometry_columns SELECT
+		''::varchar as f_table_catalogue,
+		n.nspname::varchar as f_table_schema,
+		c.relname::varchar as f_table_name,
+		a.attname::varchar as f_geometry_column,
+		2 as coord_dimension,
+		trim(both  ' =)' from
+			replace(replace(split_part(
+				sridcheck.consrc, ' = ', 2), ')', ''), '(', ''))::integer AS srid,
+		trim(both ' =)''' from substr(typecheck.consrc,
+			strpos(typecheck.consrc, '='),
+			strpos(typecheck.consrc, '::')-
+			strpos(typecheck.consrc, '=')
+			))::varchar as type
+		FROM pg_class c, pg_attribute a, pg_type t,
+			pg_namespace n,
+			pg_constraint sridcheck, pg_constraint typecheck
+		WHERE t.typname = 'geometry'
+		AND a.atttypid = t.oid
+		AND a.attrelid = c.oid
+		AND c.relnamespace = n.oid
+		AND sridcheck.connamespace = n.oid
+		AND typecheck.connamespace = n.oid
+		AND sridcheck.conrelid = c.oid
+		AND sridcheck.consrc LIKE '(st_srid('||a.attname||') = %)'
+		AND typecheck.conrelid = c.oid
+		AND typecheck.consrc LIKE
+		'((geometrytype('||a.attname||') = ''%''::text) OR (% IS NULL))'
+
+			AND NOT EXISTS (
+					SELECT oid FROM geometry_columns gc
+					WHERE c.relname::varchar = gc.f_table_name
+					AND n.nspname::varchar = gc.f_table_schema
+					AND a.attname::varchar = gc.f_geometry_column
+			);
+
+	GET DIAGNOSTICS inserted = ROW_COUNT;
+
+	IF oldcount > probed THEN
+		stale = oldcount-probed;
+	ELSE
+		stale = 0;
+	END IF;
+
+	RETURN 'probed:'||probed::text||
+		' inserted:'||inserted::text||
+		' conflicts:'||(probed-inserted)::text||
+		' stale:'||stale::text;
+END
+
+$$
+LANGUAGE 'plpgsql' VOLATILE;
+
+-----------------------------------------------------------------------
+-- ADDGEOMETRYCOLUMN
+--   <catalogue>, <schema>, <table>, <column>, <srid>, <type>, <dim>
+-----------------------------------------------------------------------
+--
+-- Type can be one of GEOMETRY, GEOMETRYCOLLECTION, POINT, MULTIPOINT, POLYGON,
+-- MULTIPOLYGON, LINESTRING, or MULTILINESTRING.
+--
+-- Geometry types (except GEOMETRY) are checked for consistency using a CHECK constraint.
+-- Uses an ALTER TABLE command to add the geometry column to the table.
+-- Addes a row to geometry_columns.
+-- Addes a constraint on the table that all the geometries MUST have the same
+-- SRID. Checks the coord_dimension to make sure its between 0 and 3.
+-- Should also check the precision grid (future expansion).
+--
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION AddGeometryColumn(varchar,varchar,varchar,varchar,integer,varchar,integer)
+	RETURNS text
+	AS
+$$
+DECLARE
+	catalog_name alias for $1;
+	schema_name alias for $2;
+	table_name alias for $3;
+	column_name alias for $4;
+	new_srid alias for $5;
+	new_type alias for $6;
+	new_dim alias for $7;
+	rec RECORD;
+	sr varchar;
+	real_schema name;
+	sql text;
+
+BEGIN
+
+	-- Verify geometry type
+	IF ( NOT ( (new_type = 'GEOMETRY') OR
+			   (new_type = 'GEOMETRYCOLLECTION') OR
+			   (new_type = 'POINT') OR
+			   (new_type = 'MULTIPOINT') OR
+			   (new_type = 'POLYGON') OR
+			   (new_type = 'MULTIPOLYGON') OR
+			   (new_type = 'LINESTRING') OR
+			   (new_type = 'MULTILINESTRING') OR
+			   (new_type = 'GEOMETRYCOLLECTIONM') OR
+			   (new_type = 'POINTM') OR
+			   (new_type = 'MULTIPOINTM') OR
+			   (new_type = 'POLYGONM') OR
+			   (new_type = 'MULTIPOLYGONM') OR
+			   (new_type = 'LINESTRINGM') OR
+			   (new_type = 'MULTILINESTRINGM') OR
+			   (new_type = 'CIRCULARSTRING') OR
+			   (new_type = 'CIRCULARSTRINGM') OR
+			   (new_type = 'COMPOUNDCURVE') OR
+			   (new_type = 'COMPOUNDCURVEM') OR
+			   (new_type = 'CURVEPOLYGON') OR
+			   (new_type = 'CURVEPOLYGONM') OR
+			   (new_type = 'MULTICURVE') OR
+			   (new_type = 'MULTICURVEM') OR
+			   (new_type = 'MULTISURFACE') OR
+			   (new_type = 'MULTISURFACEM')) )
+	THEN
+		RAISE EXCEPTION 'Invalid type name - valid ones are:
+	POINT, MULTIPOINT,
+	LINESTRING, MULTILINESTRING,
+	POLYGON, MULTIPOLYGON,
+	CIRCULARSTRING, COMPOUNDCURVE, MULTICURVE,
+	CURVEPOLYGON, MULTISURFACE,
+	GEOMETRY, GEOMETRYCOLLECTION,
+	POINTM, MULTIPOINTM,
+	LINESTRINGM, MULTILINESTRINGM,
+	POLYGONM, MULTIPOLYGONM,
+	CIRCULARSTRINGM, COMPOUNDCURVEM, MULTICURVEM
+	CURVEPOLYGONM, MULTISURFACEM,
+	or GEOMETRYCOLLECTIONM';
+		RETURN 'fail';
+	END IF;
+
+
+	-- Verify dimension
+	IF ( (new_dim >4) OR (new_dim <0) ) THEN
+		RAISE EXCEPTION 'invalid dimension';
+		RETURN 'fail';
+	END IF;
+
+	IF ( (new_type LIKE '%M') AND (new_dim!=3) ) THEN
+		RAISE EXCEPTION 'TypeM needs 3 dimensions';
+		RETURN 'fail';
+	END IF;
+
+
+	-- Verify SRID
+	IF ( new_srid != -1 ) THEN
+		SELECT SRID INTO sr FROM spatial_ref_sys WHERE SRID = new_srid;
+		IF NOT FOUND THEN
+			RAISE EXCEPTION 'AddGeometryColumns() - invalid SRID';
+			RETURN 'fail';
+		END IF;
+	END IF;
+
+
+	-- Verify schema
+	IF ( schema_name IS NOT NULL AND schema_name != '' ) THEN
+		sql := 'SELECT nspname FROM pg_namespace ' ||
+			'WHERE text(nspname) = ' || quote_literal(schema_name) ||
+			'LIMIT 1';
+		RAISE DEBUG '%', sql;
+		EXECUTE sql INTO real_schema;
+
+		IF ( real_schema IS NULL ) THEN
+			RAISE EXCEPTION 'Schema % is not a valid schemaname', quote_literal(schema_name);
+			RETURN 'fail';
+		END IF;
+	END IF;
+
+	IF ( real_schema IS NULL ) THEN
+		RAISE DEBUG 'Detecting schema';
+		sql := 'SELECT n.nspname AS schemaname ' ||
+			'FROM pg_catalog.pg_class c ' ||
+			  'JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace ' ||
+			'WHERE c.relkind = ' || quote_literal('r') ||
+			' AND n.nspname NOT IN (' || quote_literal('pg_catalog') || ', ' || quote_literal('pg_toast') || ')' ||
+			' AND pg_catalog.pg_table_is_visible(c.oid)' ||
+			' AND c.relname = ' || quote_literal(table_name);
+		RAISE DEBUG '%', sql;
+		EXECUTE sql INTO real_schema;
+
+		IF ( real_schema IS NULL ) THEN
+			RAISE EXCEPTION 'Table % does not occur in the search_path', quote_literal(table_name);
+			RETURN 'fail';
+		END IF;
+	END IF;
+
+
+	-- Add geometry column to table
+	sql := 'ALTER TABLE ' ||
+		quote_ident(real_schema) || '.' || quote_ident(table_name)
+		|| ' ADD COLUMN ' || quote_ident(column_name) ||
+		' geometry ';
+	RAISE DEBUG '%', sql;
+	EXECUTE sql;
+
+
+	-- Delete stale record in geometry_columns (if any)
+	sql := 'DELETE FROM geometry_columns WHERE
+		f_table_catalog = ' || quote_literal('') ||
+		' AND f_table_schema = ' ||
+		quote_literal(real_schema) ||
+		' AND f_table_name = ' || quote_literal(table_name) ||
+		' AND f_geometry_column = ' || quote_literal(column_name);
+	RAISE DEBUG '%', sql;
+	EXECUTE sql;
+
+
+	-- Add record in geometry_columns
+	sql := 'INSERT INTO geometry_columns (f_table_catalog,f_table_schema,f_table_name,' ||
+										  'f_geometry_column,coord_dimension,srid,type)' ||
+		' VALUES (' ||
+		quote_literal('') || ',' ||
+		quote_literal(real_schema) || ',' ||
+		quote_literal(table_name) || ',' ||
+		quote_literal(column_name) || ',' ||
+		new_dim::text || ',' ||
+		new_srid::text || ',' ||
+		quote_literal(new_type) || ')';
+	RAISE DEBUG '%', sql;
+	EXECUTE sql;
+
+
+	-- Add table CHECKs
+	sql := 'ALTER TABLE ' ||
+		quote_ident(real_schema) || '.' || quote_ident(table_name)
+		|| ' ADD CONSTRAINT '
+		|| quote_ident('enforce_srid_' || column_name)
+		|| ' CHECK (ST_SRID(' || quote_ident(column_name) ||
+		') = ' || new_srid::text || ')' ;
+	RAISE DEBUG '%', sql;
+	EXECUTE sql;
+
+	sql := 'ALTER TABLE ' ||
+		quote_ident(real_schema) || '.' || quote_ident(table_name)
+		|| ' ADD CONSTRAINT '
+		|| quote_ident('enforce_dims_' || column_name)
+		|| ' CHECK (ST_NDims(' || quote_ident(column_name) ||
+		') = ' || new_dim::text || ')' ;
+	RAISE DEBUG '%', sql;
+	EXECUTE sql;
+
+	IF ( NOT (new_type = 'GEOMETRY')) THEN
+		sql := 'ALTER TABLE ' ||
+			quote_ident(real_schema) || '.' || quote_ident(table_name) || ' ADD CONSTRAINT ' ||
+			quote_ident('enforce_geotype_' || column_name) ||
+			' CHECK (GeometryType(' ||
+			quote_ident(column_name) || ')=' ||
+			quote_literal(new_type) || ' OR (' ||
+			quote_ident(column_name) || ') is null)';
+		RAISE DEBUG '%', sql;
+		EXECUTE sql;
+	END IF;
+
+	RETURN
+		real_schema || '.' ||
+		table_name || '.' || column_name ||
+		' SRID:' || new_srid::text ||
+		' TYPE:' || new_type ||
+		' DIMS:' || new_dim::text || ' ';
+END;
+$$
+LANGUAGE 'plpgsql' VOLATILE STRICT;
+
+----------------------------------------------------------------------------
+-- ADDGEOMETRYCOLUMN ( <schema>, <table>, <column>, <srid>, <type>, <dim> )
+----------------------------------------------------------------------------
+--
+-- This is a wrapper to the real AddGeometryColumn, for use
+-- when catalogue is undefined
+--
+----------------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION AddGeometryColumn(varchar,varchar,varchar,integer,varchar,integer) RETURNS text AS $$
+DECLARE
+	ret  text;
+BEGIN
+	SELECT AddGeometryColumn('',$1,$2,$3,$4,$5,$6) into ret;
+	RETURN ret;
+END;
+$$
+LANGUAGE 'plpgsql' STABLE STRICT;
+
+----------------------------------------------------------------------------
+-- ADDGEOMETRYCOLUMN ( <table>, <column>, <srid>, <type>, <dim> )
+----------------------------------------------------------------------------
+--
+-- This is a wrapper to the real AddGeometryColumn, for use
+-- when catalogue and schema are undefined
+--
+----------------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION AddGeometryColumn(varchar,varchar,integer,varchar,integer) RETURNS text AS $$
+DECLARE
+	ret  text;
+BEGIN
+	SELECT AddGeometryColumn('','',$1,$2,$3,$4,$5) into ret;
+	RETURN ret;
+END;
+$$
+LANGUAGE 'plpgsql' VOLATILE STRICT;
+
+-----------------------------------------------------------------------
+-- DROPGEOMETRYCOLUMN
+--   <catalogue>, <schema>, <table>, <column>
+-----------------------------------------------------------------------
+--
+-- Removes geometry column reference from geometry_columns table.
+-- Drops the column with pgsql >= 73.
+-- Make some silly enforcements on it for pgsql < 73
+--
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION DropGeometryColumn(varchar, varchar,varchar,varchar)
+	RETURNS text
+	AS
+$$
+DECLARE
+	catalog_name alias for $1;
+	schema_name alias for $2;
+	table_name alias for $3;
+	column_name alias for $4;
+	myrec RECORD;
+	okay boolean;
+	real_schema name;
+
+BEGIN
+
+
+	-- Find, check or fix schema_name
+	IF ( schema_name != '' ) THEN
+		okay = 'f';
+
+		FOR myrec IN SELECT nspname FROM pg_namespace WHERE text(nspname) = schema_name LOOP
+			okay := 't';
+		END LOOP;
+
+		IF ( okay <> 't' ) THEN
+			RAISE NOTICE 'Invalid schema name - using current_schema()';
+			SELECT current_schema() into real_schema;
+		ELSE
+			real_schema = schema_name;
+		END IF;
+	ELSE
+		SELECT current_schema() into real_schema;
+	END IF;
+
+	-- Find out if the column is in the geometry_columns table
+	okay = 'f';
+	FOR myrec IN SELECT * from geometry_columns where f_table_schema = text(real_schema) and f_table_name = table_name and f_geometry_column = column_name LOOP
+		okay := 't';
+	END LOOP;
+	IF (okay <> 't') THEN
+		RAISE EXCEPTION 'column not found in geometry_columns table';
+		RETURN 'f';
+	END IF;
+
+	-- Remove ref from geometry_columns table
+	EXECUTE 'delete from geometry_columns where f_table_schema = ' ||
+		quote_literal(real_schema) || ' and f_table_name = ' ||
+		quote_literal(table_name)  || ' and f_geometry_column = ' ||
+		quote_literal(column_name);
+
+	-- Remove table column
+	EXECUTE 'ALTER TABLE ' || quote_ident(real_schema) || '.' ||
+		quote_ident(table_name) || ' DROP COLUMN ' ||
+		quote_ident(column_name);
+
+	RETURN real_schema || '.' || table_name || '.' || column_name ||' effectively removed.';
+
+END;
+$$
+LANGUAGE 'plpgsql' VOLATILE STRICT;
+
+-----------------------------------------------------------------------
+-- DROPGEOMETRYCOLUMN
+--   <schema>, <table>, <column>
+-----------------------------------------------------------------------
+--
+-- This is a wrapper to the real DropGeometryColumn, for use
+-- when catalogue is undefined
+--
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION DropGeometryColumn(varchar,varchar,varchar)
+	RETURNS text
+	AS
+$$
+DECLARE
+	ret text;
+BEGIN
+	SELECT DropGeometryColumn('',$1,$2,$3) into ret;
+	RETURN ret;
+END;
+$$
+LANGUAGE 'plpgsql' VOLATILE STRICT;
+
+-----------------------------------------------------------------------
+-- DROPGEOMETRYCOLUMN
+--   <table>, <column>
+-----------------------------------------------------------------------
+--
+-- This is a wrapper to the real DropGeometryColumn, for use
+-- when catalogue and schema is undefined.
+--
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION DropGeometryColumn(varchar,varchar)
+	RETURNS text
+	AS
+$$
+DECLARE
+	ret text;
+BEGIN
+	SELECT DropGeometryColumn('','',$1,$2) into ret;
+	RETURN ret;
+END;
+$$
+LANGUAGE 'plpgsql' VOLATILE STRICT;
+
+-----------------------------------------------------------------------
+-- DROPGEOMETRYTABLE
+--   <catalogue>, <schema>, <table>
+-----------------------------------------------------------------------
+--
+-- Drop a table and all its references in geometry_columns
+--
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION DropGeometryTable(varchar, varchar,varchar)
+	RETURNS text
+	AS
+$$
+DECLARE
+	catalog_name alias for $1;
+	schema_name alias for $2;
+	table_name alias for $3;
+	real_schema name;
+
+BEGIN
+
+	IF ( schema_name = '' ) THEN
+		SELECT current_schema() into real_schema;
+	ELSE
+		real_schema = schema_name;
+	END IF;
+
+	-- Remove refs from geometry_columns table
+	EXECUTE 'DELETE FROM geometry_columns WHERE ' ||
+		'f_table_schema = ' || quote_literal(real_schema) ||
+		' AND ' ||
+		' f_table_name = ' || quote_literal(table_name);
+
+	-- Remove table
+	EXECUTE 'DROP TABLE '
+		|| quote_ident(real_schema) || '.' ||
+		quote_ident(table_name);
+
+	RETURN
+		real_schema || '.' ||
+		table_name ||' dropped.';
+
+END;
+$$
+LANGUAGE 'plpgsql' VOLATILE STRICT;
+
+-----------------------------------------------------------------------
+-- DROPGEOMETRYTABLE
+--   <schema>, <table>
+-----------------------------------------------------------------------
+--
+-- Drop a table and all its references in geometry_columns
+--
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION DropGeometryTable(varchar,varchar) RETURNS text AS
+$$ SELECT DropGeometryTable('',$1,$2) $$
+LANGUAGE 'sql' WITH (isstrict);
+
+-----------------------------------------------------------------------
+-- DROPGEOMETRYTABLE
+--   <table>
+-----------------------------------------------------------------------
+--
+-- Drop a table and all its references in geometry_columns
+-- For PG>=73 use current_schema()
+--
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION DropGeometryTable(varchar) RETURNS text AS
+$$ SELECT DropGeometryTable('','',$1) $$
+LANGUAGE 'sql' VOLATILE STRICT;
+
+-----------------------------------------------------------------------
+-- UPDATEGEOMETRYSRID
+--   <catalogue>, <schema>, <table>, <column>, <srid>
+-----------------------------------------------------------------------
+--
+-- Change SRID of all features in a spatially-enabled table
+--
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION UpdateGeometrySRID(varchar,varchar,varchar,varchar,integer)
+	RETURNS text
+	AS
+$$
+DECLARE
+	catalog_name alias for $1;
+	schema_name alias for $2;
+	table_name alias for $3;
+	column_name alias for $4;
+	new_srid alias for $5;
+	myrec RECORD;
+	okay boolean;
+	cname varchar;
+	real_schema name;
+
+BEGIN
+
+
+	-- Find, check or fix schema_name
+	IF ( schema_name != '' ) THEN
+		okay = 'f';
+
+		FOR myrec IN SELECT nspname FROM pg_namespace WHERE text(nspname) = schema_name LOOP
+			okay := 't';
+		END LOOP;
+
+		IF ( okay <> 't' ) THEN
+			RAISE EXCEPTION 'Invalid schema name';
+		ELSE
+			real_schema = schema_name;
+		END IF;
+	ELSE
+		SELECT INTO real_schema current_schema()::text;
+	END IF;
+
+	-- Find out if the column is in the geometry_columns table
+	okay = 'f';
+	FOR myrec IN SELECT * from geometry_columns where f_table_schema = text(real_schema) and f_table_name = table_name and f_geometry_column = column_name LOOP
+		okay := 't';
+	END LOOP;
+	IF (okay <> 't') THEN
+		RAISE EXCEPTION 'column not found in geometry_columns table';
+		RETURN 'f';
+	END IF;
+
+	-- Update ref from geometry_columns table
+	EXECUTE 'UPDATE geometry_columns SET SRID = ' || new_srid::text ||
+		' where f_table_schema = ' ||
+		quote_literal(real_schema) || ' and f_table_name = ' ||
+		quote_literal(table_name)  || ' and f_geometry_column = ' ||
+		quote_literal(column_name);
+
+	-- Make up constraint name
+	cname = 'enforce_srid_'  || column_name;
+
+	-- Drop enforce_srid constraint
+	EXECUTE 'ALTER TABLE ' || quote_ident(real_schema) ||
+		'.' || quote_ident(table_name) ||
+		' DROP constraint ' || quote_ident(cname);
+
+	-- Update geometries SRID
+	EXECUTE 'UPDATE ' || quote_ident(real_schema) ||
+		'.' || quote_ident(table_name) ||
+		' SET ' || quote_ident(column_name) ||
+		' = setSRID(' || quote_ident(column_name) ||
+		', ' || new_srid::text || ')';
+
+	-- Reset enforce_srid constraint
+	EXECUTE 'ALTER TABLE ' || quote_ident(real_schema) ||
+		'.' || quote_ident(table_name) ||
+		' ADD constraint ' || quote_ident(cname) ||
+		' CHECK (srid(' || quote_ident(column_name) ||
+		') = ' || new_srid::text || ')';
+
+	RETURN real_schema || '.' || table_name || '.' || column_name ||' SRID changed to ' || new_srid::text;
+
+END;
+$$
+LANGUAGE 'plpgsql' VOLATILE STRICT;
+
+-----------------------------------------------------------------------
+-- UPDATEGEOMETRYSRID
+--   <schema>, <table>, <column>, <srid>
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION UpdateGeometrySRID(varchar,varchar,varchar,integer)
+	RETURNS text
+	AS $$
+DECLARE
+	ret  text;
+BEGIN
+	SELECT UpdateGeometrySRID('',$1,$2,$3,$4) into ret;
+	RETURN ret;
+END;
+$$
+LANGUAGE 'plpgsql' VOLATILE STRICT;
+
+-----------------------------------------------------------------------
+-- UPDATEGEOMETRYSRID
+--   <table>, <column>, <srid>
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION UpdateGeometrySRID(varchar,varchar,integer)
+	RETURNS text
+	AS $$
+DECLARE
+	ret  text;
+BEGIN
+	SELECT UpdateGeometrySRID('','',$1,$2,$3) into ret;
+	RETURN ret;
+END;
+$$
+LANGUAGE 'plpgsql' VOLATILE STRICT;
+
+-----------------------------------------------------------------------
+-- FIND_SRID( <schema>, <table>, <geom col> )
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION find_srid(varchar,varchar,varchar) RETURNS int4 AS
+$$
+DECLARE
+	schem text;
+	tabl text;
+	sr int4;
+BEGIN
+	IF $1 IS NULL THEN
+	  RAISE EXCEPTION 'find_srid() - schema is NULL!';
+	END IF;
+	IF $2 IS NULL THEN
+	  RAISE EXCEPTION 'find_srid() - table name is NULL!';
+	END IF;
+	IF $3 IS NULL THEN
+	  RAISE EXCEPTION 'find_srid() - column name is NULL!';
+	END IF;
+	schem = $1;
+	tabl = $2;
+-- if the table contains a . and the schema is empty
+-- split the table into a schema and a table
+-- otherwise drop through to default behavior
+	IF ( schem = '' and tabl LIKE '%.%' ) THEN
+	 schem = substr(tabl,1,strpos(tabl,'.')-1);
+	 tabl = substr(tabl,length(schem)+2);
+	ELSE
+	 schem = schem || '%';
+	END IF;
+
+	select SRID into sr from geometry_columns where f_table_schema like schem and f_table_name = tabl and f_geometry_column = $3;
+	IF NOT FOUND THEN
+	   RAISE EXCEPTION 'find_srid() - couldnt find the corresponding SRID - is the geometry registered in the GEOMETRY_COLUMNS table?  Is there an uppercase/lowercase missmatch?';
+	END IF;
+	return sr;
+END;
+$$
+LANGUAGE 'plpgsql' IMMUTABLE STRICT;
+
+
+---------------------------------------------------------------
+-- PROJ support
+---------------------------------------------------------------
+
+CREATE OR REPLACE FUNCTION get_proj4_from_srid(integer) RETURNS text AS
+$$
+BEGIN
+	RETURN proj4text::text FROM spatial_ref_sys WHERE srid= $1;
+END;
+$$
+LANGUAGE 'plpgsql' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION postgis_transform_geometry(geometry,text,text,int)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','transform_geom'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION transform(geometry,integer)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','transform'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: transform(geometry,integer)
+CREATE OR REPLACE FUNCTION ST_Transform(geometry,integer)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','transform'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+
+-----------------------------------------------------------------------
+-- POSTGIS_VERSION()
+-----------------------------------------------------------------------
+
+CREATE OR REPLACE FUNCTION postgis_version() RETURNS text
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION postgis_proj_version() RETURNS text
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE;
+
+--
+-- IMPORTANT:
+-- Starting at 1.1.0 this function is used by postgis_proc_upgrade.pl
+-- to extract version of postgis being installed.
+-- Do not modify this w/out also changing postgis_proc_upgrade.pl
+--
+CREATE OR REPLACE FUNCTION postgis_scripts_installed() RETURNS text
+	AS 'SELECT ''1.5 r5385''::text AS version'
+	LANGUAGE 'sql' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION postgis_lib_version() RETURNS text
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE; -- a new lib will require a new session
+
+-- NOTE: starting at 1.1.0 this is the same of postgis_lib_version()
+CREATE OR REPLACE FUNCTION postgis_scripts_released() RETURNS text
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION postgis_uses_stats() RETURNS bool
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION postgis_geos_version() RETURNS text
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION postgis_libxml_version() RETURNS text
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION postgis_scripts_build_date() RETURNS text
+	AS 'SELECT ''2010-03-11 19:15:17''::text AS version'
+	LANGUAGE 'sql' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION postgis_lib_build_date() RETURNS text
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE;
+
+
+
+CREATE OR REPLACE FUNCTION postgis_full_version() RETURNS text
+AS $$
+DECLARE
+	libver text;
+	projver text;
+	geosver text;
+	libxmlver text;
+	usestats bool;
+	dbproc text;
+	relproc text;
+	fullver text;
+BEGIN
+	SELECT postgis_lib_version() INTO libver;
+	SELECT postgis_proj_version() INTO projver;
+	SELECT postgis_geos_version() INTO geosver;
+	SELECT postgis_libxml_version() INTO libxmlver;
+	SELECT postgis_uses_stats() INTO usestats;
+	SELECT postgis_scripts_installed() INTO dbproc;
+	SELECT postgis_scripts_released() INTO relproc;
+
+	fullver = 'POSTGIS="' || libver || '"';
+
+	IF  geosver IS NOT NULL THEN
+		fullver = fullver || ' GEOS="' || geosver || '"';
+	END IF;
+
+	IF  projver IS NOT NULL THEN
+		fullver = fullver || ' PROJ="' || projver || '"';
+	END IF;
+
+	IF  libxmlver IS NOT NULL THEN
+		fullver = fullver || ' LIBXML="' || libxmlver || '"';
+	END IF;
+
+	IF usestats THEN
+		fullver = fullver || ' USE_STATS';
+	END IF;
+
+	-- fullver = fullver || ' DBPROC="' || dbproc || '"';
+	-- fullver = fullver || ' RELPROC="' || relproc || '"';
+
+	IF dbproc != relproc THEN
+		fullver = fullver || ' (procs from ' || dbproc || ' need upgrade)';
+	END IF;
+
+	RETURN fullver;
+END
+$$
+LANGUAGE 'plpgsql' IMMUTABLE;
+
+---------------------------------------------------------------
+-- CASTS
+---------------------------------------------------------------
+
+-- Legacy ST_ variants of casts, to be removed in 2.0
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_box2d(geometry)
+	RETURNS box2d
+	AS '$libdir/postgis-1.5','LWGEOM_to_BOX2DFLOAT4'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_box3d(geometry)
+	RETURNS box3d
+	AS '$libdir/postgis-1.5','LWGEOM_to_BOX3D'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_box(geometry)
+	RETURNS box
+	AS '$libdir/postgis-1.5','LWGEOM_to_BOX'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_box2d(box3d)
+	RETURNS box2d
+	AS '$libdir/postgis-1.5','BOX3D_to_BOX2DFLOAT4'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_box3d(box2d)
+	RETURNS box3d
+	AS '$libdir/postgis-1.5','BOX2DFLOAT4_to_BOX3D'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_box(box3d)
+	RETURNS box
+	AS '$libdir/postgis-1.5','BOX3D_to_BOX'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_text(geometry)
+	RETURNS text
+	AS '$libdir/postgis-1.5','LWGEOM_to_text'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry(box2d)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','BOX2DFLOAT4_to_LWGEOM'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry(box3d)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','BOX3D_to_LWGEOM'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry(text)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','parse_WKT_lwgeom'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry(chip)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','CHIP_to_LWGEOM'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry(bytea)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_from_bytea'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_bytea(geometry)
+	RETURNS bytea
+	AS '$libdir/postgis-1.5','LWGEOM_to_bytea'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_box3d_extent(box3d_extent)
+	RETURNS box3d
+	AS '$libdir/postgis-1.5', 'BOX3D_extent_to_BOX3D'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_box2d(box3d_extent)
+	RETURNS box2d
+	AS '$libdir/postgis-1.5', 'BOX3D_to_BOX2DFLOAT4'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.5.0
+CREATE OR REPLACE FUNCTION st_geometry(box3d_extent)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','BOX3D_to_LWGEOM'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+	
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 	
+		
+CREATE OR REPLACE FUNCTION box2d(geometry)
+	RETURNS box2d
+	AS '$libdir/postgis-1.5','LWGEOM_to_BOX2DFLOAT4'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION box3d(geometry)
+	RETURNS box3d
+	AS '$libdir/postgis-1.5','LWGEOM_to_BOX3D'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION box(geometry)
+	RETURNS box
+	AS '$libdir/postgis-1.5','LWGEOM_to_BOX'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION box2d(box3d)
+	RETURNS box2d
+	AS '$libdir/postgis-1.5','BOX3D_to_BOX2DFLOAT4'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION box3d(box2d)
+	RETURNS box3d
+	AS '$libdir/postgis-1.5','BOX2DFLOAT4_to_BOX3D'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION box(box3d)
+	RETURNS box
+	AS '$libdir/postgis-1.5','BOX3D_to_BOX'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION text(geometry)
+	RETURNS text
+	AS '$libdir/postgis-1.5','LWGEOM_to_text'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- this is kept for backward-compatibility
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION box3dtobox(box3d)
+	RETURNS box
+	AS 'SELECT box($1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry(box2d)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','BOX2DFLOAT4_to_LWGEOM'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry(box3d)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','BOX3D_to_LWGEOM'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry(text)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','parse_WKT_lwgeom'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry(chip)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','CHIP_to_LWGEOM'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geometry(bytea)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_from_bytea'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION bytea(geometry)
+	RETURNS bytea
+	AS '$libdir/postgis-1.5','LWGEOM_to_bytea'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- 7.3+ explicit casting definitions
+CREATE CAST (geometry AS box2d) WITH FUNCTION box2d(geometry) AS IMPLICIT;
+CREATE CAST (geometry AS box3d) WITH FUNCTION box3d(geometry) AS IMPLICIT;
+CREATE CAST (geometry AS box) WITH FUNCTION box(geometry) AS IMPLICIT;
+CREATE CAST (box3d AS box2d) WITH FUNCTION box2d(box3d) AS IMPLICIT;
+CREATE CAST (box2d AS box3d) WITH FUNCTION box3d(box2d) AS IMPLICIT;
+CREATE CAST (box2d AS geometry) WITH FUNCTION geometry(box2d) AS IMPLICIT;
+CREATE CAST (box3d AS box) WITH FUNCTION box(box3d) AS IMPLICIT;
+CREATE CAST (box3d AS geometry) WITH FUNCTION geometry(box3d) AS IMPLICIT;
+CREATE CAST (text AS geometry) WITH FUNCTION geometry(text) AS IMPLICIT;
+CREATE CAST (geometry AS text) WITH FUNCTION text(geometry) AS IMPLICIT;
+CREATE CAST (chip AS geometry) WITH FUNCTION geometry(chip) AS IMPLICIT;
+CREATE CAST (bytea AS geometry) WITH FUNCTION geometry(bytea) AS IMPLICIT;
+CREATE CAST (geometry AS bytea) WITH FUNCTION bytea(geometry) AS IMPLICIT;
+
+-- Casts to allow the box3d_extent type to automatically cast to box3d/box2d in queries
+CREATE CAST (box3d_extent AS box3d) WITH FUNCTION box3d_extent(box3d_extent) AS IMPLICIT;
+CREATE CAST (box3d_extent AS box2d) WITH FUNCTION box2d(box3d_extent) AS IMPLICIT;
+CREATE CAST (box3d_extent AS geometry) WITH FUNCTION geometry(box3d_extent) AS IMPLICIT;
+
+---------------------------------------------------------------
+-- Algorithms
+---------------------------------------------------------------
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION Simplify(geometry, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_simplify2d'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_Simplify(geometry, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_simplify2d'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- SnapToGrid(input, xoff, yoff, xsize, ysize)
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION SnapToGrid(geometry, float8, float8, float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_snaptogrid'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_SnapToGrid(geometry, float8, float8, float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_snaptogrid'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- SnapToGrid(input, xsize, ysize) # offsets=0
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION SnapToGrid(geometry, float8, float8)
+	RETURNS geometry
+	AS 'SELECT SnapToGrid($1, 0, 0, $2, $3)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_SnapToGrid(geometry, float8, float8)
+	RETURNS geometry
+	AS 'SELECT ST_SnapToGrid($1, 0, 0, $2, $3)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- SnapToGrid(input, size) # xsize=ysize=size, offsets=0
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION SnapToGrid(geometry, float8)
+	RETURNS geometry
+	AS 'SELECT SnapToGrid($1, 0, 0, $2, $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_SnapToGrid(geometry, float8)
+	RETURNS geometry
+	AS 'SELECT ST_SnapToGrid($1, 0, 0, $2, $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- SnapToGrid(input, point_offsets, xsize, ysize, zsize, msize)
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION SnapToGrid(geometry, geometry, float8, float8, float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_snaptogrid_pointoff'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_SnapToGrid(geometry, geometry, float8, float8, float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_snaptogrid_pointoff'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION Segmentize(geometry, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_segmentize2d'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_Segmentize(geometry, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_segmentize2d'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+---------------------------------------------------------------
+-- LRS
+---------------------------------------------------------------
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION line_interpolate_point(geometry, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_line_interpolate_point'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_line_interpolate_point(geometry, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_line_interpolate_point'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION line_substring(geometry, float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_line_substring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_line_substring(geometry, float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_line_substring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION line_locate_point(geometry, geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5', 'LWGEOM_line_locate_point'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_line_locate_point(geometry, geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5', 'LWGEOM_line_locate_point'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION locate_between_measures(geometry, float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_locate_between_m'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_locate_between_measures(geometry, float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_locate_between_m'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION locate_along_measure(geometry, float8)
+	RETURNS geometry
+	AS $$ SELECT locate_between_measures($1, $2, $2) $$
+	LANGUAGE 'sql' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_locate_along_measure(geometry, float8)
+	RETURNS geometry
+	AS $$ SELECT locate_between_measures($1, $2, $2) $$
+	LANGUAGE 'sql' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_AddMeasure(geometry, float8, float8) 
+	RETURNS geometry 
+	AS '$libdir/postgis-1.5', 'ST_AddMeasure' 
+	LANGUAGE 'C' IMMUTABLE STRICT;
+    
+---------------------------------------------------------------
+-- GEOS
+---------------------------------------------------------------
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION intersection(geometry,geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','intersection'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: intersection(geometry,geometry)
+CREATE OR REPLACE FUNCTION ST_Intersection(geometry,geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','intersection'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION buffer(geometry,float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','buffer'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- PostGIS equivalent function: buffer(geometry,float8)
+CREATE OR REPLACE FUNCTION ST_Buffer(geometry,float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','buffer'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.5.0 - requires GEOS-3.2 or higher
+CREATE OR REPLACE FUNCTION _ST_Buffer(geometry,float8,cstring)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','buffer'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_Buffer(geometry,float8,integer)
+	RETURNS geometry
+	AS $$ SELECT _ST_Buffer($1, $2,
+		CAST('quad_segs='||CAST($3 AS text) as cstring))
+	   $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_buffer(geometry,float8,text)
+	RETURNS geometry
+	AS $$ SELECT _ST_Buffer($1, $2,
+		CAST( regexp_replace($3, '^[0123456789]+$',
+			'quad_segs='||$3) AS cstring)
+		)
+	   $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION buffer(geometry,float8,integer)
+	RETURNS geometry
+	AS 'SELECT ST_Buffer($1, $2, $3)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION convexhull(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','convexhull'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- PostGIS equivalent function: convexhull(geometry)
+CREATE OR REPLACE FUNCTION ST_ConvexHull(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','convexhull'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Only accepts LINESTRING as parameters.
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION _ST_LineCrossingDirection(geometry, geometry)
+	RETURNS integer
+	AS '$libdir/postgis-1.5', 'ST_LineCrossingDirection'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION ST_LineCrossingDirection(geometry, geometry)
+	RETURNS integer AS
+	$$ SELECT CASE WHEN NOT $1 && $2 THEN 0 ELSE _ST_LineCrossingDirection($1,$2) END $$
+	LANGUAGE 'sql' IMMUTABLE;
+
+
+-- Only accepts LINESTRING as parameters.
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION ST_LocateBetweenElevations(geometry, float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'ST_LocateBetweenElevations'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Requires GEOS >= 3.0.0
+-- Availability: 1.3.3
+CREATE OR REPLACE FUNCTION ST_SimplifyPreserveTopology(geometry, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','topologypreservesimplify'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Requires GEOS >= 3.1.0
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION ST_IsValidReason(geometry)
+	RETURNS text
+	AS '$libdir/postgis-1.5', 'isvalidreason'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+
+-- Requires GEOS >= 3.2.0
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_HausdorffDistance(geometry, geometry)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5', 'hausdorffdistance'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+
+
+-- Requires GEOS >= 3.2.0
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_HausdorffDistance(geometry, geometry, float8)
+	RETURNS FLOAT8
+	AS '$libdir/postgis-1.5', 'hausdorffdistancedensify'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION difference(geometry,geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','difference'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: difference(geometry,geometry)
+CREATE OR REPLACE FUNCTION ST_Difference(geometry,geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','difference'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION boundary(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','boundary'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: boundary(geometry)
+CREATE OR REPLACE FUNCTION ST_Boundary(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','boundary'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION symdifference(geometry,geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','symdifference'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: symdifference(geometry,geometry)
+CREATE OR REPLACE FUNCTION ST_SymDifference(geometry,geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','symdifference'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION symmetricdifference(geometry,geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','symdifference'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_symmetricdifference(geometry,geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','symdifference'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION GeomUnion(geometry,geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','geomunion'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: GeomUnion(geometry,geometry)
+CREATE OR REPLACE FUNCTION ST_Union(geometry,geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','geomunion'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+--------------------------------------------------------------------------------
+-- Aggregates and their supporting functions
+--------------------------------------------------------------------------------
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION collect(geometry, geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_collect'
+	LANGUAGE 'C' IMMUTABLE;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_collect(geometry, geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_collect'
+	LANGUAGE 'C' IMMUTABLE;
+
+-- Deprecation in 1.2.3
+CREATE AGGREGATE memcollect(
+	sfunc = ST_collect,
+	basetype = geometry,
+	stype = geometry
+	);
+
+-- Availability: 1.2.2
+CREATE AGGREGATE ST_memcollect(
+	sfunc = ST_collect,
+	basetype = geometry,
+	stype = geometry
+	);
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_collect (geometry[])
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_collect_garray'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE AGGREGATE MemGeomUnion (
+	basetype = geometry,
+	sfunc = geomunion,
+	stype = geometry
+	);
+
+-- Availability: 1.2.2
+CREATE AGGREGATE ST_MemUnion (
+	basetype = geometry,
+	sfunc = ST_Union,
+	stype = geometry
+	);
+
+--
+-- pgis_abs
+-- Container type to hold the ArrayBuildState pointer as it passes through
+-- the geometry array accumulation aggregate.
+--
+CREATE OR REPLACE FUNCTION pgis_abs_in(cstring)
+	RETURNS pgis_abs
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION pgis_abs_out(pgis_abs)
+	RETURNS cstring
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE TYPE pgis_abs (
+	internallength = 8,
+	input = pgis_abs_in,
+	output = pgis_abs_out,
+	alignment = double
+);
+
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION pgis_geometry_accum_transfn(pgis_abs, geometry)
+	RETURNS pgis_abs
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C';
+
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION pgis_geometry_accum_finalfn(pgis_abs)
+	RETURNS geometry[]
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C';
+
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION pgis_geometry_union_finalfn(pgis_abs)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C';
+
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION pgis_geometry_collect_finalfn(pgis_abs)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C';
+
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION pgis_geometry_polygonize_finalfn(pgis_abs)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C';
+
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION pgis_geometry_makeline_finalfn(pgis_abs)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C';
+
+-- Deprecation in: 1.2.3
+CREATE AGGREGATE accum (
+	sfunc = pgis_geometry_accum_transfn,
+	basetype = geometry,
+	stype = pgis_abs,
+	finalfunc = pgis_geometry_accum_finalfn
+	);
+
+-- Availability: 1.2.2
+CREATE AGGREGATE ST_Accum (
+	sfunc = pgis_geometry_accum_transfn,
+	basetype = geometry,
+	stype = pgis_abs,
+	finalfunc = pgis_geometry_accum_finalfn
+	);
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION unite_garray (geometry[])
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'pgis_union_geometry_array'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.4.0
+CREATE OR REPLACE FUNCTION ST_unite_garray (geometry[])
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','pgis_union_geometry_array'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION ST_Union (geometry[])
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','pgis_union_geometry_array'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE AGGREGATE ST_Union (
+	basetype = geometry,
+	sfunc = pgis_geometry_accum_transfn,
+	stype = pgis_abs,
+	finalfunc = pgis_geometry_union_finalfn
+	);
+
+-- Deprecation in 1.2.3
+CREATE AGGREGATE collect (
+	basetype = geometry,
+	sfunc = pgis_geometry_accum_transfn,
+	stype = pgis_abs,
+	finalfunc = pgis_geometry_collect_finalfn
+);
+
+-- Availability: 1.2.2
+CREATE AGGREGATE ST_Collect (
+	BASETYPE = geometry,
+	SFUNC = pgis_geometry_accum_transfn,
+	STYPE = pgis_abs,
+	FINALFUNC = pgis_geometry_collect_finalfn
+	);
+
+-- Deprecation in 1.2.3
+CREATE AGGREGATE Polygonize (
+	BASETYPE = geometry,
+	SFUNC = pgis_geometry_accum_transfn,
+	STYPE = pgis_abs,
+	FINALFUNC = pgis_geometry_polygonize_finalfn
+	);
+
+-- Availability: 1.2.2
+CREATE AGGREGATE ST_Polygonize (
+	BASETYPE = geometry,
+	SFUNC = pgis_geometry_accum_transfn,
+	STYPE = pgis_abs,
+	FINALFUNC = pgis_geometry_polygonize_finalfn
+	);
+
+-- Deprecation in 1.2.3
+CREATE AGGREGATE makeline (
+	BASETYPE = geometry,
+	SFUNC = pgis_geometry_accum_transfn,
+	STYPE = pgis_abs,
+	FINALFUNC = pgis_geometry_makeline_finalfn
+	);
+
+-- Availability: 1.2.2
+CREATE AGGREGATE ST_MakeLine (
+	BASETYPE = geometry,
+	SFUNC = pgis_geometry_accum_transfn,
+	STYPE = pgis_abs,
+	FINALFUNC = pgis_geometry_makeline_finalfn
+	);
+
+
+
+--------------------------------------------------------------------------------
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION relate(geometry,geometry)
+	RETURNS text
+	AS '$libdir/postgis-1.5','relate_full'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_relate(geometry,geometry)
+	RETURNS text
+	AS '$libdir/postgis-1.5','relate_full'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION relate(geometry,geometry,text)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5','relate_pattern'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: relate(geometry,geometry,text)
+CREATE OR REPLACE FUNCTION ST_Relate(geometry,geometry,text)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5','relate_pattern'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION disjoint(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: disjoint(geometry,geometry)
+CREATE OR REPLACE FUNCTION ST_Disjoint(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5','disjoint'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION touches(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: touches(geometry,geometry)
+CREATE OR REPLACE FUNCTION _ST_Touches(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5','touches'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.2
+-- Inlines index magic
+CREATE OR REPLACE FUNCTION ST_Touches(geometry,geometry)
+	RETURNS boolean
+	AS 'SELECT $1 && $2 AND _ST_Touches($1,$2)'
+	LANGUAGE 'SQL' IMMUTABLE;
+
+-- Availability: 1.3.4
+CREATE OR REPLACE FUNCTION _ST_DWithin(geometry,geometry,float8)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5', 'LWGEOM_dwithin'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_DWithin(geometry, geometry, float8)
+	RETURNS boolean
+	AS 'SELECT $1 && ST_Expand($2,$3) AND $2 && ST_Expand($1,$3) AND _ST_DWithin($1, $2, $3)'
+	LANGUAGE 'SQL' IMMUTABLE;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION intersects(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: intersects(geometry,geometry)
+CREATE OR REPLACE FUNCTION _ST_Intersects(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5','intersects'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.2
+-- Inlines index magic
+CREATE OR REPLACE FUNCTION ST_Intersects(geometry,geometry)
+	RETURNS boolean
+	AS 'SELECT $1 && $2 AND _ST_Intersects($1,$2)'
+	LANGUAGE 'SQL' IMMUTABLE;
+	
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION crosses(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: crosses(geometry,geometry)
+CREATE OR REPLACE FUNCTION _ST_Crosses(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5','crosses'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.2
+-- Inlines index magic
+CREATE OR REPLACE FUNCTION ST_Crosses(geometry,geometry)
+	RETURNS boolean
+	AS 'SELECT $1 && $2 AND _ST_Crosses($1,$2)'
+	LANGUAGE 'SQL' IMMUTABLE;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION within(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: within(geometry,geometry)
+CREATE OR REPLACE FUNCTION _ST_Within(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5','within'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.2
+-- Inlines index magic
+CREATE OR REPLACE FUNCTION ST_Within(geometry,geometry)
+	RETURNS boolean
+	AS 'SELECT $1 && $2 AND _ST_Within($1,$2)'
+	LANGUAGE 'SQL' IMMUTABLE;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION Contains(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: contains(geometry,geometry)
+CREATE OR REPLACE FUNCTION _ST_Contains(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5','contains'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.2
+-- Inlines index magic
+CREATE OR REPLACE FUNCTION ST_Contains(geometry,geometry)
+	RETURNS boolean
+	AS 'SELECT $1 && $2 AND _ST_Contains($1,$2)'
+	LANGUAGE 'SQL' IMMUTABLE;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION _ST_CoveredBy(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5', 'coveredby'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_CoveredBy(geometry,geometry)
+	RETURNS boolean
+	AS 'SELECT $1 && $2 AND _ST_CoveredBy($1,$2)'
+	LANGUAGE 'SQL' IMMUTABLE;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION _ST_Covers(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5', 'covers'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.2
+-- Inlines index magic
+CREATE OR REPLACE FUNCTION ST_Covers(geometry,geometry)
+	RETURNS boolean
+	AS 'SELECT $1 && $2 AND _ST_Covers($1,$2)'
+	LANGUAGE 'SQL' IMMUTABLE;
+
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION _ST_ContainsProperly(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5','containsproperly'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.4.0
+-- Inlines index magic
+CREATE OR REPLACE FUNCTION ST_ContainsProperly(geometry,geometry)
+	RETURNS boolean
+	AS 'SELECT $1 && $2 AND _ST_ContainsProperly($1,$2)'
+	LANGUAGE 'SQL' IMMUTABLE;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION overlaps(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: overlaps(geometry,geometry)
+CREATE OR REPLACE FUNCTION _ST_Overlaps(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5','overlaps'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.2
+-- Inlines index magic
+CREATE OR REPLACE FUNCTION ST_Overlaps(geometry,geometry)
+	RETURNS boolean
+	AS 'SELECT $1 && $2 AND _ST_Overlaps($1,$2)'
+	LANGUAGE 'SQL' IMMUTABLE;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION IsValid(geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5', 'isvalid'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- PostGIS equivalent function: IsValid(geometry)
+-- TODO: change null returns to true
+CREATE OR REPLACE FUNCTION ST_IsValid(geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5', 'isvalid'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- This is also available w/out GEOS
+CREATE OR REPLACE FUNCTION Centroid(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+	
+-- PostGIS equivalent function: Centroid(geometry)
+CREATE OR REPLACE FUNCTION ST_Centroid(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'centroid'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+	
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION IsRing(geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: IsRing(geometry)
+CREATE OR REPLACE FUNCTION ST_IsRing(geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5', 'isring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION PointOnSurface(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: PointOnSurface(geometry)
+CREATE OR REPLACE FUNCTION ST_PointOnSurface(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'pointonsurface'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION IsSimple(geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5', 'issimple'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: IsSimple(geometry)
+CREATE OR REPLACE FUNCTION ST_IsSimple(geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5', 'issimple'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION Equals(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5','geomequals'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION _ST_Equals(geometry,geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5','geomequals'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.2.1
+CREATE OR REPLACE FUNCTION ST_Equals(geometry,geometry)
+	RETURNS boolean
+	AS 'SELECT $1 && $2 AND _ST_Equals($1,$2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+
+-----------------------------------------------------------------------
+-- GML & KML INPUT
+-- Availability: 1.5.0
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION ST_GeomFromGML(text)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','geom_from_gml'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION ST_GMLToSQL(text)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','geom_from_gml'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION ST_GeomFromKML(text)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','geom_from_kml'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-----------------------------------------------------------------------
+-- SVG OUTPUT
+-----------------------------------------------------------------------
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AsSVG(geometry,int4,int4)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','assvg_geometry'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_AsSVG(geometry,int4,int4)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','assvg_geometry'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AsSVG(geometry,int4)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','assvg_geometry'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_AsSVG(geometry,int4)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','assvg_geometry'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AsSVG(geometry)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','assvg_geometry'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_AsSVG(geometry)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','assvg_geometry'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-----------------------------------------------------------------------
+-- GML OUTPUT
+-----------------------------------------------------------------------
+-- _ST_AsGML(version, geom, precision, option)
+CREATE OR REPLACE FUNCTION _ST_AsGML(int4, geometry, int4, int4)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','LWGEOM_asGML'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- AsGML(geom, precision) / version=2
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AsGML(geometry, int4)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsGML(2, $1, $2, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_AsGML(geometry, int4)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsGML(2, $1, $2, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- AsGML(geom) / precision=15 version=2
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AsGML(geometry)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsGML(2, $1, 15, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_AsGML(geometry)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsGML(2, $1, 15, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGML(version, geom) / precision=15 version=2
+-- Availability: 1.3.2
+CREATE OR REPLACE FUNCTION ST_AsGML(int4, geometry)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsGML($1, $2, 15, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGML(version, geom, precision)
+-- Availability: 1.3.2
+CREATE OR REPLACE FUNCTION ST_AsGML(int4, geometry, int4)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsGML($1, $2, $3, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGML (geom, precision, option) / version=2
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION ST_AsGML(geometry, int4, int4)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsGML(2, $1, $2, $3)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGML(version, geom, precision, option)
+-- Availability: 1.4.0
+CREATE OR REPLACE FUNCTION ST_AsGML(int4, geometry, int4, int4)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsGML($1, $2, $3, $4)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-----------------------------------------------------------------------
+-- KML OUTPUT
+-----------------------------------------------------------------------
+-- _ST_AsKML(version, geom, precision)
+CREATE OR REPLACE FUNCTION _ST_AsKML(int4, geometry, int4)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','LWGEOM_asKML'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- AsKML(geom, precision) / version=2
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AsKML(geometry, int4)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsKML(2, transform($1,4326), $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_AsKML(geometry, int4)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsKML(2, ST_Transform($1,4326), $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- AsKML(geom) / precision=15 version=2
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AsKML(geometry)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsKML(2, transform($1,4326), 15)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- AsKML(version, geom, precision)
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AsKML(int4, geometry, int4)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsKML($1, transform($2,4326), $3)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_AsKML(geometry)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsKML(2, ST_Transform($1,4326), 15)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsKML(version, geom) / precision=15 version=2
+-- Availability: 1.3.2
+CREATE OR REPLACE FUNCTION ST_AsKML(int4, geometry)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsKML($1, ST_Transform($2,4326), 15)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsKML(version, geom, precision)
+-- Availability: 1.3.2
+CREATE OR REPLACE FUNCTION ST_AsKML(int4, geometry, int4)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsKML($1, ST_Transform($2,4326), $3)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-----------------------------------------------------------------------
+-- GEOJSON OUTPUT
+-- Availability: 1.3.4
+-----------------------------------------------------------------------
+-- _ST_AsGeoJson(version, geom, precision, options)
+CREATE OR REPLACE FUNCTION _ST_AsGeoJson(int4, geometry, int4, int4)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','LWGEOM_asGeoJson'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- ST_AsGeoJson(geom, precision) / version=1 options=0
+CREATE OR REPLACE FUNCTION ST_AsGeoJson(geometry, int4)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsGeoJson(1, $1, $2, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGeoJson(geom) / precision=15 version=1 options=0
+CREATE OR REPLACE FUNCTION ST_AsGeoJson(geometry)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsGeoJson(1, $1, 15, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGeoJson(version, geom) / precision=15 options=0
+CREATE OR REPLACE FUNCTION ST_AsGeoJson(int4, geometry)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsGeoJson($1, $2, 15, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGeoJson(version, geom, precision) / options=0
+CREATE OR REPLACE FUNCTION ST_AsGeoJson(int4, geometry, int4)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsGeoJson($1, $2, $3, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGeoJson(geom, precision, options) / version=1
+CREATE OR REPLACE FUNCTION ST_AsGeoJson(geometry, int4, int4)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsGeoJson(1, $1, $2, $3)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGeoJson(version, geom, precision,options)
+CREATE OR REPLACE FUNCTION ST_AsGeoJson(int4, geometry, int4, int4)
+	RETURNS TEXT
+	AS 'SELECT _ST_AsGeoJson($1, $2, $3, $4)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+------------------------------------------------------------------------
+-- GeoHash (geohash.org)
+------------------------------------------------------------------------
+
+-- Availability 1.4.0
+CREATE OR REPLACE FUNCTION ST_GeoHash(geometry, int4)
+	RETURNS TEXT
+		AS '$libdir/postgis-1.5', 'ST_GeoHash'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability 1.4.0
+CREATE OR REPLACE FUNCTION ST_GeoHash(geometry)
+	RETURNS TEXT
+	AS 'SELECT ST_GeoHash($1, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+------------------------------------------------------------------------
+-- OGC defined
+------------------------------------------------------------------------
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION NumPoints(geometry)
+	RETURNS int4
+	AS '$libdir/postgis-1.5', 'LWGEOM_numpoints_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: NumPoints(geometry)
+CREATE OR REPLACE FUNCTION ST_NumPoints(geometry)
+	RETURNS int4
+	AS '$libdir/postgis-1.5', 'LWGEOM_numpoints_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION NumGeometries(geometry)
+	RETURNS int4
+	AS '$libdir/postgis-1.5', 'LWGEOM_numgeometries_collection'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: NumGeometries(geometry)
+CREATE OR REPLACE FUNCTION ST_NumGeometries(geometry)
+	RETURNS int4
+	AS '$libdir/postgis-1.5', 'LWGEOM_numgeometries_collection'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION GeometryN(geometry,integer)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_geometryn_collection'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: GeometryN(geometry)
+CREATE OR REPLACE FUNCTION ST_GeometryN(geometry,integer)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_geometryn_collection'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION Dimension(geometry)
+	RETURNS int4
+	AS '$libdir/postgis-1.5', 'LWGEOM_dimension'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: Dimension(geometry)
+CREATE OR REPLACE FUNCTION ST_Dimension(geometry)
+	RETURNS int4
+	AS '$libdir/postgis-1.5', 'LWGEOM_dimension'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION ExteriorRing(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_exteriorring_polygon'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: ExteriorRing(geometry)
+CREATE OR REPLACE FUNCTION ST_ExteriorRing(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_exteriorring_polygon'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION NumInteriorRings(geometry)
+	RETURNS integer
+	AS '$libdir/postgis-1.5','LWGEOM_numinteriorrings_polygon'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: NumInteriorRings(geometry)
+CREATE OR REPLACE FUNCTION ST_NumInteriorRings(geometry)
+	RETURNS integer
+	AS '$libdir/postgis-1.5','LWGEOM_numinteriorrings_polygon'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION NumInteriorRing(geometry)
+	RETURNS integer
+	AS '$libdir/postgis-1.5','LWGEOM_numinteriorrings_polygon'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_NumInteriorRing(geometry)
+	RETURNS integer
+	AS '$libdir/postgis-1.5','LWGEOM_numinteriorrings_polygon'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION InteriorRingN(geometry,integer)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_interiorringn_polygon'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: InteriorRingN(geometry)
+CREATE OR REPLACE FUNCTION ST_InteriorRingN(geometry,integer)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_interiorringn_polygon'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION GeometryType(geometry)
+	RETURNS text
+	AS '$libdir/postgis-1.5', 'LWGEOM_getTYPE'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Not quite equivalent to GeometryType
+CREATE OR REPLACE FUNCTION ST_GeometryType(geometry)
+	RETURNS text
+	AS '$libdir/postgis-1.5', 'geometry_geometrytype'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION PointN(geometry,integer)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_pointn_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: PointN(geometry,integer)
+CREATE OR REPLACE FUNCTION ST_PointN(geometry,integer)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_pointn_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION X(geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5','LWGEOM_x_point'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: X(geometry)
+CREATE OR REPLACE FUNCTION ST_X(geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5','LWGEOM_x_point'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION Y(geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5','LWGEOM_y_point'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: Y(geometry)
+CREATE OR REPLACE FUNCTION ST_Y(geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5','LWGEOM_y_point'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION Z(geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5','LWGEOM_z_point'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_Z(geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5','LWGEOM_z_point'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION M(geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5','LWGEOM_m_point'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_M(geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5','LWGEOM_m_point'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION StartPoint(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_startpoint_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: StartPoint(geometry))
+CREATE OR REPLACE FUNCTION ST_StartPoint(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_startpoint_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION EndPoint(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_endpoint_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: EndPoint(geometry))
+CREATE OR REPLACE FUNCTION ST_EndPoint(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_endpoint_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION IsClosed(geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5', 'LWGEOM_isclosed_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: IsClosed(geometry)
+CREATE OR REPLACE FUNCTION ST_IsClosed(geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5', 'LWGEOM_isclosed_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION IsEmpty(geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5', 'LWGEOM_isempty'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: IsEmpty(geometry)
+CREATE OR REPLACE FUNCTION ST_IsEmpty(geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5', 'LWGEOM_isempty'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION SRID(geometry)
+	RETURNS int4
+	AS '$libdir/postgis-1.5','LWGEOM_getSRID'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: getSRID(geometry)
+CREATE OR REPLACE FUNCTION ST_SRID(geometry)
+	RETURNS int4
+	AS '$libdir/postgis-1.5','LWGEOM_getSRID'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION SetSRID(geometry,int4)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_setSRID'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_SetSRID(geometry,int4)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_setSRID'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AsBinary(geometry)
+	RETURNS bytea
+	AS '$libdir/postgis-1.5','LWGEOM_asBinary'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: AsBinary(geometry)
+CREATE OR REPLACE FUNCTION ST_AsBinary(geometry)
+	RETURNS bytea
+	AS '$libdir/postgis-1.5','LWGEOM_asBinary'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AsBinary(geometry,text)
+	RETURNS bytea
+	AS '$libdir/postgis-1.5','LWGEOM_asBinary'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_AsBinary(geometry,text)
+	RETURNS bytea
+	AS '$libdir/postgis-1.5','LWGEOM_asBinary'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION AsText(geometry)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','LWGEOM_asText'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: AsText(geometry)
+CREATE OR REPLACE FUNCTION ST_AsText(geometry)
+	RETURNS TEXT
+	AS '$libdir/postgis-1.5','LWGEOM_asText'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION GeometryFromText(text)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_from_text'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_GeometryFromText(text)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_from_text'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION GeometryFromText(text, int4)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_from_text'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_GeometryFromText(text, int4)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_from_text'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION GeomFromText(text)
+	RETURNS geometry AS 'SELECT geometryfromtext($1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_GeomFromText(text)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_from_text'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION GeomFromText(text, int4)
+	RETURNS geometry AS 'SELECT geometryfromtext($1, $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: ST_GeometryFromText(text, int4)
+CREATE OR REPLACE FUNCTION ST_GeomFromText(text, int4)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_from_text'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION PointFromText(text)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromText($1)) = ''POINT''
+	THEN GeomFromText($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_PointFromText(text)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromText($1)) = ''POINT''
+	THEN ST_GeomFromText($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION PointFromText(text, int4)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromText($1, $2)) = ''POINT''
+	THEN GeomFromText($1,$2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: PointFromText(text, int4)
+-- TODO: improve this ... by not duplicating constructor time.
+CREATE OR REPLACE FUNCTION ST_PointFromText(text, int4)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromText($1, $2)) = ''POINT''
+	THEN ST_GeomFromText($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION LineFromText(text)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromText($1)) = ''LINESTRING''
+	THEN GeomFromText($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_LineFromText(text)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromText($1)) = ''LINESTRING''
+	THEN ST_GeomFromText($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION LineFromText(text, int4)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromText($1, $2)) = ''LINESTRING''
+	THEN GeomFromText($1,$2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: LineFromText(text, int4)
+CREATE OR REPLACE FUNCTION ST_LineFromText(text, int4)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromText($1, $2)) = ''LINESTRING''
+	THEN GeomFromText($1,$2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION LineStringFromText(text)
+	RETURNS geometry
+	AS 'SELECT LineFromText($1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION LineStringFromText(text, int4)
+	RETURNS geometry
+	AS 'SELECT LineFromText($1, $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION PolyFromText(text)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromText($1)) = ''POLYGON''
+	THEN GeomFromText($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_PolyFromText(text)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromText($1)) = ''POLYGON''
+	THEN ST_GeomFromText($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION PolyFromText(text, int4)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromText($1, $2)) = ''POLYGON''
+	THEN GeomFromText($1,$2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: ST_PolygonFromText(text, int4)
+CREATE OR REPLACE FUNCTION ST_PolyFromText(text, int4)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromText($1, $2)) = ''POLYGON''
+	THEN ST_GeomFromText($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION PolygonFromText(text, int4)
+	RETURNS geometry
+	AS 'SELECT PolyFromText($1, $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_PolygonFromText(text, int4)
+	RETURNS geometry
+	AS 'SELECT PolyFromText($1, $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION PolygonFromText(text)
+	RETURNS geometry
+	AS 'SELECT PolyFromText($1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_PolygonFromText(text)
+	RETURNS geometry
+	AS 'SELECT ST_PolyFromText($1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MLineFromText(text, int4)
+	RETURNS geometry
+	AS '
+	SELECT CASE
+	WHEN geometrytype(GeomFromText($1, $2)) = ''MULTILINESTRING''
+	THEN GeomFromText($1,$2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: MLineFromText(text, int4)
+CREATE OR REPLACE FUNCTION ST_MLineFromText(text, int4)
+	RETURNS geometry
+	AS '
+	SELECT CASE
+	WHEN geometrytype(GeomFromText($1, $2)) = ''MULTILINESTRING''
+	THEN GeomFromText($1,$2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MLineFromText(text)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromText($1)) = ''MULTILINESTRING''
+	THEN GeomFromText($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MLineFromText(text)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromText($1)) = ''MULTILINESTRING''
+	THEN ST_GeomFromText($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MultiLineStringFromText(text)
+	RETURNS geometry
+	AS 'SELECT ST_MLineFromText($1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MultiLineStringFromText(text)
+	RETURNS geometry
+	AS 'SELECT ST_MLineFromText($1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MultiLineStringFromText(text, int4)
+	RETURNS geometry
+	AS 'SELECT MLineFromText($1, $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MultiLineStringFromText(text, int4)
+	RETURNS geometry
+	AS 'SELECT MLineFromText($1, $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MPointFromText(text, int4)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromText($1,$2)) = ''MULTIPOINT''
+	THEN GeomFromText($1,$2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: MPointFromText(text, int4)
+CREATE OR REPLACE FUNCTION ST_MPointFromText(text, int4)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromText($1, $2)) = ''MULTIPOINT''
+	THEN GeomFromText($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MPointFromText(text)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromText($1)) = ''MULTIPOINT''
+	THEN GeomFromText($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MPointFromText(text)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromText($1)) = ''MULTIPOINT''
+	THEN ST_GeomFromText($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MultiPointFromText(text, int4)
+	RETURNS geometry
+	AS 'SELECT MPointFromText($1, $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MultiPointFromText(text)
+	RETURNS geometry
+	AS 'SELECT MPointFromText($1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MultiPointFromText(text)
+	RETURNS geometry
+	AS 'SELECT ST_MPointFromText($1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MultiPointFromText(text)
+	RETURNS geometry
+	AS 'SELECT MPointFromText($1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MultiPointFromText(text)
+	RETURNS geometry
+	AS 'SELECT MPointFromText($1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MPolyFromText(text, int4)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromText($1, $2)) = ''MULTIPOLYGON''
+	THEN GeomFromText($1,$2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: MPolyFromText(text, int4)
+CREATE OR REPLACE FUNCTION ST_MPolyFromText(text, int4)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromText($1, $2)) = ''MULTIPOLYGON''
+	THEN ST_GeomFromText($1,$2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MPolyFromText(text)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromText($1)) = ''MULTIPOLYGON''
+	THEN GeomFromText($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+--Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MPolyFromText(text)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromText($1)) = ''MULTIPOLYGON''
+	THEN ST_GeomFromText($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MultiPolygonFromText(text, int4)
+	RETURNS geometry
+	AS 'SELECT MPolyFromText($1, $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MultiPolygonFromText(text, int4)
+	RETURNS geometry
+	AS 'SELECT MPolyFromText($1, $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MultiPolygonFromText(text)
+	RETURNS geometry
+	AS 'SELECT MPolyFromText($1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MultiPolygonFromText(text)
+	RETURNS geometry
+	AS 'SELECT MPolyFromText($1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION GeomCollFromText(text, int4)
+	RETURNS geometry
+	AS '
+	SELECT CASE
+	WHEN geometrytype(GeomFromText($1, $2)) = ''GEOMETRYCOLLECTION''
+	THEN GeomFromText($1,$2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_GeomCollFromText(text, int4)
+	RETURNS geometry
+	AS '
+	SELECT CASE
+	WHEN geometrytype(ST_GeomFromText($1, $2)) = ''GEOMETRYCOLLECTION''
+	THEN ST_GeomFromText($1,$2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION GeomCollFromText(text)
+	RETURNS geometry
+	AS '
+	SELECT CASE
+	WHEN geometrytype(GeomFromText($1)) = ''GEOMETRYCOLLECTION''
+	THEN GeomFromText($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_GeomCollFromText(text)
+	RETURNS geometry
+	AS '
+	SELECT CASE
+	WHEN geometrytype(ST_GeomFromText($1)) = ''GEOMETRYCOLLECTION''
+	THEN ST_GeomFromText($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION GeomFromWKB(bytea)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_from_WKB'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_GeomFromWKB(bytea)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_from_WKB'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION GeomFromWKB(bytea, int)
+	RETURNS geometry
+	AS 'SELECT setSRID(GeomFromWKB($1), $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: GeomFromWKB(bytea, int)
+CREATE OR REPLACE FUNCTION ST_GeomFromWKB(bytea, int)
+	RETURNS geometry
+	AS 'SELECT ST_SetSRID(ST_GeomFromWKB($1), $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION PointFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1, $2)) = ''POINT''
+	THEN GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: PointFromWKB(bytea, int)
+CREATE OR REPLACE FUNCTION ST_PointFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1, $2)) = ''POINT''
+	THEN ST_GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION PointFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1)) = ''POINT''
+	THEN GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_PointFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1)) = ''POINT''
+	THEN ST_GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION LineFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1, $2)) = ''LINESTRING''
+	THEN GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: LineFromWKB(bytea, int)
+CREATE OR REPLACE FUNCTION ST_LineFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1, $2)) = ''LINESTRING''
+	THEN ST_GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION LineFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1)) = ''LINESTRING''
+	THEN GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_LineFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1)) = ''LINESTRING''
+	THEN ST_GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION LinestringFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1, $2)) = ''LINESTRING''
+	THEN GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_LinestringFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1, $2)) = ''LINESTRING''
+	THEN ST_GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION LinestringFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1)) = ''LINESTRING''
+	THEN GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_LinestringFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1)) = ''LINESTRING''
+	THEN GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION PolyFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1, $2)) = ''POLYGON''
+	THEN GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: PolyFromWKB(text, int)
+CREATE OR REPLACE FUNCTION ST_PolyFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1, $2)) = ''POLYGON''
+	THEN ST_GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION PolyFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1)) = ''POLYGON''
+	THEN GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_PolyFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1)) = ''POLYGON''
+	THEN ST_GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION PolygonFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1,$2)) = ''POLYGON''
+	THEN GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_PolygonFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1,$2)) = ''POLYGON''
+	THEN ST_GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION PolygonFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1)) = ''POLYGON''
+	THEN GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_PolygonFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1)) = ''POLYGON''
+	THEN GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MPointFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1,$2)) = ''MULTIPOINT''
+	THEN GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: MPointFromWKB(text, int)
+CREATE OR REPLACE FUNCTION ST_MPointFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1, $2)) = ''MULTIPOINT''
+	THEN GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MPointFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1)) = ''MULTIPOINT''
+	THEN GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MPointFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1)) = ''MULTIPOINT''
+	THEN ST_GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MultiPointFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1,$2)) = ''MULTIPOINT''
+	THEN GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MultiPointFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1,$2)) = ''MULTIPOINT''
+	THEN ST_GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MultiPointFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1)) = ''MULTIPOINT''
+	THEN GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MultiPointFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1)) = ''MULTIPOINT''
+	THEN ST_GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MultiLineFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1, $2)) = ''MULTILINESTRING''
+	THEN GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION MultiLineFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1, $2)) = ''MULTILINESTRING''
+	THEN GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MultiLineFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1)) = ''MULTILINESTRING''
+	THEN GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MultiLineFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1)) = ''MULTILINESTRING''
+	THEN ST_GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MLineFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1, $2)) = ''MULTILINESTRING''
+	THEN GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: MLineFromWKB(text, int)
+CREATE OR REPLACE FUNCTION ST_MLineFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1, $2)) = ''MULTILINESTRING''
+	THEN ST_GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MLineFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1)) = ''MULTILINESTRING''
+	THEN GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MLineFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1)) = ''MULTILINESTRING''
+	THEN ST_GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MPolyFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1, $2)) = ''MULTIPOLYGON''
+	THEN GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: MPolyFromWKB(bytea, int)
+CREATE OR REPLACE FUNCTION ST_MPolyFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1, $2)) = ''MULTIPOLYGON''
+	THEN ST_GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MPolyFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1)) = ''MULTIPOLYGON''
+	THEN GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MPolyFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1)) = ''MULTIPOLYGON''
+	THEN ST_GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MultiPolyFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1, $2)) = ''MULTIPOLYGON''
+	THEN GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MultiPolyFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1, $2)) = ''MULTIPOLYGON''
+	THEN ST_GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION MultiPolyFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(GeomFromWKB($1)) = ''MULTIPOLYGON''
+	THEN GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_MultiPolyFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE WHEN geometrytype(ST_GeomFromWKB($1)) = ''MULTIPOLYGON''
+	THEN ST_GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION GeomCollFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE
+	WHEN geometrytype(GeomFromWKB($1, $2)) = ''GEOMETRYCOLLECTION''
+	THEN GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_GeomCollFromWKB(bytea, int)
+	RETURNS geometry
+	AS '
+	SELECT CASE
+	WHEN geometrytype(GeomFromWKB($1, $2)) = ''GEOMETRYCOLLECTION''
+	THEN GeomFromWKB($1, $2)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION GeomCollFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE
+	WHEN geometrytype(GeomFromWKB($1)) = ''GEOMETRYCOLLECTION''
+	THEN GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_GeomCollFromWKB(bytea)
+	RETURNS geometry
+	AS '
+	SELECT CASE
+	WHEN geometrytype(ST_GeomFromWKB($1)) = ''GEOMETRYCOLLECTION''
+	THEN ST_GeomFromWKB($1)
+	ELSE NULL END
+	'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+--New functions
+
+-- Maximum distance between linestrings.
+
+CREATE OR REPLACE FUNCTION max_distance(geometry,geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5', 'LWGEOM_maxdistance2d_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT; 
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION _ST_MaxDistance(geometry,geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5', 'LWGEOM_maxdistance2d_linestring'
+	LANGUAGE 'C' IMMUTABLE STRICT; 
+	
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_MaxDistance(geometry,geometry)
+	RETURNS float8
+	AS 'SELECT _ST_MaxDistance(ST_ConvexHull($1), ST_ConvexHull($2))'
+	LANGUAGE 'SQL' IMMUTABLE STRICT; 
+
+CREATE OR REPLACE FUNCTION ST_ClosestPoint(geometry,geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_closestpoint'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION ST_ShortestLine(geometry,geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_shortestline2d'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION _ST_LongestLine(geometry,geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_longestline2d'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION ST_LongestLine(geometry,geometry)
+	RETURNS geometry
+	AS 'SELECT _ST_LongestLine(ST_ConvexHull($1), ST_ConvexHull($2))'
+	LANGUAGE 'SQL' IMMUTABLE STRICT; 
+
+CREATE OR REPLACE FUNCTION _ST_DFullyWithin(geometry,geometry,float8)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5', 'LWGEOM_dfullywithin'
+	LANGUAGE 'C' IMMUTABLE STRICT; 
+
+CREATE OR REPLACE FUNCTION ST_DFullyWithin(geometry, geometry, float8)
+	RETURNS boolean
+	AS 'SELECT $1 && ST_Expand($2,$3) AND $2 && ST_Expand($1,$3) AND _ST_DFullyWithin(ST_ConvexHull($1), ST_ConvexHull($2), $3)'
+	LANGUAGE 'SQL' IMMUTABLE; 
+	
+	
+--
+-- SFSQL 1.1
+--
+-- BdPolyFromText(multiLineStringTaggedText String, SRID Integer): Polygon
+--
+--  Construct a Polygon given an arbitrary
+--  collection of closed linestrings as a
+--  MultiLineString text representation.
+--
+-- This is a PLPGSQL function rather then an SQL function
+-- To avoid double call of BuildArea (one to get GeometryType
+-- and another to actual return, in a CASE WHEN construct).
+-- Also, we profit from plpgsql to RAISE exceptions.
+--
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION BdPolyFromText(text, integer)
+RETURNS geometry
+AS $$
+DECLARE
+	geomtext alias for $1;
+	srid alias for $2;
+	mline geometry;
+	geom geometry;
+BEGIN
+	mline := MultiLineStringFromText(geomtext, srid);
+
+	IF mline IS NULL
+	THEN
+		RAISE EXCEPTION 'Input is not a MultiLinestring';
+	END IF;
+
+	geom := BuildArea(mline);
+
+	IF GeometryType(geom) != 'POLYGON'
+	THEN
+		RAISE EXCEPTION 'Input returns more then a single polygon, try using BdMPolyFromText instead';
+	END IF;
+
+	RETURN geom;
+END;
+$$
+LANGUAGE 'plpgsql' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_BdPolyFromText(text, integer)
+RETURNS geometry
+AS $$
+DECLARE
+	geomtext alias for $1;
+	srid alias for $2;
+	mline geometry;
+	geom geometry;
+BEGIN
+	mline := ST_MultiLineStringFromText(geomtext, srid);
+
+	IF mline IS NULL
+	THEN
+		RAISE EXCEPTION 'Input is not a MultiLinestring';
+	END IF;
+
+	geom := ST_BuildArea(mline);
+
+	IF GeometryType(geom) != 'POLYGON'
+	THEN
+		RAISE EXCEPTION 'Input returns more then a single polygon, try using BdMPolyFromText instead';
+	END IF;
+
+	RETURN geom;
+END;
+$$
+LANGUAGE 'plpgsql' IMMUTABLE STRICT;
+
+--
+-- SFSQL 1.1
+--
+-- BdMPolyFromText(multiLineStringTaggedText String, SRID Integer): MultiPolygon
+--
+--  Construct a MultiPolygon given an arbitrary
+--  collection of closed linestrings as a
+--  MultiLineString text representation.
+--
+-- This is a PLPGSQL function rather then an SQL function
+-- To raise an exception in case of invalid input.
+--
+-- Deprecation in 1.2.3
+CREATE OR REPLACE FUNCTION BdMPolyFromText(text, integer)
+RETURNS geometry
+AS $$
+DECLARE
+	geomtext alias for $1;
+	srid alias for $2;
+	mline geometry;
+	geom geometry;
+BEGIN
+	mline := MultiLineStringFromText(geomtext, srid);
+
+	IF mline IS NULL
+	THEN
+		RAISE EXCEPTION 'Input is not a MultiLinestring';
+	END IF;
+
+	geom := multi(BuildArea(mline));
+
+	RETURN geom;
+END;
+$$
+LANGUAGE 'plpgsql' IMMUTABLE STRICT;
+
+-- Availability: 1.2.2
+CREATE OR REPLACE FUNCTION ST_BdMPolyFromText(text, integer)
+RETURNS geometry
+AS $$
+DECLARE
+	geomtext alias for $1;
+	srid alias for $2;
+	mline geometry;
+	geom geometry;
+BEGIN
+	mline := ST_MultiLineStringFromText(geomtext, srid);
+
+	IF mline IS NULL
+	THEN
+		RAISE EXCEPTION 'Input is not a MultiLinestring';
+	END IF;
+
+	geom := multi(ST_BuildArea(mline));
+
+	RETURN geom;
+END;
+$$
+LANGUAGE 'plpgsql' IMMUTABLE STRICT;
+
+
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+-- 
+-- $Id: long_xact.sql.in.c 4894 2009-11-25 19:15:57Z pramsey $
+--
+-- PostGIS - Spatial Types for PostgreSQL
+-- http://postgis.refractions.net
+-- Copyright 2001-2003 Refractions Research Inc.
+--
+-- This is free software; you can redistribute and/or modify it under
+-- the terms of the GNU General Public Licence. See the COPYING file.
+--  
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+
+
+
+-----------------------------------------------------------------------
+-- LONG TERM LOCKING
+-----------------------------------------------------------------------
+
+-- UnlockRows(authid)
+-- removes all locks held by the given auth
+-- returns the number of locks released
+CREATE OR REPLACE FUNCTION UnlockRows(text)
+	RETURNS int
+	AS $$ 
+DECLARE
+	ret int;
+BEGIN
+
+	IF NOT LongTransactionsEnabled() THEN
+		RAISE EXCEPTION 'Long transaction support disabled, use EnableLongTransaction() to enable.';
+	END IF;
+
+	EXECUTE 'DELETE FROM authorization_table where authid = ' ||
+		quote_literal($1);
+
+	GET DIAGNOSTICS ret = ROW_COUNT;
+
+	RETURN ret;
+END;
+$$
+LANGUAGE 'plpgsql'  VOLATILE STRICT;
+
+-- LockRow([schema], table, rowid, auth, [expires]) 
+-- Returns 1 if successfully obtained the lock, 0 otherwise
+CREATE OR REPLACE FUNCTION LockRow(text, text, text, text, timestamp)
+	RETURNS int
+	AS $$ 
+DECLARE
+	myschema alias for $1;
+	mytable alias for $2;
+	myrid   alias for $3;
+	authid alias for $4;
+	expires alias for $5;
+	ret int;
+	mytoid oid;
+	myrec RECORD;
+	
+BEGIN
+
+	IF NOT LongTransactionsEnabled() THEN
+		RAISE EXCEPTION 'Long transaction support disabled, use EnableLongTransaction() to enable.';
+	END IF;
+
+	EXECUTE 'DELETE FROM authorization_table WHERE expires < now()'; 
+
+	SELECT c.oid INTO mytoid FROM pg_class c, pg_namespace n
+		WHERE c.relname = mytable
+		AND c.relnamespace = n.oid
+		AND n.nspname = myschema;
+
+	-- RAISE NOTICE 'toid: %', mytoid;
+
+	FOR myrec IN SELECT * FROM authorization_table WHERE 
+		toid = mytoid AND rid = myrid
+	LOOP
+		IF myrec.authid != authid THEN
+			RETURN 0;
+		ELSE
+			RETURN 1;
+		END IF;
+	END LOOP;
+
+	EXECUTE 'INSERT INTO authorization_table VALUES ('||
+		quote_literal(mytoid::text)||','||quote_literal(myrid)||
+		','||quote_literal(expires::text)||
+		','||quote_literal(authid) ||')';
+
+	GET DIAGNOSTICS ret = ROW_COUNT;
+
+	RETURN ret;
+END;
+$$
+LANGUAGE 'plpgsql'  VOLATILE STRICT;
+
+-- LockRow(schema, table, rid, authid);
+CREATE OR REPLACE FUNCTION LockRow(text, text, text, text)
+	RETURNS int
+	AS
+$$ SELECT LockRow($1, $2, $3, $4, now()::timestamp+'1:00'); $$
+	LANGUAGE 'sql'  VOLATILE STRICT;
+
+-- LockRow(table, rid, authid);
+CREATE OR REPLACE FUNCTION LockRow(text, text, text)
+	RETURNS int
+	AS
+$$ SELECT LockRow(current_schema(), $1, $2, $3, now()::timestamp+'1:00'); $$
+	LANGUAGE 'sql'  VOLATILE STRICT;
+
+-- LockRow(schema, table, rid, expires);
+CREATE OR REPLACE FUNCTION LockRow(text, text, text, timestamp)
+	RETURNS int
+	AS
+$$ SELECT LockRow(current_schema(), $1, $2, $3, $4); $$
+	LANGUAGE 'sql'  VOLATILE STRICT;
+
+
+CREATE OR REPLACE FUNCTION AddAuth(text)
+	RETURNS BOOLEAN
+	AS $$ 
+DECLARE
+	lockid alias for $1;
+	okay boolean;
+	myrec record;
+BEGIN
+	-- check to see if table exists
+	--  if not, CREATE TEMP TABLE mylock (transid xid, lockcode text)
+	okay := 'f';
+	FOR myrec IN SELECT * FROM pg_class WHERE relname = 'temp_lock_have_table' LOOP
+		okay := 't';
+	END LOOP; 
+	IF (okay <> 't') THEN 
+		CREATE TEMP TABLE temp_lock_have_table (transid xid, lockcode text);
+			-- this will only work from pgsql7.4 up
+			-- ON COMMIT DELETE ROWS;
+	END IF;
+
+	--  INSERT INTO mylock VALUES ( $1)
+--	EXECUTE 'INSERT INTO temp_lock_have_table VALUES ( '||
+--		quote_literal(getTransactionID()) || ',' ||
+--		quote_literal(lockid) ||')';
+
+	INSERT INTO temp_lock_have_table VALUES (getTransactionID(), lockid);
+
+	RETURN true::boolean;
+END;
+$$
+LANGUAGE PLPGSQL;
+ 
+
+-- CheckAuth( <schema>, <table>, <ridcolumn> )
+--
+-- Returns 0
+--
+CREATE OR REPLACE FUNCTION CheckAuth(text, text, text)
+	RETURNS INT
+	AS $$ 
+DECLARE
+	schema text;
+BEGIN
+	IF NOT LongTransactionsEnabled() THEN
+		RAISE EXCEPTION 'Long transaction support disabled, use EnableLongTransaction() to enable.';
+	END IF;
+
+	if ( $1 != '' ) THEN
+		schema = $1;
+	ELSE
+		SELECT current_schema() into schema;
+	END IF;
+
+	-- TODO: check for an already existing trigger ?
+
+	EXECUTE 'CREATE TRIGGER check_auth BEFORE UPDATE OR DELETE ON ' 
+		|| quote_ident(schema) || '.' || quote_ident($2)
+		||' FOR EACH ROW EXECUTE PROCEDURE CheckAuthTrigger('
+		|| quote_literal($3) || ')';
+
+	RETURN 0;
+END;
+$$
+LANGUAGE 'plpgsql';
+
+-- CheckAuth(<table>, <ridcolumn>)
+CREATE OR REPLACE FUNCTION CheckAuth(text, text)
+	RETURNS INT
+	AS
+	$$ SELECT CheckAuth('', $1, $2) $$
+	LANGUAGE 'SQL';
+
+CREATE OR REPLACE FUNCTION CheckAuthTrigger()
+	RETURNS trigger AS 
+	'$libdir/postgis-1.5', 'check_authorization'
+	LANGUAGE C;
+
+CREATE OR REPLACE FUNCTION GetTransactionID()
+	RETURNS xid AS 
+	'$libdir/postgis-1.5', 'getTransactionID'
+	LANGUAGE C;
+
+
+--
+-- Enable Long transactions support
+--
+--  Creates the authorization_table if not already existing
+--
+CREATE OR REPLACE FUNCTION EnableLongTransactions()
+	RETURNS TEXT
+	AS $$ 
+DECLARE
+	"query" text;
+	exists bool;
+	rec RECORD;
+
+BEGIN
+
+	exists = 'f';
+	FOR rec IN SELECT * FROM pg_class WHERE relname = 'authorization_table'
+	LOOP
+		exists = 't';
+	END LOOP;
+
+	IF NOT exists
+	THEN
+		"query" = 'CREATE TABLE authorization_table (
+			toid oid, -- table oid
+			rid text, -- row id
+			expires timestamp,
+			authid text
+		)';
+		EXECUTE "query";
+	END IF;
+
+	exists = 'f';
+	FOR rec IN SELECT * FROM pg_class WHERE relname = 'authorized_tables'
+	LOOP
+		exists = 't';
+	END LOOP;
+
+	IF NOT exists THEN
+		"query" = 'CREATE VIEW authorized_tables AS ' ||
+			'SELECT ' ||
+			'n.nspname as schema, ' ||
+			'c.relname as table, trim(' ||
+			quote_literal(chr(92) || '000') ||
+			' from t.tgargs) as id_column ' ||
+			'FROM pg_trigger t, pg_class c, pg_proc p ' ||
+			', pg_namespace n ' ||
+			'WHERE p.proname = ' || quote_literal('checkauthtrigger') ||
+			' AND c.relnamespace = n.oid' ||
+			' AND t.tgfoid = p.oid and t.tgrelid = c.oid';
+		EXECUTE "query";
+	END IF;
+
+	RETURN 'Long transactions support enabled';
+END;
+$$
+LANGUAGE 'plpgsql';
+
+--
+-- Check if Long transactions support is enabled
+--
+CREATE OR REPLACE FUNCTION LongTransactionsEnabled()
+	RETURNS bool
+AS $$ 
+DECLARE
+	rec RECORD;
+BEGIN
+	FOR rec IN SELECT oid FROM pg_class WHERE relname = 'authorized_tables'
+	LOOP
+		return 't';
+	END LOOP;
+	return 'f';
+END;
+$$
+LANGUAGE 'plpgsql';
+
+--
+-- Disable Long transactions support
+--
+--  (1) Drop any long_xact trigger 
+--  (2) Drop the authorization_table
+--  (3) KEEP the authorized_tables view
+--
+CREATE OR REPLACE FUNCTION DisableLongTransactions()
+	RETURNS TEXT
+	AS $$ 
+DECLARE
+	rec RECORD;
+
+BEGIN
+
+	--
+	-- Drop all triggers applied by CheckAuth()
+	--
+	FOR rec IN
+		SELECT c.relname, t.tgname, t.tgargs FROM pg_trigger t, pg_class c, pg_proc p
+		WHERE p.proname = 'checkauthtrigger' and t.tgfoid = p.oid and t.tgrelid = c.oid
+	LOOP
+		EXECUTE 'DROP TRIGGER ' || quote_ident(rec.tgname) ||
+			' ON ' || quote_ident(rec.relname);
+	END LOOP;
+
+	--
+	-- Drop the authorization_table table
+	--
+	FOR rec IN SELECT * FROM pg_class WHERE relname = 'authorization_table' LOOP
+		DROP TABLE authorization_table;
+	END LOOP;
+
+	--
+	-- Drop the authorized_tables view
+	--
+	FOR rec IN SELECT * FROM pg_class WHERE relname = 'authorized_tables' LOOP
+		DROP VIEW authorized_tables;
+	END LOOP;
+
+	RETURN 'Long transactions support disabled';
+END;
+$$
+LANGUAGE 'plpgsql';
+
+---------------------------------------------------------------
+-- END
+---------------------------------------------------------------
+
+
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+-- 
+-- $Id: sqlmm.sql.in.c 4894 2009-11-25 19:15:57Z pramsey $
+--
+-- PostGIS - Spatial Types for PostgreSQL
+-- http://postgis.refractions.net
+-- Copyright 2001-2003 Refractions Research Inc.
+--
+-- This is free software; you can redistribute and/or modify it under
+-- the terms of the GNU General Public Licence. See the COPYING file.
+--  
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+-- This file defines a subset of SQL/MM functions (that is, only those
+-- currently defined by ESRI's ArcSDE). Since these functions already exist
+-- in PostGIS (for the most part), these functions simply expose the current
+-- functions. Although mostly complying with SQL/MM standards, these prototypes
+-- follow ESRI's ArcSDE SQL Specifications and not SQL/MM standards where 
+-- disparities exist.
+--
+-- Specification Disparity Notes:
+--   * ST_OrderingEquals(geometry, geometry) is implemented as per
+--     ESRI's ArcSDE SQL specifications, not SQL/MM specifications.
+--     (http://edndoc.esri.com/arcsde/9.1/sql_api/sqlapi3.htm#ST_OrderingEquals)
+--   * Geometry constructors default to an SRID of -1, not 0 as per SQL/MM specs.
+--   * Boolean return type methods (ie. ST_IsValid, ST_IsEmpty, ...)
+--      * SQL/MM           : RETURNS 1 if TRUE, 0 if (FALSE, NULL)
+--      * ESRI in Informix : RETURNS 1 if (TRUE, NULL), 0 if FALSE
+--      * ESRI in DB2      : RETURNS 1 if TRUE, 0 if FALSE, NULL if NULL 
+--      * PostGIS          : RETURNS 1 if TRUE, 0 if FALSE, NULL if NULL 
+--
+-- TODO: Implement ESRI's Shape constructors
+--   * SE_AsShape(geometry)
+--   * SE_ShapeToSQL
+--   * SE_GeomFromShape
+--   * SE_PointFromShape
+--   * SE_LineFromShape
+--   * SE_PolyFromShape
+--   * SE_MPointFromShape
+--   * SE_MLineFromShape
+--   * SE_MPolyFromShape
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions for constructing an ST_Geometry
+--     value given its WTK representation
+-- (http://edndoc.esri.com/arcsde/9.1/general_topics/storing_geo_in_rdbms.html)
+-------------------------------------------------------------------------------
+
+-- PostGIS equivalent function: ST_GeometryFromText(text)
+-- Note: Defaults to an SRID=-1, not 0 as per SQL/MM specs.
+CREATE OR REPLACE FUNCTION ST_WKTToSQL(text)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_from_text'
+	LANGUAGE 'C' IMMUTABLE STRICT; 
+
+-- ST_GeomFromText(text, int4) - already defined
+-- ST_PointFromText(text, int4) - already defined
+-- ST_LineFromText(text, int4) - already defined
+-- ST_PolyFromText(text, int4) - already defined
+-- ST_MPointFromText(text, int4) - already defined
+-- ST_MLineFromText(text, int4) - already defined
+-- ST_MPolyFromText(text, int4) - already defined
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions for constructing an ST_Geometry
+--     value given its WKB representation
+-------------------------------------------------------------------------------
+
+-- PostGIS equivalent function: GeomFromWKB(bytea))
+-- Note: Defaults to an SRID=-1, not 0 as per SQL/MM specs.
+
+CREATE OR REPLACE FUNCTION ST_WKBToSQL(bytea)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','LWGEOM_from_WKB'
+	LANGUAGE 'C' IMMUTABLE STRICT; 
+
+-- ST_GeomFromWKB(bytea, int) - already defined
+-- ST_PointFromWKB(bytea, int) - already defined
+-- ST_LineFromWKB(bytea, int) - already defined
+-- ST_PolyFromWKB(bytea, int) - already defined
+-- ST_MPointFromWKB(bytea, int) - already defined
+-- ST_MLineFromWKB(bytea, int) - already defined
+-- ST_MPolyFromWKB(bytea, int) - already defined
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions for constructing an ST_Geometry
+--     value given an ESRI Shape representation
+-------------------------------------------------------------------------------
+
+-- TODO: SE_ShapeToSQL
+-- TODO: SE_GeomFromShape
+-- TODO: SE_PointFromShape
+-- TODO: SE_LineFromShape
+-- TODO: SE_PolyFromShape
+-- TODO: SE_MPointFromShape
+-- TODO: SE_MLineFromShape
+-- TODO: SE_MPolyFromShape
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions for obtaining the WKT representation
+--     of an ST_Geometry
+-------------------------------------------------------------------------------
+
+-- ST_AsText(geometry) - already defined
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions for obtaining the WKB representation
+--     of an ST_Geometry
+-------------------------------------------------------------------------------
+
+-- ST_AsBinary(geometry) - already defined
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions for obtaining the ESRI Shape 
+-- representation of an ST_Geometry
+-------------------------------------------------------------------------------
+
+-- TODO: SE_AsShape(geometry)
+--CREATE OR REPLACE FUNCTION SE_AsShape(geometry)
+--    RETURNS bytea
+--    AS '$libdir/postgis-1.5','LWGEOM_AsShape'
+--    LANGUAGE 'C' IMMUTABLE STRICT; 
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions on type ST_Geometry
+-------------------------------------------------------------------------------
+
+-- PostGIS equivalent function: ndims(geometry)
+CREATE OR REPLACE FUNCTION ST_CoordDim(geometry)
+	RETURNS smallint
+	AS '$libdir/postgis-1.5', 'LWGEOM_ndims'
+	LANGUAGE 'C' IMMUTABLE STRICT; 
+
+-- ST_Dimension(geometry) - already defined.
+-- ST_GeometryType(geometry) - already defined.
+-- ST_SRID(geometry) - already defined.
+-- ST_IsEmpty(geometry) - already defined.
+-- ST_IsSimple(geometry) - already defined.
+-- ST_IsValid(geometry) - already defined.
+-- ST_Boundary(geometry) - already defined.
+-- ST_Envelope(geometry) - already defined.
+-- ST_Transform(geometry) - already defined.
+-- ST_AsText(geometry) - already defined.
+-- ST_AsBinary(geometry) - already defined.
+-- SE_AsShape(geometry) - already defined.
+-- ST_X(geometry) - already defined.
+-- ST_Y(geometry) - already defined.
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION _ST_OrderingEquals(geometry, geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5', 'LWGEOM_same'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.3.0
+CREATE OR REPLACE FUNCTION ST_OrderingEquals(geometry, geometry)
+	RETURNS boolean
+	AS $$ 
+	SELECT $1 ~= $2 AND _ST_OrderingEquals($1, $2)
+	$$	
+	LANGUAGE 'SQL' IMMUTABLE STRICT; 
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION SE_Is3D(geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_hasz'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION SE_IsMeasured(geometry)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'LWGEOM_hasm'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions on type ST_Point
+-------------------------------------------------------------------------------
+
+-- PostGIS equivalent function: makePoint(float8,float8)
+CREATE OR REPLACE FUNCTION ST_Point(float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_makepoint'
+	LANGUAGE 'C' IMMUTABLE STRICT; 
+
+-- PostGIS equivalent function: Z(geometry)
+CREATE OR REPLACE FUNCTION SE_Z(geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5','LWGEOM_z_point'
+	LANGUAGE 'C' IMMUTABLE STRICT; 
+
+-- PostGIS equivalent function: M(geometry)
+CREATE OR REPLACE FUNCTION SE_M(geometry)
+	RETURNS float8
+	AS '$libdir/postgis-1.5','LWGEOM_m_point'
+	LANGUAGE 'C' IMMUTABLE STRICT; 
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions on type ST_Curve
+-------------------------------------------------------------------------------
+
+-- ST_StartPoint(geometry) - already defined.
+-- ST_EndPoint(geometry) - already defined.
+-- ST_IsClosed(geometry) - already defined.
+-- ST_IsRing(geometry) - already defined.
+-- ST_Length(geometry) - already defined.
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions on type ST_LineString
+-------------------------------------------------------------------------------
+
+-- ST_NumPoints(geometry) - already defined.
+-- ST_PointN(geometry) - already defined.
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions on type ST_Surface
+-------------------------------------------------------------------------------
+
+-- ST_Centroid(geometry) - already defined.
+-- ST_PointOnSurface(geometry) - already defined.
+-- ST_Area(geometry) - already defined.
+-- ST_Perimeter(geometry) - already defined.
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions on type ST_Polygon
+-------------------------------------------------------------------------------
+
+-- PostGIS equivalent function: MakePolygon(geometry)
+CREATE OR REPLACE FUNCTION ST_Polygon(geometry, int)
+	RETURNS geometry
+	AS $$ 
+	SELECT setSRID(makepolygon($1), $2)
+	$$	
+	LANGUAGE 'SQL' IMMUTABLE STRICT; 
+
+-- ST_ExteriorRing(geometry) - already defined.
+-- ST_NumInteriorRing(geometry) - already defined.
+-- ST_InteriorRingN(geometry, integer) - already defined.
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions on type ST_GeomCollection
+-------------------------------------------------------------------------------
+
+-- ST_NumGeometries(geometry) - already defined.
+-- ST_GeometryN(geometry, integer) - already defined.
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions on type ST_MultiCurve
+-------------------------------------------------------------------------------
+
+-- ST_IsClosed(geometry) - already defined.
+-- ST_Length(geometry) - already defined.
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions on type ST_MultiSurface
+-------------------------------------------------------------------------------
+
+-- ST_Centroid(geometry) - already defined.
+-- ST_PointOnSurface(geometry) - already defined.
+-- ST_Area(geometry) - already defined.
+-- ST_Perimeter(geometry) - already defined.
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions that test spatial relationships
+-------------------------------------------------------------------------------
+
+-- ST_Equals(geometry, geometry) - already defined.
+-- ST_Disjoint(geometry, geometry) - already defined.
+-- ST_Touches(geometry, geometry) - already defined.
+-- ST_Within(geometry, geometry) - already defined.
+-- ST_Overlaps(geometry, geometry) - already defined.
+-- ST_Crosses(geometry, geometry) - already defined.
+-- ST_Intersects(geometry, geometry) - already defined.
+-- ST_Contains(geometry, geometry) - already defined.
+-- ST_Relate(geometry, geometry, text) - already defined.
+
+-- PostGIS equivalent function: none
+CREATE OR REPLACE FUNCTION SE_EnvelopesIntersect(geometry,geometry)
+	RETURNS boolean
+	AS $$ 
+	SELECT $1 && $2
+	$$	
+	LANGUAGE 'SQL' IMMUTABLE STRICT; 
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions for distance relationships
+-------------------------------------------------------------------------------
+
+-- ST_Distance(geometry, geometry) - already defined.
+
+-------------------------------------------------------------------------------
+-- SQL/MM (ArcSDE subset) - SQL Functions that implement spatial operators
+-------------------------------------------------------------------------------
+
+-- ST_Intersection(geometry, geometry) - already defined.
+-- ST_Difference(geometry, geometry) - already defined.
+-- ST_Union(geometry, geometry) - already defined.
+-- ST_SymDifference(geometry, geometry) - already defined.
+-- ST_Buffer(geometry, float8) - already defined.
+-- ST_ConvexHull(geometry) already defined.
+
+-- PostGIS equivalent function: locate_along_measure(geometry, float8)
+CREATE OR REPLACE FUNCTION SE_LocateAlong(geometry, float8)
+	RETURNS geometry
+	AS $$ SELECT locate_between_measures($1, $2, $2) $$
+	LANGUAGE 'sql' IMMUTABLE STRICT;
+
+-- PostGIS equivalent function: locate_between_measures(geometry, float8, float8)
+CREATE OR REPLACE FUNCTION SE_LocateBetween(geometry, float8, float8)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_locate_between_m'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+
+
+-------------------------------------------------------------------------------
+-- END
+-------------------------------------------------------------------------------
+
+
+---------------------------------------------------------------------------
+-- $Id: geography.sql.in.c 5065 2009-12-30 01:25:17Z pramsey $
+--
+-- PostGIS - Spatial Types for PostgreSQL
+-- Copyright 2009 Paul Ramsey <pramsey@cleverelephant.ca>
+--
+-- This is free software; you can redistribute and/or modify it under
+-- the terms of the GNU General Public Licence. See the COPYING file.
+--
+---------------------------------------------------------------------------
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_typmod_in(cstring[])
+	RETURNS integer
+	AS '$libdir/postgis-1.5','geography_typmod_in'
+	LANGUAGE 'C' IMMUTABLE STRICT; 
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_typmod_out(integer)
+	RETURNS cstring
+	AS '$libdir/postgis-1.5','geography_typmod_out'
+	LANGUAGE 'C' IMMUTABLE STRICT; 
+	
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_in(cstring, oid, integer)
+	RETURNS geography
+	AS '$libdir/postgis-1.5','geography_in'
+	LANGUAGE 'C' IMMUTABLE STRICT; 
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_out(geography)
+	RETURNS cstring
+	AS '$libdir/postgis-1.5','geography_out'
+	LANGUAGE 'C' IMMUTABLE STRICT; 
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_analyze(internal)
+	RETURNS bool
+	AS '$libdir/postgis-1.5','geography_analyze'
+	LANGUAGE 'C' VOLATILE STRICT; 
+
+-- Availability: 1.5.0
+CREATE TYPE geography (
+	internallength = variable,
+	input = geography_in,
+	output = geography_out,
+	typmod_in = geography_typmod_in,
+	typmod_out = geography_typmod_out,
+	analyze = geography_analyze,
+	storage = main,
+	alignment = double
+);
+
+--
+-- GIDX type is used by the GiST index bindings. 
+-- In/out functions are stubs, as all access should be internal.
+---
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION gidx_in(cstring)
+	RETURNS gidx
+	AS '$libdir/postgis-1.5','gidx_in'
+	LANGUAGE 'C' IMMUTABLE STRICT; 
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION gidx_out(gidx)
+	RETURNS cstring
+	AS '$libdir/postgis-1.5','gidx_out'
+	LANGUAGE 'C' IMMUTABLE STRICT; 
+
+-- Availability: 1.5.0
+CREATE TYPE gidx (
+	internallength = variable,
+	input = gidx_in,
+	output = gidx_out,
+	storage = plain,
+	alignment = double
+);
+
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography(geography, integer, boolean)
+	RETURNS geography
+	AS '$libdir/postgis-1.5','geography_enforce_typmod'
+	LANGUAGE 'C' IMMUTABLE STRICT; 
+
+-- Availability: 1.5.0
+CREATE CAST (geography AS geography) WITH FUNCTION geography(geography, integer, boolean) AS IMPLICIT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_AsText(geography)
+	RETURNS text
+	AS '$libdir/postgis-1.5','geography_as_text'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+	
+-- Availability: 1.5.0 - this is just a hack to prevent unknown from causing ambiguous name because of geography
+-- TODO Remove in 2.0
+CREATE OR REPLACE FUNCTION ST_AsText(text)
+	RETURNS text AS
+	$$ SELECT ST_AsText($1::geometry);  $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_GeographyFromText(text)
+	RETURNS geography
+	AS '$libdir/postgis-1.5','geography_from_text'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_GeogFromText(text)
+	RETURNS geography
+	AS '$libdir/postgis-1.5','geography_from_text'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_AsBinary(geography)
+	RETURNS bytea
+	AS '$libdir/postgis-1.5','geography_as_binary'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+	
+-- Availability: 1.5.0 - this is just a hack to prevent unknown from causing ambiguous name because of geography
+-- TODO Remove in 2.0
+CREATE OR REPLACE FUNCTION ST_AsBinary(text)
+	RETURNS bytea AS
+	$$ SELECT ST_AsBinary($1::geometry);  $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_GeogFromWKB(bytea)
+	RETURNS geography
+	AS '$libdir/postgis-1.5','geography_from_binary'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_typmod_dims(integer)
+	RETURNS integer
+	AS '$libdir/postgis-1.5','geography_typmod_dims'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_typmod_srid(integer)
+	RETURNS integer
+	AS '$libdir/postgis-1.5','geography_typmod_srid'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_typmod_type(integer)
+	RETURNS text
+	AS '$libdir/postgis-1.5','geography_typmod_type'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE VIEW geography_columns AS
+	SELECT
+		current_database() AS f_table_catalog, 
+		n.nspname AS f_table_schema, 
+		c.relname AS f_table_name, 
+		a.attname AS f_geography_column,
+		geography_typmod_dims(a.atttypmod) AS coord_dimension,
+		geography_typmod_srid(a.atttypmod) AS srid,
+		geography_typmod_type(a.atttypmod) AS type
+	FROM 
+		pg_class c, 
+		pg_attribute a, 
+		pg_type t, 
+		pg_namespace n
+	WHERE c.relkind IN('r','v')
+	AND t.typname = 'geography'
+	AND a.attisdropped = false
+	AND a.atttypid = t.oid
+	AND a.attrelid = c.oid
+	AND c.relnamespace = n.oid;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography(geometry)
+	RETURNS geography
+	AS '$libdir/postgis-1.5','geography_from_geometry'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE CAST (geometry AS geography) WITH FUNCTION geography(geometry) AS IMPLICIT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geometry(geography)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5','geometry_from_geography'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE CAST (geography AS geometry) WITH FUNCTION geometry(geography) ;
+
+-- ---------- ---------- ---------- ---------- ---------- ---------- ----------
+-- GiST Support Functions
+-- ---------- ---------- ---------- ---------- ---------- ---------- ----------
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_gist_consistent(internal,geometry,int4) 
+	RETURNS bool 
+	AS '$libdir/postgis-1.5' ,'geography_gist_consistent'
+	LANGUAGE 'C';
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_gist_compress(internal) 
+	RETURNS internal 
+	AS '$libdir/postgis-1.5','geography_gist_compress'
+	LANGUAGE 'C';
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_gist_penalty(internal,internal,internal) 
+	RETURNS internal 
+	AS '$libdir/postgis-1.5' ,'geography_gist_penalty'
+	LANGUAGE 'C';
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_gist_picksplit(internal, internal) 
+	RETURNS internal 
+	AS '$libdir/postgis-1.5' ,'geography_gist_picksplit'
+	LANGUAGE 'C';
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_gist_union(bytea, internal) 
+	RETURNS internal 
+	AS '$libdir/postgis-1.5' ,'geography_gist_union'
+	LANGUAGE 'C';
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_gist_same(box2d, box2d, internal) 
+	RETURNS internal 
+	AS '$libdir/postgis-1.5' ,'geography_gist_same'
+	LANGUAGE 'C';
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_gist_decompress(internal) 
+	RETURNS internal 
+	AS '$libdir/postgis-1.5' ,'geography_gist_decompress'
+	LANGUAGE 'C';
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_gist_selectivity (internal, oid, internal, int4)
+	RETURNS float8
+	AS '$libdir/postgis-1.5', 'geography_gist_selectivity'
+	LANGUAGE 'C';
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_gist_join_selectivity(internal, oid, internal, smallint)
+	RETURNS float8
+	AS '$libdir/postgis-1.5', 'geography_gist_join_selectivity'
+	LANGUAGE 'C';
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION geography_overlaps(geography, geography) 
+	RETURNS boolean 
+	AS '$libdir/postgis-1.5' ,'geography_overlaps'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OPERATOR && (
+	LEFTARG = geography, RIGHTARG = geography, PROCEDURE = geography_overlaps,
+	COMMUTATOR = '&&',
+	RESTRICT = geography_gist_selectivity, 
+	JOIN = geography_gist_join_selectivity
+);
+
+
+-- Availability: 1.5.0
+CREATE OPERATOR CLASS gist_geography_ops
+	DEFAULT FOR TYPE geography USING GIST AS
+	STORAGE 	gidx,
+	OPERATOR        3        &&	,
+--	OPERATOR        6        ~=	,
+--	OPERATOR        7        ~	,
+--	OPERATOR        8        @	,
+	FUNCTION        1        geography_gist_consistent (internal, geometry, int4),
+	FUNCTION        2        geography_gist_union (bytea, internal),
+	FUNCTION        3        geography_gist_compress (internal),
+	FUNCTION        4        geography_gist_decompress (internal),
+	FUNCTION        5        geography_gist_penalty (internal, internal, internal),
+	FUNCTION        6        geography_gist_picksplit (internal, internal),
+	FUNCTION        7        geography_gist_same (box2d, box2d, internal);
+
+
+-- ---------- ---------- ---------- ---------- ---------- ---------- ----------
+-- B-Tree Functions
+-- For sorting and grouping
+-- Availability: 1.5.0
+-- ---------- ---------- ---------- ---------- ---------- ---------- ----------
+
+CREATE OR REPLACE FUNCTION geography_lt(geography, geography)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'geography_lt'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geography_le(geography, geography)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'geography_le'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geography_gt(geography, geography)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'geography_gt'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geography_ge(geography, geography)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'geography_ge'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geography_eq(geography, geography)
+	RETURNS bool
+	AS '$libdir/postgis-1.5', 'geography_eq'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION geography_cmp(geography, geography)
+	RETURNS integer
+	AS '$libdir/postgis-1.5', 'geography_cmp'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+--
+-- Sorting operators for Btree
+--
+
+CREATE OPERATOR < (
+	LEFTARG = geography, RIGHTARG = geography, PROCEDURE = geography_lt,
+	COMMUTATOR = '>', NEGATOR = '>=',
+	RESTRICT = contsel, JOIN = contjoinsel
+);
+
+CREATE OPERATOR <= (
+	LEFTARG = geography, RIGHTARG = geography, PROCEDURE = geography_le,
+	COMMUTATOR = '>=', NEGATOR = '>',
+	RESTRICT = contsel, JOIN = contjoinsel
+);
+
+CREATE OPERATOR = (
+	LEFTARG = geography, RIGHTARG = geography, PROCEDURE = geography_eq,
+	COMMUTATOR = '=', -- we might implement a faster negator here
+	RESTRICT = contsel, JOIN = contjoinsel
+);
+
+CREATE OPERATOR >= (
+	LEFTARG = geography, RIGHTARG = geography, PROCEDURE = geography_ge,
+	COMMUTATOR = '<=', NEGATOR = '<',
+	RESTRICT = contsel, JOIN = contjoinsel
+);
+CREATE OPERATOR > (
+	LEFTARG = geography, RIGHTARG = geography, PROCEDURE = geography_gt,
+	COMMUTATOR = '<', NEGATOR = '<=',
+	RESTRICT = contsel, JOIN = contjoinsel
+);
+
+CREATE OPERATOR CLASS btree_geography_ops
+	DEFAULT FOR TYPE geography USING btree AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	geography_cmp (geography, geography);
+
+
+-- ---------- ---------- ---------- ---------- ---------- ---------- ----------
+-- Export Functions
+-- Availability: 1.5.0
+-- ---------- ---------- ---------- ---------- ---------- ---------- ----------
+
+--
+-- SVG OUTPUT
+--
+
+-- ST_AsSVG(geography, precision, rel)
+CREATE OR REPLACE FUNCTION ST_AsSVG(geography,int4,int4)
+	RETURNS text
+	AS '$libdir/postgis-1.5','geography_as_svg'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- ST_AsSVG(geography, precision) / rel=0
+CREATE OR REPLACE FUNCTION ST_AsSVG(geography,int4)
+	RETURNS text
+	AS '$libdir/postgis-1.5','geography_as_svg'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- ST_AsSVG(geography) / precision=15, rel=0
+CREATE OR REPLACE FUNCTION ST_AsSVG(geography)
+	RETURNS text
+	AS '$libdir/postgis-1.5','geography_as_svg'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+	
+-- Availability: 1.5.0 - this is just a hack to prevent unknown from causing ambiguous name because of geography
+-- TODO Remove in 2.0
+CREATE OR REPLACE FUNCTION ST_AsSVG(text)
+	RETURNS text AS
+	$$ SELECT ST_AsSVG($1::geometry);  $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+
+--
+-- GML OUTPUT
+--
+
+-- _ST_AsGML(version, geography, precision, option)
+CREATE OR REPLACE FUNCTION _ST_AsGML(int4, geography, int4, int4)
+	RETURNS text
+	AS '$libdir/postgis-1.5','geography_as_gml'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- ST_AsGML(geography, precision) / version=2 options=0
+CREATE OR REPLACE FUNCTION ST_AsGML(geography, int4)
+	RETURNS text
+	AS 'SELECT _ST_AsGML(2, $1, $2, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGML(geography) / precision=15 version=2 options=0
+CREATE OR REPLACE FUNCTION ST_AsGML(geography)
+	RETURNS text
+	AS 'SELECT _ST_AsGML(2, $1, 15, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0 - this is just a hack to prevent unknown from causing ambiguous name because of geography
+-- TODO Remove in 2.0
+CREATE OR REPLACE FUNCTION ST_AsGML(text)
+	RETURNS text AS
+	$$ SELECT ST_AsGML($1::geometry);  $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGML(version, geography) / precision=15 version=2 options=0
+CREATE OR REPLACE FUNCTION ST_AsGML(int4, geography)
+	RETURNS text
+	AS 'SELECT _ST_AsGML($1, $2, 15, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGML(version, geography, precision) / options = 0
+CREATE OR REPLACE FUNCTION ST_AsGML(int4, geography, int4)
+	RETURNS text
+	AS 'SELECT _ST_AsGML($1, $2, $3, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGML (geography, precision, option) / version=2
+CREATE OR REPLACE FUNCTION ST_AsGML(geography, int4, int4)
+	RETURNS text
+	AS 'SELECT _ST_AsGML(2, $1, $2, $3)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGML(version, geography, precision, option)
+CREATE OR REPLACE FUNCTION ST_AsGML(int4, geography, int4, int4)
+	RETURNS text
+	AS 'SELECT _ST_AsGML($1, $2, $3, $4)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+
+
+--
+-- KML OUTPUT
+--
+
+-- _ST_AsKML(version, geography, precision)
+CREATE OR REPLACE FUNCTION _ST_AsKML(int4, geography, int4)
+	RETURNS text
+	AS '$libdir/postgis-1.5','geography_as_kml'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- AsKML(geography,precision) / version=2
+CREATE OR REPLACE FUNCTION ST_AsKML(geography, int4)
+	RETURNS text
+	AS 'SELECT _ST_AsKML(2, $1, $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- AsKML(geography) / precision=15 version=2
+CREATE OR REPLACE FUNCTION ST_AsKML(geography)
+	RETURNS text
+	AS 'SELECT _ST_AsKML(2, $1, 15)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0 - this is just a hack to prevent unknown from causing ambiguous name because of geography
+-- TODO Remove in 2.0
+CREATE OR REPLACE FUNCTION ST_AsKML(text)
+	RETURNS text AS
+	$$ SELECT ST_AsKML($1::geometry);  $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsKML(version, geography) / precision=15 
+CREATE OR REPLACE FUNCTION ST_AsKML(int4, geography)
+	RETURNS text
+	AS 'SELECT _ST_AsKML($1, $2, 15)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsKML(version, geography, precision)
+CREATE OR REPLACE FUNCTION ST_AsKML(int4, geography, int4)
+	RETURNS text
+	AS 'SELECT _ST_AsKML($1, $2, $3)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+
+
+--
+-- GeoJson Output
+--
+
+CREATE OR REPLACE FUNCTION _ST_AsGeoJson(int4, geography, int4, int4)
+	RETURNS text
+	AS '$libdir/postgis-1.5','geography_as_geojson'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- ST_AsGeoJson(geography, precision) / version=1 options=0
+CREATE OR REPLACE FUNCTION ST_AsGeoJson(geography, int4)
+	RETURNS text
+	AS 'SELECT _ST_AsGeoJson(1, $1, $2, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGeoJson(geography) / precision=15 version=1 options=0
+CREATE OR REPLACE FUNCTION ST_AsGeoJson(geography)
+	RETURNS text
+	AS 'SELECT _ST_AsGeoJson(1, $1, 15, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0 - this is just a hack to prevent unknown from causing ambiguous name because of geography
+-- TODO Remove in 2.0
+CREATE OR REPLACE FUNCTION ST_AsGeoJson(text)
+	RETURNS text AS
+	$$ SELECT ST_AsGeoJson($1::geometry);  $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGeoJson(version, geography) / precision=15 options=0
+CREATE OR REPLACE FUNCTION ST_AsGeoJson(int4, geography)
+	RETURNS text
+	AS 'SELECT _ST_AsGeoJson($1, $2, 15, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGeoJson(version, geography, precision) / options=0
+CREATE OR REPLACE FUNCTION ST_AsGeoJson(int4, geography, int4)
+	RETURNS text
+	AS 'SELECT _ST_AsGeoJson($1, $2, $3, 0)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGeoJson(geography, precision, options) / version=1
+CREATE OR REPLACE FUNCTION ST_AsGeoJson(geography, int4, int4)
+	RETURNS text
+	AS 'SELECT _ST_AsGeoJson(1, $1, $2, $3)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ST_AsGeoJson(version, geography, precision,options)
+CREATE OR REPLACE FUNCTION ST_AsGeoJson(int4, geography, int4, int4)
+	RETURNS text
+	AS 'SELECT _ST_AsGeoJson($1, $2, $3, $4)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ---------- ---------- ---------- ---------- ---------- ---------- ----------
+-- Measurement Functions
+-- Availability: 1.5.0
+-- ---------- ---------- ---------- ---------- ---------- ---------- ----------
+
+-- Stop calculation and return immediately once distance is less than tolerance
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION _ST_Distance(geography, geography, float8, boolean)
+	RETURNS float8
+	AS '$libdir/postgis-1.5','geography_distance'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Stop calculation and return immediately once distance is less than tolerance
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION _ST_DWithin(geography, geography, float8, boolean)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5','geography_dwithin'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_Distance(geography, geography, boolean)
+	RETURNS float8
+	AS 'SELECT _ST_Distance($1, $2, 0.0, $3)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Currently defaulting to spheroid calculations
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_Distance(geography, geography)
+	RETURNS float8
+	AS 'SELECT _ST_Distance($1, $2, 0.0, true)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+	
+-- Availability: 1.5.0 - this is just a hack to prevent unknown from causing ambiguous name because of geography
+-- TODO Remove in 2.0
+CREATE OR REPLACE FUNCTION ST_Distance(text, text)
+	RETURNS float8 AS
+	$$ SELECT ST_Distance($1::geometry, $2::geometry);  $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Only expands the bounding box, the actual geometry will remain unchanged, use with care.
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION _ST_Expand(geography, float8)
+	RETURNS geography
+	AS '$libdir/postgis-1.5','geography_expand'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_DWithin(geography, geography, float8, boolean)
+	RETURNS boolean
+	AS 'SELECT $1 && _ST_Expand($2,$3) AND $2 && _ST_Expand($1,$3) AND _ST_DWithin($1, $2, $3, $4)'
+	LANGUAGE 'SQL' IMMUTABLE;
+
+-- Currently defaulting to spheroid calculations
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_DWithin(geography, geography, float8)
+	RETURNS boolean
+	AS 'SELECT $1 && _ST_Expand($2,$3) AND $2 && _ST_Expand($1,$3) AND _ST_DWithin($1, $2, $3, true)'
+	LANGUAGE 'SQL' IMMUTABLE;
+
+-- Availability: 1.5.0 - this is just a hack to prevent unknown from causing ambiguous name because of geography
+-- TODO Remove in 2.0
+CREATE OR REPLACE FUNCTION ST_DWithin(text, text, float8)
+	RETURNS boolean AS
+	$$ SELECT ST_DWithin($1::geometry, $2::geometry, $3);  $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_Area(geography, boolean)
+	RETURNS float8
+	AS '$libdir/postgis-1.5','geography_area'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Currently defaulting to spheroid calculations
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_Area(geography)
+	RETURNS float8
+	AS 'SELECT ST_Area($1, true)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0 - this is just a hack to prevent unknown from causing ambiguous name because of geography
+-- TODO Remove in 2.0
+CREATE OR REPLACE FUNCTION ST_Area(text)
+	RETURNS float8 AS
+	$$ SELECT ST_Area($1::geometry);  $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_Length(geography, boolean)
+	RETURNS float8
+	AS '$libdir/postgis-1.5','geography_length'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_Length(geography)
+	RETURNS float8
+	AS 'SELECT ST_Length($1, true)'
+	LANGUAGE 'SQL' IMMUTABLE;
+
+-- Availability: 1.5.0 - this is just a hack to prevent unknown from causing ambiguous name because of geography
+-- TODO Remove in 2.0
+CREATE OR REPLACE FUNCTION ST_Length(text)
+	RETURNS float8 AS
+	$$ SELECT ST_Length($1::geometry);  $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION _ST_PointOutside(geography)
+	RETURNS geography
+	AS '$libdir/postgis-1.5','geography_point_outside'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Only implemented for polygon-over-point
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION _ST_Covers(geography, geography)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5','geography_covers'
+	LANGUAGE 'C' IMMUTABLE STRICT
+	COST 100;
+
+-- Only implemented for polygon-over-point
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_Covers(geography, geography)
+	RETURNS boolean
+	AS 'SELECT $1 && $2 AND _ST_Covers($1, $2)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0 - this is just a hack to prevent unknown from causing ambiguous name because of geography
+-- TODO Remove in 2.0
+CREATE OR REPLACE FUNCTION ST_Covers(text, text)
+	RETURNS boolean AS
+	$$ SELECT ST_Covers($1::geometry, $2::geometry);  $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Only implemented for polygon-over-point
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_CoveredBy(geography, geography)
+	RETURNS boolean
+	AS 'SELECT $1 && $2 AND _ST_Covers($2, $1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0 - this is just a hack to prevent unknown from causing ambiguous name because of geography
+-- TODO Remove in 2.0
+CREATE OR REPLACE FUNCTION ST_CoveredBy(text, text)
+	RETURNS boolean AS
+	$$ SELECT ST_CoveredBy($1::geometry, $2::geometry);  $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_Intersects(geography, geography)
+	RETURNS boolean
+	AS 'SELECT $1 && $2 AND _ST_Distance($1, $2, 0.0, false) < 0.00001'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0 - this is just a hack to prevent unknown from causing ambiguous name because of geography
+-- TODO Remove in 2.0
+CREATE OR REPLACE FUNCTION ST_Intersects(text, text)
+	RETURNS boolean AS
+	$$ SELECT ST_Intersects($1::geometry, $2::geometry);  $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION _ST_BestSRID(geography, geography)
+	RETURNS integer
+	AS '$libdir/postgis-1.5','geography_bestsrid'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION _ST_BestSRID(geography)
+	RETURNS integer
+	AS 'SELECT _ST_BestSRID($1,$1)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_Buffer(geography, float8)
+	RETURNS geography
+	AS 'SELECT geography(ST_Transform(ST_Buffer(ST_Transform(geometry($1), _ST_BestSRID($1)), $2), 4326))'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0 - this is just a hack to prevent unknown from causing ambiguous name because of geography
+-- TODO Remove in 2.0
+CREATE OR REPLACE FUNCTION ST_Buffer(text, float8)
+	RETURNS geometry AS
+	$$ SELECT ST_Buffer($1::geometry, $2);  $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0
+CREATE OR REPLACE FUNCTION ST_Intersection(geography, geography)
+	RETURNS geography
+	AS 'SELECT geography(ST_Transform(ST_Intersection(ST_Transform(geometry($1), _ST_BestSRID($1, $2)), ST_Transform(geometry($2), _ST_BestSRID($1, $2))), 4326))'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- Availability: 1.5.0 - this is just a hack to prevent unknown from causing ambiguous name because of geography
+-- TODO Remove in 2.0
+CREATE OR REPLACE FUNCTION ST_Intersection(text, text)
+	RETURNS geometry AS
+	$$ SELECT ST_Intersection($1::geometry, $2::geometry);  $$
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+-- ---------- ---------- ---------- ---------- ---------- ---------- ----------
+
+---------------------------------------------------------------
+-- SQL-MM
+---------------------------------------------------------------
+
+--
+-- SQL-MM
+--
+-- ST_CurveToLine(Geometry geometry, SegmentsPerQuarter integer)
+--
+-- Converts a given geometry to a linear geometry.  Each curveed
+-- geometry or segment is converted into a linear approximation using
+-- the given number of segments per quarter circle.
+CREATE OR REPLACE FUNCTION ST_CurveToLine(geometry, integer)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_curve_segmentize'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+--
+-- SQL-MM
+--
+-- ST_CurveToLine(Geometry geometry, SegmentsPerQuarter integer)
+--
+-- Converts a given geometry to a linear geometry.  Each curveed
+-- geometry or segment is converted into a linear approximation using
+-- the default value of 32 segments per quarter circle
+CREATE OR REPLACE FUNCTION ST_CurveToLine(geometry)
+	RETURNS geometry AS 'SELECT ST_CurveToLine($1, 32)'
+	LANGUAGE 'SQL' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION ST_HasArc(geometry)
+	RETURNS boolean
+	AS '$libdir/postgis-1.5', 'LWGEOM_has_arc'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION ST_LineToCurve(geometry)
+	RETURNS geometry
+	AS '$libdir/postgis-1.5', 'LWGEOM_line_desegmentize'
+	LANGUAGE 'C' IMMUTABLE STRICT;
+---------------------------------------------------------------
+-- END
+---------------------------------------------------------------
+
+
+---------------------------------------------------------------
+-- USER CONTRIUBUTED
+---------------------------------------------------------------
+
+-----------------------------------------------------------------------
+-- ST_MinimumBoundingCircle(inputgeom geometry, segs_per_quarter integer)
+-----------------------------------------------------------------------
+-- Returns the smallest circle polygon that can fully contain a geometry
+-- Defaults to 48 segs per quarter to approximate a circle
+-- Contributed by Bruce Rindahl
+-- Availability: 1.4.0
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION ST_MinimumBoundingCircle(inputgeom geometry, segs_per_quarter integer)
+	RETURNS geometry AS
+$BODY$
+	DECLARE
+	hull GEOMETRY;
+	ring GEOMETRY;
+	center GEOMETRY;
+	radius DOUBLE PRECISION;
+	dist DOUBLE PRECISION;
+	d DOUBLE PRECISION;
+	idx1 integer;
+	idx2 integer;
+	l1 GEOMETRY;
+	l2 GEOMETRY;
+	p1 GEOMETRY;
+	p2 GEOMETRY;
+	a1 DOUBLE PRECISION;
+	a2 DOUBLE PRECISION;
+
+
+	BEGIN
+
+	-- First compute the ConvexHull of the geometry
+	hull = ST_ConvexHull(inputgeom);
+	--A point really has no MBC
+	IF ST_GeometryType(hull) = 'ST_Point' THEN
+		RETURN hull;
+	END IF;
+	-- convert the hull perimeter to a linestring so we can manipulate individual points
+	--If its already a linestring force it to a closed linestring
+	ring = CASE WHEN ST_GeometryType(hull) = 'ST_LineString' THEN ST_AddPoint(hull, ST_StartPoint(hull)) ELSE ST_ExteriorRing(hull) END;
+
+	dist = 0;
+	-- Brute Force - check every pair
+	FOR i in 1 .. (ST_NumPoints(ring)-2)
+		LOOP
+			FOR j in i .. (ST_NumPoints(ring)-1)
+				LOOP
+				d = ST_Distance(ST_PointN(ring,i),ST_PointN(ring,j));
+				-- Check the distance and update if larger
+				IF (d > dist) THEN
+					dist = d;
+					idx1 = i;
+					idx2 = j;
+				END IF;
+			END LOOP;
+		END LOOP;
+
+	-- We now have the diameter of the convex hull.  The following line returns it if desired.
+	-- RETURN MakeLine(PointN(ring,idx1),PointN(ring,idx2));
+
+	-- Now for the Minimum Bounding Circle.  Since we know the two points furthest from each
+	-- other, the MBC must go through those two points. Start with those points as a diameter of a circle.
+
+	-- The radius is half the distance between them and the center is midway between them
+	radius = ST_Distance(ST_PointN(ring,idx1),ST_PointN(ring,idx2)) / 2.0;
+	center = ST_Line_interpolate_point(ST_MakeLine(ST_PointN(ring,idx1),ST_PointN(ring,idx2)),0.5);
+
+	-- Loop through each vertex and check if the distance from the center to the point
+	-- is greater than the current radius.
+	FOR k in 1 .. (ST_NumPoints(ring)-1)
+		LOOP
+		IF(k <> idx1 and k <> idx2) THEN
+			dist = ST_Distance(center,ST_PointN(ring,k));
+			IF (dist > radius) THEN
+				-- We have to expand the circle.  The new circle must pass trhough
+				-- three points - the two original diameters and this point.
+
+				-- Draw a line from the first diameter to this point
+				l1 = ST_Makeline(ST_PointN(ring,idx1),ST_PointN(ring,k));
+				-- Compute the midpoint
+				p1 = ST_line_interpolate_point(l1,0.5);
+				-- Rotate the line 90 degrees around the midpoint (perpendicular bisector)
+				l1 = ST_Translate(ST_Rotate(ST_Translate(l1,-X(p1),-Y(p1)),pi()/2),X(p1),Y(p1));
+				--  Compute the azimuth of the bisector
+				a1 = ST_Azimuth(ST_PointN(l1,1),ST_PointN(l1,2));
+				--  Extend the line in each direction the new computed distance to insure they will intersect
+				l1 = ST_AddPoint(l1,ST_Makepoint(X(ST_PointN(l1,2))+sin(a1)*dist,Y(ST_PointN(l1,2))+cos(a1)*dist),-1);
+				l1 = ST_AddPoint(l1,ST_Makepoint(X(ST_PointN(l1,1))-sin(a1)*dist,Y(ST_PointN(l1,1))-cos(a1)*dist),0);
+
+				-- Repeat for the line from the point to the other diameter point
+				l2 = ST_Makeline(ST_PointN(ring,idx2),ST_PointN(ring,k));
+				p2 = ST_Line_interpolate_point(l2,0.5);
+				l2 = ST_Translate(ST_Rotate(ST_Translate(l2,-X(p2),-Y(p2)),pi()/2),X(p2),Y(p2));
+				a2 = ST_Azimuth(ST_PointN(l2,1),ST_PointN(l2,2));
+				l2 = ST_AddPoint(l2,ST_Makepoint(X(ST_PointN(l2,2))+sin(a2)*dist,Y(ST_PointN(l2,2))+cos(a2)*dist),-1);
+				l2 = ST_AddPoint(l2,ST_Makepoint(X(ST_PointN(l2,1))-sin(a2)*dist,Y(ST_PointN(l2,1))-cos(a2)*dist),0);
+
+				-- The new center is the intersection of the two bisectors
+				center = ST_Intersection(l1,l2);
+				-- The new radius is the distance to any of the three points
+				radius = ST_Distance(center,ST_PointN(ring,idx1));
+			END IF;
+		END IF;
+		END LOOP;
+	--DONE!!  Return the MBC via the buffer command
+	RETURN ST_Buffer(center,radius,segs_per_quarter);
+
+	END;
+$BODY$
+	LANGUAGE 'plpgsql' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION ST_MinimumBoundingCircle(geometry)
+ RETURNS geometry AS
+'SELECT ST_MinimumBoundingCircle($1, 48)'
+ LANGUAGE 'sql' IMMUTABLE STRICT;
+COMMIT;
+
+
+-- First drop old aggregates
+DROP AGGREGATE IF EXISTS geomunion(geometry);
+DROP AGGREGATE IF EXISTS st_geomunion(geometry);
+DROP AGGREGATE IF EXISTS accum_old(geometry);
+DROP AGGREGATE IF EXISTS st_accum_old(geometry);
+
+-- Then drop old functions
+DROP FUNCTION IF EXISTS box2d_overleft(box2d, box2d);
+DROP FUNCTION IF EXISTS box2d_overright(box2d, box2d);
+DROP FUNCTION IF EXISTS box2d_left(box2d, box2d);
+DROP FUNCTION IF EXISTS box2d_right(box2d, box2d);
+DROP FUNCTION IF EXISTS box2d_contain(box2d, box2d);
+DROP FUNCTION IF EXISTS box2d_contained(box2d, box2d);
+DROP FUNCTION IF EXISTS box2d_overlap(box2d, box2d);
+DROP FUNCTION IF EXISTS box2d_same(box2d, box2d);
+DROP FUNCTION IF EXISTS box2d_intersects(box2d, box2d);
+DROP FUNCTION IF EXISTS st_box2d_overleft(box2d, box2d);
+DROP FUNCTION IF EXISTS st_box2d_overright(box2d, box2d);
+DROP FUNCTION IF EXISTS st_box2d_left(box2d, box2d);
+DROP FUNCTION IF EXISTS st_box2d_right(box2d, box2d);
+DROP FUNCTION IF EXISTS st_box2d_contain(box2d, box2d);
+DROP FUNCTION IF EXISTS st_box2d_contained(box2d, box2d);
+DROP FUNCTION IF EXISTS st_box2d_overlap(box2d, box2d);
+DROP FUNCTION IF EXISTS st_box2d_same(box2d, box2d);
+DROP FUNCTION IF EXISTS st_box2d_intersects(box2d, box2d);
+DROP FUNCTION IF EXISTS st_addbbox(geometry);
+DROP FUNCTION IF EXISTS st_dropbbox(geometry); 
+DROP FUNCTION IF EXISTS st_hasbbox(geometry); 
+DROP FUNCTION IF EXISTS cache_bbox();
+DROP FUNCTION IF EXISTS st_cache_bbox();
+DROP FUNCTION IF EXISTS transform_geometry(geometry,text,text,int);
+DROP FUNCTION IF EXISTS collector(geometry, geometry);
+DROP FUNCTION IF EXISTS st_collector(geometry, geometry);
+DROP FUNCTION IF EXISTS geom_accum (geometry[],geometry);
+DROP FUNCTION IF EXISTS st_geom_accum (geometry[],geometry);
+DROP FUNCTION IF EXISTS collect_garray (geometry[]);
+DROP FUNCTION IF EXISTS st_collect_garray (geometry[]);
+DROP FUNCTION IF EXISTS geosnoop(geometry);
+DROP FUNCTION IF EXISTS jtsnoop(geometry);
+DROP FUNCTION IF EXISTS st_noop(geometry);
+DROP FUNCTION IF EXISTS st_max_distance(geometry, geometry);
+
+
+

--- /dev/null
+++ b/lib/rolling-curl/.svn/all-wcprops
@@ -1,1 +1,42 @@
+K 25
+svn:wc:ra_dav:version-url
+V 22
+/svn/!svn/ver/20/trunk
+END
+RollingCurlGroup.php
+K 25
+svn:wc:ra_dav:version-url
+V 43
+/svn/!svn/ver/20/trunk/RollingCurlGroup.php
+END
+example_groups.php
+K 25
+svn:wc:ra_dav:version-url
+V 41
+/svn/!svn/ver/20/trunk/example_groups.php
+END
+example.php
+K 25
+svn:wc:ra_dav:version-url
+V 34
+/svn/!svn/ver/20/trunk/example.php
+END
+RollingCurl.php
+K 25
+svn:wc:ra_dav:version-url
+V 38
+/svn/!svn/ver/20/trunk/RollingCurl.php
+END
+CHANGELOG.txt
+K 25
+svn:wc:ra_dav:version-url
+V 36
+/svn/!svn/ver/20/trunk/CHANGELOG.txt
+END
+README.txt
+K 25
+svn:wc:ra_dav:version-url
+V 33
+/svn/!svn/ver/20/trunk/README.txt
+END
 

--- /dev/null
+++ b/lib/rolling-curl/.svn/entries
@@ -1,1 +1,233 @@
-
+10
+
+dir
+20
+http://rolling-curl.googlecode.com/svn/trunk
+http://rolling-curl.googlecode.com/svn
+
+
+
+2010-09-12T20:39:22.711474Z
+20
+alexander.makarow
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+74aa2acc-2e27-11de-b2a4-4f96ceaaac44
+
+RollingCurlGroup.php
+file
+
+
+
+
+2011-04-10T08:32:48.081650Z
+73c08d9e9e24b4adc89816624c7aca30
+2010-09-12T20:39:22.711474Z
+20
+alexander.makarow
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+5152
+
+example_groups.php
+file
+
+
+
+
+2011-04-10T08:32:48.082650Z
+907ed82a47d346c39acbd5578e1d0230
+2010-09-12T20:39:22.711474Z
+20
+alexander.makarow
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+1367
+
+example.php
+file
+
+
+
+
+2011-04-10T08:32:48.083650Z
+87aa845abfaffc09ed4eca024f2a8b8a
+2010-09-12T20:39:22.711474Z
+20
+alexander.makarow
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+1860
+
+RollingCurl.php
+file
+
+
+
+
+2011-04-10T08:32:48.084650Z
+205391c449f3f3ee050004dadc374dc8
+2010-09-12T20:39:22.711474Z
+20
+alexander.makarow
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+10444
+
+CHANGELOG.txt
+file
+
+
+
+
+2011-04-10T08:32:48.085650Z
+d0452f6f9530ed04580159121d0fd5f7
+2010-09-12T20:39:22.711474Z
+20
+alexander.makarow
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+662
+
+README.txt
+file
+
+
+
+
+2011-04-10T08:32:48.085650Z
+60dd357081431c0f2b82989cdbce8615
+2010-09-12T20:39:22.711474Z
+20
+alexander.makarow
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+6355
+
+

--- /dev/null
+++ b/lib/rolling-curl/.svn/prop-base/CHANGELOG.txt.svn-base
@@ -1,1 +1,6 @@
+K 13
+svn:eol-style
+V 6
+native
+END
 

--- /dev/null
+++ b/lib/rolling-curl/.svn/prop-base/RollingCurlGroup.php.svn-base
@@ -1,1 +1,6 @@
+K 13
+svn:eol-style
+V 6
+native
+END
 

--- /dev/null
+++ b/lib/rolling-curl/.svn/prop-base/example_groups.php.svn-base
@@ -1,1 +1,6 @@
+K 13
+svn:eol-style
+V 6
+native
+END
 

--- /dev/null
+++ b/lib/rolling-curl/.svn/text-base/CHANGELOG.txt.svn-base
@@ -1,1 +1,15 @@
+Rolling Curl changelog
+======================
 
+September 13, 2010
+------------------
+- Bug #12, #14: Fixed default options overriding (LionsAd)
+- Bug #10: Added use of curl_multi_select to avoid burning CPU (LionsAd)
+- Enh #6, #9: Added $request as parameter to callback function (LionsAd)
+- Chg: Request renamed to RollingCurlRequest (LionsAd)
+- Added RollingCurlGroup class that allows processing groups of requests (LionsAd)
+- More cleanup at unsetting a class (LionsAd)
+- Timeout parameter for curl_multi_select is now configurable (LionsAd)
+- single_curl now returns true (LionsAd)
+- Readme corrections (Alexander Makarov)
+- Code cleanup (Alexander Makarov)

--- /dev/null
+++ b/lib/rolling-curl/.svn/text-base/README.txt.svn-base
@@ -1,1 +1,210 @@
-
+Rolling Curl
+============
+
+RollingCurl allows you to process multiple HTTP requests in parallel using CURL PHP library.
+
+Released under the Apache License 2.0.
+
+Authors
+-------
+- Was originally written by [Josh Fraser](joshfraser.com).
+- Currently maintained by [Alexander Makarov](http://rmcreative.ru/).
+- Received significant updates and patched from [LionsAd](http://github.com/LionsAd/rolling-curl).
+
+Overview
+--------
+RollingCurl is a more efficient implementation of curl_multi() curl_multi is a great way to process multiple HTTP requests in parallel in PHP.
+curl_multi is particularly handy when working with large data sets (like fetching thousands of RSS feeds at one time). Unfortunately there is
+very little documentation on the best way to implement curl_multi. As a result, most of the examples around the web are either inefficient or
+fail entirely when asked to handle more than a few hundred requests.
+
+The problem is that most implementations of curl_multi wait for each set of requests to complete before processing them. If there are too many requests
+to process at once, they usually get broken into groups that are then processed one at a time. The problem with this is that each group has to wait for
+the slowest request to download. In a group of 100 requests, all it takes is one slow one to delay the processing of 99 others. The larger the number of
+requests you are dealing with, the more noticeable this latency becomes.
+
+The solution is to process each request as soon as it completes. This eliminates the wasted CPU cycles from busy waiting. Also there is a queue of
+cURL requests to allow for maximum throughput. Each time a request is completed, a new one is added from the queue. By dynamically adding and removing
+links, we keep a constant number of links downloading at all times. This gives us a way to throttle the amount of simultaneous requests we are sending.
+The result is a faster and more efficient way of processing large quantities of cURL requests in parallel.
+
+Callbacks
+---------
+
+Each of requests usually do have a callback to process results that is being executed when request is done
+(both successfully or not).
+
+Callback accepts three parameters and can look like the following one:
+~~~
+[php]
+function request_callback($response, $info, $request){
+    // doing something with the data received
+}
+~~~
+
+- $response contains received page body.
+- $info is an associative array that holds various information about response such as HTTP response code, content type,
+time taken to make request etc.
+- $request contains RollingCurlRequest that was used to make request.
+
+Examples
+--------
+### Hello world
+
+~~~
+[php]
+// an array of URL's to fetch
+$urls = array("http://www.google.com",
+              "http://www.facebook.com",
+              "http://www.yahoo.com");
+
+// a function that will process the returned responses
+function request_callback($response, $info, $request) {
+	// parse the page title out of the returned HTML
+	if (preg_match("~<title>(.*?)</title>~i", $response, $out)) {
+		$title = $out[1];
+	}
+	echo "<b>$title</b><br />";
+	print_r($info);
+	echo "<hr>";
+}
+
+// create a new RollingCurl object and pass it the name of your custom callback function
+$rc = new RollingCurl("request_callback");
+// the window size determines how many simultaneous requests to allow.
+$rc->window_size = 20;
+foreach ($urls as $url) {
+    // add each request to the RollingCurl object
+    $request = new RollingCurlRequest($url);
+    $rc->add($request);
+}
+$rc->execute();
+~~~
+
+
+### Setting custom options
+
+Set custom options for EVERY request:
+
+~~~
+[php]
+$rc = new RollingCurl("request_callback");
+$rc->options = array(CURLOPT_HEADER => true, CURLOPT_NOBODY => true);
+$rc->execute();
+~~~
+
+Set custom options for A SINGLE request:
+
+~~~
+[php]
+$rc = new RollingCurl("request_callback");
+$request = new RollingCurlRequest($url);
+$request->options = array(CURLOPT_HEADER => true, CURLOPT_NOBODY => true);
+$rc->add($request);
+$rc->execute();
+~~~
+
+### Shortcuts
+
+~~~
+[php]
+$rc = new RollingCurl("request_callback");
+$rc->get("http://www.google.com");
+$rc->get("http://www.yahoo.com");
+$rc->execute();
+~~~
+
+### Class callbacks
+
+~~~
+[php]
+class MyInfoCollector {
+    private $rc;
+
+    function __construct(){
+        $this->rc = new RollingCurl(array($this, 'processPage'));
+    }
+
+    function processPage($response, $info, $request){
+      //...
+    }
+
+    function run($urls){
+        foreach ($urls as $url){
+            $request = new RollingCurlRequest($url);
+            $this->rc->add($request);
+        }
+        $this->rc->execute();
+    }
+}
+
+$collector = new MyInfoCollector();
+$collector->run(array(
+    'http://google.com/',
+    'http://yahoo.com/'
+));
+~~~
+
+### Using RollingCurlGroup
+
+~~~
+[php]
+class TestCurlRequest extends RollingCurlGroupRequest {
+    public $test_verbose = true;
+
+    function process($output, $info) {
+        echo "Processing " . $this->url . "\n";
+        if ($this->test_verbose)
+            print_r($info);
+
+        parent::process($output, $info);
+    }
+}
+
+class TestCurlGroup extends RollingCurlGroup {
+    function process($output, $info, $request) {
+        echo "Group CB: Progress " . $this->name . " (" . ($this->finished_requests + 1) . "/" . $this->num_requests . ")\n";
+        parent::process($output, $info, $request);
+    }
+
+    function finished() {
+        echo "Group CB: Finished" . $this->name . "\n";
+        parent::finished();
+    }
+}
+
+$group = new TestCurlGroup("High");
+$group->add(new TestCurlRequest("www.google.de"));
+$group->add(new TestCurlRequest("www.yahoo.de"));
+$group->add(new TestCurlRequest("www.newyorktimes.com"));
+$reqs[] = $group;
+
+$group = new TestCurlGroup("Normal");
+$group->add(new TestCurlRequest("twitter.com"));
+$group->add(new TestCurlRequest("www.bing.com"));
+$group->add(new TestCurlRequest("m.facebook.com"));
+$reqs[] = $group;
+
+$reqs[] = new TestCurlRequest("www.kernel.org");
+
+// No callback here, as its done in Request class
+$rc = new GroupRollingCurl();
+
+foreach ($reqs as $req)
+$rc->add($req);
+
+$rc->execute();
+~~~
+
+The same function (add) can be used both for adding requests and groups of requests.
+The "callback" in request and groups is:
+
+process($output, $info)
+
+and
+
+process($output, $info, $request)
+
+Also you can override RollingCurlGroup::finished() that will be executed right after finishing group processing.
+
+$Id$

--- /dev/null
+++ b/lib/rolling-curl/.svn/text-base/RollingCurl.php.svn-base
@@ -1,1 +1,375 @@
-
+<?php
+/*
+Authored by Josh Fraser (www.joshfraser.com)
+Released under Apache License 2.0
+
+Maintained by Alexander Makarov, http://rmcreative.ru/
+
+$Id$
+*/
+
+/**
+ * Class that represent a single curl request
+ */
+class RollingCurlRequest {
+    public $url = false;
+    public $method = 'GET';
+    public $post_data = null;
+    public $headers = null;
+    public $options = null;
+
+    /**
+     * @param string $url
+     * @param string $method
+     * @param  $post_data
+     * @param  $headers
+     * @param  $options
+     * @return void
+     */
+    function __construct($url, $method = "GET", $post_data = null, $headers = null, $options = null) {
+        $this->url = $url;
+        $this->method = $method;
+        $this->post_data = $post_data;
+        $this->headers = $headers;
+        $this->options = $options;
+    }
+
+    /**
+     * @return void
+     */
+    public function __destruct() {
+        unset($this->url, $this->method, $this->post_data, $this->headers, $this->options);
+    }
+}
+
+/**
+ * RollingCurl custom exception
+ */
+class RollingCurlException extends Exception {
+}
+
+/**
+ * Class that holds a rolling queue of curl requests.
+ *
+ * @throws RollingCurlException
+ */
+class RollingCurl {
+    /**
+     * @var int
+     *
+     * Window size is the max number of simultaneous connections allowed.
+     *
+     * REMEMBER TO RESPECT THE SERVERS:
+     * Sending too many requests at one time can easily be perceived
+     * as a DOS attack. Increase this window_size if you are making requests
+     * to multiple servers or have permission from the receving server admins.
+     */
+    private $window_size = 5;
+
+    /**
+     * @var float
+     *
+     * Timeout is the timeout used for curl_multi_select.
+     */
+    private $timeout = 10;
+
+    /**
+     * @var string|array
+     *
+     * Callback function to be applied to each result.
+     */
+    private $callback;
+
+    /**
+     * @var array
+     *
+     * Set your base options that you want to be used with EVERY request.
+     */
+    protected $options = array(
+        CURLOPT_SSL_VERIFYPEER => 0,
+        CURLOPT_RETURNTRANSFER => 1,
+        CURLOPT_CONNECTTIMEOUT => 30,
+        CURLOPT_TIMEOUT => 30
+    );
+
+    /**
+     * @var array
+     */
+    private $headers = array();
+
+    /**
+     * @var Request[]
+     *
+     * The request queue
+     */
+    private $requests = array();
+
+    /**
+     * @var RequestMap[]
+     *
+     * Maps handles to request indexes
+     */
+    private $requestMap = array();
+
+    /**
+     * @param  $callback
+     * Callback function to be applied to each result.
+     *
+     * Can be specified as 'my_callback_function'
+     * or array($object, 'my_callback_method').
+     *
+     * Function should take three parameters: $response, $info, $request.
+     * $response is response body, $info is additional curl info.
+     * $request is the original request
+     *
+     * @return void
+     */
+    function __construct($callback = null) {
+        $this->callback = $callback;
+    }
+
+    /**
+     * @param string $name
+     * @return mixed
+     */
+    public function __get($name) {
+        return (isset($this->{$name})) ? $this->{$name} : null;
+    }
+
+    /**
+     * @param string $name
+     * @param mixed $value
+     * @return bool
+     */
+    public function __set($name, $value) {
+        // append the base options & headers
+        if ($name == "options" || $name == "headers") {
+            $this->{$name} = $value + $this->{$name};
+        } else {
+            $this->{$name} = $value;
+        }
+        return true;
+    }
+
+    /**
+     * Add a request to the request queue
+     *
+     * @param Request $request
+     * @return bool
+     */
+    public function add($request) {
+        $this->requests[] = $request;
+        return true;
+    }
+
+    /**
+     * Create new Request and add it to the request queue
+     *
+     * @param string $url
+     * @param string $method
+     * @param  $post_data
+     * @param  $headers
+     * @param  $options
+     * @return bool
+     */
+    public function request($url, $method = "GET", $post_data = null, $headers = null, $options = null) {
+        $this->requests[] = new RollingCurlRequest($url, $method, $post_data, $headers, $options);
+        return true;
+    }
+
+    /**
+     * Perform GET request
+     *
+     * @param string $url
+     * @param  $headers
+     * @param  $options
+     * @return bool
+     */
+    public function get($url, $headers = null, $options = null) {
+        return $this->request($url, "GET", null, $headers, $options);
+    }
+
+    /**
+     * Perform POST request
+     *
+     * @param string $url
+     * @param  $post_data
+     * @param  $headers
+     * @param  $options
+     * @return bool
+     */
+    public function post($url, $post_data = null, $headers = null, $options = null) {
+        return $this->request($url, "POST", $post_data, $headers, $options);
+    }
+
+    /**
+     * Execute processing
+     *
+     * @param int $window_size Max number of simultaneous connections
+     * @return string|bool
+     */
+    public function execute($window_size = null) {
+        // rolling curl window must always be greater than 1
+        if (sizeof($this->requests) == 1) {
+            return $this->single_curl();
+        } else {
+            // start the rolling curl. window_size is the max number of simultaneous connections
+            return $this->rolling_curl($window_size);
+        }
+    }
+
+    /**
+     * Performs a single curl request
+     *
+     * @access private
+     * @return string
+     */
+    private function single_curl() {
+        $ch = curl_init();
+        $request = array_shift($this->requests);
+        $options = $this->get_options($request);
+        curl_setopt_array($ch, $options);
+        $output = curl_exec($ch);
+        $info = curl_getinfo($ch);
+
+        // it's not neccesary to set a callback for one-off requests
+        if ($this->callback) {
+            $callback = $this->callback;
+            if (is_callable($this->callback)) {
+                call_user_func($callback, $output, $info, $request);
+            }
+        }
+        else
+            return $output;
+        return true;
+    }
+
+    /**
+     * Performs multiple curl requests
+     *
+     * @access private
+     * @throws RollingCurlException
+     * @param int $window_size Max number of simultaneous connections
+     * @return bool
+     */
+    private function rolling_curl($window_size = null) {
+        if ($window_size)
+            $this->window_size = $window_size;
+
+        // make sure the rolling window isn't greater than the # of urls
+        if (sizeof($this->requests) < $this->window_size)
+            $this->window_size = sizeof($this->requests);
+
+        if ($this->window_size < 2) {
+            throw new RollingCurlException("Window size must be greater than 1");
+        }
+
+        $master = curl_multi_init();
+
+        // start the first batch of requests
+        for ($i = 0; $i < $this->window_size; $i++) {
+            $ch = curl_init();
+
+            $options = $this->get_options($this->requests[$i]);
+
+            curl_setopt_array($ch, $options);
+            curl_multi_add_handle($master, $ch);
+
+            // Add to our request Maps
+            $key = (string) $ch;
+            $this->requestMap[$key] = $i;
+        }
+
+        do {
+            while (($execrun = curl_multi_exec($master, $running)) == CURLM_CALL_MULTI_PERFORM) ;
+            if ($execrun != CURLM_OK)
+                break;
+            // a request was just completed -- find out which one
+            while ($done = curl_multi_info_read($master)) {
+
+                // get the info and content returned on the request
+                $info = curl_getinfo($done['handle']);
+                $output = curl_multi_getcontent($done['handle']);
+
+                // send the return values to the callback function.
+                $callback = $this->callback;
+                if (is_callable($callback)) {
+                    $key = (string) $done['handle'];
+                    $request = $this->requests[$this->requestMap[$key]];
+                    unset($this->requestMap[$key]);
+                    call_user_func($callback, $output, $info, $request);
+                }
+
+                // start a new request (it's important to do this before removing the old one)
+                if ($i < sizeof($this->requests) && isset($this->requests[$i]) && $i < count($this->requests)) {
+                    $ch = curl_init();
+                    $options = $this->get_options($this->requests[$i]);
+                    curl_setopt_array($ch, $options);
+                    curl_multi_add_handle($master, $ch);
+
+                    // Add to our request Maps
+                    $key = (string) $ch;
+                    $this->requestMap[$key] = $i;
+                    $i++;
+                }
+
+                // remove the curl handle that just completed
+                curl_multi_remove_handle($master, $done['handle']);
+
+            }
+
+            // Block for data in / output; error handling is done by curl_multi_exec
+            if ($running)
+                curl_multi_select($master, $this->timeout);
+
+        } while ($running);
+        curl_multi_close($master);
+        return true;
+    }
+
+
+    /**
+     * Helper function to set up a new request by setting the appropriate options
+     *
+     * @access private
+     * @param Request $request
+     * @return array
+     */
+    private function get_options($request) {
+        // options for this entire curl object
+        $options = $this->__get('options');
+        if (ini_get('safe_mode') == 'Off' || !ini_get('safe_mode')) {
+            $options[CURLOPT_FOLLOWLOCATION] = 1;
+            $options[CURLOPT_MAXREDIRS] = 5;
+        }
+        $headers = $this->__get('headers');
+
+        // append custom options for this specific request
+        if ($request->options) {
+            $options = $request->options + $options;
+        }
+
+        // set the request URL
+        $options[CURLOPT_URL] = $request->url;
+
+        // posting data w/ this request?
+        if ($request->post_data) {
+            $options[CURLOPT_POST] = 1;
+            $options[CURLOPT_POSTFIELDS] = $request->post_data;
+        }
+        if ($headers) {
+            $options[CURLOPT_HEADER] = 0;
+            $options[CURLOPT_HTTPHEADER] = $headers;
+        }
+
+        return $options;
+    }
+
+    /**
+     * @return void
+     */
+    public function __destruct() {
+        unset($this->window_size, $this->callback, $this->options, $this->headers, $this->requests);
+    }
+}
+

--- /dev/null
+++ b/lib/rolling-curl/.svn/text-base/RollingCurlGroup.php.svn-base
@@ -1,1 +1,218 @@
-
+<?php
+/*
+
+  Authored by Fabian Franz (www.lionsad.de)
+  Released under Apache License 2.0
+
+$Id$
+*/
+
+class RollingCurlGroupException extends Exception {}
+
+/**
+ * @throws RollingCurlGroupException
+ */
+abstract class RollingCurlGroupRequest extends RollingCurlRequest {
+    private $group = null;
+
+    /**
+     * Set group for this request
+     *
+     * @param group The group to be set
+     */
+    function setGroup($group) {
+        if (!($group instanceof RollingCurlGroup))
+            throw new RollingCurlGroupException("setGroup: group needs to be of instance RollingCurlGroup");
+
+        $this->group = $group;
+    }
+
+    /**
+     * Process the request
+     *
+     *
+     */
+    function process($output, $info) {
+        if ($this->group)
+            $this->group->process($output, $info, $this);
+    }
+
+    /**
+     * @return void
+     */
+    public function __destruct() {
+        unset($this->group);
+        parent::__destruct();
+    }
+
+}
+
+/**
+ * A group of curl requests.
+ *
+ * @throws RollingCurlGroupException *
+ */
+class RollingCurlGroup {
+    /**
+     * @var string group name
+     */
+    protected $name;
+
+    /**
+     * @var int total number of requests in a group
+     */
+    protected $num_requests = 0;
+
+    /**
+     * @var int total number of finished requests in a group
+     */
+    protected $finished_requests = 0;
+
+    /**
+     * @var array requests array
+     */
+    private $requests = array();
+
+    /**
+     * @param string $name group name
+     * @return void
+     */
+    function __construct($name) {
+        $this->name = $name;
+    }
+
+    /**
+     * @return void
+     */
+    public function __destruct() {
+        unset($this->name, $this->num_requests, $this->finished_requests, $this->requests);
+    }
+
+    /**
+     * Adds request to a group
+     *
+     * @throws RollingCurlGroupException
+     * @param RollingCurlGroupRequest|array $request
+     * @return bool
+     */
+    function add($request) {
+        if ($request instanceof RollingCurlGroupRequest) {
+            $request->setGroup($this);
+            $this->num_requests++;
+            $this->requests[] = $request;
+        }
+        else if (is_array($request)) {
+            foreach ($request as $req)
+            $this->add($req);
+        }
+        else
+            throw new RollingCurlGroupException("add: Request needs to be of instance RollingCurlGroupRequest");
+
+        return true;
+    }
+
+    /**
+     * @throws RollingCurlGroupException
+     * @param RollingCurl $rc
+     * @return bool
+     */
+    function addToRC(RollingCurl $rc){
+        $ret = true;
+
+        while (count($this->requests) > 0){
+            $ret1 = $rc->add(array_shift($this->requests));
+            if (!$ret1)
+                $ret = false;
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Override to implement custom response processing.
+     *
+     * Don't forget to call parent::process().
+     *
+     * @param string $output received page body
+     * @param array $info holds various information about response such as HTTP response code, content type, time taken to make request etc.
+     * @param RollingCurlRequest $request request used
+     * @return void
+     */
+    function process($output, $info, $request) {
+        $this->finished_requests++;
+
+        if ($this->finished_requests >= $this->num_requests)
+            $this->finished();
+    }
+
+    /**
+     * Override to execute code after all requests in a group are processed.
+     *
+     * @return void
+     */
+    function finished() {
+    }
+
+}
+
+/**
+ * Group version of rolling curl
+ */
+class GroupRollingCurl extends RollingCurl {
+
+    /**
+     * @var mixed common callback for all groups
+     */
+    private $group_callback = null;
+
+    /**
+     * @param string $output received page body
+     * @param array $info holds various information about response such as HTTP response code, content type, time taken to make request etc.
+     * @param RollingCurlRequest $request request used
+     * @return void
+     */
+    protected function process($output, $info, $request) {
+        if ($request instanceof RollingCurlGroupRequest)
+            $request->process($output, $info);
+
+        if (is_callable($this->group_callback))
+            call_user_func($this->group_callback, $output, $info, $request);
+    }
+
+    /**
+     * @param mixed $callback common callback for all groups
+     * @return void
+     */
+    function __construct($callback = null) {
+        $this->group_callback = $callback;
+
+        parent::__construct(array(&$this, "process"));
+    }
+
+    /**
+     * Adds a group to processing queue
+     *
+     * @param RollingCurlGroup|Request $request
+     * @return bool
+     */
+    public function add($request) {
+        if ($request instanceof RollingCurlGroup)
+            return $request->addToRC($this);
+        else
+            return parent::add($request);
+    }
+
+    /**
+     * Execute processing
+     *
+     * @param int $window_size Max number of simultaneous connections
+     * @return bool|string
+     */
+    public function execute($window_size = null) {
+        if (count($this->requests) == 0)
+            return false;
+
+        return parent::execute($window_size);
+    }
+}
+

--- /dev/null
+++ b/lib/rolling-curl/.svn/text-base/example.php.svn-base
@@ -1,1 +1,66 @@
+<?php
+/*
+authored by Josh Fraser (www.joshfraser.com)
+released under Apache License 2.0
 
+Maintained by Alexander Makarov, http://rmcreative.ru/
+
+$Id$
+*/
+
+require("RollingCurl.php");
+
+// a little example that fetches a bunch of sites in parallel and echos the page title and response info for each request
+function request_callback($response, $info, $request) {
+	// parse the page title out of the returned HTML
+	if (preg_match("~<title>(.*?)</title>~i", $response, $out)) {
+		$title = $out[1];
+	}
+	echo "<b>$title</b><br />";
+	print_r($info);
+    print_r($request);
+	echo "<hr>";
+}
+
+// single curl request
+$rc = new RollingCurl("request_callback");
+$rc->request("http://www.msn.com");
+$rc->execute();
+
+// another single curl request
+$rc = new RollingCurl("request_callback");
+$rc->request("http://www.google.com");
+$rc->execute();
+
+echo "<hr>";
+
+// top 20 sites according to alexa (11/5/09)
+$urls = array("http://www.google.com",
+              "http://www.facebook.com",
+              "http://www.yahoo.com",
+              "http://www.youtube.com",
+              "http://www.live.com",
+              "http://www.wikipedia.com",
+              "http://www.blogger.com",
+              "http://www.msn.com",
+              "http://www.baidu.com",
+              "http://www.yahoo.co.jp",
+              "http://www.myspace.com",
+              "http://www.qq.com",
+              "http://www.google.co.in",
+              "http://www.twitter.com",
+              "http://www.google.de",
+              "http://www.microsoft.com",
+              "http://www.google.cn",
+              "http://www.sina.com.cn",
+              "http://www.wordpress.com",
+              "http://www.google.co.uk");
+
+$rc = new RollingCurl("request_callback");
+$rc->window_size = 20;
+foreach ($urls as $url) {
+    $request = new RollingCurlRequest($url);
+    $rc->add($request);
+}
+$rc->execute();
+

--- /dev/null
+++ b/lib/rolling-curl/.svn/text-base/example_groups.php.svn-base
@@ -1,1 +1,49 @@
+<?php
+require 'RollingCurl.php';
+require 'RollingCurlGroup.php';
 
+class TestCurlRequest extends RollingCurlGroupRequest {
+    public $test_verbose = true;
+
+    function process($output, $info) {
+        echo "Processing " . $this->url . "\n";
+        if ($this->test_verbose)
+            print_r($info);
+
+        parent::process($output, $info);
+    }
+}
+
+class TestCurlGroup extends RollingCurlGroup {
+    function process($output, $info, $request) {
+        echo "Group CB: Progress " . $this->name . " (" . ($this->finished_requests + 1) . "/" . $this->num_requests . ")\n";
+        parent::process($output, $info, $request);
+    }
+
+    function finished() {
+        echo "Group CB: Finished" . $this->name . "\n";
+        parent::finished();
+    }
+}
+
+$group = new TestCurlGroup("High");
+$group->add(new TestCurlRequest("www.google.de"));
+$group->add(new TestCurlRequest("www.yahoo.de"));
+$group->add(new TestCurlRequest("www.newyorktimes.com"));
+$reqs[] = $group;
+
+$group = new TestCurlGroup("Normal");
+$group->add(new TestCurlRequest("twitter.com"));
+$group->add(new TestCurlRequest("www.bing.com"));
+$group->add(new TestCurlRequest("m.facebook.com"));
+$reqs[] = $group;
+
+$reqs[] = new TestCurlRequest("www.kernel.org");
+
+// No callback here, as its done in Request class
+$rc = new GroupRollingCurl();
+
+foreach ($reqs as $req)
+    $rc->add($req);
+
+$rc->execute();

--- /dev/null
+++ b/lib/rolling-curl/CHANGELOG.txt
@@ -1,1 +1,15 @@
+Rolling Curl changelog
+======================
 
+September 13, 2010
+------------------
+- Bug #12, #14: Fixed default options overriding (LionsAd)
+- Bug #10: Added use of curl_multi_select to avoid burning CPU (LionsAd)
+- Enh #6, #9: Added $request as parameter to callback function (LionsAd)
+- Chg: Request renamed to RollingCurlRequest (LionsAd)
+- Added RollingCurlGroup class that allows processing groups of requests (LionsAd)
+- More cleanup at unsetting a class (LionsAd)
+- Timeout parameter for curl_multi_select is now configurable (LionsAd)
+- single_curl now returns true (LionsAd)
+- Readme corrections (Alexander Makarov)
+- Code cleanup (Alexander Makarov)

--- /dev/null
+++ b/lib/rolling-curl/README.txt
@@ -1,1 +1,210 @@
-
+Rolling Curl
+============
+
+RollingCurl allows you to process multiple HTTP requests in parallel using CURL PHP library.
+
+Released under the Apache License 2.0.
+
+Authors
+-------
+- Was originally written by [Josh Fraser](joshfraser.com).
+- Currently maintained by [Alexander Makarov](http://rmcreative.ru/).
+- Received significant updates and patched from [LionsAd](http://github.com/LionsAd/rolling-curl).
+
+Overview
+--------
+RollingCurl is a more efficient implementation of curl_multi() curl_multi is a great way to process multiple HTTP requests in parallel in PHP.
+curl_multi is particularly handy when working with large data sets (like fetching thousands of RSS feeds at one time). Unfortunately there is
+very little documentation on the best way to implement curl_multi. As a result, most of the examples around the web are either inefficient or
+fail entirely when asked to handle more than a few hundred requests.
+
+The problem is that most implementations of curl_multi wait for each set of requests to complete before processing them. If there are too many requests
+to process at once, they usually get broken into groups that are then processed one at a time. The problem with this is that each group has to wait for
+the slowest request to download. In a group of 100 requests, all it takes is one slow one to delay the processing of 99 others. The larger the number of
+requests you are dealing with, the more noticeable this latency becomes.
+
+The solution is to process each request as soon as it completes. This eliminates the wasted CPU cycles from busy waiting. Also there is a queue of
+cURL requests to allow for maximum throughput. Each time a request is completed, a new one is added from the queue. By dynamically adding and removing
+links, we keep a constant number of links downloading at all times. This gives us a way to throttle the amount of simultaneous requests we are sending.
+The result is a faster and more efficient way of processing large quantities of cURL requests in parallel.
+
+Callbacks
+---------
+
+Each of requests usually do have a callback to process results that is being executed when request is done
+(both successfully or not).
+
+Callback accepts three parameters and can look like the following one:
+~~~
+[php]
+function request_callback($response, $info, $request){
+    // doing something with the data received
+}
+~~~
+
+- $response contains received page body.
+- $info is an associative array that holds various information about response such as HTTP response code, content type,
+time taken to make request etc.
+- $request contains RollingCurlRequest that was used to make request.
+
+Examples
+--------
+### Hello world
+
+~~~
+[php]
+// an array of URL's to fetch
+$urls = array("http://www.google.com",
+              "http://www.facebook.com",
+              "http://www.yahoo.com");
+
+// a function that will process the returned responses
+function request_callback($response, $info, $request) {
+	// parse the page title out of the returned HTML
+	if (preg_match("~<title>(.*?)</title>~i", $response, $out)) {
+		$title = $out[1];
+	}
+	echo "<b>$title</b><br />";
+	print_r($info);
+	echo "<hr>";
+}
+
+// create a new RollingCurl object and pass it the name of your custom callback function
+$rc = new RollingCurl("request_callback");
+// the window size determines how many simultaneous requests to allow.
+$rc->window_size = 20;
+foreach ($urls as $url) {
+    // add each request to the RollingCurl object
+    $request = new RollingCurlRequest($url);
+    $rc->add($request);
+}
+$rc->execute();
+~~~
+
+
+### Setting custom options
+
+Set custom options for EVERY request:
+
+~~~
+[php]
+$rc = new RollingCurl("request_callback");
+$rc->options = array(CURLOPT_HEADER => true, CURLOPT_NOBODY => true);
+$rc->execute();
+~~~
+
+Set custom options for A SINGLE request:
+
+~~~
+[php]
+$rc = new RollingCurl("request_callback");
+$request = new RollingCurlRequest($url);
+$request->options = array(CURLOPT_HEADER => true, CURLOPT_NOBODY => true);
+$rc->add($request);
+$rc->execute();
+~~~
+
+### Shortcuts
+
+~~~
+[php]
+$rc = new RollingCurl("request_callback");
+$rc->get("http://www.google.com");
+$rc->get("http://www.yahoo.com");
+$rc->execute();
+~~~
+
+### Class callbacks
+
+~~~
+[php]
+class MyInfoCollector {
+    private $rc;
+
+    function __construct(){
+        $this->rc = new RollingCurl(array($this, 'processPage'));
+    }
+
+    function processPage($response, $info, $request){
+      //...
+    }
+
+    function run($urls){
+        foreach ($urls as $url){
+            $request = new RollingCurlRequest($url);
+            $this->rc->add($request);
+        }
+        $this->rc->execute();
+    }
+}
+
+$collector = new MyInfoCollector();
+$collector->run(array(
+    'http://google.com/',
+    'http://yahoo.com/'
+));
+~~~
+
+### Using RollingCurlGroup
+
+~~~
+[php]
+class TestCurlRequest extends RollingCurlGroupRequest {
+    public $test_verbose = true;
+
+    function process($output, $info) {
+        echo "Processing " . $this->url . "\n";
+        if ($this->test_verbose)
+            print_r($info);
+
+        parent::process($output, $info);
+    }
+}
+
+class TestCurlGroup extends RollingCurlGroup {
+    function process($output, $info, $request) {
+        echo "Group CB: Progress " . $this->name . " (" . ($this->finished_requests + 1) . "/" . $this->num_requests . ")\n";
+        parent::process($output, $info, $request);
+    }
+
+    function finished() {
+        echo "Group CB: Finished" . $this->name . "\n";
+        parent::finished();
+    }
+}
+
+$group = new TestCurlGroup("High");
+$group->add(new TestCurlRequest("www.google.de"));
+$group->add(new TestCurlRequest("www.yahoo.de"));
+$group->add(new TestCurlRequest("www.newyorktimes.com"));
+$reqs[] = $group;
+
+$group = new TestCurlGroup("Normal");
+$group->add(new TestCurlRequest("twitter.com"));
+$group->add(new TestCurlRequest("www.bing.com"));
+$group->add(new TestCurlRequest("m.facebook.com"));
+$reqs[] = $group;
+
+$reqs[] = new TestCurlRequest("www.kernel.org");
+
+// No callback here, as its done in Request class
+$rc = new GroupRollingCurl();
+
+foreach ($reqs as $req)
+$rc->add($req);
+
+$rc->execute();
+~~~
+
+The same function (add) can be used both for adding requests and groups of requests.
+The "callback" in request and groups is:
+
+process($output, $info)
+
+and
+
+process($output, $info, $request)
+
+Also you can override RollingCurlGroup::finished() that will be executed right after finishing group processing.
+
+$Id$

--- /dev/null
+++ b/lib/rolling-curl/RollingCurl.php
@@ -1,1 +1,376 @@
-
+<?php
+/*
+Authored by Josh Fraser (www.joshfraser.com)
+Released under Apache License 2.0
+
+Maintained by Alexander Makarov, http://rmcreative.ru/
+
+$Id$
+*/
+
+/**
+ * Class that represent a single curl request
+ */
+class RollingCurlRequest {
+    public $url = false;
+    public $method = 'GET';
+    public $post_data = null;
+    public $headers = null;
+    public $options = null;
+    public $metadata = Array();
+    
+    /**
+     * @param string $url
+     * @param string $method
+     * @param  $post_data
+     * @param  $headers
+     * @param  $options
+     * @return void
+     */
+    function __construct($url, $method = "GET", $post_data = null, $headers = null, $options = null) {
+        $this->url = $url;
+        $this->method = $method;
+        $this->post_data = $post_data;
+        $this->headers = $headers;
+        $this->options = $options;
+    }
+
+    /**
+     * @return void
+     */
+    public function __destruct() {
+        unset($this->url, $this->method, $this->post_data, $this->headers, $this->options);
+    }
+}
+
+/**
+ * RollingCurl custom exception
+ */
+class RollingCurlException extends Exception {
+}
+
+/**
+ * Class that holds a rolling queue of curl requests.
+ *
+ * @throws RollingCurlException
+ */
+class RollingCurl {
+    /**
+     * @var int
+     *
+     * Window size is the max number of simultaneous connections allowed.
+     *
+     * REMEMBER TO RESPECT THE SERVERS:
+     * Sending too many requests at one time can easily be perceived
+     * as a DOS attack. Increase this window_size if you are making requests
+     * to multiple servers or have permission from the receving server admins.
+     */
+    private $window_size = 5;
+
+    /**
+     * @var float
+     *
+     * Timeout is the timeout used for curl_multi_select.
+     */
+    private $timeout = 10;
+
+    /**
+     * @var string|array
+     *
+     * Callback function to be applied to each result.
+     */
+    private $callback;
+
+    /**
+     * @var array
+     *
+     * Set your base options that you want to be used with EVERY request.
+     */
+    protected $options = array(
+        CURLOPT_SSL_VERIFYPEER => 0,
+        CURLOPT_RETURNTRANSFER => 1,
+        CURLOPT_CONNECTTIMEOUT => 60,
+        CURLOPT_TIMEOUT => 60
+    );
+
+    /**
+     * @var array
+     */
+    private $headers = array();
+
+    /**
+     * @var Request[]
+     *
+     * The request queue
+     */
+    private $requests = array();
+
+    /**
+     * @var RequestMap[]
+     *
+     * Maps handles to request indexes
+     */
+    private $requestMap = array();
+
+    /**
+     * @param  $callback
+     * Callback function to be applied to each result.
+     *
+     * Can be specified as 'my_callback_function'
+     * or array($object, 'my_callback_method').
+     *
+     * Function should take three parameters: $response, $info, $request.
+     * $response is response body, $info is additional curl info.
+     * $request is the original request
+     *
+     * @return void
+     */
+    function __construct($callback = null) {
+        $this->callback = $callback;
+    }
+
+    /**
+     * @param string $name
+     * @return mixed
+     */
+    public function __get($name) {
+        return (isset($this->{$name})) ? $this->{$name} : null;
+    }
+
+    /**
+     * @param string $name
+     * @param mixed $value
+     * @return bool
+     */
+    public function __set($name, $value) {
+        // append the base options & headers
+        if ($name == "options" || $name == "headers") {
+            $this->{$name} = $value + $this->{$name};
+        } else {
+            $this->{$name} = $value;
+        }
+        return true;
+    }
+
+    /**
+     * Add a request to the request queue
+     *
+     * @param Request $request
+     * @return bool
+     */
+    public function add($request) {
+        $this->requests[] = $request;
+        return true;
+    }
+
+    /**
+     * Create new Request and add it to the request queue
+     *
+     * @param string $url
+     * @param string $method
+     * @param  $post_data
+     * @param  $headers
+     * @param  $options
+     * @return bool
+     */
+    public function request($url, $method = "GET", $post_data = null, $headers = null, $options = null) {
+        $this->requests[] = new RollingCurlRequest($url, $method, $post_data, $headers, $options);
+        return true;
+    }
+
+    /**
+     * Perform GET request
+     *
+     * @param string $url
+     * @param  $headers
+     * @param  $options
+     * @return bool
+     */
+    public function get($url, $headers = null, $options = null) {
+        return $this->request($url, "GET", null, $headers, $options);
+    }
+
+    /**
+     * Perform POST request
+     *
+     * @param string $url
+     * @param  $post_data
+     * @param  $headers
+     * @param  $options
+     * @return bool
+     */
+    public function post($url, $post_data = null, $headers = null, $options = null) {
+        return $this->request($url, "POST", $post_data, $headers, $options);
+    }
+
+    /**
+     * Execute processing
+     *
+     * @param int $window_size Max number of simultaneous connections
+     * @return string|bool
+     */
+    public function execute($window_size = null) {
+        // rolling curl window must always be greater than 1
+        if (sizeof($this->requests) == 1) {
+            return $this->single_curl();
+        } else {
+            // start the rolling curl. window_size is the max number of simultaneous connections
+            return $this->rolling_curl($window_size);
+        }
+    }
+
+    /**
+     * Performs a single curl request
+     *
+     * @access private
+     * @return string
+     */
+    private function single_curl() {
+        $ch = curl_init();
+        $request = array_shift($this->requests);
+        $options = $this->get_options($request);
+        curl_setopt_array($ch, $options);
+        $output = curl_exec($ch);
+        $info = curl_getinfo($ch);
+
+        // it's not neccesary to set a callback for one-off requests
+        if ($this->callback) {
+            $callback = $this->callback;
+            if (is_callable($this->callback)) {
+                call_user_func($callback, $output, $info, $request);
+            }
+        }
+        else
+            return $output;
+        return true;
+    }
+
+    /**
+     * Performs multiple curl requests
+     *
+     * @access private
+     * @throws RollingCurlException
+     * @param int $window_size Max number of simultaneous connections
+     * @return bool
+     */
+    private function rolling_curl($window_size = null) {
+        if ($window_size)
+            $this->window_size = $window_size;
+
+        // make sure the rolling window isn't greater than the # of urls
+        if (sizeof($this->requests) < $this->window_size)
+            $this->window_size = sizeof($this->requests);
+
+        if ($this->window_size < 2) {
+            throw new RollingCurlException("Window size must be greater than 1");
+        }
+
+        $master = curl_multi_init();
+
+        // start the first batch of requests
+        for ($i = 0; $i < $this->window_size; $i++) {
+            $ch = curl_init();
+
+            $options = $this->get_options($this->requests[$i]);
+
+            curl_setopt_array($ch, $options);
+            curl_multi_add_handle($master, $ch);
+
+            // Add to our request Maps
+            $key = (string) $ch;
+            $this->requestMap[$key] = $i;
+        }
+
+        do {
+            while (($execrun = curl_multi_exec($master, $running)) == CURLM_CALL_MULTI_PERFORM) ;
+            if ($execrun != CURLM_OK)
+                break;
+            // a request was just completed -- find out which one
+            while ($done = curl_multi_info_read($master)) {
+
+                // get the info and content returned on the request
+                $info = curl_getinfo($done['handle']);
+                $output = curl_multi_getcontent($done['handle']);
+
+                // send the return values to the callback function.
+                $callback = $this->callback;
+                if (is_callable($callback)) {
+                    $key = (string) $done['handle'];
+                    $request = $this->requests[$this->requestMap[$key]];
+                    unset($this->requestMap[$key]);
+                    call_user_func($callback, $output, $info, $request);
+                }
+
+                // start a new request (it's important to do this before removing the old one)
+                if ($i < sizeof($this->requests) && isset($this->requests[$i]) && $i < count($this->requests)) {
+                    $ch = curl_init();
+                    $options = $this->get_options($this->requests[$i]);
+                    curl_setopt_array($ch, $options);
+                    curl_multi_add_handle($master, $ch);
+
+                    // Add to our request Maps
+                    $key = (string) $ch;
+                    $this->requestMap[$key] = $i;
+                    $i++;
+                }
+
+                // remove the curl handle that just completed
+                curl_multi_remove_handle($master, $done['handle']);
+
+            }
+
+            // Block for data in / output; error handling is done by curl_multi_exec
+            if ($running)
+                curl_multi_select($master, $this->timeout);
+
+        } while ($running);
+        curl_multi_close($master);
+        return true;
+    }
+
+
+    /**
+     * Helper function to set up a new request by setting the appropriate options
+     *
+     * @access private
+     * @param Request $request
+     * @return array
+     */
+    private function get_options($request) {
+        // options for this entire curl object
+        $options = $this->__get('options');
+        if (ini_get('safe_mode') == 'Off' || !ini_get('safe_mode')) {
+            $options[CURLOPT_FOLLOWLOCATION] = 1;
+            $options[CURLOPT_MAXREDIRS] = 5;
+        }
+        $headers = $this->__get('headers');
+
+        // append custom options for this specific request
+        if ($request->options) {
+            $options = $request->options + $options;
+        }
+
+        // set the request URL
+        $options[CURLOPT_URL] = $request->url;
+
+        // posting data w/ this request?
+        if ($request->post_data) {
+            $options[CURLOPT_POST] = 1;
+            $options[CURLOPT_POSTFIELDS] = $request->post_data;
+        }
+        if ($headers) {
+            $options[CURLOPT_HEADER] = 0;
+            $options[CURLOPT_HTTPHEADER] = $headers;
+        }
+
+        return $options;
+    }
+
+    /**
+     * @return void
+     */
+    public function __destruct() {
+        unset($this->window_size, $this->callback, $this->options, $this->headers, $this->requests);
+    }
+}
+

--- /dev/null
+++ b/lib/rolling-curl/RollingCurlGroup.php
@@ -1,1 +1,218 @@
-
+<?php
+/*
+
+  Authored by Fabian Franz (www.lionsad.de)
+  Released under Apache License 2.0
+
+$Id$
+*/
+
+class RollingCurlGroupException extends Exception {}
+
+/**
+ * @throws RollingCurlGroupException
+ */
+abstract class RollingCurlGroupRequest extends RollingCurlRequest {
+    private $group = null;
+
+    /**
+     * Set group for this request
+     *
+     * @param group The group to be set
+     */
+    function setGroup($group) {
+        if (!($group instanceof RollingCurlGroup))
+            throw new RollingCurlGroupException("setGroup: group needs to be of instance RollingCurlGroup");
+
+        $this->group = $group;
+    }
+
+    /**
+     * Process the request
+     *
+     *
+     */
+    function process($output, $info) {
+        if ($this->group)
+            $this->group->process($output, $info, $this);
+    }
+
+    /**
+     * @return void
+     */
+    public function __destruct() {
+        unset($this->group);
+        parent::__destruct();
+    }
+
+}
+
+/**
+ * A group of curl requests.
+ *
+ * @throws RollingCurlGroupException *
+ */
+class RollingCurlGroup {
+    /**
+     * @var string group name
+     */
+    protected $name;
+
+    /**
+     * @var int total number of requests in a group
+     */
+    protected $num_requests = 0;
+
+    /**
+     * @var int total number of finished requests in a group
+     */
+    protected $finished_requests = 0;
+
+    /**
+     * @var array requests array
+     */
+    private $requests = array();
+
+    /**
+     * @param string $name group name
+     * @return void
+     */
+    function __construct($name) {
+        $this->name = $name;
+    }
+
+    /**
+     * @return void
+     */
+    public function __destruct() {
+        unset($this->name, $this->num_requests, $this->finished_requests, $this->requests);
+    }
+
+    /**
+     * Adds request to a group
+     *
+     * @throws RollingCurlGroupException
+     * @param RollingCurlGroupRequest|array $request
+     * @return bool
+     */
+    function add($request) {
+        if ($request instanceof RollingCurlGroupRequest) {
+            $request->setGroup($this);
+            $this->num_requests++;
+            $this->requests[] = $request;
+        }
+        else if (is_array($request)) {
+            foreach ($request as $req)
+            $this->add($req);
+        }
+        else
+            throw new RollingCurlGroupException("add: Request needs to be of instance RollingCurlGroupRequest");
+
+        return true;
+    }
+
+    /**
+     * @throws RollingCurlGroupException
+     * @param RollingCurl $rc
+     * @return bool
+     */
+    function addToRC(RollingCurl $rc){
+        $ret = true;
+
+        while (count($this->requests) > 0){
+            $ret1 = $rc->add(array_shift($this->requests));
+            if (!$ret1)
+                $ret = false;
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Override to implement custom response processing.
+     *
+     * Don't forget to call parent::process().
+     *
+     * @param string $output received page body
+     * @param array $info holds various information about response such as HTTP response code, content type, time taken to make request etc.
+     * @param RollingCurlRequest $request request used
+     * @return void
+     */
+    function process($output, $info, $request) {
+        $this->finished_requests++;
+
+        if ($this->finished_requests >= $this->num_requests)
+            $this->finished();
+    }
+
+    /**
+     * Override to execute code after all requests in a group are processed.
+     *
+     * @return void
+     */
+    function finished() {
+    }
+
+}
+
+/**
+ * Group version of rolling curl
+ */
+class GroupRollingCurl extends RollingCurl {
+
+    /**
+     * @var mixed common callback for all groups
+     */
+    private $group_callback = null;
+
+    /**
+     * @param string $output received page body
+     * @param array $info holds various information about response such as HTTP response code, content type, time taken to make request etc.
+     * @param RollingCurlRequest $request request used
+     * @return void
+     */
+    protected function process($output, $info, $request) {
+        if ($request instanceof RollingCurlGroupRequest)
+            $request->process($output, $info);
+
+        if (is_callable($this->group_callback))
+            call_user_func($this->group_callback, $output, $info, $request);
+    }
+
+    /**
+     * @param mixed $callback common callback for all groups
+     * @return void
+     */
+    function __construct($callback = null) {
+        $this->group_callback = $callback;
+
+        parent::__construct(array(&$this, "process"));
+    }
+
+    /**
+     * Adds a group to processing queue
+     *
+     * @param RollingCurlGroup|Request $request
+     * @return bool
+     */
+    public function add($request) {
+        if ($request instanceof RollingCurlGroup)
+            return $request->addToRC($this);
+        else
+            return parent::add($request);
+    }
+
+    /**
+     * Execute processing
+     *
+     * @param int $window_size Max number of simultaneous connections
+     * @return bool|string
+     */
+    public function execute($window_size = null) {
+        if (count($this->requests) == 0)
+            return false;
+
+        return parent::execute($window_size);
+    }
+}
+

--- /dev/null
+++ b/lib/rolling-curl/example.php
@@ -1,1 +1,66 @@
+<?php
+/*
+authored by Josh Fraser (www.joshfraser.com)
+released under Apache License 2.0
 
+Maintained by Alexander Makarov, http://rmcreative.ru/
+
+$Id$
+*/
+
+require("RollingCurl.php");
+
+// a little example that fetches a bunch of sites in parallel and echos the page title and response info for each request
+function request_callback($response, $info, $request) {
+	// parse the page title out of the returned HTML
+	if (preg_match("~<title>(.*?)</title>~i", $response, $out)) {
+		$title = $out[1];
+	}
+	echo "<b>$title</b><br />";
+	print_r($info);
+    print_r($request);
+	echo "<hr>";
+}
+
+// single curl request
+$rc = new RollingCurl("request_callback");
+$rc->request("http://www.msn.com");
+$rc->execute();
+
+// another single curl request
+$rc = new RollingCurl("request_callback");
+$rc->request("http://www.google.com");
+$rc->execute();
+
+echo "<hr>";
+
+// top 20 sites according to alexa (11/5/09)
+$urls = array("http://www.google.com",
+              "http://www.facebook.com",
+              "http://www.yahoo.com",
+              "http://www.youtube.com",
+              "http://www.live.com",
+              "http://www.wikipedia.com",
+              "http://www.blogger.com",
+              "http://www.msn.com",
+              "http://www.baidu.com",
+              "http://www.yahoo.co.jp",
+              "http://www.myspace.com",
+              "http://www.qq.com",
+              "http://www.google.co.in",
+              "http://www.twitter.com",
+              "http://www.google.de",
+              "http://www.microsoft.com",
+              "http://www.google.cn",
+              "http://www.sina.com.cn",
+              "http://www.wordpress.com",
+              "http://www.google.co.uk");
+
+$rc = new RollingCurl("request_callback");
+$rc->window_size = 20;
+foreach ($urls as $url) {
+    $request = new RollingCurlRequest($url);
+    $rc->add($request);
+}
+$rc->execute();
+

--- /dev/null
+++ b/lib/rolling-curl/example_groups.php
@@ -1,1 +1,49 @@
+<?php
+require 'RollingCurl.php';
+require 'RollingCurlGroup.php';
 
+class TestCurlRequest extends RollingCurlGroupRequest {
+    public $test_verbose = true;
+
+    function process($output, $info) {
+        echo "Processing " . $this->url . "\n";
+        if ($this->test_verbose)
+            print_r($info);
+
+        parent::process($output, $info);
+    }
+}
+
+class TestCurlGroup extends RollingCurlGroup {
+    function process($output, $info, $request) {
+        echo "Group CB: Progress " . $this->name . " (" . ($this->finished_requests + 1) . "/" . $this->num_requests . ")\n";
+        parent::process($output, $info, $request);
+    }
+
+    function finished() {
+        echo "Group CB: Finished" . $this->name . "\n";
+        parent::finished();
+    }
+}
+
+$group = new TestCurlGroup("High");
+$group->add(new TestCurlRequest("www.google.de"));
+$group->add(new TestCurlRequest("www.yahoo.de"));
+$group->add(new TestCurlRequest("www.newyorktimes.com"));
+$reqs[] = $group;
+
+$group = new TestCurlGroup("Normal");
+$group->add(new TestCurlRequest("twitter.com"));
+$group->add(new TestCurlRequest("www.bing.com"));
+$group->add(new TestCurlRequest("m.facebook.com"));
+$reqs[] = $group;
+
+$reqs[] = new TestCurlRequest("www.kernel.org");
+
+// No callback here, as its done in Request class
+$rc = new GroupRollingCurl();
+
+foreach ($reqs as $req)
+    $rc->add($req);
+
+$rc->execute();

file:a/readme.txt -> file:b/readme.txt
--- a/readme.txt
+++ b/readme.txt
@@ -2,14 +2,32 @@
 Based on the maxious-canberra-transit-feed @ http://s3-ap-southeast-1.amazonaws.com/busresources/cbrfeed.zip
 Source code for the https://github.com/maxious/ACTBus-data transit 
 feed and https://github.com/maxious/ACTBus-ui this site available from github.
-Uses jQuery Mobile, PHP, Ruby, Python, Google Transit Feed Specification 
-tools, OpenTripPlanner, OpenLayers, OpenStreetMap, Cloudmade Geocoder 
+Uses jQuery Mobile, PHP, PostgreSQL, OpenTripPlanner, OpenLayers, OpenStreetMap, Cloudmade Geocoder 
 and Tile Service
 
-Must have view.sh running on port 8765 for this webapp to work
+See aws/awsStartup.sh for example startup steps. You need to load the included database dump; 
+for other transit networks you can use the updatedb.php script to load.
 
-For static maps, may have to do
+For openstreetmap static maps, may have to do
 /usr/sbin/setsebool -P httpd_can_network_connect=1
-on fedora
+on Fedora and other SELinux systems.
+
+To enter a service override, you can use the psql tool. eg.
+transitdata=# COPY calendar_dates (service_id, date, exception_type) FROM stdin;
+Enter data to be copied [spaced with tabs] followed by a newline.
+End with a backslash and a period on a line by itself.
+>> saturday	20110416	2 
+>> sunday	20110416	1
+>> saturday	20110423 	2
+>> sunday	20110423 	1
+>> weekday	20110425        2
+>> sunday	20110425        1
+>> weekday	20110422        2
+>> noservice    20110422        1
+>> weekday	20110426        2
+>> noservice    20110426        1
+>> sunday	20110424 	2
+>> noservice    20110424 	1
+>> \.
 
 

--- a/routeList.php
+++ b/routeList.php
@@ -7,87 +7,89 @@
 			<ul> 
 				<li><a href="routeList.php">By Final Destination...</a></li> 
 				<li><a href="routeList.php?bynumber=yes">By Number... </a></li>
-				<li><a href="routeList.php?bysuburb=yes">By Suburb... </a></li>
+				<li><a href="routeList.php?bysuburbs=yes">By Suburb... </a></li>
 				<li><a href="routeList.php?nearby=yes">Nearby... </a></li>
 			</ul>
                 </div>
 	';
 }
-if ($_REQUEST['bysuburb']) {
+if (isset($bysuburbs)) {
 	include_header("Routes by Suburb", "routeList");
 	navbar();
 	echo '  <ul data-role="listview" data-filter="true" data-inset="true" >';
-	if (!isset($_REQUEST['firstLetter'])) {
+	if (!isset($firstLetter)) {
 		foreach (range('A', 'Z') as $letter) {
-			echo "<li><a href=\"routeList.php?firstLetter=$letter&bysuburb=yes\">$letter...</a></li>\n";
+			echo "<li><a href=\"routeList.php?firstLetter=$letter&amp;bysuburbs=yes\">$letter...</a></li>\n";
 		}
 	}
 	else {
 		foreach ($suburbs as $suburb) {
-			if (startsWith($suburb, $_REQUEST['firstLetter'])) {
+			if (startsWith($suburb, $firstLetter)) {
 				echo '<li><a href="routeList.php?suburb=' . urlencode($suburb) . '">' . $suburb . '</a></li>';
 			}
 		}
 	}
 	echo '</ul>';
 }
-else if ($_REQUEST['nearby'] || $_REQUEST['suburb']) {
-	if ($_REQUEST['suburb']) {
-		$suburb = filter_var($_REQUEST['suburb'], FILTER_SANITIZE_STRING);
-		$url = $APIurl . "/json/stopzonesearch?q=" . $suburb;
-		include_header("Routes by Suburb", "routeList");
+else if (isset($nearby) || isset($suburb)) {
+	$routes = Array();
+	if ($suburb) {
+		include_header($suburb . " - " . ucwords(service_period()) , "routeList");
+		navbar();
+		timePlaceSettings();
 		trackEvent("Route Lists", "Routes By Suburb", $suburb);
+		$routes = getRoutesbysuburbs($suburb);
 	}
-	if ($_REQUEST['nearby']) {
-		$url = $APIurl . "/json/neareststops?lat={$_SESSION['lat']}&lon={$_SESSION['lon']}&limit=15";
+	if (isset($nearby)) {
 		include_header("Routes Nearby", "routeList", true, true);
+		trackEvent("Route Lists", "Routes Nearby", $_SESSION['lat'] . "," . $_SESSION['lon']);
+		navbar();
 		timePlaceSettings(true);
 		if (!isset($_SESSION['lat']) || !isset($_SESSION['lat']) || $_SESSION['lat'] == "" || $_SESSION['lon'] == "") {
 			include_footer();
 			die();
 		}
+		$routes = getRoutesNearby($_SESSION['lat'], $_SESSION['lon']);
 	}
-	$stops = json_decode(getPage($url));
-	$routes = Array();
-	foreach ($stops as $stop) {
-		$url = $APIurl . "/json/stoproutes?stop=" . $stop[0];
-		$stoproutes = json_decode(getPage($url));
-		foreach ($stoproutes as $route) {
-			if (!isset($routes[$route[0]])) $routes[$route[0]] = $route;
+	echo '  <ul data-role="listview" data-filter="true" data-inset="true" >';
+	if ($routes) {
+		foreach ($routes as $route) {
+			echo '<li><a href="trip.php?routeid=' . $route['route_id'] . '"><h3>' . $route['route_short_name'] . "</h3><p>" . $route['route_long_name'] . " (" . ucwords($route['service_id']) . ")</p>";
+			if (isset($nearby)) {
+				$time = getTimeInterpolatedRouteAtStop($route['route_id'], $route['stop_id']);
+				echo '<span class="ui-li-count">' . ($time['arrival_time'] ? $time['arrival_time'] : "No more trips today") . "<br>" . floor($route['distance']) . 'm away</span>';
+			}
+			echo "</a></li>\n";
 		}
 	}
-	navbar();
-	echo '  <ul data-role="listview" data-filter="true" data-inset="true" >';
-	sksort($routes, 1, true);
-	foreach ($routes as $row) {
-		echo '<li><a href="trip.php?routeid=' . $row[0] . '"><h3>'. $row[1] . "</h3><p>". $row[2] . " (" . ucwords($row[4]) . ")</p></a></li>\n";
+	else {
+		echo "<li style='text-align: center;'> No routes nearby.</li>";
 	}
 }
-else if ($_REQUEST['bynumber'] || $_REQUEST['numberSeries']) {
+else if (isset($bynumber) || isset($numberSeries)) {
 	include_header("Routes by Number", "routeList");
 	navbar();
 	echo ' <ul data-role="listview"  data-inset="true">';
-	$url = $APIurl . "/json/routes";
-	$contents = json_decode(getPage($url));
-	$routeSeries = Array();
-	$seriesRange = Array();
-	foreach ($contents as $key => $row) {
-		foreach (explode(" ", $row[1]) as $routeNumber) {
-			$seriesNum = substr($routeNumber, 0, -1) . "0";
-			if ($seriesNum == "0") $seriesNum = $routeNumber;
-			$finalDigit = substr($routeNumber, sizeof($routeNumber) - 1, 1);
-			if (isset($seriesRange[$seriesNum])) {
-				if ($finalDigit < $seriesRange[$seriesNum]['max']) $seriesRange[$seriesNum]['max'] = $routeNumber;
-				if ($finalDigit > $seriesRange[$seriesNum]['min']) $seriesRange[$seriesNum]['min'] = $routeNumber;
+	if (isset($bynumber)) {
+		$routes = getRoutesByNumber();
+		$routeSeries = Array();
+		$seriesRange = Array();
+		foreach ($routes as $key => $routeNumber) {
+			foreach (explode(" ", $routeNumber['route_short_name']) as $routeNumber) {
+				$seriesNum = substr($routeNumber, 0, -1) . "0";
+				if ($seriesNum == "0") $seriesNum = $routeNumber;
+				$finalDigit = substr($routeNumber, sizeof($routeNumber) - 1, 1);
+				if (isset($seriesRange[$seriesNum])) {
+					if ($finalDigit < $seriesRange[$seriesNum]['max']) $seriesRange[$seriesNum]['max'] = $routeNumber;
+					if ($finalDigit > $seriesRange[$seriesNum]['min']) $seriesRange[$seriesNum]['min'] = $routeNumber;
+				}
+				else {
+					$seriesRange[$seriesNum]['max'] = $routeNumber;
+					$seriesRange[$seriesNum]['min'] = $routeNumber;
+				}
+				$routeSeries[$seriesNum][$seriesNum . "-" . $row[1] . "-" . $row[0]] = $row;
 			}
-			else {
-				$seriesRange[$seriesNum]['max'] = $routeNumber;
-				$seriesRange[$seriesNum]['min'] = $routeNumber;
-			}
-			$routeSeries[$seriesNum][$seriesNum . "-" . $row[1] . "-" . $row[0]] = $row;
 		}
-	}
-	if ($_REQUEST['bynumber']) {
 		ksort($routeSeries);
 		ksort($seriesRange);
 		foreach ($routeSeries as $series => $routes) {
@@ -97,9 +99,10 @@
 			echo "</a></li>\n";
 		}
 	}
-	else if ($_REQUEST['numberSeries']) {
-		foreach ($routeSeries[$_REQUEST['numberSeries']] as $row) {
-			echo '<li> <a href="trip.php?routeid=' . $row[0] . '"><h3>' . $row[1] . "</h3><p>".  $row[2] . " (" . ucwords($row[3]) . ")</p></a></li>\n";
+	else if ($numberSeries) {
+		$routes = getRoutesByNumberSeries($numberSeries);
+		foreach ($routes as $route) {
+			echo '<li> <a href="trip.php?routeid=' . $route['route_id'] . '"><h3>' . $route['route_short_name'] . "</h3><p>" . $route['route_long_name'] . " (" . ucwords($route['service_id']) . ")</p></a></li>\n";
 		}
 	}
 }
@@ -107,20 +110,14 @@
 	include_header("Routes by Destination", "routeList");
 	navbar();
 	echo ' <ul data-role="listview"  data-inset="true">';
-	$url = $APIurl . "/json/routes";
-	$contents = json_decode(getPage($url));
-	// by destination!
-	foreach ($contents as $row) {
-		$routeDestinations[$row[2]][] = $row;
-	}
-	if ($_REQUEST['routeDestination']) {
-		foreach ($routeDestinations[urldecode($_REQUEST['routeDestination'])] as $row) {
-			echo '<li><a href="trip.php?routeid=' . $row[0] . '"><h3>' . $row[1] . '</h3><p>'  . $row[2] . " (" . ucwords($row[3]) . ")</p></a></li>\n";
+	if (isset($routeDestination)) {
+		foreach (getRoutesByDestination($routeDestination) as $route) {
+			echo '<li><a href="trip.php?routeid=' . $route["route_id"] . '"><h3>' . $route["route_short_name"] . '</h3><p>' . $route["route_long_name"] . " (" . ucwords($route['service_id']) . ")</p></a></li>\n";
 		}
 	}
 	else {
-		foreach ($routeDestinations as $destination => $routes) {
-			echo '<li><a href="' . curPageURL() . '/routeList.php?routeDestination=' . urlencode($destination) . '">' . $destination . "... </a></li>\n";
+		foreach (getRoutesByDestination() as $destination) {
+			echo '<li><a href="' . curPageURL() . '/routeList.php?routeDestination=' . urlencode($destination['route_long_name']) . '">' . $destination['route_long_name'] . "... </a></li>\n";
 		}
 	}
 }

file:a/schedule_viewer.py (deleted)
--- a/schedule_viewer.py
+++ /dev/null
@@ -1,711 +1,1 @@
-#!/usr/bin/python2.5
 
-# Copyright (C) 2007 Google Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""
-An example application that uses the transitfeed module.
-
-You must provide a Google Maps API key.
-"""
-
-
-import BaseHTTPServer, sys, urlparse
-from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
-from SocketServer import ThreadingMixIn
-import threading
-import bisect
-from gtfsscheduleviewer.marey_graph import MareyGraph
-import gtfsscheduleviewer
-import mimetypes
-import os.path
-import re
-import signal
-import simplejson
-import socket
-import time
-import datetime
-import transitfeed
-from transitfeed import util
-import urllib
-
-
-# By default Windows kills Python with Ctrl+Break. Instead make Ctrl+Break
-# raise a KeyboardInterrupt.
-if hasattr(signal, 'SIGBREAK'):
-  signal.signal(signal.SIGBREAK, signal.default_int_handler)
-
-
-mimetypes.add_type('text/plain', '.vbs')
-
-
-class ResultEncoder(simplejson.JSONEncoder):
-  def default(self, obj):
-    try:
-      iterable = iter(obj)
-    except TypeError:
-      pass
-    else:
-      return list(iterable)
-    return simplejson.JSONEncoder.default(self, obj)
-
-class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
-    """Handle requests in a separate thread."""
-
-def StopToTuple(stop):
-  """Return tuple as expected by javascript function addStopMarkerFromList"""
-  return (stop.stop_id, stop.stop_name, float(stop.stop_lat),
-          float(stop.stop_lon), stop.location_type, stop.stop_code)
-def StopZoneToTuple(stop):
-  """Return tuple as expected by javascript function addStopMarkerFromList"""
-  return (stop.stop_id, stop.stop_name, float(stop.stop_lat),
-          float(stop.stop_lon), stop.location_type, stop.stop_code, stop.zone_id)
-
-class ScheduleRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
-  cache = {}
-  
-  def do_GET(self):
-    scheme, host, path, x, params, fragment = urlparse.urlparse(self.path)
-    parsed_params = {}
-    for k in params.split('&'):
-      k = urllib.unquote(k)
-      if '=' in k:
-        k, v = k.split('=', 1)
-        parsed_params[k] = unicode(v, 'utf8')
-      else:
-        parsed_params[k] = ''
-
-    if path == '/':
-      return self.handle_GET_home()
-
-    m = re.match(r'/json/([a-z]{1,64})', path)
-    if m:
-      handler_name = 'handle_json_GET_%s' % m.group(1)
-      handler = getattr(self, handler_name, None)
-      if callable(handler):
-        return self.handle_json_wrapper_GET(handler, parsed_params, handler_name)
-
-    # Restrict allowable file names to prevent relative path attacks etc
-    m = re.match(r'/file/([a-z0-9_-]{1,64}\.?[a-z0-9_-]{1,64})$', path)
-    if m and m.group(1):
-      try:
-        f, mime_type = self.OpenFile(m.group(1))
-        return self.handle_static_file_GET(f, mime_type)
-      except IOError, e:
-        print "Error: unable to open %s" % m.group(1)
-        # Ignore and treat as 404
-
-    m = re.match(r'/([a-z]{1,64})', path)
-    if m:
-      handler_name = 'handle_GET_%s' % m.group(1)
-      handler = getattr(self, handler_name, None)
-      if callable(handler):
-        return handler(parsed_params)
-
-    return self.handle_GET_default(parsed_params, path)
-
-  def OpenFile(self, filename):
-    """Try to open filename in the static files directory of this server.
-    Return a tuple (file object, string mime_type) or raise an exception."""
-    (mime_type, encoding) = mimetypes.guess_type(filename)
-    assert mime_type
-    # A crude guess of when we should use binary mode. Without it non-unix
-    # platforms may corrupt binary files.
-    if mime_type.startswith('text/'):
-      mode = 'r'
-    else:
-      mode = 'rb'
-    return open(os.path.join(self.server.file_dir, filename), mode), mime_type
-
-  def handle_GET_default(self, parsed_params, path):
-    self.send_error(404)
-
-  def handle_static_file_GET(self, fh, mime_type):
-    content = fh.read()
-    self.send_response(200)
-    self.send_header('Content-Type', mime_type)
-    self.send_header('Content-Length', str(len(content)))
-    self.end_headers()
-    self.wfile.write(content)
-
-  def AllowEditMode(self):
-    return False
-
-  def handle_GET_home(self):
-    schedule = self.server.schedule
-    (min_lat, min_lon, max_lat, max_lon) = schedule.GetStopBoundingBox()
-    forbid_editing = ('true', 'false')[self.AllowEditMode()]
-
-    agency = ', '.join(a.agency_name for a in schedule.GetAgencyList()).encode('utf-8')
-
-    key = self.server.key
-    host = self.server.host
-
-    # A very simple template system. For a fixed set of values replace [xxx]
-    # with the value of local variable xxx
-    f, _ = self.OpenFile('index.html')
-    content = f.read()
-    for v in ('agency', 'min_lat', 'min_lon', 'max_lat', 'max_lon', 'key',
-              'host', 'forbid_editing'):
-      content = content.replace('[%s]' % v, str(locals()[v]))
-
-    self.send_response(200)
-    self.send_header('Content-Type', 'text/html')
-    self.send_header('Content-Length', str(len(content)))
-    self.end_headers()
-    self.wfile.write(content)
-
-  def handle_json_GET_routepatterns(self, params):
-    """Given a route_id generate a list of patterns of the route. For each
-    pattern include some basic information and a few sample trips."""
-    schedule = self.server.schedule
-    route = schedule.GetRoute(params.get('route', None))
-    if not route:
-      self.send_error(404)
-      return
-    time = int(params.get('time', 0))
-    sample_size = 10  # For each pattern return the start time for this many trips
-
-    pattern_id_trip_dict = route.GetPatternIdTripDict()
-    patterns = []
-
-    for pattern_id, trips in pattern_id_trip_dict.items():
-      time_stops = trips[0].GetTimeStops()
-      if not time_stops:
-        continue
-      has_non_zero_trip_type = False;
-      for trip in trips:
-        if trip['trip_type'] and trip['trip_type'] != '0':
-          has_non_zero_trip_type = True
-      name = u'%s to %s, %d stops' % (time_stops[0][2].stop_name, time_stops[-1][2].stop_name, len(time_stops))
-      transitfeed.SortListOfTripByTime(trips)
-
-      num_trips = len(trips)
-      if num_trips <= sample_size:
-        start_sample_index = 0
-        num_after_sample = 0
-      else:
-        # Will return sample_size trips that start after the 'time' param.
-
-        # Linear search because I couldn't find a built-in way to do a binary
-        # search with a custom key.
-        start_sample_index = len(trips)
-        for i, trip in enumerate(trips):
-          if trip.GetStartTime() >= time:
-            start_sample_index = i
-            break
-
-        num_after_sample = num_trips - (start_sample_index + sample_size)
-        if num_after_sample < 0:
-          # Less than sample_size trips start after 'time' so return all the
-          # last sample_size trips.
-          num_after_sample = 0
-          start_sample_index = num_trips - sample_size
-
-      sample = []
-      for t in trips[start_sample_index:start_sample_index + sample_size]:
-        sample.append( (t.GetStartTime(), t.trip_id) )
-
-      patterns.append((name, pattern_id, start_sample_index, sample,
-                       num_after_sample, (0,1)[has_non_zero_trip_type]))
-
-    patterns.sort()
-    return patterns
-
-  def handle_json_wrapper_GET(self, handler, parsed_params, handler_name):
-    """Call handler and output the return value in JSON."""
-    schedule = self.server.schedule
-    # round times to nearest 100 seconds
-    if "time" in parsed_params:
-      parsed_params['time'] = int(round(float(parsed_params['time']),-2))
-    paramkey = tuple(sorted(parsed_params.items()))
-    if handler_name in self.cache and paramkey in self.cache[handler_name] :
-      print ("Cache hit for ",handler_name," params ",parsed_params, 
-		" thread ", threading.currentThread().getName())
-    else:
-      print ("Cache miss for ",handler_name," params ",parsed_params, 
-               	" thread ", threading.currentThread().getName())
-      result = handler(parsed_params)
-      if not handler_name in self.cache:
-        self.cache[handler_name] = {}
-      self.cache[handler_name][paramkey] = ResultEncoder().encode(result)
-    content = self.cache[handler_name][paramkey]
-    self.send_response(200)
-    self.send_header('Content-Type', 'text/plain')
-    self.send_header('Content-Length', str(len(content)))
-    self.end_headers()
-    self.wfile.write(content)
-
-  def handle_json_GET_routes(self, params):
-    """Return a list of all routes."""
-    schedule = self.server.schedule
-    result = []
-    for r in schedule.GetRouteList():
-      servicep = None
-      for t in schedule.GetTripList():
-        if t.route_id == r.route_id:
-          servicep = t.service_period
-          break
-      result.append( (r.route_id, r.route_short_name, r.route_long_name, servicep.service_id) )
-    result.sort(key = lambda x: x[1:3])
-    return result
-
-  def handle_json_GET_routesearch(self, params):
-    """Return a list of routes with matching short name."""
-    schedule = self.server.schedule
-    routeshortname = params.get('routeshortname', None)
-    result = []
-    for r in schedule.GetRouteList():
-      if r.route_short_name == routeshortname:
-        servicep = None
-        for t in schedule.GetTripList():
-          if t.route_id == r.route_id:
-            servicep = t.service_period
-            break
-        result.append( (r.route_id, r.route_short_name, r.route_long_name, servicep.service_id) )
-    result.sort(key = lambda x: x[1:3])
-    return result
-
-
-  def handle_json_GET_routerow(self, params):
-    schedule = self.server.schedule
-    route = schedule.GetRoute(params.get('route', None))
-    return [transitfeed.Route._FIELD_NAMES, route.GetFieldValuesTuple()]
-  
-  def handle_json_GET_routetrips(self, params):
-    """ Get a trip for a route_id (preferablly the next one) """
-    schedule = self.server.schedule
-    query = params.get('route_id', None).lower()
-    result = []
-    for t in schedule.GetTripList():
-      if t.route_id == query:
-        try:
-          starttime = t.GetStartTime()  
-        except:
-          print "Error for GetStartTime of trip #" + t.trip_id + sys.exc_info()[0]
-        else:
-          cursor = t._schedule._connection.cursor()
-          cursor.execute(
-              'SELECT arrival_secs,departure_secs FROM stop_times WHERE '
-              'trip_id=? ORDER BY stop_sequence DESC LIMIT 1', (t.trip_id,))
-          (arrival_secs, departure_secs) = cursor.fetchone()
-          if arrival_secs != None:
-            endtime = arrival_secs
-          elif departure_secs != None:
-            endtime = departure_secs
-          else:
-            endtime =0
-          result.append ( (starttime, t.trip_id, endtime) )
-    return sorted(result, key=lambda trip: trip[2])
-  
-  def handle_json_GET_triprows(self, params):
-    """Return a list of rows from the feed file that are related to this
-    trip."""
-    schedule = self.server.schedule
-    try:
-      trip = schedule.GetTrip(params.get('trip', None))
-    except KeyError:
-      # if a non-existent trip is searched for, the return nothing
-      return
-    route = schedule.GetRoute(trip.route_id)
-    trip_row = dict(trip.iteritems())
-    route_row = dict(route.iteritems())
-    return [['trips.txt', trip_row], ['routes.txt', route_row]]
-
-  def handle_json_GET_tripstoptimes(self, params):
-    schedule = self.server.schedule
-    try:
-      trip = schedule.GetTrip(params.get('trip'))
-    except KeyError:
-       # if a non-existent trip is searched for, the return nothing
-      return
-    time_stops = trip.GetTimeInterpolatedStops()
-    stops = []
-    times = []
-    for arr,ts,is_timingpoint in time_stops:
-      stops.append(StopToTuple(ts.stop))
-      times.append(arr)
-    return [stops, times]
-
-  def handle_json_GET_tripshape(self, params):
-    schedule = self.server.schedule
-    try:
-      trip = schedule.GetTrip(params.get('trip'))
-    except KeyError:
-       # if a non-existent trip is searched for, the return nothing
-      return
-    points = []
-    if trip.shape_id:
-      shape = schedule.GetShape(trip.shape_id)
-      for (lat, lon, dist) in shape.points:
-        points.append((lat, lon))
-    else:
-      time_stops = trip.GetTimeStops()
-      for arr,dep,stop in time_stops:
-        points.append((stop.stop_lat, stop.stop_lon))
-    return points
-
-#
-# GeoPo Encode in Python
-# @author : Shintaro Inagaki
-# @param location (Dictionary) [lat (Float), lng (Float), scale(Int)]
-# @return geopo (String)
-#
-     
-  def handle_json_GET_neareststops(self, params):
-    """Return a list of the nearest 'limit' stops to 'lat', 'lon'"""
-    schedule = self.server.schedule
-    lat = float(params.get('lat'))
-    lon = float(params.get('lon'))
-    limit = int(params.get('limit',5))
-    scale = int(params.get('scale',5)) # 5 = neighbourhood ~ 1km, 4= town 5 by 7km
-    stops = []
-    
-    # 64characters (number + big and small letter + hyphen + underscore)
-    chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"
-
-    geopo = ""
-
-    # Change a degree measure to a decimal number
-    lat = (lat + 90.0) / 180 * 8 ** 10 # 90.0 is forced FLOAT type when lat is INT
-    lon = (lon + 180.0) / 360 * 8 ** 10 # 180.0 is same
-
-       # Compute a GeoPo code from head and concatenate
-    for i in range(scale):
-            order = int(lat / (8 ** (9 - i)) % 8) + int(lon / (8 ** (9 - i)) % 8) * 8
-            geopo = geopo + chars[order]
-
-    
-    for s in schedule.GetStopList():
-      if s.stop_code.find(geopo) != -1:
-        stops.append(s)
-        
-    if scale == 5:
-      print stops
-      return [StopToTuple(s) for s in stops]
-    else:
-      dist_stop_list = []
-      for s in stops:
-      # TODO: Use util.ApproximateDistanceBetweenStops?
-        dist = (s.stop_lat - lat)**2 + (s.stop_lon - lon)**2
-        if len(dist_stop_list) < limit:
-          bisect.insort(dist_stop_list, (dist, s))
-        elif dist < dist_stop_list[-1][0]:
-          bisect.insort(dist_stop_list, (dist, s))
-          dist_stop_list.pop()  # Remove stop with greatest distance
-      print dist_stop_list
-      return [StopToTuple(s) for dist, s in dist_stop_list]
-
-  def handle_json_GET_boundboxstops(self, params):
-    """Return a list of up to 'limit' stops within bounding box with 'n','e'
-    and 's','w' in the NE and SW corners. Does not handle boxes crossing
-    longitude line 180."""
-    schedule = self.server.schedule
-    n = float(params.get('n'))
-    e = float(params.get('e'))
-    s = float(params.get('s'))
-    w = float(params.get('w'))
-    limit = int(params.get('limit'))
-    stops = schedule.GetStopsInBoundingBox(north=n, east=e, south=s, west=w, n=limit)
-    return [StopToTuple(s) for s in stops]
-
-  def handle_json_GET_stops(self, params):
-    schedule = self.server.schedule
-    return [StopToTuple(s) for s in schedule.GetStopList()]
-    
-  def handle_json_GET_timingpoints(self, params):
-    schedule = self.server.schedule
-    matches = []
-    for s in schedule.GetStopList():
-      if s.stop_code.find("Wj") == -1:
-        matches.append(StopToTuple(s))
-    return matches
-
-  def handle_json_GET_stopsearch(self, params):
-    schedule = self.server.schedule
-    query = params.get('q', None).lower()
-    matches = []
-    for s in schedule.GetStopList():
-      if s.stop_name.lower().find(query) != -1 or s.stop_code.lower().find(query) != -1:
-        matches.append(StopToTuple(s))
-    return matches
-
-  def handle_json_GET_stopnamesearch(self, params):
-    schedule = self.server.schedule
-    query = params.get('q', None).lower()
-    matches = []
-    for s in schedule.GetStopList():
-      if s.stop_name.lower().find(query) != -1:
-        matches.append(StopToTuple(s))
-    return matches
-  
-  def handle_json_GET_stopcodesearch(self, params):
-    schedule = self.server.schedule
-    query = params.get('q', None).lower()
-    matches = []
-    for s in schedule.GetStopList():
-      if s.stop_code.lower().find(query) != -1:
-        matches.append(StopToTuple(s))
-    return matches
-
-  def handle_json_GET_stopzonesearch(self, params):
-    schedule = self.server.schedule
-    query = params.get('q', None).lower()
-    matches = []
-    for s in schedule.GetStopList():
-      if s.zone_id != None and s.zone_id.lower().find(query) != -1:
-        matches.append(StopToTuple(s))
-    return matches
-
-  def handle_json_GET_stop(self, params):
-    schedule = self.server.schedule
-    query = params.get('stop_id', None).lower()
-    for s in schedule.GetStopList():
-      if s.stop_id.lower() == query:
-        return StopToTuple(s)
-    return []
-  def handle_json_GET_stoproutes(self, params):
-    """Given a stop_id return all routes to visit the stop."""
-    schedule = self.server.schedule
-    stop = schedule.GetStop(params.get('stop', None))
-    service_period = params.get('service_period', None)
-    trips = stop.GetTrips(schedule)
-    result = {}
-    for trip in trips:
-      route = schedule.GetRoute(trip.route_id)
-      if not route.route_short_name+route.route_long_name+trip.service_id in result:
-        result[route.route_short_name+route.route_long_name+trip.service_id] = (route.route_id, route.route_short_name, route.route_long_name, trip.trip_id, trip.service_id)
-    return result
-    
-  def handle_json_GET_stopalltrips(self, params):
-    """Given a stop_id return all trips to visit the stop."""
-    schedule = self.server.schedule
-    stop = schedule.GetStop(params.get('stop', None))
-    service_period = params.get('service_period', None)
-    time_trips = stop.GetStopTimeTrips(schedule)
-    result = []
-    for time, (trip, index), tp in time_trips:
-      headsign = None
-      # Find the most recent headsign from the StopTime objects
-      for stoptime in trip.GetStopTimes()[index::-1]:
-        if stoptime.stop_headsign:
-          headsign = stoptime.stop_headsign
-          break
-      # If stop_headsign isn't found, look for a trip_headsign
-      if not headsign:
-        headsign = trip.trip_headsign
-      route = schedule.GetRoute(trip.route_id)
-      trip_name = ''
-      if route.route_short_name:
-        trip_name += route.route_short_name
-      if route.route_long_name:
-        if len(trip_name):
-          trip_name += " - "
-        trip_name += route.route_long_name
-      if service_period == None or trip.service_id == service_period:
-        result.append((time, (trip.trip_id, trip_name, trip.service_id), tp))
-    return result
-  
-
-
-  def handle_json_GET_stoptrips(self, params):
-    """Given a stop_id and time in seconds since midnight return the next
-    trips to visit the stop."""
-    schedule = self.server.schedule
-    stop = schedule.GetStop(params.get('stop', None))
-    requested_time = int(params.get('time', 0))
-    limit = int(params.get('limit', 15))
-    service_period = params.get('service_period', None)
-    time_range = int(params.get('time_range', 24*60*60))
-    
-    
-    filtered_time_trips = []
-    for trip, index in stop._GetTripIndex(schedule):
-      tripstarttime = trip.GetStartTime()
-      if tripstarttime > requested_time and tripstarttime < (requested_time + time_range):
-        time, stoptime, tp = trip.GetTimeInterpolatedStops()[index]
-        if time > requested_time and time < (requested_time + time_range):
-          bisect.insort(filtered_time_trips, (time, (trip, index), tp))
-    
-    result = []
-    for time, (trip, index), tp in filtered_time_trips:
-      if len(result) > limit:
-        break
-      headsign = None
-      # Find the most recent headsign from the StopTime objects
-      for stoptime in trip.GetStopTimes()[index::-1]:
-        if stoptime.stop_headsign:
-          headsign = stoptime.stop_headsign
-          break
-      # If stop_headsign isn't found, look for a trip_headsign
-      if not headsign:
-        headsign = trip.trip_headsign
-      route = schedule.GetRoute(trip.route_id)
-      trip_name = ''
-      if route.route_short_name:
-        trip_name += route.route_short_name
-      if route.route_long_name:
-        if len(trip_name):
-          trip_name += " - "
-        trip_name += route.route_long_name
-        # comment out directions because we already have them in the long name
-      #if headsign:
-      #  trip_name += " (Direction: %s)" % headsign
-      if service_period == None or trip.service_id == service_period:
-        result.append((time, (trip.trip_id, trip_name, trip.service_id), tp))
-    return result
-
-  def handle_GET_ttablegraph(self,params):
-    """Draw a Marey graph in SVG for a pattern (collection of trips in a route
-    that visit the same sequence of stops)."""
-    schedule = self.server.schedule
-    marey = MareyGraph()
-    trip = schedule.GetTrip(params.get('trip', None))
-    route = schedule.GetRoute(trip.route_id)
-    height = int(params.get('height', 300))
-
-    if not route:
-      print 'no such route'
-      self.send_error(404)
-      return
-
-    pattern_id_trip_dict = route.GetPatternIdTripDict()
-    pattern_id = trip.pattern_id
-    if pattern_id not in pattern_id_trip_dict:
-      print 'no pattern %s found in %s' % (pattern_id, pattern_id_trip_dict.keys())
-      self.send_error(404)
-      return
-    triplist = pattern_id_trip_dict[pattern_id]
-
-    pattern_start_time = min((t.GetStartTime() for t in triplist))
-    pattern_end_time = max((t.GetEndTime() for t in triplist))
-
-    marey.SetSpan(pattern_start_time,pattern_end_time)
-    marey.Draw(triplist[0].GetPattern(), triplist, height)
-
-    content = marey.Draw()
-
-    self.send_response(200)
-    self.send_header('Content-Type', 'image/svg+xml')
-    self.send_header('Content-Length', str(len(content)))
-    self.end_headers()
-    self.wfile.write(content)
-
-
-def FindPy2ExeBase():
-  """If this is running in py2exe return the install directory else return
-  None"""
-  # py2exe puts gtfsscheduleviewer in library.zip. For py2exe setup.py is
-  # configured to put the data next to library.zip.
-  windows_ending = gtfsscheduleviewer.__file__.find('\\library.zip\\')
-  if windows_ending != -1:
-    return transitfeed.__file__[:windows_ending]
-  else:
-    return None
-
-
-def FindDefaultFileDir():
-  """Return the path of the directory containing the static files. By default
-  the directory is called 'files'. The location depends on where setup.py put
-  it."""
-  base = FindPy2ExeBase()
-  if base:
-    return os.path.join(base, 'schedule_viewer_files')
-  else:
-    # For all other distributions 'files' is in the gtfsscheduleviewer
-    # directory.
-    base = os.path.dirname(gtfsscheduleviewer.__file__)  # Strip __init__.py
-    return os.path.join(base, 'files')
-
-
-def GetDefaultKeyFilePath():
-  """In py2exe return absolute path of file in the base directory and in all
-  other distributions return relative path 'key.txt'"""
-  windows_base = FindPy2ExeBase()
-  if windows_base:
-    return os.path.join(windows_base, 'key.txt')
-  else:
-    return 'key.txt'
-
-
-def main(RequestHandlerClass = ScheduleRequestHandler):
-  usage = \
-'''%prog [options] [<input GTFS.zip>]
-
-Runs a webserver that lets you explore a <input GTFS.zip> in your browser.
-
-If <input GTFS.zip> is omited the filename is read from the console. Dragging
-a file into the console may enter the filename.
-'''
-  parser = util.OptionParserLongError(
-      usage=usage, version='%prog '+transitfeed.__version__)
-  parser.add_option('--feed_filename', '--feed', dest='feed_filename',
-                    help='file name of feed to load')
-  parser.add_option('--key', dest='key',
-                    help='Google Maps API key or the name '
-                    'of a text file that contains an API key')
-  parser.add_option('--host', dest='host', help='Host name of Google Maps')
-  parser.add_option('--port', dest='port', type='int',
-                    help='port on which to listen')
-  parser.add_option('--file_dir', dest='file_dir',
-                    help='directory containing static files')
-  parser.add_option('-n', '--noprompt', action='store_false',
-                    dest='manual_entry',
-                    help='disable interactive prompts')
-  parser.set_defaults(port=8765,
-                      host='maps.google.com',
-                      file_dir=FindDefaultFileDir(),
-                      manual_entry=True)
-  (options, args) = parser.parse_args()
-
-  if not os.path.isfile(os.path.join(options.file_dir, 'index.html')):
-    print "Can't find index.html with --file_dir=%s" % options.file_dir
-    exit(1)
-
-  if not options.feed_filename and len(args) == 1:
-    options.feed_filename = args[0]
-
-  if not options.feed_filename and options.manual_entry:
-    options.feed_filename = raw_input('Enter Feed Location: ').strip('"')
-
-  default_key_file = GetDefaultKeyFilePath()
-  if not options.key and os.path.isfile(default_key_file):
-    options.key = open(default_key_file).read().strip()
-
-  if options.key and os.path.isfile(options.key):
-    options.key = open(options.key).read().strip()
-  
-  schedule = transitfeed.Schedule(problem_reporter=transitfeed.ProblemReporter())
-  print 'Loading data from feed "%s"...' % options.feed_filename
-  print '(this may take a few minutes for larger cities)'
-  t0 = datetime.datetime.now()
-  schedule.Load(options.feed_filename)
-  print ("Loaded in", (datetime.datetime.now() - t0).seconds , "seconds")
-  server = ThreadedHTTPServer(server_address=('', options.port),
-                               RequestHandlerClass=RequestHandlerClass)
-  server.key = options.key
-  server.schedule = schedule
-  server.file_dir = options.file_dir
-  server.host = options.host
-  server.feed_path = options.feed_filename  
-
-
-  print ("To view, point your browser at http://localhost:%d/" %
-         (server.server_port))
-  server.serve_forever()
-
-
-if __name__ == '__main__':
-  main()
-

file:a/stop.php -> file:b/stop.php
--- a/stop.php
+++ b/stop.php
@@ -1,10 +1,7 @@
 <?php
 include ('include/common.inc.php');
-$stopid = filter_var($_REQUEST['stopid'], FILTER_SANITIZE_NUMBER_INT);
-$stopcode = filter_var($_REQUEST['stopcode'], FILTER_SANITIZE_STRING);
-$url = $APIurl . "/json/stop?stop_id=" . $stopid;
-$stop = json_decode(getPage($url));
-if ($stopcode != "" && $stop[5] != $stopcode) {
+if ($stopid) $stop = getStop($stopid);
+/*if ($stopcode != "" && $stop[5] != $stopcode) {
 	$url = $APIurl . "/json/stopcodesearch?q=" . $stopcode;
 	$stopsearch = json_decode(getPage($url));
 	$stopid = $stopsearch[0][0];
@@ -14,90 +11,102 @@
 if (!startsWith($stop[5], "Wj") && strpos($stop[1], "Platform") === false) {
 	// expand out to all platforms
 	
-}
+}*/
 $stops = Array();
 $stopPositions = Array();
 $stopNames = Array();
 $tripStopNumbers = Array();
 $allStopsTrips = Array();
+$fetchedTripSequences = Array();
 $stopLinks = "";
-if (isset($_REQUEST['stopids'])) {
-	$stopids = explode(",", filter_var($_REQUEST['stopids'], FILTER_SANITIZE_STRING));
+if (isset($stopids)) {
 	foreach ($stopids as $sub_stopid) {
-		$url = $APIurl . "/json/stop?stop_id=" . $sub_stopid;
-		$stop = json_decode(getPage($url));
-		$stops[] = $stop;
+		$stops[] = getStop($sub_stopid);
 	}
 	$stop = $stops[0];
-	$stopid = $stops[0][0];
+	$stopid = $stops[0]["stop_id"];
 	$stopLinks.= "Individual stop pages: ";
 	foreach ($stops as $key => $sub_stop) {
-	//	$stopNames[$key] = $sub_stop[1] . ' Stop #' . ($key + 1);
-        if (strpos($stop[1],
-                   "Station")) {
-		$stopNames[$key] = 'Platform ' . ($key + 1);
-		$stopLinks.= '<a href="stop.php?stopid=' . $sub_stop[0] . '&stopcode=' . $sub_stop[5] . '">' . $sub_stop[1] . '</a> ';  
-        }         else {
-		$stopNames[$key] = '#' . ($key + 1);
-		$stopLinks.= '<a href="stop.php?stopid=' . $sub_stop[0] . '&stopcode=' . $sub_stop[5] . '">' . $sub_stop[1] . ' Stop #' . ($key + 1) . '</a> ';
-        }
+		//	$stopNames[$key] = $sub_stop[1] . ' Stop #' . ($key + 1);
+		if (strpos($stop["stop_name"], "Station")) {
+			$stopNames[$key] = 'Platform ' . ($key + 1);
+			$stopLinks.= '<a href="stop.php?stopid=' . $sub_stop["stop_id"] . '&amp;stopcode=' . $sub_stop["stop_code"] . '">' . $sub_stop["stop_name"] . '</a> ';
+		}
+		else {
+			$stopNames[$key] = '#' . ($key + 1);
+			$stopLinks.= '<a href="stop.php?stopid=' . $sub_stop["stop_id"] . '&amp;stopcode=' . $sub_stop["stop_code"] . '">' . $sub_stop["stop_name"] . ' Stop #' . ($key + 1) . '</a> ';
+		}
 		$stopPositions[$key] = Array(
-			$sub_stop[2],
-			$sub_stop[3]
+			$sub_stop["stop_lat"],
+			$sub_stop["stop_lon"]
 		);
-		$url = $APIurl . "/json/stoptrips?stop=" . $sub_stop[0] . "&time=" . midnight_seconds() . "&service_period=" . service_period();
-		$trips = json_decode(getPage($url));
+		$trips = getStopTrips($sub_stop["stop_id"]);
+		$tripSequence = "";
 		foreach ($trips as $trip) {
-			if (!isset($allStopsTrips[$trip[1][0]])) $allStopsTrips[$trip[1][0]] = $trip;
-			$tripStopNumbers[$trip[1][0]][] = $key;
+			$tripSequence.= "{$trip['trip_id']},";
+			$tripStopNumbers[$trip['trip_id']][] = $key;
 		}
+		if (!in_array($tripSequence, $fetchedTripSequences)) {
+			// only fetch new trip sequences
+			$fetchedTripSequences[] = $tripSequence;
+			$trips = getStopTripsWithTimes($sub_stop["stop_id"]);
+			foreach ($trips as $trip) {
+				if (!isset($allStopsTrips[$trip["trip_id"]])) $allStopsTrips[$trip["trip_id"]] = $trip;
+			}
+		}
+		//else {
+		//	echo "skipped sequence $tripSequence";
+		//}
 	}
 }
-include_header($stop[1], "stop");
+include_header($stop['stop_name'], "stop");
 timePlaceSettings();
-echo '<div data-role="content" class="ui-content" role="main">        <a name="maincontent" id="maincontent"></a>';
 echo $stopLinks;
 if (sizeof($stops) > 0) {
-    trackEvent("View Stops","View Combined Stops", $stop[1], $stop[0]);
-
-	echo '<p>' . staticmap($stopPositions) . '</p>';
+	trackEvent("View Stops", "View Combined Stops", $stop["stop_name"], $stop["stop_id"]);
+	echo staticmap($stopPositions);
 }
 else {
-        trackEvent("View Stops","View Single Stop", $stop[1], $stop[0]);
-	echo '<p>' . staticmap(Array(
+	trackEvent("View Stops", "View Single Stop", $stop["stop_name"], $stop["stop_id"]);
+	echo staticmap(Array(
 		0 => Array(
-			$stop[2],
-			$stop[3]
+			$stop["stop_lat"],
+			$stop["stop_lon"]
 		)
-	)) . '</p>';
+	)) ;
 }
 echo '  <ul data-role="listview"  data-inset="true">';
 if (sizeof($allStopsTrips) > 0) {
-    sksort($allStopsTrips,0, $true);
+    sktimesort($allStopsTrips,"arrival_time", true);
 	$trips = $allStopsTrips;
 }
 else {
-	$url = $APIurl . "/json/stoptrips?stop=" . $stopid . "&time=" . midnight_seconds() . "&service_period=" . service_period();
-	$trips = json_decode(getPage($url));
+	$trips = getStopTripsWithTimes($stopid);
 }
-foreach ($trips as $row) {
-	echo '<li>';
-	echo '<a href="trip.php?stopid=' . $stopid . '&tripid=' . $row[1][0] . '"><h3>' . $row[1][1]."</h3><p>";
-        $viaPoints = viaPointNames($row[1][0], $stopid);
-        if ($viaPoints != "") echo '<br><span class="viaPoints">Via: ' . $viaPoints . '</span>';
-	if (sizeof($tripStopNumbers) > 0) {
-            echo '<br><small>Boarding At: ';
-            foreach ($tripStopNumbers[$row[1][0]] as $key) {
-                echo $stopNames[$key] .' ';
-            }
-            echo '</small>';
-        }
-	echo '</p>';
-	echo '<p class="ui-li-aside"><strong>' . midnight_seconds_to_time($row[0]) . '</strong></p>';
-	echo '</a></li>';
+if (sizeof($trips) == 0) {
+	echo "<li style='text-align: center;'>No trips in the near future.</li>";
 }
-if (sizeof($trips) == 0) echo "<li> <center>No trips in the near future.</center> </li>";
-echo '</ul></div>';
+else {
+	foreach ($trips as $trip) {
+		echo '<li>';
+		echo '<a href="trip.php?stopid=' . $stopid . '&amp;tripid=' . $trip['trip_id'] . '"><h3>' . $trip['route_short_name'] . " " . $trip['route_long_name'] . "</h3><p>";
+		$viaPoints = viaPointNames($trip['trip_id'], $trip['stop_sequence']);
+		if ($viaPoints != "") echo '<br><span class="viaPoints">Via: ' . $viaPoints . '</span>';
+		if (sizeof($tripStopNumbers) > 0) {
+			echo '<br><small>Boarding At: ';
+			foreach ($tripStopNumbers[$trip['trip_id']] as $key) {
+				echo $stopNames[$key] . ' ';
+			}
+			echo '</small>';
+		}
+		echo '</p>';
+		echo '<p class="ui-li-aside"><strong>' . $trip['arrival_time'] . '</strong></p>';
+		echo '</a></li>';
+		flush();
+		@ob_flush();
+	}
+}
+echo '</ul>';
 include_footer();
 ?>
 

--- a/stopList.php
+++ b/stopList.php
@@ -1,16 +1,13 @@
 <?php
 include ('include/common.inc.php');
-function filterByFirstLetter($var)
-{
-	return $var[1][0] == $_REQUEST['firstLetter'];
-}
+$stops = Array();
 function navbar()
 {
 	echo '
 		<div data-role="navbar">
 			<ul> 
 				<li><a href="stopList.php">Timing Points</a></li>
-				<li><a href="stopList.php?suburbs=yes">By Suburb</a></li>
+				<li><a href="stopList.php?bysuburbs=yes">By Suburb</a></li>
 				<li><a href="stopList.php?nearby=yes">Nearby Stops</a></li>
 				<li><a href="stopList.php?allstops=yes">All Stops</a></li> 
 			</ul>
@@ -18,18 +15,18 @@
 	';
 }
 // By suburb
-if (isset($_REQUEST['suburbs'])) {
+if (isset($bysuburbs)) {
 	include_header("Stops by Suburb", "stopList");
 	navbar();
 	echo '  <ul data-role="listview" data-filter="true" data-inset="true" >';
-	if (!isset($_REQUEST['firstLetter'])) {
+	if (!isset($firstLetter)) {
 		foreach (range('A', 'Z') as $letter) {
-			echo "<li><a href=\"stopList.php?firstLetter=$letter&suburbs=yes\">$letter...</a></li>\n";
+			echo "<li><a href=\"stopList.php?firstLetter=$letter&amp;bysuburbs=yes\">$letter...</a></li>\n";
 		}
 	}
 	else {
 		foreach ($suburbs as $suburb) {
-			if (startsWith($suburb, $_REQUEST['firstLetter'])) {
+			if (startsWith($suburb, $firstLetter)) {
 				echo '<li><a href="stopList.php?suburb=' . urlencode($suburb) . '">' . $suburb . '</a></li>';
 			}
 		}
@@ -38,100 +35,95 @@
 }
 else {
 	// Timing Points / All stops
-	if ($_REQUEST['allstops']) {
+	if (isset($allstops)) {
 		$listType = 'allstops=yes';
-		$url = $APIurl . "/json/stops";
+		$stops = getStops();
 		include_header("All Stops", "stopList");
 		navbar();
 		timePlaceSettings();
 	}
-	else if ($_REQUEST['nearby']) {
+	else if (isset($nearby)) {
 		$listType = 'nearby=yes';
-		$url = $APIurl . "/json/neareststops?lat={$_SESSION['lat']}&lon={$_SESSION['lon']}&limit=15";
 		include_header("Nearby Stops", "stopList", true, true);
+		trackEvent("Stop Lists", "Stops Nearby", $_SESSION['lat'] . "," . $_SESSION['lon']);
 		navbar();
 		timePlaceSettings(true);
 		if (!isset($_SESSION['lat']) || !isset($_SESSION['lat']) || $_SESSION['lat'] == "" || $_SESSION['lon'] == "") {
 			include_footer();
 			die();
 		}
+		$stops = getNearbyStops($_SESSION['lat'], $_SESSION['lon'], 15);
 	}
-	else if ($_REQUEST['suburb']) {
-		$suburb = filter_var($_REQUEST['suburb'], FILTER_SANITIZE_STRING);
-		$listType = "suburb=$suburb";
-		$url = $APIurl . "/json/stopzonesearch?q=" . $suburb;
+	else if (isset($suburb)) {
+		$stops = getStopsBySuburb($suburb);
 		include_header("Stops in " . ucwords($suburb) , "stopList");
 		navbar();
-	       trackEvent("Stop Lists","Stops By Suburb", $suburb);
+		trackEvent("Stop Lists", "Stops By Suburb", $suburb);
 	}
 	else {
-		$url = $APIurl . "/json/timingpoints";
+		$stops = getStops(true, $firstLetter);
 		include_header("Timing Points / Major Stops", "stopList");
 		navbar();
 		timePlaceSettings();
 	}
 	echo '  <ul data-role="listview" data-filter="true" data-inset="true" >';
-	if (!isset($_REQUEST['firstLetter']) && !$_REQUEST['suburb'] && !$_REQUEST['nearby']) {
+	if (!isset($firstLetter) && !isset($suburb) && !isset($nearby)) {
 		foreach (range('A', 'Z') as $letter) {
-			echo "<li><a href=\"stopList.php?firstLetter=$letter&$listType\">$letter...</a></li>\n";
+			echo "<li><a href=\"stopList.php?firstLetter=$letter&amp;$listType\">$letter...</a></li>\n";
 		}
 	}
 	else {
-		$stops = json_decode(getPage($url));
-		foreach ($stops as $key => $row) {
-			$stopName[$key] = $row[1];
-		}
-		// Sort the stops by name
-		array_multisort($stopName, SORT_ASC, $stops);
-		if (!isset($_REQUEST['suburb']) && !isset($_REQUEST['nearby'])) {
-			$stops = array_filter($stops, "filterByFirstLetter");
-		}
+		//var_dump($stops);
 		$stopsGrouped = Array();
-		foreach ($stops as $key => $row) {
-			if ((trim(preg_replace("/\(Platform.*/", "", $stops[$key][1])) != trim(preg_replace("/\(Platform.*/", "", $stops[$key + 1][1]))) || $key + 1 >= sizeof($stops)) {
+		foreach ($stops as $key => $stop) {
+			if ((trim(preg_replace("/\(Platform.*/", "", $stops[$key]["stop_name"])) != trim(preg_replace("/\(Platform.*/", "", $stops[$key + 1]["stop_name"]))) || $key + 1 >= sizeof($stops)) {
 				if (sizeof($stopsGrouped) > 0) {
 					// print and empty grouped stops
 					// subsequent duplicates
-					$stopsGrouped["stop_ids"][] = $row[0];
+					$stopsGrouped["stop_ids"][] = $stop['stop_id'];
 					echo '<li>';
 					if (!startsWith($stopsGrouped['stop_codes'][0], "Wj")) echo '<img src="css/images/time.png" alt="Timing Point: " class="ui-li-icon">';
 					echo '<a href="stop.php?stopids=' . implode(",", $stopsGrouped['stop_ids']) . '">';
 					if (isset($_SESSION['lat']) && isset($_SESSION['lon'])) {
-						echo '<span class="ui-li-count">' . distance($row[2], $row[3], $_SESSION['lat'], $_SESSION['lon'], true) . 'm away</span>';
+						echo '<span class="ui-li-count">' . distance($stop['stop_lat'], $stop['stop_lon'], $_SESSION['lat'], $_SESSION['lon'], true) . 'm away</span>';
 					}
-					echo bracketsMeanNewLine(trim(preg_replace("/\(Platform.*/", "", $row[1])) . '(' . sizeof($stopsGrouped["stop_ids"]) . ' stops)');
+					echo bracketsMeanNewLine(trim(preg_replace("/\(Platform.*/", "", $stop['stop_name'])) . '(' . sizeof($stopsGrouped["stop_ids"]) . ' stops)');
 					echo "</a></li>\n";
+					flush();
+					@ob_flush();
 					$stopsGrouped = Array();
 				}
 				else {
 					// just a normal stop
 					echo '<li>';
-					if (!startsWith($row[5], "Wj")) echo '<img src="css/images/time.png" alt="Timing Point" class="ui-li-icon">';
-					echo '<a href="stop.php?stopid=' . $row[0] . (startsWith($row[5], "Wj") ? '&stopcode=' . $row[5] : "") . '">';
+					if (!startsWith($stop['stop_code'], "Wj")) echo '<img src="css/images/time.png" alt="Timing Point" class="ui-li-icon">';
+					echo '<a href="stop.php?stopid=' . $stop['stop_id'] . (startsWith($stop['stop_code'], "Wj") ? '&amp;stopcode=' . $stop['stop_code'] : "") . '">';
 					if (isset($_SESSION['lat']) && isset($_SESSION['lon'])) {
-						echo '<span class="ui-li-count">' . distance($row[2], $row[3], $_SESSION['lat'], $_SESSION['lon'], true) . 'm away</span>';
+						echo '<span class="ui-li-count">' . distance($stop['stop_lat'], $stop['stop_lon'], $_SESSION['lat'], $_SESSION['lon'], true) . 'm away</span>';
 					}
-					echo bracketsMeanNewLine($row[1]);
+					echo bracketsMeanNewLine($stop['stop_name']);
 					echo "</a></li>\n";
+					flush();
+					@ob_flush();
 				}
 			}
 			else {
 				// this is a duplicated line item
-				if ($key - 1 <= 0 || (trim(preg_replace("/\(Platform.*/", "", $stops[$key][1])) != trim(preg_replace("/\(Platform.*/", "", $stops[$key - 1][1])))) {
+				if ($key - 1 <= 0 || (trim(preg_replace("/\(Platform.*/", "", $stops[$key]['stop_name'])) != trim(preg_replace("/\(Platform.*/", "", $stops[$key - 1]['stop_name'])))) {
 					// first duplicate
 					$stopsGrouped = Array(
-						"name" => trim(preg_replace("/\(Platform.*/", "", $row[1])) ,
+						"name" => trim(preg_replace("/\(Platform.*/", "", $stop['stop_name'])) ,
 						"stop_ids" => Array(
-							$row[0]
+							$stop['stop_id']
 						) ,
 						"stop_codes" => Array(
-							$row[5]
+							$stop['stop_code']
 						)
 					);
 				}
 				else {
 					// subsequent duplicates
-					$stopsGrouped["stop_ids"][] = $row[0];
+					$stopsGrouped["stop_ids"][] = $stop['stop_id'];;
 				}
 			}
 		}

file:a/trip.php -> file:b/trip.php
--- a/trip.php
+++ b/trip.php
@@ -1,93 +1,86 @@
 <?php
 include ('include/common.inc.php');
-$tripid = filter_var($_REQUEST['tripid'], FILTER_SANITIZE_NUMBER_INT);
-$stopid = filter_var($_REQUEST['stopid'], FILTER_SANITIZE_NUMBER_INT);
-$routeid = filter_var($_REQUEST['routeid'], FILTER_SANITIZE_NUMBER_INT);
+
 $routetrips = Array();
-if ($_REQUEST['routeid'] && !$_REQUEST['tripid']) {
-	$tripid = 0;
-	$url = $APIurl . "/json/routetrips?route_id=" . $routeid;
-	$routetrips = json_decode(getPage($url));
-	foreach ($routetrips as $trip) {
-		if ($trip[2] > midnight_seconds()) {
-			$tripid = $trip[1];
-			break;
-		}
-	}
-	if ($tripid == 0) $tripid = $routetrips[0][1];
+
+if (isset($routeid) && !isset($tripid)) {
+    $trip = getRouteNextTrip($routeid);
+    $tripid = $trip['trip_id'];
+} else {
+    $trip = getTrip($tripid);
+    $routeid = $trip["route_id"];
 }
-$url = $APIurl . "/json/triprows?trip=" . $tripid;
-$trips = array_flatten(json_decode(getPage($url)));
-if (sizeof($routetrips) == 0) {
-	$routeid = $trips[1]->route_id;
-	$url = $APIurl . "/json/routetrips?route_id=" . $trips[1]->route_id;
-	$routetrips = json_decode(getPage($url));
+
+$routetrips = getRouteTrips($routeid);
+    
+include_header("Stops on " . $trip['route_short_name'] . ' ' . $trip['route_long_name'], "trip");
+trackEvent("Route/Trip View","View Route",  $trip['route_short_name'] . ' ' . $trip['route_long_name'], $routeid);
+
+
+echo '<h2>Via:</h2> <small>' . viaPointNames($tripid) . '</small>';
+echo '<h2>Other Trips:</h2> ';
+foreach (getRouteTrips($routeid) as $othertrip) {
+	echo '<a href="trip.php?tripid=' . $othertrip['trip_id'] . "&amp;routeid=" . $routeid . '">' . str_replace("  ",":00",str_replace(":00"," ",$othertrip['arrival_time'])). '</a> ';
 }
-include_header("Stops on " . $trips[1]->route_short_name . ' ' . $trips[1]->route_long_name, "trip");
-trackEvent("Route/Trip View","View Route", $trips[1]->route_short_name . ' ' . $trips[1]->route_long_name, $trips[1]->route_id);
-$url = $APIurl . "/json/tripstoptimes?trip=" . $tripid;
-$json = json_decode(getPage($url));
-$stops = $json[0];
-$times = $json[1];
-$viaPoints = Array();
-foreach ($stops as $stop) {
-	if (!startsWith($stop[5], "Wj")) {
-		$viaPoints[] = $stop[1];
-	}
+flush(); @ob_flush();
+echo '<h2>Other directions/timing periods:</h2> ';
+foreach (getRoutesByNumber($trip['route_short_name']) as $row) {
+	if ($row['route_id'] != $routeid) echo '<a href="trip.php?routeid=' . $row['route_id'] . '">' . $row['route_long_name'] . ' (' . ucwords($row['service_id']) . ')</a> ';
 }
-echo '<p><h2>Via:</h2> ' . implode(", ", $viaPoints) . '</small></p>';
-echo '<p><h2>Other Trips:</h2> ';
-foreach ($routetrips as $othertrip) {
-	echo '<a href="trip.php?tripid=' . $othertrip[1] . "&routeid=" . $routeid . '">' . midnight_seconds_to_time($othertrip[0]) . '</a> ';
-}
-echo '</p><p><h2>Other directions/timing periods:</h2> ';
-$url = $APIurl . "/json/routesearch?routeshortname=" . rawurlencode($trips[1]->route_short_name);
-$json = json_decode(getPage($url));
-foreach ($json as $row) {
-	if ($row[0] != $routeid) echo '<a href="trip.php?routeid=' . $row[0] . '">' . $row[2] . ' (' . ucwords($row[3]) . ')</a> ';
-}
+flush(); @ob_flush();
 echo '  <ul data-role="listview"  data-inset="true">';
-echo '<li data-role="list-divider">' . midnight_seconds_to_time($times[0]) . '-' . midnight_seconds_to_time($times[sizeof($times) - 1]) . ' ' . $trips[1]->route_long_name . '</li>';
 $stopsGrouped = Array();
-foreach ($stops as $key => $row) {
-	if (($stops[$key][1] != $stops[$key + 1][1]) || $key + 1 >= sizeof($stops)) {
+$tripStopTimes = getTimeInterpolatedTrip($tripid);
+echo '<li data-role="list-divider">' . $tripStopTimes[0]['arrival_time'] . ' to ' . $tripStopTimes[sizeof($tripStopTimes) - 1]['arrival_time'] . ' ' . $trip['route_long_name'] . ' (' . ucwords($tripStopTimes[0]['service_id']).')</li>';
+
+foreach ($tripStopTimes as $key => $tripStopTime) {
+	if (($tripStopTimes[$key]["stop_name"] != $tripStopTimes[$key + 1]["stop_name"]) || $key + 1 >= sizeof($tripStopTimes)) {
 		echo '<li>';
-		if (!startsWith($row[5], "Wj")) echo '<img src="css/images/time.png" alt="Timing Point" class="ui-li-icon">';
+		if (!startsWith($tripStopTime['stop_code'], "Wj")) echo '<img src="css/images/time.png" alt="Timing Point" class="ui-li-icon">';
 		if (sizeof($stopsGrouped) > 0) {
 			// print and empty grouped stops
 			// subsequent duplicates
-			$stopsGrouped["stop_ids"][] = $row[0];
-			$stopsGrouped["endTime"] = $times[$key];
+			$stopsGrouped["stop_ids"][] = $tripStopTime['stop_id'];
+			$stopsGrouped["endTime"] = $tripStopTime['arrival_time'];
 			echo '<a href="stop.php?stopids=' . implode(",", $stopsGrouped['stop_ids']) . '">';
-			echo '<p class="ui-li-aside">' . midnight_seconds_to_time($stopsGrouped['startTime']) . ' to ' . midnight_seconds_to_time($stopsGrouped['endTime']) . '</p>';
-			echo bracketsMeanNewLine($row[1]);
+			echo '<p class="ui-li-aside">' . $stopsGrouped['startTime'] . ' to ' . $stopsGrouped['endTime'];
+                        echo '</p>';
+                        if (isset($_SESSION['lat']) && isset($_SESSION['lon'])) {
+						echo '<span class="ui-li-count">' . distance($stop['stop_lat'],$stop['stop_lon'], $_SESSION['lat'], $_SESSION['lon'], true) . 'm away</span>';
+					}
+			echo bracketsMeanNewLine($tripStopTime["stop_name"]);
 			echo '</a></li>';
+                        flush(); @ob_flush();
 			$stopsGrouped = Array();
 		}
 		else {
 			// just a normal stop
-			echo '<a href="stop.php?stopid=' . $row[0] . (startsWith($row[5], "Wj") ? '&stopcode=' . $row[5] : "") . '">';
-			echo '<p class="ui-li-aside">' . midnight_seconds_to_time($times[$key]) . '</p>';
-			echo bracketsMeanNewLine($row[1]);
+			echo '<a href="stop.php?stopid=' . $tripStopTime['stop_id'] . (startsWith($tripStopTime['stop_code'], "Wj") ? '&amp;stopcode=' . $tripStopTime['stop_code'] : "") . '">';
+			echo '<p class="ui-li-aside">' . $tripStopTime['arrival_time'] . '</p>';
+			if (isset($_SESSION['lat']) && isset($_SESSION['lon'])) {
+						echo '<span class="ui-li-count">' . distance($stop['stop_lat'],$stop['stop_lon'], $_SESSION['lat'], $_SESSION['lon'], true) . 'm away</span>';
+					}
+                                        echo bracketsMeanNewLine($tripStopTime['stop_name']);
 			echo '</a></li>';
+                        flush(); @ob_flush();
 		}
 	}
 	else {
 		// this is a duplicated line item
-		if ($key - 1 <= 0 || ($stops[$key][1] != $stops[$key - 1][1])) {
+		if ($key - 1 <= 0 || ($tripStopTimes[$key]['stop_name'] != $tripStopTimes[$key - 1]['stop_name'])) {
 			// first duplicate
 			$stopsGrouped = Array(
-				"name" => $row[1],
-				"startTime" => $times[$key],
+				"name" => $tripStopTime['stop_name'],
+				"startTime" => $tripStopTime['arrival_time'],
 				"stop_ids" => Array(
-					$row[0]
+					$tripStopTime['stop_id']
 				)
 			);
 		}
 		else {
 			// subsequent duplicates
-			$stopsGrouped["stop_ids"][] = $row[0];
-			$stopsGrouped["endTime"] = $times[$key];
+			$stopsGrouped["stop_ids"][] = $tripStopTime['stop_id'];
+			$stopsGrouped["endTime"] = $tripStopTime['arrival_time'];
 		}
 	}
 }

--- a/tripPlanner.php
+++ b/tripPlanner.php
@@ -1,17 +1,17 @@
 <?php
 include ('include/common.inc.php');
 include_header("Trip Planner", "tripPlanner", true, true, true);
-$from = (isset($_REQUEST['from']) ? filter_var($_REQUEST['from'], FILTER_SANITIZE_STRING) : "Brigalow");
-$to = (isset($_REQUEST['to']) ? filter_var($_REQUEST['to'], FILTER_SANITIZE_STRING) : "Barry");
+$from = (isset($_REQUEST['from']) ? filter_var($_REQUEST['from'], FILTER_SANITIZE_STRING) : "");
+$to = (isset($_REQUEST['to']) ? filter_var($_REQUEST['to'], FILTER_SANITIZE_STRING) : "");
 $date = (isset($_REQUEST['date']) ? filter_var($_REQUEST['date'], FILTER_SANITIZE_STRING) : date("m/d/Y"));
 $time = (isset($_REQUEST['time']) ? filter_var($_REQUEST['time'], FILTER_SANITIZE_STRING) : date("H:i"));
-function formatTime($timeString) {
-  $timeParts = explode("T",$timeString);
-  return str_replace("Z","",$timeParts[1]);
+function formatTime($timeString)
+{
+	$timeParts = explode("T", $timeString);
+	return str_replace("Z", "", $timeParts[1]);
 }
 function tripPlanForm($errorMessage = "")
 {
-
 	global $date, $time, $from, $to;
 	echo "<font color=red>$errorMessage</font>";
 	echo '<form action="tripPlanner.php" method="post">
@@ -44,7 +44,7 @@
 }
 function processItinerary($itineraryNumber, $itinerary)
 {
-	echo '<div data-role="collapsible" ' . ($itineraryNumber > 0 ? 'data-collapsed="true"' : "") . '> <h3> Option #' . ($itineraryNumber + 1) . ": " . floor($itinerary->duration / 60000) . " minutes (".formatTime($itinerary->startTime)." to ".formatTime($itinerary->endTime).")</h3><p>";
+	echo '<div data-role="collapsible" ' . ($itineraryNumber > 0 ? 'data-collapsed="true"' : "") . '> <h3> Option #' . ($itineraryNumber + 1) . ": " . floor($itinerary->duration / 60000) . " minutes (" . formatTime($itinerary->startTime) . " to " . formatTime($itinerary->endTime) . ")</h3><p>";
 	echo "Walking time: " . floor($itinerary->walkTime / 60000) . " minutes (" . floor($itinerary->walkDistance) . " meters)<br>\n";
 	echo "Transit time: " . floor($itinerary->transitTime / 60000) . " minutes<br>\n";
 	echo "Waiting time: " . floor($itinerary->waitingTime / 60000) . " minutes<br>\n";
@@ -62,6 +62,8 @@
 			echo '<li>';
 			processLeg($legNumber, $leg);
 			echo "</li>";
+			flush();
+			@ob_flush();
 		}
 		echo "</ul>";
 	}
@@ -94,55 +96,79 @@
 		echo "" . staticmap($walkStepMarkers, 0, "icong", false) . "<br>\n";
 		foreach ($leg->steps->walkSteps as $stepNumber => $step) {
 			echo "Walking step " . ($stepNumber + 1) . ": ";
-                        if ($step->relativeDirection == "CONTINUE") {
-                          echo "Continue, ";
-                        } else if ($step->relativeDirection) echo "Turn ".ucwords(strtolower(str_replace("_"," ",$step->relativeDirection))).", ";
-                        echo "Go ".ucwords(strtolower($step->absoluteDirection))." on ";
-                        if (strpos($step->streetName,"from") !== false && strpos($step->streetName,"way") !== false) {
-                          echo "footpath";
-                        } else {
-                          echo $step->streetName;
-                        }
-                        echo " for " . floor($step->distance) . " meters<br>\n";
+			if ($step->relativeDirection == "CONTINUE") {
+				echo "Continue, ";
+			}
+			else if ($step->relativeDirection) echo "Turn " . ucwords(strtolower(str_replace("_", " ", $step->relativeDirection))) . ", ";
+			echo "Go " . ucwords(strtolower($step->absoluteDirection)) . " on ";
+			if (strpos($step->streetName, "from") !== false && strpos($step->streetName, "way") !== false) {
+				echo "footpath";
+			}
+			else {
+				echo $step->streetName;
+			}
+			echo " for " . floor($step->distance) . " meters<br>\n";
 		}
 	}
 }
 if ($_REQUEST['time']) {
-	$toPlace = (startsWith($to, "-") ? $to : geocode($to, false));
-	$fromPlace = (startsWith($from, "-") ? $from : geocode($from, false));
+	if (startsWith($to, "-")) {
+		$toPlace = $to;
+	}
+	else if (strpos($to, "(") !== false) {
+		$toParts = explode("(", $to);
+                print_r($toParts);
+		$toPlace = str_replace( ")", "", $toParts[1]);
+	}
+	else {
+		$toPlace = geocode($to, false);
+	}
+        
+	if (startsWith($from, "-")) {
+		$fromPlace = $from;
+	}
+	else if (strpos($from, "(") !== false) {
+		$fromParts = explode("(", urldecode($from));
+		$fromPlace = str_replace(")", "", $fromParts[1]);
+	}
+	else {
+		$fromPlace = geocode($from, false);
+	}
+        
 	if ($toPlace == "" || $fromPlace == "") {
 		$errorMessage = "";
-		if ($toPlace === "") {
-                  $errorMessage.= urlencode($to) . " not found.<br>\n";
-                  trackEvent("Trip Planner","Geocoder Failed", $to);
-                }
-		if ($fromPlace === "") {
-                  $errorMessage.= urlencode($from) . " not found.<br>\n";
-                  trackEvent("Trip Planner","Geocoder Failed", $from);
-                }
+		if ($toPlace == "") {
+			$errorMessage.= urlencode($to) . " not found.<br>\n";
+			trackEvent("Trip Planner", "Geocoder Failed", $to);
+		}
+		if ($fromPlace == "") {
+			$errorMessage.= urlencode($from) . " not found.<br>\n";
+			trackEvent("Trip Planner", "Geocoder Failed", $from);
+		}
 		tripPlanForm($errorMessage);
 	}
 	else {
 		$url = $otpAPIurl . "ws/plan?date=" . urlencode($_REQUEST['date']) . "&time=" . urlencode($_REQUEST['time']) . "&mode=TRANSIT%2CWALK&optimize=QUICK&maxWalkDistance=840&wheelchair=false&toPlace=$toPlace&fromPlace=$fromPlace&intermediatePlaces=";
+		debug($url);
 		$ch = curl_init($url);
 		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
 		curl_setopt($ch, CURLOPT_HEADER, 0);
 		curl_setopt($ch, CURLOPT_HTTPHEADER, array(
 			"Accept: application/json"
 		));
-		curl_setopt($ch, CURLOPT_TIMEOUT, 5);
+		curl_setopt($ch, CURLOPT_TIMEOUT, 10);
 		$page = curl_exec($ch);
-		if (curl_errno($ch)) {
-			tripPlanForm("Trip planner temporarily unavailable: " . curl_errno($ch) . " " . curl_error($ch) .(isDebug() ? $url : ""));
-                        trackEvent("Trip Planner","Trip Planner Failed", $url);
-                }
+		if (curl_errno($ch) || curl_getinfo($ch, CURLINFO_HTTP_CODE) != 200) {
+			tripPlanForm("Trip planner temporarily unavailable: " . curl_errno($ch) . " " . curl_error($ch) . " " . curl_getinfo($ch, CURLINFO_HTTP_CODE) . (isDebug() ? "<br>" . $url : ""));
+			trackEvent("Trip Planner", "Trip Planner Failed", $url);
+		}
 		else {
-                  	trackEvent("Trip Planner","Plan Trip From", $from);
-                        trackEvent("Trip Planner","Plan Trip To", $to);
+			trackEvent("Trip Planner", "Plan Trip From", $from);
+			trackEvent("Trip Planner", "Plan Trip To", $to);
 			$tripplan = json_decode($page);
-			debug(print_r($triplan, true));
+			debug(print_r($tripplan, true));
 			echo "<h1> From: {$tripplan->plan->from->name} To: {$tripplan->plan->to->name} </h1>";
-			echo "<h1> At: ".formatTime($tripplan->plan->date)." </h1>";
+			echo "<h1> At: " . formatTime($tripplan->plan->date) . " </h1>";
 			if (is_array($tripplan->plan->itineraries->itinerary)) {
 				echo '<div data-role="collapsible-set">';
 				foreach ($tripplan->plan->itineraries->itinerary as $itineraryNumber => $itinerary) {

file:b/updatedb.php (new)
--- /dev/null
+++ b/updatedb.php
@@ -1,1 +1,60 @@
+<?php
+include('include/common-db.inc.php');
+// Unzip cbrfeed.zip, import all csv files to database
+$unzip = true;
+$zip = zip_open(dirname(__FILE__) . "/cbrfeed.zip");
+$tmpdir = "/tmp/cbrfeed/";
+mkdir($tmpdir);
+if ($unzip) {
+if (is_resource($zip)) {
+	while ($zip_entry = zip_read($zip)) {
+		$fp = fopen($tmpdir . zip_entry_name($zip_entry) , "w");
+		if (zip_entry_open($zip, $zip_entry, "r")) {
+			echo "Extracting " . zip_entry_name($zip_entry) . "\n";
+			$buf = zip_entry_read($zip_entry, zip_entry_filesize($zip_entry));
+			fwrite($fp, "$buf");
+			zip_entry_close($zip_entry);
+			fclose($fp);
+		}
+	}
+	zip_close($zip);
+}
+}
+                        
+foreach (scandir($tmpdir) as $file) {
+	if (!strpos($file, ".txt") === false) {
+		$fieldseparator = ",";
+		$lineseparator = "\n";
+		$tablename = str_replace(".txt", "", $file);
+		echo "Opening $file \n";
+		$line = 0;
+		$handle = fopen($tmpdir . $file, "r");
+		while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
+			if ($line > 0) {
+				$query = "insert into $tablename values(";
+                                $valueCount = 0;
+                                foreach ($data as $value) {
+                                $query.=($valueCount >0 ? "','" :"'").pg_escape_string($value);
+                                $valueCount++;
+                                }
+				if ($tablename == "stops") {
+                                $query.= "', ST_GeographyFromText('SRID=4326;POINT({$data[2]} {$data[0]})'));";
+				} else {
+                                  $query.= "');";
+                                }
+                                if ($tablename =="stop_times" && $data[1] == "") {
+                                  $query = "insert into $tablename (trip_id,stop_id,stop_sequence) values('{$data[0]}','{$data[3]}','{$data[4]}');";
+                                }
+                                 
+			}
+                        $result = pg_query($conn, $query);
+			$line++;
+                        if ($line % 10000 == 0) echo "$line records... \n";
+		}
+		fclose($handle);
+		echo "Found a total of $line records in $file.\n";
 
+	}
+}
+?>
+

file:a/view.sh (deleted)
--- a/view.sh
+++ /dev/null
@@ -1,6 +1,1 @@
- #!/bin/sh
- f=`dirname $0`
- cd $f
- python schedule_viewer.py --feed=/var/www/cbrfeed.zip \
---key=ABQIAAAA95XYXN0cki3Yj_Sb71CFvBTPaLd08ONybQDjcH_VdYtHHLgZvRTw2INzI_m17_IoOUqH3RNNmlTk1Q