OpenLayers OpenLayers

Ticket #1259: kml_styling_v3.1.diff

File kml_styling_v3.1.diff, 22.5 kB (added by rdewit, 1 year ago)

Same as v3, but with extra parameter 'options' in read() to allow user to set styleBaseUrl

  • 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     
     
    5960    internalns: null, 
    6061 
    6162    /** 
     63     * Property: features 
     64     * {Array} Array of features 
     65     *      
     66     */ 
     67    features: null, 
     68 
     69    /** 
     70     * Property: styles 
     71     * {Object} Storage of style objects 
     72     *      
     73     */ 
     74    styles: null, 
     75 
     76    /** 
     77     * Property: fetched 
     78     * {Object} Storage of KML URLs that have been fetched before 
     79     *     in order to prevent reloading them. 
     80     */ 
     81    fetched: null, 
     82 
     83    /** 
     84     * APIProperty: maxDepth 
     85     * {Integer} Maximum depth for recursive loading external KML URLs  
     86     *           Defaults to 0: do no external fetching 
     87     */ 
     88    maxDepth: 0, 
     89 
     90    /** 
    6291     * Constructor: OpenLayers.Format.KML 
    6392     * Create a new parser for KML. 
    6493     * 
     
    72101            trimSpace: (/^\s*|\s*$/g), 
    73102            removeSpace: (/\s*/g), 
    74103            splitSpace: (/\s+/), 
    75             trimComma: (/\s*,\s*/g) 
     104            trimComma: (/\s*,\s*/g), 
     105            kmlColor: (/(\w{2})(\w{2})(\w{2})(\w{2})/), 
     106            kmlIconPalette: (/root:\/\/icons\/palette-(\d+)(\.\w+)/), 
     107            straightBracket: (/\$\[(.*?)\]/g) 
    76108        }; 
    77109        OpenLayers.Format.XML.prototype.initialize.apply(this, [options]); 
    78110    }, 
     
    82114     * Read data from a string, and return a list of features.  
    83115     *  
    84116     * Parameters:  
    85      * data - {String} or {DOMElement} data to read/parse. 
     117     * data    - {String} or {DOMElement} data to read/parse. 
     118     * options - {Object} Hash of options 
    86119     * 
    87120     * Returns: 
    88121     * {Array(<OpenLayers.Feature.Vector>)} List of features. 
    89122     */ 
    90     read: function(data) { 
     123    read: function(data, options) { 
     124        this.features = this.features || []; 
     125        this.styles   = this.styles   || {}; 
     126        this.fetched  = this.fetched  || {}; 
     127        return this.parseData(data, options); 
     128    }, 
     129 
     130    /** 
     131     * Method: parseData 
     132     * Read data from a string, and return a list of features.  
     133     *  
     134     * Parameters:  
     135     * data    - {String} or {DOMElement} data to read/parse. 
     136     * options - {Object} Hash of options 
     137     * 
     138     * Returns: 
     139     * {Array(<OpenLayers.Feature.Vector>)} List of features. 
     140     */ 
     141    parseData: function(data, options) { 
    91142        if(typeof data == "string") { 
    92143            data = OpenLayers.Format.XML.prototype.read.apply(this, [data]); 
    93144        } 
    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]); 
     145         
     146        // create default options hash if none provided 
     147        if (!options) { 
     148            options = {}; 
     149        } 
     150 
     151        // Set default options  
     152        OpenLayers.Util.applyDefaults(options, { 
     153            depth: 0, 
     154            styleBaseUrl: "" 
     155        }); 
     156 
     157        // Loop throught the following node types in this order and 
     158        // process the nodes found  
     159        var types = ["Link", "NetworkLink", "Style", "StyleMap", "Placemark"]; 
     160        for(var i=0; i<types.length; ++i) { 
     161            var type = types[i]; 
     162 
     163            var nodes = this.getElementsByTagNameNS(data, "*", type); 
     164 
     165            // skip to next type if no nodes are found 
     166            if(nodes.length == 0) {  
     167                continue; 
     168            } 
     169 
     170            switch (type.toLowerCase()) { 
     171 
     172                // Fetch external links  
     173                case "link": 
     174                case "networklink": 
     175                    this.parseLinks(nodes, options); 
     176                    break; 
     177 
     178                // parse style information 
     179                case "style": 
     180                    this.parseStyles(nodes, options); 
     181                    break; 
     182                case "stylemap": 
     183                    this.parseStyleMaps(nodes, options); 
     184                    break; 
     185 
     186                // parse features 
     187                case "placemark": 
     188                    this.parseFeatures(nodes, options); 
     189                    break; 
     190            } 
     191        } 
     192         
     193        return this.features; 
     194    }, 
     195 
     196    /** 
     197     * Method: parseLinks 
     198     * Finds URLs of linked KML documents and fetches them 
     199     *  
     200     * Parameters:  
     201     * nodes   - {Array} of {DOMElement} data to read/parse. 
     202     * options - {Object} Hash of options 
     203     *  
     204     */ 
     205    parseLinks: function(nodes, options) { 
     206         
     207        // Fetch external links <NetworkLink> and <Link> 
     208        // Don't do anything if we have reached our maximum depth for recursion 
     209        if (options.depth >= this.maxDepth) { 
     210            return false; 
     211        } 
     212 
     213        // increase depth 
     214        var newOptions = OpenLayers.Util.extend({}, options); 
     215        newOptions.depth++; 
     216 
     217        for(var i=0; i < nodes.length; i++) { 
     218            var href = this.parseProperty(nodes[i], "*", "href"); 
     219            if(href && !this.fetched[href]) { 
     220                this.fetched[href] = true; // prevent reloading the same urls 
     221                var data = this.fetchLink(href); 
     222                if (data) { 
     223                    this.parseData(data, newOptions); 
     224                } 
     225            }  
     226        } 
     227 
     228    }, 
     229 
     230    /** 
     231     * Method: fetchLink 
     232     * Fetches a URL and returns the result 
     233     *  
     234     * Parameters:  
     235     * href  - {String} url to be fetched 
     236     *  
     237     */ 
     238    fetchLink: function(href) { 
     239 
     240        if (OpenLayers.ProxyHost  
     241                && OpenLayers.String.startsWith(href, "http")) { 
     242            href = OpenLayers.ProxyHost + escape(href); 
     243        } 
     244 
     245        var request = new OpenLayers.Ajax.Request(href,  
     246                      {method: 'get', asynchronous: false }); 
     247 
     248        if (request && request.transport) { 
     249            return request.transport.responseText; 
     250        } 
     251 
     252    }, 
     253 
     254    /** 
     255     * Method: parseStyles 
     256     * Looks for <Style> nodes in the data and parses them 
     257     * Also parses <StyleMap> nodes, but only uses the 'normal' key 
     258     *  
     259     * Parameters:  
     260     * nodes    - {Array} of {DOMElement} data to read/parse. 
     261     * options  - {Object} Hash of options 
     262     *  
     263     */ 
     264    parseStyles: function(nodes, options) { 
     265        for(var i=0; i < nodes.length; i++) { 
     266            var style = this.parseStyle(nodes[i]); 
     267            if(style) { 
     268                styleName = (options.styleBaseUrl || "") + "#" + style.id; 
     269                 
     270                this.styles[styleName] = style; 
     271            } 
     272        } 
     273    }, 
     274 
     275    /** 
     276     * Method: parseStyle 
     277     * Parses the children of a <Style> node and builds the style hash 
     278     * accordingly 
     279     *  
     280     * Parameters:  
     281     * node - {DOMElement} <Style> node 
     282     *  
     283     */ 
     284    parseStyle: function(node) { 
     285        var style = {}; 
     286         
     287        var types = ["LineStyle", "PolyStyle", "IconStyle", "BalloonStyle"]; 
     288        var type, nodeList, geometry, parser; 
     289        for(var i=0; i<types.length; ++i) { 
     290            type = types[i]; 
     291            styleTypeNode = this.getElementsByTagNameNS(node,  
     292                                                   "*", type)[0]; 
     293            if(!styleTypeNode) {  
     294                continue; 
     295            } 
     296 
     297            // only deal with first geometry of this type 
     298            switch (type.toLowerCase()) { 
     299                case "linestyle": 
     300                    var color = this.parseProperty(styleTypeNode, "*", "color"); 
     301                    if (color) { 
     302                        var matches = (color.toString()).match( 
     303                                                         this.regExes.kmlColor); 
     304 
     305                        // transparency 
     306                        var alpha = matches[1]; 
     307                        style["strokeOpacity"] = parseInt(alpha, 16) / 255; 
     308 
     309                        // rgb colors (google uses bgr) 
     310                        var b = matches[2];  
     311                        var g = matches[3];  
     312                        var r = matches[4];  
     313                        style["strokeColor"] = "#" + r + g + b; 
     314                    } 
     315                     
     316                    var width = this.parseProperty(styleTypeNode, "*", "width"); 
     317                    if (width) { 
     318                        style["strokeWidth"] = width; 
     319                    } 
     320 
     321                case "polystyle": 
     322                    var color = this.parseProperty(styleTypeNode, "*", "color"); 
     323                    if (color) { 
     324                        var matches = (color.toString()).match( 
     325                                                         this.regExes.kmlColor); 
     326 
     327                        // transparency 
     328                        var alpha = matches[1]; 
     329                        style["fillOpacity"] = parseInt(alpha, 16) / 255; 
     330 
     331                        // rgb colors (google uses bgr) 
     332                        var b = matches[2];  
     333                        var g = matches[3];  
     334                        var r = matches[4];  
     335                        style["fillColor"] = "#" + r + g + b; 
     336                    } 
     337                     
     338                    break; 
     339                case "iconstyle": 
     340                    var iconNode = this.getElementsByTagNameNS(styleTypeNode,  
     341                                               "*",  
     342                                               "Icon")[0]; 
     343 
     344                    // set default width and height of icon 
     345                    style["graphicWidth"] = 32; 
     346                    style["graphicHeight"] = 32; 
     347 
     348                    if (iconNode) { 
     349                        var href = this.parseProperty(iconNode, "*", "href"); 
     350                        if (href) {                                                    
     351 
     352                            // support for internal icons  
     353                            //    (/root://icons/palette-x.png) 
     354                            // x and y tell the position on the palette: 
     355                            // - in pixels 
     356                            // - starting from the left bottom 
     357                            // We translate that to a position in the list  
     358                            // and request the appropriate icon from the  
     359                            // google maps website 
     360                            var matches = href.match(this.regExes.kmlIconPalette); 
     361                            if (matches)  { 
     362                                var palette = matches[1]; 
     363                                var file_extension = matches[2]; 
     364 
     365                                var x = this.parseProperty(iconNode, "*", "x"); 
     366                                var y = this.parseProperty(iconNode, "*", "y"); 
     367 
     368                                var posX = x ? x/32 : 0; 
     369                                var posY = y ? (7 - y/32) : 7; 
     370 
     371                                var pos = posY * 8 + posX; 
     372                                href = "http://maps.google.com/mapfiles/kml/pal"  
     373                                     + palette + "/icon" + pos + file_extension; 
     374                            } 
     375 
     376 
     377                            var w = this.parseProperty(iconNode, "*", "w"); 
     378                            if (w) { 
     379                                style["graphicWidth"] = parseInt(w); 
     380                            } 
     381 
     382                            var h = this.parseProperty(iconNode, "*", "h"); 
     383                            if (h) { 
     384                                style["graphicHeight"] = parseInt(h); 
     385                            } 
     386 
     387                            style["graphicOpacity"] = 1; // fully opaque 
     388                            style["externalGraphic"] = href; 
     389                        } 
     390 
     391                    } 
     392 
     393 
     394                    // hotSpots define the offset for an Icon 
     395                    var hotSpotNode = this.getElementsByTagNameNS(styleTypeNode,  
     396                                               "*",  
     397                                               "hotSpot")[0]; 
     398                    if (hotSpotNode) { 
     399                        var x = hotSpotNode.getAttribute("x"); 
     400                        var y = hotSpotNode.getAttribute("y"); 
     401 
     402                        var xUnits = hotSpotNode.getAttribute("xunits"); 
     403                        if (xUnits == "pixels") { 
     404                            style["graphicXOffset"] = parseInt(x); 
     405                        } 
     406                        else if (xUnits == "insetPixels") { 
     407                            style["graphicXOffset"] = style["graphicWidth"]  
     408                                                          - parseInt(x); 
     409                        } 
     410                        else if (xUnits == "fraction") { 
     411                            style["graphicXOffset"] = style["graphicWidth"]  
     412                                                          * parseFloat(x); 
     413                        } 
     414 
     415                        var yUnits = hotSpotNode.getAttribute("yunits"); 
     416                        if (yUnits == "pixels") { 
     417                            style["graphicYOffset"] = parseInt(y); 
     418                        } 
     419                        else if (yUnits == "insetPixels") { 
     420                            style["graphicYOffset"] = style["graphicHeight"]  
     421                                                          - parseInt(y); 
     422                        } 
     423                        else if (yUnits == "fraction") { 
     424                            style["graphicYOffset"] = style["graphicHeight"]  
     425                                                          * parseFloat(y); 
     426                        } 
     427                    } 
     428                    break; 
     429                case "balloonstyle": 
     430                    var balloonStyle = OpenLayers.Util.getXmlNodeValue( 
     431                                            styleTypeNode); 
     432                    if (balloonStyle) { 
     433                        style["balloonStyle"] = balloonStyle.replace( 
     434                                       this.regExes.straightBracket, "${$1}"); 
     435                    } 
     436                    break; 
     437                default: 
     438            } 
     439        } 
     440 
     441        // Some polygons have no line color, so we use the fillColor for that 
     442        if (!style["strokeColor"] && style["fillColor"]) { 
     443            style["strokeColor"] = style["fillColor"]; 
     444        } 
     445 
     446        var id = node.getAttribute("id"); 
     447        if (id && style) { 
     448            style.id = id; 
     449        } 
     450 
     451        // Apply default styles 
     452        OpenLayers.Util.applyDefaults(style,  
     453                           OpenLayers.Feature.Vector.style["default"]); 
     454                  
     455        return style; 
     456    }, 
     457 
     458    /** 
     459     * Method: parseStyleMaps 
     460     * Looks for <Style> nodes in the data and parses them 
     461     * Also parses <StyleMap> nodes, but only uses the 'normal' key 
     462     *  
     463     * Parameters:  
     464     * nodes    - {Array} of {DOMElement} data to read/parse. 
     465     * options  - {Object} Hash of options 
     466     *  
     467     */ 
     468    parseStyleMaps: function(nodes, options) { 
     469        // Only the default or "normal" part of the StyleMap is processed now 
     470        // To do the select or "highlight" bit, we'd need to change lots more 
     471 
     472        for(var i=0; i < nodes.length; i++) { 
     473            var node = nodes[i]; 
     474            var pairs = this.getElementsByTagNameNS(node, "*",  
     475                            "Pair"); 
     476 
     477            var id = node.getAttribute("id"); 
     478            for (var j=0; j<pairs.length; j++) { 
     479                var pair = pairs[j]; 
     480                // Use the shortcut in the SLD format to quickly retrieve the  
     481                // value of a node. Maybe it's good to have a method in  
     482                // Format.XML to do this 
     483                var key = this.parseProperty(pair, "*", "key"); 
     484                var styleUrl = this.parseProperty(pair, "*", "styleUrl"); 
     485 
     486                if (styleUrl && key == "normal") { 
     487                    this.styles[(options.styleBaseUrl || "") + "#" + id] = 
     488                        this.styles[(options.styleBaseUrl || "") + styleUrl]; 
     489                } 
     490 
     491                if (styleUrl && key == "highlight") { 
     492                    // TODO: implement the "select" part 
     493                } 
     494 
     495            } 
     496        } 
     497 
     498    }, 
     499 
     500 
     501    /** 
     502     * Method: parseFeatures 
     503     * Loop through all Placemark nodes and parse them. 
     504     * Will create a list of features 
     505     *  
     506     * Parameters:  
     507     * nodes    - {Array} of {DOMElement} data to read/parse. 
     508     * options  - {Object} Hash of options 
     509     *  
     510     */ 
     511    parseFeatures: function(nodes, options) { 
     512        var features = new Array(nodes.length); 
     513        for(var i=0; i < nodes.length; i++) { 
     514            var featureNode = nodes[i]; 
     515            var feature = this.parseFeature.apply(this,[featureNode]) ; 
    101516            if(feature) { 
     517 
     518                // Create reference to styleUrl  
     519                if (feature.attributes && feature.attributes.styleUrl) { 
     520                    feature.style = this.getStyle(feature.attributes.styleUrl); 
     521                } 
     522 
     523                // Make sure that <Style> nodes within a placemark are  
     524                // processed as well 
     525                var inlineStyleNode = this.getElementsByTagNameNS(featureNode, 
     526                                                       "*", 
     527                                                       "Style")[0]; 
     528                if (inlineStyleNode) { 
     529                    var inlineStyle= this.parseStyle(styleNode); 
     530                    if (inlineStyle) { 
     531                        feature.style = OpenLayers.Util.extend({},  
     532                                            feature.style); 
     533                        OpenLayers.Util.extend(feature.style, inlineStyle); 
     534                    } 
     535                } 
     536 
     537                // add feature to list of features 
    102538                features[i] = feature; 
    103539            } else { 
    104540                throw "Bad Placemark: " + i; 
    105541            } 
    106542        } 
    107         return features; 
     543 
     544        // add new features to existing feature list 
     545        this.features = this.features.concat(features); 
    108546    }, 
    109547 
    110548    /** 
     
    154592        } 
    155593        var feature = new OpenLayers.Feature.Vector(geometry, attributes); 
    156594 
    157         var fid = node.getAttribute("id")
     595        var fid = node.getAttribute("id") || node.getAttribute("name")
    158596        if(fid != null) { 
    159597            feature.fid = fid; 
    160598        } 
     
    163601    },         
    164602     
    165603    /** 
     604     * Method: getStyle 
     605     * Retrieves a style from a style hash using styleUrl as the key 
     606     * If the styleUrl doesn't exist yet, we try to fetch it  
     607     * Internet 
     608     *  
     609     * Parameters:  
     610     * styleUrl  - {String} URL of style 
     611     * options   - {Object} Hash of options  
     612     * 
     613     * Returns: 
     614     * {Object}  - (reference to) Style hash 
     615     */ 
     616    getStyle: function(styleUrl, options) { 
     617 
     618        var styleBaseUrl = OpenLayers.Util.removeTail(styleUrl); 
     619 
     620        var newOptions = OpenLayers.Util.extend({}, options); 
     621        newOptions.depth++; 
     622        newOptions.styleBaseUrl = styleBaseUrl; 
     623 
     624        // Fetch remote Style URLs (if not fetched before)  
     625        if (!this.styles[styleUrl]  
     626                && !OpenLayers.String.startsWith(styleUrl, "#")  
     627                && newOptions.depth <= this.maxDepth 
     628                && !this.fetched[styleBaseUrl] ) { 
     629 
     630            var data = this.fetchLink(styleBaseUrl); 
     631            if (data) { 
     632                this.parseData(data, newOptions); 
     633            } 
     634 
     635        } 
     636 
     637        // return requested style 
     638        var style = this.styles[styleUrl]; 
     639        return style; 
     640    }, 
     641     
     642    /** 
    166643     * Property: parseGeometry 
    167644     * Properties of this object are the functions that parse geometries based 
    168645     *     on their type. 
     
    334811     */ 
    335812    parseAttributes: function(node) { 
    336813        var attributes = {}; 
    337         // assume attribute nodes are type 1 children with a type 3 child 
     814        // assume attribute nodes are type 1 children with a type 3 or 4 child 
    338815        var child, grandchildren, grandchild; 
    339816        var children = node.childNodes; 
    340817        for(var i=0; i<children.length; ++i) { 
    341818            child = children[i]; 
    342819            if(child.nodeType == 1) { 
    343820                grandchildren = child.childNodes; 
    344                 if(grandchildren.length == 1) { 
    345                     grandchild = grandchildren[0]; 
     821                if(grandchildren.length == 1 || grandchildren.length == 3) { 
     822                    var grandchild; 
     823                    switch (grandchildren.length) { 
     824                        case 1: 
     825                            grandchild = grandchildren[0]; 
     826                            break; 
     827                        case 3: 
     828                        default: 
     829                            grandchild = grandchildren[1]; 
     830                            break; 
     831                    } 
    346832                    if(grandchild.nodeType == 3 || grandchild.nodeType == 4) { 
    347833                        var name = (child.prefix) ? 
    348834                                child.nodeName.split(":")[1] : 
    349835                                child.nodeName; 
    350                         var value = grandchild.nodeValue.replace( 
    351                                                 this.regExes.trimSpace, ""); 
    352                         attributes[name] = value; 
     836                        var value = OpenLayers.Util.getXmlNodeValue(grandchild) 
     837                        if (value) { 
     838                            value = value.replace(this.regExes.trimSpace, ""); 
     839                            attributes[name] = value; 
     840                        } 
    353841                    } 
    354                 } 
     842                }  
    355843            } 
    356844        } 
    357845        return attributes; 
    358846    }, 
    359847 
     848     
    360849    /** 
     850     * Method: parseProperty 
     851     * Convenience method to find a node and return its value 
     852     * 
     853     * Parameters: 
     854     * xmlNode    - {<DOMElement>} 
     855     * namespace  - {String} namespace of the node to find 
     856     * tagName    - {String} name of the property to parse 
     857     *  
     858     * Returns: 
     859     * {String} The value for the requested property (defaults to null) 
     860     */     
     861    parseProperty: function(xmlNode, namespace, tagName) { 
     862        var value; 
     863        var nodeList = this.getElementsByTagNameNS(xmlNode, namespace, tagName); 
     864        try { 
     865            value = OpenLayers.Util.getXmlNodeValue(nodeList[0]); 
     866        } catch(e) { 
     867            value = null; 
     868        } 
     869      
     870        return value; 
     871    },                                                               
     872 
     873    /** 
    361874     * APIMethod: write 
    362875     * Accept Feature Collection, and return a string.  
    363876     *