add osm plugin
[ikiwiki] / IkiWiki / Plugin / osm.pm
1 #!/usr/bin/perl
2 # Copyright 2011 Blars Blarson
3 # Released under GPL version 2
4
5 package IkiWiki::Plugin::osm;
6 use utf8;
7 use strict;
8 use warnings;
9 use IkiWiki 3.0;
10
11 sub import {
12     add_underlay("javascript");
13     hook(type => "getsetup", id => "osm", call => \&getsetup);
14     hook(type => "format", id => "osm", call => \&format);
15     hook(type => "preprocess", id => "osm", call => \&preprocess); # backward compatibility
16     hook(type => "preprocess", id => "waypoint", call => \&process_waypoint);
17     hook(type => "savestate", id => "waypoint", call => \&savestate);
18     hook(type => "cgi", id => "osm", call => \&cgi);
19 }
20
21 sub getsetup () {
22     return
23         plugin => {
24             safe => 1,
25             rebuild => 1,
26             section => "special-purpose",
27     },
28     osm_default_zoom => {
29         type => "integer",
30         example => "15",
31         description => "the default zoon when you click on the map link",
32         safe => 1,
33         rebuild => 1,
34     },
35     osm_default_icon => {
36         type => "string",
37         example => "img/osm.png",
38         description => "the icon showed on links and on the main map",
39         safe => 0,
40         rebuild => 1,
41     },
42     osm_alt => {
43         type => "string",
44         example => "",
45         description => "the alt tag of links, defaults to empty",
46         safe => 0,
47         rebuild => 1,
48     },
49     osm_format => {
50         type => "string",
51         example => "KML",
52         description => "the output format for waypoints, can be KML, GeoJSON or CSV (one or many, comma-separated)",
53         safe => 1,
54         rebuild => 1,
55     },
56     osm_tag_default_icon => {
57         type => "string",
58         example => "icon.png",
59         description => "the icon attached to a tag so that pages tagged with that tag will have that icon on the map",
60         safe => 0,
61         rebuild => 1,
62     },
63     osm_tag_icons => {
64         type => "string",
65         example => { 'test' => '/img/test.png',
66                      'trailer' => '/img/trailer.png' ,},
67         description => "tag to icon mapping, leading slash is important!",
68         safe => 0,
69         rebuild => 1,
70     },
71 }
72
73 sub preprocess {
74     my %params=@_;
75     my $page = $params{'page'};
76     my $dest = $params{'destpage'};
77     my $loc = $params{'loc'}; # sanitized below
78     my $lat = $params{'lat'}; # sanitized below
79     my $lon = $params{'lon'}; # sanitized below
80     my $href = $params{'href'};
81
82     my $fullscreen = defined($params{'fullscreen'}); # sanitized here
83     my ($width, $height, $float);
84     if ($fullscreen) {
85         $height = '100%';
86         $width = '100%';
87         $float = 0;
88     } else {
89         $height = scrub($params{'height'} || "300px", $page, $dest); # sanitized here
90         $width = scrub($params{'width'} || "500px", $page, $dest); # sanitized here
91         $float = (defined($params{'right'}) && 'right') || (defined($params{'left'}) && 'left'); # sanitized here
92     }
93     my $zoom = scrub($params{'zoom'} // $config{'osm_default_zoom'} // 15, $page, $dest); # sanitized below
94     my $map;
95     if ($fullscreen) {
96         $map = $params{'map'} || $page;
97     } else {
98         $map = $params{'map'} || 'map';
99     }
100     $map = scrub($map, $page, $dest); # sanitized here
101     my $name = scrub($params{'name'} || $map, $page, $dest);
102
103     if (defined($lon) || defined($lat) || defined($loc)) {
104         ($lon, $lat) = scrub_lonlat($loc, $lon, $lat);
105     }
106
107     if ($zoom !~ /^\d\d?$/ || $zoom < 2 || $zoom > 18) {
108         error("Bad zoom");
109     }
110     $pagestate{$page}{'osm'}{$map}{'displays'}{$name} =
111     {
112         'height' => $height,
113         'width' => $width,
114         'float' => $float,
115         'zoom' => $zoom,
116         'fullscreen' => $fullscreen,
117         'editable' => defined($params{'editable'}),
118         'lat' => $lat,
119         'lon' => $lon,
120         'href' => $href,
121     };
122     return "<div id=\"mapdiv-$name\"></div>";
123 }
124
125 sub process_waypoint {
126     my %params=@_;
127     my $loc = $params{'loc'}; # sanitized below
128     my $lat = $params{'lat'}; # sanitized below
129     my $lon = $params{'lon'}; # sanitized below
130     my $page = $params{'page'}; # not sanitized?
131     my $dest = $params{'destpage'}; # not sanitized?
132     my $hidden = defined($params{'hidden'}); # sanitized here
133     my ($p) = $page =~ /(?:^|\/)([^\/]+)\/?$/; # shorter page name
134     my $name = scrub($params{'name'} || $p, $page, $dest); # sanitized here
135     my $desc = scrub($params{'desc'} || '', $page, $dest); # sanitized here
136     my $zoom = scrub($params{'zoom'} // $config{'osm_default_zoom'} // 15, $page, $dest); # sanitized below
137     my $icon = $config{'osm__default_icon'} || "img/osm.png"; # sanitized: we trust $config
138     my $map = scrub($params{'map'} || 'map', $page, $dest); # sanitized here
139     my $alt = $config{'osm_alt'} ? "alt=\"$config{'osm_alt'}\"" : ''; # sanitized: we trust $config
140     if ($zoom !~ /^\d\d?$/ || $zoom < 2 || $zoom > 18) {
141         error("Bad zoom");
142     }
143
144     ($lon, $lat) = scrub_lonlat($loc, $lon, $lat);
145     if (!defined($lat) || !defined($lon)) {
146         error("Must specify lat and lon");
147     }
148
149     my $tag = $params{'tag'};
150     if ($tag) {
151         if (!defined($config{'osm_tag_icons'}->{$tag})) {
152             error("invalid tag specified, see osm_tag_icons configuration or don't specify any");
153         }
154         $icon = $config{'osm_tag_icons'}->{$tag};
155     } else {
156         foreach my $t (keys %{$typedlinks{$page}{'tag'}}) {
157             if ($icon = get_tag_icon($t)) {
158                 $tag = $t;
159                 last;
160             }
161             $t =~ s!/$config{'tagbase'}/!!;
162             if ($icon = get_tag_icon($t)) {
163                 $tag = $t;
164                 last;
165             }
166         }
167     }
168     $icon = "/img/unknown.png" unless $icon;
169     $tag = '' unless $tag;
170     if ($page eq $dest) {
171         if (!defined($config{'osm_format'}) || !$config{'osm_format'}) {
172             $config{'osm_format'} = 'KML';
173         }
174         my %formats = map { $_ => 1 } split(/, */, $config{'osm_format'});
175         if ($formats{'GeoJSON'}) {
176             will_render($page,$config{destdir} . "/$map/pois.json");
177         }
178         if ($formats{'CSV'}) {
179             will_render($page,$config{destdir} . "/$map/pois.txt");
180         }
181         if ($formats{'KML'}) {
182             will_render($page,$config{destdir} . "/$map/pois.kml");
183         }
184
185     }
186     my $href = "/ikiwiki.cgi?do=osm&map=$map&lat=$lat&lon=$lon&zoom=$zoom";
187     if (defined($destsources{htmlpage($map)})) {
188         $href = urlto($map,$page) . "?lat=$lat&lon=$lon&zoom=$zoom";
189     }
190     $pagestate{$page}{'osm'}{$map}{'waypoints'}{$name} =
191     {
192         'page' => $page,
193         'desc' => $desc,
194         'icon' => $icon,
195         'tag' => $tag,
196         'lat' => $lat,
197         'lon' => $lon,
198         'href' => urlto($page,$map), # how to link back to the page from the map, not to be confused with the URL of the map itself sent to the embeded map below
199     };
200     my $output = '';
201     if (defined($params{'embed'})) {
202         $params{'href'} = $href; # propagate down to embeded
203         $output .= preprocess(%params);
204     }
205     if (!$hidden) {
206         $href =~ s!&!&amp;!g;
207         $output .= "<a href=\"$href\"><img class=\"img\" src=\"$icon\" $alt /></a>";
208     }
209     return $output;
210 }
211
212 # get the icon from the given tag
213 sub get_tag_icon($) {
214     my $tag = shift;
215     # look for an icon attached to the tag
216     my $attached = $tag . '/' . $config{'osm_tag_default_icon'};
217     if (srcfile($attached)) {
218         return $attached;
219     }
220     # look for the old way: mappings
221     if ($config{'osm_tag_icons'}->{$tag}) {
222         return $config{'osm_tag_icons'}->{$tag};
223     } else {
224         return undef;
225     }    
226 }
227
228 sub scrub_lonlat($$$) {
229     my ($loc, $lon, $lat) = @_;
230     if($loc) {
231         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*$/) {
232             $lat = $1;
233             $lon = $2;
234         } else {
235             error("Bad loc");
236         }
237     }
238     if(defined($lat)) {
239         if($lat =~ /^(\-?)(\d+)(?:(\.\d*)°?|(?:°|\s)\s*(\d+)(?:(\.\d*)\'?|(?:\'|\s)\s*(\d+(?:\.\d*)?\"?)|\'?)|°?)\s*([NS])?\s*$/) {
240             $lat = $2 + ($3//0) + ((($4//0) + (($5//0) + (($6//0)/60.)))/60.);
241             if (($1 eq '-') || (($7//'') eq 'S')) {
242                 $lat = - $lat;
243             }
244         } else {
245             error("Bad lat");
246         }
247     }
248     if(defined($lon)) {
249         if($lon =~ /^(\-?)(\d+)(?:(\.\d*)°?|(?:°|\s)\s*(\d+)(?:(\.\d*)\'?|(?:\'|\s)\s*(\d+(?:\.\d*)?\"?)|\'?)|°?)\s*([EW])?$/) {
250             $lon = $2 + ($3//0) + ((($4//0) + (($5//0) + (($6//0)/60.)))/60.);
251             if (($1 eq '-') || (($7//'') eq 'W')) {
252                 $lon = - $lon;
253             }
254         } else {
255             error("Bad lon");
256         }
257     }
258     if ($lat < -90 || $lat > 90 || $lon < -180 || $lon > 180) {
259         error("Location out of range");
260     }
261     return ($lon, $lat);
262 }
263
264 sub savestate {
265     my %waypoints = ();
266     my %linestrings = ();
267     foreach my $page (keys %pagestate) {
268         if (exists $pagestate{$page}{'osm'}) {
269             foreach my $map (keys %{$pagestate{$page}{'osm'}}) {
270                 foreach my $name (keys %{$pagestate{$page}{'osm'}{$map}{'waypoints'}}) {
271                     debug("found waypoint $name");
272                     $waypoints{$map}{$name} = $pagestate{$page}{'osm'}{$map}{'waypoints'}{$name};
273                 }
274             }
275         }
276     }
277     foreach my $page (keys %pagestate) {
278         if (exists $pagestate{$page}{'osm'}) {
279             foreach my $map (keys %{$pagestate{$page}{'osm'}}) {
280                 # examine the links on this page
281                 foreach my $name (keys %{$pagestate{$page}{'osm'}{$map}{'waypoints'}}) {
282                     if (exists $links{$page}) {
283                         foreach my $otherpage (@{$links{$page}}) {
284                             if (exists $waypoints{$map}{$otherpage}) {
285                                 push(@{$linestrings{$map}}, [ [ $waypoints{$map}{$name}{'lon'}, $waypoints{$map}{$name}{'lat'} ], 
286                                                              [ $waypoints{$map}{$otherpage}{'lon'}, $waypoints{$map}{$otherpage}{'lat'} ] ]);
287                             }
288                         }
289                     }
290                 }
291             }
292             # clear the state, it will be regenerated on the next parse
293             # the idea here is to clear up removed waypoints...
294             $pagestate{$page}{'osm'} = ();
295         }
296     }
297     if (!defined($config{'osm_format'}) || !$config{'osm_format'}) {
298         $config{'osm_format'} = 'KML';
299     }
300     my %formats = map { $_ => 1 } split(/, */, $config{'osm_format'});
301     if ($formats{'GeoJSON'}) {
302         writejson(\%waypoints, \%linestrings);
303     }
304     if ($formats{'CSV'}) {
305         writecsvs(\%waypoints, \%linestrings);
306     }
307     if ($formats{'KML'}) {
308         writekml(\%waypoints, \%linestrings);
309     }
310 }
311
312 sub writejson($;$) {
313     my %waypoints = %{$_[0]};
314     my %linestrings = %{$_[1]};
315     eval q{use JSON};
316     error $@ if $@;
317     foreach my $map (keys %waypoints) {
318         my %geojson = ( "type" => "FeatureCollection", "features" => []);
319         foreach my $name (keys %{$waypoints{$map}}) {
320             my %marker = ( "type" => "Feature",
321                            "geometry" => { "type" => "Point", "coordinates" => [ $waypoints{$map}{$name}{'lon'}, $waypoints{$map}{$name}{'lat'} ] },
322                            "properties" => $waypoints{$map}{$name} );
323             push(@{$geojson{'features'}}, \%marker);
324         }
325         foreach my $linestring (@{$linestrings{$map}}) {
326             my %json  = ( "type" => "Feature",
327                             "geometry" => { "type" => "LineString", "coordinates" => $linestring });
328             push(@{$geojson{'features'}}, \%json);
329         }
330         debug('writing pois file pois.json in ' . $config{destdir} . "/$map");
331         writefile("pois.json",$config{destdir} . "/$map",to_json(\%geojson));
332     }
333 }
334
335 sub writekml($;$) {
336     my %waypoints = %{$_[0]};
337     my %linestrings = %{$_[1]};
338     eval q{use XML::Writer};
339     error $@ if $@;
340     foreach my $map (keys %waypoints) {
341         debug("writing pois file pois.kml in " . $config{destdir} . "/$map");
342
343 =pod
344 Sample placemark:
345
346 <?xml version="1.0" encoding="UTF-8"?>
347 <kml xmlns="http://www.opengis.net/kml/2.2">
348   <Placemark>
349     <name>Simple placemark</name>
350     <description>Attached to the ground. Intelligently places itself 
351        at the height of the underlying terrain.</description>
352     <Point>
353       <coordinates>-122.0822035425683,37.42228990140251,0</coordinates>
354     </Point>
355   </Placemark>
356 </kml>
357
358 Sample style:
359
360
361         <Style id="sh_sunny_copy69">
362                 <IconStyle>
363                         <scale>1.4</scale>
364                         <Icon>
365                                 <href>http://waypoints.google.com/mapfiles/kml/shapes/sunny.png</href>
366                         </Icon>
367                         <hotSpot x="0.5" y="0.5" xunits="fraction" yunits="fraction"/>
368                 </IconStyle>
369                 <LabelStyle>
370                         <color>ff00aaff</color>
371                 </LabelStyle>
372         </Style>
373
374
375 =cut
376
377         use IO::File;
378         my $output = IO::File->new(">".$config{destdir} . "/$map/pois.kml");
379
380         my $writer = XML::Writer->new( OUTPUT => $output, DATA_MODE => 1, ENCODING => 'UTF-8');
381         $writer->xmlDecl();
382         $writer->startTag("kml", "xmlns" => "http://www.opengis.net/kml/2.2");
383
384
385         # first pass: get the icons
386         foreach my $name (keys %{$waypoints{$map}}) {
387             my %options = %{$waypoints{$map}{$name}};
388             $writer->startTag("Style", id => $options{tag});
389             $writer->startTag("IconStyle");
390             $writer->startTag("Icon");
391             $writer->startTag("href");
392             $writer->characters($options{icon});
393             $writer->endTag();
394             $writer->endTag();
395             $writer->endTag();
396             $writer->endTag();
397         }
398
399         foreach my $name (keys %{$waypoints{$map}}) {
400             my %options = %{$waypoints{$map}{$name}};
401             $writer->startTag("Placemark");
402             $writer->startTag("name");
403             $writer->characters($name);
404             $writer->endTag();
405             $writer->startTag("styleUrl");
406             $writer->characters('#' . $options{tag});
407             $writer->endTag();
408             #$writer->emptyTag('atom:link', href => $options{href});
409             $writer->startTag('href'); # to make it easier for us as the atom:link parameter is hard to access from javascript
410             $writer->characters($options{href});
411             $writer->endTag();
412             $writer->startTag("description");
413             $writer->characters($options{desc});
414             $writer->endTag();
415             $writer->startTag("Point");
416             $writer->startTag("coordinates");
417             $writer->characters($options{lon} . "," . $options{lat});
418             $writer->endTag();
419             $writer->endTag();
420             $writer->endTag();
421         }
422
423         my $i = 0;
424         foreach my $linestring (@{$linestrings{$map}}) {
425             $writer->startTag("Placemark");
426             $writer->startTag("name");
427             $writer->characters("linestring " . $i++);
428             $writer->endTag();
429             $writer->startTag("LineString");
430             $writer->startTag("coordinates");
431             my $str = '';
432             foreach my $coord (@{$linestring}) {
433                 $str .= join(',', @{$coord}) . " \n";
434             }
435             $writer->characters($str);
436             $writer->endTag();
437             $writer->endTag();
438             $writer->endTag();
439         }
440         $writer->endTag();
441         $writer->end();
442         $output->close();
443     }
444 }
445
446 sub writecsvs($;$) {
447     my %waypoints = %{$_[0]};
448     foreach my $map (keys %waypoints) {
449         my $poisf = "lat\tlon\ttitle\tdescription\ticon\ticonSize\ticonOffset\n";
450         foreach my $name (keys %{$waypoints{$map}}) {
451             my %options = %{$waypoints{$map}{$name}};
452             my $line = 
453                 $options{'lat'} . "\t" .
454                 $options{'lon'} . "\t" .
455                 $name . "\t" .
456                 $options{'desc'} . '<br /><a href="' . $options{'page'} . '">' . $name . "</a>\t" .
457                 $options{'icon'} . "\n";
458             $poisf .= $line;
459         }
460         debug("writing pois file pois.txt in " . $config{destdir} . "/$map");
461         writefile("pois.txt",$config{destdir} . "/$map",$poisf);
462     }
463 }
464
465 # pipe some data through the HTML scrubber
466 #
467 # code taken from the meta.pm plugin
468 sub scrub($$$) {
469         if (IkiWiki::Plugin::htmlscrubber->can("sanitize")) {
470                 return IkiWiki::Plugin::htmlscrubber::sanitize(
471                         content => shift, page => shift, destpage => shift);
472         }
473         else {
474                 return shift;
475         }
476 }
477
478 # taken from toggle.pm
479 sub format (@) {
480     my %params=@_;
481
482     if ($params{content}=~m!<div[^>]*id="mapdiv-[^"]*"[^>]*>!g) {
483         if (! ($params{content}=~s!</body>!include_javascript($params{page})."</body>"!em)) {
484             # no <body> tag, probably in preview mode
485             $params{content}=$params{content} . include_javascript($params{page});
486         }
487     }
488     return $params{content};
489 }
490
491 sub prefered_format() {
492     if (!defined($config{'osm_format'}) || !$config{'osm_format'}) {
493         $config{'osm_format'} = 'KML';
494     }
495     my @spl = split(/, */, $config{'osm_format'});
496     return shift @spl;
497 }
498
499 sub include_javascript ($) {
500     my $page=shift;
501     my $loader;
502
503     eval q{use JSON};
504     error $@ if $@;
505     if (exists $pagestate{$page}{'osm'}) {
506         foreach my $map (keys %{$pagestate{$page}{'osm'}}) {
507             foreach my $name (keys %{$pagestate{$page}{'osm'}{$map}{'displays'}}) {
508                 my %options = %{$pagestate{$page}{'osm'}{$map}{'displays'}{$name}};
509                 $options{'map'} = $map;
510                 $options{'format'} = prefered_format();
511                 $loader .= "mapsetup(\"mapdiv-$name\", " . to_json(\%options) . ");\n";
512             }
513         }
514     }
515     if ($loader) {
516         return embed_map_code() . "<script type=\"text/javascript\" charset=\"utf-8\">$loader</script>";
517     } else {
518         return '';
519     }
520 }
521
522 sub cgi($) {
523     my $cgi=shift;
524
525     return unless defined $cgi->param('do') &&
526         $cgi->param("do") eq "osm";
527
528     IkiWiki::decode_cgi_utf8($cgi);
529
530     my $map = $cgi->param('map');
531     if (!defined $map || $map !~ /^[a-z]*$/) {
532         error("invalid map parameter");
533     }
534
535     print "Content-Type: text/html\r\n";
536     print ("\r\n");
537     print "<html><body>";
538     print "<div id=\"mapdiv-$map\"></div>";
539     print embed_map_code($map);
540     print "<script type=\"text/javascript\" charset=\"utf-8\">mapsetup( 'mapdiv-$map', { 'map': '$map', 'lat': urlParams['lat'], 'lon': urlParams['lon'], 'zoom': urlParams['zoom'], 'fullscreen': 1, 'editable': 1, 'format': '" . prefered_format() . "'});</script>";
541     print "</body></html>";
542
543     exit 0;
544 }
545
546 sub embed_map_code() {
547     return <<EOF;
548   <script src="http://www.openlayers.org/api/OpenLayers.js"></script>
549   <script>
550 // taken from http://stackoverflow.com/questions/901115/get-query-string-values-in-javascript
551 var urlParams = {};
552 (function () {
553     var e,
554         a = /\\+/g,  // Regex for replacing addition symbol with a space
555         r = /([^&=]+)=?([^&]*)/g,
556         d = function (s) { return decodeURIComponent(s.replace(a, " ")); },
557         q = window.location.search.substring(1);
558
559     while (e = r.exec(q))
560        urlParams[d(e[1])] = d(e[2]);
561 })();
562
563 function mapsetup(divname, options) {
564     div = document.getElementById(divname);
565     if (options.fullscreen) {
566         permalink = 'permalink';
567         div.style.top = 0;
568         div.style.left = 0;
569         div.style.position = 'absolute';
570         div.style.width = '100%';
571         div.style.height = '100%';
572     } else {
573         div.style.height = options.height;
574         div.style.width = options.width;
575         div.style.float = options.float;
576         permalink = {base: options.href, title: "View larger map"};
577     }
578     map = new OpenLayers.Map(divname, {
579         controls: [
580             new OpenLayers.Control.Navigation(),
581             new OpenLayers.Control.ScaleLine(),
582             new OpenLayers.Control.Permalink(permalink)
583         ],
584         displayProjection: new OpenLayers.Projection("EPSG:4326"),
585         numZoomLevels: 18
586     });
587
588
589     map.addLayer(new OpenLayers.Layer.OSM());
590     if (options.format == 'CSV') {
591         pois = new OpenLayers.Layer.Text( "CSV",
592                                           { location:"/" + options.map + "/pois.txt",
593                                             projection: map.displayProjection
594                                           });
595     } else if (options.format == 'GeoJSON') {
596         pois = new OpenLayers.Layer.Vector("GeoJSON", {
597             protocol: new OpenLayers.Protocol.HTTP({
598                 url: "/" + options.map + "/pois.json",
599                 format: new OpenLayers.Format.GeoJSON()
600             }),
601             strategies: [new OpenLayers.Strategy.Fixed()]
602         });
603     } else {
604         pois = new OpenLayers.Layer.Vector("KML", {
605             protocol: new OpenLayers.Protocol.HTTP({
606                 url: "/" + options.map + "/pois.kml",
607                 format: new OpenLayers.Format.KML({
608                     extractStyles: true,
609                     extractAttributes: true
610                 })
611             }),
612             strategies: [new OpenLayers.Strategy.Fixed()]});
613     }
614     map.addLayer(pois);
615     select = new OpenLayers.Control.SelectFeature(pois);
616     map.addControl(select);
617     select.activate();
618
619     pois.events.on({
620         "featureselected": function (event) {
621             var feature = event.feature;
622             var content = '<h2><a href="' + feature.attributes.href + '">' +feature.attributes.name + "</a></h2>" + feature.attributes.description;
623             popup = new OpenLayers.Popup.FramedCloud("chicken",
624                                                      feature.geometry.getBounds().getCenterLonLat(),
625                                                      new OpenLayers.Size(100,100),
626                                                      content,
627                                                      null, true, function () {select.unselectAll()});
628             feature.popup = popup;
629             map.addPopup(popup);
630         },
631         "featureunselected": function (event) {
632             var feature = event.feature;
633             if(feature.popup) {
634                 map.removePopup(feature.popup);
635                 feature.popup.destroy();
636                 delete feature.popup;
637             }
638         }
639     });
640
641     if (options.editable) {
642         vlayer = new OpenLayers.Layer.Vector( "Editable" );
643         map.addControl(new OpenLayers.Control.EditingToolbar(vlayer));
644         map.addLayer(vlayer);
645     }
646
647     if (options.fullscreen) {
648         map.addControl(new OpenLayers.Control.PanZoomBar());
649         map.addControl(new OpenLayers.Control.LayerSwitcher({'ascending':false}));
650         map.addControl(new OpenLayers.Control.MousePosition());
651         map.addControl(new OpenLayers.Control.KeyboardDefaults());
652     } else {
653         map.addControl(new OpenLayers.Control.ZoomPanel());
654     }
655
656     //Set start centrepoint and zoom    
657     if (!options.lat || !options.lon) {
658         options.lat = urlParams['lat'];
659         options.lon = urlParams['lon'];
660     }
661     if (!options.zoom) {
662         options.zoom = urlParams['zoom'];
663     }
664     if (options.lat && options.lon) {
665         var lat = options.lat;
666         var lon = options.lon;
667         var zoom= options.zoom || 10;
668         center = new OpenLayers.LonLat( lon, lat )
669             .transform(
670                 new OpenLayers.Projection("EPSG:4326"), // transform from WGS 1984
671                 map.getProjectionObject() // to Spherical Mercator Projection
672             );
673         map.setCenter (center, zoom);
674     } else {
675         pois.events.register("loadend", this, function () { map.zoomToExtent(pois.getDataExtent()); });
676     }
677 }
678   </script>
679 EOF
680 }
681
682 1;