Browse Source

Initial commit

master
Phyks 10 years ago
parent
commit
b4b5891c0e
  1. 79
      LICENSE
  2. 127
      Polyline.encoded.js
  3. 64
      README.md
  4. 197
      ajax.php
  5. 1
      data/checksum
  6. 1
      data/data
  7. BIN
      favicon.ico
  8. BIN
      images/carte.png
  9. BIN
      images/layers.png
  10. BIN
      images/marker-icon-red.png
  11. BIN
      images/marker-icon.png
  12. BIN
      images/marker-icon@2x.png
  13. BIN
      images/marker-shadow.png
  14. BIN
      images/marker_cycle.png
  15. BIN
      images/marker_parking.png
  16. BIN
      images/marker_parking_bonus.png
  17. BIN
      images/parking.png
  18. BIN
      images/shadow_icons.png
  19. BIN
      images/velo.png
  20. 104
      index.php
  21. 372
      js.js
  22. 458
      leaflet.css
  23. 58
      leaflet.ie.css
  24. 8
      leaflet.js
  25. 116
      main.css
  26. 48
      station.php

79
LICENSE

@ -0,0 +1,79 @@ @@ -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

@ -0,0 +1,127 @@ @@ -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 @@ @@ -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

@ -0,0 +1,197 @@ @@ -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

@ -0,0 +1 @@ @@ -0,0 +1 @@
-203876984

1
data/data

File diff suppressed because one or more lines are too long

BIN
favicon.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
images/carte.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
images/layers.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 973 B

BIN
images/marker-icon-red.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
images/marker-icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
images/marker-icon@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
images/marker-shadow.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 797 B

BIN
images/marker_cycle.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
images/marker_parking.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 712 B

BIN
images/marker_parking_bonus.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

BIN
images/parking.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
images/shadow_icons.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 979 B

BIN
images/velo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

104
index.php

@ -0,0 +1,104 @@ @@ -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

@ -0,0 +1,372 @@ @@ -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: '&copy; <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

@ -0,0 +1,458 @@ @@ -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

@ -0,0 +1,58 @@ @@ -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