' . t('The OpenLayers module is the base module for the OpenLayers suite of modules, and provides the main API.') . '
'; } return ''; } /** * Implements hook_theme(). */ function openlayers_theme($existing, $type, $theme, $path) { return array( 'openlayers_map' => array( 'arguments' => array( 'map' => array(), ), 'file' => 'includes/openlayers.theme.inc', 'template' => '/templates/openlayers-map' ), 'openlayers_styles' => array( 'arguments' => array( 'styles' => array(), 'map' => array(), ), 'file' => 'includes/openlayers.theme.inc', ), ); } /** * Implements hook_ctools_plugin_directory(). */ function openlayers_ctools_plugin_directory($module, $plugin) { return 'plugins/' . $plugin; } /** * Implements hook_ctools_plugin_type(). */ function openlayers_ctools_plugin_type() { // For backwards compatibility, we allow for the use // of hooks to define these plugins. // // This should be removed in 7.x-3.x return array( 'behaviors' => array( 'use hooks' => TRUE, 'classes' => array('behavior'), ), 'layer_types' => array( 'use hooks' => TRUE, 'classes' => array('layer_types'), ) ); } /** * Include necessary CSS and JS for rendering maps * * @ingroup openlayers_api */ function openlayers_include() { if ('internal' == variable_get('openlayers_source_type', 'external')) { $variant = variable_get('openlayers_source_internal_variant', NULL); if ($variant == 'original') $variant = NULL; libraries_load('openlayers', $variant); } else { // Use a static variable to prevent running URL check code repeatedly. static $once; if (!isset($once)) { $once = TRUE; $path = check_plain(variable_get('openlayers_source_external', OPENLAYERS_DEFAULT_LIBRARY)); // Correctly handle URLs beginning with a double backslash, see RFC 1808 Section 4 if (substr($path, 0, 2) == '//') { $http_protocol = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https' : 'http'; $path = $http_protocol . ':' . $path; } // Check for full URL and include it manually if external. if (valid_url($path, TRUE)) { drupal_add_js($path, 'external'); } else { drupal_add_js($path); } drupal_add_css(drupal_get_path('module', 'openlayers') . '/css/openlayers.css', 'file'); drupal_add_js(drupal_get_path('module', 'openlayers') . '/js/openlayers.js', 'file'); } } } /** * Prepare a map for rendering. * * Takes a map array and builds up the data given the * reference to objects like styles, layers, and behaviors. * * @ingroup openlayers_api * * @param $map * Array of map settings * @return * Filled in map array. */ function openlayers_build_map($map = array()) { // Get the necessary parts openlayers_include(); module_load_include('inc', 'openlayers', 'includes/openlayers.render'); // If no map is specified, use the default map. if (empty($map)) { if ($loaded_map = openlayers_map_load( variable_get('openlayers_default_map', 'default'))) { $map = $loaded_map->data; } } // Create ID for map as this will help with alters. $map['id'] = !isset($map['id']) ? _openlayers_create_map_id() : $map['id']; // Hook to alter map before main processing. Styles, behaviors, // layers may all be added here. // hook_openlayers_map_preprocess_alter($map) drupal_alter('openlayers_map_preprocess', $map); // Styles and layer styles are not required parameters $map['styles'] = isset($map['styles']) ? $map['styles'] : array(); $map['layer_styles'] = isset($map['layer_styles']) ? $map['layer_styles'] : array(); $map['layer_styles_select'] = isset($map['layer_styles_select']) ? $map['layer_styles_select'] : array(); $map['layer_styles_temporary'] = isset($map['layer_styles_temporary']) ? $map['layer_styles_temporary'] : array(); // Process map parts. $map['layers'] = _openlayers_layers_process($map['layers'], $map); $map['behaviors'] = _openlayers_behaviors_render($map['behaviors'], $map); $map['styles'] = _openlayers_styles_process($map['styles'], $map['layer_styles'], $map['layer_styles_select'], $map['layer_styles_temporary'], $map); // Restrict map to its projection extent (data outwith cannot be represented). // Layer can additionally specfiy their maxExtent in case they use // non-default grids. $projection = openlayers_get_projection_by_identifier($map['projection']); $map['maxExtent'] = $projection->getProjectedExtent(); // In case the layer offers the same projection as the map, use this and do not provide // projection definition to client. Otherwise rely on the client to reproject on the fly. foreach ($map['layers'] as $layer_name => $layer) { if(in_array($map['projection'], $layer['projection'])){ $map['layers'][$layer_name]['projection'] = $map['projection']; } else { // Client is able to reproject any possible projection because their definitions need to be // known to be able to set up a layer with a certain projection. Thus choice does not matter. $layerProjectionIdentifier = reset($layer['projection']); if($layerProjectionIdentifier===FALSE){ throw new Exception( t('Layer !title lacks its projection. Please edit it to select a projection.', array( '!title' => $layer['title'] )) ); } $map['layers'][$layer_name]['projection'] = $layerProjectionIdentifier; } // Ensure JavaScript gets proper type. $map['layers'][$layer_name]['isBaseLayer'] = (boolean)($layer['isBaseLayer']); } // Hook to alter map one last time. Final modification to existing // styles, behaviors, layers can happen here, but adding new styles, // behaviors will not get rendered. // hook_openlayers_map_alter($map) drupal_alter('openlayers_map', $map); // Check map for errors $map['errors'] = openlayers_error_check_map($map); return $map; } /** * Render map array * * Given a map array, render into HTML to display * a map. * * @ingroup openlayers_api * * @param $map * Associative array of map paramters. * @return * Map HTML. */ function openlayers_render_map_data($map = array()) { // Run map through build process $map = openlayers_build_map($map); $output = ''; // Given hide_empty_map flag, check if the map has any features // defined. If not, assume it is an empty map and shouldn't be displayed. if (isset($map['hide_empty_map']) && $map['hide_empty_map'] == TRUE) { $empty = TRUE; foreach ($map['layers'] as $layer) { if (isset($layer['features']) && count($layer['features'])) { $empty = FALSE; } } if ($empty) { // Abort early because there are no features to display on the map anyway return ''; } } // Currently the restricted extent of maps is always given in EPSG:3857 so // this projection needs to be available in the client for all restricted // maps. Using EPSG:4326 instead would likely be better. if(array_key_exists('restrict', $map['center']) && (boolean)$map['center']['restrict']['restrictextent']){ openlayers_add_js_projection_definition( openlayers_get_projection_by_identifier('EPSG:3857') ); } // Return themed map if no errors found if (empty($map['errors'])) { // In case the layer offers the same projection as the map, use this and do not provide // projection definition to client. Otherwise rely on the client to reproject on the fly. foreach ($map['layers'] as $layer_name => $layer) { // Provide client with projection definition so that it can reproject openlayers_add_js_projection_definition( openlayers_get_projection_by_identifier($map['layers'][$layer_name]['projection']) ); } // Ensure projections in use are known to the client (loads Proj4js if required) openlayers_add_js_projection_definition(openlayers_get_projection_by_identifier($map['projection'])); openlayers_add_js_projection_definition(openlayers_get_projection_by_identifier($map['displayProjection'])); $js = array('openlayers' => array('maps' => array($map['id'] => $map))); drupal_add_js($js, 'setting'); // Push map through theme function and return $output = theme('openlayers_map', array( 'map' => $map, )); } return $output; } /** * Load projection transformations in case OpenLayers does not support projections in use natively * @param openlayers_projection $projection */ function openlayers_add_js_projection_definition(openlayers_projection $projection) { $openlayers_natively_supported = array('EPSG:4326', 'EPSG:900913'); // Only load Proj4js if projection not supported by OpenLayers anyway if (!in_array($projection->identifier, $openlayers_natively_supported)) { proj4js_load_definition($projection->identifier, $projection->getDefinition()); } } /** * Render a map by name * * Given a map name render it into a full map object. * * @ingroup openlayers_api * * @param $map * Name of the map * @return * Map HTML. */ function openlayers_render_map($map = '') { // If it's an array, then we have been passed the map data array if (is_array($map)) { return openlayers_render_map_data($map); } // If it's a string, then we are passing a map name instead of the whole map object // so we need to load the object if (!$map || is_string($map)) { $map_name = $map; if (!$map_name) { $map_name = variable_get('openlayers_default_map', 'default'); } $map = openlayers_map_load($map_name); if (!is_object($map)) { throw new Exception("Failed to load map called " . $map_name); } } return openlayers_render_map_data($map->data); } /** * Get layer object * * @ingroup openlayers_api * @return openlayers_layer_type|FALSE * array of layer info */ function openlayers_get_layer_object($layer, $map = array()) { ctools_include('plugins'); // Static cache because this function will possibly be called in big loops static $layer_types; if (!isset($layer_types)) { $layer_types = openlayers_layer_types(); } $layer->title = t($layer->title); $layer->description = t($layer->description); // Attempt to get ctool class if (isset($layer_types[$layer->data['layer_type']]) && $class = ctools_plugin_get_class( $layer_types[$layer->data['layer_type']], 'layer_type') ) { $layer_object = new $class($layer, $map); return $layer_object; } else { watchdog('openlayers', 'Layer !layer_name is unavailable because its layer type or the module that provides its layer type is missing', array('!layer_name' => $layer->title), WATCHDOG_ERROR); return FALSE; } } /** * Menu loader for layers. (%openlayers_layer) * @ingroup openlayers_api * * @param $name * Layer name * @param $reset * Boolean whether to reset cache or not * @return openlayers_layer_type|FALSE * Layer export */ function openlayers_layer_load($name, $reset = FALSE) { ctools_include('export'); if ($reset) ctools_export_load_object_reset('openlayers_layers'); $layer = ctools_export_load_object('openlayers_layers', 'names', array($name)); if (is_array($layer) && isset($layer[$name])) { $layer_object = openlayers_get_layer_object($layer[$name]); if (openlayers_layer_sanity_check($layer_object)) { return $layer_object; } } else { return FALSE; } } /** * Get all openlayers layers as objects. * @ingroup openlayers_api * * @param $reset * Boolean whether to reset cache or not * @return array * array of layer info */ function openlayers_layers_load($reset = FALSE, $include_disabled = FALSE) { ctools_include('export'); $layer_objects = array(); if ($reset) ctools_export_load_object_reset('openlayers_layers'); $layers = ctools_export_load_object('openlayers_layers', 'all', array()); foreach ($layers as $layer) { if (!$include_disabled && isset($layer->disabled) && $layer->disabled) { continue; } $layer_objects[$layer->name] = openlayers_get_layer_object($layer); } return array_filter($layer_objects, 'openlayers_layer_sanity_check'); } /** * Check the plugin definition of a layer. * Some field *MUST* be there to work correctly with OL. * * @ingroup openlayers_api * @param $definition * @return bool */ function openlayers_layer_definition_check($definition) { $mandatory_fields = array( array('title'), array('description'), array('name'), array('path'), array('layer_type', 'file'), array('layer_type', 'class'), array('layer_type', 'parent'), ); foreach ($mandatory_fields as $field) { $missing = drupal_array_nested_key_exists($definition, $field); if (!$missing) { drupal_set_message(t("Key !key is missing in in the plugin definition of the layer type !type. The layer will be disabled.", array( '!key' => htmlspecialchars(implode(', ', $field)), '!type' => htmlspecialchars($definition['name']), )), 'warning'); watchdog('openlayers', 'Layer !layer is unavailable because its plugin definition is incomplete.', array('!layer' => $definition['name']), WATCHDOG_ERROR); return FALSE; } } return TRUE; } /** * Check the plugin definition of a behavior. * Some field *MUST* be there to work correctly with OL. * * @ingroup openlayers_api * @param $definition * @return bool */ function openlayers_behavior_definition_check($definition) { $mandatory_fields = array( array('title'), array('description'), array('name'), array('path'), array('type'), array('behavior', 'file'), array('behavior', 'class'), array('behavior', 'parent'), ); foreach ($mandatory_fields as $field) { $missing = drupal_array_nested_key_exists($definition, $field); if (!$missing) { drupal_set_message(t("Key !key is missing in the definition of the behavior !behavior. The behavior will be disabled.", array( '!key' => htmlspecialchars(implode(', ', $field)), '!behavior' => htmlspecialchars($definition['name']), )), 'warning'); watchdog('openlayers', 'Behavior !behavior is unavailable because its plugin definition is incomplete.', array('!behavior' => $definition['name']), WATCHDOG_ERROR); return FALSE; } } return TRUE; } /** * Check layer to determine whether it has all the * necessary attributes to be rendered. This is necessary * because of API changes, and is a consolidation from other * layer-error-checking in this module * * @param $layer * Layer object * @param $projection * Projection number (EPSG) to check compatibility with * @param $strict * reject invalid layers * @return boolean * layer validity if strict is set, otherwise always true */ function openlayers_layer_sanity_check($layer, $projection = FALSE, $strict = FALSE) { // Handle layers after they've been rendered for a map $layer = (is_array($layer)) ? (object) $layer : $layer; if (!isset($layer->name)) { return !$strict; } if (!isset($layer->data['projection']) || !is_array($layer->data['projection'])) { watchdog('openlayers', 'Layer %name does not have a projection set.', array('%name' => $layer->name)); drupal_set_message( t('OpenLayers layers failed the sanity check. See the Drupal log for details', array('@drupallog' => url('admin/reports/dblog'))) ); return !$strict; } if (!isset($layer->data['layer_type'])) { watchdog('openlayers', 'Layer %name does not have its layer_type set.', array('%name' => $layer->name)); drupal_set_message( t('OpenLayers layers failed the sanity check. See the Drupal log for details', array('@drupallog' => url('admin/reports/dblog'))) ); return !$strict; } if ($projection && empty($layer->data['vector']) && (!in_array($projection, $layer->data['projection']))) { watchdog('openlayers', 'The layer %layer_name cannot be reprojected to the map projection: EPSG: %map_proj', array( '%layer_name' => $layer->name, // TODO: $map is not defined. '%map_proj' => $map['projection'], ) ); return !$strict; } return TRUE; } /** * Delete a layer object from the database. * * @ingroup openlayers_api * * @param $layer * String identifier of a layer or layer object with name. * @return * The results of DB delete. */ function openlayers_layer_delete($layer) { return openlayers_object_delete($layer, 'layer'); } /** * Get all layer types. * * @ingroup openlayers_api * * @param $reset * Boolean whether to reset cache or not. * @return * Array of layer type info. */ function openlayers_layer_types($reset = FALSE, $include_disabled = FALSE) { ctools_include('plugins'); $layers = ctools_get_plugins('openlayers', 'layer_types'); if (!$include_disabled) { $layers = array_filter($layers, function($layer) { return (!isset($layer->disabled) || !$layer->disabled); }); } return array_filter($layers, 'openlayers_layer_definition_check'); } /** * Menu loader for layer types. * * @ingroup openlayers_api * * @param $name * String identifier of layer type. * @param $reset * Boolean whether to reset cache or not. * @return openlayers_layer_type * An instantiated layer type object or FALSE if not found. */ function openlayers_layer_type_load($name, $reset = FALSE) { ctools_include('plugins'); if ($layer_type_class = ctools_plugin_load_class( 'openlayers', 'layer_types', $name, 'layer_type')) { $layer_type = new $layer_type_class(); return $layer_type; } return FALSE; } /** * Get all behaviors. * * @ingroup openlayers_api * * @param $reset * Boolean whether to reset cache or not. * @return * Array of behavior info. */ function openlayers_behaviors($reset = FALSE, $include_disabled = FALSE) { ctools_include('plugins'); $behaviors = ctools_get_plugins('openlayers', 'behaviors'); if (!$include_disabled) { $behaviors = array_filter($behaviors, function($behavior) { return (!isset($behavior->disabled) || !$behavior->disabled); }); } return array_filter($behaviors, 'openlayers_behavior_definition_check'); } /** * Get all openlayers styles. * * @ingroup openlayers_api * * @param $reset * Boolean whether to reset cache or not. * @return * Array of all available styles. */ function openlayers_styles($reset = FALSE, $include_disabled = FALSE) { ctools_include('export'); if ($reset) { ctools_export_load_object_reset('openlayers_styles'); } $styles = ctools_export_load_object('openlayers_styles', 'all', array()); if (!$include_disabled) { $styles = array_filter($styles, function($style) { return !(isset($style->disabled) && $style->disabled); }); } return $styles; } /** * Load a style object by name. * * This function can also be used as a * menu loader for a style. * * @ingroup openlayers_api * * @param $name * The string identifier of the style. * @param $reset * Boolean whether to reset the cache or not. * @return * A style object or FALSE if not found. */ function openlayers_style_load($name, $reset = FALSE) { $styles = openlayers_styles($reset); return !empty($styles[$name]) ? $styles[$name] : FALSE; } /** * Save style. * * @ingroup openlayers_api * * @param $style * The style object to save. * @return * The results of DB write or FALSE if no name. */ function openlayers_style_save($style) { if (!empty($style->name)) { return (db_select('openlayers_styles') ->fields('openlayers_styles', array('name')) ->condition('name', $style->name) ->execute() ->fetchCol()) ? drupal_write_record('openlayers_styles', $style, 'name') : drupal_write_record('openlayers_styles', $style); } return FALSE; } /** * Delete a style object from the database. * * @ingroup openlayers_api * * @param $style * String identifier of a style or style object with name. * @return * The results of DB delete. */ function openlayers_style_delete($style) { return openlayers_object_delete($style, 'style'); } /** * Get maps from DB or code, via cache. * * @ingroup openlayers_api * * @param $reset * Boolean whether to reset or not. * @return * Return array of maps. */ function openlayers_maps($reset = FALSE, $include_disabled = FALSE) { ctools_include('export'); if ($reset) { ctools_export_load_object_reset('openlayers_maps'); } $maps = ctools_export_load_object('openlayers_maps', 'all', array()); if (!$include_disabled) { $maps = array_filter($maps, function($map) { return !(isset($map->disabled) && $map->disabled); }); } return $maps; } /** * Given a map name, get full map object. * * This function can also be used as a * menu loader for a style. * * @ingroup openlayers_api * * @param $name * String identifier of the map. * @param $reset * Boolean whether to reset cache. * @return * map object or FALSE if not found. */ function openlayers_map_load($name = '', $reset = FALSE) { ctools_include('export'); if ($reset) { ctools_export_load_object_reset('openlayers_maps'); } $maps = ctools_export_load_object('openlayers_maps', 'names', array($name)); if (empty($maps[$name])) { return FALSE; } else { $map = $maps[$name]; $map->data['map_name'] = $name; return clone $map; } } /** * Save a map object to the database. * * @ingroup openlayers_api * * @param $map * map object. * @return * The results of DB write or FALSE if no name. */ function openlayers_map_save($map) { if (!empty($map->name)) { return (db_select('openlayers_maps') ->fields('openlayers_maps', array('name')) ->condition('name', $map->name) ->execute() ->fetchCol()) ? drupal_write_record('openlayers_maps', $map, 'name') : drupal_write_record('openlayers_maps', $map); } return FALSE; } /** * Delete a map object from the database. * * @ingroup openlayers_api * * @param $map * String identifier of a map or map object with name. * @return * The results of DB delete. */ function openlayers_map_delete($map) { return openlayers_object_delete($map, 'map'); } /** * Get map options in an array suitable for a FormAPI element. * * @ingroup openlayers_api * * @param $reset * Boolean whether to reset or not. * @return * Return array of formatted data. */ function openlayers_map_options($reset = FALSE) { $maps = openlayers_maps($reset); $options = array(); foreach ($maps as $map) { $options[$map->name] = $map->title; } return $options; } /** * Delete an object from the database. * * @ingroup openlayers_api * * @param $ol_object * String identifier of an object or the object with name. * @param $type * Type of object to delete. The options are the following: * - 'layer' * - 'style' * = 'map' * @return * The results of the DB delete. */ function openlayers_object_delete($ol_object, $type) { // Check for object or name $tables = array( 'style' => 'openlayers_styles', 'layer' => 'openlayers_layers', 'map' => 'openlayers_maps'); if (is_object($ol_object) && isset($ol_object->name) && isset($tables[$type])) { $ol_object = $ol_object->name; return db_delete($tables[$type])->condition('name', $ol_object)->execute(); } } /** * Checks map array for incompatibilities or errors. * * @ingroup openlayers_api * * @param $map * Map array * @param $log_errors * Boolean whether to log errors. * @return * FALSE if passed. Array of descriptive errors if fail. */ function openlayers_error_check_map($map, $log_errors = TRUE) { $errors = array(); // Check for layers if (!is_array($map['layers'])) { $errors[] = t('Map contains no renderable layers.'); } else { // Check layer projections foreach ($map['layers'] as $layer) { openlayers_layer_sanity_check( array('data' => $layer), $map['projection'], TRUE); } } // Check if any errors found to log if (count($errors) > 0 && $log_errors) { // Log the Error(s) watchdog('openlayers', implode(', ', $errors), array(), WATCHDOG_ERROR); } // Check if errors and return return (count($errors) > 0) ? $errors : FALSE; } /** * Models a projection, a description of a coordinate system. */ class openlayers_projection { /** * @var String Opaque primary key (should not be exposed but ctools API doesn't allow for a more rigid structure) */ public $identifier; /** * @var String proj4 definition for on-the-fly reprojections of vector data. */ private $definition; /** * @var number Leftmost boundary where coordinate system is valid */ private $projectedextentleft; /** * @var number Bottommost boundary where coordinate system is valid */ private $projectedextentbottom; /** * @var number Rightmost boundary where coordinate system is valid */ private $projectedextentright; /** * @var number Topmost boundary where coordinate system is valid */ private $projectedextenttop; public function __construct($identifier, $definition, $projectedextentleft, $projectedextentbottom, $projectedextentright, $projectedextenttop) { $this->identifier = $identifier; $this->definition = $definition; $this->projectedextentleft = $projectedextentleft; $this->projectedextentbottom = $projectedextentbottom; $this->projectedextentright = $projectedextentright; $this->projectedextenttop = $projectedextenttop; } /** * @return string Textual representation for the user. */ public function getLocalizedMessage() { return $this->identifier; } /** * @return array Boundaries of projection in projected coordinates */ public function getProjectedExtent() { return array_map('floatval', array($this->projectedextentleft, $this->projectedextentbottom, $this->projectedextentright, $this->projectedextenttop)); } /** * @return String Proj4 style definition */ public function getDefinition() { return $this->definition; } } /** * @param stdClass $record Object as returned by ctools_export_crud_load for type openlayers_projections * @return openlayers_projection */ function openlayers_projection_from_record($record) { return new openlayers_projection($record->identifier, $record->definition, $record->projectedextentleft, $record->projectedextentbottom, $record->projectedextentright, $record->projectedextenttop); } /** * @param String $authority Organization who defined the code * @param String $code Projection identifier * @return openlayers_projection */ function openlayers_get_projection($authority, $code) { static $projections; if (!isset($projections)) { $projections = array(); } $identifier = $authority . ':' . $code; if (!array_key_exists($identifier, $projections)) { $projections[$identifier] = openlayers_get_projection_by_identifier($identifier); } return $projections[$identifier]; } /** * @param String $identifier Identifier, such as “EPSG:4326” * @return openlayers_projection */ function openlayers_get_projection_by_identifier($identifier) { ctools_include('export'); //patch nd@oieau.fr le 12/07/2013 //Some modules such as geofield only specify the projection number - Default authority to EPSG: if unspecified. if (is_numeric($identifier)) { $identifier = "EPSG:".$identifier; } $records = ctools_export_load_object('openlayers_projections', 'names', array($identifier)); if (empty($records)) { if(mb_strpos($identifier, ':')===FALSE){ throw new Exception(t("Projection !projection lacks an authority code. Read http://drupal.org/node/1944074 for hints.", array( '!projection' => $identifier ))); } throw new Exception(t("Projection !projection requested but not supported.", array( '!projection' => $identifier ))); } return openlayers_projection_from_record($records[key($records)]); } /** * @return array