From d52080e18f65ca97a2cd73e1e68a56df8c891f2f Mon Sep 17 00:00:00 2001 From: 4ndr3 Date: Wed, 28 Dec 2016 14:48:52 +0100 Subject: [PATCH] erster commit --- .gitignore | 2 + README.md | 3 + line-of-sight.kml | 14 ++ line-of-sight.php | 474 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 493 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 line-of-sight.kml create mode 100755 line-of-sight.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dcbbd69 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +!.gitignore diff --git a/README.md b/README.md new file mode 100644 index 0000000..ff05fae --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Fresnelzonen-Mopped + +Zeigt Fresnelzonen zu bestehenden Hamburger freifunk Richtfunkstandorten in Google Earth an. Anpassung für Hamburg basierend auf einem [Skript von Rop](https://wiki.freifunk.net/Berlin:Line-of-Sight_visualiser). diff --git a/line-of-sight.kml b/line-of-sight.kml new file mode 100644 index 0000000..cea6c25 --- /dev/null +++ b/line-of-sight.kml @@ -0,0 +1,14 @@ + + Freifunk Hamburg Line-of-Sight Visualiser + 0 + 1 + 1 + 1 + + https://meta.hamburg.freifunk.net/line-of-sight.php + onChange + onRequest + VARS=[cameraLon],[cameraLat],[cameraAlt] + + + diff --git a/line-of-sight.php b/line-of-sight.php new file mode 100755 index 0000000..8d37ccc --- /dev/null +++ b/line-of-sight.php @@ -0,0 +1,474 @@ +"; +// 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; +} + +?>