#!/usr/bin/perl # # David M. Zendzian dmz@dmzs.com # 2002-07-21 - v0.9 # 2002-09-17 - v0.9rc1 # # http://carte.dmzs.com/index.html # # carte.pl [-h] [-t] [-d] [-s] [-o] [-m] [-y ] [-p ] -i # -i : input file from netstumbler text output (required) # -p : where to store datafiles, default /tmp/ # -y : Set the opacity of the overlay images. Default 60 # -t: Do not download terraserver map (offline and already have maps) # otherwise: Save terraserver map as //terramap.png # -s: create simple signal 'dots' overlay image: //overlay-circle.png & merge with terramap for map-circle.png # -o: create SNR Overlay image: //overlay-idw.png & merge with terramap for map-idw.png # -m: Do not merge overlay with terramap # -d: debug # -h: this page # # requires Image::Magick from ftp://ftp.wizards.dupont.com/pub/ImageMagick/ # requires Image::Grab from http://mah.everybody.org/hacks/perl/Image-Grab/ and cpan # # will parse which should be a NetStumbler text export with GPS scan info of the format: # Latitude Longitude ( SSID ) Type ( BSSID ) Time (GMT) [ SNR Sig Noise ] # ( Name ) Flags Channelbits BcnIntvl # # This data will be pushed into WarDrive hash # Work will be done per each MAC # Find center Latitude/Longitude # Grab 3x3 image map from acme.com: # www.acme.com/mapper/save.cgi?lat=&long=&scale=11&theme=Image&width=3&height=3&dot=No # create overlay of all Sig found for MAC # merge overlay with acme/terraserver image # goto next MAC group # -- # Known Problems: # If Network Name is blank or has spaces then the split will break, bad regex :/ # doesn't test input file for escapes in mac which would cause outputfile to be able to be written anywhere (don't run as root!) # Terraserver images are a little out of date (not much I can do about this, but if you know your area it might help :/ # I will get to overlaying over streetmap @ future point # Acme mapper for some reason doesn't grab the image you request, but one a little offset :/ # # TODO: # provide gtk interface # create user defined image scale map # create animated view of idw-overlay creation # perldoc # inline::c optimizations # # (C) 2002, DMZS, Inc -- info@dmzs.com # # (BSD License) # Copyright (c) 2002, David M. Zendzian/DMZ Service, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the # distribution. # # Neither the name of DMZ Services, Inc nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT # HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY # AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER # OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY # WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # p.s. If you are out to make $$ off this endevor, please consider dropping dmz@dmzs.com a note so we can participate (: # # Greets & thanks go out to: # -------------------------- # Tony -- for getting me into this! # Glenn [at] netmud [dot] com for the insite needed to make lat/long/pixel conversion work and allowing me to browse some of his code (see next line) # Contains portions (some modified) of Copyright 2002 (see below), Netmud, LLC. All rights reserved: (getPixelWeight_Trivial distance_cmp getPixel getLatLong) # W. Slavin -- for making netstumbler & all of this possible # Jeffrey A. Poskanzer -- for such an awsome wrapper for terraserver & assistance with rad to deg conversion # Mike Kershaw -- for kismit & code that helped me understand what i was doing with deg to rad conversion :) # Peter -- for the wireless insight & views into other ways of mapping wireless networks # Change [at] dmzs [dot] com -- for letting me borrow hardware, FIRE & being a general good human being # tvsg [dot] org people for helping get my academic spirit flowing again # Whole crew at W-F-B for listening to these crazy ideas # My wife for letting me work too much # # NetMud License (getPixelWeight_Trivial distance_cmp getPixel getLatLong) # Copyright (c) 2002 Netmud, LLC. All rights reserved. # # # # Redistribution and use in source and binary forms, with or without # # modification, are permitted provided that the following conditions # # are met: # # 1. Redistributions of source code must retain the above copyright # # notice, this list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright # # notice, this list of conditions and the following disclaimer in the # # documentation and/or other materials provided with the distribution. # # 3. All advertising materials mentioning features or use of this software # # must display the following acknowledgement: # # This product includes software developed by Netmud, LLC # # 4. The name of Netmud, LLC may not be used to endorse or promote products # # derived from this software without specific prior written permission. # # # # PORTIONS OF THIS SOFTWARE IS PROVIDED BY NETMUD ``AS IS'' AND ANY EXPRESS OR IMPLIED # # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN # # NO EVENT SHALL NETMUD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # # Questions? Email me at: glenn@netmud.com # # use Getopt::Std; use Image::Grab; use Image::Magick; use Data::Dumper; #use Curses; #initscr(); &getopts('dhstmoy:p:i:'); use vars qw($opt_d,$opt_h,$opt_m,$opt_s,$opt_t,$opt_o,$opt_p,$opt_i,$opt_y,$datapath,$overlay_idw,$overlay_circle,$get_terra,$debug,$logfiledata); # need to sync this with the size that terraserver returns and make cmd line variable my $IMAGEWIDTH=600; my $get_terra = 1; my $merge_map = 1; if (!$opt_i || $opt_h) { help(); } if (!$opt_p) { $datapath = "/tmp/"; } else { $datapath = $opt_p; mkdir "$datapath" if !stat "$datapath"; } if (!$opt_y) { $opacity=60; } else { $opacity=$opt_y; } if ($opt_m) { $merge_map = 0; } if ($opt_o) { $overlay_idw = 1; } if ($opt_s) { $overlay_circle = 1; } if ($opt_t) { $get_terra = 0; } if ($opt_d) { $debug = 1; } &ScanInputLog($opt_i); my ($filename) = $opt_i; my (@data, $mac, $ssid, $time, $tz, $lat_1, $lat, $long_1, $long, $type, $srn, $sig, $noise, $flags, $channelbits, $max_lat, $min_lat, $max_long, $min_long, $maplat, $maplong, $terraservermap, $map_circle, $map_idw, $long_width, $lat_height); my %nodes_latlong = ( ); my %nodes = ( ); my $image_width = $IMAGEWIDTH; # pixels my $image_height = $image_width; # the code assumes the image is square banner("Processing",$filename) if $debug; # For each mac address, process the datapoints foreach $mac (sort keys %logfiledata) { banner("$mac",$filename) if $debug; mkdir "$datapath/$mac" if !stat "$datapath/$mac"; ($max_lat, $min_lat, $max_long, $min_long, $maplat, $maplong) = findmaxmin_latlong($mac); ($min_lat, $max_lat, $min_long, $max_long) = process_datapoints($mac, $max_lat, $min_lat, $max_long, $min_long, $maplat, $maplong); $terraservermap=$datapath."/".$mac."/terramap.png"; get_terramap($maplat,$maplong, $mac, $terraservermap) if $get_terra; ($map_circle) = create_overlay_circle($mac, $terraservermap, $min_long, $min_lat) if $overlay_circle; ($map_idw) = create_overlay_idw($mac, $terraservermap, $max_lat, $min_lat, $max_long, $min_long, $maplat, $maplong) if $overlay_idw; } banner("Processing Done",$filename) if $debug; #endwin(); #end of program logic # ------------------------------------------------------------------------------------------- # Subroutines # Print Help sub help { print("usage: ./carte.pl -i \n"); print("Available Options\n"); print("\t-i : input file from netstumbler text output (required) \n"); print("\t-p : where to store datafiles, default /tmp/ \n"); print("\t-y : Set the opacity of the overlay images. Default 60 \n"); print("\t-t: Do not download terraserver map (offline and already have maps)\n"); print("\t otherwise: Save terraserver map as //terramap.png\n"); print("\t-s: create simple signal 'dots' overlay image: //overlay-circle.png & merge with terramap for map-circle.png\n"); print("\t-o: create SNR Overlay image: //overlay-idw.png & merge with terramap for map-idw.png\n"); print("\t-m: Do not merge overlay with terramap\n"); print("\t-d: debug\n"); print("\t-h: this page\n"); exit(0); } # Print banner sub banner { my($banner_type, $banner_info) = @_; #clear(); #refresh(); print("\n#####################################################################\n"); print("####### $banner_type | $banner_info \n"); print("#####################################################################\n\n"); } sub ScanInputLog { my ($filename) = @_; my ($data, $counter, $lat_1,$lat,$long_1,$long,$ssid,$type,$mac,$time,$srn,$sig,$noise,$flags,$channelbits); banner("Parsing",$filename) if $debug; open(INPUTLOG, $filename) or die "Unable to open $filename $!\n"; while () { # skip any lines that start with # next if /^#/; # use an array slice to select fields we want # note: this needs to be cleaned up & done better. If you have a ( ) with nothing for SSID or somewhere it expects info, it will cause all kinds of wierdness ($lat_1, $lat, $long_1, $long, $ssid, $type, $mac, $time, $tz, $srn, $sig, $noise, $name, $flags, $channelbits, $bcnintvl) = split(/[\s\ \[\]\(\)\#]+/); # skip any lines with lat and long of 0 next if (($lat==0) && ($long==0)); # change : to - for mac address $mac =~ s/:/-/g; ##print("$lat_1, $lat, $long_1, $long, $ssid, $type, $mac, $time, $tz, $srn, $sig, $noise, $name, $flags, $channelbits\n") if $debug; # push value into hash $logfiledata{mac}=({ssid, time, $tz, lat_1, lat, long_1, long, type, srn, sig, noise, flags, channelbits}, ...) $data = {SSID=>$ssid, TIME=>$time, TZ=>$tz, LAT1=>$lat_1, LAT=>$lat, LONG1=>$long_1, LONG=>$long, TYPE=>$type, SRN=>$srn, SIG=>$sig, NOISE=>$noise, FLAGS=>$flags, CHANNELBITS=>$channelbits}; push (@{$logfiledata{"$mac"}},$data); ##banner("Dumping Logfiledata",$mac) if $debug; ##print Dumper @{$logfiledata{"$mac"}}; ##banner("Done Dumping Logfiledata",$mac) if $debug; } close (INPUTLOG); banner("Parsing Complete",$filename) if $debug; } sub findmaxmin_latlong { my ($mac) = @_; my ($key, $data, $lat_1, $lat, $long_1, $long, $max_lat, $min_lat, $max_long, $min_long, $maplat, $maplong); banner("Finding Max & Min",$mac) if $debug; my $min_lat = 360; # Assume the extremes my $min_long = 360; my $max_lat = -360; my $max_long = -360; $numcoords = 0; # # find max/min lat & long to get center of map needed print ("coord#\t| lat_1\t| lat\t\t| long_1| long\n") if $debug; foreach $data (@{$logfiledata{"$mac"}}) { $lat_1 = $data->{LAT1}; $lat = $data->{LAT}; $long_1 = $data->{LONG1}; $long = $data->{LONG}; $numcoords++; # Determine if lat/long is max/min. $lat *= -1 if $lat_1 eq "S"; $long *= -1 if $long_1 eq "W"; $min_lat = $lat if ($lat < $min_lat); $min_long = $long if ($long < $min_long); $max_lat = $lat if ($lat > $min_lat); $max_long = $long if ($long > $max_long); print ("$numcoords\t| $lat_1\t| $lat\t| $long_1\t| $long\n") if $debug; } # find center of coordinates $maplat = $max_lat - (($max_lat - $min_lat) / 2); $maplong = $max_long - (($max_long - $min_long) / 2); print ("\nmaplat=$maplat\t| maplong=$maplong\t| max_lat=$max_lat\t| min_lat=$min_lat\t| max_long=$max_long\t| min_long=$min_long\n") if $debug; print ("data range: $min_lat,$min_long-$max_lat,$max_long\n") if $debug; banner("Finding Max & Min complete",$mac) if $debug; return($max_lat, $min_lat, $max_long, $min_long, $maplat, $maplong); } sub get_terramap { my ($maplat,$maplong,$mac,$terraservermap) = @_; my ($url, $pic); # get map from acme and save under $datapath/$mac-terramap.png banner("Getting acme/terraserver map for ",$mac) if $debug; $url = "http://www.acme.com/mapper/save.cgi?lat=".$maplat."&long=".$maplong."&scale=10&theme=Image&width=3&height=3&dot=No"; print ("URL\t| $url\n\n") if $debug; $pic = new Image::Grab; $pic->regexp('.*save_image\.cgi.*'); $pic->search_url($url); $pic->grab; # Now to save the image to disk # should just convert blob over to imagemagic image and pass back... # probably don't need to convert to png, but for alpha channel... open(IMAGE, ">terramap.jpg"); # || die "terramap.jpg: $!"; binmode IMAGE; print IMAGE $pic->image; close IMAGE; $terramap = new Image::Magick; $terramap->Read("terramap.jpg"); $terramap->Write($terraservermap); unlink("terramap.jpg"); print("Filename\t| $terraservermap\n") if $debug; banner("Done Getting acme/terraserver map for ",$mac) if $debug; } # Convert lat/long to nearest pixel x/y sub getPixel { my ($lat, $long, $min_long, $min_lat, $long_width, $lat_height, $image_width, $image_height) = @_; my $x = ($long - $min_long) * ($image_width / $long_width); my $y = $image_height - ($lat - $min_lat) * ($image_height / $lat_height); return (int($x), int($y)); } sub process_datapoints { my ($mac, $max_lat, $min_lat, $max_long, $min_long, $maplat, $maplong) = @_; my (@data, $ssid, $time, $tz, $lat_1, $lat, $long_1, $long, $type, $srn, $sig, $noise, $flags, $channelbits); banner("Processing datapoints, creating data hashes for overlays",$mac); # Plot imagepoints $numcoords=0; # Save the highest signal recorded at this lat/long, if there # are multiple: foreach $data (@{$logfiledata{"$mac"}}) { $lat_1 = $data->{LAT1}; $lat = $data->{LAT}; $long_1 = $data->{LONG1}; $long = $data->{LONG}; $srn = $data->{SRN}; $numcoords++; $lat *= -1 if $lat_1 eq "S"; $long *= -1 if $long_1 eq "W"; my $latlong = $lat . "," . $long; if ( (! $nodes_latlong{$latlong}) || ($nodes_latlong{$latlong} < $srn) ) { $nodes_latlong{$latlong} = $srn; } print ("$numcoords\t| $lat_1\t| $lat\t| $long_1\t| $long\n") if $debug; } $long_width = ($max_long - $min_long) * 3.0; $lat_height = ($max_lat - $min_lat) * 3.0; print ("\nmin=$min_lat\t| min_long=$min_long\t| | max_lat=$max_lat\t| max_long=$max_long\n") if $debug; print ("data range: $min_lat,$min_long-$max_lat,$max_long\n") if $debug; print ("original width * 3=$long_width, height * 3=$lat_height\n") if $debug; # Adjust the lat/long range to be square (max of width & height): if ($long_width > $lat_height) { $lat_height = $long_width; } else { $long_width = $lat_height; } print ("adjusted width=$long_width, height=$lat_height\n") if $debug; # Adjust the data range to cover twice the area: my $middle_lat = ($min_lat + $max_lat) / 2.0; my $middle_long = ($min_long + $max_long) / 2.0; print ("middle_lat=$middle_lat, middle_long=$middle_long\n") if $debug; $min_lat = $middle_lat - ($lat_height / 2.0); $min_long = $middle_long - ($long_width / 2.0); $max_lat = $min_lat + $lat_height; $max_long = $min_long + $long_width; my $degrees_per_pixel = $long_width / $image_width; if (!$degrees_per_pixel) { print("Not enough data"); return(0); } print ("one pixel is ", $degrees_per_pixel, " degrees of longitude.\n") if $debug; # Convert the 'nodes_latlong' hash from lat/long to x/y: foreach my $key (keys(%nodes_latlong)) { my ($lat, $long) = split(',', $key); my ($x, $y) = getPixel($lat, $long, $min_long, $min_lat, $long_width, $lat_height, $image_width, $image_height); my $xy = $x . "," . $y; $nodes{$key} = $nodes_latlong{$key}; # just copy the data print ("xy=$xy\t| $nodes{$key}\n") if $debug; } banner("Done processing datapoints for overlays",$mac); return($min_lat,$max_lat,$min_long,$max_long); } sub create_overlay_circle { my ($mac, $terraservermap, $min_long, $min_lat) = @_; # Plot scan into image and save as $mac/overlay.png banner("Creating Simple-Circle Overlay Image",$mac) if $debug; # Create overlay image and have background be uniform grey with opacity channel set my $plotcircle = Image::Magick->new(size=>"${image_width}x${image_height}"); my $g = 255 - $opacity/100 * 255; my $bgcolor = sprintf "#%02x%02x%02x%02x", $g,$g,$g,$g; $plotcircle->Read("xc:$bgcolor"); # Plot Each signal onto overlay image foreach my $key (keys(%nodes_latlong)) { my ($lat, $long) = split(',', $key); my ($x, $y) = getPixel($lat, $long, $min_long, $min_lat, $long_width, $lat_height, $image_width, $image_height); my $right = 5 + $x; my $weight = $nodes_latlong{$key}; my $color = 'red'; if ($weight > 25) { $color = 'green'; $right += 20; } elsif ($weight > 18) { $color = 'green'; $right += 20; } elsif ($weight > 12) { $color = 'yellow'; $right += 12; } elsif ($weight > 4) { $color = 'red'; $right += 5; } elsif ($weight > 1) { $color = 'black'; $right += 2; } else { } $plotcircle->Draw( primitive=>'circle', fill=>$color, stroke=>$color, strokewidth=>1, points => "$x,$y $right,$y"); print ("Draw( fill=>$color, stroke=>$color, strokewidth=>1, primitive => 'circle', points => $x,$y $right,$y weight=$weight\n") if $debug; } # Create transparancy mask and add to overlay my $mask = Image::Magick->new(size=>"${image_width}x${image_height}"); my $g = 255 - $opacity/100 * 255; my $color = sprintf "#%02x%02x%02x", $g,$g,$g; $mask->Read("xc:$color"); my $cur_mask = $plotcircle->Clone(); $cur_mask->Channel('Matte'); $rc = $mask->Composite(image => $cur_mask, compose => 'Plus'); warn $rc if $rc; $rc = $plotcircle->Composite(image => $mask, compose => 'ReplaceMatte'); warn $rc if $rc; # Write out the created overlay my $plotimage = $datapath."/".$mac."/overlay-circle.png"; $plotcircle->Write($plotimage); print("\nFilename\t| $plotimage") if $debug; # Merge overlay with terramapfile if ($merge_map) { my $map_circle = Image::Magick->new(); $map_circle->Read($terraservermap); $rc = $map_circle->Composite(image => $plotcircle, compose => 'Over'); warn $rc if $rc; # Write out the terramap with simple circles overlayed my $mapimage = $datapath."/".$mac."/map-circle.png"; $map_circle->Write($mapimage); print("\nFilename\t| $mapimage") if $debug; } banner("Done Creating Simple Image",$mac) if $debug; return($map_circle); } # Convert pixel x/y to lat/long # # The way this works now returns the lat/long of the "top left" of the pixel. # # XXX Maybe it should return the lat/long at the center instead. sub getLatLong { my ($x, $y, $max_lat, $min_long, $lat_height, $long_width, $image_height, $image_width) = @_; my $lat = $max_lat - ($y * $lat_height / $image_height); my $long = $min_long + ($x * $long_width / $image_width); ##print ("x=$x, y=$y, max_lat=$max_lat, min_long=$min_long, lat_height=$lat_height, long_width=$long_width, image_height=$image_height, image_width=$image_width, lat=$lat, long=$long\n"); return ($lat, $long); } my $sort_lat = 0; my $sort_long = 0; sub distance_cmp { my ($lat1, $long1) = split(',', $a); my ($lat2, $long2) = split(',', $b); my $dist1 = (($lat1 - $sort_lat)**2) + (($long1 - $sort_long)**2); my $dist2 = (($lat2 - $sort_lat)**2) + (($long2 - $sort_long)**2); return $dist1 <=> $dist2; } # Inline::C ?? # 2002/07/28 - Removed all code that was mentioned as not needed - DMZ sub getPixelWeight_Trivial { my ($x, $y, $max_lat, $min_long, $lat_height, $long_width, $image_height, $image_width, %nodes) = @_; my ($pix_lat, $pix_long) = getLatLong($x, $y, $max_lat, $min_long, $lat_height, $long_width, $image_height, $image_width); my $weight = 0; # What we're returning my $weightcap = 0; # The highest weight actually seen. We never return a value greater than this (because signal isn't cumulative). my @nodekeys = keys(%nodes); ##print ("x=$x, y=$y, max_lat=$max_lat, min_long=$min_long, latheight=$lat_height, longwidth=$long_width, imageheight=$image_height, imagewidth=$image_width\n"); ##print ("x=$x, y=$y, pix_lat=$pix_lat, pix_long=$pix_long\n"); # Sort the node keys by distance to this point: $sort_lat = $pix_lat; $sort_long = $pix_long; my @sortedkeys = sort distance_cmp @nodekeys; # Only use the N closest nodes: # XXX This may break for data sets with less than 6 points: foreach my $i (0..6) { my $key = $sortedkeys[$i]; my ($i_lat, $i_long) = split(',', $key); my $dist_i = ((($i_lat - $pix_lat)**2) + (($i_long - $pix_long)**2)); ##print ("ilat=$i_lat\t| plat=$pix_lat\t| ilong=$i_long\t| plong=$pix_long\t| dist=$dist_i\n") if $debug; # Where did 0.000000025197117696 come from? Great question! # Let me know if you figure it out! I just screwed around # until I was happy with the output I was getting. # Maybe it's some kind of mapping between pixels & lat/long # (and should really relate to $degrees_per_pixel) $dist_i = (0.000000025197117696) / ($dist_i) unless ($dist_i eq 0); my $tempweight = $dist_i * $nodes{$key}; $weightcap = $nodes{$key} if ($nodes{$key} > $weightcap); ##print ("dist=$dist_i\t| tempweight=$tempweight\t| weight=$nodes{$key}\t| W=$weight\n") if $debug; if ($tempweight > 0.000001) { # If the weight is close enough to 0, don't bother counting this data point. (hack) $weight += $tempweight; } } $weight = $weightcap if ($weight > $weightcap); # Cap at weightcap #print ("Weight\t| $weight\n") if $debug; return ($weight); } sub create_overlay_idw { my ($mac, $terraservermap, $max_lat, $min_lat, $max_long, $min_long, $maplat, $maplong) = @_; my (@data, $ssid, $time, $tz, $lat_1, $lat, $long_1, $long, $type, $srn, $sig, $noise, $flags, $channelbits); my @weights = (); # One per pixel my $minweight = 999999999; my $maxweight = 0; my $weightsum = 0; # Loop through all the pixels in the output and calculate the weight for each: foreach my $x_pix (0..$image_width) { foreach my $y_pix (0..$image_height) { $weights[$x_pix][$y_pix] = getPixelWeight_Trivial($x_pix, $y_pix, $max_lat, $min_long, $lat_height, $long_width, $image_height, $image_width, %nodes); $minweight = $weights[$x_pix][$y_pix] if ($weights[$x_pix][$y_pix] < $minweight); print ("for $x_pix, $y_pix, weight=$weights[$x_pix][$y_pix], minweight=$minweight\n") if (($x_pix eq $y_pix) && $debug); #print ("for $x_pix, $y_pix, weight=$weights[$x_pix][$y_pix], minweight=$minweight\n") if $debug; } } # Create overlay image and have background be uniform grey with opacity channel set my $plotidw = Image::Magick->new(size=>"${image_width}x${image_height}"); my $g = 255 - $opacity/100 * 255; my $bgcolor = sprintf "#%02x%02x%02x%02x", $g,$g,$g,$g; $plotidw->Read("xc:$bgcolor"); print ("Generating overlay image:\n") if $debug; # The colors used here are just what I liked. Tweak 'em, make 'em configurable, whatever: foreach my $x_pix (0..$image_width) { foreach my $y_pix (0..$image_height) { my $weight = $weights[$x_pix][$y_pix]; my $red = $g; my $green = $g; my $blue = $g; if ($weight > 25) { $red = 100; #int(128 * $weight); $green = 133; $blue = 142; } elsif ($weight > 18) { $red = 122; $green = 165; #int(128 * ($weight + 0.4)); $blue = 128; } elsif ($weight > 12) { $red = 168; $green = 181; #int(128 * ($weight + 0.4)); $blue = 112; } elsif ($weight > 4) { $red = 183; $green = 162; $blue = 71; } elsif ($weight > 1) { $red = 172; $green = 152; $blue = 119; } else { $red = $green = $blue = $g; } my $colorstring = sprintf("#%02x%02x%02x%02x", $red, $green, $blue, $g); if (length($colorstring) > 9) { print ("red=$red, green=$green, blue=$blue, weight=$weight\n") if $debug; } $plotidw->Set("Pixel[$x_pix,$y_pix]"=>$colorstring); print ("x=$x_pix,y=$y_pix\t| colorstring = $colorstring\n") if ($debug && ($x_pix eq $y_pix)); } } # Create transparancy mask and add to overlay my $mask = Image::Magick->new(size=>"${image_width}x${image_height}"); my $g = 255 - $opacity/100 * 255; my $color = sprintf "#%02x%02x%02x", $g,$g,$g; $mask->Read("xc:$color"); my $cur_mask = $plotidw->Clone(); $cur_mask->Channel('Matte'); $rc = $mask->Composite(image => $cur_mask, compose => 'Plus'); warn $rc if $rc; $rc = $plotidw->Composite(image => $mask, compose => 'ReplaceMatte'); warn $rc if $rc; # Write out the created overlay my $plotimage = $datapath."/".$mac."/overlay-idw.png"; $plotidw->Write($plotimage); print("\nFilename\t| $plotimage") if $debug; # Merge overlay with terramapfile if ($merge_map) { my $map_idw = Image::Magick->new(); $map_idw->Read($terraservermap); $rc = $map_idw->Composite(image => $plotidw, compose => 'Over'); warn $rc if $rc; my $mapimage = $datapath."/".$mac."/map-idw.png"; $map_idw->Write($mapimage); # Write out the terramap with idw overlayed print("\nFilename\t| $mapimage") if $debug; } banner("Done Creating Overlay Image",$mac) if $debug; return($map_idw) if $merge_map; }