OpenLayers OpenLayers

Ticket #1259: 1259-r6161-A0.patch

File 1259-r6161-A0.patch, 24.9 kB (added by ahocevar, 1 year ago)

See ticket comments for changes

  • D:/eclipse/workspace/openlayers/lib/OpenLayers/Format/KML.js

    old new  
    4848    /** 
    4949     * APIProperty: extractAttributes 
    5050     * {Boolean} Extract attributes from KML.  Default is true. 
     51     *           Extracting styleUrls requires this to be set to true 
    5152     */ 
    5253    extractAttributes: true, 
    5354     
    5455    /** 
     56     * Property: extractStyles 
     57     * {Boolean} Extract styles from KML.  Default is false. 
     58     *           Extracting styleUrls also requires extractAttributes to be 
     59     *           set to true 
     60     */ 
     61    extractStyles: false, 
     62     
     63    /** 
    5564     * Property: internalns 
    5665     * {String} KML Namespace to use -- defaults to the namespace of the 
    5766     *     Placemark node being parsed, but falls back to kmlns.  
     
    5968    internalns: null, 
    6069 
    6170    /** 
     71     * Property: features 
     72     * {Array} Array of features 
     73     *      
     74     */ 
     75    features: null, 
     76 
     77    /** 
     78     * Property: styles 
     79     * {Object} Storage of style objects 
     80     *      
     81     */ 
     82    styles: null, 
     83     
     84    /** 
     85     * Property: styleBaseUrl 
     86     * {String} 
     87     */ 
     88    styleBaseUrl: "", 
     89 
     90    /** 
     91     * Property: fetched 
     92     * {Object} Storage of KML URLs that have been fetched before 
     93     *     in order to prevent reloading them. 
     94     */ 
     95    fetched: null, 
     96 
     97    /** 
     98     * APIProperty: maxDepth 
     99     * {Integer} Maximum depth for recursive loading external KML URLs  
     100     *           Defaults to 0: do no external fetching 
     101     */ 
     102    maxDepth: 0, 
     103 
     104    /** 
    62105     * Constructor: OpenLayers.Format.KML 
    63106     * Create a new parser for KML. 
    64107     * 
     
    72115            trimSpace: (/^\s*|\s*$/g), 
    73116            removeSpace: (/\s*/g), 
    74117            splitSpace: (/\s+/), 
    75             trimComma: (/\s*,\s*/g) 
     118            trimComma: (/\s*,\s*/g), 
     119            kmlColor: (/(\w{2})(\w{2})(\w{2})(\w{2})/), 
     120            kmlIconPalette: (/root:\/\/icons\/palette-(\d+)(\.\w+)/), 
     121            straightBracket: (/\$\[(.*?)\]/g) 
    76122        }; 
    77123        OpenLayers.Format.XML.prototype.initialize.apply(this, [options]); 
    78124    }, 
     
    82128     * Read data from a string, and return a list of features.  
    83129     *  
    84130     * Parameters:  
    85      * data - {String} or {DOMElement} data to read/parse. 
     131     * data    - {String} or {DOMElement} data to read/parse. 
    86132     * 
    87133     * Returns: 
    88134     * {Array(<OpenLayers.Feature.Vector>)} List of features. 
    89135     */ 
    90136    read: function(data) { 
     137        this.features = []; 
     138        this.styles   = {}; 
     139        this.fetched  = {}; 
     140 
     141        // Set default options  
     142        var options = { 
     143            depth: this.maxDepth, 
     144            styleBaseUrl: this.styleBaseUrl 
     145        }; 
     146 
     147        return this.parseData(data, options); 
     148    }, 
     149 
     150    /** 
     151     * Method: parseData 
     152     * Read data from a string, and return a list of features.  
     153     *  
     154     * Parameters:  
     155     * data    - {String} or {DOMElement} data to read/parse. 
     156     * options - {Object} Hash of options 
     157     * 
     158     * Returns: 
     159     * {Array(<OpenLayers.Feature.Vector>)} List of features. 
     160     */ 
     161    parseData: function(data, options) { 
    91162        if(typeof data == "string") { 
    92163            data = OpenLayers.Format.XML.prototype.read.apply(this, [data]); 
    93164        } 
    94         var featureNodes = this.getElementsByTagNameNS(data, 
    95                                                        '*', 
    96                                                        "Placemark"); 
    97         var numFeatures = featureNodes.length; 
    98         var features = new Array(numFeatures); 
    99         for(var i=0; i<numFeatures; i++) { 
    100             var feature = this.parseFeature(featureNodes[i]); 
     165 
     166        // Loop throught the following node types in this order and 
     167        // process the nodes found  
     168        var types = ["Link", "NetworkLink", "Style", "StyleMap", "Placemark"]; 
     169        for(var i=0; i<types.length; ++i) { 
     170            var type = types[i]; 
     171 
     172            var nodes = this.getElementsByTagNameNS(data, "*", type); 
     173 
     174            // skip to next type if no nodes are found 
     175            if(nodes.length == 0) {  
     176                continue; 
     177            } 
     178 
     179            switch (type.toLowerCase()) { 
     180 
     181                // Fetch external links  
     182                case "link": 
     183                case "networklink": 
     184                    this.parseLinks(nodes, options); 
     185                    break; 
     186 
     187                // parse style information 
     188                case "style": 
     189                    if (this.extractStyles) { 
     190                        this.parseStyles(nodes, options); 
     191                    } 
     192                    break; 
     193                case "stylemap": 
     194                    if (this.extractStyles) { 
     195                        this.parseStyleMaps(nodes, options); 
     196                    } 
     197                    break; 
     198 
     199                // parse features 
     200                case "placemark": 
     201                    this.parseFeatures(nodes, options); 
     202                    break; 
     203            } 
     204        } 
     205         
     206        return this.features; 
     207    }, 
     208 
     209    /** 
     210     * Method: parseLinks 
     211     * Finds URLs of linked KML documents and fetches them 
     212     *  
     213     * Parameters:  
     214     * nodes   - {Array} of {DOMElement} data to read/parse. 
     215     * options - {Object} Hash of options 
     216     *  
     217     */ 
     218    parseLinks: function(nodes, options) { 
     219         
     220        // Fetch external links <NetworkLink> and <Link> 
     221        // Don't do anything if we have reached our maximum depth for recursion 
     222        if (options.depth >= this.maxDepth) { 
     223            return false; 
     224        } 
     225 
     226        // increase depth 
     227        var newOptions = OpenLayers.Util.extend({}, options); 
     228        newOptions.depth++; 
     229 
     230        for(var i=0; i < nodes.length; i++) { 
     231            var href = this.parseProperty(nodes[i], "*", "href"); 
     232            if(href && !this.fetched[href]) { 
     233                this.fetched[href] = true; // prevent reloading the same urls 
     234                var data = this.fetchLink(href); 
     235                if (data) { 
     236                    this.parseData(data, newOptions); 
     237                } 
     238            }  
     239        } 
     240 
     241    }, 
     242 
     243    /** 
     244     * Method: fetchLink 
     245     * Fetches a URL and returns the result 
     246     *  
     247     * Parameters:  
     248     * href  - {String} url to be fetched 
     249     *  
     250     */ 
     251    fetchLink: function(href) { 
     252 
     253        if (OpenLayers.ProxyHost  
     254                && OpenLayers.String.startsWith(href, "http")) { 
     255            href = OpenLayers.ProxyHost + escape(href); 
     256        } 
     257 
     258        var request = new OpenLayers.Ajax.Request(href,  
     259                      {method: 'get', asynchronous: false }); 
     260 
     261        if (request && request.transport) { 
     262            return request.transport.responseText; 
     263        } 
     264 
     265    }, 
     266 
     267    /** 
     268     * Method: parseStyles 
     269     * Looks for <Style> nodes in the data and parses them 
     270     * Also parses <StyleMap> nodes, but only uses the 'normal' key 
     271     *  
     272     * Parameters:  
     273     * nodes    - {Array} of {DOMElement} data to read/parse. 
     274     * options  - {Object} Hash of options 
     275     *  
     276     */ 
     277    parseStyles: function(nodes, options) { 
     278        for(var i=0; i < nodes.length; i++) { 
     279            var style = this.parseStyle(nodes[i]); 
     280            if(style) { 
     281                styleName = (options.styleBaseUrl || "") + "#" + style.id; 
     282                 
     283                this.styles[styleName] = style; 
     284            } 
     285        } 
     286    }, 
     287 
     288    /** 
     289     * Method: parseStyle 
     290     * Parses the children of a <Style> node and builds the style hash 
     291     * accordingly 
     292     *  
     293     * Parameters:  
     294     * node - {DOMElement} <Style> node 
     295     *  
     296     */ 
     297    parseStyle: function(node) { 
     298        var style = {}; 
     299         
     300        var types = ["LineStyle", "PolyStyle", "IconStyle", "BalloonStyle"]; 
     301        var type, nodeList, geometry, parser; 
     302        for(var i=0; i<types.length; ++i) { 
     303            type = types[i]; 
     304            styleTypeNode = this.getElementsByTagNameNS(node,  
     305                                                   "*", type)[0]; 
     306            if(!styleTypeNode) {  
     307                continue; 
     308            } 
     309 
     310            // only deal with first geometry of this type 
     311            switch (type.toLowerCase()) { 
     312                case "linestyle": 
     313                    var color = this.parseProperty(styleTypeNode, "*", "color"); 
     314                    if (color) { 
     315                        var matches = (color.toString()).match( 
     316                                                         this.regExes.kmlColor); 
     317 
     318                        // transparency 
     319                        var alpha = matches[1]; 
     320                        style["strokeOpacity"] = parseInt(alpha, 16) / 255; 
     321 
     322                        // rgb colors (google uses bgr) 
     323                        var b = matches[2];  
     324                        var g = matches[3];  
     325                        var r = matches[4];  
     326                        style["strokeColor"] = "#" + r + g + b; 
     327                    } 
     328                     
     329                    var width = this.parseProperty(styleTypeNode, "*", "width"); 
     330                    if (width) { 
     331                        style["strokeWidth"] = width; 
     332                    } 
     333 
     334                case "polystyle": 
     335                    var color = this.parseProperty(styleTypeNode, "*", "color"); 
     336                    if (color) { 
     337                        var matches = (color.toString()).match( 
     338                                                         this.regExes.kmlColor); 
     339 
     340                        // transparency 
     341                        var alpha = matches[1]; 
     342                        style["fillOpacity"] = parseInt(alpha, 16) / 255; 
     343 
     344                        // rgb colors (google uses bgr) 
     345                        var b = matches[2];  
     346                        var g = matches[3];  
     347                        var r = matches[4];  
     348                        style["fillColor"] = "#" + r + g + b; 
     349                    } 
     350                     
     351                    break; 
     352                case "iconstyle": 
     353                    var iconNode = this.getElementsByTagNameNS(styleTypeNode,  
     354                                               "*",  
     355                                               "Icon")[0]; 
     356 
     357                    // set default width and height of icon 
     358                    style["graphicWidth"] = 32; 
     359                    style["graphicHeight"] = 32; 
     360 
     361                    if (iconNode) { 
     362                        var href = this.parseProperty(iconNode, "*", "href"); 
     363                        if (href) {                                                    
     364 
     365                            // support for internal icons  
     366                            //    (/root://icons/palette-x.png) 
     367                            // x and y tell the position on the palette: 
     368                            // - in pixels 
     369                            // - starting from the left bottom 
     370                            // We translate that to a position in the list  
     371                            // and request the appropriate icon from the  
     372                            // google maps website 
     373                            var matches = href.match(this.regExes.kmlIconPalette); 
     374                            if (matches)  { 
     375                                var palette = matches[1]; 
     376                                var file_extension = matches[2]; 
     377 
     378                                var x = this.parseProperty(iconNode, "*", "x"); 
     379                                var y = this.parseProperty(iconNode, "*", "y"); 
     380 
     381                                var posX = x ? x/32 : 0; 
     382                                var posY = y ? (7 - y/32) : 7; 
     383 
     384                                var pos = posY * 8 + posX; 
     385                                href = "http://maps.google.com/mapfiles/kml/pal"  
     386                                     + palette + "/icon" + pos + file_extension; 
     387                            } 
     388 
     389 
     390                            var w = this.parseProperty(iconNode, "*", "w"); 
     391                            if (w) { 
     392                                style["graphicWidth"] = parseInt(w); 
     393                            } 
     394 
     395                            var h = this.parseProperty(iconNode, "*", "h"); 
     396                            if (h) { 
     397                                style["graphicHeight"] = parseInt(h); 
     398                            } 
     399 
     400                            style["graphicOpacity"] = 1; // fully opaque 
     401                            style["externalGraphic"] = href; 
     402                        } 
     403 
     404                    } 
     405 
     406 
     407                    // hotSpots define the offset for an Icon 
     408                    var hotSpotNode = this.getElementsByTagNameNS(styleTypeNode,  
     409                                               "*",  
     410                                               "hotSpot")[0]; 
     411                    if (hotSpotNode) { 
     412                        var x = hotSpotNode.getAttribute("x"); 
     413                        var y = hotSpotNode.getAttribute("y"); 
     414 
     415                        var xUnits = hotSpotNode.getAttribute("xunits"); 
     416                        if (xUnits == "pixels") { 
     417                            style["graphicXOffset"] = parseInt(x); 
     418                        } 
     419                        else if (xUnits == "insetPixels") { 
     420                            style["graphicXOffset"] = style["graphicWidth"]  
     421                                                          - parseInt(x); 
     422                        } 
     423                        else if (xUnits == "fraction") { 
     424                            style["graphicXOffset"] = style["graphicWidth"]  
     425                                                          * parseFloat(x); 
     426                        } 
     427 
     428                        var yUnits = hotSpotNode.getAttribute("yunits"); 
     429                        if (yUnits == "pixels") { 
     430                            style["graphicYOffset"] = parseInt(y); 
     431                        } 
     432                        else if (yUnits == "insetPixels") { 
     433                            style["graphicYOffset"] = style["graphicHeight"]  
     434                                                          - parseInt(y); 
     435                        } 
     436                        else if (yUnits == "fraction") { 
     437                            style["graphicYOffset"] = style["graphicHeight"]  
     438                                                          * parseFloat(y); 
     439                        } 
     440                    } 
     441                    break; 
     442                case "balloonstyle": 
     443                    var balloonStyle = OpenLayers.Util.getXmlNodeValue( 
     444                                            styleTypeNode); 
     445                    if (balloonStyle) { 
     446                        style["balloonStyle"] = balloonStyle.replace( 
     447                                       this.regExes.straightBracket, "${$1}"); 
     448                    } 
     449                    break; 
     450                default: 
     451            } 
     452        } 
     453 
     454        // Some polygons have no line color, so we use the fillColor for that 
     455        if (!style["strokeColor"] && style["fillColor"]) { 
     456            style["strokeColor"] = style["fillColor"]; 
     457        } 
     458 
     459        var id = node.getAttribute("id"); 
     460        if (id && style) { 
     461            style.id = id; 
     462        } 
     463 
     464        return style; 
     465    }, 
     466 
     467    /** 
     468     * Method: parseStyleMaps 
     469     * Looks for <Style> nodes in the data and parses them 
     470     * Also parses <StyleMap> nodes, but only uses the 'normal' key 
     471     *  
     472     * Parameters:  
     473     * nodes    - {Array} of {DOMElement} data to read/parse. 
     474     * options  - {Object} Hash of options 
     475     *  
     476     */ 
     477    parseStyleMaps: function(nodes, options) { 
     478        // Only the default or "normal" part of the StyleMap is processed now 
     479        // To do the select or "highlight" bit, we'd need to change lots more 
     480 
     481        for(var i=0; i < nodes.length; i++) { 
     482            var node = nodes[i]; 
     483            var pairs = this.getElementsByTagNameNS(node, "*",  
     484                            "Pair"); 
     485 
     486            var id = node.getAttribute("id"); 
     487            for (var j=0; j<pairs.length; j++) { 
     488                var pair = pairs[j]; 
     489                // Use the shortcut in the SLD format to quickly retrieve the  
     490                // value of a node. Maybe it's good to have a method in  
     491                // Format.XML to do this 
     492                var key = this.parseProperty(pair, "*", "key"); 
     493                var styleUrl = this.parseProperty(pair, "*", "styleUrl"); 
     494 
     495                if (styleUrl && key == "normal") { 
     496                    this.styles[(options.styleBaseUrl || "") + "#" + id] = 
     497                        this.styles[(options.styleBaseUrl || "") + styleUrl]; 
     498                } 
     499 
     500                if (styleUrl && key == "highlight") { 
     501                    // TODO: implement the "select" part 
     502                } 
     503 
     504            } 
     505        } 
     506 
     507    }, 
     508 
     509 
     510    /** 
     511     * Method: parseFeatures 
     512     * Loop through all Placemark nodes and parse them. 
     513     * Will create a list of features 
     514     *  
     515     * Parameters:  
     516     * nodes    - {Array} of {DOMElement} data to read/parse. 
     517     * options  - {Object} Hash of options 
     518     *  
     519     */ 
     520    parseFeatures: function(nodes, options) { 
     521        var features = new Array(nodes.length); 
     522        for(var i=0; i < nodes.length; i++) { 
     523            var featureNode = nodes[i]; 
     524            var feature = this.parseFeature.apply(this,[featureNode]) ; 
    101525            if(feature) { 
     526 
     527                // Create reference to styleUrl  
     528                if (this.extractStyles && feature.attributes && 
     529                    feature.attributes.styleUrl) { 
     530                    feature.style = this.getStyle(feature.attributes.styleUrl); 
     531                } 
     532 
     533                // Make sure that <Style> nodes within a placemark are  
     534                // processed as well 
     535                var inlineStyleNode = this.getElementsByTagNameNS(featureNode, 
     536                                                       "*", 
     537                                                       "Style")[0]; 
     538                if (inlineStyleNode) { 
     539                    var inlineStyle= this.parseStyle(styleNode); 
     540                    if (inlineStyle) { 
     541                        feature.style = OpenLayers.Util.extend({},  
     542                                            feature.style); 
     543                        OpenLayers.Util.extend(feature.style, inlineStyle); 
     544                    } 
     545                } 
     546 
     547                // add feature to list of features 
    102548                features[i] = feature; 
    103549            } else { 
    104550                throw "Bad Placemark: " + i; 
    105551            } 
    106552        } 
    107         return features; 
     553 
     554        // add new features to existing feature list 
     555        this.features = this.features.concat(features); 
    108556    }, 
    109557 
    110558    /** 
     
    154602        } 
    155603        var feature = new OpenLayers.Feature.Vector(geometry, attributes); 
    156604 
    157         var fid = node.getAttribute("id")
     605        var fid = node.getAttribute("id") || node.getAttribute("name")
    158606        if(fid != null) { 
    159607            feature.fid = fid; 
    160608        } 
     
    163611    },         
    164612     
    165613    /** 
     614     * Method: getStyle 
     615     * Retrieves a style from a style hash using styleUrl as the key 
     616     * If the styleUrl doesn't exist yet, we try to fetch it  
     617     * Internet 
     618     *  
     619     * Parameters:  
     620     * styleUrl  - {String} URL of style 
     621     * options   - {Object} Hash of options  
     622     * 
     623     * Returns: 
     624     * {Object}  - (reference to) Style hash 
     625     */ 
     626    getStyle: function(styleUrl, options) { 
     627 
     628        var styleBaseUrl = OpenLayers.Util.removeTail(styleUrl); 
     629 
     630        var newOptions = OpenLayers.Util.extend({}, options); 
     631        newOptions.depth++; 
     632        newOptions.styleBaseUrl = styleBaseUrl; 
     633 
     634        // Fetch remote Style URLs (if not fetched before)  
     635        if (!this.styles[styleUrl]  
     636                && !OpenLayers.String.startsWith(styleUrl, "#")  
     637                && newOptions.depth <= this.maxDepth 
     638                && !this.fetched[styleBaseUrl] ) { 
     639 
     640            var data = this.fetchLink(styleBaseUrl); 
     641            if (data) { 
     642                this.parseData(data, newOptions); 
     643            } 
     644 
     645        } 
     646 
     647        // return requested style 
     648        var style = this.styles[styleUrl]; 
     649        return style; 
     650    }, 
     651     
     652    /** 
    166653     * Property: parseGeometry 
    167654     * Properties of this object are the functions that parse geometries based 
    168655     *     on their type. 
     
    334821     */ 
    335822    parseAttributes: function(node) { 
    336823        var attributes = {}; 
    337         // assume attribute nodes are type 1 children with a type 3 child 
     824        // assume attribute nodes are type 1 children with a type 3 or 4 child 
    338825        var child, grandchildren, grandchild; 
    339826        var children = node.childNodes; 
    340827        for(var i=0; i<children.length; ++i) { 
    341828            child = children[i]; 
    342829            if(child.nodeType == 1) { 
    343830                grandchildren = child.childNodes; 
    344                 if(grandchildren.length == 1) { 
    345                     grandchild = grandchildren[0]; 
     831                if(grandchildren.length == 1 || grandchildren.length == 3) { 
     832                    var grandchild; 
     833                    switch (grandchildren.length) { 
     834                        case 1: 
     835                            grandchild = grandchildren[0]; 
     836                            break; 
     837                        case 3: 
     838                        default: 
     839                            grandchild = grandchildren[1]; 
     840                            break; 
     841                    } 
    346842                    if(grandchild.nodeType == 3 || grandchild.nodeType == 4) { 
    347843                        var name = (child.prefix) ? 
    348844                                child.nodeName.split(":")[1] : 
    349845                                child.nodeName; 
    350                         var value = grandchild.nodeValue.replace( 
    351                                                 this.regExes.trimSpace, ""); 
    352                         attributes[name] = value; 
     846                        var value = OpenLayers.Util.getXmlNodeValue(grandchild) 
     847                        if (value) { 
     848                            value = value.replace(this.regExes.trimSpace, ""); 
     849                            attributes[name] = value; 
     850                        } 
    353851                    } 
    354                 } 
     852                }  
    355853            } 
    356854        } 
    357855        return attributes; 
    358856    }, 
    359857 
     858     
    360859    /** 
     860     * Method: parseProperty 
     861     * Convenience method to find a node and return its value 
     862     * 
     863     * Parameters: 
     864     * xmlNode    - {<DOMElement>} 
     865     * namespace  - {String} namespace of the node to find 
     866     * tagName    - {String} name of the property to parse 
     867     *  
     868     * Returns: 
     869     * {String} The value for the requested property (defaults to null) 
     870     */     
     871    parseProperty: function(xmlNode, namespace, tagName) { 
     872        var value; 
     873        var nodeList = this.getElementsByTagNameNS(xmlNode, namespace, tagName); 
     874        try { 
     875            value = OpenLayers.Util.getXmlNodeValue(nodeList[0]); 
     876        } catch(e) { 
     877            value = null; 
     878        } 
     879      
     880        return value; 
     881    },                                                               
     882 
     883    /** 
    361884     * APIMethod: write 
    362885     * Accept Feature Collection, and return a string.  
    363886     *  
  • D:/eclipse/workspace/openlayers/examples/KMLParser.html

    old new  
    44    <script src="../lib/OpenLayers.js"></script> 
    55    <script type="text/javascript"> 
    66        function parseData(req) { 
    7           g =  new OpenLayers.Format.KML(); 
     7          g =  new OpenLayers.Format.KML({extractStyles: true}); 
    88          html = "" 
    99          features = g.read(req.responseText); 
    1010          for(var feat in features) { 
    1111            html += "Feature: Geometry: "+ features[feat].geometry+","; 
    1212                html += "<ul>"; 
    1313            for (var j in features[feat].attributes) { 
    14                 html += "<li>"+j+":"+features[feat].attributes[j]+"</li>"; 
     14                html += "<li>Attribute "+j+":"+features[feat].attributes[j]+"</li>"; 
    1515            } 
    1616                html += "</ul>" 
     17                html += "<ul>"; 
     18            for (var j in features[feat].style) { 
     19                html += "<li>Style "+j+":"+features[feat].style[j]+"</li>"; 
     20            } 
     21                html += "</ul>" 
    1722          } 
    1823          document.getElementById('output').innerHTML = html; 
    1924        }