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 GXml, 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         tagName, x;
 57     
 58     // Add ExtendedData parameters to extra params
 59     for (x=0; x < extendedData.length; x++) {
 60         tagName = extendedData[x];
 61         loader.extraParams.push(
 62             new TimeMap.params.ExtendedDataParam(tagMap[tagName] || tagName, tagName)
 63         );
 64     }
 65     
 66     // set custom parser
 67     loader.parse = TimeMap.loaders.kml.parse;
 68     return loader;
 69 };
 70 
 71 /**
 72  * Static function to parse KML with time data.
 73  *
 74  * @param {XML string} kml      KML to be parsed
 75  * @return {TimeMapItem Array}  Array of TimeMapItems
 76  */
 77 TimeMap.loaders.kml.parse = function(kml) {
 78     var items = [], data, kmlnode, placemarks, pm, i, j;
 79     kmlnode = GXml.parse(kml);
 80     
 81     // get TimeMap utilty functions
 82     // assigning to variables should compress better
 83     var util = TimeMap.util,
 84         getTagValue = util.getTagValue,
 85         getNodeList = util.getNodeList,
 86         makePoint = util.makePoint,
 87         makePoly = util.makePoly,
 88         formatDate = util.formatDate;
 89     
 90     // recursive time data search
 91     var findNodeTime = function(n, data) {
 92         var check = false;
 93         // look for instant timestamp
 94         var nList = getNodeList(n, "TimeStamp");
 95         if (nList.length > 0) {
 96             data.start = getTagValue(nList[0], "when");
 97             check = true;
 98         }
 99         // otherwise look for span
100         else {
101             nList = getNodeList(n, "TimeSpan");
102             if (nList.length > 0) {
103                 data.start = getTagValue(nList[0], "begin");
104                 data.end = getTagValue(nList[0], "end") ||
105                     // unbounded spans end at the present time
106                     formatDate(new Date());
107                 check = true;
108             }
109         }
110         // try looking recursively at parent nodes
111         if (!check) {
112             var pn = n.parentNode;
113             if (pn.nodeName == "Folder" || pn.nodeName=="Document") {
114                 findNodeTime(pn, data);
115             }
116             pn = null;
117         }
118     };
119     
120     // look for placemarks
121     placemarks = getNodeList(kmlnode, "Placemark");
122     for (i=0; i<placemarks.length; i++) {
123         pm = placemarks[i];
124         data = { options: {} };
125         // get title & description
126         data.title = getTagValue(pm, "name");
127         data.options.description = getTagValue(pm, "description");
128         // get time information
129         findNodeTime(pm, data);
130         // find placemark(s)
131         var nList, coords, pmobj;
132         data.placemarks = [];
133         // look for marker
134         nList = getNodeList(pm, "Point");
135         for (j=0; j<nList.length; j++) {
136             pmobj = { point: {} };
137             // get lat/lon
138             coords = getTagValue(nList[j], "coordinates");
139             pmobj.point = makePoint(coords, 1);
140             data.placemarks.push(pmobj);
141         }
142         // look for polylines
143         nList = getNodeList(pm, "LineString");
144         for (j=0; j<nList.length; j++) {
145             pmobj = { polyline: [] };
146             // get lat/lon
147             coords = getTagValue(nList[j], "coordinates");
148             pmobj.polyline = makePoly(coords, 1);
149             data.placemarks.push(pmobj);
150         }
151         // look for polygons
152         nList = getNodeList(pm, "Polygon");
153         for (j=0; j<nList.length; j++) {
154             pmobj = { polygon: [] };
155             // get lat/lon
156             coords = getTagValue(nList[j], "coordinates");
157             pmobj.polygon = makePoly(coords, 1);
158             // XXX: worth closing unclosed polygons?
159             data.placemarks.push(pmobj);
160         }
161         // look for any extra tags and/or ExtendedData specified
162         this.parseExtra(data, pm);
163         
164         items.push(data);
165     }
166     
167     // look for ground overlays
168     placemarks = getNodeList(kmlnode, "GroundOverlay");
169     for (i=0; i<placemarks.length; i++) {
170         pm = placemarks[i];
171         data = { options: {}, overlay: {} };
172         // get title & description
173         data.title = getTagValue(pm, "name");
174         data.options.description = getTagValue(pm, "description");
175         // get time information
176         findNodeTime(pm, data);
177         // get image
178         nList = getNodeList(pm, "Icon");
179         data.overlay.image = getTagValue(nList[0], "href");
180         // get coordinates
181         nList = getNodeList(pm, "LatLonBox");
182         data.overlay.north = getTagValue(nList[0], "north");
183         data.overlay.south = getTagValue(nList[0], "south");
184         data.overlay.east = getTagValue(nList[0], "east");
185         data.overlay.west = getTagValue(nList[0], "west");
186         // look for any extra tags and/or ExtendedData specified
187         this.parseExtra(data, pm);
188         items.push(data);
189     }
190     
191     // clean up
192     kmlnode = placemarks = pm = nList = null;
193     
194     return items;
195 };
196 
197 /**
198  * @class
199  * Class for parameters loaded from KML ExtendedData elements
200  *
201  * @augments TimeMap.params.OptionParam
202  *
203  * @constructor
204  * @param {String} paramName        String name of the parameter
205  * @param {String} [tagName]        Tag name, if different
206  */
207 TimeMap.params.ExtendedDataParam = function(paramName, tagName) {
208     return new TimeMap.params.OptionParam(paramName, {
209     
210         /**
211          * Set a config object based on an ExtendedData element
212          * @name TimeMap.params.ExtendedDataParam#setConfigKML
213          * @function
214          * 
215          * @param {Object} config       Config object to modify
216          * @param {XML NodeList} node   Parent node to look for tags in
217          */
218         setConfigXML: function(config, node) {
219             var util = TimeMap.util,
220                 nList = util.getNodeList(node, "Data"),
221                 i;
222             for (i=0; i<nList.length; i++) {
223                 if (nList[i].getAttribute("name") == tagName) {
224                     this.setConfig(config, util.getTagValue(nList[i], "value"))
225                 }
226             }
227             node = nList = null;
228         },
229         
230         sourceName: tagName
231     
232     });
233 };
234