summaryrefslogtreecommitdiff
path: root/IkiWiki/Plugin/osm.pm
blob: e6707d420e40ceb66aab164364178692b7bd3349 (plain)
  1. #!/usr/bin/perl
  2. # Copyright 2011 Blars Blarson
  3. # Released under GPL version 2
  4. package IkiWiki::Plugin::osm;
  5. use utf8;
  6. use strict;
  7. use warnings;
  8. use IkiWiki 3.0;
  9. sub import {
  10.     add_underlay("osm");
  11.     hook(type => "getsetup"id => "osm"call => \&getsetup);
  12.     hook(type => "format"id => "osm"call => \&format);
  13.     hook(type => "preprocess"id => "osm"call => \&preprocess);
  14.     hook(type => "preprocess"id => "waypoint"call => \&process_waypoint);
  15.     hook(type => "savestate"id => "waypoint"call => \&savestate);
  16.     hook(type => "cgi"id => "osm"call => \&cgi);
  17. }
  18. sub getsetup () {
  19.     return
  20.         plugin => {
  21.             safe => 1,
  22.             rebuild => 1,
  23.             section => "special-purpose",
  24.         },
  25.         osm_default_zoom => {
  26.             type => "integer",
  27.             example => "15",
  28.             description => "the default zoom when you click on the map link",
  29.             safe => 1,
  30.             rebuild => 1,
  31.         },
  32.         osm_default_icon => {
  33.             type => "string",
  34.             example => "ikiwiki/images/osm.png",
  35.             description => "the icon shown on links and on the main map",
  36.             safe => 0,
  37.             rebuild => 1,
  38.         },
  39.         osm_alt => {
  40.             type => "string",
  41.             example => "",
  42.             description => "the alt tag of links, defaults to empty",
  43.             safe => 0,
  44.             rebuild => 1,
  45.         },
  46.         osm_format => {
  47.             type => "string",
  48.             example => "KML",
  49.             description => "the output format for waypoints, can be KML, GeoJSON or CSV (one or many, comma-separated)",
  50.             safe => 1,
  51.             rebuild => 1,
  52.         },
  53.         osm_tag_default_icon => {
  54.             type => "string",
  55.             example => "icon.png",
  56.             description => "the icon attached to a tag, displayed on the map for tagged pages",
  57.             safe => 0,
  58.             rebuild => 1,
  59.         },
  60.         osm_openlayers_url => {
  61.             type => "string",
  62.             example => "http://www.openlayers.org/api/OpenLayers.js",
  63.             description => "Url for the OpenLayers.js file",
  64.             safe => 0,
  65.             rebuild => 1,
  66.         },
  67.         osm_leaflet_url_stem => {
  68.             type => "string",
  69.             example => "http://cdn.leafletjs.com/leaflet-0.6.4/leaflet",
  70.             description => "Url used as stem for the Leaflet files",
  71.             safe => 0,
  72.             rebuild => 1,
  73.         },
  74.         osm_layers => {
  75.             type => "string",
  76.             example => { 'OSM''GoogleSatellite' },
  77.             description => "Layers to use in the map. Can be either the 'OSM' string or a type option for Google maps (GoogleNormal, GoogleSatellite, GoogleHybrid or GooglePhysical). It can also be an arbitrary URL in a syntax acceptable for OpenLayers.Layer.OSM.url parameter.",
  78.             safe => 0,
  79.             rebuild => 1,
  80.         },
  81.             osm_google_apikey => {
  82.             type => "string",
  83.             example => "",
  84.             description => "Google maps API key, Google layer not used if missing, see https://code.google.com/apis/console/ to get an API key",
  85.             safe => 1,
  86.             rebuild => 1,
  87.         },
  88.         osm_slippy_engine => {
  89.             type => "string",
  90.             example => "openlayers",
  91.             description => "the slippy map engine to use, can be openlayers or leaflet (leaflet always uses GeoJSON, disregarding osm_format)",
  92.             safe => 0,
  93.             rebuild => 1,
  94.         },
  95. }
  96. sub register_rendered_files {
  97.     my $map shift;
  98.     my $page shift;
  99.     my $dest shift;
  100.     if ($page eq $dest) {
  101.         my %formats get_formats();
  102.         if ($formats{'GeoJSON'}) {
  103.             will_render($page"$map/pois.json");
  104.         }
  105.         if ($formats{'CSV'}) {
  106.             will_render($page"$map/pois.txt");
  107.         }
  108.         if ($formats{'KML'}) {
  109.             will_render($page"$map/pois.kml");
  110.         }
  111.     }
  112. }
  113. sub preprocess {
  114.     my %params=@_;
  115.     my $page $params{page};
  116.     my $dest $params{destpage};
  117.     my $loc $params{loc}; # sanitized below
  118.     my $lat $params{lat}; # sanitized below
  119.     my $lon $params{lon}; # sanitized below
  120.     my $href $params{href};
  121.     my ($width$height$float);
  122.     $height scrub($params{'height'} || "300px"$page$dest); # sanitized here
  123.     $width scrub($params{'width'} || "500px"$page$dest); # sanitized here
  124.     $float = (defined($params{'right'}) && 'right') || (defined($params{'left'}) && 'left'); # sanitized here
  125.     
  126.     my $zoom scrub($params{'zoom'// $config{'osm_default_zoom'// 15$page$dest); # sanitized below
  127.     my $map;
  128.     $map $params{'map'} || 'map';
  129.     
  130.     $map scrub($map$page$dest); # sanitized here
  131.     my $name scrub($params{'name'} || $map$page$dest);
  132.     if (defined($lon) || defined($lat) || defined($loc)) {
  133.         ($lon$lat) = scrub_lonlat($loc$lon$lat);
  134.     }
  135.     if ($zoom !~ /^\d\d?$/ || $zoom || $zoom 18) {
  136.         error("Bad zoom");
  137.     }
  138.     if (! defined $href || ! length $href) {
  139.         $href=IkiWiki::cgiurl(
  140.             do => "osm",
  141.             map => $map,
  142.         );
  143.     }
  144.     register_rendered_files($map$page$dest);
  145.     $pagestate{$page}{'osm'}{$map}{'displays'}{$name} = {
  146.         height => $height,
  147.         width => $width,
  148.         float => $float,
  149.         zoom => $zoom,
  150.         fullscreen => 0,
  151.         editable => defined($params{'editable'}),
  152.         lat => $lat,
  153.         lon => $lon,
  154.         href => $href,
  155.         google_apikey => $config{'osm_google_apikey'},
  156.     };
  157.     return "<div id=\"mapdiv-$name\"></div>";
  158. }
  159. sub process_waypoint {
  160.     my %params=@_;
  161.     my $loc $params{'loc'}; # sanitized below
  162.     my $lat $params{'lat'}; # sanitized below
  163.     my $lon $params{'lon'}; # sanitized below
  164.     my $page $params{'page'}; # not sanitized?
  165.     my $dest $params{'destpage'}; # not sanitized?
  166.     my $hidden defined($params{'hidden'}); # sanitized here
  167.     my ($p) = $page =~ /(?:^|\/)([^\/]+)\/?$/; # shorter page name
  168.     my $name scrub($params{'name'} || $p$page$dest); # sanitized here
  169.     my $desc scrub($params{'desc'} || ''$page$dest); # sanitized here
  170.     my $zoom scrub($params{'zoom'// $config{'osm_default_zoom'// 15$page$dest); # sanitized below
  171.     my $icon $config{'osm_default_icon'} || "ikiwiki/images/osm.png"# sanitized: we trust $config
  172.     my $map scrub($params{'map'} || 'map'$page$dest); # sanitized here
  173.     my $alt $config{'osm_alt'"alt=\"$config{'osm_alt'}\"" ''# sanitized: we trust $config
  174.     if ($zoom !~ /^\d\d?$/ || $zoom || $zoom 18) {
  175.         error("Bad zoom");
  176.     }
  177.     ($lon$lat) = scrub_lonlat($loc$lon$lat);
  178.     if (!defined($lat) || !defined($lon)) {
  179.         error("Must specify lat and lon");
  180.     }
  181.     my $tag $params{'tag'};
  182.     foreach my $t (keys %{$typedlinks{$page}{'tag'}}) {
  183.         if ($icon get_tag_icon($t)) {
  184.             $tag $t;
  185.             last;
  186.         }
  187.         $t =~ s!/$config{'tagbase'}/!!;
  188.         if ($icon get_tag_icon($t)) {
  189.             $tag $t;
  190.             last;
  191.         }
  192.     }
  193.     $icon urlto($icon$dest1);
  194.     $icon =~ s!/*$!!# hack - urlto shouldn't be appending a slash in the first place
  195.     $tag '' unless $tag;
  196.     register_rendered_files($map$page$dest);
  197.     $pagestate{$page}{'osm'}{$map}{'waypoints'}{$name} = {
  198.         page => $page,
  199.         desc => $desc,
  200.         icon => $icon,
  201.         tag => $tag,
  202.         lat => $lat,
  203.         lon => $lon,
  204.         # How to link back to the page from the map, not to be
  205.         # confused with the URL of the map itself sent to the
  206.         # embeded map below. Note: used in generated KML etc file,
  207.         # so must be absolute.
  208.         href => urlto($page),
  209.     };
  210.     my $mapurl IkiWiki::cgiurl(
  211.         do => "osm",
  212.         map => $map,
  213.         lat => $lat,
  214.         lon => $lon,
  215.         zoom => $zoom,
  216.     );
  217.     my $output '';
  218.     if (defined($params{'embed'})) {
  219.         $output .= preprocess(%params,
  220.             href => $mapurl,
  221.         );
  222.     }
  223.     if (!$hidden) {
  224.         $output .= "<a href=\"$mapurl\"><img class=\"img\" src=\"$icon\" $alt /></a>";
  225.     }
  226.     return $output;
  227. }
  228. # get the icon from the given tag
  229. sub get_tag_icon($) {
  230.     my $tag shift;
  231.     # look for an icon attached to the tag
  232.     my $attached $tag '/' $config{'osm_tag_default_icon'};
  233.     if (srcfile($attached)) {
  234.         return $attached;
  235.     }
  236.     else {
  237.         return undef;
  238.     }
  239. }
  240. sub scrub_lonlat($$$) {
  241.     my ($loc$lon$lat) = @_;
  242.     if ($loc) {
  243.         if ($loc =~ /^\s*(\-?\d+(?:\.\d*°?|(?:°?|\s)\s*\d+(?:\.\d*\'?|(?:\'|\s)\s*\d+(?:\.\d*)?\"?|\'?)°?)[NS]?)\s*\,?\;?\s*(\-?\d+(?:\.\d*°?|(?:°?|\s)\s*\d+(?:\.\d*\'?|(?:\'|\s)\s*\d+(?:\.\d*)?\"?|\'?)°?)[EW]?)\s*$/) {
  244.             $lat $1;
  245.             $lon $2;
  246.         }
  247.         else {
  248.             error("Bad loc");
  249.         }
  250.     }
  251.     if (defined($lat)) {
  252.         if ($lat =~ /^(\-?)(\d+)(?:(\.\d*)°?|(?:°|\s)\s*(\d+)(?:(\.\d*)\'?|(?:\'|\s)\s*(\d+(?:\.\d*)?\"?)|\'?)|°?)\s*([NS])?\s*$/) {
  253.             $lat $2 + ($3//0) + ((($4//0) + (($5//0) + (($6//0)/60.)))/60.);
  254.             if (($1 eq '-') || (($7//''eq 'S')) {
  255.                 $lat = - $lat;
  256.             }
  257.         }
  258.         else {
  259.             error("Bad lat");
  260.         }
  261.     }
  262.     if (defined($lon)) {
  263.         if ($lon =~ /^(\-?)(\d+)(?:(\.\d*)°?|(?:°|\s)\s*(\d+)(?:(\.\d*)\'?|(?:\'|\s)\s*(\d+(?:\.\d*)?\"?)|\'?)|°?)\s*([EW])?$/) {
  264.             $lon $2 + ($3//0) + ((($4//0) + (($5//0) + (($6//0)/60.)))/60.);
  265.             if (($1 eq '-') || (($7//''eq 'W')) {
  266.                 $lon = - $lon;
  267.             }
  268.         }
  269.         else {
  270.             error("Bad lon");
  271.         }
  272.     }
  273.     if ($lat < -90 || $lat 90 || $lon < -180 || $lon 180) {
  274.         error("Location out of range");
  275.     }
  276.     return ($lon$lat);
  277. }
  278. sub savestate {
  279.     my %waypoints = ();
  280.     my %linestrings = ();
  281.     foreach my $page (keys %pagestate) {
  282.         if (exists $pagestate{$page}{'osm'}) {
  283.             foreach my $map (keys %{$pagestate{$page}{'osm'}}) {
  284.                 foreach my $name (keys %{$pagestate{$page}{'osm'}{$map}{'waypoints'}}) {
  285.                     debug("found waypoint $name");
  286.                     $waypoints{$map}{$name} = $pagestate{$page}{'osm'}{$map}{'waypoints'}{$name};
  287.                 }
  288.             }
  289.         }
  290.     }
  291.     foreach my $page (keys %pagestate) {
  292.         if (exists $pagestate{$page}{'osm'}) {
  293.             foreach my $map (keys %{$pagestate{$page}{'osm'}}) {
  294.                 # examine the links on this page
  295.                 foreach my $name (keys %{$pagestate{$page}{'osm'}{$map}{'waypoints'}}) {
  296.                     if (exists $links{$page}) {
  297.                         foreach my $otherpage (@{$links{$page}}) {
  298.                             if (exists $waypoints{$map}{$otherpage}) {
  299.                                 push(@{$linestrings{$map}}, [
  300.                                     $waypoints{$map}{$name}{'lon'}, $waypoints{$map}{$name}{'lat'} ],
  301.                                     $waypoints{$map}{$otherpage}{'lon'}, $waypoints{$map}{$otherpage}{'lat'} ]
  302.                                 ]);
  303.                             }
  304.                         }
  305.                     }
  306.                 }
  307.             }
  308.             # clear the state, it will be regenerated on the next parse
  309.             # the idea here is to clear up removed waypoints...
  310.             $pagestate{$page}{'osm'} = ();
  311.         }
  312.     }
  313.     my %formats get_formats();
  314.     if ($formats{'GeoJSON'}) {
  315.         writejson(\%waypoints\%linestrings);
  316.     }
  317.     if ($formats{'CSV'}) {
  318.         writecsvs(\%waypoints\%linestrings);
  319.     }
  320.     if ($formats{'KML'}) {
  321.         writekml(\%waypoints\%linestrings);
  322.     }
  323. }
  324. sub writejson($;$) {
  325.     my %waypoints = %{$_[0]};
  326.     my %linestrings = %{$_[1]};
  327.     eval q{use JSON};
  328.     error $if $@;
  329.     foreach my $map (keys %waypoints) {
  330.         my %geojson = ( "type" => "FeatureCollection""features" => []);
  331.         foreach my $name (keys %{$waypoints{$map}}) {
  332.             my %marker = ( "type" => "Feature",
  333.                 "geometry" => { "type" => "Point""coordinates" => [ $waypoints{$map}{$name}{'lon'}, $waypoints{$map}{$name}{'lat'} ] },
  334.                 "properties" => $waypoints{$map}{$name} );
  335.             push @{$geojson{'features'}}, \%marker;
  336.         }
  337.         foreach my $linestring (@{$linestrings{$map}}) {
  338.             my %json  = ( "type" => "Feature",
  339.                 "geometry" => { "type" => "LineString""coordinates" => $linestring });
  340.             push @{$geojson{'features'}}, \%json;
  341.         }
  342.         writefile("pois.json"$config{destdir} . "/$map"to_json(\%geojson));
  343.     }
  344. }
  345. sub writekml($;$) {
  346.     my %waypoints = %{$_[0]};
  347.     my %linestrings = %{$_[1]};
  348.     eval q{use XML::Writer};
  349.     error $if $@;
  350.     foreach my $map (keys %waypoints) {
  351.         my $output;
  352.         my $writer XML::Writer->newOUTPUT => \$output,
  353.             DATA_MODE => 1DATA_INDENT => ' 'ENCODING => 'UTF-8');
  354.         $writer->xmlDecl();
  355.         $writer->startTag("kml""xmlns" => "http://www.opengis.net/kml/2.2");
  356.         $writer->startTag("Document");
  357.         # first pass: get the icons
  358.         my %tags_map = (); # keep track of tags seen
  359.         foreach my $name (keys %{$waypoints{$map}}) {
  360.             my %options = %{$waypoints{$map}{$name}};
  361.             if (!$tags_map{$options{tag}}) {
  362.                 debug("found new style " $options{tag});
  363.                 $tags_map{$options{tag}} = ();
  364.                 $writer->startTag("Style"id => $options{tag});
  365.                 $writer->startTag("IconStyle");
  366.                 $writer->startTag("Icon");
  367.                 $writer->startTag("href");
  368.                 $writer->characters($options{icon});
  369.                 $writer->endTag();
  370.                 $writer->endTag();
  371.                 $writer->endTag();
  372.                 $writer->endTag();
  373.             }
  374.             $tags_map{$options{tag}}{$name} = \%options;
  375.         }
  376.     
  377.         foreach my $name (keys %{$waypoints{$map}}) {
  378.             my %options = %{$waypoints{$map}{$name}};
  379.             $writer->startTag("Placemark");
  380.             $writer->startTag("name");
  381.             $writer->characters($name);
  382.             $writer->endTag();
  383.             $writer->startTag("styleUrl");
  384.             $writer->characters('#' $options{tag});
  385.             $writer->endTag();
  386.             #$writer->emptyTag('atom:link', href => $options{href});
  387.             # to make it easier for us as the atom:link parameter is
  388.             # hard to access from javascript
  389.             $writer->startTag('href');
  390.             $writer->characters($options{href});
  391.             $writer->endTag();
  392.             $writer->startTag("description");
  393.             $writer->characters($options{desc});
  394.             $writer->endTag();
  395.             $writer->startTag("Point");
  396.             $writer->startTag("coordinates");
  397.             $writer->characters($options{lon} . "," $options{lat});
  398.             $writer->endTag();
  399.             $writer->endTag();
  400.             $writer->endTag();
  401.         }
  402.         
  403.         my $i 0;
  404.         foreach my $linestring (@{$linestrings{$map}}) {
  405.             $writer->startTag("Placemark");
  406.             $writer->startTag("name");
  407.             $writer->characters("linestring " $i++);
  408.             $writer->endTag();
  409.             $writer->startTag("LineString");
  410.             $writer->startTag("coordinates");
  411.             my $str '';
  412.             foreach my $coord (@{$linestring}) {
  413.                 $str .= join(','@{$coord}) . \n";
  414.             }
  415.             $writer->characters($str);
  416.             $writer->endTag();
  417.             $writer->endTag();
  418.             $writer->endTag();
  419.         }
  420.         $writer->endTag();
  421.         $writer->endTag();
  422.         $writer->end();
  423.         writefile("pois.kml"$config{destdir} . "/$map"$output);
  424.     }
  425. }
  426. sub writecsvs($;$) {
  427.     my %waypoints = %{$_[0]};
  428.     foreach my $map (keys %waypoints) {
  429.         my $poisf "lat\tlon\ttitle\tdescription\ticon\ticonSize\ticonOffset\n";
  430.         foreach my $name (keys %{$waypoints{$map}}) {
  431.             my %options = %{$waypoints{$map}{$name}};
  432.             my $line 
  433.                 $options{'lat'} . "\t" .
  434.                 $options{'lon'} . "\t" .
  435.                 $name "\t" .
  436.                 $options{'desc'} . '<br /><a href="' $options{'page'} . '">' $name "</a>\t" .
  437.                 $options{'icon'} . "\n";
  438.             $poisf .= $line;
  439.         }
  440.         writefile("pois.txt"$config{destdir} . "/$map"$poisf);
  441.     }
  442. }
  443. # pipe some data through the HTML scrubber
  444. #
  445. # code taken from the meta.pm plugin
  446. sub scrub($$$) {
  447.     if (IkiWiki::Plugin::htmlscrubber->can("sanitize")) {
  448.         return IkiWiki::Plugin::htmlscrubber::sanitize(
  449.             content => shiftpage => shiftdestpage => shift);
  450.     }
  451.     else {
  452.         return shift;
  453.     }
  454. }
  455. # taken from toggle.pm
  456. sub format (@) {
  457.     my %params=@_;
  458.     if ($params{content}=~m!<div[^>]*id="mapdiv-[^"]*"[^>]*>!g) {
  459.         if (! ($params{content}=~s!</body>!include_javascript($params{page})."</body>"!em)) { #"
  460.             # no <body> tag, probably in preview mode
  461.             $params{content}=$params{content} . include_javascript($params{page});
  462.         }
  463.     }
  464.     return $params{content};
  465. }
  466. sub preferred_format() {
  467.     if (get_slippy_engine() eq 'leaflet') {
  468.         $config{'osm_format'} = 'GeoJSON';
  469.     }
  470.     if (!defined($config{'osm_format'}) || !$config{'osm_format'}) {
  471.         $config{'osm_format'} = 'KML';
  472.     }
  473.     my @spl split(/, */$config{'osm_format'});
  474.     return shift @spl;
  475. }
  476. sub get_formats() {
  477.     if (get_slippy_engine() eq 'leaflet') {
  478.         $config{'osm_format'} = 'GeoJSON';
  479.     }
  480.     if (!defined($config{'osm_format'}) || !$config{'osm_format'}) {
  481.         $config{'osm_format'} = 'KML';
  482.     }
  483.     map $_ => split(/, */$config{'osm_format'});
  484. }
  485. sub get_slippy_engine() {
  486.     if (!defined($config{'osm_slippy_engine'}) || !$config{'osm_slippy_engine'}) {
  487.         $config{'osm_slippy_engine'} = 'openlayers';
  488.     }
  489.     return $config{'osm_slippy_engine'};
  490. }
  491. sub include_javascript ($) {
  492.     my $page=shift;
  493.     my $loader;
  494.     if (exists $pagestate{$page}{'osm'}) {
  495.         foreach my $map (keys %{$pagestate{$page}{'osm'}}) {
  496.             foreach my $name (keys %{$pagestate{$page}{'osm'}{$map}{'displays'}}) {
  497.                 $loader .= map_setup_code($map$name, %{$pagestate{$page}{'osm'}{$map}{'displays'}{$name}});
  498.             }
  499.         }
  500.     }
  501.     if ($loader) {
  502.         return embed_map_code($page) . "<script type=\"text/javascript\">$loader</script>";
  503.     }
  504.     else {
  505.             return '';
  506.     }
  507. }
  508. sub cgi($) {
  509.     my $cgi=shift;
  510.     return unless defined $cgi->param('do') &&
  511.         $cgi->param("do"eq "osm";
  512.     
  513.     IkiWiki::loadindex();
  514.     IkiWiki::decode_cgi_utf8($cgi);
  515.     my $map $cgi->param('map');
  516.     if (!defined $map || $map !~ /^[a-z]*$/) {
  517.         error("invalid map parameter");
  518.     }
  519.     print "Content-Type: text/html\r\n";
  520.     print ("\r\n");
  521.     print "<html><body>";
  522.     print "<div id=\"mapdiv-$map\"></div>";
  523.     print embed_map_code();
  524.     print "<script type=\"text/javascript\">";
  525.     print map_setup_code($map$map,
  526.         lat => "urlParams['lat']",
  527.         lon => "urlParams['lon']",
  528.         zoom => "urlParams['zoom']",
  529.         fullscreen => 1,
  530.         editable => 1,
  531.         google_apikey => $config{'osm_google_apikey'},
  532.     );
  533.     print "</script>";
  534.     print "</body></html>";
  535.     exit 0;
  536. }
  537. sub embed_map_code(;$) {
  538.     my $page=shift;
  539.     my $code;
  540.     if (get_slippy_engine() eq 'openlayers') {
  541.         my $olurl $config{osm_openlayers_url} || "http://www.openlayers.org/api/OpenLayers.js";
  542.         $code '<script src="'.$olurl.'" type="text/javascript" charset="utf-8"></script>'."\n".
  543.             '<script src="'.urlto("ikiwiki/osm.js"$page).
  544.             '" type="text/javascript" charset="utf-8"></script>'."\n";
  545.     }
  546.     if (get_slippy_engine() eq 'leaflet') {
  547.         my $leafurlbase $config{osm_leaflet_url_stem} || "http://cdn.leafletjs.com/leaflet-0.6.4/leaflet";
  548.         $code '<link href="'.$leafurlbase.'.css" rel="stylesheet" type="text/css">'."\n".
  549.             '<!--[if lte IE 8]>'."\n".
  550.             '<link href="'.$leafurlbase.'.ie.css" rel="stylesheet" type="text/css">'."\n".
  551.             '<![endif]-->'."\n".
  552.             '<script src="'.$leafurlbase.'.js" type="text/javascript" charset="utf-8"></script>'."\n".
  553.             '<script src="'.urlto("ikiwiki/leaflet.js"$page).
  554.             '" type="text/javascript" charset="utf-8"></script>'."\n";
  555.     }
  556.     if ($config{'osm_google_apikey'}) {
  557.         $code .= '<script src="http://maps.google.com/maps?file=api&amp;v=2&amp;key='.$config{'osm_google_apikey'}.'&sensor=false" type="text/javascript" charset="utf-8"></script>';
  558.     }
  559.     return $code;
  560. }
  561. sub map_setup_code($;@) {
  562.     my $map=shift;
  563.     my $name=shift;
  564.     my %options=@_;
  565.     my $mapurl $config{osm_map_url};
  566.     eval q{use JSON};
  567.     error $if $@;
  568.                 
  569.     $options{'format'} = preferred_format();
  570.     my %formats get_formats();
  571.     if ($formats{'GeoJSON'}) {
  572.         $options{'jsonurl'} = urlto($map."/pois.json");
  573.     }
  574.     if ($formats{'CSV'}) {
  575.         $options{'csvurl'} = urlto($map."/pois.txt");
  576.     }
  577.     if ($formats{'KML'}) {
  578.         $options{'kmlurl'} = urlto($map."/pois.kml");
  579.     }
  580.     if ($mapurl) {
  581.         $options{'mapurl'} = $mapurl;
  582.     }
  583.         $options{'layers'} = $config{osm_layers};
  584.     $name=~s/'//g# $name comes from user input
  585.     return "mapsetup('mapdiv-$name', " to_json(\%options) . ");";
  586. }
  587. 1;