#!/usr/bin/perl use strict; #this script is the same as genPlacemarksSOS.pl , except the paths are hard-coded to the existing server paths so we don't have to go through http and zips my $gearth_dir = '/var/www/html/gearth'; my $sos_config_dir = '/sos/metadata'; my $sos_data_dir = '/sos/data/now'; =comment #usage notes Watch the server path specific literals, everything gets unzipped and worked on in $target_dir so you'll want to set that to some temporary folder area that gets periodically flushed. The other xml files this script uses are units.xml and style.xml . units.xml provides unit and conversion labeling lookups. Note it also uses 'eval' so the units.xml shouldn't be provided from outside unless the 'eval' issue is addressed. style.xml provides the default observation range limits to be used and can also be ignored in favor of a user supplied style.xml via $styel_url Also there is a literal http address returned to the calling php page which will need to be changed accordingly. =cut use LWP::Simple; use XML::LibXML; #using print `date` for script time benchmarking print `date`; #opening this lookup file once for later use in sub my $xp_units = XML::LibXML->new->parse_file('./units.xml'); ################## #convert xml config and data to hash ################## my $xp = XML::LibXML->new->parse_file("$sos_config_dir/sos_config.xml"); #note that the below hashing convention assumes each element contains only one type of child element except at the terminals my %HoH = (); my $rHoH = \%HoH; #below hash used later for sorting by ObsTypes my %ObsTypes = (); my $rObsTypes = \%ObsTypes; #get header data my $regional_assn = $xp->find('//RegionalAssociation/OrganizationName'); my $regional_url = $xp->find('//RegionalAssociation/OrganizationURL'); #print `date`; foreach my $observation ($xp->findnodes('//Observation')) { my $operator = $observation->find('operator'); my $operator_url = $observation->find('operatorURL'); #'operator' and 'operatorURL' are missing in the sos_config.xml, which kills my kml functionality for breakout by operator # substituting regional args for now if (!($operator)) { $operator = $regional_assn; } if (!($operator_url)) { $operator_url = $regional_url; } my $local_platform = $observation->find('localPlatformName'); #print $local_platform."\n"; my $obs_property = $observation->find('observedProperty'); #using the below hash trick to get a list of unique ObsTypes $ObsTypes{ $obs_property } = 1; $HoH{ $operator }{ $local_platform }{ $obs_property }{ 'operator_url' } = $operator_url; $HoH{ $operator }{ $local_platform }{ $obs_property }{ 'platform_url' } = $observation->find('platformURL'); $HoH{ $operator }{ $local_platform }{ $obs_property }{ 'longitude' } = $observation->find('longitude'); $HoH{ $operator }{ $local_platform }{ $obs_property }{ 'latitude' } = $observation->find('latitude'); $HoH{ $operator }{ $local_platform }{ $obs_property }{ 'data_url' } = $observation->find('dataURL'); #below assumes convention of just single line of latest data in csv format with time in first column, measurement in the last column #additional lines (depth profile) will be ignored for now my @path_args = split(/\//,$observation->find('SOSDataFile')); my $sos_data_filename = @path_args[-1]; #get last filename argument in path open (CSV_FILE, "$sos_data_dir/$sos_data_filename"); my @file_args = split(',',); close (CSV_FILE); my $datetime = @file_args[0]; #get first argument in file #print $datetime."\n"; my $measurement = @file_args[-1]; #get last argument in file chomp($measurement); #print $measurement."\n"; $HoH{ $operator }{ $local_platform }{ $obs_property }{ 'datetime' } = $datetime; $HoH{ $operator }{ $local_platform }{ $obs_property }{ 'measurement' } = $measurement; } my $kmz_time = `date +%y%m%d%H%M`; my $kmz_time_full = '20'.substr($kmz_time,0,2).'_'.substr($kmz_time,2,2).'_'.substr($kmz_time,4,2).'_'.substr($kmz_time,6,2).'_'.substr($kmz_time,8,2); #my $kmz_time_day = '20'.substr($kmz_time,0,2).'_'.substr($kmz_time,2,2).'_'.substr($kmz_time,4,2); my $kmz_time_hour = '20'.substr($kmz_time,0,2).'_'.substr($kmz_time,2,2).'_'.substr($kmz_time,4,2).'_'.substr($kmz_time,6,2); ################## #kml content ################## my $kml_content = <<"END_OF_FILE"; Ocean Observing Platform Data $kmz_time_full OOSTechKML Please email jeremy.cothran\@gmail.com regarding questions or comments on this kml product or sharing/registering your observation data using these tools.]]> END_OF_FILE #generating the kml content below if not directly output to $kml_content is sent to 'buffer' variables so that the buffer can be used # or cleared depending on the available data ################## #list by operator ################## #print `date`; $kml_content .= "List by operator0"; foreach my $operator ( sort keys %{$rHoH} ) { #print "operator:$operator\n"; $kml_content .= "$operator0"; foreach my $local_platform ( sort keys %{$rHoH->{$operator}} ) { #print "localPlatformName:$local_platform\n"; my $desc_header = ''; my $desc_table = ''; my $desc_footer = ''; my $operator_url = ''; my $platform_url = ''; my $longitude = ''; my $latitude = ''; my $measurement = ''; my $datetime = ''; my $datetime_iso = ''; my $data_url = ''; my ($longitude, $latitude, $datetime); foreach my $obs_property ( sort keys %{$rHoH->{$operator}{$local_platform}} ) { #print "obs_property:$obs_property\n"; #only need to do this initially once per platform if (!($operator_url)) { $operator_url = $HoH{$operator}{$local_platform}{$obs_property}{'operator_url'}; $platform_url = $HoH{$operator}{$local_platform}{$obs_property}{'platform_url'}; $desc_header .= 'OperatorURL
'; $desc_header .= 'PlatformURL
'; $desc_header .= "Related links: "; $desc_header .= "OOSTechKML "; $desc_header .= "http://oostethys.org "; $desc_header .= "http://seacoos.org

"; } if (!($datetime)) { $datetime = $HoH{$operator}{$local_platform}{$obs_property}{'datetime'}; #print "datetime:".$datetime.":\n"; #using $datetime to determine whether we should print table, first $datetime gathered represents # the rest at this placemark if ($datetime) { $datetime_iso = $datetime; $datetime =~ s/T/ /g; $datetime = substr($datetime,0,16).' GMT'; #print "$datetime\n"; $desc_header .= "Click on the latest observation reading to view graphs of previous observations.

"; $desc_header .= "Last update: $datetime
"; $desc_table .= ''; } } $longitude = $HoH{$operator}{$local_platform}{$obs_property}{'longitude'}; $latitude = $HoH{$operator}{$local_platform}{$obs_property}{'latitude'}; my $measurement = $HoH{$operator}{$local_platform}{$obs_property}{'measurement'}; #print "$measurement\n"; my $data_url = $HoH{$operator}{$local_platform}{$obs_property}{'data_url'}; $desc_table .= unit_convert_table($obs_property,$measurement,$data_url); } #only closing table if exists if ($desc_table ne '') { $desc_table .= '
'; } $desc_footer .= ']]>'; my $description = $desc_header.$desc_table.$desc_footer; $kml_content .= <<"END_OF_FILE"; $datetime_iso $local_platform 1 $description $longitude,$latitude,0 END_OF_FILE } #foreach $local_platform $kml_content .= "
"; } #foreach $operator $kml_content .= "
"; ################## #list by observation ################## #print `date`; #the below is a cut and paste for the most part of the 'list be operator' except running through the ObsTypes list and comparing to determine #whether kml is output or not and the style application of colored ranges on the icons my $xp_style = XML::LibXML->new->parse_file("$gearth_dir/style.xml"); $kml_content .= "List by observation0"; $kml_content .= ""; foreach my $obs_type ( sort keys %{$rObsTypes} ) { #print "obs_type:$obs_type\n"; $kml_content .= "$obs_type0"; my $uom; foreach my $unit ($xp_units->findnodes('//unit_list[@id="seacoos"]/parameter[@id="'.$obs_type.'"]')) { $uom = $unit->find('uom'); } #must convert below to int, otherwise errors out on pass to subroutines and improper type handline my $range_high = int($xp_style->findvalue("//$obs_type/range_high")); #print "range_high:$range_high\n"; my $range_low = int($xp_style->findvalue("//$obs_type/range_low")); #print "range_low:$range_low\n"; my $yellow_range = 'yellow = below '.int(unit_convert($obs_type,$range_low)); my $orange_range = 'orange = above '.int(unit_convert($obs_type,$range_high)); my $color_span = ($range_high - $range_low)/3; my $blue_range = 'blue = '.int(unit_convert($obs_type,$range_low)).' to '.int(unit_convert($obs_type,$range_low+$color_span)); my $green_range = 'green = '.int(unit_convert($obs_type,$range_low+$color_span)).' to '.int(unit_convert($obs_type,$range_low+$color_span*2)); my $red_range = 'red = '.int(unit_convert($obs_type,$range_low+$color_span*2)).' to '.int(unit_convert($obs_type,$range_high)); $kml_content .= "The following color ranges apply to this observation
Unit of measure: $uom
$yellow_range
$blue_range
$green_range
$red_range
$orange_range
"; foreach my $operator ( sort keys %{$rHoH} ) { #print "operator:$operator\n"; my $kml_temp_content = ''; foreach my $local_platform ( sort keys %{$rHoH->{$operator}} ) { #print "localPlatformName:$local_platform\n"; my $desc_header = ''; my $desc_table = ''; my $desc_footer = ''; my $operator_url = ''; my $platform_url = ''; my $longitude = ''; my $latitude = ''; my $measurement = ''; my $datetime = ''; my $datetime_iso = ''; my $data_url = ''; my $color = ''; my ($longitude, $latitude, $datetime); foreach my $obs_property ( sort keys %{$rHoH->{$operator}{$local_platform}} ) { #print "obs_property:$obs_property\n"; if ($obs_property ne $obs_type) { next; } #only need to do this initially once per platform if (!($operator_url)) { $operator_url = $HoH{$operator}{$local_platform}{$obs_property}{'operator_url'}; $platform_url = $HoH{$operator}{$local_platform}{$obs_property}{'platform_url'}; $desc_header .= 'OperatorURL
'; $desc_header .= 'PlatformURL
'; $desc_header .= "Related links: "; $desc_header .= "OOSTechKML "; $desc_header .= "http://oostethys.org "; $desc_header .= "http://seacoos.org

"; } if (!($datetime)) { $datetime = $HoH{$operator}{$local_platform}{$obs_property}{'datetime'}; #print "datetime:".$datetime.":\n"; #using $datetime to determine whether we should print table, first $datetime gathered represents # the rest at this placemark if ($datetime) { $datetime_iso = $datetime; $datetime =~ s/T/ /g; $datetime = substr($datetime,0,16).' GMT'; #print "$datetime\n"; $desc_header .= "Click on the latest observation reading to view graphs of previous observations.

"; $desc_header .= "Last update: $datetime
"; $desc_table .= ''; } } $longitude = $HoH{$operator}{$local_platform}{$obs_property}{'longitude'}; $latitude = $HoH{$operator}{$local_platform}{$obs_property}{'latitude'}; my $measurement = $HoH{$operator}{$local_platform}{$obs_property}{'measurement'}; #print "$measurement\n"; $color = conv_measurement_to_color($range_high,$range_low,$measurement); #print "color:$color\n"; my $data_url = $HoH{$operator}{$local_platform}{$obs_property}{'data_url'}; $desc_table .= unit_convert_table($obs_property,$measurement,$data_url); } #only closing table if exists if ($desc_table ne '') { $desc_table .= '
'; } $desc_footer .= ']]>'; my $description = $desc_header.$desc_table.$desc_footer; if ($latitude) { #testing for content before output kml - using latitude, but could be any of above content vars $kml_temp_content .= <<"END_OF_FILE"; $datetime_iso $local_platform 0 $description $longitude,$latitude,0 END_OF_FILE } } #foreach $local_platform if ($kml_temp_content) { $kml_content .= "$operator0"; $kml_content .= $kml_temp_content; $kml_content .= ""; #copy placemarks to running daily file #print "$obs_type:$operator:$kmz_time_hour\n"; open (SHADOW_FILE, ">>$gearth_dir/archive_kmz/temp/$obs_type\_$operator\_$kmz_time_hour"); print SHADOW_FILE $kml_temp_content; close (SHADOW_FILE); } } #foreach $operator $kml_content .= "
"; } #foreach $obs_type $kml_content .= "
"; ################## #kml close and return file handle ################## $kml_content .= "
"; open (FILE_KML,">$gearth_dir/latest_placemarks.kml"); print FILE_KML ''; print FILE_KML $kml_content; print FILE_KML ''; close (FILE_KML); #zip our latest file `cd $gearth_dir; zip latest_placemarks.kmz latest_placemarks.kml; cp latest_placemarks.kmz archive_kmz/$kmz_time_full.kmz`; #zip our daily file `cd $gearth_dir; perl genDayArchive.pl`; print `date`; exit 0; ##subroutines################################ sub unit_convert_table { #this sub takes $parameter_id, $parameter_value and uses an xml lookup to convert them to another unit of measure returned as an html table row my ($parameter_id, $parameter_value, $graph) = @_; #print "$parameter_id $parameter_value\n"; my $string; #don't build table string if null parameter value if ($parameter_value eq 'NULL') { return $string; } my ($label, $conversion, $uom); foreach my $unit ($xp_units->findnodes('//unit_list[@id="seacoos"]/parameter[@id="'.$parameter_id.'"]')) { $label = $unit->find('label'); $conversion = $unit->find('conversion'); $uom = $unit->find('uom'); } $conversion =~ s/var1/$parameter_value/g; $conversion = eval $conversion; $string = "$label$conversion$uom"; #print "$string\n"; return $string; } sub unit_convert { #this sub takes $parameter_id, $parameter_value and uses an xml lookup to convert them to another unit of measure my ($parameter_id, $parameter_value) = @_; #print "$parameter_id $parameter_value\n"; my $string; #don't build table string if null parameter value if ($parameter_value eq 'NULL') { return $string; } my $conversion; foreach my $unit ($xp_units->findnodes('//unit_list[@id="seacoos"]/parameter[@id="'.$parameter_id.'"]')) { $conversion = $unit->find('conversion_pure'); if (!($conversion)) { $conversion = $unit->find('conversion'); } } $conversion =~ s/var1/$parameter_value/g; $conversion = eval $conversion; $string = $conversion; #print "$string\n"; return $string; } sub conv_measurement_to_color { my ($range_high, $range_low, $measurement) = @_; if ($measurement eq 'NULL' || $range_high == $range_low ) { return 'FF00FF'; } #violet if ($measurement < $range_low) { return '00FFFF'; } #yellow if ($measurement > $range_high) { return '0099FF'; } #orange #NOTE: google kml color scheme is bgr(blue green red) instead of rgb(red green blue) preceeded by alpha/transparency - I've hardcoded the #transparency as not transparent (ff) in the element and the below are the bgr hex that are substituted from the color scale. #The color scale is low to high: light blue->dark blue, light green->dark green, light red->dark red #anything which falls outside the range low is yellow and outside the range high is orange #anything where the range or measurement is not defined is violet #found the following website helpful also: http://www.yvg.com/twrs/RGBConverter.html my $color_scale_count = 21; my @color_scale = qw(FFBEBE FF9E9E FF7E7E FF5F5F FF3F3F FF1F1F FF0000 BEFFBE 9EFF9E 7EFF7E 5FFF5F 3FFF3F 1FFF1F 00FF00 BEBEFF 9E9EFF 7E7EFF 5F5FFF 3E3EFF 1F1FFF 0000FF); my $increment = ($range_high - $range_low) / $color_scale_count; my $choice = int(($measurement - $range_low)/$increment); #print "$measurement:$increment:$choice\n"; return @color_scale[$choice]; } #this sub called during conversion using units.xml sub conv_degrees_to_compass{ my $degrees = shift; if ($degrees eq 'NULL') { return $degrees; } my @compass = qw(N NNE NE ENE E ESE SE SSE S SSW SW WSW W WNW NW NNW); $degrees = ($degrees + 22.5) / 22.5; $degrees -= .5; my $quad = $degrees % 16; return $compass[$quad]; }