~dongpo-deng/sahana-eden/test

« back to all changes in this revision

Viewing changes to static/scripts/gis/geoext/lib/GeoExt/data/PrintProvider.js

  • Committer: Deng Dongpo
  • Date: 2010-08-01 09:29:44 UTC
  • Revision ID: dongpo@dhcp-21193.iis.sinica.edu.tw-20100801092944-8t9obt4xtl7otesb
initial

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/**
 
2
 * Copyright (c) 2008-2010 The Open Source Geospatial Foundation
 
3
 * 
 
4
 * Published under the BSD license.
 
5
 * See http://svn.geoext.org/core/trunk/geoext/license.txt for the full text
 
6
 * of the license.
 
7
 */
 
8
Ext.namespace("GeoExt.data");
 
9
 
 
10
/** api: (define)
 
11
 *  module = GeoExt.data
 
12
 *  class = PrintProvider
 
13
 *  base_link = `Ext.util.Observable <http://extjs.com/deploy/dev/docs/?class=Ext.util.Observable>`_
 
14
 */
 
15
 
 
16
/** api: example
 
17
 *  Minimal code to print as much of the current map extent as possible as
 
18
 *  soon as the print service capabilities are loaded, using the first layout
 
19
 *  reported by the print service:
 
20
 * 
 
21
 *  .. code-block:: javascript
 
22
 *     
 
23
 *      var mapPanel = new GeoExt.MapPanel({
 
24
 *          renderTo: "mappanel",
 
25
 *          layers: [new OpenLayers.Layer.WMS("wms", "/geoserver/wms",
 
26
 *              {layers: "topp:tasmania_state_boundaries"})],
 
27
 *          center: [146.56, -41.56],
 
28
 *          zoom: 7
 
29
 *      });
 
30
 *      var printProvider = new GeoExt.data.PrintProvider({
 
31
 *          url: "/geoserver/pdf",
 
32
 *          listeners: {
 
33
 *              "loadcapabilities": function() {
 
34
 *                  var printPage = new GeoExt.data.PrintPage({
 
35
 *                      printProvider: printProvider
 
36
 *                  });
 
37
 *                  printPage.fit(mapPanel, true);
 
38
 *                  printProvider.print(mapPanel, printPage);
 
39
 *              }
 
40
 *          }
 
41
 *      });
 
42
 */
 
43
 
 
44
/** api: constructor
 
45
 *  .. class:: PrintProvider
 
46
 * 
 
47
 *  Provides an interface to a Mapfish or GeoServer print module. For printing,
 
48
 *  one or more instances of :class:`GeoExt.data.PrintPage` are also required
 
49
 *  to tell the PrintProvider about the scale and extent (and optionally
 
50
 *  rotation) of the page(s) we want to print. 
 
51
 */
 
52
GeoExt.data.PrintProvider = Ext.extend(Ext.util.Observable, {
 
53
    
 
54
    /** api: config[url]
 
55
     *  ``String`` Base url of the print service. Only required if
 
56
     *  ``capabilities`` is not provided. This
 
57
     *  is usually something like http://path/to/mapfish/print for Mapfish,
 
58
     *  and http://path/to/geoserver/pdf for GeoServer with the printing
 
59
     *  extension installed. This property requires that the print service is
 
60
     *  at the same origin as the application (or accessible via proxy).
 
61
     */
 
62
    
 
63
    /** private:  property[url]
 
64
     *  ``String`` Base url of the print service. Will always have a trailing
 
65
     *  "/".
 
66
     */
 
67
    url: null,
 
68
    
 
69
    /** api: config[capabilities]
 
70
     *  ``Object`` Capabilities of the print service. Only required if ``url``
 
71
     *  is not provided. This is the object returned by the ``info.json``
 
72
     *  endpoint of the print service, and is usually obtained by including a
 
73
     *  script tag pointing to
 
74
     *  http://path/to/printservice/info.json?var=myvar in the head of the
 
75
     *  html document, making the capabilities accessible as ``window.myvar``.
 
76
     *  This property should be used when no local print service or proxy is
 
77
     *  available, or when you do not listen for the ``loadcapabilities``
 
78
     *  events before creating components that require the PrintProvider's
 
79
     *  capabilities to be available.
 
80
     */
 
81
    
 
82
    /** private: property[capabilities]
 
83
     *  ``Object`` Capabilities as returned from the print service.
 
84
     */
 
85
    capabilities: null,
 
86
    
 
87
    /** api: config[method]
 
88
     *  ``String`` Either ``POST`` or ``GET`` (case-sensitive). Method to use
 
89
     *  when sending print requests to the servlet. If the print service is at
 
90
     *  the same origin as the application (or accessible via proxy), then
 
91
     *  ``POST`` is recommended. Use ``GET`` when accessing a remote print
 
92
     *  service with no proxy available, but expect issues with character
 
93
     *  encoding and URLs exceeding the maximum length. Default is ``POST``.
 
94
     */
 
95
    
 
96
    /** private: property[method]
 
97
     *  ``String`` Either ``POST`` or ``GET`` (case-sensitive). Method to use
 
98
     *  when sending print requests to the servlet.
 
99
     */
 
100
    method: "POST",
 
101
 
 
102
    /** api: config[customParams]
 
103
     *  ``Object`` Key-value pairs of custom data to be sent to the print
 
104
     *  service. Optional. This is e.g. useful for complex layout definitions
 
105
     *  on the server side that require additional parameters.
 
106
     */
 
107
    
 
108
    /** api: property[customParams]
 
109
     *  ``Object`` Key-value pairs of custom data to be sent to the print
 
110
     *  service. Optional. This is e.g. useful for complex layout definitions
 
111
     *  on the server side that require additional parameters.
 
112
     */
 
113
    customParams: null,
 
114
    
 
115
    /** api: property[scales]
 
116
     *  ``Ext.data.JsonStore`` read-only. A store representing the scales
 
117
     *  available.
 
118
     *  
 
119
     *  Fields of records in this store:
 
120
     *  
 
121
     *  * name - ``String`` the name of the scale
 
122
     *  * value - ``Float`` the scale denominator
 
123
     */
 
124
    scales: null,
 
125
    
 
126
    /** api: property[dpis]
 
127
     *  ``Ext.data.JsonStore`` read-only. A store representing the dpis
 
128
     *  available.
 
129
     *  
 
130
     *  Fields of records in this store:
 
131
     *  
 
132
     *  * name - ``String`` the name of the dpi
 
133
     *  * value - ``Float`` the dots per inch
 
134
     */
 
135
    dpis: null,
 
136
        
 
137
    /** api: property[layouts]
 
138
     *  ``Ext.data.JsonStore`` read-only. A store representing the layouts
 
139
     *  available.
 
140
     *  
 
141
     *  Fields of records in this store:
 
142
     *  
 
143
     *  * name - ``String`` the name of the layout
 
144
     *  * size - ``Object`` width and height of the map in points
 
145
     *  * rotation - ``Boolean`` indicates if rotation is supported
 
146
     */
 
147
    layouts: null,
 
148
    
 
149
    /** api: property[dpi]
 
150
     *  ``Ext.data.Record`` the record for the currently used resolution.
 
151
     *  Read-only, use ``setDpi`` to set the value.
 
152
     */
 
153
    dpi: null,
 
154
 
 
155
    /** api: property[layout]
 
156
     *  ``Ext.data.Record`` the record of the currently used layout. Read-only,
 
157
     *  use ``setLayout`` to set the value.
 
158
     */
 
159
    layout: null,
 
160
 
 
161
    /** private:  method[constructor]
 
162
     *  Private constructor override.
 
163
     */
 
164
    constructor: function(config) {
 
165
        this.initialConfig = config;
 
166
        Ext.apply(this, config);
 
167
        
 
168
        if(!this.customParams) {
 
169
            this.customParams = {};
 
170
        }
 
171
 
 
172
        this.addEvents(
 
173
            /** api: events[loadcapabilities]
 
174
             *  Triggered when the capabilities have finished loading. This
 
175
             *  event will only fire when ``capabilities`` is not  configured.
 
176
             *  
 
177
             *  Listener arguments:
 
178
             *  * printProvider - :class:`GeoExt.data.PrintProvider` this
 
179
             *    PrintProvider
 
180
             *  * capabilities - ``Object`` the capabilities
 
181
             */
 
182
            "loadcapabilities",
 
183
            
 
184
            /** api: events[layoutchange]
 
185
             *  Triggered when the layout is changed.
 
186
             *  
 
187
             *  Listener arguments:
 
188
             *  * printProvider - :class:`GeoExt.data.PrintProvider` this
 
189
             *    PrintProvider
 
190
             *  * layout - ``Ext.data.Record`` the new layout
 
191
             */
 
192
            "layoutchange",
 
193
 
 
194
            /** api: events[dpichange]
 
195
             *  Triggered when the dpi value is changed.
 
196
             *  
 
197
             *  Listener arguments:
 
198
             *  * printProvider - :class:`GeoExt.data.PrintProvider` this
 
199
             *    PrintProvider
 
200
             *  * dpi - ``Ext.data.Record`` the new dpi record
 
201
             */
 
202
            "dpichange",
 
203
            
 
204
            /** api: events[beforeprint]
 
205
             *  Triggered when the print method is called.
 
206
             *  
 
207
             *  Listener arguments:
 
208
             *  * printProvider - :class:`GeoExt.data.PrintProvider` this
 
209
             *    PrintProvider
 
210
             *  * map - ``OpenLayers.Map`` the map being printed
 
211
             *  * pages - Array of :class:`GeoExt.data.PrintPage` the print
 
212
             *    pages being printed
 
213
             *  * options - ``Object`` the options to the print command
 
214
             */
 
215
            "beforeprint",
 
216
            
 
217
            /** api: events[print]
 
218
             *  Triggered when the print document is opened.
 
219
             *  
 
220
             *  Listener arguments:
 
221
             *  * printProvider - :class:`GeoExt.data.PrintProvider` this
 
222
             *    PrintProvider
 
223
             *  * url - ``String`` the url of the print document
 
224
             */
 
225
            "print",
 
226
 
 
227
            /** api: events[printexception]
 
228
             *  Triggered when using the ``POST`` method, when the print
 
229
             *  backend returns an exception.
 
230
             *  
 
231
             *  Listener arguments:
 
232
             *  * printProvider - :class:`GeoExt.data.PrintProvider` this
 
233
             *    PrintProvider
 
234
             *  * response - ``Object`` the response object of the XHR
 
235
             */
 
236
            "printexception",
 
237
 
 
238
            /** api: events[beforeencodelayer]
 
239
             *  Triggered before a layer is encoded. This can be used to
 
240
             *  exclude layers from the printing, by having the listener
 
241
             *  return false.
 
242
             *
 
243
             *  Listener arguments:
 
244
             *  * printProvider - :class:`GeoExt.data.PrintProvider` this
 
245
             *    PrintProvider
 
246
             *  * layer - ``OpenLayers.Layer`` the layer which is about to be 
 
247
             *    encoded.
 
248
             */
 
249
            "beforeencodelayer",
 
250
            
 
251
            /** api: events[encodelayer]
 
252
             *  Triggered when a layer is encoded. This can be used to modify
 
253
             *  the encoded low-level layer object that will be sent to the
 
254
             *  print service.
 
255
             *  
 
256
             *  Listener arguments:
 
257
             *  * printProvider - :class:`GeoExt.data.PrintProvider` this
 
258
             *    PrintProvider
 
259
             *  * layer - ``OpenLayers.Layer`` the layer which is about to be 
 
260
             *    encoded.
 
261
             *  * encodedLayer - ``Object`` the encoded layer that will be
 
262
             *    sent to the print service.
 
263
             */
 
264
            "encodelayer"
 
265
 
 
266
        );
 
267
        
 
268
        GeoExt.data.PrintProvider.superclass.constructor.apply(this, arguments);
 
269
 
 
270
        this.scales = new Ext.data.JsonStore({
 
271
            root: "scales",
 
272
            sortInfo: {field: "value", direction: "DESC"},
 
273
            fields: ["name", {name: "value", type: "float"}]
 
274
        });
 
275
        
 
276
        this.dpis = new Ext.data.JsonStore({
 
277
            root: "dpis",
 
278
            fields: ["name", {name: "value", type: "float"}]
 
279
        });
 
280
        
 
281
        this.layouts = new Ext.data.JsonStore({
 
282
            root: "layouts",
 
283
            fields: [
 
284
                "name",
 
285
                {name: "size", mapping: "map"},
 
286
                {name: "rotation", type: "boolean"}
 
287
            ]
 
288
        });
 
289
        
 
290
        if(config.capabilities) {
 
291
            this.loadStores();
 
292
        } else {
 
293
            if(this.url.split("/").pop()) {
 
294
                this.url += "/";            
 
295
            }
 
296
            this.loadCapabilities();
 
297
        }
 
298
    },
 
299
    
 
300
    /** api: method[setLayout]
 
301
     *  :param layout: ``Ext.data.Record`` the record of the layout.
 
302
     *  
 
303
     *  Sets the layout for this printProvider.
 
304
     */
 
305
    setLayout: function(layout) {
 
306
        this.layout = layout;
 
307
        this.fireEvent("layoutchange", this, layout);
 
308
    },
 
309
    
 
310
    /** api: method[setDpi]
 
311
     *  :param dpi: ``Ext.data.Record`` the dpi record.
 
312
     *  
 
313
     *  Sets the dpi for this printProvider.
 
314
     */
 
315
    setDpi: function(dpi) {
 
316
        this.dpi = dpi;
 
317
        this.fireEvent("dpichange", this, dpi);
 
318
    },
 
319
 
 
320
    /** api: method[print]
 
321
     *  :param map: ``GeoExt.MapPanel`` or ``OpenLayers.Map`` The map to print.
 
322
     *  :param pages: ``Array`` of :class:`GeoExt.data.PrintPage` or
 
323
     *      :class:`GeoExt.data.PrintPage` page(s) to print.
 
324
     *  :param options: ``Object`` of additional options, see below.
 
325
     *  
 
326
     *  Sends the print command to the print service and opens a new window
 
327
     *  with the resulting PDF.
 
328
     *  
 
329
     *  Valid properties for the ``options`` argument:
 
330
     *
 
331
     *      * ``legend`` - :class:`GeoExt.LegendPanel` If provided, the legend
 
332
     *        will be added to the print document. For the printed result to
 
333
     *        look like the LegendPanel, the following settings in the
 
334
     *        ``!legends`` block of the print module are recommended:
 
335
     *        
 
336
     *        .. code-block:: none
 
337
     *        
 
338
     *          maxIconWidth: 0
 
339
     *          maxIconHeight: 0
 
340
     *          classIndentation: 0
 
341
     *
 
342
     *      * ``overview`` - :class:`OpenLayers.Control.OverviewMap` If provided,
 
343
     *        the layers for the overview map in the printout will be taken from
 
344
     *        the OverviewMap control. If not provided, the print service will
 
345
     *        use the main map's layers for the overview map. Applies only for
 
346
     *        layouts configured to print an overview map.
 
347
     */
 
348
    print: function(map, pages, options) {
 
349
        if(map instanceof GeoExt.MapPanel) {
 
350
            map = map.map;
 
351
        }
 
352
        pages = pages instanceof Array ? pages : [pages];
 
353
        options = options || {};
 
354
        if(this.fireEvent("beforeprint", this, map, pages, options) === false) {
 
355
            return;
 
356
        }
 
357
 
 
358
        var jsonData = Ext.apply({
 
359
            units: map.getUnits(),
 
360
            srs: map.baseLayer.projection.getCode(),
 
361
            layout: this.layout.get("name"),
 
362
            dpi: this.dpi.get("value")
 
363
        }, this.customParams);
 
364
 
 
365
        var pagesLayer = pages[0].feature.layer;
 
366
        var encodedLayers = [];
 
367
        Ext.each(map.layers, function(layer){
 
368
            if(layer !== pagesLayer && layer.getVisibility() === true) {
 
369
                var enc = this.encodeLayer(layer);
 
370
                enc && encodedLayers.push(enc);
 
371
            }
 
372
        }, this);
 
373
        jsonData.layers = encodedLayers;
 
374
        
 
375
        var encodedPages = [];
 
376
        Ext.each(pages, function(page) {
 
377
            encodedPages.push(Ext.apply({
 
378
                center: [page.center.lon, page.center.lat],
 
379
                scale: page.scale.get("value"),
 
380
                rotation: page.rotation
 
381
            }, page.customParams));
 
382
        }, this);
 
383
        jsonData.pages = encodedPages;
 
384
        
 
385
        if (options.overview) {
 
386
            var encodedOverviewLayers = [];
 
387
            Ext.each(options.overview.layers, function(layer) {
 
388
                var enc = this.encodeLayer(layer);
 
389
                enc && encodedOverviewLayers.push(enc);
 
390
            }, this);
 
391
            jsonData.overviewLayers = encodedOverviewLayers;
 
392
        }
 
393
 
 
394
        if(options.legend) {
 
395
            var encodedLegends = [];
 
396
            options.legend.items.each(function(cmp) {
 
397
                if(!cmp.hidden) {
 
398
                    var encFn = this.encoders.legends[cmp.getXType()];
 
399
                    encodedLegends = encodedLegends.concat(
 
400
                        encFn.call(this, cmp));
 
401
                }
 
402
            }, this);
 
403
            jsonData.legends = encodedLegends;
 
404
        }
 
405
 
 
406
        if(this.method === "GET") {
 
407
            var url = this.capabilities.printURL + "?spec=" +
 
408
                encodeURIComponent(Ext.encode(jsonData));
 
409
            window.open(url);
 
410
            this.fireEvent("print", this, url);
 
411
        } else {
 
412
            Ext.Ajax.request({
 
413
                url: this.capabilities.createURL,
 
414
                jsonData: jsonData,
 
415
                success: function(response) {
 
416
                    // In IE, using a Content-disposition: attachment header
 
417
                    // may make it hard or impossible to download the pdf due
 
418
                    // to security settings. So we'll display the pdf inline.
 
419
                    var url = Ext.decode(response.responseText).getURL +
 
420
                        (Ext.isIE ? "?inline=true" : "");
 
421
                    if(Ext.isOpera || Ext.isIE) {
 
422
                        // Make sure that Opera and IE don't replace the
 
423
                        // content tab with the pdf
 
424
                        window.open(url);
 
425
                    } else {
 
426
                        // This avoids popup blockers for all other browers
 
427
                        window.location.href = url;                        
 
428
                    } 
 
429
                    this.fireEvent("print", this, url);
 
430
                },
 
431
                failure: function(response) {
 
432
                    this.fireEvent("printexception", this, response);
 
433
                },
 
434
                scope: this
 
435
            });
 
436
        }
 
437
    },
 
438
    
 
439
    /** private: method[loadCapabilities]
 
440
     */
 
441
    loadCapabilities: function() {
 
442
        var url = this.url + "info.json";
 
443
        Ext.Ajax.request({
 
444
            url: url,
 
445
            disableCaching: false,
 
446
            success: function(response) {
 
447
                this.capabilities = Ext.decode(response.responseText);
 
448
                this.loadStores();
 
449
            },
 
450
            scope: this
 
451
        });
 
452
    },
 
453
    
 
454
    /** private: method[loadStores]
 
455
     */
 
456
    loadStores: function() {
 
457
        this.scales.loadData(this.capabilities);
 
458
        this.dpis.loadData(this.capabilities);
 
459
        this.layouts.loadData(this.capabilities);
 
460
        
 
461
        this.setLayout(this.layouts.getAt(0));
 
462
        this.setDpi(this.dpis.getAt(0));
 
463
        this.fireEvent("loadcapabilities", this, this.capabilities);
 
464
    },
 
465
        
 
466
    /** private: method[encodeLayer]
 
467
     *  :param layer: ``OpenLayers.Layer``
 
468
     *  :return: ``Object``
 
469
     * 
 
470
     *  Encodes a layer for the print service.
 
471
     */
 
472
    encodeLayer: function(layer) {
 
473
        var encLayer;
 
474
        for(var c in this.encoders.layers) {
 
475
            if(OpenLayers.Layer[c] && layer instanceof OpenLayers.Layer[c]) {
 
476
                if(this.fireEvent("beforeencodelayer", this, layer) === false) {
 
477
                    return;
 
478
                }
 
479
                encLayer = this.encoders.layers[c].call(this, layer);
 
480
                this.fireEvent("encodelayer", this, layer, encLayer);
 
481
                break;
 
482
            }
 
483
        }
 
484
        // only return the encLayer object when we have a type. Prevents a
 
485
        // fallback on base encoders like HTTPRequest.
 
486
        return (encLayer && encLayer.type) ? encLayer : null;
 
487
    },
 
488
 
 
489
    /** private: method[getAbsoluteUrl]
 
490
     *  :param url: ``String``
 
491
     *  :return: ``String``
 
492
     *  
 
493
     *  Converts the provided url to an absolute url.
 
494
     */
 
495
    getAbsoluteUrl: function(url) {
 
496
        var a;
 
497
        if(Ext.isIE) {
 
498
            a = document.createElement("<a href='" + url + "'/>");
 
499
            a.style.display = "none";
 
500
            document.body.appendChild(a);
 
501
            a.href = a.href;
 
502
            document.body.removeChild(a);
 
503
        } else {
 
504
            a = document.createElement("a");
 
505
            a.href = url;
 
506
        }
 
507
        return a.href;
 
508
    },
 
509
    
 
510
    /** private: property[encoders]
 
511
     *  ``Object`` Encoders for all print content
 
512
     */
 
513
    encoders: {
 
514
        "layers": {
 
515
            "WMS": function(layer) {
 
516
                var enc = this.encoders.layers.HTTPRequest.call(this, layer);
 
517
                Ext.apply(enc, {
 
518
                    type: 'WMS',
 
519
                    layers: [layer.params.LAYERS].join(",").split(","),
 
520
                    format: layer.params.FORMAT,
 
521
                    styles: [layer.params.STYLES].join(",").split(",")
 
522
                });
 
523
                var param;
 
524
                for(var p in layer.params) {
 
525
                    param = p.toLowerCase();
 
526
                    if(!layer.DEFAULT_PARAMS[param] &&
 
527
                    "layers,styles,width,height,srs".indexOf(param) == -1) {
 
528
                        if(!enc.customParams) {
 
529
                            enc.customParams = {};
 
530
                        }
 
531
                        enc.customParams[p] = layer.params[p];
 
532
                    }
 
533
                }
 
534
                return enc;
 
535
            },
 
536
            "OSM": function(layer) {
 
537
                var enc = this.encoders.layers.TileCache.call(this, layer);
 
538
                return Ext.apply(enc, {
 
539
                    type: 'OSM',
 
540
                    baseURL: enc.baseURL.substr(0, enc.baseURL.indexOf("$")),
 
541
                    extension: "png"
 
542
                });
 
543
            },
 
544
            "TMS": function(layer) {
 
545
                var enc = this.encoders.layers.TileCache.call(this, layer);
 
546
                return Ext.apply(enc, {
 
547
                    type: 'TMS',
 
548
                    format: layer.type
 
549
                });
 
550
            },
 
551
            "TileCache": function(layer) {
 
552
                var enc = this.encoders.layers.HTTPRequest.call(this, layer);
 
553
                return Ext.apply(enc, {
 
554
                    type: 'TileCache',
 
555
                    layer: layer.layername,
 
556
                    maxExtent: layer.maxExtent.toArray(),
 
557
                    tileSize: [layer.tileSize.w, layer.tileSize.h],
 
558
                    extension: layer.extension,
 
559
                    resolutions: layer.serverResolutions || layer.resolutions
 
560
                });
 
561
            },
 
562
            "HTTPRequest": function(layer) {
 
563
                return {
 
564
                    baseURL: this.getAbsoluteUrl(layer.url instanceof Array ?
 
565
                        layer.url[0] : layer.url),
 
566
                    opacity: (layer.opacity != null) ? layer.opacity : 1.0,
 
567
                    singleTile: layer.singleTile
 
568
                };
 
569
            },
 
570
            "Image": function(layer) {
 
571
                return {
 
572
                    type: 'Image',
 
573
                    baseURL: this.getAbsoluteUrl(layer.getURL(layer.extent)),
 
574
                    opacity: (layer.opacity != null) ? layer.opacity : 1.0,
 
575
                    extent: layer.extent.toArray(),
 
576
                    pixelSize: [layer.size.w, layer.size.h],
 
577
                    name: layer.name
 
578
                };
 
579
            },
 
580
            "Vector": function(layer) {
 
581
                if(!layer.features.length) {
 
582
                    return;
 
583
                }
 
584
                
 
585
                var encFeatures = [];
 
586
                var encStyles = {};
 
587
                var features = layer.features;
 
588
                var featureFormat = new OpenLayers.Format.GeoJSON();
 
589
                var styleFormat = new OpenLayers.Format.JSON();
 
590
                var nextId = 1;
 
591
                var styleDict = {};
 
592
                var feature, style, dictKey, dictItem, styleName;
 
593
                for(var i=0, len=features.length; i<len; ++i) {
 
594
                    feature = features[i];
 
595
                    style = feature.style || layer.style ||
 
596
                    layer.styleMap.createSymbolizer(feature,
 
597
                        feature.renderIntent);
 
598
                    dictKey = styleFormat.write(style);
 
599
                    dictItem = styleDict[dictKey];
 
600
                    if(dictItem) {
 
601
                        //this style is already known
 
602
                        styleName = dictItem;
 
603
                    } else {
 
604
                        //new style
 
605
                        styleDict[dictKey] = styleName = nextId++;
 
606
                        if(style.externalGraphic) {
 
607
                            encStyles[styleName] = Ext.applyIf({
 
608
                                externalGraphic: this.getAbsoluteUrl(
 
609
                                    style.externalGraphic)}, style);
 
610
                        } else {
 
611
                            encStyles[styleName] = style;
 
612
                        }
 
613
                    }
 
614
                    var featureGeoJson = featureFormat.extract.feature.call(
 
615
                        featureFormat, feature);
 
616
                    
 
617
                    featureGeoJson.properties = OpenLayers.Util.extend({
 
618
                        _gx_style: styleName
 
619
                    }, featureGeoJson.properties);
 
620
                    
 
621
                    encFeatures.push(featureGeoJson);
 
622
                }
 
623
                
 
624
                return {
 
625
                    type: 'Vector',
 
626
                    styles: encStyles,
 
627
                    styleProperty: '_gx_style',
 
628
                    geoJson: {
 
629
                        type: "FeatureCollection",
 
630
                        features: encFeatures
 
631
                    },
 
632
                    name: layer.name,
 
633
                    opacity: (layer.opacity != null) ? layer.opacity : 1.0
 
634
                };
 
635
            }
 
636
        },
 
637
        "legends": {
 
638
            "gx_wmslegend": function(legend) {
 
639
                var enc = this.encoders.legends.base.call(this, legend);
 
640
                var icons = [];
 
641
                for(var i=1, len=legend.items.getCount(); i<len; ++i) {
 
642
                    icons.push(this.getAbsoluteUrl(legend.items.get(i).url));
 
643
                };
 
644
                enc[0].classes[0] = {
 
645
                    name: "",
 
646
                    icons: icons
 
647
                };
 
648
                return enc;
 
649
            },
 
650
            "gx_urllegend": function(legend) {
 
651
                var enc = this.encoders.legends.base.call(this, legend);
 
652
                enc[0].classes.push({
 
653
                    name: "",
 
654
                    icon: this.getAbsoluteUrl(legend.items.get(1).url)
 
655
                });
 
656
                return enc;
 
657
            },
 
658
            "base": function(legend){
 
659
                return [{
 
660
                    name: legend.items.get(0).text,
 
661
                    classes: []
 
662
                }];
 
663
            }
 
664
        }
 
665
    }
 
666
    
 
667
});