-
Notifications
You must be signed in to change notification settings - Fork 4
/
map_helper.php
619 lines (604 loc) · 31.1 KB
/
map_helper.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
<?php
/**
* Indicia, the OPAL Online Recording Toolkit.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/gpl.html.
*
* @author Indicia Team
* @license http://www.gnu.org/licenses/gpl.html GPL 3.0
* @link https://github.com/Indicia-Team/client_helpers
*/
require_once 'helper_base.php';
/**
* Static helper class that provides methods for dealing with maps.
*/
class map_helper extends helper_base {
/**
* Outputs a map panel.
*
* The map panel can be augmented by adding any of the following controls
* which automatically link themselves to the map:
* * {@link sref_textbox()}
* * {@link sref_system_select()}
* * {@link sref_and_system()}
* * {@link georeference_lookup()}
* * {@link location_select()}
* * {@link location_autocomplete()}
* * {@link postcode_textbox()}
* To run JavaScript at the end of map initialisation, add a function to the
* global array called mapInitialisationHooks. Code cannot access the map at
* any previous point because maps may not be initialised when the page loads,
* e.g. if the map initialisation is delayed until the tab it is on is shown.
* To run JavaScript which updates any of the map settings, add a function to
* the mapSettingsHooks global array. For example this is used to configure
* the map by report parameters panels which need certain tools on the map.
*
* @param array $options
* Associative array of options to pass to the jQuery.indiciaMapPanel
* plugin. Has the following possible options:
* * indiciaSvc
* * indiciaGeoSvc
* * readAuth - Provides read authentication tokens for the warehouse. Only
* required when there is a location control linked to the warehouse
* associated with this map.
* * height - Height of the map panel, in pixels.
* * width - Width of the map panel, in pixels or as a percentage if
* followed by a % symbol.
* * initial_lat - Latitude of the centre of the initially displayed map,
* using WGS84.
* * initial_long - Longitude of the centre of the initially displayed map,
* using WGS84.
* * initial_zoom
* * scroll_wheel_zoom - Does the scroll wheel zoom the map in and out when
* over the map? Defaults to true. When using the scroll wheel to look up
* and down a data entry form it can be easy to inadvertantly scroll the
* map, so it may be desirable to disable this feature in some cases.
* * proxy
* * displayFormat
* * presetLayers - Array of preset layers to include. Options are
* 'google_physical', 'google_streets', 'google_hybrid',
* 'google_satellite', 'openlayers_wms', 'bing_aerial', 'bing_hybrid',
* 'bing_shaded', 'bing_os', 'osm' (for OpenStreetMap), 'os_outdoor',
* 'os_road', 'os_light', 'os_leisure'.
* * otherBaseLayerConfig - Array of layer definitions for any other layers
* to add to the map. Each entry is an object with 2 properties -
* * class - name of the Openlayers layer class (must be a subclass of
* OpenLayers.layer), e.g. WMTS.
* * params - list of parameters to pass to the constructor of the layer
* as defined in the OpenLayers documentation.
* * indiciaWMSLayers
* * indiciaWFSLayers
* * layers - An array of JavaScript variables which point to additional
* OpenLayers layer objects to add to the map. The JavaScript for
* creating these layers can be added to
* data_entry_helper::$onload_javascript before calling the map_panel
* method.
* * clickableLayers - If support for clicking on a layer to provide info
* on the clicked objects is required, set this to an array containing
* the JavaScript variable names for the OpenLayers WMS layer objects you
* have created for the clickable layers. The JavaScript for creating
* these layers can be added to data_entry_helper::$onload_javascript
* before calling the map_panel method and they can be the same layers as
* those referred to in the layers parameter.
* * clickableLayersOutputMode - Set to popup to display the information
* retrieved from a click operation on a popup window, set to div to
* display the information in a specified HTML div, or to customFunction
* to call a JavaScript function after the click operation allowing
* custom functionality such as navigation to another page. Default is
* popup.
* * clickableLayersOutputDiv - ID of the HTML div to output information
* retrieved from a click operation into, if clickableLayersOutputMode
* is set to div.
* * selectFeatureBufferProjection - Set this to the EPSG code of a
* projection to enable a control to be added to the map allowing the
* tolerance to be specified when clicking to select a feature on the
* map. E.g. set to 27700 for OSGB Easting/Northings.
* * allowBox - Default true. Set to false to disable drag boxes for
* selecting items on clickable layers. The advantage of this is that the
* drag boxes don't hinder attempts to drag the map to navigate.
* * customClickFn - Set to the name of a global custom JavaScript function
* which will handle the event of clicking on the map if you want custom
* functionality. Provide this when
* clickableLayersOutputMode=customFunction. The function will receive a
* single parameter containing an array of features.
* * clickableLayersOutputFn - Allows overridding of the appearance of the
* output when clicking on the map for WMS or vector layers. Should be
* set to a JavaScript function name which takes a list of features and
* the map div as parameters, then returns the HTML to output.
* * clickableLayersOutputColumns - An associated array of column field
* names with column titles as the values which defines the columns that
* are output when clicking on a data point. If ommitted, then all
* columns are output using their original field names.
* * locationLayerName - If using a location select or autocomplete
* control, then set this to the name of a feature type exposed on
* GeoServer which contains the id, name and boundary geometry of each
* location that can be selected. Then when the user clicks on the map
* the system is able to automatically populate the locations control
* with the clicked on location. Ensure that the feature type is styled
* on GeoServer to appear as required, though it will be added to the map
* with semi-transparency. To use this feature ensure that a proxy is
* set, e.g. by using the Indicia Proxy module in Drupal.
* * locationLayerFilter - If using a location layer, then set this to a
* cql filter in order to select e.g. locations for a website or
* locations of a type. The filter can act on any fields in the feature
* type that locationLayerName refers to.
* * controls
* * toolbarDiv - If set to 'map' then any required toolbuttons are output
* directly onto the map canvas (in the top right corner). Alternatively
* can be set to 'top', 'bottom' or the id of a div on the page to output
* them into.
* * toolbarPrefix - Content to include at the beginning of the map
* toolbar. Not applicable when the toolbar is added directly to the map.
* * toolbarSuffix - Content to include at the end of the map toolbar. Not
* applicable when the toolbar is added directly to the map.
* * helpDiv - Set to 'bottom' to add a div containing help hints below the
* map. Set to the name of a div to output help hints into that div.
* Otherwise no help hints are displayed.
* * clickForSpatialRef - Does clicking on the map set the spatial
* reference of the sample input controls on the form the map appears on
* (if any)? Defaults to true.
* * allowPolygonRecording - If a drawPolygon or drawLine control is
* present, do these set the spatial reference of the sample input
* controls on the form the map appears on (if any)? The spatial ref is
* set to the polygon centroid and the sample geometry is set to the
* polygon itself allowing polygons for records.
* * editLayer
* * editLayerName
* * standardControls - An array of predefined controls that are added to
* the map. Select from:
* * layerSwitcher - a button in the corner of the map which opens a
* panel allowing selection of the visible layers.
* * drawPolygon - a tool for drawing polygons onto the map edit layer.
* * drawLine - a tool for drawing lines onto the map edit layer.
* * drawPoint - a tool for drawing points onto the map edit layer.
* * zoomBox - allow zooming to a bounding box, drawn whilst holding the
* shift key down. This functionality is provided by the panZoom and
* panZoomBar controls as well so is only relevant when they are not
* selected.
* * zoom - +/- buttons for controlling the zoom level.
* * panZoom - simple controls in the corner of the map for panning and
* zooming.
* * panZoomBar - controls in the corner of the map for panning and
* zooming, including a slide bar for zooming.
* * modifyFeature - a tool for selecting a feature on the map edit layer
* then editing the vertices of the feature.
* * selectFeature - a tool for selecting a feature on the map edit
* layer.
* * hoverFeatureHighlight - highlights the feature on the map edit layer
* which is under the mouse cursor position.
* * fullscreen - add a button allowing the map to be shown in full
* screen mode.
* Default is layerSwitcher, panZoom and graticule.
* * initialFeatureWkt - Well known text for a geometry to load onto the
* map at startup, normally corresponding to the geometry of the record
* being edited.
* * initialBoundaryWkt - Well known text for a geometry to load onto the
* map at startup, normally corresponding to the geometry of the boundary
* being edited (e.g. a site boundary).
* * defaultSystem
* * latLongFormat - Override the format for display of lat long
* references. Select from D (decimal degrees, the default), DM (degrees
* and decimal minutes) or DMS (degrees, minutes and decimal seconds).
* * srefId - Override the id of the control that has the grid reference
* value
* * srefSystemId - Override the id of the control that has the spatial
* reference system value.
* * geomId
* * clickedSrefPrecisionMin - Specify the minimum precision allowed when
* clicking on the map to get a grid square. If not set then the grid
* square selected will increase to its maximum - size as the map is
* zoomed out. E.g. specify 4 for a 1km British National Grid square.
* * clickedSrefPrecisionMax - Specify the maximum precision allowed when
* clicking on the map to get a grid square. If not set then the grid
* square selected will decrease to its minimum size as the map is zoomed
* in. E.g. specify 4 for a 1km British National Grid square.
* * msgGeorefSelectPlace
* * msgGeorefNothingFound
* * msgSrefOutsideGrid - Message displayed when point outside of grid
* reference range is clicked.
* * msgSrefNotRecognised - Message displayed when a grid reference is
* typed that is not recognised.
* * maxZoom - Limit the maximum zoom used when clicking on the map to set
* a point spatial reference. Use this to prevent over zooming on
* background maps.
* * tabDiv - If loading this control onto a set of tabs, specify the tab
* control's div ID here. This allows the control to automatically
* generate code which only generates the map when the tab is shown.
* * setupJs - When there is JavaScript to run before the map is
* initialised, put the JavaScript into this option. This allows the map
* to run the setup JavaScript just in time, immediately before the map
* is created. This avoids problems where the setup JavaScript causes the
* OpenLayers library to be initialised too earlier if the map is on a
* div.
* * graticuleIntervalColours - A list of possible graticule CSS colours
* corresponding to each graticule width.
* * rememberPos - Set to true to enable restoring the map position when
* the page is reloaded. Requires jquery.cookie plugin. As this feature
* requires cookies, you should notify your users in compliance with
* European cookie law if you use this option.
* * helpDiv - Set to bottom to output a help div under the map, or set to
* the ID of a div to output into.
* * helpToPickPrecisionMin - Set to a precision in metres (e.g. 10, 100,
* 1000) to provide help guiding the recorder to pick a grid square of at
* least that precision. Ensure that helpDiv is set when using this
* option.
* * helpToPickPrecisionMax - Set to a precision in metres (e.g. 10, 100,
* 1000) that the help system will accept as not requiring further
* refinement when a grid square of this precision is picked.
* * helpToPickPrecisionSwitchAt - Set to a precision in metres (e.g. 10,
* 100, 1000) that the map will switch to the satellite layer (if Google
* or Bing satellite layers active) when the recorder picks a grid square
* of at least that precision.
* * gridRefHint - Set to true to put the currently hovered over grid ref
* in an element with id grid-ref-hint. Use the next setting to automate
* adding this to the page.
* * gridRefHintInFooter - Defaults to true. If there is a grid ref hint,
* should it go in the footer area of the map? If so, there is no need to
* add an element id grid-ref-hint to the page.
* * graticules - JSON to override the graticules defined for this map.
* Specify an object where the properties are the names of the projection
* associated with the graticule and each property holds an object
* defining the settings for the graticule shown when that projection is
* selected. This should hold the following properties:
* * projection - EPSG code for the graticule's projection.
* * bounds - bounding box for the projection in the same projection. An
* array of [left, bottom, right, top].
* * intervals - an array of the grid sizes shown from largest to
* smallest.
* * intervalColours - a matching array of CSV colour definitions for
* each grid size.
* * lineWidth - a matching array of line widths in pixels for each grid
* size.
* * lineOpacity - a matching array of line opacities (0 to 1) for each
* grid size.
* If you specify one graticule per projection available on a recording
* form, then the map will automatically switch to show the graticule
* associated with the current projection. If you only specify a single
* graticule then it will always be visible.
* @param array $olOptions
* Optional array of settings for the OpenLayers map object. If overriding
* the projection or displayProjection settings, just pass the EPSG number,
* e.g. 27700.
*/
public static function map_panel($options, array $olOptions = []) {
if (!$options) {
return '<div class="error">Form error. No options supplied to the map_panel method.</div>';
}
else {
global $indicia_templates;
if ((!array_key_exists('presetLayers', $options) or count($options['presetLayers']) === 0)
&& empty($options['otherBaseLayerConfig'])) {
// If no layers set default to OSM.
$options['presetLayers'][] = "osm";
}
$options = array_merge([
'indiciaSvc' => parent::getProxiedBaseUrl(),
'indiciaGeoSvc' => self::$geoserver_url,
'divId' => 'map',
'class' => '',
'width' => 600,
'height' => 470,
'jsPath' => self::$js_path,
'clickForSpatialRef' => TRUE,
'gridRefHintInFooter' => TRUE,
'gridRefHint' => FALSE,
], $options);
// Map deprecated tilecacheLayers setting to newer otherBaseLayerConfig.
if (isset($options['tilecacheLayers'])) {
if (!isset($options['otherBaseLayerConfig'])) {
$options['otherBaseLayerConfig'] = [];
}
foreach ($options['tilecacheLayers'] as $layer) {
$options['otherBaseLayerConfig'][] = [
'class' => 'TileCache',
'params' => [$layer['caption'], $layer['servers'], $layer['layerName'], $layer['settings']],
];
}
unset($options['tilecacheLayers']);
}
// When using custom base layer classes, the Openlayers defaults cannot
// be used. The caller must take control of Openlayers settings.
if (isset($options['otherBaseLayerConfig'])) {
$options['useOlDefaults'] = FALSE;
}
// Width and height may be numeric, which is interpreted as pixels, or a
// css string, e.g. '50%'.
if (is_numeric($options['height']))
$options['height'] .= 'px';
if (is_numeric($options['width']))
$options['width'] .= 'px';
if (array_key_exists('readAuth', $options)) {
// Convert the readAuth into a query string so it can pass straight to
// the JS class.
$options['readAuth'] = '&' . self::array_to_query_string($options['readAuth']);
str_replace('&', '&', $options['readAuth']);
}
// Convert textual true/false to boolean equivalents.
foreach ($options as $key => $value) {
if ($options[$key] === "false") {
$options[$key] = FALSE;
}
elseif ($options[$key] === "true") {
$options[$key] = TRUE;
}
}
// Autogenerate the links to the various mapping libraries as required.
if (array_key_exists('presetLayers', $options)) {
foreach ($options['presetLayers'] as $layer) {
$a = explode('_', $layer);
$a = strtolower($a[0]);
// Google also used in the dynamicOSGoogleSat layer pairing. We just
// store the key so the API can be lazy-loaded.
if ($a === 'google' || substr($a, 0, 7) === 'dynamic') {
self::$indiciaData['googleApiKey'] = empty(self::$google_maps_api_key) ? '' : '&key=' . self::$google_maps_api_key;
}
if ($a === 'bing' && (!isset(self::$bing_api_key) || empty(self::$bing_api_key))) {
return '<p class="error">To use the Bing map layers, please ensure that you declare the $bing_api_key ' .
'setting. Either set a value in the helper_config.php file or set to an empty string and specify a ' .
'value in the IForm settings page if using the Drupal module.</p>';
}
if ($a === 'os' && (!isset(self::$os_api_key) || empty(self::$os_api_key))) {
return '<p class="error">To use the Ordnance Survey map layers, please ensure that you declare the ' .
'$os_api_key setting. Either set a value in the helper_config.php file or set to an empty string and ' .
'specify a value in the IForm settings page if using the Drupal module.</p>';
}
}
}
self::add_resource('indiciaMapPanel');
if (array_key_exists('standardControls', $options)) {
if (in_array('graticule', $options['standardControls'])) {
self::add_resource('graticule');
}
if (in_array('clearEditLayer', $options['standardControls'])) {
self::add_resource('clearLayer');
}
}
// We need to fudge the JSON passed to the JavaScript class so it passes any actual layers, functions
// and controls, not the string class names.
$json_insert = '';
$js_entities = ['controls', 'layers', 'clickableLayers'];
foreach ($js_entities as $entity) {
if (array_key_exists($entity, $options)) {
$json_insert .= ',"' . $entity . '":[' . implode(',', $options[$entity]) . ']';
unset($options[$entity]);
}
}
// Same for 'clickableLayersOutputFn'.
if (isset($options['clickableLayersOutputFn'])) {
$json_insert .= ',"clickableLayersOutputFn":' . $options['clickableLayersOutputFn'];
unset($options['clickableLayersOutputFn']);
}
// Same for 'customClickFn'.
if (isset($options['customClickFn'])) {
$json_insert .= ',"customClickFn":' . $options['customClickFn'];
unset($options['customClickFn']);
}
// Make a copy of the options to pass into JavaScript, with a few entries
// removed.
$jsoptions = array_merge($options);
unset($jsoptions['setupJs']);
unset($jsoptions['tabDiv']);
if (isset(self::$bing_api_key)) {
$jsoptions['bing_api_key'] = self::$bing_api_key;
}
if (isset(self::$os_api_key)) {
$jsoptions['os_api_key'] = self::$os_api_key;
}
$json = substr(json_encode($jsoptions), 0, -1) . $json_insert . '}';
$olOptions = array_merge([
'theme' => self::$js_path . 'theme/default/style.css',
], $olOptions);
$json .= ',' . json_encode($olOptions);
$javascript = '';
$mapSetupJs = '';
if (isset($options['setupJs'])) {
$mapSetupJs .= "$options[setupJs]\n";
}
$mapSetupJs .= "jQuery('#$options[divId]').indiciaMapPanel($json);\n";
// Trigger a change event on the sref if it's set in case locking in use.
// This will draw the polygon on the map.
$srefId = empty($options['srefId']) ? '$.fn.indiciaMapPanel.defaults.srefId' : "'{$options['srefId']}'";
if (!(isset($options['switchOffSrefRetrigger']) && $options['switchOffSrefRetrigger'] == TRUE)) {
$mapSetupJs .= <<<JS
var srefId = $srefId;
if (srefId && $('#' + srefId).length && $('#' + srefId).val()!==''
&& indiciaData.mapdiv.settings.initialBoundaryWkt===null && indiciaData.mapdiv.settings.initialFeatureWkt===null) {
jQuery('#'+srefId).change();
}
JS;
}
// If the map is displayed on a tab, so we must only generate it when the tab is displayed as creating the
// map on a hidden div can cause problems. Also, the map must not be created until onload or later. So
// we have to set use the mapTabLoaded and windowLoaded to track when these events are fired, and only
// load the map when BOTH the events have fired.
if (isset($options['tabDiv'])) {
$javascript .= <<<SCRIPT
indiciaData.mapZoomPlanned = true;
var mapTabHandler = function(event, ui) {
panel = typeof ui.newPanel==='undefined' ? ui.panel : ui.newPanel[0];
if (typeof indiciaData.mapdiv !== 'undefined' && $(indiciaData.mapdiv).parents('#'+panel.id).length) {
indiciaData.mapdiv.map.updateSize();
if (typeof indiciaData.zoomedBounds !== "undefined") {
indiciaData.mapdiv.map.zoomToExtent(indiciaData.zoomedBounds);
delete indiciaData.zoomedBounds;
} else if (typeof indiciaData.initialBounds !== "undefined") {
indiciaFns.zoomToBounds(indiciaData.mapdiv, indiciaData.initialBounds);
delete indiciaData.initialBounds;
} else {
// Sometimes the map is not resized : googlemaps are too optimised and don't redraw with updateSize above.
if (typeof indiciaData.mapdiv.map.baseLayer.onMapResize !== "undefined") {
indiciaData.mapdiv.map.baseLayer.onMapResize();
}
}
}
}
indiciaFns.bindTabsActivate($($('#$options[tabDiv]').parent()), mapTabHandler);
SCRIPT;
// Insert this script at the beginning, because it must be done before
// the tabs are initialised or the first tab cannot fire the event.
self::$javascript = $javascript . self::$javascript;
}
$options['suffixTemplate'] = 'blank';
self::$onload_javascript .= $mapSetupJs;
$r = self::apply_template('map_panel', $options);
if ($options['gridRefHintInFooter'] && $options['gridRefHint']) {
$div = '<div id="map-footer" class="grid-ref-hints ui-helper-clearfix" style="width: ' . $options['width'] . '" ' .
'title="When you hover the mouse over the map, the grid reference is displayed here. Hold the minus key or plus key when clicking on the map ' .
'to decrease or increase the grid square precision respectively.">';
if ($options['clickForSpatialRef']) {
$r .= $div . '<span>' . lang::get('Click to set map ref') . '</span>' .
'<div class="grid-ref-hint hint-minus">' .
'<span class="label"></span><span class="data"></span> <span>(' . lang::get('hold -') . ')</span></div>' .
'<div class="grid-ref-hint hint-normal"><span class="label"> </span><span class="data"></span></div>' .
'<div class="grid-ref-hint hint-plus">' .
'<span class="label"></span><span class="data"></span> <span>(' . lang::get('hold +') . ')</span></div>';
}
else {
$r .= $div . '<span>' . lang::get('Map ref at pointer') . '</span>' .
'<div class="grid-ref-hint hint-normal"><span class="label"></span><span class="data"></span></div>';
}
$r .= '</div>';
}
return $r;
}
}
/**
* Map layer legend.
*
* Outputs a map layer list panel which automatically integrates with the
* map_panel added to the same page. The list by default will behave like a
* map legend, showing an icon and caption for each visible layer, but can be
* configured to show all hidden layers and display a checkbox or radio button
* alongside each item, making it into a layer switcher panel.
*
* @param array $options
* Associative array of options to pass to the jQuery.indiciaMapPanel
* plugin. Has the following possible options:
* * id - Optional CSS id for the output panel. Always set a value if there
* are multiple layer pickers on one page.
* * includeIcons - Set to true to include icons alongside each layer item.
* Default true.
* * includeSwitchers - Set to true to include radio buttons and/or
* checkboxes for switching on or off the visible base layers and
* overlays. Default false.
* * includeHiddenLayers - True or false to include layers that are not
* currently visible on the map. Default is false.
* * layerTypes - Array of layer types to include, options are base or
* overlay. Default is both.
* * class - Class to add to the outer div.
*
* @return string
* Control HTML.
*/
public static function layer_list($options) {
$options = array_merge([
'id' => 'layers',
'includeIcons' => TRUE,
'includeSwitchers' => FALSE,
'includeHiddenLayers' => FALSE,
'layerTypes' => ['base', 'overlay'],
'class' => '',
'prefix' => '',
'suffix' => '',
], $options);
$options['class'] .= (empty($options['class']) ? '' : ' ') . 'layer_list';
$r = '<div class="' . $options['class'] . '" id="' . $options['id'] . '" class="ui-widget ui-widget-content ui-corner-all">';
$r .= "$options[prefix]\n<ul>";
$r .= "</ul>\n" . $options['suffix'] . "</div>";
$funcSuffix = str_replace('-', '_', $options['id']);
self::$javascript .= "function getLayerHtml_$funcSuffix(layer, div) {\n ";
if (!$options['includeHiddenLayers']) {
self::$javascript .= "if (!layer.visibility) {return '';}\n ";
}
if (!in_array('base', $options['layerTypes'])) {
self::$javascript .= "if (layer.isBaseLayer) {return '';}\n ";
}
if (!in_array('overlay', $options['layerTypes'])) {
self::$javascript .= "if (!layer.isBaseLayer) {return '';}\n ";
}
self::$javascript .= "var layerHtml = '<li id=\"'+layer.id.replace(/\./g,'-')+'\">';\n ";
if ($options['includeSwitchers']) {
self::$javascript .= "
if (!layer.displayInLayerSwitcher) { return ''; }
var type='', name='';
if (layer.isBaseLayer) {
type='radio';
name='base-" . $options['id'] . "';
} else {
type='checkbox';
name='base-'+layer.id.replace(/\./g,'-');
}
var checked = layer.visibility ? ' checked=\"checked\"' : '';
layerHtml += '<input type=\"' + type + '\" name=\"' + name + '\" class=\"layer-switcher\" id=\"switch-'+layer.id.replace(/\./g,'-')+'\" ' + checked + '/>';\n ";
}
if ($options['includeIcons'])
self::$javascript .= "if (layer.isBaseLayer) {
layerHtml += '<img src=\"" . self::getRootFolder() . self::client_helper_path() . "../media/images/map.png\" width=\"16\" height=\"16\"/>';
} else if (layer instanceof OpenLayers.Layer.WMS) {
layerHtml += '<img src=\"' + layer.url + '?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetLegendGraphic&WIDTH=16&HEIGHT=16&LAYER='+layer.params.LAYERS+'&Format=image/jpeg'+
'&STYLE='+layer.params.STYLES +'\" alt=\"'+layer.name+'\"/>';
} else if (layer instanceof OpenLayers.Layer.Vector) {
var style=layer.styleMap.styles['default']['defaultStyle'];
layerHtml += '<div style=\"border: solid 1px ' + style.strokeColor +'; background-color: ' + style.fillColor + '\"> </div>';
} else {
layerHtml += '<div></div>';
}\n";
self::$javascript .= " layerHtml += '<label for=\"switch-'+layer.id.replace(/\./g,'-')+'\" class=\"layer-title\">' + layer.name + '</label>';
return layerHtml;
}\n";
if ($options['includeSwitchers'])
self::$javascript .= "
function layerSwitcherClick() {
var id = this.id.replace(/^switch-/, '').replace(/-/g, '.'),
visible=this.checked,
map = indiciaData.mapdiv.map;
$.each(map.layers, function(i, layer) {
if (layer.id==id) {
if (layer.isBaseLayer) {
if (visible) { map.setBaseLayer(layer); }
} else {
layer.setVisibility(visible);
}
}
});
}\n";
self::$javascript .= "
function refreshLayers_$funcSuffix(div) {
$('#".$options['id']." ul li').remove();
$.each(div.map.layers, function(i, layer) {
if (layer.displayInLayerSwitcher) {
$('#".$options['id']." ul').append(getLayerHtml_$funcSuffix(layer, div));
}
});\n";
if ($options['includeSwitchers'])
self::$javascript .= " $('.layer-switcher').click(layerSwitcherClick);\n";
self::$javascript .= "}
mapInitialisationHooks.push(function(div) {
refreshLayers_$funcSuffix(div);
div.map.events.register('addlayer', div.map, function(object, element) {
refreshLayers_$funcSuffix(div);
});
div.map.events.register('changelayer', div.map, function(object, element) {
if (!object.layer.isBaseLayer) {
refreshLayers_$funcSuffix(div);
}
});
div.map.events.register('changebaselayer', div.map, function(object, element) {
refreshLayers_$funcSuffix(div);
});
div.map.events.register('removelayer', div.map, function(object, element) {
$('#'+object.layer.id.replace(/\./g,'-')).remove();
});
});\n";
return $r;
}
}