You are here

openlayers.module in Openlayers 7.2

Main OpenLayers API File

This file holds the main Drupal hook functions, and the openlayers API functions for the openlayers module.

File

openlayers.module
View source
<?php

/**
 * @defgroup openlayers OpenLayers provides an API and
 * Modules to interface with OpenLayers
 */

/**
 * @file
 * Main OpenLayers API File
 *
 * This file holds the main Drupal hook functions,
 * and the openlayers API functions for the openlayers module.
 *
 * @ingroup openlayers
 */

/**
 * OpenLayers hosted default library.
 */
define('OPENLAYERS_DEFAULT_LIBRARY', '//cdnjs.cloudflare.com/ajax/libs/openlayers/2.13.1/OpenLayers.js');

/**
 * OpenLayers library compatible suggestion.
 */
define('OPENLAYERS_SUGGESTED_LIBRARY', 2.13);

/**
 * OpenLayers hosted API version.  What version is used when going to
 * http://openlayers.org/api/OpenLayers.js
 */
define('OPENLAYERS_HOSTED_API_LIBRARY', 2.13);

/**
 * Implements hook_help().
 */
function openlayers_help($path, $arg) {
  switch ($path) {
    case 'admin/help#openlayers':
      return '<p>' . t('The OpenLayers module is the base module for the
        OpenLayers suite of modules, and provides the main API.') . '</p>';
  }
  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() {

  // Get the page language for string translation.
  global $language;
  $lang = $language->language;
  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);
    if ($lang != 'en') {
      $main_locale = libraries_get_path('openlayers') . '/lib/OpenLayers/Lang.js';
      if (file_exists($main_locale)) {
        drupal_add_js($main_locale, 'file');
      }
      $language_locale = libraries_get_path('openlayers') . '/lib/OpenLayers/Lang/' . $lang . '.js';
      if (file_exists($language_locale)) {
        drupal_add_js($language_locale, 'file');
      }
      else {
        $country_code = variable_get('site_default_country', '');
        $language_locale = libraries_get_path('openlayers') . '/lib/OpenLayers/Lang/' . $lang . '-' . $country_code . '.js';
        if (file_exists($language_locale)) {
          drupal_add_js($language_locale, 'file');
        }
      }
    }
  }
  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);
      }
      if ($lang != 'en') {
        $main_locale = libraries_get_path('openlayers') . '/lib/OpenLayers/Lang.js';
        if (file_exists($main_locale)) {
          drupal_add_js($main_locale, 'file');
        }
        $language_locale = libraries_get_path('openlayers') . '/lib/OpenLayers/Lang/' . $lang . '.js';
        if (file_exists($language_locale)) {
          drupal_add_js($language_locale, 'file');
        }
        else {
          $country_code = variable_get('site_default_country', '');
          $language_locale = libraries_get_path('openlayers') . '/lib/OpenLayers/Lang/' . $lang . '-' . $country_code . '.js';
          if (file_exists($language_locale)) {
            drupal_add_js($language_locale, 'file');
          }
        }
      }
      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'] = (bool) $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']) && (bool) $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']));

    // Make sure, that any previously added settings are removed (see Drupal core issue #208611).
    $js = array(
      'openlayers' => array(
        'maps' => array(
          $map['id'] => '',
        ),
      ),
    );
    drupal_add_js($js, 'setting');
    $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 <em>!type</em>. 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 <em>!behavior</em>. 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
      <a href="@drupallog">Drupal log</a> 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
      <a href="@drupallog">Drupal log</a> 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, 'openlayers_object_enabled');
  }
  return array_filter($layers, 'openlayers_layer_definition_check');
}

/**
 * Confirm that an object is not disabled.
 *
 * @ingroup openlayers_api
 *
 * @param Object $object
 *   An object -- a layer, behavior, style or map.
 *
 * @return Boolean
 *   Returns TRUE if the object is not disabled.
 */
function openlayers_object_enabled($object) {
  return !isset($object->disabled) || !$object->disabled;
}

/**
 * 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, 'openlayers_object_enabled');
  }
  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, 'openlayers_object_enabled');
  }
  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, 'openlayers_object_enabled');
  }
  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');
  $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<openlayers_projection>
 */
function openlayers_get_all_projections($reset = FALSE, $include_disabled = FALSE) {
  ctools_include('export');
  $projections = array();
  foreach (ctools_export_crud_load_all('openlayers_projections', $reset) as $record) {
    if (!$include_disabled && isset($record->disabled) && $record->disabled) {
      continue;
    }
    $projections[] = openlayers_projection_from_record($record);
  }
  return $projections;
}

/**
 * Rerenders form part “Layers & Styles” when map projections changes
 * @param array $form
 * @param array $form_state
 * @return array Form part to rerender
 */
function openlayers_map_layerlist(&$form, &$form_state) {
  module_load_include('inc', 'openlayers_ui', '/include/openlayers_ui.theme');

  // Remove group property as Drupal otherwise returns NULL instead of the rendered form part
  unset($form['layerstyles']['#group']);

  // Force a diffent, fixed identifier so that the form part can reliably replaced
  $form['layerstyles']['#id'] = 'edit-layerstyles';

  // Rerender the form part
  return $form['layerstyles'];
}

/**
 * Get extent given projection
 *
 * Returns standard world-max-extents for common projections.
 * Layers implementing other projections and subsets of the
 * world should define their maxExtent in the layer array.
 *
 * @ingroup openlayers_api
 *
 * @param $authority
 *   String Organization code, such as EPSG.
 * @param $projection
 *   String of the projection value, such as 4326.
 * @return
 *   Array of maxExtent in OpenLayers toArray() format.
 */
function openlayers_get_extent($authority, $projection) {

  // Use projected extend from openlayers_projection instead but keep this function for now as it's marked as API
  return openlayers_get_projection($authority, $projection)
    ->getProjectedExtent();
}

/**
 * Get resolution given projection
 *
 * Returns a default set of resolutions for standard
 * TMS, WMS, etc servers serving up common projections.
 * Layers supporting less common projections and resolutions
 * can easily define custom resolutions arrays.
 *
 * @ingroup openlayers_api
 *
 * @param $projection
 *   String specifying which projection this should take, like EPSG:900913.
 * @param $zoom_start
 *   Integer of first zoom level, default 0.
 * @param $zoom_end
 *   Integer of last zoom level, default FALSE.
 * @return
 *   Array of resolutions.
 */
function openlayers_get_resolutions($projection, $zoom_start = 0, $zoom_end = FALSE) {

  // TODO This is conceptually flawed and should not exist. Resolutions depend on the server in use in combination with the tile size.
  switch ($projection) {
    case 'EPSG:900913':

      // 16 zoom levels, taken from
      // openlayers.org TMS map
      $res = array(
        156543.0339,
        78271.51695,
        39135.758475,
        19567.8792375,
        9783.939618750001,
        4891.969809375,
        2445.9849046875,
        1222.99245234375,
        611.496226171875,
        305.7481130859375,
        152.87405654296876,
        76.43702827148438,
        38.21851413574219,
        19.109257067871095,
        9.554628533935547,
        4.777314266967774,
        2.388657133483887,
        1.1943285667419434,
        0.5971642833709717,
        0.29858214169740677,
        0.14929107084870338,
        0.07464553542435169,
      );
      break;
    case 'EPSG:4326':

      // 16 zoom levels, taken from
      // openlayers.org default WMS map
      $res = array(
        0.703125,
        0.3515625,
        0.17578125,
        0.087890625,
        0.0439453125,
        0.02197265625,
        0.010986328125,
        0.0054931640625,
        0.00274658203125,
        0.001373291015625,
        0.0006866455078125,
        0.00034332275390625,
        0.000171661376953125,
        8.58306884765625E-5,
        4.291534423828125E-5,
        2.1457672119140625E-5,
        1.0728836059570312E-5,
      );
      break;
    default:
      $res = array();
      break;
  }
  $length = $zoom_end == FALSE ? NULL : $zoom_end - $zoom_start;

  // By default this will not actually clip the array
  $resolutions = array_slice($res, $zoom_start, $length);
  return $resolutions;
}

/**
 * We define base classes in the core module.
 * All other parent classes can be autoloaded through ctools.
 */
class openlayers_behavior {
  var $options, $map;
  function __construct($options = array(), $map = array()) {
    $this->options = $options + $this
      ->options_init();
    $this->map = $map;
  }

  /*
   * @return array of JavaScript functions required to be defined
   * in order for this function to work
   */
  function js_dependency() {
    return array();
  }
  function options_init() {
    return array();
  }

  /*
   * @param $defaults default values for the form as an array
   * @return a FormAPI form
   */
  function options_form($defaults = array()) {
    return array();
  }
  function render(&$map) {
  }

}

/**
 * We define base classes in the core module.
 * All other parent classes can be autoloaded through ctools.
 */
class openlayers_layer_type {

  /**
   * Stores the options for this layer.
   * @var array
   */
  public $data = array();

  /**
   * Stores the current map.
   * @var array
   */
  public $map;

  /**
   * Set configuration and store map.
   *
   * @param stdClass $layer
   *   Configuration object with the options for the layer.
   * @param $map
   *   Array with the current map.
   */
  function __construct($layer = array(), $map = array()) {
    foreach (array(
      'name',
      'title',
      'description',
      'data',
      'export_type',
    ) as $k) {
      if (isset($layer->{$k})) {
        $this->{$k} = $layer->{$k};
      }
    }

    // Extend options with the defaults.
    $this->data += $this
      ->options_init();
    $this->map = $map;
  }

  /**
   * @return array<openlayers_projection>
   *   List of all projections that are supported by the layer.
   */
  public function getProjections() {
    $projections = array();

    // TODO Ignore incomplete data until cause is fixed (projection for every layer set during migration)
    if (isset($this->data['projection'])) {
      foreach ($this->data['projection'] as $projection) {
        $projections[] = openlayers_get_projection_by_identifier($projection);
      }
    }
    return $projections;
  }

  /**
   * Provides the default options for the layer.
   *
   * @return
   *   An associative array with the default options.
   */
  function options_init() {
    return array(
      'layer_type' => get_class($this),
      'isBaseLayer' => TRUE,
      // TODO: Remove hard-coded resolutions
      'projection' => array(
        'EPSG:900913',
      ),
      'serverResolutions' => openlayers_get_resolutions('EPSG:900913'),
      'resolutions' => openlayers_get_resolutions('EPSG:900913'),
      'base_url' => NULL,
      'transitionEffect' => 'resize',
      'weight' => 0,
    );
  }

  /**
   * Options form to configure layer instance options.
   *
   * @return
   *   Array with form elements.
   */
  function options_form($default = array()) {
    $allProjectionOptions = array();
    foreach (openlayers_get_all_projections() as $projection) {
      $allProjectionOptions[$projection->identifier] = $projection
        ->getLocalizedMessage();
    }
    return array(
      'projection' => array(
        '#type' => 'select',
        '#title' => t('Projection'),
        '#multiple' => TRUE,
        '#options' => $allProjectionOptions,
        '#default_value' => isset($default->data['projection']) ? $default->data['projection'] : openlayers_get_projection('EPSG', '3857')->identifier,
      ),
      'isBaseLayer' => array(
        '#type' => 'checkbox',
        '#title' => t('Base Layer'),
        '#description' => t('Uncheck to make this map an overlay'),
        '#default_value' => !empty($default->data['isBaseLayer']) ? $default->data['isBaseLayer'] : FALSE,
      ),
    );
  }

  /**
   * Validate the options_form().
   *
   * @param array $default
   */
  function options_form_validate($form, &$form_state) {
  }

  /**
   * Submit the options_form().
   *
   * @param array $form
   * @param array $form_state
   */
  function options_form_submit($form, &$form_state) {
    $form_state['values']['data']['projection'] = array_keys($form_state['values']['data']['projection']);

    // TODO Resolutions should be processed only in layers
    // that provide a form field for it.
    $form_state['values']['data']['resolutions'] = array_map('floatval', array_values($form_state['values']['data']['resolutions']));
  }

  /**
   * Options form to configure layer-type-wide options.
   *
   * @return
   *   Array with form elements.
   */
  function settings_form() {
    return array();
  }

  /**
   * Render the layer and return the layer options.
   *
   * Has no return value.
   *
   * @param $map
   */
  function render(&$map) {
  }

}

/**
 * Implements hook_ctools_plugin_api().
 */
function openlayers_ctools_plugin_api($module, $api) {
  if ($module == "openlayers") {
    switch ($api) {
      case 'openlayers_maps':
        return array(
          'version' => 1,
        );
      case 'openlayers_layers':
        return array(
          'version' => 1,
        );
      case 'openlayers_projections':
        return array(
          'version' => 1,
        );
      case 'openlayers_styles':
        return array(
          'version' => 1,
        );
      case 'openlayers_layer_types':
        return array(
          'version' => 1,
        );
      case 'openlayers_behaviors':
        return array(
          'version' => 1,
        );
    }
  }
  elseif ($module == 'boxes' && $api == 'plugins') {
    return array(
      'version' => 1,
    );
  }
}

/**
 * Implements hook_openlayers_behaviors().
 */
function openlayers_openlayers_behaviors() {
  module_load_include('inc', 'openlayers', 'includes/openlayers.behaviors');
  return _openlayers_openlayers_behaviors();
}

/**
 * Implements hook_openlayers_layer_types().
 */
function openlayers_openlayers_layer_types() {
  module_load_include('inc', 'openlayers', 'includes/openlayers.layer_types');
  return _openlayers_openlayers_layer_types();
}

/**
 * Implements hook_openlayers_layers().
 */
function openlayers_openlayers_layers() {
  module_load_include('inc', 'openlayers', 'includes/openlayers.layers');
  return _openlayers_openlayers_layers();
}

/**
 * Implements hook_openlayers_projections().
 */
function openlayers_openlayers_projections() {
  module_load_include('inc', 'openlayers', 'includes/openlayers.projections');
  return _openlayers_openlayers_projections();
}

/**
 * Implements hook_openlayers_styles().
 */
function openlayers_openlayers_styles() {
  module_load_include('inc', 'openlayers', 'includes/openlayers.styles');
  return _openlayers_openlayers_styles();
}

/**
 * Implements hook_openlayers_maps().
 */
function openlayers_openlayers_maps() {
  module_load_include('inc', 'openlayers', 'includes/openlayers.maps');
  return _openlayers_openlayers_maps();
}

/**
 * Implements hook_boxes_plugins().
 */
function openlayers_boxes_plugins() {
  return array(
    'openlayers_simple' => array(
      'title' => 'OpenLayers',
      'handler' => array(
        'parent' => 'boxes_box',
        'class' => 'openlayers_simple',
        'file' => 'openlayers_simple.inc',
        'path' => drupal_get_path('module', 'openlayers') . '/plugins/boxes',
      ),
    ),
  );
}

/**
 * Implements hook_quicktabs_contents().
 */
function openlayers_quicktabs_contents() {
  $info = array();
  $path = drupal_get_path('module', 'openlayers') . '/plugins/quicktabs';
  $info['map'] = array(
    'path' => $path,
    'handler' => array(
      'file' => 'QuickMap.inc',
      'class' => 'QuickMap',
    ),
  );
  return $info;
}

/**
 * Implements hook_libraries_info().
 */
function openlayers_libraries_info() {
  $libraries['openlayers'] = array(
    'name' => 'OpenLayers',
    'vendor url' => 'http://openlayers.org/',
    'download url' => 'http://openlayers.org/download/OpenLayers-2.13.tar.gz',
    'version arguments' => array(
      'file' => 'lib/OpenLayers.js',
      'pattern' => '/OpenLayers.VERSION_NUMBER="Release (.*?)"/',
      'lines' => 1000,
    ),
    'files' => array(
      'js' => array(
        'OpenLayers.js',
      ),
    ),
    'variants' => array(
      'original debug' => array(
        'files' => array(
          'js' => array(
            'OpenLayers.debug.js',
          ),
        ),
      ),
      'light' => array(
        'files' => array(
          'js' => array(
            'OpenLayers.light.js',
          ),
        ),
      ),
      'light debug' => array(
        'files' => array(
          'js' => array(
            'OpenLayers.light.debug.js',
          ),
        ),
      ),
      'mobile' => array(
        'files' => array(
          'js' => array(
            'OpenLayers.mobile.js',
          ),
        ),
      ),
      'mobile debug' => array(
        'files' => array(
          'js' => array(
            'OpenLayers.mobile.debug.js',
          ),
        ),
      ),
    ),
    'integration files' => array(
      'openlayers' => array(
        'js' => array(
          'js/openlayers.js',
        ),
        'css' => array(
          'css/openlayers.css',
        ),
      ),
    ),
  );
  return $libraries;
}

/**
 * Alias Functions
 *
 * These functions temporarily map the alias the renamed 'map' functions to their
 * previous 'preset' functions to allow time for contrib modules to catch up.
 * These will eventually be removed
 *
 * These should be removed in 7.x-3.x
 */
function openlayers_build_preset($map = array()) {
  return openlayers_build_map($map);
}
function openlayers_preset_load($name = '', $reset = FALSE) {
  return openlayers_map_load($name, $reset);
}
function openlayers_render_preset_data($map = array(), $map_name = '') {
  return openlayers_render_map_data($map, $map_name);
}
function openlayers_presets($reset = FALSE) {
  return openlayers_maps($reset);
}
function openlayers_preset_save($map) {
  return openlayers_map_save($map);
}
function openlayers_preset_delete($map) {
  return openlayers_map_delete($map);
}
function openlayers_preset_options($reset = FALSE) {
  return openlayers_map_options($reset);
}
function openlayers_error_check_preset($map, $log_errors = TRUE) {
  return openlayers_error_check_map($map, $log_errors);
}
function openlayers_render_preset($map = '', $map_name = '') {
  if (is_array($map)) {
    return openlayers_render_preset_data($map, $map_name);
  }
  else {
    return openlayers_render_map($map);
  }
}

Related topics

Functions

Namesort descending Description
openlayers_add_js_projection_definition Load projection transformations in case OpenLayers does not support projections in use natively
openlayers_behaviors Get all behaviors.
openlayers_behavior_definition_check Check the plugin definition of a behavior. Some field *MUST* be there to work correctly with OL.
openlayers_boxes_plugins Implements hook_boxes_plugins().
openlayers_build_map Prepare a map for rendering.
openlayers_build_preset Alias Functions
openlayers_ctools_plugin_api Implements hook_ctools_plugin_api().
openlayers_ctools_plugin_directory Implements hook_ctools_plugin_directory().
openlayers_ctools_plugin_type Implements hook_ctools_plugin_type().
openlayers_error_check_map Checks map array for incompatibilities or errors.
openlayers_error_check_preset
openlayers_get_all_projections
openlayers_get_extent Get extent given projection
openlayers_get_layer_object Get layer object
openlayers_get_projection
openlayers_get_projection_by_identifier
openlayers_get_resolutions Get resolution given projection
openlayers_help Implements hook_help().
openlayers_include Include necessary CSS and JS for rendering maps
openlayers_layers_load Get all openlayers layers as objects.
openlayers_layer_definition_check Check the plugin definition of a layer. Some field *MUST* be there to work correctly with OL.
openlayers_layer_delete Delete a layer object from the database.
openlayers_layer_load Menu loader for layers. (%openlayers_layer)
openlayers_layer_sanity_check 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
openlayers_layer_types Get all layer types.
openlayers_layer_type_load Menu loader for layer types.
openlayers_libraries_info Implements hook_libraries_info().
openlayers_maps Get maps from DB or code, via cache.
openlayers_map_delete Delete a map object from the database.
openlayers_map_layerlist Rerenders form part “Layers & Styles” when map projections changes
openlayers_map_load Given a map name, get full map object.
openlayers_map_options Get map options in an array suitable for a FormAPI element.
openlayers_map_save Save a map object to the database.
openlayers_object_delete Delete an object from the database.
openlayers_object_enabled Confirm that an object is not disabled.
openlayers_openlayers_behaviors Implements hook_openlayers_behaviors().
openlayers_openlayers_layers Implements hook_openlayers_layers().
openlayers_openlayers_layer_types Implements hook_openlayers_layer_types().
openlayers_openlayers_maps Implements hook_openlayers_maps().
openlayers_openlayers_projections Implements hook_openlayers_projections().
openlayers_openlayers_styles Implements hook_openlayers_styles().
openlayers_presets
openlayers_preset_delete
openlayers_preset_load
openlayers_preset_options
openlayers_preset_save
openlayers_projection_from_record
openlayers_quicktabs_contents Implements hook_quicktabs_contents().
openlayers_render_map Render a map by name
openlayers_render_map_data Render map array
openlayers_render_preset
openlayers_render_preset_data
openlayers_styles Get all openlayers styles.
openlayers_style_delete Delete a style object from the database.
openlayers_style_load Load a style object by name.
openlayers_style_save Save style.
openlayers_theme Implements hook_theme().

Constants

Namesort descending Description
OPENLAYERS_DEFAULT_LIBRARY OpenLayers hosted default library.
OPENLAYERS_HOSTED_API_LIBRARY OpenLayers hosted API version. What version is used when going to http://openlayers.org/api/OpenLayers.js
OPENLAYERS_SUGGESTED_LIBRARY OpenLayers library compatible suggestion.

Classes

Namesort descending Description
openlayers_behavior We define base classes in the core module. All other parent classes can be autoloaded through ctools.
openlayers_layer_type We define base classes in the core module. All other parent classes can be autoloaded through ctools.
openlayers_projection Models a projection, a description of a coordinate system.