/[drupal]/contributions/modules/ymap/ymap.module
ViewVC logotype

Contents of /contributions/modules/ymap/ymap.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.4 - (show annotations) (download) (as text)
Mon Dec 18 18:05:36 2006 UTC (2 years, 11 months ago) by dayre
Branch: MAIN
CVS Tags: HEAD
Branch point for: DRUPAL-5
Changes since 1.3: +3 -3 lines
File MIME type: text/x-php
Fixed bug in form with #validate directive not using an array value.
1 <?php
2 //$Id: ymap.module,v 1.3 2006/12/15 21:05:54 dayre Exp $
3
4
5 /**
6 * Implementation of hook_perm.
7 */
8 function ymap_perm() {
9 return array('view maps');
10 }
11
12 /**
13 * Implementation of hook_help.
14 */
15 function ymap_help($section) {
16 switch ($section) {
17 case 'admin/help#ymap':
18 $output = '<p>'. t('Add some help text here for Yahoo Maps') .'</p>';
19 return $output;
20 case 'admin/modules#description':
21 return t('Lets you show location data on Yahoo! map.');
22 case 'admin/settings/ymap':
23 return t('The ymap module allows the display of location data on a Yahoo! map. Once an API key has been set, you can ' .
24 'enable maps and default map settings per ') . l(t('individual content type'), 'admin/settings/content-types') . '.';
25
26 }
27 }
28
29
30 /**
31 * Implentation of hook_nodeapi
32 */
33 function ymap_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
34 //if the node we're looking at is not supposed to display maps, just bail out here...
35 if (!variable_get('ymap_'. $node->type, 0)) return;
36
37 switch ($op) {
38 //---------------------
39 // V i e w
40 //---------------------
41 case 'view':
42 //set the map size for teaser or large view...
43 if (_ymap_location_is_mappable($node->location)) {
44 ($teaser && $node->location) ?
45 $node->teaser .= theme('ymap_node', $node, 'teaser')
46 : $node->body .= theme('ymap_node', $node);
47 }
48 break;
49 }
50 }
51
52 /**
53 * Returns TRUE if the given array of location data contains the
54 * necessary data for a map to represent it.
55 *
56 * @param $location
57 * A location data structure.
58 */
59 function _ymap_location_is_mappable($location) {
60 return $location && (
61 $location['city'] ||
62 $location['province'] ||
63 $location['postal_code'] ||
64 $location['country'] ||
65 ($location['latitude'] && $location['longitude']) );
66 }
67
68
69 /**
70 * Implementation of hook_form_alter.
71 */
72 function ymap_form_alter($form_id, &$form) {
73 //If we are looking at the node settings page at admin/settings/content-types/[blog|page|story|etc...]>
74 //then show the following form to toggle the map on and off for each content type.
75 if (isset($form['type']) && $form['type']['#value'] .'_node_settings' == $form_id) {
76 $type = $form['type']['#value'];
77 $form['ymap'] = array(
78 '#type' => 'fieldset',
79 '#title' => t('Yahoo! map settings'),
80 '#collapsible' => TRUE,
81 '#collapsed' => TRUE,
82 '#weight' => 0
83 );
84 $form['ymap']['ymap_'. $type] = array(
85 '#type' => 'checkbox',
86 '#title' => t('Display map'),
87 '#return_value' => 1,
88 '#default_value' => variable_get('ymap_'. $type, 0),
89 '#description' => t('Show a Yahoo! map for nodes of this type that have location information associated with them.')
90 );
91
92 $form['ymap']['ymap_'. $type .'_detail_size'] = array(
93 '#type' => 'textfield',
94 '#title' => t('Default size of the map detailed view (width x height)'),
95 '#size' => 8,
96 '#maxlength' => 15,
97 '#default_value' => variable_get('ymap_'. $type .'_detail_size', '500 x 300'),
98 '#description' => t('Enter in the width and height of the detail view map in pixels. This size will be used to display the map on the "details" page.'),
99 );
100
101 $form['ymap']['ymap_'. $type .'_teaser_size'] = array(
102 '#type' => 'textfield',
103 '#title' => t('Default size of the map teaser view (width x height)'),
104 '#size' => 8,
105 '#maxlength' => 15,
106 '#default_value' => variable_get('ymap_'. $type .'_teaser_size', '300 x 200'),
107 '#description' => t('Enter in the width and height of the teaser view map in pixels. This size will be used to display the map on the "teaser" page.'),
108 );
109
110 $form['ymap']['ymap_'. $type .'_map_type'] = array(
111 '#type' => 'radios',
112 '#title' => t('Default map type'),
113 '#default_value' => variable_get('ymap_'. $type .'_map_type', 0),
114 '#options' => array(t('Regular'), t('Satellite'), t('Hybrid')),
115 );
116
117 $form['ymap']['ymap_'. $type .'_show_map_type_control'] = array(
118 '#type' => 'radios',
119 '#title' => t('Show map type control'),
120 '#default_value' => variable_get('ymap_'. $type .'_show_map_type_control', 1),
121 '#options' => array(t('Off'), t('On')),
122 );
123
124 $form['ymap']['ymap_'. $type .'_show_zoom_control'] = array(
125 '#type' => 'radios',
126 '#title' => t('Show zoom control'),
127 '#default_value' => variable_get('ymap_'. $type .'_show_zoom_control', 1),
128 '#options' => array(t('Off'), t('Short zoom control'), t('Long zoom control')),
129 );
130
131 $form['ymap']['ymap_'. $type .'_show_zoom_scale'] = array(
132 '#type' => 'radios',
133 '#title' => t('Show zoom scale indicator'),
134 '#default_value' => variable_get('ymap_'. $type .'_show_zoom_scale', 1),
135 '#options' => array(t('Off'), t('On')),
136 );
137
138 $form['ymap']['ymap_'. $type .'_show_pan_control'] = array(
139 '#type' => 'radios',
140 '#title' => t('Show pan control'),
141 '#default_value' => variable_get('ymap_'. $type .'_show_pan_control', 0),
142 '#options' => array(t('Off'), t('On')),
143 );
144
145 $form['ymap']['ymap_'. $type .'_enable_map_dragging'] = array(
146 '#type' => 'radios',
147 '#title' => t('Enable map dragging'),
148 '#default_value' => variable_get('ymap_'. $type .'_enable_map_dragging', 1),
149 '#options' => array(t('Off'), t('On')),
150 );
151
152 $form['ymap']['ymap_'. $type .'_enable_key_controls'] = array(
153 '#type' => 'radios',
154 '#title' => t('Enable key controls'),
155 '#default_value' => variable_get('ymap_'. $type .'_enable_key_controls', 1),
156 '#description' => t('Enables keyboard/mouse wheel zoom and pan controls shortcuts.'),
157 '#options' => array(t('Off'), t('On')),
158 );
159
160 // we use the #validate form directive to call our validation
161 // function for our content type specific form.
162 $form['#validate']['ymap_form_validate'] = array($type);
163
164 }
165 }
166
167
168 /**
169 * Implementation of hook_settings.
170 */
171 function ymap_settings() {
172 $form['ymap_apikey'] = array(
173 '#type' => 'textfield',
174 '#title' => t('Yahoo! Maps API key'),
175 '#default_value' => variable_get('ymap_apikey', 'drupalmaps'),
176 '#size' => 20,
177 '#maxlength' => 255,
178 '#description' => t('Enter in your Yahoo! maps API key from ') .
179 l('here', 'http://api.search.yahoo.com/webservices/register_application') . t('.'),
180 );
181
182 return $form;
183 }
184
185 /**
186 * Validates the individual content type YMap settings form, in particular,
187 * the size specifications for the teaser and detail view.
188 * @param $form_id
189 * @param $form_values
190 * @param $form
191 */
192 function ymap_form_validate($form_id, $form_values, $form) {
193
194 // we need to get the content type name as this makes
195 // up the keys for the form values we are about to test.
196 $type = $form['#validate']['ymap_form_validate'][0];
197 if (!$type) {
198 return;
199 }
200
201 // validate size specifications
202 $size_form_fields = array("ymap_" . $type . "_teaser_size", "ymap_" . $type . "_detail_size");
203 foreach ($size_form_fields as $field) {
204 if ($form_values[$field] && !preg_match("/^(\d+)\s*x\s*(\d+)$/i", $form_values[$field], $matches)) {
205 form_set_error($field, t('Invalid size format, size must be width x height (eg. 300x200)'));
206 }
207 }
208 }
209
210 /**
211 * Returns an array listing the view plugins available from this module.
212 */
213 function ymap_views_style_plugins() {
214 return array(
215 'ymap' => array(
216 'name' => t('Ymap View'),
217 'theme' => 'views_view_ymap',
218 'needs_fields' => true,
219 'validate' => 'views_ui_plugin_validate_list',
220 ),
221 );
222 }
223
224 /**
225 * Returns HTML to render all the nodes in the YMap view on a
226 * single Yahoo map. Those nodes which have location data with
227 * longitude and latitude will display the visible fields
228 * configured in the view in an expandable section when the user
229 * moves their mouse over the map marker.
230 *
231 * Those locations which only have an address and no long/lat
232 * data can have a marker, but can not have an expandable section
233 * attached displaying the visible fields configured in the view.
234 * This is due to a limitation of the Yahoo map API in the way
235 * it performs geocoding with an asynchronous network call.
236 *
237 * @param $view
238 * @param $nodes
239 */
240 function theme_views_view_ymap($view, $nodes) {
241
242 $fields = _views_get_fields();
243 $fullnodes = array();
244 foreach ($nodes as $node) {
245 $ndata = node_load(array('nid'=>$node->nid));
246
247 // only create markers for nodes which are mappable
248 if (_ymap_location_is_mappable($ndata->location)) {
249
250 // create markers with possible custom styling for
251 // each field in the view.
252 $marker_label = "";
253 foreach ($view->field as $field) {
254
255 // output any labels if the fields have labels (from view settings)
256 if ($field['label']) {
257 $marker_label .= "<div class=\"view-label view-label-$field[queryname]\">" . $field['label'] . "</div>";
258 }
259
260 // now output the field data itself for those exposed fields set in
261 // the view.
262 $marker_label .= "<div class=\"view-field view-data-$field[queryname]\">" . views_theme_field('views_handle_field', $field['queryname'], $fields, $field, $node, $view) . "</div>";
263 }
264
265 // We need a container to wrap the field data
266 $ndata->ymap_marker = '<div style="width:160px;height:50px;">' . strtr($marker_label,"'\n\r",'" ') . "</div>";
267
268 $fullnodes[] = $ndata;
269 }
270 }
271
272 return theme_ymap_nodes($fullnodes, _ymap_get_default_detail_map_settings());
273 }
274
275 /**
276 * Returns a default set of map settings containing display
277 * properties to render the Yahoo map. For a complete list of
278 * map setting properites, refer to theme_ymap_nodes().
279 */
280 function _ymap_get_default_teaser_map_settings() {
281 return _ymap_get_map_settings_for_node(null, 1);
282 }
283
284 /**
285 * Returns a default set of map settings containing display
286 * properties to render the Yahoo map. For a complete list of
287 * map setting properites, refer to theme_ymap_nodes().
288 */
289 function _ymap_get_default_detail_map_settings() {
290 return _ymap_get_map_settings_for_node(null);
291 }
292
293 /**
294 * Returns a map settings array with the dispaly properites needed to
295 * create the Yahoo map to render a specific node. Refer to
296 * theme_ymap_nodes() for a list of all the properties required for the map.
297 *
298 * This function currently returns the map settings based on the
299 * node type which is configured in the admin/settings/content-type menu.
300 *
301 * Future enhancements will allow a node to override the default node
302 * type settings so an individual node can configure it's own map
303 * settings.
304 *
305 * @param $node
306 * The node to return map settings for. If null, a default set of
307 * map settings is returned using default map width/height for
308 * the 'detail' map type.
309 * @param $teaser
310 * a flag either 1/0 which affects the size of the displayed map. A
311 * teaser view ($teaser = 1) can have a different size than the
312 * detailed view ($teaser = 0).
313 */
314 function _ymap_get_map_settings_for_node($node, $teaser = 0) {
315
316 // used for caching map settings per node type, saves
317 // a bit of work.
318 static $settings_map;
319
320 // for now, we get settings based on node type, but
321 // in the future, a node could override with specific node settings
322 $type = $node->type ? $node->type : "nonode";
323 $settings = $settings_map[$type];
324 if ($settings) {
325 return $settings;
326 }
327
328 // there are two sizes, one for teaser, and one for detail.
329 $size = $teaser ? variable_get('ymap_'. $type .'_teaser_size', '300 x 200')
330 : variable_get('ymap_'. $type .'_detail_size', '500 x 300');
331 preg_match("/^(\d+)\s*x\s*(\d+)$/i", $size, $matches);
332 $settings['width'] = $matches[1] ? $matches[1] : 500;
333 $settings['height'] = $matches[2] ? $matches[2] : 300;
334 $settings['map_type'] = variable_get('ymap_'. $type .'_map_type', 0);
335 $settings['show_zoom_control'] = variable_get('ymap_'. $type .'_show_zoom_control', 1);
336 $settings['show_map_type_control'] = variable_get('ymap_'. $type .'_show_map_type_control', 1);
337 $settings['show_zoom_scale'] = variable_get('ymap_'. $type .'_show_zoom_scale', 1);
338 $settings['show_pan_control'] = variable_get('ymap_'. $type .'_show_pan_control', 0);
339 $settings['enable_map_dragging'] = variable_get('ymap_'. $type .'_enable_map_dragging', 1);
340 $settings['enable_key_controls'] = variable_get('ymap_'. $type .'_enable_key_controls', 0);
341
342 // save a bit of work for multi node display by
343 // caching fetched/computed settings by type
344 $settings_map[$type] = $settings;
345
346 return $settings;
347 }
348
349 /**
350 * Delegates to theme_ymap_nodes(array($node)).
351 *
352 * @param $node
353 * the node to display the location of
354 * @param $teaser
355 * a flag either 1/0 which affects the size of the displayed map. A
356 * teaser view ($teaser = 1) can have a different size than the
357 * detailed view ($teaser = 0).
358 */
359 function theme_ymap_node($node, $teaser = 0) {
360 return theme_ymap_nodes(array($node), _ymap_get_map_settings_for_node($node, $teaser));
361 }
362
363 /**
364 * Outputs a javascript snippet for displaying the given set of
365 * nodes which have mappable locations onto a Yahoo map. The map
366 * can display exact locations based on location longitude and latitude,
367 * as well as locations with only addresses. Address locations are
368 * handled by Yahoo maps built in support for geocoding address locations.
369 *
370 * The map is configured based on the given map settings array. The settings
371 * array uses the following properties to configure the map:
372 *
373 * width = map width in pixels
374 * height = map height in pixels
375 * map_type = YAHOO_MAP_REG, YAHOO_MAP_SAT, YAHOO_MAP_HYB
376 * show_zoom_control = 0-off, 1-short zoom contro, 2-long zoom control
377 * show_map_type_control = 0-off, 1-on
378 * show_zoom_scale = 0-off, 1-on
379 * show_pan_control = 0-off, 1-on
380 * enable_map_dragging = 0-off, 1-on
381 * enable_key_controls = 0-off, 1-on
382 *
383 * @param $nodes
384 * an array of nodes to display the locations of
385 * @param $map_settings
386 * display settings for the map, an array, properties listed above
387 */
388 function theme_ymap_nodes($nodes, $map_settings = null){
389
390 if (!$map_settings) {
391 $map_settings = _ymap_get_default_detail_map_settings();
392 }
393
394 // if no nodes, nothing to do.
395 if (!$nodes || !count($nodes)) {
396 return "";
397 }
398
399 static $one_time_stuff_done = false;
400 static $AJAX_MAP_TYPES;
401 static $multi_map_counter = 0;
402 if (!$one_time_stuff_done){
403 //add yahoo js files and css
404 _ymap_add_ajax_head();
405
406 $AJAX_MAP_TYPES = array("YAHOO_MAP_REG", "YAHOO_MAP_SAT", "YAHOO_MAP_HYB");
407
408 $one_time_stuff_done = true;
409 }
410
411 // if we have an array of nodes, then we are displaying them all on one map,
412 // else we just have one to display and we can use the node id to create a unique
413 // name for the map container.
414 $map_id = count($nodes) == 1 ? $nodes[0]->nid : $multi_map_counter++;
415
416 // if more than 1 node, then we let ymap estimate the best zoom, else
417 // we estimate.
418 $zoom_level = count($nodes) > 1 ? "zoomAndCenter.zoomLevel" : _ymap_get_best_zoom_for_location($nodes[0]->location);
419
420 drupal_set_html_head('<style type="text/css">
421 #mapContainer_'.$map_id.' {
422 height: '.$map_settings['height'].'px;
423 width: ' .$map_settings['width'].'px;
424 }
425 </style>');
426
427 $output = '<div id="mapContainer_'.$map_id.'"></div>
428 <script type="text/javascript">
429 // Create a map object
430 var map = new YMap(document.getElementById(\'mapContainer_'.$map_id.'\'), ' . $AJAX_MAP_TYPES[$map_settings['map_type']] . ');
431
432 // Display the map centered on given address
433 map.resizeTo(new YSize('.$map_settings['width'].', '.$map_settings['height'].'));
434
435 var _geoPoints = [];
436
437 // for those locations where we do not have lat/long, then
438 // we define a callback function to be used with the
439 // map.geoCodeAddress() calls. The callback will get the
440 // geocoded location and add a marker. For those locations where
441 // we have a lat/long, we create the marker directly.
442 var myCallback = function(resultObj) {
443 // check if OK
444 if (resultObj.success) {
445
446 // TODO customize marker image
447 var marker = new YMarker(resultObj.GeoPoint);
448 //marker.addAutoExpand(resultObj.Address);
449 map.addOverlay(marker);
450
451 _geoPoints.push(resultObj.GeoPoint);
452
453 // TODO calls to these two functions allow the map to zoom and center itself
454 // automatically after each marker is added. This is probably creating a lot of
455 // work for the API to calulate this on each marker placement and could get
456 // very slow with hundreds of markers... how best to handle this ?
457 var zoomAndCenter = map.getBestZoomAndCenter(_geoPoints);
458 map.drawZoomAndCenter(zoomAndCenter.YGeoPoint, ' . $zoom_level . ');
459 }
460 }
461
462 // Register geocoder callback for those nodes which require
463 // geocoding
464 YEvent.Capture(map,EventsList.onEndGeoCode,myCallback);
465
466 // controls
467 ';
468
469 // do controls
470 if ($map_settings['show_zoom_control']) {
471 $output .= $map_settings['show_zoom_control'] == 1 ? " map.addZoomShort();\n" : " map.addZoomLong();\n";
472 }
473 $output .= $map_settings['show_map_type_control'] ? " map.addTypeControl();\n" : "";
474 $output .= $map_settings['show_zoom_scale'] ? " map.addZoomScale();\n" : " map.removeZoomScale();\n";
475 $output .= $map_settings['show_pan_control'] ? " map.addPanControl();\n" : "";
476 $output .= $map_settings['enable_map_dragging'] ? " map.enableDragMap();\n" : " map.disableDragMap();\n";
477 $output .= !$map_settings['enable_key_controls'] ? " map.disableKeyControls();\n" : "";
478
479 // do markers
480 $output .= "\n" . ' // markers'. "\n";
481 $isMappable = false;
482 foreach ($nodes as $node) {
483 if (_ymap_location_is_mappable($node->location)) {
484 $output .= _ymap_get_location_javascript($node) . "\n";
485
486 // if we have at least one node with a mappable location,
487 // then will return the output
488 $isMappable = true;
489 }
490 }
491
492 $output .=' var zoomAndCenter = map.getBestZoomAndCenter(_geoPoints);';
493 $output .= "\n" . ' map.drawZoomAndCenter(zoomAndCenter.YGeoPoint, ' . $zoom_level . ');';
494
495
496 $output .= "\n" . ' </script>';
497
498 return $isMappable ? $output : "";
499 }
500
501
502 /**
503 * Add the JS to the html head of our returned page
504 */
505 function _ymap_add_ajax_head(){
506 drupal_set_html_head('
507 <script type="text/javascript"
508 src="http://api.maps.yahoo.com/ajaxymap?v=3.4&appid='.variable_get('ymap_apikey', 'drupalmaps').'">
509 </script>
510 ');
511 }
512
513 /**
514 * Format node location for yahoo mapping. If the node has a
515 * longitude and latitude, then we use that by creating a new
516 * YGeoPoint in the javascript, else we rely on the Yahoo
517 * geocoder by massaging the location address into a form
518 * suitable for the geocoder.
519 *
520 * Note: As of Dec/06, geocoding is supposed to be restricted to US
521 * locations, so country wasn't listed as a valid attribute, but
522 * it does seem to work for some Euorpean countries which indicates
523 * wider support in the future, so we use country and hope for the best.
524 *
525 * Available formats are:
526 * city, state
527 * city, state, country
528 * city, state, zip
529 * city, state, country, zip
530 * zip
531 * street, city, state
532 * street, city, state, country
533 * street, city, state, zip
534 * street, city, state, country, zip
535 * street, zip
536 *
537 * @param $node
538 */
539 function _ymap_get_location_javascript($node){
540
541 // if we have long/lat, then create a marker, else we just
542 // geocode the address
543 if ($node->location['longitude'] && $node->location['latitude']) {
544 $js_string = ' var point_' . $node->nid . ' = new YGeoPoint('. $node->location['latitude'] . ', ' . $node->location['longitude'] . ');';
545 $js_string .= "\n" . ' var marker_' . $node->nid . ' = new YMarker(point_' . $node->nid .');';
546 if ($node->ymap_marker) {
547 $js_string .= "\n" . ' marker_' . $node->nid . '.addAutoExpand(\''. $node->ymap_marker . '\');';
548 }
549 $js_string .= "\n" . ' map.addOverlay(marker_' . $node->nid . ');';
550 $js_string .= "\n" . ' _geoPoints.push(point_' . $node->nid . ');';
551 return $js_string;
552 }
553
554 // geocode the address
555 $address = null;
556 $keys = array('street', 'city', 'province', 'country', 'postal_code');
557 foreach ($keys as $key)
558 if ($node->location[$key]) $address .= $node->location[$key] . ', ';
559
560 //strip last ', ' off of string...
561 $address = substr($address, 0, strlen($address) - 2);
562
563 return ' map.geoCodeAddress("'. $address .'")';
564 }
565
566 /**
567 * Returns the best estimated zoom level based on the given location
568 * data.
569 * @param $location
570 */
571 function _ymap_get_best_zoom_for_location($location) {
572
573 static $zoom_types = array(
574 "longlat" => 1,
575 "street" => 3,
576 "city" => 6,
577 "province" => 14,
578 "country" => 16,
579 "world" => 18);
580
581 if (!$location) return 3;
582
583 if ($location['longitude'] && $location['latitude'])
584 return $zoom_types["longlat"];
585
586 if ($location['zip'] || $location['street'])
587 return $zoom_types["street"];
588
589 if ($location["city"])
590 return $zoom_types["city"];
591
592 if ($location["province"])
593 return $zoom_types["province"];
594
595 if ($location["country"])
596 return $zoom_types["country"];
597
598 return 3;
599 }
600
601

  ViewVC Help
Powered by ViewVC 1.1.2