| 1 | = mapquakes.pl = |
| 2 | Here's the pilot script that !MapScript was written from. It's called mapquakes.pl and is an example that integrates remote data (earthquake locations from the USGS) with a local raster image of the earth. Here's how it works: |
| 3 | |
| 4 | 1) use the perl LWP module to retrieve a text file from USGS servers that contains the locations and magnitudes of the most recent earthquakes worldwide |
| 5 | |
| 6 | 2) parse the file, storing the location and magnitude information |
| 7 | |
| 8 | 3) draw the raster layer (global topographic data) |
| 9 | |
| 10 | 4) loop through the quakes drawing each in turn, scaling the symbol by the magnitude. Each quake is labeled with an ID that is used to reference the quake in a lookup table that is presented below the interface. |
| 11 | |
| 12 | 5) save the final map, and return a page to the user |
| 13 | |
| 14 | The script shows how to build a simple pan/zoom interface and details how one might build maps from disparate sources. Map files and topographic data are available upon request. This should run just fine with MapServer version 3.5 and I will post a version that runs with 3.6 soon. |
| 15 | |
| 16 | Steve |
| 17 | |
| 18 | ---- |
| 19 | {{{ |
| 20 | #!perl |
| 21 | #!/usr/bin/perl |
| 22 | |
| 23 | use CGI qw(:standard :html escape); |
| 24 | use LWP::Simple; |
| 25 | use mapscript; |
| 26 | |
| 27 | # |
| 28 | # defaults, make any necessary changes here... |
| 29 | # |
| 30 | $quake_map = '/home/sdlime/mapquakes/quakes.map'; |
| 31 | $quake_source = 'ftp://ghtftp.cr.usgs.gov/pub/quake'; |
| 32 | |
| 33 | @quakes = ''; |
| 34 | $quake_data = ''; |
| 35 | $quake_timestamp = ''; |
| 36 | |
| 37 | $image_path = '/usr/local/apache/htdocs/tmp/'; |
| 38 | $image_virtual_path = '/tmp/'; |
| 39 | $image_id = $$ . time() . ".gif"; |
| 40 | $map = ''; |
| 41 | |
| 42 | $zoom_direction = 0; |
| 43 | $zoom_size = 2; |
| 44 | |
| 45 | sub get_quakes { |
| 46 | $quake_data = get $quake_source or die 'get failed'; |
| 47 | |
| 48 | my $i = 0; |
| 49 | my $is_quake = 0; |
| 50 | |
| 51 | foreach (split(/^/, $quake_data)) { |
| 52 | $quake_timestamp = $_ if /^Updated as of/; |
| 53 | |
| 54 | if($is_quake) { |
| 55 | $quakes[$i]{lat} = substr($_, 18, 7); |
| 56 | $quakes[$i]{lon} = substr($_, 26, 7); |
| 57 | $quakes[$i]{magnitude} = substr($_, 40, 3); |
| 58 | |
| 59 | if($quakes[$i]{lon} =~ /E$/) { |
| 60 | $quakes[$i]{lon} =~ s/E//; |
| 61 | } else { |
| 62 | $quakes[$i]{lon} =~ s/W//; |
| 63 | $quakes[$i]{lon} *= -1; |
| 64 | } |
| 65 | |
| 66 | if($quakes[$i]{lat} =~ /N$/) { |
| 67 | $quakes[$i]{lat} =~ s/N//; |
| 68 | } else { |
| 69 | $quakes[$i]{lat} =~ s/S//; |
| 70 | $quakes[$i]{lat} *= -1; |
| 71 | } |
| 72 | |
| 73 | $i++; |
| 74 | } |
| 75 | |
| 76 | $is_quake = 1 if /^yy/; |
| 77 | } |
| 78 | |
| 79 | return; |
| 80 | } |
| 81 | |
| 82 | sub set_extent() { |
| 83 | my $zoom, @imgext; |
| 84 | my $x, $y; |
| 85 | my $cellsizex, $cellsizey; |
| 86 | |
| 87 | if($cgi->param('imgext')) { # if an interactive interface then calculate a new extent |
| 88 | @imgext = split(' ', $cgi->param('imgext')); |
| 89 | $x = $cgi->param('img.x'); |
| 90 | $y = $cgi->param('img.y'); |
| 91 | $zoom_size = $cgi->param('zoomsize'); |
| 92 | $zoom_direction = $cgi->param('zoomdir'); |
| 93 | |
| 94 | if($zoom_direction == 0) { # pan |
| 95 | $zoom = 1; |
| 96 | } else { # in or out |
| 97 | $zoom = $zoom_size*$zoom_direction; |
| 98 | $zoom = 1.0/abs($zoom) if $zoom < 0; |
| 99 | } |
| 100 | |
| 101 | $cx = ($imgext[2]-$imgext[0])/($map->{width}); # calculate cellsize in x and y |
| 102 | $cy = ($imgext[3]-$imgext[1])/($map->{height}); |
| 103 | |
| 104 | $x = $imgext[0] + $cx*$x; # change x,y from image to map coordinates, offset from UL corner of previous image |
| 105 | $y = $imgext[3] - $cy*$y; |
| 106 | |
| 107 | $map->{extent}->{minx} = $x - .5*(($imgext[2] - $imgext[0])/$zoom); # calculate new extent |
| 108 | $map->{extent}->{miny} = $y - .5*(($imgext[3] - $imgext[1])/$zoom); |
| 109 | $map->{extent}->{maxx} = $x + .5*(($imgext[2] - $imgext[0])/$zoom); |
| 110 | $map->{extent}->{maxy} = $y + .5*(($imgext[3] - $imgext[1])/$zoom); |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | sub render_quakes { |
| 115 | die $mapscript::ms_error->{message} unless $map = new mapObj($quake_map); |
| 116 | |
| 117 | &set_extent(); |
| 118 | |
| 119 | my $img = $map->prepareImage(); |
| 120 | |
| 121 | my $point = new pointObj(); |
| 122 | |
| 123 | my $layer = $map->getLayerByName('relief'); |
| 124 | $layer->draw($map, $img); # draw basemap |
| 125 | |
| 126 | $layer = $map->getLayerByName('quakes'); |
| 127 | my $class = $layer->getClass(0); |
| 128 | my $i = 1; # quake counter |
| 129 | foreach my $q (@quakes) { |
| 130 | $class->{size} = int($q->{magnitude}*2); |
| 131 | $class->{maxsize} = $class->{minsize} = $class->{size}; # don't want to scale |
| 132 | |
| 133 | $point->{x} = $q->{lon}; |
| 134 | $point->{y} = $q->{lat}; |
| 135 | $point->draw($map, $layer, $img, undef, "$i"); |
| 136 | $i++; |
| 137 | } |
| 138 | |
| 139 | #$layer = $map->getLayerByName('timestamp'); # add timestamp |
| 140 | #$layer->{status} = 1; # turn the layer on |
| 141 | #$class = $layer->getClass(0); |
| 142 | #$class->setText($quake_timestamp); |
| 143 | #$layer->draw($map, $img); |
| 144 | |
| 145 | $map->drawLabelCache($img); |
| 146 | mapscript::msSaveImage($img, $image_path . $image_id, $map->{imagetype}, $map->{transparent}, $map->{interlace}, $map->{imagequality}); |
| 147 | |
| 148 | return; |
| 149 | } |
| 150 | |
| 151 | # here is the main program |
| 152 | $cgi = new CGI; |
| 153 | |
| 154 | &get_quakes(); |
| 155 | &render_quakes(); |
| 156 | |
| 157 | # now the html |
| 158 | |
| 159 | print header(); |
| 160 | print start_html(-title=>'MapServer Test Suite - Earthquake Mapper', -bgcolor=>"#ffffff"); |
| 161 | |
| 162 | print "<!-- " .mapscript::msGetVersion(). " -->"; |
| 163 | print "<!-- " .$map->{scale}. " -->"; |
| 164 | |
| 165 | print "<center><table width=\"600\"><tr><td>"; |
| 166 | |
| 167 | print "<font size=\"+2\" face=\"arial,helvetica\"><b>MapScript - Earthquake Mapper</b></font><p>"; |
| 168 | |
| 169 | print "This is a simple of example of using MapScript. Near real-time earthquake data is retrieved "; |
| 170 | print "from the USGS web servers. The resulting information is parsed using perl and each "; |
| 171 | print "quake is plotted on top of a basemap. The size of the marker indicates quake magnitude. "; |
| 172 | print "Many other possible enhancements. If the interface seems a bit slow it's because the "; |
| 173 | print "USGS servers can be difficult to reach- the perils of remote data. Here's the "; |
| 174 | print "<a href=\"/mapserver_demos/tests/perl/quakes/mapquakes.pl\">source</a> code.<p>"; |
| 175 | |
| 176 | print "<form name=\"mapquakes\" action=\"/cgi-bin/mapquakes.pl\" method=\"get\">"; |
| 177 | print "<center>"; |
| 178 | print "<input border=\"0\" type=\"image\" name=\"img\" src=\"". $image_virtual_path . $image_id ."\"><br>"; |
| 179 | print "<input type=\"hidden\" name=\"imgext\" value=\"" . join(' ', $map->{extent}->{minx},$map->{extent}->{miny},$map->{extent}->{maxx},$map->{extent}->{maxy}) ."\">"; |
| 180 | |
| 181 | if($zoom_direction == 0) { |
| 182 | print "<input type=\"radio\" name=\"zoomdir\" value=\"1\"> zoom in "; |
| 183 | print "<input type=\"radio\" name=\"zoomdir\" value=\"0\" checked> pan "; |
| 184 | print "<input type=\"radio\" name=\"zoomdir\" value=\"-1\"> zoom out "; |
| 185 | } else { |
| 186 | if($zoom_direction == -1) { |
| 187 | print "<input type=\"radio\" name=\"zoomdir\" value=\"1\"> zoom in "; |
| 188 | print "<input type=\"radio\" name=\"zoomdir\" value=\"0\"> pan "; |
| 189 | print "<input type=\"radio\" name=\"zoomdir\" value=\"-1\" checked> zoom out "; |
| 190 | } else { |
| 191 | print "<input type=\"radio\" name=\"zoomdir\" value=\"1\" checked> zoom in "; |
| 192 | print "<input type=\"radio\" name=\"zoomdir\" value=\"0\"> pan "; |
| 193 | print "<input type=\"radio\" name=\"zoomdir\" value=\"-1\"> zoom out "; |
| 194 | } |
| 195 | } |
| 196 | |
| 197 | print "zoom size <input type=\"text\" size=\"2\" name=\"zoomsize\" value=\"$zoom_size\">"; |
| 198 | |
| 199 | print "</center>"; |
| 200 | print "</form>"; |
| 201 | |
| 202 | print "<p><font face=\"arial,helvetica\"><b>Here's some of the output from the USGS:</b></font><p>"; |
| 203 | print "<p><pre>"; |
| 204 | |
| 205 | $show = 0; |
| 206 | $number = 0; |
| 207 | foreach (split(/^/, $quake_data)) { |
| 208 | $show = 1 if /Updated/; |
| 209 | |
| 210 | printf "<b>%2d</b>: ", $number if $show and $number; |
| 211 | print " ". $_ if $show and !$number; |
| 212 | print $_ if $show and $number; |
| 213 | $number++ if $show and $number; |
| 214 | |
| 215 | $number = 1 if /^yy/; |
| 216 | } |
| 217 | |
| 218 | print "<\/pre>;"; |
| 219 | |
| 220 | print "</td></tr></table>"; |
| 221 | |
| 222 | print end_html(); |
| 223 | exit(0); |
| 224 | }}} |
| 225 | |
| 226 | == Begin Quakes.map == |
| 227 | {{{ |
| 228 | MAP |
| 229 | STATUS ON |
| 230 | EXTENT -180 -90 180 90 |
| 231 | SIZE 561 281 |
| 232 | UNITS DD |
| 233 | IMAGECOLOR 255 0 0 |
| 234 | |
| 235 | LAYER |
| 236 | NAME relief |
| 237 | TYPE RASTER |
| 238 | DATA relief.tif |
| 239 | STATUS DEFAULT |
| 240 | END |
| 241 | |
| 242 | SYMBOL |
| 243 | TYPE ELLIPSE |
| 244 | NAME "circle" |
| 245 | POINTS 1 1 END |
| 246 | FILLED |
| 247 | END |
| 248 | |
| 249 | LAYER |
| 250 | NAME quakes |
| 251 | TYPE POINT |
| 252 | STATUS OFF |
| 253 | SYMBOLSCALE 50000000 |
| 254 | CLASS |
| 255 | NAME "Recent earth quakes." |
| 256 | SYMBOL circle |
| 257 | SIZE 15 |
| 258 | OUTLINECOLOR 0 0 0 |
| 259 | COLOR 255 0 0 |
| 260 | LABEL |
| 261 | OUTLINECOLOR 0 0 0 |
| 262 | COLOR 255 255 255 |
| 263 | SIZE TINY |
| 264 | POSITION AUTO |
| 265 | PARTIALS FALSE |
| 266 | END |
| 267 | END |
| 268 | TEMPLATE "dummy" |
| 269 | END |
| 270 | |
| 271 | LAYER |
| 272 | NAME timestamp |
| 273 | TYPE ANNOTATION |
| 274 | STATUS OFF |
| 275 | TRANSFORM FALSE |
| 276 | CLASS |
| 277 | TEXT 'This is where the timestamp goes.' |
| 278 | LABEL |
| 279 | COLOR 0 0 0 |
| 280 | OUTLINECOLOR 225 225 225 |
| 281 | SIZE MEDIUM |
| 282 | POSITION CR |
| 283 | FORCE ON |
| 284 | END |
| 285 | END |
| 286 | FEATURE |
| 287 | POINTS 5 270 END |
| 288 | END |
| 289 | END |
| 290 | END |
| 291 | }}} |