kml.module in KML 5
Same filename and directory in other branches
KML Module
Creates Google Earth KML feeds from location-enabled nodes in Drupal. @author Dan Karran (geodaniel) <dan at karran dot net>
File
kml.moduleView source
<?php
/**
* KML Module
*
* @file
* Creates Google Earth KML feeds from location-enabled nodes in Drupal.
* @author Dan Karran (geodaniel) <dan at karran dot net>
*/
/**
* Implementation of hook_help().
*/
function kml_help($section = 'admin/help#kml') {
$output = '';
switch ($section) {
case 'admin/help#kml':
$output = t('<p>The KML module allows you to create KML feeds from Drupal for use in Google Earth. It requires the use of the location module to assign geographic locations to nodes.</p>');
$output .= t('<p>You can</p>
<ul>
<li>administer KML feed options at !admin_link.</li>
</ul>', array(
'!admin_link' => l('admin » settings » kml', 'admin/settings/kml', NULL, NULL, NULL, FALSE, TRUE),
));
return $output;
case 'admin/modules#description':
return t('Module to feed KML from Drupal to Google Earth');
}
}
/**
* Implementation of hook_perm().
*/
function kml_perm() {
return array(
'access kml',
'administer kml',
);
}
/**
* Implementation of hook_menu().
*/
function kml_menu($may_cache) {
$items = array();
if ($may_cache) {
$items[] = array(
'path' => 'kml',
'title' => t('KML feeds'),
'callback' => 'kml_interface',
'access' => user_access('access kml'),
'type' => MENU_SUGGESTED_ITEM,
);
}
else {
$items[] = array(
'path' => 'admin/settings/kml',
'title' => t('KML'),
'description' => t('Settings for the KML module'),
'callback' => 'drupal_get_form',
'callback arguments' => array(
'kml_admin_settings',
),
'access' => user_access('administer kml'),
'type' => MENU_NORMAL_ITEM,
);
}
return $items;
}
/**
* Implementation of hook_simpletest().
*/
function kml_simpletest() {
return array_keys(file_scan_directory(drupal_get_path('module', 'kml') . '/tests', '\\.test$'));
}
/**
* Default callback
*/
function kml_interface($a = NULL, $b = NULL, $c = NULL, $d = NULL, $e = NULL) {
$sortmode = variable_get('kml_sortmode', 'n.created');
$sortorder = variable_get('kml_sortorder', 'asc');
if ($a == 'node') {
if (is_numeric($b)) {
/* Single node */
if ($c == 'networklink') {
$attributes['kml_feed'] = url('kml/node/' . $b, NULL, NULL, TRUE);
kml_networklink($attributes);
}
else {
$cache_name = 'kml:node:' . $b;
if (function_exists('location_newapi')) {
$nodes = db_query("SELECT n.nid, n.created FROM {node} n INNER JOIN {location_instance} l ON n.vid = l.vid WHERE n.status = 1 AND n.nid = %d", $b);
}
else {
$nodes = db_query("SELECT n.nid, n.created FROM {node} n WHERE n.status = 1 AND n.vid IN (SELECT l.eid FROM {location} l WHERE l.type = 'node' AND l.eid = n.vid) AND n.nid = %d", $b);
}
_kml_feed_check_access($nodes, $attributes, $cache_name);
}
}
else {
if ($b == 'networklink') {
/* Network link for all location-enabled nodes */
$attributes['kml_feed'] = url('kml/node/', NULL, NULL, TRUE);
kml_networklink($attributes);
}
else {
/* All location-enabled nodes */
$cache_name = 'kml:node';
if (function_exists('location_newapi')) {
$nodes = db_query("SELECT n.nid, n.created FROM {node} n INNER JOIN {location_instance} l ON n.vid = l.vid WHERE n.status = 1 ORDER BY %s %s", $sortmode, $sortorder);
}
else {
$nodes = db_query("SELECT n.nid, n.created FROM {node} n WHERE n.status = 1 AND n.vid IN (SELECT l.eid FROM {location} l WHERE l.type = 'node' AND l.eid = n.vid) ORDER BY %s %s", $sortmode, $sortorder);
}
_kml_feed_check_access($nodes, $attributes, $cache_name);
}
}
}
else {
if ($a == 'term' && is_numeric($b)) {
if (module_exists('taxonomy')) {
if ($term = taxonomy_get_term($b)) {
$tag = $term->name;
if ($c == 'networklink') {
/* Network link for all location-enabled nodes tagged with a certain term */
$attributes['kml_feed'] = url('kml/term/' . $b . '/', NULL, NULL, TRUE);
$attributes['title'] = t('Tag: %tag', array(
'%tag' => $tag,
));
$attributes['description'] = t('Nodes tagged with %tag', array(
'%tag' => $tag,
));
kml_networklink($attributes);
}
else {
/* All location-enabled nodes tagged with a certain term */
$cache_name = 'kml:term:' . $b;
if (function_exists('location_newapi')) {
$nodes = db_query("SELECT n.nid, n.created FROM {node} n INNER JOIN {term_node} tn ON n.nid = tn.nid INNER JOIN {location_instance} l ON n.vid = l.vid WHERE n.status = 1 AND tn.tid = %d ORDER BY %s %s", $b, $sortmode, $sortorder);
}
else {
$nodes = db_query("SELECT n.nid, n.created FROM {node} n INNER JOIN {term_node} tn ON n.nid = tn.nid AND n.vid = tn.vid WHERE n.status = 1 AND n.vid IN (SELECT l.eid FROM {location} l WHERE l.type = 'node' AND l.eid = n.vid) AND tn.tid = %d ORDER BY %s %s", $b, $sortmode, $sortorder);
}
$attributes['title'] = t('Tag: %tag', array(
'%tag' => $tag,
));
$attributes['description'] = t('Nodes tagged with %tag', array(
'%tag' => $tag,
));
_kml_feed_check_access($nodes, $attributes, $cache_name);
}
}
else {
drupal_not_found();
}
}
}
else {
if ($a == 'group' && is_numeric($b)) {
if (module_exists('og')) {
$groupnode = node_load($b);
if (og_is_group_type($groupnode->type)) {
// TODO: check also to see if user is part of group
$group_name = $groupnode->title;
if ($c == 'networklink') {
/* Network link for all location-enabled nodes in a certain group */
$attributes['kml_feed'] = url('kml/group/' . $b . '/', NULL, NULL, TRUE);
$attributes['title'] = t('Group: %group', array(
'%group' => $group_name,
));
$attributes['description'] = t('Nodes in %group', array(
'%group' => $group_name,
));
kml_networklink($attributes);
}
else {
/* All location-enabled nodes in a certain group */
$cache_name = 'kml:group:' . $b;
if (function_exists('location_newapi')) {
$nodes = db_query("SELECT n.nid, n.created FROM {node} n INNER JOIN {node_access} na ON n.nid = na.nid INNER JOIN {location_instance} l ON n.vid = l.vid WHERE n.status = 1 AND na.gid = %d ORDER BY %s %s", $b, $sortmode, $sortorder);
}
else {
$nodes = db_query("SELECT n.nid, n.created FROM {node} n INNER JOIN {node_access} na ON n.nid = na.nid WHERE n.status = 1 AND n.vid IN (SELECT l.eid FROM {location} l WHERE l.type = 'node' AND l.eid = n.vid) AND na.gid = %d ORDER BY %s %s", $b, $sortmode, $sortorder);
}
$attributes['title'] = t('Group: %group', array(
'%group' => $group_name,
));
$attributes['description'] = t('Nodes in %group', array(
'%group' => $group_name,
));
_kml_feed_check_access($nodes, $attributes, $cache_name);
}
}
else {
drupal_not_found();
}
}
else {
drupal_not_found();
}
}
else {
if ($a == 'view' && $b) {
if (module_exists('views')) {
$b = check_plain($b);
$view = module_invoke('views', 'get_view', $b);
if ($view->url) {
if ($c == 'networklink') {
/* Network link for nodes in a view */
if ($view->page_type == 'kml') {
// a view intended to be used as KML feed
$attributes['kml_feed'] = url($view->url . '/', NULL, NULL, TRUE);
}
else {
// any other view
$attributes['kml_feed'] = url('kml/view/' . $view->name . '/', NULL, NULL, TRUE);
}
$attributes['title'] = t('%view_title', array(
'%view_title' => $view->page_title,
));
$attributes['description'] = t('%view_description', array(
'%view_description' => $view->description,
));
kml_networklink($attributes);
}
else {
/* Invoke views module to get the page defined by the view */
if ($view->page_type == 'kml') {
print module_invoke('views', 'build_view', 'page', $view, $args);
}
else {
$nodes = module_invoke('views', 'build_view', 'items', $view, $args);
theme_kml_feed($view, $nodes['items'], 'page');
}
}
}
else {
drupal_not_found();
}
}
else {
drupal_not_found();
}
}
else {
if ($a == 'search' && $b == 'node' && $c) {
if (module_exists('search')) {
$type = check_plain($b);
$keys = check_plain($c);
if ($d == 'networklink') {
/* Network link for all nodes returned by a certain search */
$attributes['kml_feed'] = url('kml/search/' . $type . '/' . $keys . '/', NULL, NULL, TRUE);
$attributes['title'] = t('Search: %terms', array(
'%terms' => $keys,
));
$attributes['description'] = t('Nodes matching %terms', array(
'%terms' => $keys,
));
kml_networklink($attributes);
}
else {
/* All nodes returned by a certain search */
$nodes = module_invoke($type, 'search', 'search', $keys);
$attributes['title'] = t('Search: %terms', array(
'%terms' => $keys,
));
$attributes['description'] = t('Nodes matching %terms', array(
'%terms' => $keys,
));
_kml_feed($nodes, $attributes);
}
}
else {
drupal_not_found();
}
}
else {
if ($a == 'user') {
// TODO: user locations
}
else {
kml_page();
}
}
}
}
}
}
}
/**
* Form for settings page
*/
function kml_admin_settings() {
$form['individual_nodes'] = array(
'#type' => 'fieldset',
'#title' => t('Node display'),
'#description' => t('You can add a KML link to the bottom of location-enabled nodes using these options.'),
);
$form['individual_nodes']['kml_nodelink'] = array(
'#type' => 'checkbox',
'#title' => t('Add KML link to footer of each full node that has coordinates'),
'#default_value' => variable_get('kml_nodelink', 1),
);
$form['individual_nodes']['kml_nodelink_teaser'] = array(
'#type' => 'checkbox',
'#title' => t('Add KML link to footer of each node teaser that has coordinates'),
'#default_value' => variable_get('kml_nodelink_teaser', 0),
);
$form['multiple_nodes'] = array(
'#type' => 'fieldset',
'#title' => t('Node ordering'),
'#description' => t('Choose the order in which multiple nodes are sent in KML feeds. Google Earth should display them in this order, allowing a user to fly through them.'),
);
$sort_modes = array(
'n.created' => 'Time created',
'n.changed' => 'Time changed',
'u.name' => 'Author',
'n.title' => 'Title',
);
$sort_orders = array(
'asc' => 'Ascending',
'desc' => 'Descending',
);
$form['multiple_nodes']['kml_sortmode'] = array(
'#type' => 'radios',
'#title' => t('Sort mode'),
'#default_value' => variable_get('kml_sortmode', 'n.created'),
'#options' => $sort_modes,
'#description' => t('Note that this will not affect KML feeds defined through views module.'),
);
$form['multiple_nodes']['kml_sortorder'] = array(
'#type' => 'radios',
'#title' => t('Sort order'),
'#default_value' => variable_get('kml_sortorder', 'asc'),
'#options' => $sort_orders,
);
$form['time_information'] = array(
'#type' => 'fieldset',
'#title' => t('Time information'),
'#description' => t('Choose the type of time information to include in the KML feeds. This will allow users to filter the information by time in Google Earth.'),
);
$time_types = array(
'created' => 'Time created',
'changed' => 'Time changed',
'none' => 'None',
);
$form['time_information']['kml_timetype'] = array(
'#type' => 'radios',
'#title' => t('Timestamp'),
'#default_value' => variable_get('kml_timetype', 'created'),
'#options' => $time_types,
);
$form['style'] = array(
'#type' => 'fieldset',
'#title' => t('Display style'),
'#description' => t('Customise how nodes will be displayed in Google Earth.'),
);
$form['style']['kml_sitelogo_url'] = array(
'#type' => 'textfield',
'#title' => t('Site logo'),
'#default_value' => variable_get('kml_sitelogo_url', ''),
'#description' => t("Add the URL to an image to include a logo in the top left of the Google Earth screen."),
'#length' => 40,
);
$form['style']['kml_altitude'] = array(
'#type' => 'textfield',
'#title' => t('Node altitude (meters)'),
'#default_value' => variable_get('kml_altitude', 0),
'#description' => t("This will extrude a node from the earth by the specified distance. A line will join the icon to the position on earth. It is useful if you don't want to obscure an area with its own icon."),
'#size' => '2',
);
$form['style']['kml_extrude'] = array(
'#type' => 'checkbox',
'#title' => t('Extrude nodes'),
'#default_value' => variable_get('kml_extrude', 0),
'#description' => t("This will draw a line joining the icon to the position on earth if an altitude is set above."),
);
$content_types = node_get_types('names');
foreach ($content_types as $name => $title) {
$form['style']['placemarks'][$name] = array(
'#type' => 'fieldset',
'#title' => t('Google Earth Highlight placemark url for ') . $title,
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['style']['placemarks'][$name]['kml_highlightplacemark_url_' . $name] = array(
'#type' => 'textfield',
'#title' => t('Google Earth Highlight placemark url'),
'#default_value' => variable_get('kml_highlightplacemark_url_' . $name, 'http://maps.google.com/mapfiles/kml/paddle/red-stars.png'),
'#description' => t("Add the URL to an image to use as Google Earth highlight placemark."),
'#length' => 40,
);
$form['style']['placemarks'][$name]['kml_normalplacemark_url_' . $name] = array(
'#type' => 'textfield',
'#title' => t('Google Earth normal placemark url'),
'#default_value' => variable_get('kml_normalplacemark_url_' . $name, 'http://maps.google.com/mapfiles/kml/paddle/wht-blank.png'),
'#description' => t("Add the URL to an image to use as Google Earth normal placemark."),
'#length' => 40,
);
}
$form['network_links'] = array(
'#type' => 'fieldset',
'#title' => t('Network links'),
'#description' => t('Network links are simply a pointer to the current version of the KML feed. A user need only download a Network link once, and have the data within it automatically refresh to the latest data from this Drupal site.'),
);
$use_networklinks = array(
1 => 'Yes',
0 => 'No',
);
$form['network_links']['kml_usenetworklinks'] = array(
'#type' => 'radios',
'#title' => t('Use network links?'),
'#default_value' => variable_get('kml_usenetworklinks', 1),
'#description' => t('Plain feeds are good if the user does not need the information to stay up-to-date. Note that you may have problems when using network links on a site which requires users to log in, and you will require a module such as securesite to provide a standard HTTP Auth login option for any KML feeds.'),
'#options' => $use_networklinks,
);
$refresh_modes = array(
'never' => 'Never',
'onStop' => 'onStop',
'onRequest' => 'onRequest',
);
$form['network_links']['kml_refreshmode'] = array(
'#type' => 'radios',
'#title' => t('Refresh mode'),
'#default_value' => variable_get('kml_refreshmode', 'onStop'),
'#description' => t('When information should be requested from the server.'),
'#options' => $refresh_modes,
);
$form['network_links']['kml_refreshtime'] = array(
'#type' => 'textfield',
'#title' => t('Refresh delay'),
'#default_value' => variable_get('kml_refreshtime', 60),
'#description' => t("The number of seconds to wait before refreshing the Network Link after the view in Google Earth has stopped moving. Requires 'onStop' to be selected above."),
'#maxlength' => '3',
'#size' => '2',
);
$form['file_format'] = array(
'#type' => 'fieldset',
'#title' => t('File format'),
'#description' => t('Specify the settings to be used for the KML files produced.'),
);
//$kmz_disabled = 0; // TODO: add KMZ file support - http://drupal.org/node/289832
$kmz_disabled = 1;
$kmz_note = t('<em><a href="http://drupal.org/node/289832">KMZ support</a> is not yet available.</em>');
$form['file_format']['kml_usekmz'] = array(
'#type' => 'checkbox',
'#title' => t('Compress KML into KMZ?'),
'#default_value' => variable_get('kml_usekmz', 0),
'#description' => t('By letting the module compress your KML files into KMZ format you will save a lot on bandwidth and your users will be able to download your data quicker.') . ' ' . $kmz_note,
'#disabled' => $kmz_disabled,
);
// TODO: allow for token module support in here so sites can create dynamic filenames
$form['file_format']['kml_filename'] = array(
'#type' => 'textfield',
'#title' => t('KML filename'),
'#default_value' => variable_get('kml_filename', 'nodes'),
'#description' => t('Filename to use for KML feeds. Note that .kml or .kmz will be appended to the above string, so do not include that yourself.'),
);
$form['file_format']['kml_networklinkfilename'] = array(
'#type' => 'textfield',
'#title' => t('Network Link filename'),
'#default_value' => variable_get('kml_networklinkfilename', 'networklink'),
'#description' => t('Filename to use for KML network links. Note that .kml or .kmz will be appended to the above string, so do not include that yourself.'),
);
return system_settings_form($form);
}
/**
* Displays directory of KML feeds available
*/
function kml_page() {
// TODO: actually list all terms, groups, etc with links to their feeds
$content = t('<p>You can view all of the location-enabled content from this site using Google Earth. Simply click the icon and if prompted, tell your browser to open the file with Google Earth.</p>') . "\n";
$url = 'kml/node';
if (variable_get('kml_usenetworklinks', 1)) {
$url .= '/networklink';
}
$feeds[] = theme('kml_link', $url) . ' ' . l('All content from this site', $url);
$content .= theme('item_list', $feeds);
print theme('page', $content);
}
/**
* Implementation of hook_block()
*/
function kml_block($op = 'list', $delta = 0, $edit = array()) {
switch ($op) {
case 'list':
$blocks[0]['info'] = t('KML links for node groupings');
return $blocks;
case 'configure':
$form = array();
if ($delta == 0) {
}
return $form;
case 'save':
if ($delta == 0) {
}
return;
case 'view':
default:
switch ($delta) {
case 0:
$block['subject'] = t('View this content in Google Earth');
$block['content'] = kml_block_content($delta);
$block['weight'] = -6;
$block['enabled'] = 1;
$block['region'] = 'right';
break;
}
return $block;
}
}
/**
* Function to generate block content
*/
function kml_block_content($block) {
if ($block == 0) {
// TODO: allow admin to choose if network links are used, or normal links
if (arg(0) == 'taxonomy' && arg(1) == 'term') {
$path = 'kml/term/' . arg(2);
if (variable_get('kml_usenetworklinks', 1)) {
$path .= '/networklink';
}
return theme('kml_link', url($path));
}
else {
if (module_exists('og') && ($groupnode = og_get_group_context())) {
if ($groupnode->nid == arg(1)) {
// show block only on group homepage
$path = 'kml/group/' . $groupnode->nid;
if (variable_get('kml_usenetworklinks', 1)) {
$path .= '/networklink';
}
return theme('kml_link', url($path));
}
}
else {
if (arg(0) == 'search' && arg(1) == 'node' && arg(2)) {
$path = 'kml/search/' . arg(1) . '/' . arg(2);
if (variable_get('kml_usenetworklinks', 1)) {
$path .= '/networklink';
}
return theme('kml_link', url($path));
}
}
}
}
}
/**
* Implementation of hook_link(). Adds KML links to individual nodes
*/
function kml_link($type, $node = 0, $main = 0) {
$links = array();
// if node type is location enabled and has location info
if (variable_get('location_maxnum_' . $node->type, 0) && $node->location) {
if (variable_get('kml_nodelink', 1) && $main == 0 || variable_get('kml_nodelink_teaser', 0) && $main == 1) {
$url = 'kml/node/' . $node->nid;
if (variable_get('kml_usenetworklinks', 1)) {
$url .= '/networklink';
}
$links['kml_link_node'] = array(
'title' => t('KML'),
'href' => $url,
'attributes' => array(
'title' => t('View location in Google Earth'),
),
);
}
}
return $links;
}
/**
* Standardised KML link
*/
function theme_kml_link($link) {
return theme_kml_icon($link, 'kml');
}
/**
* Display the KML icon
*/
function theme_kml_icon($url, $type) {
$icon = drupal_get_path('module', 'kml') . '/images/kml.gif';
$text = 'View in Google Earth';
if ($image = theme('image', $icon, $text, $text)) {
return '<a href="' . check_url($url) . '" class="feed-icon">' . $image . '</a>';
}
}
/**
* Send KML feed.
*/
function _kml_send_feed($output) {
$extension = variable_get('kml_usekmz', 0) == 1 ? '.kmz' : '.kml';
$filename = variable_get('kml_filename', 'nodes') . $extension;
drupal_set_header('Content-Type: application/vnd.google-earth.kml+xml');
drupal_set_header('Content-Disposition: attachment; filename="' . $filename . '"');
print $output;
module_invoke_all('exit');
exit;
}
/*
* Look in the cache to see if there is something.
* Check that the nodes we've been asked to build are not newer.
* Send the cahe if OK, or rebuild and save.
*/
function _kml_feed_check_access($nodes, $attributes, $cache_name) {
/* Get the node list and take note of latest change */
$max_changed = 0;
$hash_list = array();
$nodes_array = array();
while ($node = db_fetch_object($nodes)) {
$node = node_load($node->nid);
if (node_access('view', $node)) {
$nodes_array[] = $node;
$hash_list[] = $node->nid;
if ($node->changed > $max_changed) {
$max_changed = $node->changed;
}
}
}
sort($hash_list);
$cid = $cache_name . ':' . md5(join('+', $hash_list));
_kml_feed_check_cache($nodes_array, $attributes, $cid, $max_changed);
}
function _kml_feed_check_cache($nodes, $attributes, $cid, $changed) {
$cache = cache_get($cid);
if ($cache && $cache->created > $changed) {
/* Cache is good, return it */
_kml_send_feed(unserialize($cache->data));
}
/* Not in the cache, or it's out of date - rebuild. */
_kml_feed($nodes, $attributes, $cid);
}
/**
* Displays a KML feed containing all location-enabled nodes.
*/
function _kml_feed($nodes, $attributes = array(), $cid = FALSE) {
$title = $attributes['title'] ? $attributes['title'] : variable_get('site_name', 'drupal');
$link = $attributes['link'] ? $attributes['link'] : url('', NULL, NULL, TRUE);
$description = $attributes['description'] ? $attributes['description'] : variable_get('site_mission', '');
$channel['title'] = $title;
$channel['link'] = $link;
$channel['description'] = $description;
$output = _kml_format_feed($nodes, $channel);
if ($cid) {
cache_set($cid, 'cache', serialize($output), time() + variable_get('kml_cache_lifetime', 3600));
}
_kml_send_feed($output);
}
/**
* Provide views plugins for creating KML feeds.
*/
function kml_views_style_plugins() {
return array(
'kml' => array(
'name' => t('KML feed'),
'theme' => 'kml_feed',
'needs_table_header' => TRUE,
'needs_fields' => TRUE,
),
);
}
function kml_views_feed_argument($op, &$view, $arg) {
if ($op == 'argument' && $arg == 'kml') {
$view->page_type = 'kml';
}
else {
if ($op == 'post_view') {
$path = views_post_view_make_url($view, $arg, 'kml');
if (variable_get('kml_usenetworklinks', 1)) {
// TODO: need a better way of dealing with network links using args.
$path = 'kml/view/' . $view->name . '/networklink';
}
$url = url($path, NULL, NULL, TRUE);
drupal_add_link(array(
'rel' => 'alternate',
'type' => 'application/vnd.google-earth.kml+xml',
'title' => t('kml'),
'href' => $url,
));
return theme('kml_icon', $url, 'kml');
}
}
}
/**
* Views plugin that displays the KML feed for views
*/
function theme_kml_feed($view, $nodes, $type) {
if ($type == 'block') {
return;
}
$attributes['title'] = t('%view_title', array(
'%view_title' => $view->page_title,
));
$attributes['description'] = t('%view_description', array(
'%view_description' => $view->description,
));
/* Get changed date for cache. */
$max_changed = 0;
$hash_list = array();
foreach ($nodes as $node) {
$hash_list[] = $node->nid;
/* Get the changed for this node for cache */
$changed = db_result(db_query("SELECT n.changed FROM {node} n WHERE n.nid = %d", $node->nid));
if ($changed > $max_changed) {
$max_changed = $changed;
}
}
sort($hash_list);
$cache_name = 'kml:view:' . $view->vid;
$cid = $cache_name . ':' . md5(join('+', $hash_list));
/* There's no need to check access because views does that for us */
_kml_feed_check_cache($nodes, $attributes, $cid, $max_changed);
}
/**
* A generic function for generating KML (Keyhole Markup Language for Google Earth) feeds from a set of nodes.
*
* @param $nodes
* An object as returned by db_query() which contains the nid field.
* @param $channel
* An associative array containing title, link, description and other keys.
* The link should be an absolute URL.
*/
function _kml_format_feed($nodes_array = array(), $channel = array()) {
$items = '';
// get altitude to display placemarks at, and whether to extrude
$altitude = variable_get('kml_altitude', 0);
$kml_extra['altitudeMode'] = 'relativeToGround';
// TODO: make configurable?
if ($kml_extrude = variable_get('kml_extrude', 0)) {
$kml_extra['extrude'] = variable_get('kml_extrude', 0);
}
if ($nodes_array) {
/*
* If we're going to load nodes, might as well remember then for
* later use.
*/
foreach ($nodes_array as &$node) {
// Load the specified node:
if ($node = node_load($node->nid)) {
// TODO: using db_rewrite_sql above may make node_load redundant
// TODO: check to make sure the node has geo properties
$link = url('node/' . $node->nid, NULL, NULL, 1);
// Filter and prepare node teaser
if (node_hook($node, 'view')) {
node_invoke($node, 'view', TRUE, FALSE);
}
else {
$node = node_prepare($node, TRUE);
}
// Allow modules to change $node->teaser before viewing.
node_invoke_nodeapi($node, 'view', true, false);
// Allow modules to add additional item fields
$extra = node_invoke_nodeapi($node, 'kml item');
$extra = array_merge($extra, $kml_extra);
// TODO: if node has more than one location, add a folder containing the locations as placemarks
$items .= kml_format_placemark($node, $link, $altitude, $extra);
}
}
}
$output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" . "<kml xmlns=\"http://earth.google.com/kml/2.1\">\n" . " <Document>\n";
$content_types = node_get_types('names');
foreach ($content_types as $name => $title) {
$output .= ' <Style id="highlightPlacemark_' . $name . '">' . "\n";
$output .= ' <IconStyle>' . "\n";
$output .= ' <Icon>' . "\n";
$output .= ' <href>' . variable_get('kml_highlightplacemark_url_' . $name, 'http://maps.google.com/mapfiles/kml/paddle/red-stars.png') . '</href>' . "\n";
$output .= ' </Icon>' . "\n";
$output .= ' </IconStyle>' . "\n";
$output .= ' </Style>' . "\n";
$output .= ' <Style id="normalPlacemark_' . $name . '">' . "\n";
$output .= ' <IconStyle>' . "\n";
$output .= ' <Icon>' . "\n";
$output .= ' <href>' . variable_get('kml_normalplacemark_url_' . $name, 'http://maps.google.com/mapfiles/kml/paddle/wht-blank.png') . '</href>' . "\n";
$output .= ' </Icon>' . "\n";
$output .= ' </IconStyle>' . "\n";
$output .= ' </Style>' . "\n";
$output .= ' <StyleMap id="myStyleMap_' . $name . '">' . "\n";
$output .= ' <Pair>' . "\n";
$output .= ' <key>normal</key>' . "\n";
$output .= ' <styleUrl>#normalPlacemark_' . $name . '</styleUrl>' . "\n";
$output .= ' </Pair>' . "\n";
$output .= ' <Pair>' . "\n";
$output .= ' <key>highlight</key>' . "\n";
$output .= ' <styleUrl>#highlightPlacemark_' . $name . '</styleUrl>' . "\n";
$output .= ' </Pair>' . "\n";
$output .= ' </StyleMap>' . "\n";
}
// See if any modules want to add anything to the file.
// Pass in the list of nodes we'll present for their reference.
$feed_extras = module_invoke_all('kml_feed_extras', $nodes_array);
foreach ($feed_extras as $feed_extra) {
$output .= $feed_extra;
}
// Add site logo if one is specified
if ($sitelogo = variable_get('kml_sitelogo_url', '')) {
$output .= " <ScreenOverlay>\n" . " <name><![CDATA[" . t('%site logo', array(
'%site' => $channel['title'],
)) . "]]></name>\n" . " <Icon>\n" . " <href>" . $sitelogo . "</href>\n" . " </Icon>\n" . " <overlayXY x=\"0\" y=\"1\" xunits=\"fraction\" yunits=\"fraction\"/>\n" . " <screenXY x=\"0\" y=\"1\" xunits=\"fraction\" yunits=\"fraction\"/>\n" . " <size x=\"0\" y=\"0\" xunits=\"fraction\" yunits=\"fraction\"/>\n" . " </ScreenOverlay>\n";
}
$output .= kml_format_folder($channel['title'], $channel['description'], $items, $extras) . " </Document>\n" . "</kml>\n";
return $output;
}
/**
* Formats a KML Folder (based on format_rss_channel()).
*
* Arbitrary elements may be added using the $args associative array.
*/
function kml_format_folder($title, $description, $items, $args = array()) {
// arbitrary elements may be added using the $args associative array
$output = " <Folder>\n" . ' <name>' . check_plain($title) . "</name>\n" . ' <description>' . check_plain($description) . "</description>\n";
if ($args) {
// TODO: needs better way of structuring this data so we can embed elements and their attributes
foreach ($args as $key => $value) {
if (is_array($value)) {
if ($value['key']) {
$output .= ' <' . $value['key'];
if (is_array($value['attributes'])) {
$output .= drupal_attributes($value['attributes']);
}
if ($value['value']) {
$output .= '>' . $value['value'] . '</' . $value['key'] . ">\n";
}
else {
$output .= " />\n";
}
}
}
else {
$output .= ' <' . $key . '>' . check_plain($value) . "</{$key}>\n";
}
}
}
$output .= $items . " </Folder>\n";
return $output;
}
/**
* Format a single KML Placemark (based on format_rss_item()).
*
* Arbitrary elements may be added using the $args associative array.
*/
function kml_format_placemark($item, $link, $alt = 0, $args = array()) {
$title = $item->title;
$output = " <Placemark>\n" . ' <name>' . check_plain($title) . "</name>\n" . " <description><![CDATA[\n" . theme('kml_placemark_description', $item, $link) . "\n]]></description>\n" . ' <styleUrl>#myStyleMap_' . $item->type . '</styleUrl>' . "\n";
$time_type = variable_get('kml_timetype', 'created');
if ($time_type != 'none') {
if ($time_type == 'created') {
$timestamp = date("Y-m-d\\TH:i:s\\Z", $item->created);
}
else {
if ($time_type == 'changed') {
$timestamp = date("Y-m-d\\TH:i:s\\Z", $item->changed);
}
}
if ($timestamp) {
$output .= " <TimeStamp><when>" . $timestamp . "</when></TimeStamp>\n";
}
}
// Strip any geometry information from args
$geometrys = array();
$force_multi = FALSE;
// Handle Placemark on it's own.
if ($args['Placemark']) {
$extra_places = $args['Placemark'];
unset($args['Placemark']);
}
// Handle MultiGeometry on it's own.
if ($args['MultiGeometry']) {
$geometrys[] = $args['MultiGeometry'] . "\n";
unset($args['MultiGeometry']);
$force_multi = TRUE;
}
$geo_types = array(
'Point',
'LineString',
'LinearRing',
'Polygon',
'Model',
);
foreach ($geo_types as $geo_type) {
if ($args[$geo_type]) {
$geometrys[] = "<{$geo_type}>" . $args[$geo_type] . "</{$geo_type}>\n";
unset($args[$geo_type]);
}
}
// Coordinate information
$lat = $item->location['latitude'];
$long = $item->location['longitude'];
if ($lat && $long) {
$point = " <Point>\n";
$pointelements = array(
'altitudeMode',
'extrude',
);
// elements that extend Point
foreach ($pointelements as $pointelement) {
if ($args[$pointelement]) {
$point .= " <{$pointelement}>" . $args[$pointelement] . "</{$pointelement}>\n";
unset($args[$pointelement]);
}
}
$point .= ' <coordinates>' . $long . ',' . $lat . ',' . $alt . "</coordinates>\n";
$point .= " </Point>\n";
$geometrys[] = $point;
}
if ($force_multi or count($geometrys) > 1) {
$output .= " <MultiGeometry>\n";
while ($geometrys) {
$output .= ' ' . array_shift($geometrys);
}
$output .= " </MultiGeometry>\n";
}
else {
if (count($geometrys)) {
$output .= array_shift($geometrys);
}
}
// Address information. Needs city and country at a minimum.
// Includes <address> element as well as <AddressDetails> based on xAL format
// see http://www.oasis-open.org/committees/ciq/Downloads/xNAL/xAL/Versions/xALv2_0/
if ($item->location['city'] && $item->location['country']) {
if ($item->location['name']) {
$address['name'] = check_plain($item->location['name']);
}
if ($item->location['street']) {
$address['street'] = check_plain($item->location['street']);
}
if ($item->location['additional']) {
$address['additional'] = check_plain($item->location['additional']);
}
if ($item->location['city']) {
$address['city'] = check_plain($item->location['city']);
}
if ($item->location['province']) {
$address['province'] = check_plain($item->location['province']);
}
if ($item->location['postal_code']) {
$address['postal_code'] = check_plain($item->location['postal_code']);
}
if ($item->location['country']) {
if (module_exists('location')) {
// look up country name if location module is enabled
$countries = location_get_iso3166_list();
$address['country'] = $countries[$item->location['country']];
}
else {
$address['country'] = check_plain($item->location['country']);
}
}
// Single line address
$output .= ' <address>' . implode(', ', $address) . "</address>\n";
$output .= " <AddressDetails>\n";
$output .= " <Country>\n";
$output .= " <AddressLine>" . $address['country'] . "</AddressLine>\n";
if ($address['province']) {
$output .= " <AdministrativeArea>\n";
$output .= " <AddressLine>" . $address['province'] . "</AddressLine>\n";
}
$output .= " <Locality>\n";
$output .= " <AddressLine>" . $address['city'] . "</AddressLine>\n";
if ($address['street']) {
$output .= " <Thoroughfare>\n";
$output .= " <AddressLine>" . $address['street'] . "</AddressLine>\n";
$output .= " </Thoroughfare>\n";
}
if ($address['postal_code']) {
$output .= " <PostalCode>\n";
$output .= " <AddressLine>" . $address['postal_code'] . "</AddressLine>\n";
$output .= " </PostalCode>\n";
}
$output .= " </Locality>\n";
if ($address['province']) {
$output .= " </AdministrativeArea>\n";
}
$output .= " </Country>\n";
$output .= " </AddressDetails>\n";
}
if ($args) {
// TODO: needs better way of structuring this data so we can embed elements and their attributes
foreach ($args as $key => $value) {
if (is_array($value)) {
if ($value['key']) {
$output .= ' <' . $value['key'];
if (is_array($value['attributes'])) {
$output .= drupal_attributes($value['attributes']);
}
if ($value['value']) {
$output .= '>' . $value['value'] . '</' . $value['key'] . ">\n";
}
else {
$output .= " />\n";
}
}
}
else {
$output .= ' <' . $key . '>' . check_plain($value) . "</{$key}>\n";
}
}
}
$output .= " </Placemark>\n";
// Add in extra_places if set
if (isset($extra_places)) {
if (is_array($extra_places)) {
foreach ($extra_places as $placemark) {
$output .= " <Placemark>\n{$placemark}\n</Placemark>\n";
}
}
else {
$output .= " <Placemark>\n{$extra_places}\n</Placemark>\n";
}
}
return $output;
}
/**
* Theme the contents of the placemark
*/
function theme_kml_placemark_description($item, $link) {
$output = check_markup($item->teaser);
$output .= '<br/><a href="' . check_url($link) . '">' . t('View page') . '</a>';
return $output;
}
/**
* A function for creating a KML Network Link for a specific KML feed.
*
*/
function kml_networklink($attributes = array()) {
$title = $attributes['title'] ? $attributes['title'] : variable_get('site_name', 'drupal');
// set link to KML feed to send out through Network Link
$kml_feed = $attributes['kml_feed'] ? $attributes['kml_feed'] : url('kml/node', NULL, NULL, TRUE);
// TODO: allow these to be customised in a settings page
$url['name'] = $title;
$url['href'] = $kml_feed;
$url['viewRefreshMode'] = variable_get('kml_refreshmode', 'onStop');
$url['viewRefreshTime'] = variable_get('kml_refreshtime', 2);
$output = kml_format_networklink($title, $kml_feed, $url);
$extension = variable_get('kml_usekmz', 0) == 1 ? '.kmz' : '.kml';
$filename = variable_get('kml_networklinkfilename', 'networklink') . $extension;
drupal_set_header('Content-Type: application/vnd.google-earth.kml+xml');
drupal_set_header('Content-Disposition: attachment; filename="' . $filename . '"');
print $output;
}
/**
* A generic function for generating a KML (Keyhole Markup Language for Google Earth) Network Link.
*
* Arbitrary elements may be added using the $args associative array.
*/
function kml_format_networklink($title, $kml_feed, $url_items = array(), $args = array()) {
$output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
$output .= "<kml xmlns=\"http://earth.google.com/kml/2.0\">\n";
$output .= " <Document>\n";
$output .= " <NetworkLink>\n";
$output .= " <Url>\n";
foreach ($url_items as $url_item => $value) {
$output .= ' <' . $url_item . '>' . check_plain($value) . '</' . $url_item . ">\n";
}
$output .= " </Url>\n";
$output .= " </NetworkLink>\n";
$output .= " </Document>\n";
$output .= "</kml>\n";
return $output;
}
Functions
Name | Description |
---|---|
kml_admin_settings | Form for settings page |
kml_block | Implementation of hook_block() |
kml_block_content | Function to generate block content |
kml_format_folder | Formats a KML Folder (based on format_rss_channel()). |
kml_format_networklink | A generic function for generating a KML (Keyhole Markup Language for Google Earth) Network Link. |
kml_format_placemark | Format a single KML Placemark (based on format_rss_item()). |
kml_help | Implementation of hook_help(). |
kml_interface | Default callback |
kml_link | Implementation of hook_link(). Adds KML links to individual nodes |
kml_menu | Implementation of hook_menu(). |
kml_networklink | A function for creating a KML Network Link for a specific KML feed. |
kml_page | Displays directory of KML feeds available |
kml_perm | Implementation of hook_perm(). |
kml_simpletest | Implementation of hook_simpletest(). |
kml_views_feed_argument | |
kml_views_style_plugins | Provide views plugins for creating KML feeds. |
theme_kml_feed | Views plugin that displays the KML feed for views |
theme_kml_icon | Display the KML icon |
theme_kml_link | Standardised KML link |
theme_kml_placemark_description | Theme the contents of the placemark |
_kml_feed | Displays a KML feed containing all location-enabled nodes. |
_kml_feed_check_access | |
_kml_feed_check_cache | |
_kml_format_feed | A generic function for generating KML (Keyhole Markup Language for Google Earth) feeds from a set of nodes. |
_kml_send_feed | Send KML feed. |