1 /*
  2  * Timemap.js Copyright 2010 Nick Rabinowitz.
  3  * Licensed under the MIT License (see LICENSE.txt)
  4  */
  5  
  6 /**
  7  * @fileOverview
  8  * KML Loader
  9  *
 10  * @author Nick Rabinowitz (www.nickrabinowitz.com)
 11  */
 12 
 13 /*globals TimeMap */
 14 
 15 /**
 16  * @class
 17  * KML loader: load KML files.
 18  *
 19  * <p>This is a loader class for KML files. Currently supports all geometry
 20  * types (point, polyline, polygon, and overlay) and multiple geometries. Supports loading
 21  * <a href="http://code.google.com/apis/kml/documentation/extendeddata.html">ExtendedData</a>
 22  * through the extendedData parameter.
 23  * </p>
 24  *
 25  * @augments TimeMap.loaders.xml
 26  * @requires loaders/xml.js
 27  * @requires param.js
 28  * @borrows TimeMap.loaders.kml.parse as #parse
 29  *
 30  * @example
 31 TimeMap.init({
 32     datasets: [
 33         {
 34             title: "KML Dataset",
 35             type: "kml",
 36             options: {
 37                 url: "mydata.kml"   // Must be local
 38             }
 39         }
 40     ],
 41     // etc...
 42 });
 43  * @see <a href="../../examples/kenya.html">KML Example</a>
 44  * @see <a href="../../examples/kml_extendeddata.html">KML ExtendedData Example</a>
 45  *
 46  * @param {Object} options          All options for the loader
 47  * @param {String} options.url              URL of KML file to load (NB: must be local address)
 48  * @param {String[]} [options.extendedData] Array of names for ExtendedData data elements
 49  * @param {mixed} [options[...]]            Other options (see {@link TimeMap.loaders.xml})
 50  * @return {TimeMap.loaders.xml}    Loader configured for KML
 51  */
 52 TimeMap.loaders.kml = function(options) {
 53     var loader = new TimeMap.loaders.xml(options),
 54         tagMap = options.tagMap || {},
 55         extendedData = options.extendedData || [];
 56     
 57     // Add ExtendedData parameters to extra params
 58     extendedData.forEach(function(tagName) {
 59         loader.extraParams.push(
 60             new TimeMap.params.ExtendedDataParam(tagMap[tagName] || tagName, tagName)
 61         );
 62     });
 63     
 64     // set custom parser
 65     loader.parse = TimeMap.loaders.kml.parse;
 66     return loader;
 67 };
 68 
 69 /**
 70  * Static function to parse KML with time data.
 71  *
 72  * @param {XML} kml      KML node to be parsed
 73  * @return {TimeMapItem Array}  Array of TimeMapItems
 74  */
 75 TimeMap.loaders.kml.parse = function(kmlnode) {
 76     var loader = this,
 77         items = [], 
 78         data, placemarks, nList, coords, pmobj;
 79     
 80     // get TimeMap utilty functions
 81     // assigning to variables should compress better
 82     var util = TimeMap.util,
 83         getTagValue = util.getTagValue,
 84         getNodeList = util.getNodeList,
 85         makePoint = util.makePoint,
 86         makePoly = util.makePoly,
 87         formatDate = util.formatDate;
 88     
 89     // recursive time data search
 90     function findNodeTime(n, data) {
 91         var tstamp = $(n).children("TimeStamp"),
 92             tspan = $(n).children("TimeSpan");
 93         // set start if found
 94         if (tspan.length) {
 95             data.start = getTagValue(tspan, 'begin');
 96             data.end = getTagValue(tspan, 'end')  ||
 97                 // unbounded spans end at the present time
 98                 formatDate(new Date());
 99         } else {
100             data.start = getTagValue(tstamp, 'when');
101         }
102         // try looking recursively at parent nodes
103         if (!data.start) {
104             var $pn = $(n).parent();
105             if ($pn.is("Folder") || $pn.is("Document")) {
106                 findNodeTime($pn, data);
107             }
108         }
109     }
110     
111     // look for placemarks
112     getNodeList(kmlnode, "Placemark").each(function() {
113         var pm = this;
114         data = { options: {} };
115         // get title & description
116         data.title = getTagValue(pm, "name");
117         data.options.description = getTagValue(pm, "description");
118         // get time information
119         findNodeTime(pm, data);
120         // find placemark(s)
121         data.placemarks = [];
122         // look for marker
123         getNodeList(pm, "Point").each(function() {
124             pmobj = { point: {} };
125             // get lat/lon
126             coords = getTagValue(this, "coordinates");
127             pmobj.point = makePoint(coords, 1);
128             data.placemarks.push(pmobj);
129         });
130         // look for polylines
131         getNodeList(pm, "LineString").each(function() {
132             pmobj = { polyline: [] };
133             // get lat/lon
134             coords = getTagValue(this, "coordinates");
135             pmobj.polyline = makePoly(coords, 1);
136             data.placemarks.push(pmobj);
137         });
138         // look for polygons
139         getNodeList(pm, "Polygon").each(function() {
140             pmobj = { polygon: [] };
141             // get lat/lon
142             coords = getTagValue(this, "coordinates");
143             pmobj.polygon = makePoly(coords, 1);
144             data.placemarks.push(pmobj);
145         });
146         // look for any extra tags and/or ExtendedData specified
147         loader.parseExtra(data, pm);
148         
149         items.push(data);
150     });
151     
152     // look for ground overlays
153     getNodeList(kmlnode, "GroundOverlay").each(function() {
154         var pm = this;
155         data = { options: {}, overlay: {} };
156         // get title & description
157         data.title = getTagValue(pm, "name");
158         data.options.description = getTagValue(pm, "description");
159         // get time information
160         findNodeTime(pm, data);
161         // get image
162         data.overlay.image = getTagValue(pm, "Icon href");
163         // get coordinates
164         nList = getNodeList(pm, "LatLonBox");
165         data.overlay.north = getTagValue(nList, "north");
166         data.overlay.south = getTagValue(nList, "south");
167         data.overlay.east = getTagValue(nList, "east");
168         data.overlay.west = getTagValue(nList, "west");
169         // look for any extra tags and/or ExtendedData specified
170         loader.parseExtra(data, pm);
171         items.push(data);
172     });
173     
174     // clean up
175     kmlnode = null;
176     
177     return items;
178 };
179 
180 /**
181  * @class
182  * Class for parameters loaded from KML ExtendedData elements
183  *
184  * @augments TimeMap.params.OptionParam
185  *
186  * @constructor
187  * @param {String} paramName        String name of the parameter
188  * @param {String} [tagName]        Tag name, if different
189  */
190 TimeMap.params.ExtendedDataParam = function(paramName, tagName) {
191     return new TimeMap.params.OptionParam(paramName, {
192     
193         /**
194          * Set a config object based on an ExtendedData element
195          * @name TimeMap.params.ExtendedDataParam#setConfigKML
196          * @function
197          * 
198          * @param {Object} config       Config object to modify
199          * @param {XML NodeList} node   Parent node to look for tags in
200          */
201         setConfigXML: function(config, node) {
202             var util = TimeMap.util,
203                 param = this;
204             util.getNodeList(node, "Data").each(function() {
205                 var $n = $(this);
206                 if ($n.attr("name") == tagName) {
207                     param.setConfig(config, util.getTagValue($n, "value"));
208                 }
209             });
210         },
211         
212         sourceName: tagName
213     
214     });
215 };
216