1 /***************************************
  2  * Copyright 2011, 2012 GlobWeb contributors.
  3  *
  4  * This file is part of GlobWeb.
  5  *
  6  * GlobWeb is free software: you can redistribute it and/or modify
  7  * it under the terms of the GNU Lesser General Public License as published by
  8  * the Free Software Foundation, version 3 of the License, or
  9  * (at your option) any later version.
 10  *
 11  * GlobWeb is distributed in the hope that it will be useful,
 12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 14  * GNU Lesser General Public License for more details.
 15  *
 16  * You should have received a copy of the GNU General Public License
 17  * along with GlobWeb. If not, see <http://www.gnu.org/licenses/>.
 18  ***************************************/
 19 
 20 define( ['./FeatureStyle'], function(FeatureStyle) {
 21 
 22 /**************************************************************************************************************/
 23 
 24 /** @constructor
 25 	KMLParser constructor
 26  */
 27 var KMLParser = (function()
 28 {
 29 	var featureCollection = { type: "FeatureCollection",
 30 				features: [] };
 31 				
 32 	var styles = {};
 33 	
 34 	var parseColor = /^(\w{2})(\w{2})(\w{2})(\w{2})$/;
 35 	
 36 	/*
 37 	 * Parse a color string
 38 	 * @param color_string : the color string
 39 	 * @return the color
 40 	 */
 41 	var fromStringToColor = function(color_string)
 42 	{
 43 		var match = parseColor.exec(color_string);
 44 		if ( match )
 45 		{
 46 			return [ parseInt(match[4],16) / 255.0, parseInt(match[3],16) / 255.0, parseInt(match[2],16) / 255.0, parseInt(match[1],16) / 255.0 ];
 47 		}
 48 		
 49 		return [ 1., 1., 1., 1. ];
 50 	};
 51 		
 52 	/*
 53 	 * Parse coordinates, split them and return an array of coordinates in GeoJSON format
 54 	 * @param coordsText : the text node value for coordinates
 55 	 */
 56 	var parseCoordinates = function(coordsText)
 57 	{
 58 		var coordinates = [];
 59 		// Trim the coordinates, then split them
 60 		var coords = coordsText.trim().split(/[\s,]+/);
 61 		for ( var i = 0; i < coords.length; i += 3 )
 62 		{
 63 			coordinates.push( [ parseFloat(coords[i]), parseFloat(coords[i+1]) ] );
 64 		}
 65 		return coordinates;
 66 	}
 67 	
 68 	/*
 69 	 * Parse KML geometry, return a GeoJSON geometry
 70 	 * @param node : a candiate node for geoemtry
 71 	 */
 72 	var checkAndParseGeometry = function(node)
 73 	{
 74 		switch ( node.nodeName )
 75 		{
 76 			case "MultiGeometry":
 77 			{
 78 				var geoms = [];
 79 				
 80 				var children = node.childNodes;
 81 				for (var i = 0; i < children.length; i++)
 82 				{
 83 					var geometry = checkAndParseGeometry(children[i]);
 84 					if ( geometry )
 85 					{
 86 						geoms.push( geometry );
 87 					}
 88 				}
 89 				
 90 				return 	{ type: "GeometryCollection", geometries: geoms };
 91 			}
 92 			break;
 93 			case "LineString":
 94 			{
 95 				var coordNode = node.getElementsByTagName("coordinates");
 96 				if ( coordNode.length == 1 )
 97 				{
 98 					return { type: "LineString",
 99 							coordinates: parseCoordinates( coordNode[0].textContent ) };
100 				}
101 			}
102 			break;
103 			case "Polygon":
104 			{
105 				// TODO : manage holes
106 				var coordNode = node.firstElementChild.getElementsByTagName("coordinates");
107 				if ( coordNode.length == 1 )
108 				{
109 					return { type: "Polygon",
110 							coordinates: [ parseCoordinates( coordNode[0].textContent ) ] };
111 				}
112 			}
113 			break;
114 			case "Point":
115 			{
116 				var coordNode = node.getElementsByTagName("coordinates");
117 				if ( coordNode.length == 1 )
118 				{
119 					var coord = coordNode[0].textContent.split(",");
120 					return { type: "Point",
121 							coordinates: [ coord[0], coord[1] ] };
122 				}
123 			}
124 			break;
125 		}
126 	
127 		return null;
128 	}
129 	
130 	/*
131 	 * Parse placemark
132 	 */
133 	var parsePlacemark = function(node)
134 	{
135 		// Create a feature
136 		var feature = { type: "Feature",
137 					properties: {},
138 					geometry: null };
139 		
140 		var shareStyle = false;
141 		var child = node.firstElementChild;
142 		while ( child )
143 		{
144 			switch ( child.nodeName )
145 			{
146 			case "name":
147 				feature.properties.name = child.childNodes[0].nodeValue;
148 				break;
149 			case "styleUrl":
150 				{
151 					var id = child.childNodes[0].nodeValue;
152 					if ( styles.hasOwnProperty(id) ) 
153 					{
154 						feature.properties.style = styles[id];
155 						shareStyle = true;
156 					}
157 				}
158 				break;
159 			case "Style":
160 				{
161 					var style = parseStyle(child,feature.properties.name);
162 					if ( style )
163 					{
164 						feature.properties.style = style;
165 					}
166 				}
167 				break;
168 			default:
169 				// Try with geometry
170 				if ( feature.geometry == null )
171 				{
172 					feature.geometry = checkAndParseGeometry(child);
173 				}
174 			}
175 			child = child.nextElementSibling;
176 		}
177 		
178 		if ( feature.geometry )
179 		{
180 			// Manage the fact that labels are always active with KML
181 			var style = feature.properties.style;
182 			if ( style && style.textColor[3] > 0.0 && feature.geometry.type == "Point" )
183 			{
184 				if ( shareStyle )
185 				{
186 					style = feature.properties.style = new FeatureStyle(style);
187 				}
188 				style.label = feature.properties.name;
189 			}
190 			
191 			featureCollection.features.push( feature );
192 		}
193 	}
194 		
195 	/*
196 	 * Parse Document or folder
197 	 */
198 	var parseDocumentOrFolder = function(node)
199 	{
200 		var child = node.firstElementChild;
201 		while ( child )
202 		{
203 			switch ( child.nodeName )
204 			{
205 			case "visibility":
206 				var vis = parseInt(child.textContent);
207 				if ( vis == 0 )
208 					return;
209 				break;
210 			case "Style":
211 				parseStyle(child);
212 				break;
213 			default:
214 				checkAndParseFeature(child);
215 			}
216 			child = child.nextElementSibling;
217 		}
218 	}
219 	
220 	/*
221 	 * Parse line style
222 	 */
223 	var parseLineStyle = function(node,style)
224 	{
225 		var child = node.firstElementChild;
226 		while ( child )
227 		{
228 			switch ( child.nodeName )
229 			{
230 			case "color":
231 				style.strokeColor = fromStringToColor( child.childNodes[0].nodeValue );
232 				break;
233 			case "width":
234 				style.strokeWidth = parseFloat( child.childNodes[0].nodeValue );
235 				break;
236 			}
237 			child = child.nextElementSibling;
238 		}
239 	}
240 	
241 	/*
242 	 * Parse icon style
243 	 */
244 	var parseIconStyle = function(node,style)
245 	{
246 		var child = node.firstElementChild;
247 		while ( child )
248 		{
249 			switch ( child.nodeName )
250 			{
251 			case "color":
252 				//style.strokeColor = fromStringToColor( child.childNodes[0].nodeValue );
253 				break;
254 			case "Icon":
255 				if ( child.firstElementChild )
256 					style.iconUrl = child.firstElementChild.childNodes[0].nodeValue;
257 				else
258 					style.iconUrl = null;
259 				break;
260 			}
261 			child = child.nextElementSibling;
262 		}
263 	}
264 
265 	/*
266 	 * Parse label style
267 	 */
268 	var parseLabelStyle = function(node,style)
269 	{
270 		var child = node.firstElementChild;
271 		while ( child )
272 		{
273 			switch ( child.nodeName )
274 			{
275 			case "color":
276 				var labelColor = fromStringToColor( child.textContent.trim() );
277 				if ( labelColor[3] == 0 )
278 				{
279 					style.label = null;
280 					style.textColor = labelColor;
281 				}
282 				break;
283 			/*case "Icon":
284 				if ( child.firstElementChild )
285 					style.iconUrl = child.firstElementChild.childNodes[0].nodeValue;
286 				else
287 					style.iconUrl = null;
288 				break;*/
289 			}
290 			child = child.nextElementSibling;
291 		}
292 	}
293 	
294 	/*
295 	 * Parse style
296 	 */
297 	var parseStyle = function(node)
298 	{
299 		var id = '#' + node.getAttribute("id");
300 
301 		var style = new FeatureStyle();
302 		styles[id] = style;
303 		
304 		// Iterate through child to manage all different style element
305 		var child = node.firstElementChild;
306 		while ( child )
307 		{
308 			switch ( child.nodeName )
309 			{
310 			case "LineStyle":
311 				parseLineStyle(child,style);
312 				break;
313 			case "IconStyle":
314 				parseIconStyle(child,style);
315 				break;
316 			case "LabelStyle":
317 				parseLabelStyle(child,style);
318 				break;
319 			}
320 			child = child.nextElementSibling;
321 		}
322 			
323 		return style;
324 	}
325 	
326 	/*
327 	 * Parse feature
328 	 */
329 	var checkAndParseFeature = function(node)
330 	{
331 		switch ( node.nodeName )
332 		{
333 		case "Placemark":
334 			parsePlacemark( node );
335 			break
336 		case "Document":
337 		case "Folder":
338 			parseDocumentOrFolder( node );
339 			break;
340 		}
341 	}
342 	
343 	/*
344 	 * Parse a KML document
345 	 */
346 	var parse = function(doc)
347 	{
348 		var root = doc.documentElement;
349 		var child = root.firstElementChild;
350 		while ( child )
351 		{
352 			checkAndParseFeature(child);
353 			child = child.nextElementSibling;
354 		}
355 		
356 		return featureCollection;
357 	}
358 	
359 	return { parse: parse };
360 })();
361 
362 return KMLParser;
363 
364 });
365 
366