"; // print_r ($standorte); // exit(); foreach ($standorte as $standort) { unset($location); $location['url'] = "https://wiki.freifunk.net/Hamburg/Richtfunknetz:" . $standort[1]; $location['name'] = $standort[2]; $standort_page_src = file_get_contents($location['url'] . '?action=raw'); preg_match("/{{Infobox Freifunk-Standort(.*?)\n}}/s", $standort_page_src, $matches); $infobox = $matches[1]; foreach( explode( "| ", $infobox) as $pair) { list($name, $value) = explode(" = ", $pair); if ( strlen(trim($name)) && strlen(trim($value)) ) { $value = trim($value); $value = preg_replace('/\[\[(.*?)\|(.*?)\]\]/', '$2', $value); $value = preg_replace('/\[\[(.*?)\]\]/', '$1', $value); $value = preg_replace('/\{\{Knoten (.*?)\}\},?\s?/', '$1 (Monitor, OWM)
', $value); $value = preg_replace('/\[(.*?) (.*?)\]/', '$2', $value); $value = preg_replace('/([A-Za-z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/', '$1', $value); $location[$name]=$value; } } if (trim($location['Koordinaten'])) { list($location['lat'], $location['lon']) = explode(",", $location['Koordinaten']); unset($location['Koordinaten']); } if (strlen(trim($location['ueberNN'])) == 0) { $location['alt'] = 0; } else { $location['alt'] = $location['ueberNN']; } unset($location['ueberNN']); $locations[] = $location; } // And write the array to the cache file file_put_contents( $cachefile, serialize($locations) ); } // Parse the variables Google Earth passes with each refresh as instructed by the first KML file. list ($cameraLon, $cameraLat, $cameraAlt) = explode(",", $_GET['VARS']); // Create links if the eye altitude is below 250 meters if (isset ($cameraAlt) && $cameraAlt < 250) { $survey_location['lat'] = $cameraLat; $survey_location['lon'] = $cameraLon; $survey_location['alt'] = $cameraAlt - 6; $survey_location['name'] = "Survey Location"; foreach($locations as $location) { if ( $location['alt'] > 0 ) { unset($link); $link['name'] = $location['name']; $link['lat'] = $location['lat']; $link['lon'] = $location['lon']; $link['alt'] = $location['alt']; $link['distance'] = distance($survey_location, $link) . " m"; $link['azimuth to'] = bearing($survey_location, $link) . "°"; $link['elevation to'] = elevation($survey_location, $link) . "°"; $link['azimuth from'] = bearing($link, $survey_location) . "°"; $link['fspl_2.4'] = fspl( $link['distance'], 2400000000); $link['fspl_5'] = fspl( $link['distance'], 5000000000); $links[] = $link; } } // Sort the $links array by direction to $survey_location, starting north. if (isset($links)) { foreach ($links as $key => $link) { $direction[$key] = $link['azimuth to']; } array_multisort($direction, SORT_ASC, SORT_NUMERIC, $links); } $locations[] = $survey_location; } // This creates $kml which is what's output in the end $kml = headerKML(); // First all the nodes in a non-expanding folder with a placemark icon. $kml .= ''; $kml .= 'Locations'; $kml .= ''; foreach ($locations as $location) { if ($location['lat'] > 0){ $kml .= ''; $kml .= '' . $location['name'] . ''; $kml .= '' . $location['name'] . ''; $kml .= ''; foreach ($location as $name => $val) { if ($name !== 'name' && $name !== 'url') { $kml .= ''; } } $kml .= '
' . $name . ': ' . $val . '
]]>
'; $kml .= '#msn_placemark_circle'; $kml .= ''; $kml .= 'absolute'; $kml .= '' . $location['lon'] . ',' . $location['lat'] . ',' . $location['alt'] . ''; $kml .= ''; $kml .= '
' . "\n\n"; } } $kml .= '
'; // And then all the links if we have a survey location (i.e. we're under 250 m altitude) if (isset($links)) { $kml .= "Links (clockwise from north)11\n\n"; foreach ($links as $link) { $kml .= ''; $kml .= 'Link to ' . $link['name'] . ' (' . number_format($link['distance'] / 1000, 1) . ' km)1'; $kml .= ''; $kml .= ''; $kml .= 'Line with screen to ground'; $kml .= '#line'; $kml .= ''; $kml .= '1'; $kml .= 'absolute'; $kml .= '' . $link['lon'] . ',' . $link['lat'] . ',' . $link['alt'] . ',' . $survey_location['lon'] . ',' . $survey_location['lat'] . ',' . ( $survey_location['alt'] ) . ''; $kml .= ''; $kml .= '' . "\n"; $kml .= ''; $kml .= '2.4 GHz fresnel zone'; $kml .= ''; $kml .= 'Link to ' . $link['name'] . ' (' . number_format($link['distance'] / 1000, 1) . ' km)'; $kml .= ''; $kml .= ''; $kml .= ''; $kml .= ''; $kml .= ''; $kml .= ''; $kml .= '
distance: ' . $link['distance'] . '
azimuth to: ' . $link['azimuth to'] . '
elevation to: ' . $link['elevation to'] . '
azimuth from: ' . $link['azimuth from'] . '
FSPL: ' . $link['fspl_2.4'] . ' dB @ 2.4 GHz
' . $link['fspl_5'] . ' dB @ 5 GHz
]]>
'; $kml .= '#polygon-transparent'; $kml .= '' . "\n\n"; $kml .= makeFresnelPolygons( $survey_location, $link, 2400000000, 20); $kml .= '' . "\n\n"; $kml .= '
'; $kml .= ''; $kml .= '5 GHz fresnel zone'; $kml .= '#polygon'; $kml .= '' . "\n\n"; $kml .= makeFresnelPolygons( $survey_location, $link, 5000000000,20); $kml .= '' . "\n\n"; $kml .= ''; $kml .= '
' . "\n\n"; } $kml .= '
'; } $kml .= '' . "\n"; echo $kml; // The arguments to distance, bearing and elevation functions are arrays that expected to have keys // named 'lat', 'lon' and (except for bearing) 'alt'. function distance($from, $to) { $lat1 = deg2rad($from['lat']); $lon1 = deg2rad($from['lon']); $lat2 = deg2rad($to['lat']); $lon2 = deg2rad($to['lon']); $theta = $lon1 - $lon2; $dist = rad2deg( acos( sin($lat1) * sin($lat2) + cos($lat1) * cos($lat2) * cos($theta) ) ) * ( CIRCUMFERENCE_OF_EARTH / 360 ); // Add the diagonal component using pythagoras // (even if diff minimal in most of our cases) $alt_diff = abs($from['alt'] - $to['alt']); $dist = sqrt( ($alt_diff * $alt_diff) + ($dist * $dist) ); return intval ($dist); } function bearing($from, $to) { $lat1 = deg2rad($from['lat']); $lon1 = deg2rad($from['lon']); $lat2 = deg2rad($to['lat']); $lon2 = deg2rad($to['lon']); //difference in longitudinal coordinates $dLon = $lon2 - $lon1; //difference in the phi of latitudinal coordinates $dPhi = log(tan($lat2 / 2 + M_PI / 4) / tan($lat1 / 2 + M_PI / 4)); //we need to recalculate $dLon if it is greater than pi if( abs($dLon) > M_PI ) { if($dLon > 0) { $dLon = (2 * M_PI - $dLon) * -1; } else { $dLon = 2 * M_PI + $dLon; } } //return the angle, normalized return ( rad2deg(atan2($dLon, $dPhi)) + 360 ) % 360; } function elevation($from, $to) { return intval(rad2deg(atan2( $to['alt'] - $from['alt'], distance($from, $to) ))); } function fspl($dist, $freq) { return intval(20 * log10(((4 * M_PI) / SPEED_OF_LIGHT) * $dist * $freq)); } function makeFresnelPolygons($from, $to, $freq, $steps_in_circles) { // How many degrees is a meter? $lat_meter = 1 / ( CIRCUMFERENCE_OF_EARTH / 360 ); $lon_meter = (1 / cos(deg2rad($from['lat']))) * $lat_meter; $distance = distance($from, $to); $bearing = bearing($from, $to); $wavelen = SPEED_OF_LIGHT / $freq; // Speed of light // $steps_in_path is an array of values between 0 (at $from) and 1 (at $to) // These are the distances where new polygons are started to show elipse // First we do that at some fixed fractions of path $steps_in_path = array(0,0.25,0.4); // Then we add some steps set in meters because that looks better at // the ends of the beam foreach (array(0.3,1,2,4,7,10,20,40,70,100) as $meters) { // calculate fraction of path $steps_in_path[] = $meters / $distance; } // Add the reverse of these steps on other side of beam $temp = $steps_in_path; foreach ($temp as $step) { $steps_in_path[] = 1 - $step; } // Sort and remove duplicates sort($steps_in_path, SORT_NUMERIC); $steps_in_path = array_unique($steps_in_path); // Fill array $rings with arrays that each hold a ring of points surrounding the beam foreach ($steps_in_path as $step) { $centerpoint['lat'] = $from['lat'] + ( ($to['lat'] - $from['lat']) * $step ); $centerpoint['lon'] = $from['lon'] + ( ($to['lon'] - $from['lon']) * $step ); $centerpoint['alt'] = $from['alt'] + ( ($to['alt'] - $from['alt']) * $step ); // Fresnel radius calculation $d1 = $distance * $step; $d2 = $distance - $d1; $radius = sqrt( ($wavelen * $d1 * $d2) / $distance ); // Bearing of line perpendicular to bearing of line of sight. $ring_bearing = $bearing + 90 % 360; unset ($ring); for ($n=0; $n<$steps_in_circles; $n++) { $angle = $n * ( 360 / $steps_in_circles ); $vertical_factor = cos(deg2rad($angle)); $horizontal_factor = sin(deg2rad($angle)); $lat_factor = cos(deg2rad($ring_bearing)) * $horizontal_factor; $lon_factor = sin(deg2rad($ring_bearing)) * $horizontal_factor; $new_point['lat'] = $centerpoint['lat'] + ($lat_factor * $lat_meter * $radius); $new_point['lon'] = $centerpoint['lon'] + ($lon_factor * $lon_meter * $radius); $new_point['alt'] = $centerpoint['alt'] + ($vertical_factor * $radius); $ring[] = $new_point; } $rings[] = $ring; } // Make the polygons // since polygons connect this ring with next, skip last one. for ($ring_nr = 0; $ring_nr < count($rings) - 1; $ring_nr++) { $next_ring_nr = $ring_nr + 1; for ($point_nr = 0; $point_nr < $steps_in_circles; $point_nr++) { $next_point_nr = $point_nr + 1; if ($point_nr == $steps_in_circles - 1) { $next_point_nr = 0; } unset ($polygon); $polygon[] = $rings[$ring_nr][$point_nr]; $polygon[] = $rings[$next_ring_nr][$point_nr]; $polygon[] = $rings[$next_ring_nr][$next_point_nr]; $polygon[] = $rings[$ring_nr][$next_point_nr]; $polygons[] = $polygon; } } $ret = ''; foreach ($polygons as $polygon) { $ret .= 'absolute'; foreach ($polygon as $point) { $ret .= $point['lon'] . ',' . $point['lat'] . ',' . $point['alt'] . " "; } $ret .= ''; } return $ret; } function headerKML() { $kml = << line-of-sight.php 1 normal #sn_placemark_circle highlight #sh_placemark_circle_highlight HEREDOC; return $kml; } function balloonCSS() { // There is no global stylesheet in Google Earth, so this needs to be appended to each balloon to make it display nicely. $css = << a:link {text-decoration: none;} td.left {text-align: right; vertical-align: top; margin-bottom: 5px;} td, h2 {white-space: nowrap; font-family: verdana;} HEREDOC; return $css; } ?>