Commits

Ben Wing committed 2c5fdc6 Merge

Automatic merge

  • Participants
  • Parent commits 9d520de, 15190ed

Comments (0)

Files changed (6)

File python/convert-old-docfile-to-metadata-schema-and-file

 }
 
 output_dir=new-convert-schema-and-file
-dir_prefix=true
+dir_prefix=false
 while true; do
   case "$1" in
-    --no-dir-prefix ) dir_prefix=false; shift 1 ;;
+    --add-dir-prefix ) dir_prefix=true; shift 1 ;;
     --output-dir ) output_dir="$2"; shift 2 ;;
     * ) break ;;
   esac

File python/download-preprocess-wiki

+#!/bin/sh
+
+# USAGE: download-preprocess-wiki WIKITAG
+#
+# where WIKITAG is something like 'dewiki-20120225'. (Which needs to exist.)
+
+wikitag="$1"
+mkdir -p $wikitag
+cd $wikitag
+echo "Downloading Wikipedia corpus $wikitag ..."
+wikidir="`echo $wikitag | sed 's/-/\//'`"
+wget -nd http://dumps.wikimedia.org/$wikidir/$wikitag-pages-articles.xml.bz2
+echo "Downloading Wikipedia corpus $wikitag ... done."
+echo "Preprocessing Wikipedia corpus $wikitag ..."
+preprocess-dump $wikitag
+echo "Preprocessing Wikipedia corpus $wikitag ... done."
+echo "Converting Wikipedia corpus $wikitag to latest format ..."
+mkdir convert
+cd convert
+ln -s .. $wikitag
+run-convert-corpus --steps wiki $wikitag
+mv convert-corpora-3/$wikitag/* $wikitag
+cd ..
+rm -rf convert
+echo "Converting Wikipedia corpus $wikitag to latest format ... done."
+cd ..

File python/processwiki.py

       wikiwarning("Expected number, saw %s" % x)
     return 0.
 
+def get_german_style_coord(arg):
+  '''Handle plain floating-point numbers as well as "German-style"
+deg/min/sec/DIR indicators like 45/32/30/E.'''
+  if arg is None:
+    return 0. # FIXME: Return None?
+  if ' ' in arg:
+    arg = re.sub(' .*$', '', arg)
+  if '/' in arg:
+    m = re.match('([0-9.]+)/([0-9.]+)?/([0-9.]+)?/([NSEWnsew])', arg)
+    if m:
+      (deg, min, sec, offind) = m.groups()
+      offind = offind.upper()
+      if offind in convert_ns:
+        off = convert_ns[offind]
+      else:
+        off = convert_ew[offind]
+      return convert_dms(off, deg, min, sec)
+    wikiwarning("Unrecognized DEG/MIN/SEC/HEMIS-style indicator: %s" % arg)
+    return 0. # FIXME: Return None?
+  else:
+    return safe_float(arg)
+
 def convert_dms(nsew, d, m, s):
   '''Convert a multiplier (1 or N or E, -1 for S or W) and degree/min/sec
 values into a decimal +/- latitude or longitude.'''
-  return nsew*(safe_float(d) + safe_float(m)/60. + safe_float(s)/3600.)
+  return nsew*(get_german_style_coord(d) + safe_float(m)/60. + safe_float(s)/3600.)
 
 convert_ns = {'N':1, 'S':-1}
 convert_ew = {'E':1, 'W':-1, 'L':1, 'O':-1}
 # Get the default value for the hemisphere, as a multiplier +1 or -1.
 # We need to handle Australian places specially, as S latitude, E longitude.
 # We need to handle Pittsburgh neighborhoods specially, as N latitude, W longitude.
-# Otherwise assume +1.
+# Otherwise assume +1, so that we leave the values alone.  This is important
+# because some fields may specifically use signed values to indicate the
+# hemisphere directly, or use other methods of indicating hemisphere (e.g.
+# "German"-style "72/50/35/W").
 def get_hemisphere(temptype, is_lat):
   if temptype.lower().startswith('infobox australia'):
     if is_lat: return -1
     else: return -1
   else: return 1
 
+# Get an argument (ARGSEARCH) by name from a hash table (ARGS).  Multiple
+# synonymous names can be looked up by giving a list or tuple for ARGSEARCH.
+# Other parameters control warning messages.
 def getarg(argsearch, temptype, args, rawargs, warnifnot=True):
   if isinstance(argsearch, tuple) or isinstance(argsearch, list):
     for x in argsearch:
     if val is not None:
       return val
     if warnifnot:
-      wikiwarning("Param %s seen in template {{%s|%s}}" % (
+      wikiwarning("Param %s not seen in template {{%s|%s}}" % (
         argsearch, temptype, bound_string_length('|'.join(rawargs))))
   return None
 
                (offparam, temptype, hemis))
   return convert_dms(hemismult, d, m, s)
 
+latd_arguments = ('latd', 'latg', 'lat_deg', 'latitudedegrees',
+  'latitudinegradi', 'latitudine gradi', 'latitudine_d',
+  'breitengrad', 'breddegrad', 'bredde_grad')
 def get_latd_coord(temptype, args, rawargs):
   '''Given a template of type TEMPTYPE with arguments ARGS (converted into
 a hash table; also available in raw form as RAWARGS), assumed to have
 longd/lon_deg/etc., extract out and return a tuple of decimal
 (latitude, longitude) values.'''
   lat = get_lat_long_1(temptype, args, rawargs,
-      ('latd', 'latg', 'lat_deg'),
-      ('latm', 'lat_min'),
-      ('lats', 'lat_sec'),
-      ('latns', 'latp', 'lap', 'lat_dir'),
+      latd_arguments,
+      ('latm', 'lat_min', 'latitudeminutes',
+         'latitudineminuti', 'latitudine minute', 'latitudine_m',
+         'breitenminute', 'bredde_min'),
+      ('lats', 'lat_sec', 'latitudeseconds', 'latitudinesecondi',
+         'latitudine_s', 'breitensekunde'),
+      ('latns', 'latp', 'lap', 'lat_dir', 'latitudine ns'),
       is_lat=True)
   long = get_lat_long_1(temptype, args, rawargs,
-      ('longd', 'lond', 'longg', 'long', 'lon_deg'),
-      ('longm', 'lonm', 'lon_min'),
-      ('longs', 'lons', 'lon_sec'),
-      ('longew', 'longp', 'lonp', 'lon_dir'),
+      # Typos like Longtitude do occur in the Spanish Wikipedia at least
+      ('longd', 'lond', 'longg', 'long', 'lon_deg',
+         'longitudinegradi', 'longitudine gradi',
+         'longitudine_d', 'longitudedegrees', 'longtitudedegrees',
+         u'längengrad', 'lengdegrad', u'længde_grad'),
+      ('longm', 'lonm', 'lon_min', 'longitudineminuti', 'longitudine_m',
+         'longitudeminutes', 'longtitudeminutes',
+         u'längenminute', u'længde_min'),
+      ('longs', 'lons', 'lon_sec', 'longitudinesecondi', 'longitudine_s',
+         'longitudeseconds', 'longtitudeseconds', u'längensekunde'),
+      ('longew', 'longp', 'lonp', 'lon_dir', 'longitudine ew'),
       is_lat=False)
   return (lat, long)
 
+def get_built_in_lat_long_1(temptype, args, rawargs, latd, latm, lats, is_lat):
+  d = getarg(latd, temptype, args, rawargs)
+  m = getarg(latm, temptype, args, rawargs, warnifnot=False) 
+  s = getarg(lats, temptype, args, rawargs, warnifnot=False)
+  return convert_dms(mult, d, m, s)
+
+built_in_latd_north_arguments = ('stopnin')
+built_in_latd_south_arguments = ('stopnis')
+built_in_longd_north_arguments = ('stopnie')
+built_in_longd_south_arguments = ('stopniw')
+
+def get_built_in_lat_coord(temptype, args, rawargs):
+  '''Given a template of type TEMPTYPE with arguments ARGS (converted into
+a hash table; also available in raw form as RAWARGS), assumed to have
+a latitude/longitude specification in it using stopniN/etc. (where the
+direction NSEW is built into the argument name), extract out and return a
+tuple of decimal (latitude, longitude) values.'''
+  if getarg(built_in_latd_north_arguments) is not None:
+    mult = 1
+  elif getarg(built_in_latd_south_arguments) is not None:
+    mult = -1
+  else:
+    wikiwarning("Didn't see any appropriate stopniN/stopniS param")
+    mult = 0.
+  lat = get_built_in_lat_long_1(temptype, args, rawargs,
+      ('stopnin', 'stopnis'),
+      ('minutn', 'minuts'),
+      ('sekundn', 'sekunds'),
+      mult)
+  if getarg(built_in_longd_north_arguments) is not None:
+    mult = 1
+  elif getarg(built_in_longd_south_arguments) is not None:
+    mult = -1
+  else:
+    wikiwarning("Didn't see any appropriate stopniE/stopniW param")
+    mult = 0.
+  long = get_built_in_lat_long_1(temptype, args, rawargs,
+      ('stopnie', 'stopniw'),
+      ('minute', 'minutw'),
+      ('sekunde', 'sekundw'),
+      mult)
+  return (lat, long)
+
+latitude_arguments = ('latitude', 'latitud')
+longitude_arguments = ('longitude', 'longitud')
+
 def get_latitude_coord(temptype, args, rawargs):
   '''Given a template of type TEMPTYPE with arguments ARGS, assumed to have
-a latitude/longitude specification in it using latitude/longitude, extract out
-and return a tuple of decimal (latitude, longitude) values.'''
-  lat = safe_float(getarg('latitude', temptype, args, rawargs))
-  long = safe_float(getarg('longitude', temptype, args, rawargs))
-  return (lat, long)
-
-def get_german_style_coord(arg):
-  if arg is None:
-    return arg
-  arg = re.sub(' .*$', '', arg)
-  if '/' in arg:
-    m = re.match('([0-9.]+)/([0-9.]+)?/([0-9.]+)?/([NSEWnsew])', arg)
-    if m:
-      (deg, min, sec, offind) = m.groups()
-      offind = offind.upper()
-      if offind in convert_ns:
-        off = convert_ns[offind]
-      else:
-        off = convert_ew[offind]
-      return convert_dms(off, deg, min, sec)
-    wikiwarning("Unrecognized DEG/MIN/SEC/HEMIS-style indicator: %s" % arg)
-    return None
-  else:
-    return safe_float(arg)
-
-def get_breitengrad_coord(temptype, args, rawargs):
-  '''Given a template of type TEMPTYPE with arguments ARGS, assumed to have
-a latitude/longitude specification in it using breitengrad/längengrad,
-extract out and return a tuple of decimal (latitude, longitude) values.'''
-  lat = get_german_style_coord(getarg('breitengrad', temptype, args, rawargs))
-  long = get_german_style_coord(getarg(u'längengrad', temptype, args, rawargs))
+a latitude/longitude specification in it, extract out and return a tuple of
+decimal (latitude, longitude) values.'''
+  # German-style (e.g. 72/53/15/E) also occurs in the French Wikipedia with
+  # 'latitude' and such, so just check for it everywhere.
+  lat = get_german_style_coord(getarg(latitude_arguments,
+    temptype, args, rawargs))
+  long = get_german_style_coord(getarg(longitude_arguments,
+    temptype, args, rawargs))
   return (lat, long)
 
 # Utility function for get_coord().  Extract out the latitude or longitude
     lowertemp = temptype.lower()
     rawargs = tempargs[1:]
     # Look for a coordinate template
-    if lowertemp in ('coord', 'coor d', 'coor dm', 'coor dms',
+    if lowertemp in ('coord', 'coordp', 'coords',
+                     'koord', #Norwegian
+                     'coor', 'coor d', 'coor dm', 'coor dms',
                      'coor title d', 'coor title dm', 'coor title dms',
                      'coor dec', 'coorheader') \
         or lowertemp.startswith('geolinks') \
-        or lowertemp.startswith('mapit'):
+        or lowertemp.startswith('mapit') \
+        or lowertemp.startswith('koordynaty'): # Coordinates in Polish:
       (lat, long) = get_coord(temptype, rawargs)
     elif lowertemp == 'coordinate':
       (lat, long) = get_coordinate_coord(temptype, rawargs)
-    elif lowertemp == 'geocoordenadas':
+    elif lowertemp in ('geocoordenadas', u'coördinaten'):
+      # geocoordenadas is Portuguese, coördinaten is Dutch, and they work
+      # the same way
       (lat, long) = get_geocoordenadas_coord(temptype, rawargs)
     else:
       # Look for any other template with a 'latd' or 'latitude' parameter.
       # Usually these will be Infobox-type templates.  Possibly we should only
       # look at templates whose lowercased name begins with "infobox".
       (paramshash, _) = find_template_params(rawargs, True)
-      # 'latg' is Portuguese (g = grau)
-      if ('latd' in paramshash or 'latg' in paramshash or
-          'lat_deg' in paramshash):
+      if getarg(latd_arguments, temptype, paramshash, rawargs, warnifnot=False) is not None:
+        #errprint("seen: [%s] in {{%s|%s}}" % (getarg(latd_arguments, temptype, paramshash, rawargs), temptype, rawargs))
         templates_with_coords[lowertemp] += 1
         (lat, long) = get_latd_coord(temptype, paramshash, rawargs)
-      elif 'latitude' in paramshash:
+      elif getarg(latitude_arguments, temptype, paramshash, rawargs, warnifnot=False) is not None:
+        #errprint("seen: [%s] in {{%s|%s}}" % (getarg(latitude_arguments, temptype, paramshash, rawargs), temptype, rawargs))
         templates_with_coords[lowertemp] += 1
         (lat, long) = get_latitude_coord(temptype, paramshash, rawargs)
-      elif 'breitengrad' in paramshash:
+      elif (getarg(built_in_latd_north_arguments, temptype, paramshash,
+                   rawargs, warnifnot=False) is not None or
+            getarg(built_in_latd_south_arguments, temptype, paramshash,
+                   rawargs, warnifnot=False) is not None):
+        #errprint("seen: [%s] in {{%s|%s}}" % (getarg(built_in_latd_north_arguments, temptype, paramshash, rawargs), temptype, rawargs))
+        #errprint("seen: [%s] in {{%s|%s}}" % (getarg(built_in_latd_south_arguments, temptype, paramshash, rawargs), temptype, rawargs))
         templates_with_coords[lowertemp] += 1
-        (lat, long) = get_breitengrad_coord(temptype, paramshash, rawargs)
+        (lat, long) = get_built_in_lat_coord(temptype, paramshash, rawargs)
+
     if lat or long:
       if lat is None:
         lat = 0.

File python/run-convert-corpus

 # mmv output-*-docthresh docthresh-*
 # cd $tge
 # rm -rf convert-corpora-*
-# run-convert-corpus --steps all $cotg/docthresh-*
+# run-convert-corpus --steps all --add-dir-prefix $cotg/docthresh-*
 # cd convert-corpora-4
 # for x in docthresh-*; do (echo $x; cd $x; mmv geotext-twitter-* twitter-geotext-*; bzip2 *-unigram-counts.txt); done
 # cd $cotg
 
 help() {
   cat <<FOO
-Usage: $0 --steps "STEPS ..." DIR ...
+Usage: $0 --steps "STEPS ..." [--output-dir-prefix PREFIX] [--add-dir-prefix] DIR ...
 
 Convert corpora using various steps (e.g. from old-style to new-style,
 removing unneeded GeoText fields, splitting by training/dev/test split).
 Each step writes its output into a new directory, and the next step uses
 that directory and writes its output into another new directory.
 
+--output-dir-prefix specifies the prefix used for naming the temporary
+output directories into which intermediate and final results are stored.
+The default is 'convert-corpora'; then, 'convert-corpora-1' contains the
+results from running the first step in --steps, 'convert-corpora-2'
+contains results from the second step, etc.  Final results are in the
+highest-numbered such directory.
+
+--add-dir-prefix, if given, controls whether the INPUT directory will be
+added to the end of the prefix used in the schema and data files generated
+in the corpora inside of the output dirs.  Normally, the existing prefix
+of the files is used as the new prefix, but with --add-dir-prefix, the
+input directory will also be added.  This is mostly useful for handling
+the different threshold values, where the input corpora files for all
+threshold values have the same names but we want differently-named output
+corpora.  For Wikipedia corpora, don't use it.
 FOO
   exit 1
 }
 
 steps=
 output_dir_prefix=convert-corpora
+add_dir_prefix=
 while true; do
   case "$1" in
     --steps ) steps="$2"; shift 2 ;;
     --output-dir-prefix ) output_dir_prefix="$2"; shift 2 ;;
+    --add-dir-prefix ) add_dir_prefix="--add-dir-prefix"; shift ;;
     * ) break ;;
   esac
 done
   steps="convert-to-schema-and-document merge-metadata-and-old-counts frob-geotext split-by-training"
 fi
 
+if [ "$steps" = wiki ]; then
+  steps="convert-to-schema-and-document merge-metadata-and-old-counts split-by-training"
+fi
+
 echo "Steps are $steps"
 
 for dir in ${1+"$@"}; do
 
 if [ "$step" = convert-to-schema-and-document ]; then
   $TG_PREPROC_DIR/convert-old-docfile-to-metadata-schema-and-file \
-    --output-dir "$output_dir" "$input_dir"
+    $add_dir_prefix --output-dir "$output_dir" "$input_dir"
 
 elif [ "$step" = merge-metadata-and-old-counts ]; then
   textgrounder run opennlp.textgrounder.preprocess.MergeMetadataAndOldCounts \

File python/run-processwiki

 
 # Uses a different program, not processwiki.
 echo "Combining article data ..."
+echo "Beginning at `date`:"
+echo "Executing: $GENERATE_COMBINED \
+  --links-file $OUT_COORD_LINKS_FILE \
+  --coords-file $OUT_COORDS_FILE \
+  --article-data-file $OUT_ORIG_DOCUMENT_DATA_FILE \
+  > $OUT_COMBINED_DOCUMENT_DATA_FILE"
 $GENERATE_COMBINED \
   --links-file $OUT_COORD_LINKS_FILE \
   --coords-file $OUT_COORDS_FILE \
   --article-data-file $OUT_ORIG_DOCUMENT_DATA_FILE \
   > $OUT_COMBINED_DOCUMENT_DATA_FILE
+echo "Ended at `date`."
 
 elif [ "$step" = split-dump ]; then
 
 
 # Uses a different program, not processwiki.
 echo "Splitting dump file ..."
+echo "Beginning at `date`:"
+echo "Executing: bzcat $OUT_DUMP_FILE | $PERMUTE_WIKI --mode=split \
+  --article-data-file $OUT_ORIG_DOCUMENT_DATA_FILE \
+  --split-prefix $SPLIT_PREFIX \
+  --number-of-splits $NUM_SPLITS $OTHEROPTS"
 bzcat $OUT_DUMP_FILE | $PERMUTE_WIKI --mode=split \
   --article-data-file $OUT_ORIG_DOCUMENT_DATA_FILE \
   --split-prefix $SPLIT_PREFIX \
   --number-of-splits $NUM_SPLITS $OTHEROPTS
+echo "Ended at `date`."
 
 elif [ "$step" = coord-counts ]; then
 
 if [ "$NUM_SIMULTANEOUS" -eq 1 -o -z "$outfile" -o "$cansplit" = "no" ]; then
 
   # Operate in non-split mode
+  echo "Beginning at `date`:"
   echo "$action ..."
   if [ -n "$outfile" ]; then
+    echo "Executing: bzcat $OUT_DUMP_FILE | $PROCESSWIKI $args $OTHEROPTS > $outfile"
     bzcat $OUT_DUMP_FILE | $PROCESSWIKI $args $OTHEROPTS > $outfile
   else
+    echo "Executing: bzcat $OUT_DUMP_FILE | $PROCESSWIKI $args $OTHEROPTS"
     bzcat $OUT_DUMP_FILE | $PROCESSWIKI $args $OTHEROPTS
   fi
   echo "$action ... done."
+  echo "Ended at `date`."
 
 else
 
   numrun=0
   i=0
   splits=""
+  splits_removable=""
   while [ "$i" -lt "$NUM_SPLITS" ]; do
     SPLITFILE="$SPLIT_PREFIX.$i"
     if [ ! -e "$SPLITFILE" ]; then
     if [ "$numleft" -gt 0 ]; then
       split_outfile="$outfile.split-processwiki.$i"
       splits="$splits $split_outfile"
+      splits_removable="$splits_removable $split_outfile"
+      echo "Beginning at `date`:"
+      echo "Executing: cat $SPLIT_PREFIX.prolog $SPLITFILE $SPLIT_PREFIX.epilog | $PROCESSWIKI $args $OTHEROPTS > $split_outfile &"
       cat $SPLIT_PREFIX.prolog $SPLITFILE $SPLIT_PREFIX.epilog | $PROCESSWIKI $args $OTHEROPTS > $split_outfile &
+      echo "Ended at `date`."
       numleft=`expr $numleft - 1`
       numrun=`expr $numrun + 1`
     fi
     if [ "$numleft" -eq 0 ]; then
       echo "Waiting for $numrun processes to finish..."
       wait
+      echo "Ended at `date`."
       numleft="$NUM_SIMULTANEOUS"
       numrun=0
     fi
   if [ "$numrun" -gt 0 ]; then
     echo "Waiting for $numrun processes to finish..."
     wait
+      echo "Ended at `date`."
     numrun=0
   fi
   echo "$action, combining the files ..."
   all_files="$splits"
   echo "$action, concatenating all files ($all_files) ..."
+  echo "Beginning at `date`:"
+  echo "Executing: cat $all_files > $outfile"
   cat $all_files > $outfile
+  echo "Ended at `date`."
+  echo "$action, removing intermediate split files ($splits_removable) ..."
+  rm -f $splits_removable
   echo "$action ... done."
 
 fi

File src/main/scala/opennlp/textgrounder/geolocate/DocumentPinKMLGenerator.scala

+///////////////////////////////////////////////////////////////////////////////
+//  Copyright (C) 2011 The University of Texas at Austin
+//
+//  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.
+///////////////////////////////////////////////////////////////////////////////
+
+////////
+//////// DocumentPinKMLGenerator.scala
+////////
+//////// Copyright (c) 2012.
+////////
+
+package opennlp.textgrounder.geolocate
+
+import java.io._
+import javax.xml.datatype._
+import javax.xml.stream._
+import opennlp.textgrounder.topo._
+import opennlp.textgrounder.util.KMLUtil
+import opennlp.textgrounder.util.LogUtil
+import scala.collection.JavaConversions._
+import org.clapper.argot._
+
+object DocumentPinKMLGenerator {
+
+  val factory = XMLOutputFactory.newInstance
+  val rand = new scala.util.Random
+
+  import ArgotConverters._
+
+  val parser = new ArgotParser("textgrounder run opennlp.textgrounder.geolocate.DocumentPinKMLGenerator", preUsage = Some("TextGrounder"))
+  val inFile = parser.option[String](List("i", "input"), "input", "input file")
+  val kmlOutFile = parser.option[String](List("k", "kml"), "kml", "kml output file")
+  val tokenIndexOffset = parser.option[Int](List("o", "offset"), "offset", "token index offset")
+
+  def main(args: Array[String]) {
+    try {
+      parser.parse(args)
+    }
+    catch {
+      case e: ArgotUsageException => println(e.message); sys.exit(0)
+    }
+
+    if(inFile.value == None) {
+      println("You must specify an input file via -i.")
+      sys.exit(0)
+    }
+    if(kmlOutFile.value == None) {
+      println("You must specify a KML output file via -k.")
+      sys.exit(0)
+    }
+    val offset = if(tokenIndexOffset.value != None) tokenIndexOffset.value.get else 0
+
+    val outFile = new File(kmlOutFile.value.get)
+    val stream = new BufferedOutputStream(new FileOutputStream(outFile))
+    val out = factory.createXMLStreamWriter(stream, "UTF-8")
+
+    KMLUtil.writeHeader(out, inFile.value.get)
+
+    for(line <- scala.io.Source.fromFile(inFile.value.get).getLines) {
+      val tokens = line.split("\t")
+      if(tokens.length >= 3+offset) {
+        val docName = tokens(1+offset)
+        val coordTextPair = tokens(2+offset).split(",")
+        val coord = Coordinate.fromDegrees(coordTextPair(0).toDouble, coordTextPair(1).toDouble)
+        KMLUtil.writePinPlacemark(out, docName, coord)
+      }
+    }
+
+    KMLUtil.writeFooter(out)
+
+    out.close
+  }
+}