3 // Copyright 2010 Guillaume Cottenceau.
5 // Licensed under the Apache License, Version 2.0 (the "License"); you
6 // may not use this site or the software running this site except in
7 // compliance with the License. You may obtain a copy of the License at
9 // http://www.apache.org/licenses/LICENSE-2.0
11 // Unless required by applicable law or agreed to in writing, this is
12 // provided on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
13 // KIND, either express or implied. See the License for the specific
14 // language governing permissions and limitations under the
17 ini_set("track_errors", 1);
18 ini_set('display_errors', 0);
22 // Helper function for fatal errors
23 function fatal($msg) {
24 echo "<h2>Error</h2>\n";
26 echo "</body></html>";
31 error_log($_SERVER['REQUEST_URI']);
33 if (!preg_match('/saved/', $_SERVER['REQUEST_URI'])) {
34 $host = 'api.hostip.info';
36 $path = '/get_html.php?position=true&ip=' . $_SERVER['REMOTE_ADDR'];
38 error_log('Geolocate with api.hostip.info...');
39 $fp = @fsockopen($host, $port, $errno, $errstr);
41 fatal("<p>Unable to open connection to <tt>$host</tt> ".
42 "(port <tt>$port</tt>):</p><pre>$errstr</pre>");
44 fwrite($fp, "GET $path HTTP/1.0\r\n");
45 fwrite($fp, "Host: $host:$port\r\n");
51 $buf .= fread($fp, 8192);
52 // Parse the HTTP answer and try to extract response code,
54 $answer = parse_answer($buf);
56 // We need to discover Content-Length, to know when server
57 // finishes sending data
59 if ($answer[0] == 200)
60 $length = $answer[1]['content-length'];
63 // When length is available, we can check if response has
64 // been fully transmitted; this is the case when all what
65 // we received equals the headers plus the full contents.
67 if (strlen_b($buf) == strlen_b($answer[3]) + $length)
75 $latitude = $longitude = $zoom = 0;
76 if (preg_match('/Latitude: (\S+)/', $body, $matches)) {
77 error_log('Got lat/lng information from api.hostip.info');
78 $latitude = $matches[1];
81 if (preg_match('/Longitude: (\S+)/', $body, $matches)) {
82 $longitude = $matches[1];
84 if (($latitude == 0 || $longitude == 0) && preg_match('/Country: .*?\((\S{2})\)/', $body, $matches)) {
85 $country = $matches[1];
86 if ($country == 'XX') {
87 // no luck with hostip.info, at least position to proper country with whois on IP
88 error_log('Geolocate with whois...');
89 $whois = shell_exec("whois " . $_SERVER['REMOTE_ADDR']);
90 if (preg_match('/country:\s*(\S{2})/i', $whois, $matches)) {
91 error_log('Got country information from whois');
92 $country = $matches[1];
95 error_log('Got only country information from api.hostip.info');
97 include('countries.phpi');
98 $latitude = $countries[$country][0];
99 $longitude = $countries[$country][1];
104 // PHP's strlen should return the length of a string in bytes,
105 // except when using multibyte strings, in which case
106 // mb_orig_strlen must be used (in case strlen was overloaded
108 function strlen_b($str) {
109 if (function_exists('mb_orig_strlen')) {
110 return mb_orig_strlen($str);
116 // Parse an HTTP answer to extract response code, headers and body
117 // Returns an array containing: response code, headers, body, raw headers
118 function parse_answer($answer) {
120 // This first pattern matching allows to separate first line
121 // (server response status), headers, and data contents
122 if (ereg("^(([^\n]+)\r\n(.*)\r\n\r\n)(.*)", $answer, $regs)) {
123 $full_headers = $regs[1];
124 $response = $regs[2];
125 $headers_string = $regs[3];
127 // Parse first line (server response status)
128 if (ereg("^HTTP/[0-9\.]+ ([0-9]+)", $response, $regs)) {
129 $response_code = $regs[1];
131 fatal("<p>Unable to parse response line <tt>$response</tt> ".
132 "from the server.</p>");
134 // Parse headers and build a hash with them
135 foreach (split("\r\n", $headers_string) as $line) {
136 if (ereg("^([^:]+): (.*)", $line, $regs)) {
137 $headers[strtolower($regs[1])] = $regs[2];
139 fatal("<p>Unable to parse header line <tt>$line</tt> ".
141 "All headers:</p><pre>$headers_string</pre>");
145 // Return -1 as response code if parsing was not possible
148 return array($response_code, $headers, $body, $full_headers);
155 <title>Routes Compare</title>
156 <link rel="stylesheet" type="text/css" media="screen" href="style.css"/>
157 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
158 <script type="text/javascript" src="prototype.js"></script>
159 <script type="text/javascript" src="effects.js"></script>
160 <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false&language=<?php echo $language ?>"></script>
161 <script type="text/javascript" src="googlemaps.js.php"></script>
162 <script type="text/javascript">
164 // Copyright (c) Guillaume Cottenceau and licensed under the Apache 2.0 License
169 var places = new Array();
170 var geocoder = new google.maps.Geocoder();
171 var directions_service = new google.maps.DirectionsService();
172 var info_window = null;
174 var travelmode = google.maps.DirectionsTravelMode.DRIVING;
175 var undo_history = new Array();
177 function removePlaces() {
178 for (var i = 0; i < places.length; i++) {
179 places[i].marker.setMap(null);
180 places[i].directions_display.setMap(null);
182 places = new Array();
183 document.getElementById("resultslist").innerHTML = '';
186 function reload(new_places) {
188 for (var i = 0; i < new_places.length; i++) {
189 createPlace(new_places[i].latlng, new_places[i].name);
193 function setTravelmode(tm) {
194 if (tm == 'DRIVING') {
195 travelmode = google.maps.DirectionsTravelMode.DRIVING;
196 } else if (tm == 'WALKING') {
197 travelmode = google.maps.DirectionsTravelMode.WALKING;
200 function travelmodeChanged() {
201 setTravelmode(document.getElementById("travelmode").value);
206 function showDirectionCapture(i) {
207 var request = { origin: new google.maps.LatLng(mylat, mylng),
208 destination: places[i].latlng,
209 travelMode: travelmode };
210 directions_service.route(request, function(result, status) {
211 if (status == google.maps.DirectionsStatus.OK) {
212 places[i].directions_display.setMap(map);
213 places[i].directions_display.setDirections(result);
214 displayDirectionsMoreinfo(result.routes[0], document.getElementById("directions" + ( 1 + i )));
220 if (document.getElementById("fitmap").checked && bounds) {
221 map.fitBounds(bounds);
225 function fitmapChanged() {
230 function showDirections() {
231 if (places.length > 0) {
232 bounds = new google.maps.LatLngBounds();
233 bounds.extend(new google.maps.LatLng(mylat, mylng));
235 for (var i = 0; i < places.length; i++) {
236 if (!places[i].directions_display.getMap()) {
237 showDirectionCapture(i);
239 bounds.extend(places[i].latlng);
244 function showDel(number) {
245 document.getElementById("delete" + number).style.visibility = 'visible';
248 function hideDel(number) {
249 document.getElementById("delete" + number).style.visibility = 'hidden';
252 function del(number) {
253 var new_places = new Array();
254 for (var i = 0; i < places.length; i++) {
255 if (i == number - 1) {
256 places[i].marker.setMap(null);
257 places[i].directions_display.setMap(null);
259 new_places.push(places[i]);
266 function createPlace(latlng, name) {
267 var number = places.length + 1;
271 var iconname = 'icon' + ( 7 + number );
272 var marker = new google.maps.Marker({ position: latlng,
274 icon:getIcon(iconname),
275 shadow:getShadow(iconname) });
277 document.getElementById("resultslist").innerHTML += "<li onmouseover='showDel(" + number + ")' onmouseout='hideDel(" + number + ")'><img class='iconwithtext' src='images/" + iconname + ".png'/> " + name
278 + "<div style='float:right; padding:3; visibility:hidden' id='delete" + number
279 + "'><a href='#' onclick='del(" + number + ")'><img src='images/bin.png'/></a></div>"
280 + "<br/><span id='directions" + number + "'></span></li>";
281 var directions_display = new google.maps.DirectionsRenderer({ preserveViewport: true, suppressMarkers: true, draggable: true });
282 google.maps.event.addListener(directions_display, "directions_changed", function() {
283 displayDirectionsMoreinfo(directions_display.getDirections().routes[0], document.getElementById("directions" + number ));
285 places.push({marker:marker, latlng:latlng, name:name, directions_display:directions_display});
294 new Effect.Fade(document.getElementById('newbie'), {duration: 0.4});
295 new Effect.Fade(document.getElementById('usecases'), {duration: 0.4});
296 var address = document.getElementById('address').value;
297 geocoder.geocode({ 'address': address },
298 function(results, status) {
299 if (status == google.maps.GeocoderStatus.OK) {
300 createPlace(results[0].geometry.location, address);
301 document.getElementById('address').value = '';
302 document.getElementById('address').focus();
305 document.getElementById('submitshortresult').innerHTML = "<?php echo t('address.not.found') ?>";
306 document.getElementById('submitshortresult').style.display = '';
307 setTimeout('new Effect.Fade(document.getElementById("submitshortresult"), {duration: 0.4})', 1000);
312 function getstate() {
313 var link = "me=" + mylat + ";" + mylng;
314 link += "&tm=" + document.getElementById("travelmode").value;
315 link += "&fb=" + document.getElementById("fitmap").checked;
316 link += "&c=" + map.getCenter().lat() + ";" + map.getCenter().lng() + ";" + map.getZoom()
317 for (var i = 0; i < places.length; i++) {
318 link += "&p=" + places[i].latlng.lat() + ";" + places[i].latlng.lng() + ";" + encodeURI(places[i].name);
324 document.getElementById('savediv').style.display = '';
325 var save = document.getElementById('save');
326 save.value = 'http://routes-compare.zarb.org/?saved#' + getstate();
331 function startUp(setcenter) {
335 me = new google.maps.Marker({position: new google.maps.LatLng(mylat, mylng),
338 icon: getIcon('icon31'),
339 shadow: getShadow('icon31')});
341 google.maps.event.addListener(me, "dragend", function(mouseEvent) {
342 mylat = mouseEvent.latLng.lat();
343 mylng = mouseEvent.latLng.lng();
348 map.setCenter(new google.maps.LatLng(mylat, mylng));
353 function setFromState(state) {
354 var params = state.split("&");
355 for (var i = 0; i < params.length; i++) {
356 var keyvalue = params[i].split("=");
357 var values = keyvalue[1].split(";");
358 if (keyvalue[0] == 'me') {
361 } else if (keyvalue[0] == 'tm') {
362 setTravelmode(values[0]);
363 var tm = document.getElementById("travelmode");
364 for (var j = 0; j < tm.length; j++) {
365 if (tm.options[j].value == values[0]) {
366 tm.options[j].selected = true;
369 } else if (keyvalue[0] == 'fb') {
370 document.getElementById("fitmap").checked = values[0] == 'true';
371 } else if (keyvalue[0] == 'c') {
372 map.setCenter(new google.maps.LatLng(values[0], values[1]));
373 map.setZoom(parseInt(values[2]));
374 } else if (keyvalue[0] == 'p') {
375 createPlace(new google.maps.LatLng(values[0], values[1]), decodeURI(values[2]));
381 function historyPush() {
382 var state = getstate();
383 if (undo_history.length == 0 || undo_history[undo_history.length-1] != state) {
384 undo_history.push(getstate());
385 if (undo_history.length == 2) {
386 document.getElementById('undo').style.opacity = 1.0;
387 document.getElementById('undo').style.filter = '';
389 // debug('after push: ' + undo_history);
391 // debug('not pushing ' + state);
394 function historyPop() {
395 // when loading a map-related state, a new event is fired and a history element is thus added;
396 // to compensate for that effect, two pops are performed here, and an additional push below to
397 // have the same effect for non map-related states
399 var state = undo_history.pop();
400 // debug('state to load: ' + state);
405 if (undo_history.length == 1) {
406 document.getElementById('undo').style.opacity = 0.4;
407 document.getElementById('undo').style.filter = 'alpha(opacity=40)';
412 function initialize() {
413 map = new google.maps.Map(document.getElementById("mapbox"),
414 { mapTypeId: google.maps.MapTypeId.ROADMAP,
416 var currently_dragging = false;
417 google.maps.event.addListener(map, "bounds_changed", function() {
418 if (!currently_dragging) {
422 google.maps.event.addListener(map, "dragstart", function() {
423 currently_dragging = true;
425 google.maps.event.addListener(map, "dragend", function() {
427 currently_dragging = false;
432 var all_params = location.href.split("#")
433 if (all_params.length > 1 && all_params[1].length > 0) {
434 setFromState(all_params[1]);
439 mylat = <?php echo $latitude ?>;
440 mylng = <?php echo $longitude ?>;
441 var baseinfomessage = '<?php echo t('geolocated') ?>';
445 var baseinfomessage = '<?php echo t('not.geolocated') ?>';
449 map.setZoom(<?php echo $zoom ?>);
451 info_window = new google.maps.InfoWindow({ content: baseinfomessage + '<?php echo t('home.icon.move.possible') ?>' });
452 info_window.open(map, me);
453 if (!document.cookie || document.cookie.indexOf("not-a-newbie") == -1) {
454 document.getElementById("newbie").style.display = '';
456 var expires = new Date(new Date().getTime() + (10 * 86400000)); // 10 days
457 document.cookie = "not-a-newbie=true"
458 + "; expires=" + expires.toGMTString()
466 <body onload="initialize()">
468 <div id="mainbox" style="height: 100%">
470 <div id="debugbox"></div>
472 <div style="height: 2em">
473 <div style="float: right">
474 <?php echo t('travelmode') ?>
475 <select id="travelmode" onchange="travelmodeChanged()">
476 <option value="DRIVING"><?php echo t('travelmode.driving') ?></option>
477 <option value="WALKING"><?php echo t('travelmode.walking') ?></option>
478 <!-- doesn't work <option value="google.maps.DirectionsTravelMode.BICYCLING">Bicycling</option> -->
481 <input type="checkbox" id="fitmap" checked="true" onclick="fitmapChanged()"><?php echo t('fit.to.elements') ?>
483 <a href="#" onclick="save()">
484 <?php echo t('save.this.page') ?>
485 <img src="images/save.png" class="iconwithtext"/>
488 <a href="#" onclick="historyPop()" id="undo" style="opacity:0.4;filter:alpha(opacity=40)">
489 <?php echo t('undo') ?>
490 <img src="images/undo.png" class="iconwithtext"/>
493 <?php if ($language != 'en') { ?>
494 <a href="?language=en"><img src="images/en.png" class="iconwithtext" style="opacity:0.4;filter:alpha(opacity=40)"/></a>
496 <a href="?language=en"><img src="images/en.png" class="iconwithtext"/></a>
498 <?php if ($language != 'fr') { ?>
499 <a href="?language=fr"><img src="images/fr.png" class="iconwithtext" style="opacity:0.4;filter:alpha(opacity=40)"/></a>
501 <a href="?language=fr"><img src="images/fr.png" class="iconwithtext"/></a>
503 <div id="savediv" style="display: none; position:absolute; right:1em; top:3em; background:#E0E0FF; border:solid 1px black; padding:0.5em; z-index:1">
504 <?php echo t('save.page.expl') ?>
506 <input type="text" id="save" size="40"/>
508 <div style="float:right"><a href="#" onclick="javascript:new Effect.Fade(document.getElementById('savediv'), {duration: 0.4})"><?php echo t('Okay') ?></a></div>
512 <form action="javascript:submit()">
513 <?php echo t('address') ?>
515 <input type="text" size="32" id="address"/>
516 <script type="text/javascript">
517 document.getElementById('address').focus();
519 <input type="submit" value="<?php echo t('show.route') ?>"/>
520 <span id="submitshortresult" style="color: red">
526 <div id="usecases" style="display:none; background-color: #F77; border: solid 1px blue; position:absolute; top:10%; left:30%; right:30%; z-index:1">
527 <p style="margin:1em">
528 <?php echo t('use.cases.title') ?>
530 <p style="margin:1em">
531 <?php echo t('use.case.val') ?>
533 <p style="margin:1em">
534 <?php echo t('use.case.riviera') ?>
536 <div style="float:right; margin:1em"><a href="#" onclick="javascript:new Effect.Fade(document.getElementById('usecases'), {duration: 0.4})"><?php echo t('Okay') ?></a></div>
539 <div class="containermainbox">
540 <div style="float: right; width: 75%; height: 100%">
541 <div id="mapbox" style="width: 100%; height: 95%">
543 <div style="float:right; font-size:70%; margin-top:3px">
545 <?php echo t('bottomstuff') ?>
549 <div id="newbie" style="display: none; background-color: pink; border: solid 1px blue; position:absolute; width:20%; margin:1.5em">
551 <img src="images/top.png"/>
553 <p align="right" style="margin-right:1em; margin-top:-1em">
554 <?php echo t('add.addess.top') ?>
558 <img src="images/right.png"/>
560 <p align="right" style="margin-right:1em; margin-top:-1em">
561 <?php echo t('routes.shown') ?>
565 <p align="right" style="margin-right:1em; margin-top:-1em">
566 <a href="#" onclick="javascript:new Effect.Fade(document.getElementById('newbie'), {duration: 0.4})"><?php echo t('Okay') ?></a>
569 <p style="margin:1em">
570 <a href="#" onclick="javascript:new Effect.Appear(document.getElementById('usecases'), {duration: 0.4})"><?php echo t('show.use.cases') ?></a>
573 <ul id="resultslist">