Initial commit
79
LICENSE
Normal file
@ -0,0 +1,79 @@
|
||||
This program uses several libraries and external services.
|
||||
|
||||
The main license for this program is (everything but below) :
|
||||
=========================================================================================
|
||||
This software (Velib) is licensed under the zlib/libpng License.
|
||||
|
||||
Copyright (c) 2013 Phyks
|
||||
|
||||
This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
==========================================================================================
|
||||
==========================================================================================
|
||||
|
||||
==========================================================================================
|
||||
Leaflet license (files leaflet.css, leaflet.ie.css, leaflet.js, images/layers.png, images/marker-icon.png, images/marker-icon@2x.png, marker-shadow.png) :
|
||||
==========================================================================================
|
||||
Copyright (c) 2010-2013, Vladimir Agafonkin
|
||||
Copyright (c) 2010-2011, CloudMade
|
||||
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.
|
||||
|
||||
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 HOLDER 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.
|
||||
|
||||
|
||||
|
||||
Note : The file images/marker-icon-red.png is a derivative work from marker-icon.png.
|
||||
|
||||
==========================================================================================
|
||||
Leaflet.encoded is a plugin that extends the Leaflet API with functions to encode and decode Google Maps polyline encoding (Polyline.encoded.js)
|
||||
==========================================================================================
|
||||
More information can be found at https://github.com/jieter/Leaflet.encoded
|
||||
|
||||
|
||||
==========================================================================================
|
||||
images/velo.png
|
||||
==========================================================================================
|
||||
It is part of The Noun Project and designed by National Park Service in United States (1982). It is in public domain.
|
||||
|
||||
==========================================================================================
|
||||
images/carte.png
|
||||
==========================================================================================
|
||||
It is from bevelandemboss.net and this icon is free for commercial use.
|
||||
|
||||
==========================================================================================
|
||||
images/marker_parking.png, images/marker_marker_cycle.png
|
||||
==========================================================================================
|
||||
This is from Nicolas Mollet and is licensed under Creative Commons (Attribution-Share Alike 3.0 Unported).
|
||||
More informations at http://mapicons.nicolasmollet.com
|
||||
|
||||
==========================================================================================
|
||||
images/parking.png
|
||||
==========================================================================================
|
||||
This image is in public domain.
|
||||
|
127
Polyline.encoded.js
Normal file
@ -0,0 +1,127 @@
|
||||
/*
|
||||
* L.PolylineUtil contains utilify functions for polylines, two methods
|
||||
* are added to the L.Polyline object to support creation of polylines
|
||||
* from an encoded string and converting existing polylines to an
|
||||
* encoded string.
|
||||
*
|
||||
* - L.Polyline.fromEncoded(encoded [, options]) returns a L.Polyline
|
||||
* - L.Polyline.encodePath() returns a string
|
||||
*
|
||||
* Actual code from:
|
||||
* http://facstaff.unca.edu/mcmcclur/GoogleMaps/EncodePolyline/\
|
||||
*/
|
||||
|
||||
/*jshint browser:true, debug: true, strict:false, globalstrict:false, indent:4, white:true, smarttabs:true*/
|
||||
/*global L:true, console:true*/
|
||||
|
||||
|
||||
// Inject functionality into Leaflet
|
||||
(function (L) {
|
||||
if (!(L.Polyline.prototype.fromEncoded)) {
|
||||
L.Polyline.fromEncoded = function (encoded, options) {
|
||||
return new L.Polyline(L.PolylineUtil.decode(encoded), options);
|
||||
};
|
||||
}
|
||||
if (!(L.Polygon.prototype.fromEncoded)) {
|
||||
L.Polygon.fromEncoded = function (encoded, options) {
|
||||
return new L.Polygon(L.PolylineUtil.decode(encoded), options);
|
||||
};
|
||||
}
|
||||
|
||||
var encodeMixin = {
|
||||
encodePath: function () {
|
||||
return L.PolylineUtil.encode(this.getLatLngs());
|
||||
}
|
||||
};
|
||||
|
||||
if (!L.Polyline.prototype.encodePath) {
|
||||
L.Polyline.include(encodeMixin);
|
||||
}
|
||||
if (!L.Polygon.prototype.encodePath) {
|
||||
L.Polygon.include(encodeMixin);
|
||||
}
|
||||
})(L);
|
||||
|
||||
// Utility functions.
|
||||
L.PolylineUtil = {};
|
||||
|
||||
L.PolylineUtil.encode = function (latlngs) {
|
||||
var i, dlat, dlng;
|
||||
var plat = 0;
|
||||
var plng = 0;
|
||||
var encoded_points = "";
|
||||
|
||||
for (i = 0; i < latlngs.length; i++) {
|
||||
var lat = latlngs[i].lat;
|
||||
var lng = latlngs[i].lng;
|
||||
var late5 = Math.floor(lat * 1e5);
|
||||
var lnge5 = Math.floor(lng * 1e5);
|
||||
dlat = late5 - plat;
|
||||
dlng = lnge5 - plng;
|
||||
plat = late5;
|
||||
plng = lnge5;
|
||||
encoded_points +=
|
||||
L.PolylineUtil.encodeSignedNumber(dlat) +
|
||||
L.PolylineUtil.encodeSignedNumber(dlng);
|
||||
}
|
||||
return encoded_points;
|
||||
};
|
||||
|
||||
// This function is very similar to Google's, but I added
|
||||
// some stuff to deal with the double slash issue.
|
||||
L.PolylineUtil.encodeNumber = function (num) {
|
||||
var encodeString = "";
|
||||
var nextValue, finalValue;
|
||||
while (num >= 0x20) {
|
||||
nextValue = (0x20 | (num & 0x1f)) + 63;
|
||||
encodeString += (String.fromCharCode(nextValue));
|
||||
num >>= 5;
|
||||
}
|
||||
finalValue = num + 63;
|
||||
encodeString += (String.fromCharCode(finalValue));
|
||||
return encodeString;
|
||||
};
|
||||
|
||||
// This one is Google's verbatim.
|
||||
L.PolylineUtil.encodeSignedNumber = function (num) {
|
||||
var sgn_num = num << 1;
|
||||
if (num < 0) {
|
||||
sgn_num = ~(sgn_num);
|
||||
}
|
||||
return (L.PolylineUtil.encodeNumber(sgn_num));
|
||||
};
|
||||
|
||||
L.PolylineUtil.decode = function (encoded) {
|
||||
var len = encoded.length;
|
||||
var index = 0;
|
||||
var latlngs = [];
|
||||
var lat = 0;
|
||||
var lng = 0;
|
||||
|
||||
while (index < len) {
|
||||
var b;
|
||||
var shift = 0;
|
||||
var result = 0;
|
||||
do {
|
||||
b = encoded.charCodeAt(index++) - 63;
|
||||
result |= (b & 0x1f) << shift;
|
||||
shift += 5;
|
||||
} while (b >= 0x20);
|
||||
var dlat = ((result & 1) ? ~(result >> 1) : (result >> 1));
|
||||
lat += dlat;
|
||||
|
||||
shift = 0;
|
||||
result = 0;
|
||||
do {
|
||||
b = encoded.charCodeAt(index++) - 63;
|
||||
result |= (b & 0x1f) << shift;
|
||||
shift += 5;
|
||||
} while (b >= 0x20);
|
||||
var dlng = ((result & 1) ? ~(result >> 1) : (result >> 1));
|
||||
lng += dlng;
|
||||
|
||||
latlngs.push(new L.LatLng(lat * 1e-5, lng * 1e-5));
|
||||
}
|
||||
|
||||
return latlngs;
|
||||
};
|
64
README.md
@ -1,2 +1,62 @@
|
||||
Bikes-Paris
|
||||
===========
|
||||
README for Velib app
|
||||
====================================================================================
|
||||
(An english version is available below)
|
||||
|
||||
Cette application a été developpée par Phyks (webmaster@phyks.me). Elle vous permet de localiser les vélibs et les emplacements de vélibs les plus proches de vous et de vous y guider. Elle est distribuée sous licence zlib/libpng.
|
||||
|
||||
Pour plus d'informations sur les licences des différentes parties (leaflet, images), se référer au fichier LICENSE.
|
||||
|
||||
La fonction d'obtention des noms à partir des coordonnées GPS est fournie par Mapquest, les cartes sont fournies par OpenStreetMap et les itinéraires sont fournis par OSRM.
|
||||
|
||||
Pour toute suggestion ou remarque, envoyer un e-mail à webmaster@phyks.me.
|
||||
|
||||
Installation sur votre serveur :
|
||||
================================
|
||||
* Décompresser l'archive dans un dossier accessible par votre serveur web.
|
||||
* S'assurer que le serveur web a les droits en écriture sur le répertoire "data".
|
||||
* Éditer la configuration en haut du fichier js.js (fournisseur de tiles OSM et de fonctions de reverse geoposition, adresse e-mail).
|
||||
* L'application mettra automatiquement à jour la liste des stations au premier démarrage.
|
||||
|
||||
* Pour mettre à jour automatiquement la liste des stations, vous pouvez utiliser une tâche cron comme suit :
|
||||
sudo crontab -e
|
||||
puis insérer la ligne
|
||||
* * * * * wget -q -O adresse_de_base_de_velib/index.php?update=1&code=code_synchro #Commande de mise a jour des stations de velib
|
||||
|
||||
en remplaçant code_synchro par votre code de synchronisation et en définissant * conformément à la fréquence de mises à jour souhaitée.
|
||||
|
||||
Notes :
|
||||
=======
|
||||
* Si vous avez perdu votre code de synchronisation, il suffit de supprimer le fichier data/data pour le réinitialiser (il faudra alors refaire une synchronisation des stations à la visite suivante).
|
||||
* Bien que cette application ait été optimisée, notamment au niveau du nombre de requêtes vers des services distants, elle a été créée dans l'optique de répondre à mon besoin et peut supporter difficilement une charge importante.
|
||||
|
||||
====================================================================================
|
||||
====================================================================================
|
||||
====================================================================================
|
||||
English version :
|
||||
|
||||
This app has been developped by Phyks (webmaster@phyks.me). It allows you to locate the nearest velibs (parisian public bicycle sharing service) and the nearest velibs station. It is released under zlib/libpng license.
|
||||
|
||||
For more information about the licenses of the diverse libraries and images (leaflet ...), please refer to the LICENSE file.
|
||||
|
||||
The reverse geolocation system is provided by Mapquest, maps are provided by OpenStreetMap and routes are provided by OSRM.
|
||||
|
||||
For any suggestion or remark, please send me an e-mail at webmaster@phyks.me.
|
||||
|
||||
Installation on your own server :
|
||||
================================
|
||||
* Decompress the archive file in a folder accessible to your web server.
|
||||
* Ensure that your web server can write in the "data" directory.
|
||||
* Edit the configuration in the js.js file (OSM tiles provider, reverse geolocation provider and email).
|
||||
* The application will automatically update the stations list at first run.
|
||||
|
||||
* To automatically update the stations list, you can use a cron task as following :
|
||||
sudo crontab -e
|
||||
then, add a line
|
||||
* * * * * wget -q -O adresse_de_base_de_velib/index.php?update=1&code=code_synchro #Update velib stations
|
||||
|
||||
Don't forget to replace code_synchro by your synchronisation code and define * according to the update frequency you want.
|
||||
|
||||
Notes :
|
||||
=======
|
||||
* If you have lost your synchronisation code, just delete the file data/data to reset it. You'll then have to make a full update at the next visit.
|
||||
* Although I tried to optimize this application, it was created to answer my own need and may not be suited for a large scale website with a great load.
|
||||
|
197
ajax.php
Normal file
@ -0,0 +1,197 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
//AJAX query code
|
||||
if(!empty($_POST['latitude']) && !empty($_POST['longitude']) && (!empty($_POST['available']) || !empty($_POST['free'])) && !empty($_POST['email']) && !empty($_POST['reverse_geolocation_provider']) && !empty($_POST['directions_provider']))
|
||||
{
|
||||
$updated_position = false;
|
||||
$updated_stations = false;
|
||||
|
||||
//Define UserAgent etc
|
||||
$opts = array(
|
||||
'http'=>array(
|
||||
'method'=>"GET",
|
||||
'header'=>"Accept-language: fr\r\n" .
|
||||
"Referer: ".$_SERVER['HTTP_REFERER'].
|
||||
"User-Agent: Velib service at http://velib.phyks.me (contact : webmaster@phyks.me) \r\n"
|
||||
)
|
||||
);
|
||||
$context = stream_context_create($opts);
|
||||
|
||||
//Check wether the position was updated
|
||||
//-------------------------------------
|
||||
|
||||
//Convert latitude and longitude to degrees (useful for calculation)
|
||||
$latitude = deg2rad((float) $_POST['latitude']);
|
||||
$longitude = deg2rad((float) $_POST['longitude']);
|
||||
|
||||
$a = pow(sin($_SESSION['latitude'] - $latitude)/2, 2) + cos($latitude)*cos($_SESSION['latitude'])*pow(sin($_SESSION['longitude'] - $longitude)/2, 2);
|
||||
$c = 2*atan2(sqrt($a),sqrt(1-$a));
|
||||
$R = 6371000;
|
||||
$distance = $R*$c;
|
||||
|
||||
if(empty($_SESSION['latitude']) || empty($_SESSION['longitude']) || empty($_SESSION['reverse_geolocation']) || $distance >= 25 || empty($_SESSION['distances']))
|
||||
{
|
||||
$_SESSION['latitude'] = $_POST['latitude'];
|
||||
$_SESSION['longitude'] = $_POST['longitude'];
|
||||
$updated_position = true;
|
||||
}
|
||||
|
||||
//If yes, update the address
|
||||
if($updated_position)
|
||||
{
|
||||
$reverse_geolocation_json = file_get_contents($_POST['reverse_geolocation_provider']."?format=json&lat=".$_POST['latitude']."&lon=".$_POST['longitude']."&zoom=18&addressdetails=1&email=".$_POST['email'], false, $context);
|
||||
$reverse_geolocation_json = json_decode($reverse_geolocation_json, true);
|
||||
|
||||
$reverse_geolocation = '';
|
||||
foreach($reverse_geolocation_json['address'] as $key=>$element)
|
||||
{
|
||||
if($key == 'city')
|
||||
break;
|
||||
|
||||
if(!empty($reverse_geolocation))
|
||||
$reverse_geolocation .= ", ";
|
||||
|
||||
$reverse_geolocation .= $element;
|
||||
}
|
||||
|
||||
$_SESSION['reverse_geolocation'] = $reverse_geolocation;
|
||||
}
|
||||
else //Else, keep what was stored in session
|
||||
{
|
||||
$reverse_geolocation = $_SESSION['reverse_geolocation'];
|
||||
}
|
||||
|
||||
if(is_file('data/data')) //And open the data file
|
||||
{
|
||||
$data = unserialize(gzinflate(base64_decode(file_get_contents('data/data'))));
|
||||
$stations = $data[1];
|
||||
}
|
||||
else
|
||||
exit("[{'error': '<p>La liste des stations n'a pu être récupérée. Essayez de la mettre à jour manuellement.</p>'}]");
|
||||
|
||||
|
||||
if(!empty($_POST['station'])) //If we want information about a specific station
|
||||
{
|
||||
$stations_used[$_POST['station']] = $stations[$_POST['station']]; //We only use it - little trick to keep the same code
|
||||
}
|
||||
else
|
||||
{
|
||||
$stations_used = $stations; //Else, we use all the stations
|
||||
}
|
||||
|
||||
if($updated_position) //If position updated
|
||||
{
|
||||
unset($_SESSION['distances']);
|
||||
|
||||
//Compute the distance
|
||||
foreach($stations_used as $key=>$station) //We start by sorting the stations by distance to me
|
||||
{
|
||||
$station_lat = deg2rad($station['lat']);
|
||||
$station_lng = deg2rad($station['lng']);
|
||||
|
||||
$a = pow(sin($station_lat - $latitude)/2, 2) + cos($latitude)*cos($station_lat)*pow(sin($station_lng - $longitude)/2, 2);
|
||||
$c = 2*atan2(sqrt($a),sqrt(1-$a));
|
||||
$R = 6371000;
|
||||
$distances[$key] = $R*$c;
|
||||
}
|
||||
asort($distances);
|
||||
|
||||
if(!empty($_POST['station'])) //But store the result only if not computed for a single station
|
||||
$_SESSION['distances'] = array_slice($distances, 0, 10, true); //Store the 10 first values in session
|
||||
}
|
||||
else //Else, get the result stored
|
||||
{
|
||||
if(empty($_POST['station']))
|
||||
$distances = $_SESSION['distances']; //If list required, get the currently stored list
|
||||
else //Else, get the only one we want
|
||||
$distances[(int) $_POST['station']] = $_SESSION['distances'][(int) $_POST['station']];
|
||||
}
|
||||
|
||||
//Print the JSON
|
||||
echo '[{"reverse_geolocation": "'.htmlentities($reverse_geolocation).'"}, ';
|
||||
|
||||
$i = 0;
|
||||
foreach($distances as $key=>$distance) //Print the information about the 10 nearest stations
|
||||
{
|
||||
if($i >= 10)
|
||||
break;
|
||||
|
||||
//Get number of velibs / parkings available
|
||||
if($stations[$key]['updated'] < time() - 60) //If data is older than 1 minute, update it
|
||||
{
|
||||
//Mise à jour du tableau
|
||||
$station_xml = simplexml_load_file('http://www.velib.paris.fr/service/stationdetails/paris/'.$stations[$key]['id']);
|
||||
|
||||
$stations[$key]['updated'] = time(); //Update the stations array
|
||||
$stations[$key]['available'] = (int) $station_xml->available;
|
||||
$stations[$key]['free'] = (int) $station_xml->free;
|
||||
$stations[$key]['open'] = (int) $station_xml->open;
|
||||
|
||||
$updated_stations = true; //We updated the array (so we must update the data file)
|
||||
}
|
||||
|
||||
$number = (!empty($_POST['free'])) ? $stations[$key]['free'] : $stations[$key]['available']; //Get the number of velibs / parkings ($stations is always up to date or acceptable)
|
||||
|
||||
if($number != 0 && $stations[$key]['open'] == 1) //If this station is interesting and opened
|
||||
{
|
||||
echo '{"key": "'.(int) $key.'", "dist": "'.(int) $distance.'", "bonus": "'.(int) $stations[$key]['bonus'].'", "lat": "'.(float) $stations[$key]['lat'].'", "lng": "'.(float) $stations[$key]['lng'].'", "nombre": "'.(int) $number.'", "nom": "'.substr($stations[$key]['name'], strpos($stations[$key]['name'], "-")+1).'"';
|
||||
|
||||
if(!empty($_POST['station'])) //If we only want content about this station, get the directions and the address
|
||||
{
|
||||
if($updated_position || empty($_SESSION['directions']) || $_SESSION['directions']['key'] != $_POST['station']) //Check wether position was updated, session var doesn't exist or routes isn't stored for this particular station
|
||||
{
|
||||
unset($_SESSION['directions']); //Destroy the previous variable
|
||||
|
||||
if(is_file('data/checksum')) //Checksum is required in OSRM Usage Policy
|
||||
$checksum = '&checksum='.file_get_contents('data/checksum');
|
||||
else
|
||||
$checksum = '';
|
||||
|
||||
$directions_json = file_get_contents($_POST['directions_provider'].'?loc='.$_POST['latitude'].','.$_POST['longitude'].'&loc='.$stations[$key]['lat'].','.$stations[$key]['lng'].'&z=18&output=json&instructions=false&alt=false&geomformat=cmp'.$checksum, false, $context);
|
||||
|
||||
$directions_json = json_decode($directions_json, true);
|
||||
|
||||
if(!empty($directions_json['hint_data']['checksum']))
|
||||
file_put_contents('data/checksum', $directions_json['hint_data']['checksum']);
|
||||
|
||||
$directions_encoded = json_encode($directions_json['route_geometry']);
|
||||
|
||||
if(!empty($directions_json['route_geometry']))
|
||||
echo ', "directions": '.$directions_encoded;
|
||||
else
|
||||
echo ', "directions": ""';
|
||||
|
||||
//And then, set new ones
|
||||
$_SESSION['directions']['key'] = (int) $_POST['station'];
|
||||
$_SESSION['directions']['directions'] = $directions_encoded;
|
||||
}
|
||||
else
|
||||
{
|
||||
echo ', "directions": '.$_SESSION['directions']['directions'];
|
||||
}
|
||||
|
||||
//Get the address
|
||||
echo ', "address": "'.substr($stations[$key]['address'], 0, strpos($stations[$key]['address'], " - 75")+1).'"';
|
||||
}
|
||||
|
||||
//Print the JSON data
|
||||
echo '}';
|
||||
|
||||
if($i != 9 && $i != count($distances) - 1) //Attention : distance can be less than 9 elements long (if we specify a station for example)
|
||||
echo ', ';
|
||||
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
||||
echo ']'; //And close the JSON array
|
||||
|
||||
//If needed, update the data file
|
||||
if($updated_stations)
|
||||
{
|
||||
file_put_contents('data/data', base64_encode(gzdeflate(serialize(array($data[0], $stations)))));
|
||||
}
|
||||
}
|
||||
else
|
||||
exit("[{'error': '<p>La liste des stations n'a pu être récupérée. Essayez de la mettre à jour manuellement.</p>'}]");
|
1
data/checksum
Normal file
@ -0,0 +1 @@
|
||||
-203876984
|
BIN
favicon.ico
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
images/carte.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
images/layers.png
Normal file
After Width: | Height: | Size: 973 B |
BIN
images/marker-icon-red.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
images/marker-icon.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
images/marker-icon@2x.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
images/marker-shadow.png
Normal file
After Width: | Height: | Size: 797 B |
BIN
images/marker_cycle.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
images/marker_parking.png
Normal file
After Width: | Height: | Size: 712 B |
BIN
images/marker_parking_bonus.png
Normal file
After Width: | Height: | Size: 526 B |
BIN
images/parking.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
images/shadow_icons.png
Normal file
After Width: | Height: | Size: 979 B |
BIN
images/velo.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
104
index.php
Normal file
@ -0,0 +1,104 @@
|
||||
<?php
|
||||
session_start(); //Sessions are used to limit the ajax need and the communication between server and velib server (velib servers will block you if you make too many requests).
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Vélibs à proximité</title>
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="phyks">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="stylesheet" href="main.css" type="text/css" media="screen">
|
||||
|
||||
<link rel="stylesheet" href="leaflet.css" />
|
||||
<!--[if lte IE 8]>
|
||||
<link rel="stylesheet" href="leaflet.ie.css" />
|
||||
<![endif]-->
|
||||
<script src="leaflet.js"></script>
|
||||
<script type="text/javascript" src="Polyline.encoded.js"></script>
|
||||
<script type="text/javascript" src="js.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1><a href="index.php">Vélibs à proximité</a></h1>
|
||||
|
||||
<?php
|
||||
if(!is_file('data/data')) //First run
|
||||
{
|
||||
//Define a new synchronisation code
|
||||
$code_synchro = substr(sha1(rand(0,30).time().rand(0,30)),0,10);
|
||||
|
||||
file_put_contents('data/data', base64_encode(gzdeflate(serialize(array($code_synchro, ''))))); //Save it in data/data file
|
||||
|
||||
$_GET['code'] = $code_synchro;
|
||||
|
||||
echo "<p>
|
||||
Définition du code de synchronisation.<br/>
|
||||
Vous pouvez désormais mettre à jour la liste des stations en visitant l'adresse suivante (update URL) :<br/>
|
||||
<a href='http://" . $_SERVER["SERVER_NAME"].$_SERVER['REQUEST_URI']."?update=1&code=".$code_synchro."'>http://" . $_SERVER["SERVER_NAME"].$_SERVER['REQUEST_URI']."?update=1&code=".$code_synchro."</a>
|
||||
</p>
|
||||
<p>
|
||||
Il est possible d'automatiser la tâche via une tâche cron. Par exemple (see README) :<br/>
|
||||
* * * * * wget -q -O <a href='http://" . $_SERVER["SERVER_NAME"].$_SERVER['REQUEST_URI']."?update=1&code=".$code_synchro."'>http://" . $_SERVER["SERVER_NAME"].$_SERVER['REQUEST_URI']."?update=1&code=".$code_synchro."</a> #Commande de mise a jour des stations de velib
|
||||
</p>";
|
||||
}
|
||||
|
||||
if(!empty($_GET['update']) || !empty($code_synchro)) //If we want to make an update (or first run)
|
||||
{
|
||||
if(empty($code_synchro) && is_file('data/data')) //If not first run, get the synchronisation code from data file
|
||||
{
|
||||
$data = unserialize(gzinflate(base64_decode(file_get_contents('data/data'))));
|
||||
$code_synchro = $data[0];
|
||||
}
|
||||
|
||||
if(!empty($_GET['code']) && $_GET['code'] == $code_synchro) //Once we have the code and it is correct
|
||||
{
|
||||
$stations_xml = simplexml_load_file('http://www.velib.paris.fr/service/carto');
|
||||
|
||||
$liste_stations = $stations_xml->markers->marker; //Get the stations list
|
||||
|
||||
foreach($liste_stations as $station)
|
||||
{
|
||||
$stations[] = array('name' => htmlentities((string) $station['name']), 'id' => (int) $station['number'], 'address' => htmlentities((string) $station['fullAddress']), 'lat' => (float) $station['lat'], 'lng' => (float) $station['lng'], 'open' => (int) $station['open'], 'bonus' => (int) $station['bonus'], 'free' => 0, 'available' => 0, 'updated' => 0);
|
||||
}
|
||||
|
||||
file_put_contents('data/data', base64_encode(gzdeflate(serialize(array($code_synchro, $stations))))); //And put the content in the data file
|
||||
|
||||
echo "<p>Mise à jour de la liste des stations effectuée avec succès (Update successful).</p>";
|
||||
}
|
||||
else
|
||||
{
|
||||
echo "<p>Mauvais code de vérification (Error : bad synchronisation code). Veuillez réessayer la mise à jour. Se référer au README pour plus d'informations sur la mise à jour.</p>";
|
||||
}
|
||||
echo "<p><a href='index.php'>Retourner à l'application (Back to index)</a></p>";
|
||||
}
|
||||
else
|
||||
{
|
||||
?>
|
||||
<div id="see_map"></div>
|
||||
<div id="stations"></div>
|
||||
<?php
|
||||
if(!empty($_GET['map']))
|
||||
{
|
||||
?>
|
||||
<div id="map"></div>
|
||||
<?php
|
||||
$param = (!empty($_GET['available'])) ? 'available' : 'free';
|
||||
echo "<p><a href='index.php?".$param."=1'>← Retour à la liste</a></p>";
|
||||
}
|
||||
if(!empty($_GET))
|
||||
{
|
||||
echo "<hr/>";
|
||||
}
|
||||
?>
|
||||
<div id="position">
|
||||
<p><strong>Votre navigateur doit prendre en charge la géolocalisation pour que ce site puisse fonctionner correctement.<br/>Your browser must have geolocation capabilities for this site to display.</strong></p>
|
||||
</div>
|
||||
<hr/>
|
||||
<p id="thanks">Map is handled thanks to the <a href="http://leafletjs.com/">Leaflet</a> library, using © <a href="http://osm.org/copyright">OpenStreetMap</a> contributors tiles. Reverse geolocation (Nominatim) are provided by the <a href="http://www.mapquest.com/" alt="MapQuest icon">MapQuest</a> <img src="http://developer.mapquest.com/content/osm/mq_logo.png"> open API. Routes are provided by <a href='http://project-osrm.org/'>the OSRM project</a> (OSRM is a free and open source program under GNU Affero GPL).</p>
|
||||
<p id="suggestions">N'hésitez pas à m'envoyer vos suggestions à <a href="mailto:webmaster@phyks.me">webmaster@phyks.me</a>.</p>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
}
|
||||
?>
|
372
js.js
Normal file
@ -0,0 +1,372 @@
|
||||
/* Config : À éditer selon vos besoins */
|
||||
var tiles_provider = 'http://{s}.tile.osm.org/{z}/{x}/{y}.png';
|
||||
var reverse_geolocation_provider = "http://open.mapquestapi.com/nominatim/v1/reverse.php";
|
||||
var directions_provider = "http://router.project-osrm.org/viaroute";
|
||||
var email = "webmaster@phyks.me"; //Mettre votre adresse e-mail ici si vous utilisez Nominatim (cf Usage Policy de Nominatim)
|
||||
|
||||
/* Script : */
|
||||
|
||||
window.onload = function() {
|
||||
function params() //Get all the parameters in the URL
|
||||
{
|
||||
var t = location.search.substring(1).split('&');
|
||||
var params = [];
|
||||
|
||||
for (var i=0; i<t.length; i++)
|
||||
{
|
||||
var x = t[ i ].split('=');
|
||||
params[x[0]] = x[1];
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
var params = params();
|
||||
|
||||
var params_url = '';
|
||||
var map_get = false;
|
||||
var station = false;
|
||||
var free = false;
|
||||
var available = false;
|
||||
var update = false;
|
||||
var refresh = false;
|
||||
|
||||
for(GET in params) //Define boolean to handle the different cases next
|
||||
{
|
||||
if(params_url != '')
|
||||
params_url += '&';
|
||||
|
||||
params_url += GET+'='+params[GET];
|
||||
|
||||
switch(GET)
|
||||
{
|
||||
case 'map':
|
||||
map_get = true;
|
||||
break;
|
||||
|
||||
case 'available':
|
||||
available = true;
|
||||
break;
|
||||
|
||||
case 'free':
|
||||
free = true;
|
||||
break;
|
||||
|
||||
case 'station':
|
||||
station = true;
|
||||
break;
|
||||
|
||||
case 'update':
|
||||
update = true;
|
||||
break;
|
||||
|
||||
case 'refresh':
|
||||
refresh = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(available || free || station)
|
||||
{
|
||||
if(update == false && navigator.geolocation) //We don't want to update and the navigator as geolocation capabilities
|
||||
{
|
||||
function successFunction(position)
|
||||
{
|
||||
var latitude = position.coords.latitude; //Get the current position
|
||||
var longitude = position.coords.longitude;
|
||||
|
||||
var xhr; //Define xhr variables
|
||||
try
|
||||
{
|
||||
xhr = new XMLHttpRequest();
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
try
|
||||
{
|
||||
xhr = new ActiveXObject('Msxml2.XMLHTTP');
|
||||
}
|
||||
catch (e2)
|
||||
{
|
||||
try
|
||||
{
|
||||
xhr = new ActiveXObject('Microsoft.XMLHTTP');
|
||||
}
|
||||
catch (e3)
|
||||
{
|
||||
xhr = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(xhr == false)
|
||||
{
|
||||
document.getElementById("position").innerHTML = "<p>Une erreur a été rencontrée. Veuillez réessayer.</p>";
|
||||
}
|
||||
else
|
||||
{
|
||||
xhr.onreadystatechange = function()
|
||||
{
|
||||
if(xhr.readyState == 4)
|
||||
{
|
||||
if(xhr.status == 200)
|
||||
{
|
||||
var json = JSON.parse(xhr.responseText); //Parse the response
|
||||
|
||||
if(json.length == 1) //If there was an error
|
||||
{
|
||||
document.getElementById('stations').innerHTML = json[0].error;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(map_get == false && station == false) //If we want to display a table
|
||||
{
|
||||
var display = '<table>';
|
||||
for(var i = 1; i < json.length; i++)
|
||||
{
|
||||
display += "<tr><td class='left'>À "+json[i].dist+" mètres</td><td class='right'><a href='station.php?"+params_url+"&station="+json[i].key+"'>"+json[i].nom+"</a></td>";
|
||||
if(json[i].nombre == 1)
|
||||
{
|
||||
if(available == true)
|
||||
display += "<td class='right'>"+json[i].nombre+" vélo</td></tr>";
|
||||
else
|
||||
display += "<td class='right'>"+json[i].nombre+" emplacement</td></tr>";
|
||||
}
|
||||
else
|
||||
{
|
||||
if(available == true)
|
||||
display += "<td class='right'>"+json[i].nombre+" vélos</td></tr>";
|
||||
else
|
||||
display += "<td class='right'>"+json[i].nombre+" emplacements</td></tr>";
|
||||
}
|
||||
}
|
||||
|
||||
display += "</table>";
|
||||
|
||||
document.getElementById('stations').innerHTML = display;
|
||||
}
|
||||
else if(station == true) //Else, if we want to display information about a specific station
|
||||
{
|
||||
var display = "<h2><a href='station.php?"+params_url+"'>Station "+json[1].nom+"</a> (À "+json[1].dist+" mètres)</h2>";
|
||||
|
||||
display += "<p><em>Adresse : </em>"+json[1].address+"</p>";
|
||||
|
||||
if(json[1].nombre == 1)
|
||||
{
|
||||
if(available == true)
|
||||
{
|
||||
display += "<p>Il y a actuellement <strong>"+json[1].nombre+" vélo</strong> disponible.</p>";
|
||||
nombre = "1 vélo";
|
||||
}
|
||||
else
|
||||
{
|
||||
display += "<p>Il y a actuellement <strong>"+json[1].nombre+" emplacement</strong> disponible.</p>";
|
||||
nombre = "1 emplacement";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(available == true)
|
||||
{
|
||||
display += "<p>Il y a actuellement <strong>"+json[1].nombre+" vélos</strong> disponibles.</p>";
|
||||
nombre = json[1].nombre+" vélos";
|
||||
}
|
||||
else
|
||||
{
|
||||
display += "<p>Il y a actuellement <strong>"+json[1].nombre+" emplacements</strong> disponibles.</p>";
|
||||
nombre = json[1].nombre+" emplacements";
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('stations').innerHTML = display;
|
||||
|
||||
if(available) //Add the markers and the popups
|
||||
L.marker([json[1].lat, json[1].lng], {icon: cycleMarker}).addTo(map).bindPopup(json[1].nom+" <br/>"+nombre+"<br/>(À "+json[1].dist+" mètres)").openPopup();
|
||||
else
|
||||
{
|
||||
if(json[i].bonus == 1)
|
||||
L.marker([json[1].lat, json[1].lng], {icon: parkingMarkerBonus}).addTo(map).bindPopup("<a href='station.php?"+params_url+"&station="+json[i].key+"'>"+json[i].nom+"</a><br/>"+nombre+"<br/>(À "+json[i].dist+" mètres)");
|
||||
else
|
||||
L.marker([json[1].lat, json[1].lng], {icon: parkingMarker}).addTo(map).bindPopup("<a href='station.php?"+params_url+"&station="+json[i].key+"'>"+json[i].nom+"</a><br/>"+nombre+"<br/>(À "+json[i].dist+" mètres)");
|
||||
}
|
||||
|
||||
if(free)
|
||||
var routeType = "bicycle";
|
||||
else
|
||||
var routeType = "pedestrian";
|
||||
|
||||
var route_line = L.Polyline.fromEncoded(json[1].directions, {color: 'blue'}).addTo(map);
|
||||
|
||||
map.fitBounds(route_line.getBounds()); //Make the map size optimized for the content
|
||||
}
|
||||
else //Else, we want to display a map
|
||||
{
|
||||
var latitude_max = 0;
|
||||
var latitude_min = 90;
|
||||
var longitude_max = -180;
|
||||
var longitude_min = 180;
|
||||
|
||||
for(var i = 1; i < json.length; i++)
|
||||
{
|
||||
var nombre;
|
||||
|
||||
if(json[i].nombre == 1)
|
||||
{
|
||||
if(available == true)
|
||||
nombre = json[i].nombre+" vélo";
|
||||
else
|
||||
nombre = json[i].nombre+" emplacement";
|
||||
}
|
||||
else
|
||||
{
|
||||
if(available == true)
|
||||
nombre = json[i].nombre+" vélos";
|
||||
else
|
||||
nombre = json[i].nombre+" emplacements";
|
||||
}
|
||||
|
||||
if(json[i].lat < latitude_min)
|
||||
latitude_min = json[i].lat;
|
||||
if(json[i].lat > latitude_max)
|
||||
latitude_max = json[i].lat;
|
||||
|
||||
if(json[i].lng < longitude_min)
|
||||
longitude_min = json[i].lng;
|
||||
if(json[i].lng > longitude_max)
|
||||
longitude_max = json[i].lng;
|
||||
|
||||
if(available) //Set the markers and popups
|
||||
L.marker([json[i].lat, json[i].lng], {icon: cycleMarker}).addTo(map).bindPopup("<a href='station.php?"+params_url+"&station="+json[i].key+"'>"+json[i].nom+"</a><br/>"+nombre+"<br/>(À "+json[i].dist+" mètres)");
|
||||
else
|
||||
{
|
||||
if(json[i].bonus == 1)
|
||||
L.marker([json[i].lat, json[i].lng], {icon: parkingMarkerBonus}).addTo(map).bindPopup("<a href='station.php?"+params_url+"&station="+json[i].key+"'>"+json[i].nom+"</a><br/>"+nombre+"<br/>(À "+json[i].dist+" mètres)");
|
||||
else
|
||||
L.marker([json[i].lat, json[i].lng], {icon: parkingMarker}).addTo(map).bindPopup("<a href='station.php?"+params_url+"&station="+json[i].key+"'>"+json[i].nom+"</a><br/>"+nombre+"<br/>(À "+json[i].dist+" mètres)");
|
||||
}
|
||||
}
|
||||
|
||||
map.fitBounds([[parseFloat(latitude_min), parseFloat(longitude_min)], [parseFloat(latitude_max) + 0.00015, parseFloat(longitude_max) + 0.00015]]); //0.00015 = margin because of markers size
|
||||
//Make the map fit the data
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
document.getElementById("stations").innerHTML = "<p>La liste des stations n'a pu être récupérée.</p>";
|
||||
}
|
||||
|
||||
document.getElementById("adresse").innerHTML = json[0].reverse_geolocation+"→ <a href='index.php?"+params_url+"&refresh=1'>↻ Actualiser ?</a>"; //Display the interesting part of the address
|
||||
}
|
||||
};
|
||||
|
||||
xhr.open("POST", "ajax.php", true); //xhr handle the data about stations
|
||||
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
xhr.send("latitude=" + latitude + "&longitude=" + longitude + "&" + params_url + "&email=" + email + "&directions_provider="+ encodeURI(directions_provider) +"&reverse_geolocation_provider="+ encodeURI(reverse_geolocation_provider));
|
||||
}
|
||||
|
||||
if(map_get == true || station) //If we need a map
|
||||
{
|
||||
document.getElementById("position").innerHTML = "<h2>Position :</h2><p id='adresse'>Latitude : "+latitude+", Longitude : "+longitude+" → <a href='index.php?"+params_url+"&refresh=1'>↻ Actualiser ?</a></p>";
|
||||
// create a map in the "map" div, set the view to a given place and zoom
|
||||
var map = L.map('map').setView([latitude, longitude], 16);
|
||||
|
||||
// add an OpenStreetMap tile layer
|
||||
L.tileLayer(tiles_provider, {attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'}).addTo(map);
|
||||
|
||||
var redMarker = L.icon({
|
||||
iconUrl: 'images/marker-icon-red.png',
|
||||
shadowUrl: 'images/marker-shadow.png',
|
||||
|
||||
iconSize: [25, 41], // size of the icon
|
||||
shadowSize: [41, 41], // size of the shadow
|
||||
iconAnchor: [12, 41], // point of the icon which will correspond to marker's location
|
||||
shadowAnchor: [12, 41], // the same for the shadow
|
||||
popupAnchor: [1, -34] // point from which the popup should open relative to the iconAnchor
|
||||
});
|
||||
|
||||
var cycleMarker = L.icon({
|
||||
iconUrl: 'images/marker_cycle.png',
|
||||
shadowUrl: 'images/shadow_icons.png',
|
||||
|
||||
iconSize: [32, 37], // size of the icon
|
||||
shadowSize: [38, 25], // size of the shadow
|
||||
iconAnchor: [16, 35], // point of the icon which will correspond to marker's location
|
||||
shadowAnchor: [11, 18], // the same for the shadow
|
||||
popupAnchor: [0, -31] // point from which the popup should open relative to the iconAnchor
|
||||
});
|
||||
|
||||
var parkingMarker = L.icon({
|
||||
iconUrl: 'images/marker_parking.png',
|
||||
shadowUrl: 'images/shadow_icons.png',
|
||||
|
||||
iconSize: [32, 37], // size of the icon
|
||||
shadowSize: [37, 21], // size of the shadow
|
||||
iconAnchor: [16, 35], // point of the icon which will correspond to marker's location
|
||||
shadowAnchor: [11, 18], // the same for the shadow
|
||||
popupAnchor: [0, -31] // point from which the popup should open relative to the iconAnchor
|
||||
});
|
||||
|
||||
var parkingMarkerBonus = L.icon({
|
||||
iconUrl: 'images/marker_parking_bonus.png',
|
||||
shadowUrl: 'images/shadow_icons.png',
|
||||
|
||||
iconSize: [32, 37], // size of the icon
|
||||
shadowSize: [37, 21], // size of the shadow
|
||||
iconAnchor: [16, 35], // point of the icon which will correspond to marker's location
|
||||
shadowAnchor: [11, 18], // the same for the shadow
|
||||
popupAnchor: [0, -31] // point from which the popup should open relative to the iconAnchor
|
||||
});
|
||||
|
||||
// add a marker in the given location, attach some popup content to it and open the popup
|
||||
var position_marker = L.marker([latitude, longitude], {icon: redMarker}).addTo(map);
|
||||
position_marker.bindPopup('Ma position.');
|
||||
}
|
||||
else
|
||||
{
|
||||
document.getElementById("position").innerHTML = "<h2>Position :</h2><p id='adresse'>Latitude : "+latitude+", Longitude : "+longitude+" → <a href='index.php?"+params_url+"&refresh=1'>↻ Actualiser ?</a></p>";
|
||||
document.getElementById("see_map").innerHTML = "<p id='map_p'><a href='index.php?"+params_url+"&map=1'><img src='images/carte.png'/> Voir la carte</p>";
|
||||
}
|
||||
}
|
||||
|
||||
function errorFunction(error) //Handle errors
|
||||
{
|
||||
switch(error.code)
|
||||
{
|
||||
case error.TIMEOUT:
|
||||
//Restart with a greater timeout
|
||||
if(refresh)
|
||||
navigator.geolocation.getCurrentPosition(successFunction, errorFunction, {enableHighAccuracy:true, maximumAge:0, timeout:20000});
|
||||
else
|
||||
navigator.geolocation.getCurrentPosition(successFunction, errorFunction, {enableHighAccuracy:true, maximumAge:60000, timeout:20000});
|
||||
break;
|
||||
|
||||
|
||||
case error.PERMISSION_DENIED:
|
||||
document.getElementById("position").innerHTML = "<p>Erreur : L'application n'a pas l'autorisation d'utiliser les ressources de geolocalisation.</p>";
|
||||
break;
|
||||
|
||||
case error.POSITION_UNAVAILABLE:
|
||||
document.getElementById("position").innerHTML = "<p>Erreur : La position n'a pu être déterminée.</p>";
|
||||
break;
|
||||
|
||||
default:
|
||||
document.getElementById("position").innerHTML = "<p>Erreur "+error.code+" : "+error.message+"</p>";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(refresh) //If refresh, we want to force a new position (non cached)
|
||||
navigator.geolocation.getCurrentPosition(successFunction, errorFunction, {enableHighAccuracy:true, maximumAge:0, timeout:2000});
|
||||
else
|
||||
navigator.geolocation.getCurrentPosition(successFunction, errorFunction, {enableHighAccuracy:true, maximumAge:60000, timeout:2000}); //Else, we are ok with 60 seconds old position
|
||||
}
|
||||
else
|
||||
{
|
||||
document.getElementById("position").innerHTML = "<p>Votre navigateur doit prendre en charge la géolocalisation pour que ce site puisse fonctionner correctement.</p>";
|
||||
}
|
||||
}
|
||||
else //If we didn't choose what to do, display the choices
|
||||
{
|
||||
document.getElementById("position").innerHTML = "<p><a href='?available=1'><img src='images/velo.png' alt='Retirer un vélo'/></a><span id='ou'> ou </span><a href='?free=1'><img src='images/parking.png' alt='Rendre un vélo'/></a>";
|
||||
}
|
||||
}
|
458
leaflet.css
Normal file
@ -0,0 +1,458 @@
|
||||
/* required styles */
|
||||
|
||||
.leaflet-map-pane,
|
||||
.leaflet-tile,
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow,
|
||||
.leaflet-tile-pane,
|
||||
.leaflet-overlay-pane,
|
||||
.leaflet-shadow-pane,
|
||||
.leaflet-marker-pane,
|
||||
.leaflet-popup-pane,
|
||||
.leaflet-overlay-pane svg,
|
||||
.leaflet-zoom-box,
|
||||
.leaflet-image-layer,
|
||||
.leaflet-layer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.leaflet-container {
|
||||
overflow: hidden;
|
||||
-ms-touch-action: none;
|
||||
}
|
||||
.leaflet-tile,
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow {
|
||||
display: block;
|
||||
}
|
||||
/* map is broken in FF if you have max-width: 100% on tiles */
|
||||
.leaflet-container img {
|
||||
max-width: none !important;
|
||||
}
|
||||
/* stupid Android 2 doesn't understand "max-width: none" properly */
|
||||
.leaflet-container img.leaflet-image-layer {
|
||||
max-width: 15000px !important;
|
||||
}
|
||||
.leaflet-tile {
|
||||
filter: inherit;
|
||||
visibility: hidden;
|
||||
}
|
||||
.leaflet-tile-loaded {
|
||||
visibility: inherit;
|
||||
}
|
||||
.leaflet-zoom-box {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.leaflet-tile-pane { z-index: 2; }
|
||||
.leaflet-objects-pane { z-index: 3; }
|
||||
.leaflet-overlay-pane { z-index: 4; }
|
||||
.leaflet-shadow-pane { z-index: 5; }
|
||||
.leaflet-marker-pane { z-index: 6; }
|
||||
.leaflet-popup-pane { z-index: 7; }
|
||||
|
||||
|
||||
/* control positioning */
|
||||
|
||||
.leaflet-control {
|
||||
position: relative;
|
||||
z-index: 7;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.leaflet-top,
|
||||
.leaflet-bottom {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
}
|
||||
.leaflet-top {
|
||||
top: 0;
|
||||
}
|
||||
.leaflet-right {
|
||||
right: 0;
|
||||
}
|
||||
.leaflet-bottom {
|
||||
bottom: 0;
|
||||
}
|
||||
.leaflet-left {
|
||||
left: 0;
|
||||
}
|
||||
.leaflet-control {
|
||||
float: left;
|
||||
clear: both;
|
||||
}
|
||||
.leaflet-right .leaflet-control {
|
||||
float: right;
|
||||
}
|
||||
.leaflet-top .leaflet-control {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.leaflet-bottom .leaflet-control {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.leaflet-left .leaflet-control {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.leaflet-right .leaflet-control {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
|
||||
/* zoom and fade animations */
|
||||
|
||||
.leaflet-fade-anim .leaflet-tile,
|
||||
.leaflet-fade-anim .leaflet-popup {
|
||||
opacity: 0;
|
||||
-webkit-transition: opacity 0.2s linear;
|
||||
-moz-transition: opacity 0.2s linear;
|
||||
-o-transition: opacity 0.2s linear;
|
||||
transition: opacity 0.2s linear;
|
||||
}
|
||||
.leaflet-fade-anim .leaflet-tile-loaded,
|
||||
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.leaflet-zoom-anim .leaflet-zoom-animated {
|
||||
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
-o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
}
|
||||
.leaflet-zoom-anim .leaflet-tile,
|
||||
.leaflet-pan-anim .leaflet-tile,
|
||||
.leaflet-touching .leaflet-zoom-animated {
|
||||
-webkit-transition: none;
|
||||
-moz-transition: none;
|
||||
-o-transition: none;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.leaflet-zoom-anim .leaflet-zoom-hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
||||
/* cursors */
|
||||
|
||||
.leaflet-clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
.leaflet-container {
|
||||
cursor: -webkit-grab;
|
||||
cursor: -moz-grab;
|
||||
}
|
||||
.leaflet-popup-pane,
|
||||
.leaflet-control {
|
||||
cursor: auto;
|
||||
}
|
||||
.leaflet-dragging,
|
||||
.leaflet-dragging .leaflet-clickable,
|
||||
.leaflet-dragging .leaflet-container {
|
||||
cursor: move;
|
||||
cursor: -webkit-grabbing;
|
||||
cursor: -moz-grabbing;
|
||||
}
|
||||
|
||||
|
||||
/* visual tweaks */
|
||||
|
||||
.leaflet-container {
|
||||
background: #ddd;
|
||||
outline: 0;
|
||||
}
|
||||
.leaflet-container a {
|
||||
color: #0078A8;
|
||||
}
|
||||
.leaflet-container a.leaflet-active {
|
||||
outline: 2px solid orange;
|
||||
}
|
||||
.leaflet-zoom-box {
|
||||
border: 2px dotted #05f;
|
||||
background: white;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
|
||||
/* general typography */
|
||||
.leaflet-container {
|
||||
font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
|
||||
/* general toolbar styles */
|
||||
|
||||
.leaflet-bar {
|
||||
box-shadow: 0 0 8px rgba(0,0,0,0.4);
|
||||
border: 1px solid #888;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.leaflet-bar-part {
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
border-bottom: 1px solid #aaa;
|
||||
}
|
||||
.leaflet-bar-part-top {
|
||||
-webkit-border-radius: 4px 4px 0 0;
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
.leaflet-bar-part-bottom {
|
||||
-webkit-border-radius: 0 0 4px 4px;
|
||||
border-radius: 0 0 4px 4px;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-bar {
|
||||
-webkit-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.leaflet-touch .leaflet-bar-part {
|
||||
border-bottom: 4px solid rgba(0,0,0,0.3);
|
||||
}
|
||||
.leaflet-touch .leaflet-bar-part-top {
|
||||
-webkit-border-radius: 7px 7px 0 0;
|
||||
border-radius: 7px 7px 0 0;
|
||||
}
|
||||
.leaflet-touch .leaflet-bar-part-bottom {
|
||||
-webkit-border-radius: 0 0 7px 7px;
|
||||
border-radius: 0 0 7px 7px;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
|
||||
/* zoom control */
|
||||
|
||||
.leaflet-container .leaflet-control-zoom {
|
||||
margin-left: 13px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.leaflet-control-zoom a {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
.leaflet-control-zoom a,
|
||||
.leaflet-control-layers-toggle {
|
||||
background-position: 50% 50%;
|
||||
background-repeat: no-repeat;
|
||||
display: block;
|
||||
}
|
||||
.leaflet-control-zoom a:hover {
|
||||
background-color: #fff;
|
||||
color: #777;
|
||||
}
|
||||
.leaflet-control-zoom-in {
|
||||
font: bold 18px/24px Arial, Helvetica, sans-serif;
|
||||
}
|
||||
.leaflet-control-zoom-out {
|
||||
font: bold 23px/20px Tahoma, Verdana, sans-serif;
|
||||
}
|
||||
.leaflet-control-zoom a.leaflet-control-zoom-disabled {
|
||||
cursor: default;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-control-zoom a {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
.leaflet-touch .leaflet-control-zoom-in {
|
||||
font-size: 24px;
|
||||
line-height: 29px;
|
||||
}
|
||||
.leaflet-touch .leaflet-control-zoom-out {
|
||||
font-size: 28px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
/* layers control */
|
||||
|
||||
.leaflet-control-layers {
|
||||
box-shadow: 0 1px 7px rgba(0,0,0,0.4);
|
||||
background: #f8f8f9;
|
||||
-webkit-border-radius: 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.leaflet-control-layers-toggle {
|
||||
background-image: url(images/layers.png);
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
.leaflet-touch .leaflet-control-layers-toggle {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
.leaflet-control-layers .leaflet-control-layers-list,
|
||||
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
|
||||
display: none;
|
||||
}
|
||||
.leaflet-control-layers-expanded .leaflet-control-layers-list {
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
.leaflet-control-layers-expanded {
|
||||
padding: 6px 10px 6px 6px;
|
||||
color: #333;
|
||||
background: #fff;
|
||||
}
|
||||
.leaflet-control-layers-selector {
|
||||
margin-top: 2px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
.leaflet-control-layers label {
|
||||
display: block;
|
||||
}
|
||||
.leaflet-control-layers-separator {
|
||||
height: 0;
|
||||
border-top: 1px solid #ddd;
|
||||
margin: 5px -10px 5px -6px;
|
||||
}
|
||||
|
||||
|
||||
/* attribution and scale controls */
|
||||
|
||||
.leaflet-container .leaflet-control-attribution {
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
box-shadow: 0 0 5px #bbb;
|
||||
margin: 0;
|
||||
}
|
||||
.leaflet-control-attribution,
|
||||
.leaflet-control-scale-line {
|
||||
padding: 0 5px;
|
||||
color: #333;
|
||||
}
|
||||
.leaflet-container .leaflet-control-attribution,
|
||||
.leaflet-container .leaflet-control-scale {
|
||||
font-size: 11px;
|
||||
}
|
||||
.leaflet-left .leaflet-control-scale {
|
||||
margin-left: 5px;
|
||||
}
|
||||
.leaflet-bottom .leaflet-control-scale {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.leaflet-control-scale-line {
|
||||
border: 2px solid #777;
|
||||
border-top: none;
|
||||
color: black;
|
||||
line-height: 1.1;
|
||||
padding: 2px 5px 1px;
|
||||
font-size: 11px;
|
||||
text-shadow: 1px 1px 1px #fff;
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
box-shadow: 0 -1px 5px rgba(0, 0, 0, 0.2);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
.leaflet-control-scale-line:not(:first-child) {
|
||||
border-top: 2px solid #777;
|
||||
border-bottom: none;
|
||||
margin-top: -2px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
|
||||
border-bottom: 2px solid #777;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-control-attribution,
|
||||
.leaflet-touch .leaflet-control-layers,
|
||||
.leaflet-touch .leaflet-control-zoom {
|
||||
box-shadow: none;
|
||||
}
|
||||
.leaflet-touch .leaflet-control-layers,
|
||||
.leaflet-touch .leaflet-control-zoom {
|
||||
border: 4px solid rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
|
||||
/* popup */
|
||||
|
||||
.leaflet-popup {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
}
|
||||
.leaflet-popup-content-wrapper {
|
||||
padding: 1px;
|
||||
text-align: left;
|
||||
-webkit-border-radius: 20px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
.leaflet-popup-content {
|
||||
margin: 14px 20px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.leaflet-popup-content p {
|
||||
margin: 18px 0;
|
||||
}
|
||||
.leaflet-popup-tip-container {
|
||||
margin: 0 auto;
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.leaflet-popup-tip {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
padding: 1px;
|
||||
|
||||
margin: -8px auto 0;
|
||||
|
||||
-webkit-transform: rotate(45deg);
|
||||
-moz-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
-o-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
.leaflet-popup-content-wrapper, .leaflet-popup-tip {
|
||||
background: white;
|
||||
|
||||
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
|
||||
}
|
||||
.leaflet-container a.leaflet-popup-close-button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding: 4px 5px 0 0;
|
||||
text-align: center;
|
||||
width: 18px;
|
||||
height: 14px;
|
||||
font: 16px/14px Tahoma, Verdana, sans-serif;
|
||||
color: #c3c3c3;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
background: transparent;
|
||||
}
|
||||
.leaflet-container a.leaflet-popup-close-button:hover {
|
||||
color: #999;
|
||||
}
|
||||
.leaflet-popup-scrolled {
|
||||
overflow: auto;
|
||||
border-bottom: 1px solid #ddd;
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
|
||||
/* div icon */
|
||||
|
||||
.leaflet-div-icon {
|
||||
background: #fff;
|
||||
border: 1px solid #666;
|
||||
}
|
||||
.leaflet-editing-icon {
|
||||
-webkit-border-radius: 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
58
leaflet.ie.css
Normal file
@ -0,0 +1,58 @@
|
||||
.leaflet-vml-shape {
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
}
|
||||
.lvml {
|
||||
behavior: url(#default#VML);
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.leaflet-control {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.leaflet-popup-tip {
|
||||
width: 21px;
|
||||
_width: 27px;
|
||||
margin: 0 auto;
|
||||
_margin-top: -3px;
|
||||
|
||||
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
|
||||
}
|
||||
.leaflet-popup-tip-container {
|
||||
margin-top: -1px;
|
||||
}
|
||||
.leaflet-popup-content-wrapper, .leaflet-popup-tip {
|
||||
border: 1px solid #999;
|
||||
}
|
||||
.leaflet-popup-content-wrapper {
|
||||
zoom: 1;
|
||||
}
|
||||
|
||||
.leaflet-control-zoom,
|
||||
.leaflet-control-layers {
|
||||
border: 3px solid #999;
|
||||
}
|
||||
.leaflet-control-zoom a {
|
||||
background-color: #eee;
|
||||
}
|
||||
.leaflet-control-zoom a:hover {
|
||||
background-color: #fff;
|
||||
}
|
||||
.leaflet-control-layers-toggle {
|
||||
}
|
||||
.leaflet-control-attribution,
|
||||
.leaflet-control-layers,
|
||||
.leaflet-control-scale-line {
|
||||
background: white;
|
||||
}
|
||||
.leaflet-zoom-box {
|
||||
filter: alpha(opacity=50);
|
||||
}
|
||||
.leaflet-control-attribution {
|
||||
border-top: 1px solid #bbb;
|
||||
border-left: 1px solid #bbb;
|
||||
}
|
||||
|
8
leaflet.js
Normal file
116
main.css
Normal file
@ -0,0 +1,116 @@
|
||||
body, html
|
||||
{
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body h1:first-child
|
||||
{
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
h1 a, h2 a
|
||||
{
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
|
||||
table
|
||||
{
|
||||
margin: auto;
|
||||
vertical-align: middle;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
td
|
||||
{
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
td
|
||||
{
|
||||
border-bottom: 1px dotted black;
|
||||
}
|
||||
|
||||
table tr:last-child>td
|
||||
{
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
|
||||
.left
|
||||
{
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.right
|
||||
{
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
a
|
||||
{
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#position img
|
||||
{
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#ou
|
||||
{
|
||||
margin-left: 25px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 327px) /* a little trick to be more beautiful on small screens */
|
||||
{
|
||||
#ou
|
||||
{
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
#map
|
||||
{
|
||||
width: 100%;
|
||||
height: 75%;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
#map_p
|
||||
{
|
||||
margin: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#map_p img
|
||||
{
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#position h2
|
||||
{
|
||||
font-size: 1em;
|
||||
text-decoration: underline;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#position p
|
||||
{
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#thanks, #suggestions
|
||||
{
|
||||
font-size: 0.7em;
|
||||
}
|
||||
|
||||
#thanks img
|
||||
{
|
||||
height: 1em;
|
||||
}
|
48
station.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if(empty($_GET['station']) || !is_file('data/data'))
|
||||
{
|
||||
header('location: index.php');
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Vélibs à proximité</title>
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="phyks">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="stylesheet" href="main.css" type="text/css" media="screen">
|
||||
|
||||
<link rel="stylesheet" href="leaflet.css" />
|
||||
<!--[if lte IE 8]>
|
||||
<link rel="stylesheet" href="leaflet.ie.css" />
|
||||
<![endif]-->
|
||||
<script src="leaflet.js"></script>
|
||||
<script type="text/javascript" src="Polyline.encoded.js"></script>
|
||||
<script type="text/javascript" src="js.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1><a href="index.php">Vélibs à proximité</a></h1>
|
||||
|
||||
<div id="stations"></div>
|
||||
<div id="map" style="height: 500px;"></div>
|
||||
|
||||
<?php
|
||||
$param = (!empty($_GET['available'])) ? 'available' : 'free';
|
||||
echo "<p><a href='index.php?".$param."=1'>← Retour à la liste</a></p>";
|
||||
?>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div id="position">
|
||||
<p><strong>Votre navigateur doit prendre en charge la géolocalisation pour que ce site puisse fonctionner correctement.</strong></p>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
<p id="thanks">Map is handled thanks to the <a href="http://leafletjs.com/">Leaflet</a> library, using © <a href="http://osm.org/copyright">OpenStreetMap</a> contributors tiles. Reverse geolocation (Nominatim) are provided by the <a href="http://www.mapquest.com/" alt="MapQuest icon">MapQuest</a> <img src="http://developer.mapquest.com/content/osm/mq_logo.png"> open API. Routes are provided by <a href='http://project-osrm.org/'>the OSRM project</a> (OSRM is a free and open source program under GNU Affero GPL).</p>
|
||||
<p id="suggestions">N'hésitez pas à m'envoyer vos suggestions à <a href="mailto:webmaster@phyks.me">webmaster@phyks.me</a>.</p>
|
||||
</body>
|
||||
</html>
|