# us_census.tcl
#
#	This file defines Tcl code that accesses and manipulates data
#	from the U.S. Census.
#	Ref. http://www.census.gov/geo/www/tiger/index.html
#
# Copyright (c) 2003 Gordon D. Carrie
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# Please submit bug reports and suggestions to:
# tkgeomap@users.sourceforge.net

if {[catch "package present Tk"] == 0} {
    package require tkgeomap 2
} else {
    package require tclgeomap 2
}
package provide us_census

namespace eval us_census {
    namespace eval places {
	# Each place in the places database has a unique identifier.
	#
	# This identifier is an index for the place in the following arrays:
	# name(id)	- stores the common name for the place, e.g. "New York"
	# state(id)	- stores the two letter abbreviation for the state
	# type(id)	- stores the place type, e.g. "city", "CDP", etc.
	# pop(id)	- stores the place population
	#
	# us_census::places::places contains a list all place identifiers,
	#   sorted from most populous place to least.
	# us_census::n_places::places is the same as places, except the
	#   identifiers are fully qualified.
	# us_census::places::types contains a list of all place types.

	variable name
	variable state
	variable type
	variable pop
	variable places
	variable n_places
	variable types
    }
}

package provide us_census 1.0

# us_census::places::read --
#
#	This procedure reads U.S. Census gazetteer data.
#
# Arguments:
#	inFile	- copy of http://www.census.gov/tiger/tms/gazetteer/places2k.txt
#	verbose	- (optional) if true, print progress information
#
# Results:
#	Returns number of places entered in the data base.

proc us_census::places::read {inFile {verbose 0}} {
    variable name
    variable state
    variable type
    variable pop
    variable places
    variable n_places
    variable types

    set nmspc [namespace current]
    if $verbose {
	puts "Reading place data"
    }
    set in [open $inFile r]
    while {[gets $in line] >= 0} {

	# Read information for a place

	set st [string range $line 0 1]
	set fips [string range $line 2 8]
	set place [string range $line 9 72]
	if {[scan [string range $line 73 81] {%d} population ] != 1} {
	    error "Could not get population from $line"
	}
	if {[scan [string range $line 143 163] {%f %f} lat lon] != 2} {
	    error "Could not get lat lon from $line"
	}

	# Store information for the place

	::geomap::place new ${nmspc}::$fips [list $lat $lon]
	if {[llength $place] > 1} {
	    set type($fips) [string tolower [lindex $place end]]
	    set name($fips) [string trim [lreplace $place end end]]
	} else {
	    set type($fips) "unknown"
	    set name($fips) [string trim $place]
	}
	set state($fips) $st
	lappend types $type($fips)
	set pop($fips) $population
	lappend places $fips
    }
    close $in

    if $verbose {
	puts "Sorting place data"
    }
    set places [lsort -command popcmp -decreasing $places]
    foreach place $places {
	lappend n_places ${nmspc}::$place
    }
    set types [lsort -unique $types]
    return [llength $places]
}

# us_census::places::read_sorted --
#
#	This procedure reads place data from an optimized file
#
# Arguments:
#	inFile	- optimized input file.  There should be one line for each
#		  place, with the places sorted from most populous to least.
#		  Each line should be a list of form:
#		  "id name state {lat lon} type population"
#
# Results:
#	Return value is number of places read.

proc us_census::places::read_sorted {inFile} {
    variable name
    variable state
    variable type
    variable pop
    variable places
    variable n_places
    variable types

    set nmspc [namespace current]
    set in [open $inFile r]
    while {[gets $in line] >= 0} {
	set id [lindex $line 0]
	set name($id) [lindex $line 1]
	set state($id) [lindex $line 2]
	set latLon [lindex $line 3]
	::geomap::place new ${nmspc}::$id $latLon
	set type($id) [lindex $line 4]
	lappend types $type($id)
	set pop($id) [lindex $line 5]
	lappend places $id
	lappend n_places ${nmspc}::$id
    }
    close $in
    set types [lsort -unique $types]
    return [llength $places]
}

# us_census::places::popcmp --
#
#	This procedure compares two places by population.
#
# Arguments:
#	p1	Identifier for a place.
#	p2	Identifier for another place.
#
# Results:
#	-1 if the population of p1 is less than the population of p2
#	0  if p1 and p2 have the same population
#	1  if the population of p1 is greater than the population of p2

proc us_census::places::popcmp {p1 p2} {
    variable pop
    expr {$pop($p1) > $pop($p2) ? 1 : $pop($p1) < $pop($p2) ? -1 : 0}
}

# us_census::places::regexp --
#
#	This procedure searches the list of place names for a pattern
#
# Arguments:
#	ptn	regular expression pattern
#	nocase	(optional) if true, ignore case when matching patterns.
#
# Results:
#	A list of identifiers for places whose names match ptn.

proc us_census::places::regexp {ptn {nocase 0}} {
    variable places
    variable name

    set nc [expr {$nocase ? "-nocase" : "--"}]
    set rslt {}
    foreach plc $places {
	if [::regexp $nc $ptn $name($plc)] {
	    lappend rslt $plc
	}
    }
    return $rslt
}

# us_census::places::smallest --
#
#	This procedure finds the smallest place with a population greater
#	than a given value.
#
# Arguments:
#	minPop	- population value
#
# Results:
#	Returns the index of the place in places (or n_places) with the
#	smallest population that is still greater than pop.

proc us_census::places::smallest {minPop} {
    variable places
    variable pop

    set i 0
    foreach place $places {
	if {$pop($place) >= $minPop} {
	    incr i
	} else {
	    break
	}
    }
    return [expr {$i - 1}]
}
