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