| 1 | Using HTML::Template allows (almost) complete separation of programming from display. Not only is this useful if separating responsibility for programming from html and web design, it is also a cleaner code which allows for easy changes later on as new functionality is added to the application. |
| 2 | |
| 3 | There are many templating solutions in the Perl world. I like HTML::Template for its utter simplicity and effectiveness at doing what it claims to do. |
| 4 | |
| 5 | The following Perl script/template combo shows a simple example of making a map and displaying it. |
| 6 | ---- |
| 7 | {{{ |
| 8 | #!perl |
| 9 | #!/usr/bin/perl -w |
| 10 | |
| 11 | # Import the modules |
| 12 | use strict; |
| 13 | use CGI::Pretty qw(:standard); |
| 14 | use HTML::Template; |
| 15 | use mapscript; |
| 16 | |
| 17 | # Create new template |
| 18 | my $template = HTML::Template->new( filename => 'index.tmpl' ); |
| 19 | |
| 20 | $ENV{MS_ERRORFILE} = "path/to/mapserver.log"; # Mapserver error log |
| 21 | |
| 22 | # create a new map object |
| 23 | my $mapObj = new mapscript::mapObj("mymapfile.map") or die("$mapscript::ms_error->{message}"); |
| 24 | |
| 25 | my @draw_layers = param('draw_layer'); # layers to be drawn submitted by the user |
| 26 | # submitted as form variables, usually via |
| 27 | # checkboxes checked or unchecked for the |
| 28 | # layers to be drawn. |
| 29 | |
| 30 | my @layers; # layers to be sent back to the browser to |
| 31 | # construct the set of checkboxes so the |
| 32 | # user may choose what to draw |
| 33 | |
| 34 | my $tool = param("tool") eq "" ? "zoomin" : param("tool"); # currently selected tool |
| 35 | |
| 36 | my $x = param("map.x"); # coordinates of the mouseclick on the map |
| 37 | my $y = $mapObj->{height} - param("map.y"); # invert the y coordinate because image |
| 38 | # origin is top-left while map origin is |
| 39 | # bottom-left |
| 40 | |
| 41 | my $zf = 2; # zoom in/out by this much |
| 42 | my $jitter = 20; # Catch jerky mouse movement |
| 43 | |
| 44 | my $mapname_suf = time() . ".png"; # Tmp map and legend image names |
| 45 | my $mapimgname = $mapObj->{name} . "_map_" . $mapname_suf; # |
| 46 | |
| 47 | # Store max extent |
| 48 | my $mminx = $mapObj->{extent}->{minx}; |
| 49 | my $mminy = $mapObj->{extent}->{miny}; |
| 50 | my $mmaxx = $mapObj->{extent}->{maxx}; |
| 51 | my $mmaxy = $mapObj->{extent}->{maxy}; |
| 52 | |
| 53 | # variables to hold new extent |
| 54 | my ($minx, $miny, $maxx, $maxy); |
| 55 | |
| 56 | # $currect are the current geo rect coords used to calc the new geo rect coords. |
| 57 | # $currect arrives at the server as a hidden form field submitted by the user. |
| 58 | # split the form field value into component bounding coordinates. |
| 59 | my ($f_minx, $f_miny, $f_maxx, $f_maxy) = split(" ", param("currect")); |
| 60 | |
| 61 | # Check if this is the first visit or a return visit. If $f_minx variable |
| 62 | # (or any of the form variables calculated above) is empty then this is the |
| 63 | # first visit |
| 64 | if (($f_minx eq "") || ($tool eq "zoomall")) { |
| 65 | |
| 66 | # make new extent equal to max extent |
| 67 | ($minx, $miny, $maxx, $maxy) = ($mminx, $mminy, $mmaxx, $mmaxy); |
| 68 | |
| 69 | for (0..$mapObj->{numlayers} - 1) { |
| 70 | my $layerObj = $mapObj->getLayer($_); |
| 71 | my %layer = ( |
| 72 | 'layername' => $layerObj->{name}, |
| 73 | 'layerindx' => $layerObj->{index}, |
| 74 | 'layerchek' => $layerObj->{status} ? "checked" : "" |
| 75 | ); |
| 76 | push(@layers, \%layer); |
| 77 | } |
| 78 | |
| 79 | } else { |
| 80 | |
| 81 | # Calc the amount of $zf based on the kind of redraw tool. |
| 82 | # Pan will have a $zf of 1, while zoomout will inverse the $zf. |
| 83 | # Leave $zf alone in case of zoomin. |
| 84 | if ($tool eq "pan") { $zf = 1; } elsif ($tool eq "zoomput") { $zf = 1 / $zf; } |
| 85 | |
| 86 | # Calculate cellsize in x and y directions. cellsize is the geographic |
| 87 | # size of each pixel on the screen. |
| 88 | my $old_ground_width = $f_maxx - $f_minx; |
| 89 | my $old_ground_height = $f_maxy - $f_miny; |
| 90 | |
| 91 | my $cx = $old_ground_width / $mapObj->{width}; |
| 92 | my $cy = $old_ground_height / $mapObj->{height}; |
| 93 | |
| 94 | my $new_ground_width = $old_ground_width / $zf; |
| 95 | my $new_ground_height = $old_ground_height / $zf; |
| 96 | |
| 97 | # use the cellsize and the zoom to calc the new extent |
| 98 | $minx = ($f_minx + ($x * $cx)) - ($new_ground_width / 2); |
| 99 | $miny = ($f_miny + ($y * $cy)) - ($new_ground_height / 2); |
| 100 | $maxx = ($f_minx + ($x * $cx)) + ($new_ground_width / 2); |
| 101 | $maxy = ($f_miny + ($y * $cx)) + ($new_ground_height / 2); |
| 102 | |
| 103 | # now figure out what layers to draw. |
| 104 | # loop over all the layers... |
| 105 | for (0..$mapObj->{numlayers} - 1) { |
| 106 | |
| 107 | # get the layer |
| 108 | my $layerObj = $mapObj->getLayer($_); |
| 109 | |
| 110 | # toggle layer on if requested by user, otherwise toggle off |
| 111 | $layerObj->{status} = grep($layerObj->{index} == $_, @draw_layers) ? 1 : 0; |
| 112 | my %layer = ( |
| 113 | 'layername' => $layerObj->{name}, |
| 114 | 'layerindx' => $layerObj->{index}, |
| 115 | 'layerchek' => $layerObj->{status} ? "checked" : "" |
| 116 | ); |
| 117 | push(@layers, \%layer); |
| 118 | |
| 119 | } |
| 120 | |
| 121 | } |
| 122 | |
| 123 | # set the new map extent |
| 124 | $mapObj->{extent}->{minx} = $minx; $mapObj->{extent}->{miny} = $miny; |
| 125 | $mapObj->{extent}->{maxx} = $maxx; $mapObj->{extent}->{maxy} = $maxy; |
| 126 | |
| 127 | # create the map |
| 128 | my $imgObj = $mapObj->draw() or die('Unable to draw map'); |
| 129 | $imgObj->saveImage( |
| 130 | $mapimgname, |
| 131 | $mapscript::MS_PNG, |
| 132 | $mapObj->{interlace}, |
| 133 | $mapObj->{transparent}, |
| 134 | $mapObj->{imagequality} |
| 135 | ); |
| 136 | |
| 137 | # ref to array of layers info to be sent back to the browser |
| 138 | $template->param(layers => \@layers); |
| 139 | |
| 140 | # create vars to be sent back to the browser |
| 141 | $template->param(map_img => "path/to/$mapimgname"); |
| 142 | |
| 143 | $template->param(currect => "$minx $miny $maxx $maxy"); |
| 144 | |
| 145 | #reset the tool to zoomin, if necessary |
| 146 | $template->param(tool => ($tool eq "zoomall") ? "zoomin" : "$tool"); |
| 147 | |
| 148 | # Send the obligatory Content-Type and output the template |
| 149 | print "Content-Type: text/html\n\n"; |
| 150 | print $template->output; |
| 151 | exit(0); |
| 152 | }}} |
| 153 | Meanwhile, in the template... |
| 154 | {{{ |
| 155 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> |
| 156 | |
| 157 | <html> |
| 158 | <head> |
| 159 | <title>My Mapping Application</title> |
| 160 | </head> |
| 161 | |
| 162 | <body> |
| 163 | |
| 164 | <form name="mapform" action="index.cgi" method="post"> |
| 165 | |
| 166 | <table> |
| 167 | <!-- start map toolbar --> |
| 168 | <tr> |
| 169 | <td colspan="2"> |
| 170 | <input type="radio" name="tool" value="zoomin" checked> |
| 171 | <input type="radio" name="tool" value="zoomout"> |
| 172 | <input type="radio" name="tool" value="zoomall" onClick="document.mapform.submit();"> |
| 173 | <input type="radio" name="tool" value="pan"> |
| 174 | </td> |
| 175 | </tr> |
| 176 | <!-- end map toolbar --> |
| 177 | |
| 178 | <tr> |
| 179 | <!-- start checkboxes for layers --> |
| 180 | <td> |
| 181 | <!-- start layers loop --> |
| 182 | <tmpl_loop layers> |
| 183 | <input type="checkbox" name="draw_layer" value="<tmpl_var layerindx>" <tmpl_var layerchek>> |
| 184 | <tmpl_var layername> |
| 185 | </tmpl_loop> |
| 186 | <!-- end layers loop --> |
| 187 | </td> |
| 188 | <!-- end checkboxes for layers --> |
| 189 | |
| 190 | <!-- start map image --> |
| 191 | <td> |
| 192 | <input type="image" name="map" src="<tmpl_var map_img>" border="1"> |
| 193 | <input type="hidden" name="currect" value="<tmpl_var currect>"> |
| 194 | <input type="hidden" name="tool" value="<tmpl_var tool>"> |
| 195 | </td> |
| 196 | <!-- end map image --> |
| 197 | </tr> |
| 198 | </table> |
| 199 | |
| 200 | </form> |
| 201 | |
| 202 | </body> |
| 203 | </html> |
| 204 | }}} |
| 205 | I encourage reading documentation for HTML::Template, and using it or some other templating solution to create web applications. Email me if further help is needed, or ask on the list. |
| 206 | |
| 207 | -- Puneet Kishor (pkishor at geoanalytics dot com) |
| 208 | ---- |
| 209 | back to PerlMapScript |