]> git.openstreetmap.org Git - rails.git/commitdiff
Merge branch 'master' into notes
authorTom Hughes <tom@compton.nu>
Sat, 1 Dec 2012 18:22:30 +0000 (18:22 +0000)
committerTom Hughes <tom@compton.nu>
Sat, 1 Dec 2012 18:22:30 +0000 (18:22 +0000)
Conflicts:
app/assets/javascripts/browse.js
app/assets/javascripts/map.js.erb
app/assets/stylesheets/common.css.scss
app/views/site/index.html.erb

133 files changed:
Gemfile
Gemfile.lock
Vendorfile [new file with mode: 0644]
app/assets/images/arrow.png [deleted file]
app/assets/images/map_sprite.png [moved from app/assets/openlayers/theme/openstreetmap/img/map_sprite.png with 100% similarity]
app/assets/images/marker-blue.png [new file with mode: 0644]
app/assets/images/marker-green.png [new file with mode: 0644]
app/assets/images/marker-red.png [new file with mode: 0644]
app/assets/images/marker-yellow.png [new file with mode: 0644]
app/assets/javascripts/application.js
app/assets/javascripts/browse.js
app/assets/javascripts/changeset.js
app/assets/javascripts/diary_entry.js
app/assets/javascripts/embed.js.erb [new file with mode: 0644]
app/assets/javascripts/index.js
app/assets/javascripts/index/browse.js
app/assets/javascripts/index/export.js
app/assets/javascripts/index/key.js
app/assets/javascripts/map.js.erb
app/assets/javascripts/openlayers.js.erb [deleted file]
app/assets/javascripts/osm.js.erb
app/assets/javascripts/sidebar.js
app/assets/javascripts/user.js
app/assets/openlayers/SimpleLayerSwitcher.js [deleted file]
app/assets/openlayers/SimplePanZoom.js [deleted file]
app/assets/openlayers/theme/openstreetmap/SimpleLayerSwitcher.css.scss [deleted file]
app/assets/openlayers/theme/openstreetmap/SimplePanZoom.css.scss [deleted file]
app/assets/openlayers/theme/openstreetmap/img/carat.png [deleted file]
app/assets/openlayers/theme/openstreetmap/img/missing-tile.png [deleted file]
app/assets/openlayers/theme/openstreetmap/style.css.scss [deleted file]
app/assets/stylesheets/common.css.scss
app/assets/stylesheets/embed.css.scss [new file with mode: 0644]
app/assets/stylesheets/large.css
app/assets/stylesheets/leaflet-all.css.scss [new file with mode: 0644]
app/assets/stylesheets/leaflet.pan.css.scss [new file with mode: 0644]
app/assets/stylesheets/leaflet.zoom.css.scss [new file with mode: 0644]
app/assets/stylesheets/print.css
app/assets/stylesheets/small.css.scss
app/controllers/export_controller.rb
app/views/browse/start.html.erb
app/views/export/embed.html.erb [new file with mode: 0644]
app/views/layouts/_head.html.erb
app/views/site/index.html.erb
app/views/user/_contact.html.erb
app/views/user/view.html.erb
config/environments/production.rb
config/initializers/active_record.rb [deleted file]
config/locales/af.yml
config/locales/aln.yml
config/locales/ar.yml
config/locales/arz.yml
config/locales/ast.yml
config/locales/be-Tarask.yml
config/locales/br.yml
config/locales/bs.yml
config/locales/ca.yml
config/locales/cs.yml
config/locales/da.yml
config/locales/de.yml
config/locales/diq.yml
config/locales/dsb.yml
config/locales/el.yml
config/locales/en.yml
config/locales/eo.yml
config/locales/es.yml
config/locales/et.yml
config/locales/fa.yml
config/locales/fi.yml
config/locales/fr.yml
config/locales/fur.yml
config/locales/gl.yml
config/locales/he.yml
config/locales/hr.yml
config/locales/hsb.yml
config/locales/hu.yml
config/locales/ia.yml
config/locales/id.yml
config/locales/is.yml
config/locales/it.yml
config/locales/ja.yml
config/locales/ka.yml
config/locales/ko.yml
config/locales/lb.yml
config/locales/lt.yml
config/locales/lv.yml
config/locales/mk.yml
config/locales/ms.yml
config/locales/nb.yml
config/locales/nl.yml
config/locales/nn.yml
config/locales/pl.yml
config/locales/pt-BR.yml
config/locales/pt.yml
config/locales/ro.yml
config/locales/ru.yml
config/locales/sk.yml
config/locales/sl.yml
config/locales/sq.yml
config/locales/sr-Latn.yml
config/locales/sr.yml
config/locales/sv.yml
config/locales/ta.yml
config/locales/tl.yml
config/locales/tr.yml
config/locales/uk.yml
config/locales/vi.yml
config/locales/zh-CN.yml
config/locales/zh-TW.yml
config/routes.rb
lib/potlatch2.rb
public/export/embed.html [deleted file]
test/functional/export_controller_test.rb
vendor/assets/leaflet/images/layers.png [new file with mode: 0644]
vendor/assets/leaflet/images/marker-icon.png [new file with mode: 0644]
vendor/assets/leaflet/images/marker-shadow.png [new file with mode: 0644]
vendor/assets/leaflet/images/zoom-in.png [new file with mode: 0644]
vendor/assets/leaflet/images/zoom-out.png [new file with mode: 0644]
vendor/assets/leaflet/img/filter-icon.png [new file with mode: 0644]
vendor/assets/leaflet/img/move-handle.png [new file with mode: 0644]
vendor/assets/leaflet/img/resize-handle.png [new file with mode: 0644]
vendor/assets/leaflet/leaflet.css [new file with mode: 0644]
vendor/assets/leaflet/leaflet.ie.css [new file with mode: 0644]
vendor/assets/leaflet/leaflet.js [new file with mode: 0644]
vendor/assets/leaflet/leaflet.locationfilter.css [new file with mode: 0644]
vendor/assets/leaflet/leaflet.locationfilter.js [new file with mode: 0644]
vendor/assets/leaflet/leaflet.osm.js [new file with mode: 0644]
vendor/assets/leaflet/leaflet.pan.js [new file with mode: 0644]
vendor/assets/leaflet/leaflet.zoom.js [new file with mode: 0644]
vendor/assets/potlatch2/potlatch2.swf
vendor/assets/potlatch2/potlatch2/assets.zip
vendor/assets/potlatch2/potlatch2/locales/bs.swf [new file with mode: 0644]
vendor/assets/potlatch2/potlatch2/locales/ca.swf
vendor/assets/potlatch2/potlatch2/locales/diq.swf

diff --git a/Gemfile b/Gemfile
index 4b2d2ba9e0351ce6d7e2de3ab2ad17fbc1115498..5b5a76e650d15abc1f14bed7fe936864c1102c05 100644 (file)
--- a/Gemfile
+++ b/Gemfile
@@ -23,7 +23,7 @@ gem 'rinku', '>= 1.2.2', :require => 'rails_rinku'
 gem 'oauth-plugin', '>= 0.4.1', :require => 'oauth-plugin'
 gem 'open_id_authentication', '>= 1.1.0'
 gem 'validates_email_format_of', '>= 1.5.1'
-gem 'composite_primary_keys', '>= 5.0.9'
+gem 'composite_primary_keys', '>= 5.0.10'
 gem 'http_accept_language', '>= 1.0.2'
 gem 'paperclip', '~> 2.0'
 gem 'deadlock_retry', '>= 1.2.0'
index eafd5d341a16a4ba61ad384214e5ce40c3cc6f78..8fea3da8e6f6d5b9265d9486dda3076946355a24 100644 (file)
@@ -40,8 +40,8 @@ GEM
       coffee-script-source
       execjs
     coffee-script-source (1.4.0)
-    composite_primary_keys (5.0.9)
-      activerecord (~> 3.2.0, >= 3.2.8)
+    composite_primary_keys (5.0.10)
+      activerecord (~> 3.2.0, >= 3.2.9)
     deadlock_retry (1.2.0)
     dynamic_form (1.1.4)
     ejs (1.1.1)
@@ -173,7 +173,7 @@ DEPENDENCIES
   SystemTimer (>= 1.1.3)
   bigdecimal
   coffee-rails (~> 3.2.1)
-  composite_primary_keys (>= 5.0.9)
+  composite_primary_keys (>= 5.0.10)
   deadlock_retry (>= 1.2.0)
   dynamic_form
   ejs
diff --git a/Vendorfile b/Vendorfile
new file mode 100644 (file)
index 0000000..d7779f8
--- /dev/null
@@ -0,0 +1,28 @@
+folder 'vendor/assets' do
+  folder 'leaflet' do
+    from 'https://github.com/CloudMade/Leaflet.git' do
+      file 'leaflet.css', 'dist/leaflet.css'
+      file 'leaflet.ie.css', 'dist/leaflet.ie.css'
+      file 'leaflet.js', 'dist/leaflet-src.js'
+      folder 'images', 'dist/images'
+    end
+
+    from 'https://github.com/tripbirds/leaflet-locationfilter.git' do
+      file 'leaflet.locationfilter.css', 'src/locationfilter.css'
+      file 'leaflet.locationfilter.js', 'src/locationfilter.js'
+      folder 'img', 'src/img'
+    end
+
+    from 'https://github.com/kartena/Leaflet.Pancontrol.git' do
+      file 'leaflet.pan.js', 'src/L.Control.Pan.js'
+    end
+
+    from 'https://github.com/kartena/Leaflet.zoomslider.git' do
+      file 'leaflet.zoom.js', 'src/L.Control.Zoomslider.js'
+    end
+
+    from 'https://github.com/jfirebaugh/leaflet-osm.git' do
+      file 'leaflet.osm.js', 'leaflet-osm.js'
+    end
+  end
+end
diff --git a/app/assets/images/arrow.png b/app/assets/images/arrow.png
deleted file mode 100644 (file)
index aa9b58f..0000000
Binary files a/app/assets/images/arrow.png and /dev/null differ
diff --git a/app/assets/images/marker-blue.png b/app/assets/images/marker-blue.png
new file mode 100644 (file)
index 0000000..7614b7c
Binary files /dev/null and b/app/assets/images/marker-blue.png differ
diff --git a/app/assets/images/marker-green.png b/app/assets/images/marker-green.png
new file mode 100644 (file)
index 0000000..56a0b1c
Binary files /dev/null and b/app/assets/images/marker-green.png differ
diff --git a/app/assets/images/marker-red.png b/app/assets/images/marker-red.png
new file mode 100644 (file)
index 0000000..46c7018
Binary files /dev/null and b/app/assets/images/marker-red.png differ
diff --git a/app/assets/images/marker-yellow.png b/app/assets/images/marker-yellow.png
new file mode 100644 (file)
index 0000000..eb6d6ef
Binary files /dev/null and b/app/assets/images/marker-yellow.png differ
index c52c072a754fa941a33b61b920aebe96e7b3ffe0..8d6c13503095b9d3282446ade0e19637116a9b4a 100644 (file)
@@ -4,7 +4,11 @@
 //= require jquery.timers
 //= require jquery.cookie
 //= require augment
-//= require openlayers
+//= require leaflet
+//= require leaflet.osm
+//= require leaflet.locationfilter
+//= require leaflet.pan
+//= require leaflet.zoom
 //= require i18n/translations
 //= require osm
 //= require piwik
index 906bb0baeb9f9749bbfda2eeccf2d12c0cf152b7..fb6d2d304e01383dd45da85ea188e7256370ccdd 100644 (file)
@@ -1,9 +1,9 @@
 $(document).ready(function () {
   function remoteEditHandler(bbox, select) {
-    var left = bbox.left - 0.0001;
-    var top = bbox.top + 0.0001;
-    var right = bbox.right + 0.0001;
-    var bottom = bbox.bottom - 0.0001;
+    var left = bbox.getWestLng() - 0.0001;
+    var top = bbox.getNorthLat() + 0.0001;
+    var right = bbox.getEastLng() + 0.0001;
+    var bottom = bbox.getSouthLat() - 0.0001;
     var loaded = false;
 
     $("#linkloader").load(function () { loaded = true; });
@@ -22,15 +22,17 @@ $(document).ready(function () {
   }
 
   var map = createMap("small_map", {
-    controls: [ new OpenLayers.Control.Navigation() ]
+    layerControl: false,
+    panZoomControl: false,
+    attributionControl: false
   });
 
   var params = $("#small_map").data();
   if (params.type == "changeset") {
-    var bbox = new OpenLayers.Bounds(params.minlon, params.minlat, params.maxlon, params.maxlat);
-    var centre = bbox.getCenterLonLat();
+    var bbox = L.latLngBounds([params.minlat, params.minlon],
+                              [params.maxlat, params.maxlon]);
 
-    map.zoomToExtent(proj(bbox));
+    map.fitBounds(bbox);
     addBoxToMap(bbox);
 
     $("#loading").hide();
@@ -40,6 +42,7 @@ $(document).ready(function () {
       return remoteEditHandler(bbox);
     });
 
+    var centre = bbox.getCenter();
     updatelinks(centre.lon, centre.lat, 16, null, params.minlon, params.minlat, params.maxlon, params.maxlat);
   } else if (params.type == "note") {
     var centre = new OpenLayers.LonLat(params.lon, params.lat);
@@ -72,10 +75,6 @@ $(document).ready(function () {
       $("#browse_map .geolink").show();
 
       if (extent) {
-        extent = unproj(extent);
-
-        var centre = extent.getCenterLonLat();
-
         $("a.bbox[data-editor=remote]").click(function () {
           return remoteEditHandler(extent);
         });
@@ -87,7 +86,15 @@ $(document).ready(function () {
         $("#object_larger_map").show();
         $("#object_edit").show();
 
-        updatelinks(centre.lon, centre.lat, 16, null, extent.left, extent.bottom, extent.right, extent.top, object);
+        var centre = extent.getCenter();
+        updatelinks(centre.lng,
+                    centre.lat,
+                    16, null,
+                    extent.getWestLng(),
+                    extent.getSouthLat(),
+                    extent.getEastLng(),
+                    extent.getNorthLat(),
+                    object);
       } else {
         $("#small_map").hide();
       }
index ba6e541b6b3b2ff202592b579692c5baa0fbbd3b..152b3b4f77f4636f470ff216cf397ecef660a6df 100644 (file)
@@ -1,83 +1,63 @@
 $(document).ready(function () {
-  var highlight;
+  var changesets = [], rects = {};
+  var map = createMap("changeset_list_map");
+  var group = L.featureGroup().addTo(map);
 
-  function highlightChangeset(id) {
-    var feature = vectors.getFeatureByFid(id);
-    var bounds = feature.geometry.getBounds();
-
-    if (bounds.containsBounds(map.getExtent())) {
-      bounds = map.getExtent().scale(1.1);
+  $("[data-changeset]").each(function () {
+    var changeset = $(this).data('changeset');
+    if (changeset.bbox) {
+      changeset.bounds = L.latLngBounds([changeset.bbox.minlat, changeset.bbox.minlon],
+                                        [changeset.bbox.maxlat, changeset.bbox.maxlon]);
+      changesets.push(changeset);
     }
+  });
 
-    if (highlight) vectors.removeFeatures(highlight);
-
-    highlight = new OpenLayers.Feature.Vector(bounds.toGeometry(), {}, {
-      strokeWidth: 2,
-      strokeColor: "#ee9900",
-      fillColor: "#ffff55",
-      fillOpacity: 0.5
-    });
+  changesets.sort(function (a, b) {
+    return b.bounds.getSize() - a.bounds.getSize();
+  });
 
-    vectors.addFeatures(highlight);
+  for (var i = 0; i < changesets.length; ++i) {
+    var changeset = changesets[i],
+        rect = L.rectangle(changeset.bounds,
+                           {weight: 2, color: "#ee9900", fillColor: "#ffff55", fillOpacity: 0});
+    rect.id = changeset.id;
+    rects[changeset.id] = rect;
+    rect.addTo(group);
+  }
 
+  function highlightChangeset(id) {
+    rects[id].setStyle({fillOpacity: 0.5});
     $("#tr-changeset-" + id).addClass("selected");
   }
 
   function unHighlightChangeset(id) {
-    vectors.removeFeatures(highlight);
-
+    rects[id].setStyle({fillOpacity: 0});
     $("#tr-changeset-" + id).removeClass("selected");
   }
 
-  var map = createMap("changeset_list_map", {
-    controls: [
-      new OpenLayers.Control.Navigation(),
-      new OpenLayers.Control.Zoom(),
-      new OpenLayers.Control.SimplePanZoom()
-    ]
-  });
-
-  var bounds = new OpenLayers.Bounds();
-
-  $("[data-changeset]").each(function () {
-    var changeset = $(this).data('changeset');
-    if (changeset.bbox) {
-      var bbox = new OpenLayers.Bounds(changeset.bbox.minlon, changeset.bbox.minlat, changeset.bbox.maxlon, changeset.bbox.maxlat);
-
-      bounds.extend(bbox);
-
-      addBoxToMap(bbox, changeset.id, true);
+  group.on({
+    mouseover: function (e) {
+      highlightChangeset(e.layer.id);
+    },
+    mouseout: function (e) {
+      unHighlightChangeset(e.layer.id);
     }
   });
 
-  vectors.events.on({
-    "featureselected": function(feature) {
-      highlightChangeset(feature.feature.fid);
+  $("[data-changeset]").on({
+    mouseover: function () {
+      highlightChangeset($(this).data("changeset").id);
     },
-    "featureunselected": function(feature) {
-      unHighlightChangeset(feature.feature.fid);
+    mouseout: function () {
+      unHighlightChangeset($(this).data("changeset").id);
     }
   });
 
-  var selectControl = new OpenLayers.Control.SelectFeature(vectors, {
-    multiple: false,
-    hover: true
-  });
-  map.addControl(selectControl);
-  selectControl.activate();
-
   var params = OSM.mapParams();
   if (params.bbox) {
-    map.zoomToExtent(proj(new OpenLayers.Bounds(params.minlon, params.minlat, params.maxlon, params.maxlat)));
+    map.fitBounds([[params.minlat, params.minlon],
+                   [params.maxlat, params.maxlon]]);
   } else {
-    map.zoomToExtent(proj(bounds));
+    map.fitBounds(group.getBounds());
   }
-
-  $("[data-changeset]").mouseover(function() {
-    highlightChangeset($(this).data("changeset").id);
-  });
-
-  $("[data-changeset]").mouseout(function() {
-    unHighlightChangeset($(this).data("changeset").id);
-  });
 });
index c16930480e5ff75f7d3f0ba041d302c8ffa4d44d..291c8652382a85ab4cee1414eb6ce5d962a8bfef 100644 (file)
@@ -2,18 +2,15 @@ $(document).ready(function () {
   var marker;
 
   function setLocation(e) {
-    closeMapPopup();
-
-    var lonlat = getEventPosition(e);
-
-    $("#latitude").val(lonlat.lat);
-    $("#longitude").val(lonlat.lon);
+    $("#latitude").val(e.latlng.lat);
+    $("#longitude").val(e.latlng.lng);
 
     if (marker) {
-      removeMarkerFromMap(marker);
+      map.removeLayer(marker);
     }
 
-    marker = addMarkerToMap(lonlat, null, I18n.t('diary_entry.edit.marker_text'));
+    marker = L.marker(e.latlng, {icon: getUserIcon()}).addTo(map)
+      .bindPopup(I18n.t('diary_entry.edit.marker_text'));
   }
 
   $("#usemap").click(function (e) {
@@ -23,15 +20,16 @@ $(document).ready(function () {
     $("#usemap").hide();
 
     var params = $("#map").data();
-    var centre = new OpenLayers.LonLat(params.lon, params.lat);
+    var centre = [params.lat, params.lon];
     var map = createMap("map");
 
-    setMapCenter(centre, params.zoom);
+    map.setView(centre, params.zoom);
 
     if ($("#latitude").val() && $("#longitude").val()) {
-      marker = addMarkerToMap(centre, null, I18n.t('diary_entry.edit.marker_text'));
+      marker = L.marker(centre, {icon: getUserIcon()}).addTo(map)
+        .bindPopup(I18n.t('diary_entry.edit.marker_text'));
     }
 
-    map.events.register("click", map, setLocation);
+    map.on("click", setLocation);
   });
 });
diff --git a/app/assets/javascripts/embed.js.erb b/app/assets/javascripts/embed.js.erb
new file mode 100644 (file)
index 0000000..742061f
--- /dev/null
@@ -0,0 +1,44 @@
+//= require leaflet
+//= require leaflet.osm
+
+window.onload = function () {
+  var query = (window.location.search || '?').substr(1),
+      args  = {};
+
+  query.replace(/([^&=]+)=?([^&]*)(?:&+|$)/g, function(match, key, value) {
+    value = value.split(",");
+    if (value.length == 1)
+      value = value[0];
+    args[key] = value;
+  });
+
+  var map = L.map("map");
+  map.attributionControl.setPrefix('');
+
+  if (!args.layer || args.layer == "mapnik" || args.layer == "osmarender") {
+    new L.OSM.Mapnik().addTo(map);
+  } else if (args.layer == "cyclemap" || args.layer == "cycle map") {
+    new L.OSM.CycleMap().addTo(map);
+  } else if (args.layer == "transportmap") {
+    new L.OSM.TransportMap().addTo(map);
+  } else if (args.layer == "mapquest") {
+    new L.OSM.MapQuestOpen().addTo(map);
+  }
+
+  if (args.marker) {
+    L.marker(args.marker, {icon: L.icon({
+      iconUrl: <%= asset_path('images/marker-icon.png').to_json %>,
+      iconSize: new L.Point(25, 41),
+      iconAnchor: new L.Point(12, 41),
+      shadowUrl: <%= asset_path('images/marker-shadow.png').to_json %>,
+      shadowSize: new L.Point(41, 41)
+    })}).addTo(map);
+  }
+
+  if (args.bbox) {
+    map.fitBounds([L.latLng(args.bbox[1], args.bbox[0]),
+                   L.latLng(args.bbox[3], args.bbox[2])])
+  } else {
+    map.fitWorld();
+  }  
+};
index c0decd6a6ce9a9af8e3bdc0a3eaffd8cae490af8..c90d96c3997d93219c95293c8fd192f1bb27a7cc 100644 (file)
@@ -1,27 +1,33 @@
+//= require_self
 //= require index/browse
 //= require index/export
 //= require index/key
 //= require index/notes
 
 $(document).ready(function () {
+  var permalinks = $("#permalink").html();
   var marker;
   var params = OSM.mapParams();
   var map = createMap("map");
 
-  map.events.register("moveend", map, updateLocation);
-  map.events.register("changelayer", map, updateLocation);
+  L.control.scale().addTo(map);
+
+  map.attributionControl.setPrefix(permalinks);
+
+  map.on("moveend baselayerchange", updateLocation);
 
   if (!params.object_zoom) {
     if (params.bbox) {
-      var bbox = new OpenLayers.Bounds(params.minlon, params.minlat, params.maxlon, params.maxlat);
+      var bbox = L.latLngBounds([params.minlat, params.minlon],
+                                [params.maxlat, params.maxlon]);
 
-      map.zoomToExtent(proj(bbox));
+      map.fitBounds(bbox);
 
       if (params.box) {
         addBoxToMap(bbox);
       }
     } else {
-      setMapCenter(new OpenLayers.LonLat(params.lon, params.lat), params.zoom);
+      map.setView([params.lat, params.lon], params.zoom);
     }
   }
 
@@ -30,7 +36,7 @@ $(document).ready(function () {
   }
 
   if (params.marker) {
-    marker = addMarkerToMap(new OpenLayers.LonLat(params.mlon, params.mlat));
+    marker = L.marker([params.mlat, params.mlon], {icon: getUserIcon()}).addTo(map);
   }
 
   if (params.object) {
@@ -39,46 +45,56 @@ $(document).ready(function () {
 
   handleResize();
 
-  $("body").on("click", "a.set_position", function () {
+  $("body").on("click", "a.set_position", function (e) {
+    e.preventDefault();
+
     var data = $(this).data();
-    var centre = new OpenLayers.LonLat(data.lon, data.lat);
+    var centre = L.latLng(data.lat, data.lon);
 
     if (data.minLon && data.minLat && data.maxLon && data.maxLat) {
-      var bbox = new OpenLayers.Bounds(data.minLon, data.minLat, data.maxLon, data.maxLat);
-
-      map.zoomToExtent(proj(bbox));
+      map.fitBounds([[data.minLat, data.minLon],
+                     [data.maxLat, data.maxLon]]);
     } else {
-      setMapCenter(centre, data.zoom);
+      map.setView(centre, data.zoom);
     }
 
     if (marker) {
-      removeMarkerFromMap(marker);
+      map.removeLayer(marker);
     }
 
-    marker = addMarkerToMap(centre, getArrowIcon());
-
-    return false;
+    marker = L.marker(centre, {icon: getUserIcon()}).addTo(map);
   });
 
   function updateLocation() {
-    var lonlat = unproj(map.getCenter());
+    var center = map.getCenter();
     var zoom = map.getZoom();
     var layers = getMapLayers();
-    var extents = unproj(map.getExtent());
-    var expiry = new Date();
-
-    updatelinks(lonlat.lon, lonlat.lat, zoom, layers, extents.left, extents.bottom, extents.right, extents.top, params.object);
+    var extents = map.getBounds();
+
+    updatelinks(center.lng,
+                center.lat,
+                zoom,
+                layers,
+                extents.getWestLng(),
+                extents.getSouthLat(),
+                extents.getEastLng(),
+                extents.getNorthLat(),
+                params.object);
 
+    var expiry = new Date();
     expiry.setYear(expiry.getFullYear() + 10);
-    $.cookie("_osm_location", [lonlat.lon, lonlat.lat, zoom, layers].join("|"), {expires: expiry});
+    $.cookie("_osm_location", [center.lng, center.lat, zoom, layers].join("|"), {expires: expiry});
   }
 
-  function remoteEditHandler(event) {
-    var extent = unproj(map.getExtent());
+  function remoteEditHandler() {
+    var extent = map.getBounds();
     var loaded = false;
 
     $("#linkloader").load(function () { loaded = true; });
-    $("#linkloader").attr("src", "http://127.0.0.1:8111/load_and_zoom?left=" + extent.left + "&top=" + extent.top + "&right=" + extent.right + "&bottom=" + extent.bottom);
+    $("#linkloader").attr("src", "http://127.0.0.1:8111/load_and_zoom?left=" + extent.getWestLng()
+                                                                   + "&bottom=" + extent.getSouthLat()
+                                                                   + "&right=" + extent.getEastLng()
+                                                                   + "&top=" + extent.getNorthLat());
 
     setTimeout(function () {
       if (!loaded) alert(I18n.t('site.index.remote_failed'));
@@ -93,25 +109,18 @@ $(document).ready(function () {
     remoteEditHandler();
   }
 
-  $(window).resize(function() {
-    var centre = map.getCenter();
-    var zoom = map.getZoom();
-
-    handleResize();
-
-    map.setCenter(centre, zoom);
-  });
+  $(window).resize(handleResize);
 
   $("#search_form").submit(function () {
-    var extent = unproj(map.getExtent());
+    var bounds = map.getBounds();
 
     $("#sidebar_title").html(I18n.t('site.sidebar.search_results'));
     $("#sidebar_content").load($(this).attr("action"), {
       query: $("#query").val(),
-      minlon: extent.left,
-      minlat: extent.bottom,
-      maxlon: extent.right,
-      maxlat: extent.top
+      minlon: bounds.getWestLng(),
+      minlat: bounds.getSouthLat(),
+      maxlon: bounds.getEastLng(),
+      maxlat: bounds.getNorthLat()
     }, openSidebar);
 
     return false;
index 1792c8577f5b250ed547791f8c6bc32eea18dbb1..5ca29e56822292812b2ba061f50eefd609378180 100644 (file)
 //= require templates/browse/feature_history
 
 $(document).ready(function () {
-  $("#show_data").click(function (e) {
-    $.ajax({ url: $(this).attr('href'), success: function (sidebarHtml) {
-      startBrowse(sidebarHtml);
-    }});
-    e.preventDefault();
+  var browseBounds;
+  var layersById;
+  var selectedLayer;
+  var browseObjectList;
+  var areasHidden = false;
+  var locationFilter;
+
+  var dataLayer = new L.OSM.DataLayer(null, {
+    styles: {
+      way: {
+        weight: 3,
+        color: "#000000",
+        opacity: 0.4
+      },
+      area: {
+        weight: 3,
+        color: "#ff0000"
+      },
+      node: {
+        color: "#00ff00"
+      }
+    }
   });
 
-  function startBrowse(sidebarHtml) {
-    var browseBoxControl;
-    var browseMode = "auto";
-    var browseBounds;
-    var browseFeatureList;
-    var browseActiveFeature;
-    var browseDataLayer;
-    var browseSelectControl;
-    var browseObjectList;
-    var areasHidden = false;
+  dataLayer.isWayArea = function () {
+    return !areasHidden && L.OSM.DataLayer.prototype.isWayArea.apply(this, arguments);
+  };
 
-    OpenLayers.Feature.Vector.style['default'].strokeWidth = 3;
-    OpenLayers.Feature.Vector.style['default'].cursor = "pointer";
+  dataLayer.on("click", function (e) {
+    onSelect(e.layer);
+  });
 
-    map.dataLayer.active = true;
+  if (OSM.STATUS != 'api_offline' && OSM.STATUS != 'database_offline') {
+    map.layersControl.addOverlay(dataLayer, I18n.t("browse.start_rjs.data_layer_name"));
+  }
 
-    $("#sidebar_title").html(I18n.t('browse.start_rjs.data_frame_title'));
-    $("#sidebar_content").html(sidebarHtml);
+  map.on('layeradd', function (e) {
+    if (e.layer === dataLayer) {
+      $.ajax({ url: "/browse/start", success: function (sidebarHtml) {
+        startBrowse(sidebarHtml);
+      }});
+    }
+  });
 
-    openSidebar();
+  map.on('layerremove', function (e) {
+    if (e.layer === dataLayer) {
+      closeSidebar();
+    }
+  });
 
-    var vectors = new OpenLayers.Layer.Vector();
+  function startBrowse(sidebarHtml) {
+    locationFilter = new L.LocationFilter({
+      enableButton: false,
+      adjustButton: false
+    }).addTo(map);
 
-    browseBoxControl = new OpenLayers.Control.DrawFeature(vectors, OpenLayers.Handler.RegularPolygon, {
-      handlerOptions: {
-        sides: 4,
-        snapAngle: 90,
-        irregular: true,
-        persist: true
-      }
-    });
-    browseBoxControl.handler.callbacks.done = endDrag;
-    map.addControl(browseBoxControl);
+    locationFilter.on("change", getData);
 
-    map.events.register("moveend", map, updateData);
-    map.events.triggerEvent("moveend");
+    $("#sidebar_title").html(I18n.t('browse.start_rjs.data_frame_title'));
+    $("#sidebar_content").html(sidebarHtml);
 
-    $("#browse_select_view").click(useMap);
+    openSidebar();
 
-    $("#browse_select_box").click(startDrag);
+    map.on("moveend", updateData);
+    updateData();
 
-    $("#browse_hide_areas_box").html(I18n.t('browse.start_rjs.hide_areas'));
-    $("#browse_hide_areas_box").show();
-    $("#browse_hide_areas_box").click(hideAreas);
+    $("#browse_filter_toggle").toggle(enableFilter, disableFilter);
 
-    function updateData() {
-      if (browseMode == "auto") {
-        if (map.getZoom() >= 15) {
-            useMap(false);
-        } else {
-            setStatus(I18n.t('browse.start_rjs.zoom_or_select'));
-        }
-      }
-    }
+    $("#browse_hide_areas_box").html(I18n.t('browse.start_rjs.hide_areas'));
+    $("#browse_hide_areas_box").toggle(hideAreas, showAreas);
 
     $("#sidebar").one("closed", function () {
-      if (map.dataLayer.active) {
-        map.dataLayer.active = false;
-
-        if (browseSelectControl) {
-          browseSelectControl.destroy();
-          browseSelectControl = null;
-        }
-
-        if (browseBoxControl) {
-          browseBoxControl.destroy();
-          browseBoxControl = null;
-        }
-
-        if (browseActiveFeature) {
-          browseActiveFeature.destroy();
-          browseActiveFeature = null;
-        }
+      map.removeLayer(dataLayer);
+      map.removeLayer(locationFilter);
+      map.off("moveend", updateData);
+      locationFilter.off("change", getData);
+    });
+  }
 
-        if (browseDataLayer) {
-          browseDataLayer.destroy();
-          browseDataLayer = null;
+  function updateData() {
+    if (!locationFilter.isEnabled()) {
+      if (map.getZoom() >= 15) {
+        var bounds = map.getBounds();
+        if (!browseBounds || !browseBounds.contains(bounds)) {
+          browseBounds = bounds;
+          getData();
         }
-
-        map.dataLayer.setVisibility(false);
-        map.events.unregister("moveend", map, updateData);
+      } else {
+        setStatus(I18n.t('browse.start_rjs.zoom_or_select'));
       }
-    });
-
-    function startDrag() {
-      $("#browse_select_box").html(I18n.t('browse.start_rjs.drag_a_box'));
-
-      browseBoxControl.activate();
-
-      return false;
     }
+  }
 
-    function useMap(reload) {
-      var bounds = map.getExtent();
-      var projected = unproj(bounds);
-
-      if (!browseBounds || !browseBounds.containsBounds(projected)) {
-        var center = bounds.getCenterLonLat();
-        var tileWidth = bounds.getWidth() * 1.2;
-        var tileHeight = bounds.getHeight() * 1.2;
-        var tileBounds = new OpenLayers.Bounds(center.lon - (tileWidth / 2),
-                                               center.lat - (tileHeight / 2),
-                                               center.lon + (tileWidth / 2),
-                                               center.lat + (tileHeight / 2));
+  function enableFilter() {
+    $("#browse_filter_toggle").html(I18n.t('browse.start_rjs.view_data'));
+    locationFilter.setBounds(map.getBounds().pad(-0.2));
+    locationFilter.enable();
+    getData();
+  }
 
-        browseBounds = tileBounds;
-        getData(tileBounds, reload);
+  function disableFilter() {
+    $("#browse_filter_toggle").html(I18n.t('browse.start_rjs.manually_select'));
+    locationFilter.disable();
+    getData();
+  }
 
-        browseMode = "auto";
+  function hideAreas() {
+    $("#browse_hide_areas_box").html(I18n.t('browse.start_rjs.show_areas'));
+    areasHidden = true;
+    getData();
+  }
 
-        $("#browse_select_view").hide();
-      }
+  function showAreas() {
+    $("#browse_hide_areas_box").html(I18n.t('browse.start_rjs.hide_areas'));
+    areasHidden = false;
+    getData();
+  }
 
-      return false;
-    }
+  function displayFeatureWarning(count, limit, callback) {
+    clearStatus();
 
-    function hideAreas() {
-      $("#browse_hide_areas_box").html(I18n.t('browse.start_rjs.show_areas'));
-      $("#browse_hide_areas_box").show();
-      $("#browse_hide_areas_box").click(showAreas);
+    var div = document.createElement("div");
 
-      areasHidden = true;
+    var p = document.createElement("p");
+    p.appendChild(document.createTextNode(I18n.t("browse.start_rjs.loaded_an_area_with_num_features", { num_features: count, max_features: limit })));
+    div.appendChild(p);
 
-      useMap(true);
-    }
+    var input = document.createElement("input");
+    input.type = "submit";
+    input.value = I18n.t('browse.start_rjs.load_data');
+    input.onclick = callback;
+    div.appendChild(input);
 
-    function showAreas() {
-      $("#browse_hide_areas_box").html(I18n.t('browse.start_rjs.hide_areas'));
-      $("#browse_hide_areas_box").show();
-      $("#browse_hide_areas_box").click(hideAreas);
+    $("#browse_content").html("");
+    $("#browse_content").append(div);
+  }
 
-      areasHidden = false;
+  function getData() {
+    var bounds = locationFilter.isEnabled() ? locationFilter.getBounds() : map.getBounds();
+    var size = bounds.getSize();
 
-      useMap(true);
+    if (size > OSM.MAX_REQUEST_AREA) {
+      setStatus(I18n.t("browse.start_rjs.unable_to_load_size", { max_bbox_size: OSM.MAX_REQUEST_AREA, bbox_size: size }));
+      return;
     }
 
-    function endDrag(bbox) {
-      var bounds = bbox.getBounds();
-      var projected = unproj(bounds);
+    setStatus(I18n.t('browse.start_rjs.loading'));
 
-      browseBoxControl.deactivate();
-      browseBounds = projected;
-      getData(bounds);
+    var url = "/api/" + OSM.API_VERSION + "/map?bbox=" + bounds.toBBOX();
 
-      browseMode = "manual";
+    /*
+     * Modern browsers are quite happy showing far more than 100 features in
+     * the data browser, so increase the limit to 2000 by default, but keep
+     * it restricted to 500 for IE8 and 100 for older IEs.
+     */
+    var maxFeatures = 2000;
 
-      $("#browse_select_box").html(I18n.t('browse.start_rjs.manually_select'));
-      $("#browse_select_view").show();
-    }
+    /*@cc_on
+      if (navigator.appVersion < 8) {
+        maxFeatures = 100;
+      } else if (navigator.appVersion < 9) {
+        maxFeatures = 500;
+      }
+    @*/
 
-    function displayFeatureWarning(count, limit, callback) {
-      clearStatus();
+    $.ajax({
+      url: url,
+      success: function (xml) {
+        clearStatus();
 
-      var div = document.createElement("div");
+        $("#browse_content").empty();
+        dataLayer.clearLayers();
+        selectedLayer = null;
 
-      var p = document.createElement("p");
-      p.appendChild(document.createTextNode(I18n.t("browse.start_rjs.loaded_an_area_with_num_features", { num_features: count, max_features: limit })));
-      div.appendChild(p);
+        var features = dataLayer.buildFeatures(xml);
 
-      var input = document.createElement("input");
-      input.type = "submit";
-      input.value = I18n.t('browse.start_rjs.load_data');
-      input.onclick = callback;
-      div.appendChild(input);
+        function addFeatures() {
+          dataLayer.addData(features);
 
-      $("#browse_content").html("");
-      $("#browse_content").append(div);
-    }
+          layersById = {};
 
-    function customDataLoader(resp, options) {
-      if (map.dataLayer.active) {
-        var request = resp.priv;
-        var doc = request.responseXML;
+          dataLayer.eachLayer(function (layer) {
+            var feature = layer.feature;
+            layersById[feature.id] = layer;
+            $.extend(feature, {
+              typeName: featureTypeName(feature),
+              url: "/browse/" + feature.type + "/" + feature.id,
+              name: featureName(feature)
+            });
+          });
 
-        if (!doc || !doc.documentElement) {
-          doc = request.responseText;
-        }
+          browseObjectList = $(JST["templates/browse/feature_list"]({
+            features: features,
+            url: url
+          }))[0];
 
-        resp.features = this.format.read(doc);
+          loadObjectList();
+        }
 
-        if (!this.maxFeatures || resp.features.length <= this.maxFeatures) {
-          options.callback.call(options.scope, resp);
+        if (features.length < maxFeatures) {
+          addFeatures();
         } else {
-          displayFeatureWarning(resp.features.length, this.maxFeatures, function () {
-            options.callback.call(options.scope, resp);
-          });
+          displayFeatureWarning(features.length, maxFeatures, addFeatures);
         }
       }
-    }
-
-    function getData(bounds, reload) {
-      var projected = unproj(bounds);
-      var size = projected.getWidth() * projected.getHeight();
-
-      if (size > OSM.MAX_REQUEST_AREA) {
-        setStatus(I18n.t("browse.start_rjs.unable_to_load_size", { max_bbox_size: OSM.MAX_REQUEST_AREA, bbox_size: size }));
-      } else {
-        loadData("/api/" + OSM.API_VERSION + "/map?bbox=" + projected.toBBOX(), reload);
-      }
-    }
-
-    function loadData(url, reload) {
-      setStatus(I18n.t('browse.start_rjs.loading'));
-
-      $("#browse_content").empty();
-
-      var formatOptions = {
-        checkTags: true,
-        interestingTagsExclude: ['source','source_ref','source:ref','history','attribution','created_by','tiger:county','tiger:tlid','tiger:upload_uuid']
-      };
-
-      if (areasHidden) formatOptions.areaTags = [];
-
-      if (!browseDataLayer || reload) {
-        var style = new OpenLayers.Style();
-
-        style.addRules([new OpenLayers.Rule({
-          symbolizer: {
-            Polygon: { fillColor: '#ff0000', strokeColor: '#ff0000' },
-            Line: { fillColor: '#ffff00', strokeColor: '#000000', strokeOpacity: '0.4' },
-            Point: { fillColor: '#00ff00', strokeColor: '#00ff00' }
-          }
-        })]);
-
-        if (browseDataLayer) browseDataLayer.destroyFeatures();
-
-        /*
-         * Modern browsers are quite happy showing far more than 100 features in
-         * the data browser, so increase the limit to 2000 by default, but keep
-         * it restricted to 500 for IE8 and 100 for older IEs.
-         */
-        var maxFeatures = 2000;
-
-        /*@cc_on
-          if (navigator.appVersion < 8) {
-            maxFeatures = 100;
-          } else if (navigator.appVersion < 9) {
-            maxFeatures = 500;
-          }
-        @*/
-
-        browseDataLayer = new OpenLayers.Layer.Vector("Data", {
-          strategies: [
-            new OpenLayers.Strategy.Fixed()
-          ],
-          protocol: new OpenLayers.Protocol.HTTP({
-            url: url,
-            format: new OpenLayers.Format.OSM(formatOptions),
-            maxFeatures: maxFeatures,
-            handleRead: customDataLoader
-          }),
-          projection: new OpenLayers.Projection("EPSG:4326"),
-          displayInLayerSwitcher: false,
-          styleMap: new OpenLayers.StyleMap({
-            'default': style,
-            'select': { strokeColor: '#0000ff', strokeWidth: 8 }
-          })
-        });
-        browseDataLayer.events.register("loadend", browseDataLayer, dataLoaded );
-        map.addLayer(browseDataLayer);
-
-        browseSelectControl = new OpenLayers.Control.SelectFeature(browseDataLayer, { onSelect: onFeatureSelect });
-        browseSelectControl.handlers.feature.stopDown = false;
-        browseSelectControl.handlers.feature.stopUp = false;
-        map.addControl(browseSelectControl);
-        browseSelectControl.activate();
-      } else {
-        browseDataLayer.destroyFeatures();
-        browseDataLayer.refresh({ url: url });
-      }
-
-      browseActiveFeature = null;
-    }
-
-    function dataLoaded() {
-      if (this.map.dataLayer.active) {
-        clearStatus();
+    });
+  }
 
-        var features = [];
-        for (var i = 0; i < this.features.length; i++) {
-          var feature = this.features[i];
-          features.push({
-            typeName: featureTypeName(feature),
-            url: "/browse/" + featureType(feature) + "/" + feature.osm_id,
-            name: featureName(feature),
-            id: feature.id
-          });
-        }
+  function viewFeatureLink() {
+    var layer = layersById[$(this).data("feature-id")];
 
-        browseObjectList = $(JST["templates/browse/feature_list"]({
-          features: features,
-          url: this.protocol.url
-        }))[0];
+    onSelect(layer);
 
-        loadObjectList();
-      }
+    if (locationFilter.isEnabled()) {
+      map.panTo(layer.getBounds().getCenter());
     }
 
-    function viewFeatureLink() {
-      var feature = browseDataLayer.getFeatureById($(this).data("feature-id"));
-      var layer = feature.layer;
-
-      for (var i = 0; i < layer.selectedFeatures.length; i++) {
-        var f = layer.selectedFeatures[i];
-        layer.drawFeature(f, layer.styleMap.createSymbolizer(f, "default"));
-      }
+    return false;
+  }
 
-      onFeatureSelect(feature);
+  function loadObjectList() {
+    $("#browse_content").html(browseObjectList);
+    $("#browse_content").find("a[data-feature-id]").click(viewFeatureLink);
 
-      if (browseMode != "auto") {
-        map.setCenter(feature.geometry.getBounds().getCenterLonLat());
-      }
+    return false;
+  }
 
-      return false;
+  function onSelect(layer) {
+    // Unselect previously selected feature
+    if (selectedLayer) {
+      selectedLayer.setStyle(selectedLayer.originalStyle);
     }
 
-    function loadObjectList() {
-      $("#browse_content").html(browseObjectList);
-      $("#browse_content").find("a[data-feature-id]").click(viewFeatureLink);
+    // Redraw in selected style
+    layer.originalStyle = layer.options;
+    layer.setStyle({color: '#0000ff', weight: 8});
 
-      return false;
+    // If the current object is the list, don't innerHTML="", since that could clear it.
+    if ($("#browse_content").firstChild == browseObjectList) {
+      $("#browse_content").removeChild(browseObjectList);
+    } else {
+      $("#browse_content").empty();
     }
 
-    function onFeatureSelect(feature) {
-      // Unselect previously selected feature
-      if (browseActiveFeature) {
-        browseActiveFeature.layer.drawFeature(
-          browseActiveFeature,
-          browseActiveFeature.layer.styleMap.createSymbolizer(browseActiveFeature, "default")
-        );
-      }
-
-      // Redraw in selected style
-      feature.layer.drawFeature(
-        feature, feature.layer.styleMap.createSymbolizer(feature, "select")
-      );
-
-      // If the current object is the list, don't innerHTML="", since that could clear it.
-      if ($("#browse_content").firstChild == browseObjectList) {
-        $("#browse_content").removeChild(browseObjectList);
-      } else {
-        $("#browse_content").empty();
-      }
-
-      $("#browse_content").html(JST["templates/browse/feature"]({
-        name: featureNameSelect(feature),
-        url: "/browse/" + featureType(feature) + "/" + feature.osm_id,
-        attributes: feature.attributes
-      }));
+    var feature = layer.feature;
 
-      $("#browse_content").find("a.browse_show_list").click(loadObjectList);
-      $("#browse_content").find("a.browse_show_history").click(loadHistory);
+    $("#browse_content").html(JST["templates/browse/feature"]({
+      name: featureNameSelect(feature),
+      url: "/browse/" + feature.type + "/" + feature.id,
+      attributes: feature.tags
+    }));
 
-      // Stash the currently drawn feature
-      browseActiveFeature = feature;
-    }
+    $("#browse_content").find("a.browse_show_list").click(loadObjectList);
+    $("#browse_content").find("a.browse_show_history").click(loadHistory);
 
-    function loadHistory() {
-      $(this).attr("href", "").text(I18n.t('browse.start_rjs.wait'));
+    // Stash the currently drawn feature
+    selectedLayer = layer;
+  }
 
-      var feature = browseActiveFeature;
+  function loadHistory() {
+    $(this).attr("href", "").text(I18n.t('browse.start_rjs.wait'));
 
-      $.ajax({
-        url: "/api/" + OSM.API_VERSION + "/" + featureType(feature) + "/" + feature.osm_id + "/history",
-        success: function (xml) {
-          if (browseActiveFeature != feature || $("#browse_content").firstChild == browseObjectList) {
-            return;
-          }
+    var feature = selectedLayer.feature;
 
-          $(this).remove();
+    $.ajax({
+      url: "/api/" + OSM.API_VERSION + "/" + feature.type + "/" + feature.id + "/history",
+      success: function (xml) {
+        if (selectedLayer.feature != feature || $("#browse_content").firstChild == browseObjectList) {
+          return;
+        }
 
-          var history = [];
-          var nodes = xml.getElementsByTagName(featureType(feature));
-          for (var i = nodes.length - 1; i >= 0; i--) {
-            history.push({
-              user: nodes[i].getAttribute("user") || I18n.t('browse.start_rjs.private_user'),
-              timestamp: nodes[i].getAttribute("timestamp")
-            });
-          }
+        $(this).remove();
 
-          $("#browse_content").append(JST["templates/browse/feature_history"]({
-            name: featureNameHistory(feature),
-            url: "/browse/" + featureType(feature) + "/" + feature.osm_id,
-            history: history
-          }));
-        }.bind(this)
-      });
+        var history = [];
+        var nodes = xml.getElementsByTagName(feature.type);
+        for (var i = nodes.length - 1; i >= 0; i--) {
+          history.push({
+            user: nodes[i].getAttribute("user") || I18n.t('browse.start_rjs.private_user'),
+            timestamp: nodes[i].getAttribute("timestamp")
+          });
+        }
 
-      return false;
-    }
+        $("#browse_content").append(JST["templates/browse/feature_history"]({
+          name: featureNameHistory(feature),
+          url: "/browse/" + feature.type + "/" + feature.id,
+          history: history
+        }));
+      }.bind(this)
+    });
 
-    function featureType(feature) {
-      if (feature.geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
-        return "node";
-      } else {
-        return "way";
-      }
-    }
+    return false;
+  }
 
-    function featureTypeName(feature) {
-      if (featureType(feature) == "node") {
-        return I18n.t('browse.start_rjs.object_list.type.node');
-      } else if (featureType(feature) == "way") {
-        return I18n.t('browse.start_rjs.object_list.type.way');
-      }
-    }
+  function featureTypeName(feature) {
+    return I18n.t('browse.start_rjs.object_list.type.' + feature.type);
+  }
 
-    function featureName(feature) {
-      var lang = $('html').attr('lang');
-      if (feature.attributes['name:' + lang]) {
-        return feature.attributes['name:' + lang];
-      } else if (feature.attributes.name) {
-        return feature.attributes.name;
-      } else {
-        return feature.osm_id;
-      }
-    }
+  function featureName(feature) {
+    return feature.tags['name:' + $('html').attr('lang')] ||
+      feature.tags.name ||
+      feature.id;
+  }
 
-    function featureNameSelect(feature) {
-      var lang = $('html').attr('lang');
-      if (feature.attributes['name:' + lang]) {
-        return feature.attributes['name:' + lang];
-      } else if (feature.attributes.name) {
-        return feature.attributes.name;
-      } else if (featureType(feature) == "node") {
-        return I18n.t("browse.start_rjs.object_list.selected.type.node", { id: feature.osm_id });
-      } else if (featureType(feature) == "way") {
-        return I18n.t("browse.start_rjs.object_list.selected.type.way", { id: feature.osm_id });
-      }
-    }
+  function featureNameSelect(feature) {
+    return feature.tags['name:' + $('html').attr('lang')] ||
+      feature.tags.name ||
+      I18n.t("browse.start_rjs.object_list.selected.type." + feature.type, { id: feature.id });
+  }
 
-    function featureNameHistory(feature) {
-      var lang = $('html').attr('lang');
-      if (feature.attributes['name:' + lang]) {
-        return feature.attributes['name:' + lang];
-      } else if (feature.attributes.name) {
-        return feature.attributes.name;
-      } else if (featureType(feature) == "node") {
-        return I18n.t("browse.start_rjs.object_list.history.type.node", { id: feature.osm_id });
-      } else if (featureType(feature) == "way") {
-        return I18n.t("browse.start_rjs.object_list.history.type.way", { id: feature.osm_id });
-      }
-    }
+  function featureNameHistory(feature) {
+    return feature.tags['name:' + $('html').attr('lang')] ||
+      feature.tags.name ||
+      I18n.t("browse.start_rjs.object_list.history.type." + feature.type, { id: feature.id });
+  }
 
-    function setStatus(status) {
-      $("#browse_status").html(status);
-      $("#browse_status").show();
-    }
+  function setStatus(status) {
+    $("#browse_status").html(status);
+    $("#browse_status").show();
+  }
 
-    function clearStatus() {
-      $("#browse_status").html("");
-      $("#browse_status").hide();
-    }
+  function clearStatus() {
+    $("#browse_status").html("");
+    $("#browse_status").hide();
   }
-});
\ No newline at end of file
+});
index 8696338ab403fade93cbf53eb34100bf792f6d46..31a981ec9a15b34ff87d183e9b3f91b58b6ce676 100644 (file)
@@ -11,44 +11,24 @@ $(document).ready(function () {
   }
 
   function startExport(sidebarHtml) {
-    var vectors,
-        box,
-        transform,
-        markerLayer,
-        markerControl;
-
-    vectors = new OpenLayers.Layer.Vector("Vector Layer", {
-      displayInLayerSwitcher: false
-    });
-    map.addLayer(vectors);
-
-    box = new OpenLayers.Control.DrawFeature(vectors, OpenLayers.Handler.RegularPolygon, {
-      handlerOptions: {
-        sides: 4,
-        snapAngle: 90,
-        irregular: true,
-        persist: true
-      }
-    });
-    box.handler.callbacks.done = endDrag;
-    map.addControl(box);
+    var marker;
 
-    transform = new OpenLayers.Control.TransformFeature(vectors, {
-      rotate: false,
-      irregular: true
-    });
-    transform.events.register("transformcomplete", transform, transformComplete);
-    map.addControl(transform);
+    var locationFilter = new L.LocationFilter({
+      enableButton: false,
+      adjustButton: false
+    }).addTo(map);
 
-    map.events.register("moveend", map, mapMoved);
-    map.events.register("changebaselayer", map, htmlUrlChanged);
+    locationFilter.on("change", filterChanged);
+
+    map.on("moveend", mapMoved);
+    map.on("baselayerchange", htmlUrlChanged);
 
     $("#sidebar_title").html(I18n.t('export.start_rjs.export'));
     $("#sidebar_content").html(sidebarHtml);
 
     $("#maxlat,#minlon,#maxlon,#minlat").change(boundsChanged);
 
-    $("#drag_box").click(startDrag);
+    $("#drag_box").click(enableFilter);
 
     $("#add_marker").click(startMarker);
 
@@ -58,107 +38,96 @@ $(document).ready(function () {
 
     openSidebar();
 
-    if (map.baseLayer.name == "Mapnik") {
+    if (getMapBaseLayer().keyid == "mapnik") {
       $("#format_mapnik").prop("checked", true);
     }
 
+    setBounds(map.getBounds());
     formatChanged();
-    setBounds(map.getExtent());
 
     $("body").removeClass("site-index").addClass("site-export");
 
     $("#sidebar").one("closed", function () {
       $("body").removeClass("site-export").addClass("site-index");
 
-      clearBox();
+      map.removeLayer(locationFilter);
       clearMarker();
-      map.events.unregister("moveend", map, mapMoved);
-      map.events.unregister("changebaselayer", map, htmlUrlChanged);
-      map.removeLayer(vectors);
+
+      map.off("moveend", mapMoved);
+      map.off("baselayerchange", htmlUrlChanged);
+      locationFilter.off("change", filterChanged);
     });
 
-    function getMercatorBounds() {
-      var bounds = new OpenLayers.Bounds($("#minlon").val(), $("#minlat").val(),
-                                         $("#maxlon").val(), $("#maxlat").val());
+    function getBounds() {
+      return L.latLngBounds(L.latLng($("#minlat").val(), $("#minlon").val()),
+                            L.latLng($("#maxlat").val(), $("#maxlon").val()));
+    }
 
-      return proj(bounds);
+    function getScale() {
+      var bounds = map.getBounds(),
+        centerLat = bounds.getCenter().lat,
+        halfWorldMeters = 6378137 * Math.PI * Math.cos(centerLat * Math.PI / 180),
+        meters = halfWorldMeters * (bounds.getNorthEast().lng - bounds.getSouthWest().lng) / 180,
+        pixelsPerMeter = map.getSize().x / meters,
+        metersPerPixel = 1 / (92 * 39.3701);
+      return Math.round(1 / (pixelsPerMeter * metersPerPixel));
     }
 
-    function boundsChanged() {
-      var bounds = getMercatorBounds();
+    function getMercatorBounds() {
+      var bounds = getBounds();
+      return L.bounds(L.CRS.EPSG3857.project(bounds.getSouthWest()),
+                      L.CRS.EPSG3857.project(bounds.getNorthEast()));
+    }
 
-      map.events.unregister("moveend", map, mapMoved);
-      map.zoomToExtent(bounds);
+    function boundsChanged() {
+      var bounds = getBounds();
 
-      clearBox();
-      drawBox(bounds);
+      map.fitBounds(bounds);
+      locationFilter.setBounds(bounds);
 
+      enableFilter();
       validateControls();
       mapnikSizeChanged();
     }
 
-    function startDrag() {
-      $("#drag_box").html(I18n.t('export.start_rjs.drag_a_box'));
-
-      clearBox();
-      box.activate();
-    };
-
-    function endDrag(bbox) {
-      var bounds = bbox.getBounds();
-
-      map.events.unregister("moveend", map, mapMoved);
-      setBounds(bounds);
-      drawBox(bounds);
-      box.deactivate();
-      validateControls();
+    function enableFilter() {
+      if (!locationFilter.getBounds().isValid()) {
+        locationFilter.setBounds(map.getBounds().pad(-0.2));
+      }
 
-      $("#drag_box").html(I18n.t('export.start_rjs.manually_select'));
+      $("#drag_box").hide();
+      locationFilter.enable();
     }
 
-    function transformComplete(event) {
-      setBounds(event.feature.geometry.bounds);
+    function filterChanged() {
+      setBounds(locationFilter.getBounds());
       validateControls();
     }
 
     function startMarker() {
       $("#add_marker").html(I18n.t('export.start_rjs.click_add_marker'));
 
-      if (!markerLayer) {
-        markerLayer = new OpenLayers.Layer.Vector("",{
-          displayInLayerSwitcher: false,
-          style: {
-            externalGraphic: OpenLayers.Util.getImageLocation("marker.png"),
-            graphicXOffset: -10.5,
-            graphicYOffset: -25,
-            graphicWidth: 21,
-            graphicHeight: 25
-          }
-        });
-        map.addLayer(markerLayer);
-
-        markerControl = new OpenLayers.Control.DrawFeature(markerLayer, OpenLayers.Handler.Point);
-        map.addControl(markerControl);
-
-        markerLayer.events.on({ "featureadded": endMarker });
-      }
-
-      markerLayer.destroyFeatures();
-      markerControl.activate();
+      map.on("click", endMarker);
 
       return false;
     }
 
     function endMarker(event) {
-      markerControl.deactivate();
+      map.off("click", endMarker);
 
       $("#add_marker").html(I18n.t('export.start_rjs.change_marker'));
       $("#marker_inputs").show();
 
-      var geom = unproj(event.feature.geometry);
+      var latlng = event.latlng;
 
-      $("#marker_lon").val(geom.x.toFixed(5));
-      $("#marker_lat").val(geom.y.toFixed(5));
+      if (marker) {
+        map.removeLayer(marker);
+      }
+
+      marker = L.marker(latlng).addTo(map);
+
+      $("#marker_lon").val(latlng.lng.toFixed(5));
+      $("#marker_lat").val(latlng.lat.toFixed(5));
 
       htmlUrlChanged();
     }
@@ -168,49 +137,35 @@ $(document).ready(function () {
       $("#marker_inputs").hide();
       $("#add_marker").html(I18n.t('export.start_rjs.add_marker'));
 
-      if (markerLayer) {
-        markerControl.destroy();
-        markerLayer.destroy();
-        markerLayer = null;
-        markerControl = null;
+      if (marker) {
+        map.removeLayer(marker);
       }
     }
 
     function mapMoved() {
-      setBounds(map.getExtent());
-      validateControls();
+      if (!locationFilter.isEnabled()) {
+        setBounds(map.getBounds());
+        validateControls();
+      }
     }
 
     function setBounds(bounds) {
       var toPrecision = zoomPrecision(map.getZoom());
 
-      bounds = unproj(bounds);
-
-      $("#minlon").val(toPrecision(bounds.left));
-      $("#minlat").val(toPrecision(bounds.bottom));
-      $("#maxlon").val(toPrecision(bounds.right));
-      $("#maxlat").val(toPrecision(bounds.top));
+      $("#minlon").val(toPrecision(bounds.getWestLng()));
+      $("#minlat").val(toPrecision(bounds.getSouthLat()));
+      $("#maxlon").val(toPrecision(bounds.getEastLng()));
+      $("#maxlat").val(toPrecision(bounds.getNorthLat()));
 
       mapnikSizeChanged();
       htmlUrlChanged();
     }
 
-    function clearBox() {
-      transform.deactivate();
-      vectors.destroyFeatures();
-    }
-
-    function drawBox(bounds) {
-      var feature = new OpenLayers.Feature.Vector(bounds.toGeometry());
-
-      vectors.addFeatures(feature);
-      transform.setFeature(feature);
-    }
-
     function validateControls() {
-      var bounds = new OpenLayers.Bounds($("#minlon").val(), $("#minlat").val(), $("#maxlon").val(), $("#maxlat").val());
+      var bounds = getBounds();
 
-      if (bounds.getWidth() * bounds.getHeight() > OSM.MAX_REQUEST_AREA) {
+      var tooLarge = bounds.getSize() > OSM.MAX_REQUEST_AREA;
+      if (tooLarge) {
         $("#export_osm_too_large").show();
       } else {
         $("#export_osm_too_large").hide();
@@ -220,7 +175,7 @@ $(document).ready(function () {
       var disabled = true;
 
       if ($("#format_osm").prop("checked")) {
-        disabled = bounds.getWidth() * bounds.getHeight() > OSM.MAX_REQUEST_AREA;
+        disabled = tooLarge;
       } else if ($("#format_mapnik").prop("checked")) {
         disabled = $("#mapnik_scale").val() < max_scale;
       }
@@ -230,8 +185,9 @@ $(document).ready(function () {
     }
 
     function htmlUrlChanged() {
-      var bounds = new OpenLayers.Bounds($("#minlon").val(), $("#minlat").val(), $("#maxlon").val(), $("#maxlat").val());
-      var layerName = map.baseLayer.keyid;
+      var bounds = getBounds();
+      var layerName = getMapBaseLayer().keyid;
+
       var url = "http://" + OSM.SERVER_URL + "/export/embed.html?bbox=" + bounds.toBBOX() + "&amp;layer=" + layerName;
       var markerUrl = "";
 
@@ -243,9 +199,9 @@ $(document).ready(function () {
       var html = '<iframe width="425" height="350" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" src="'+url+'" style="border: 1px solid black"></iframe>';
 
       // Create "larger map" link
-      var center = bounds.getCenterLonLat();
+      var center = bounds.getCenter();
 
-      var zoom = map.getZoomForExtent(proj(bounds));
+      var zoom = map.getBoundsZoom(bounds);
 
       var layers = getMapLayers();
 
@@ -257,7 +213,7 @@ $(document).ready(function () {
         escaped.push(c < 127 ? text.charAt(i) : "&#" + c + ";");
       }
 
-      html += '<br /><small><a href="http://' + OSM.SERVER_URL + '/?lat='+center.lat+'&amp;lon='+center.lon+'&amp;zoom='+zoom+'&amp;layers='+layers+markerUrl+'">'+escaped.join("")+'</a></small>';
+      html += '<br /><small><a href="http://' + OSM.SERVER_URL + '/?lat='+center.lat+'&amp;lon='+center.lng+'&amp;zoom='+zoom+'&amp;layers='+layers+markerUrl+'">'+escaped.join("")+'</a></small>';
 
       $("#export_html_text").val(html);
 
@@ -276,7 +232,7 @@ $(document).ready(function () {
       }
 
       if ($("#format_mapnik").prop("checked")) {
-        $("#mapnik_scale").val(roundScale(map.getScale()));
+        $("#mapnik_scale").val(getScale());
         $("#export_mapnik").show();
 
         mapnikSizeChanged();
@@ -306,8 +262,8 @@ $(document).ready(function () {
     function mapnikImageSize(scale) {
       var bounds = getMercatorBounds();
 
-      return new OpenLayers.Size(Math.round(bounds.getWidth() / scale / 0.00028),
-                                 Math.round(bounds.getHeight() / scale / 0.00028));
+      return {w: Math.round(bounds.getWidth() / scale / 0.00028),
+              h: Math.round(bounds.getHeight() / scale / 0.00028)};
     }
 
     function roundScale(scale) {
index 1e4138b70fc7b449b4ba3cae54f5cf8c9b4a93df..9722cf4cf0511f684c95ff7b651a4d06393cf9dd 100644 (file)
@@ -1,10 +1,12 @@
 $(document).ready(function () {
   $("#open_map_key").click(function (e) {
+    e.preventDefault();
+
     var url = $(this).attr('href'),
         title = $(this).text();
 
     function updateMapKey() {
-      var mapLayer = map.baseLayer.keyid,
+      var mapLayer = getMapBaseLayer().keyid,
           mapZoom = map.getZoom();
 
       $(".mapkey-table-entry").each(function () {
@@ -24,13 +26,9 @@ $(document).ready(function () {
     openSidebar({ title: title });
 
     $("#sidebar").one("closed", function () {
-      map.events.unregister("zoomend", map, updateMapKey);
-      map.events.unregister("changelayer", map, updateMapKey);
+      map.off("zoomend baselayerchange", updateMapKey);
     });
 
-    map.events.register("zoomend", map, updateMapKey);
-    map.events.register("changelayer", map, updateMapKey);
-
-    e.preventDefault();
+    map.on("zoomend baselayerchange", updateMapKey);
   });
 });
index 4c1a849dc25d5f4a4572ac74e649515a378eb0e1..f069eee993e76d90a6842e48cabf3dd70534f521 100644 (file)
-var epsg4326 = new OpenLayers.Projection("EPSG:4326");
+// Leaflet extensions
+L.LatLngBounds.include({
+  getSouthLat: function () {
+    return this._southWest.lat;
+  },
+
+  getWestLng: function () {
+    return this._southWest.lng;
+  },
+
+  getNorthLat: function () {
+    return this._northEast.lat;
+  },
+
+  getEastLng: function () {
+    return this._northEast.lng;
+  },
+
+  toBBOX: function () {
+    var decimal = 6;
+    var mult = Math.pow(10, decimal);
+    var xmin = Math.round(this.getWestLng() * mult) / mult;
+    var ymin = Math.round(this.getSouthLat() * mult) / mult;
+    var xmax = Math.round(this.getEastLng() * mult) / mult;
+    var ymax = Math.round(this.getNorthLat() * mult) / mult;
+    return xmin + "," + ymin + "," + xmax + "," + ymax;
+  },
+
+  getSize: function () {
+    return (this._northEast.lat - this._southWest.lat) *
+           (this._northEast.lng - this._southWest.lng);
+  }
+});
+
+L.Bounds.include({
+  getWidth: function () {
+   return this.max.x - this.min.x;
+  },
+
+  getHeight: function () {
+   return this.max.y - this.min.y;
+  }
+});
+
+L.Icon.Default.imagePath = <%= "#{asset_prefix}/images".to_json %>;
+
 var map;
-var markers;
-var vectors;
-var popup;
+
+var layers = [
+  {
+    klass: L.OSM.Mapnik,
+    attribution: "",
+    keyid: "mapnik",
+    layerCode: "M",
+    name: I18n.t("javascripts.map.base.standard")
+  },
+  {
+    klass: L.OSM.CycleMap,
+    attribution: "Tiles courtesy of <a href='http://www.opencyclemap.org/' target='_blank'>Andy Allan</a>",
+    keyid: "cyclemap",
+    layerCode: "C",
+    name: I18n.t("javascripts.map.base.cycle_map")
+  },
+  {
+    klass: L.OSM.TransportMap,
+    attribution: "Tiles courtesy of <a href='http://www.opencyclemap.org/' target='_blank'>Andy Allan</a>",
+    keyid: "transportmap",
+    layerCode: "T",
+    name: I18n.t("javascripts.map.base.transport_map")
+  },
+  {
+    klass: L.OSM.MapQuestOpen,
+    attribution: "Tiles courtesy of <a href='http://www.mapquest.com/' target='_blank'>MapQuest</a> <img src='http://developer.mapquest.com/content/osm/mq_logo.png'>",
+    keyid: "mapquest",
+    layerCode: "Q",
+    name: I18n.t("javascripts.map.base.mapquest")
+  }
+];
 
 function createMap(divName, options) {
-   options = options || {};
-
-   map = new OpenLayers.Map(divName, {
-      controls: options.controls || [
-         new OpenLayers.Control.ArgParser(),
-         new OpenLayers.Control.Attribution(),
-         new SimpleLayerSwitcher(),
-         new OpenLayers.Control.Navigation(),
-         new OpenLayers.Control.Zoom(),
-         new OpenLayers.Control.SimplePanZoom(),
-         new OpenLayers.Control.ScaleLine({geodesic: true})
-      ],
-      numZoomLevels: 20,
-      displayProjection: new OpenLayers.Projection("EPSG:4326"),
-      theme: "<%= asset_path 'theme/openstreetmap/style.css' %>"
-   });
-
-   var mapnik = new OpenLayers.Layer.OSM.Mapnik(I18n.t("javascripts.map.base.standard"), {
-      attribution: "",
-      keyid: "mapnik",
-      displayOutsideMaxExtent: true,
-      wrapDateLine: true,
-      layerCode: "M"
-   });
-   map.addLayer(mapnik);
-
-   var cyclemap = new OpenLayers.Layer.OSM.CycleMap(I18n.t("javascripts.map.base.cycle_map"), {
-      attribution: "Tiles courtesy of <a href='http://www.opencyclemap.org/' target='_blank'>Andy Allan</a>",
-      keyid: "cyclemap",
-      displayOutsideMaxExtent: true,
-      wrapDateLine: true,
-      layerCode: "C"
-   });
-   map.addLayer(cyclemap);
-
-   var transportmap = new OpenLayers.Layer.OSM.TransportMap(I18n.t("javascripts.map.base.transport_map"), {
-      attribution: "Tiles courtesy of <a href='http://www.opencyclemap.org/' target='_blank'>Andy Allan</a>",
-      keyid: "transportmap",
-      displayOutsideMaxExtent: true,
-      wrapDateLine: true,
-      layerCode: "T"
-   });
-   map.addLayer(transportmap);
-
-   var mapquest = new OpenLayers.Layer.OSM(I18n.t("javascripts.map.base.mapquest"), [
-      "http://otile1.mqcdn.com/tiles/1.0.0/osm/${z}/${x}/${y}.png",
-      "http://otile2.mqcdn.com/tiles/1.0.0/osm/${z}/${x}/${y}.png",
-      "http://otile3.mqcdn.com/tiles/1.0.0/osm/${z}/${x}/${y}.png",
-      "http://otile4.mqcdn.com/tiles/1.0.0/osm/${z}/${x}/${y}.png"
-   ], {
-      attribution: "Tiles courtesy of <a href='http://www.mapquest.com/' target='_blank'>MapQuest</a> <img src='http://developer.mapquest.com/content/osm/mq_logo.png'>",
-      keyid: "mapquest",
-      displayOutsideMaxExtent: true,
-      wrapDateLine: true,
-      numZoomLevels: 19,
-      layerCode: "Q"
-   });
-   map.addLayer(mapquest);
-
-   markers = new OpenLayers.Layer.Markers("Markers", {
-      displayInLayerSwitcher: false,
-      numZoomLevels: 20,
-      projection: "EPSG:900913"
-   });
-   map.addLayer(markers);
-
-   map.dataLayer = new OpenLayers.Layer(I18n.t('browse.start_rjs.data_layer_name'), {
-     visibility: false,
-     displayInLayerSwitcher: false
-   });
-   map.addLayer(map.dataLayer);
-
-   $("#" + divName).on("resized", function () {
-     map.updateSize();
-   });
-
-   $("#" + divName).trigger("initialised");
-
-   return map;
-}
+  options = $.extend({zoomControl: true, panZoomControl: true, layerControl: true}, options);
 
-function getArrowIcon() {
-   var size = new OpenLayers.Size(25, 22);
-   var offset = new OpenLayers.Pixel(-22, -20);
-   var icon = new OpenLayers.Icon("<%= asset_path 'arrow.png' %>", size, offset);
+  map = L.map(divName, $.extend({}, options, {panControl: false, zoomsliderControl: false, maxZoom: 18}));
 
-   return icon;
-}
+  if (map.attributionControl) {
+    map.attributionControl.setPrefix('');
+  }
+
+  if (options.panZoomControl) {
+    new L.Control.Pan().addTo(map);
+    new L.Control.Zoomslider({stepHeight: 7}).addTo(map);
+  }
+
+  var layersControl = L.control.layers();
+
+  if (options.layerControl) {
+    layersControl.addTo(map);
+    map.layersControl = layersControl;
+  }
+
+  for (var i = 0; i < layers.length; i++) {
+    layers[i].layer = new (layers[i].klass)(layers[i]);
+    layersControl.addBaseLayer(layers[i].layer, layers[i].name);
+  }
 
-function addMarkerToMap(position, icon, description) {
-   var marker = new OpenLayers.Marker(proj(position), icon);
+  layers[0].layer.addTo(map);
 
-   markers.addMarker(marker);
+  $("#" + divName).on("resized", function () {
+    map.invalidateSize();
+  });
 
-   if (description) {
-       marker.events.register("mouseover", marker, function() {
-           openMapPopup(marker, description);
-       });
-   }
+ $("#" + divName).trigger("initialised");
 
-   return marker;
+  return map;
+}
+
+function getUserIcon(url) {
+  return L.icon({
+    iconUrl: url || <%= asset_path('marker-red.png').to_json %>,
+    iconSize: [25, 41],
+    iconAnchor: [12, 41],
+    popupAnchor: [1, -34],
+    shadowUrl: <%= asset_path('images/marker-shadow.png').to_json %>,
+    shadowSize: [41, 41]
+  });
 }
 
 function addObjectToMap(object, zoom, callback) {
-   var layer = new OpenLayers.Layer.Vector("Objects", {
-      strategies: [ 
-          new OpenLayers.Strategy.Fixed()
-      ],
-      protocol: new OpenLayers.Protocol.HTTP({
-          url: OSM.apiUrl(object),
-          format: new OpenLayers.Format.OSM()
-      }),
-      style: {
+  $.ajax({
+    url: OSM.apiUrl(object),
+    dataType: "xml",
+    success: function (xml) {
+      var layer = new L.OSM.DataLayer(xml, {
+        style: {
           strokeColor: "blue",
           strokeWidth: 3,
           strokeOpacity: 0.5,
           fillOpacity: 0.2,
           fillColor: "lightblue",
           pointRadius: 6
-      },
-      projection: new OpenLayers.Projection("EPSG:4326"),
-      displayInLayerSwitcher: false
-   });
-
-   layer.events.register("loadend", layer, function() {
-      var extent;
-
-      if (this.features.length) {
-         extent = this.features[0].geometry.getBounds();
-
-         for (var i = 1; i < this.features.length; i++) {
-            extent.extend(this.features[i].geometry.getBounds());
-         }
-
-         if (zoom) {
-            if (extent) {
-               this.map.zoomToExtent(extent);
-            } else {
-               this.map.zoomToMaxExtent();
-            }
-         }
+        }
+      });
+
+      var bounds = layer.getBounds();
+
+      if (zoom) {
+        map.fitBounds(bounds);
       }
 
       if (callback) {
-         callback(extent);
+        callback(bounds);
       }
-   });
-
-   map.addLayer(layer);
-}
-
-function addBoxToMap(boxbounds, id, outline) {
-   if (!vectors) {
-     // Be aware that IE requires Vector layers be initialised on page load, and not under deferred script conditions
-     vectors = new OpenLayers.Layer.Vector("Boxes", {
-        displayInLayerSwitcher: false
-     });
-     map.addLayer(vectors);
-   }
-   var geometry;
-   if (outline) {
-     vertices = boxbounds.toGeometry().getVertices();
-     vertices.push(new OpenLayers.Geometry.Point(vertices[0].x, vertices[0].y));
-     geometry = proj(new OpenLayers.Geometry.LineString(vertices));
-   } else {
-     geometry = proj(boxbounds.toGeometry());
-   }
-   var box = new OpenLayers.Feature.Vector(geometry, {}, {
-      strokeWidth: 2,
-      strokeColor: '#ee9900',
-      fillOpacity: 0
-   });
-   box.fid = id;
-
-   vectors.addFeatures(box);
-
-   return box;
-}
-
-function openMapPopup(marker, description) {
-   closeMapPopup();
-
-   popup = new OpenLayers.Popup.FramedCloud("popup", marker.lonlat, null,
-                                            description, marker.icon, true);
-   popup.setBackgroundColor("#E3FFC5");
-   map.addPopup(popup);
-
-   return popup;
-}
 
-function closeMapPopup() {
-   if (popup) {
-      map.removePopup(popup);
-   }
+      layer.addTo(map);
+    }
+  });
 }
 
-function removeMarkerFromMap(marker){
-   markers.removeMarker(marker);
-}
+function addBoxToMap(bounds) {
+  var box = L.rectangle(bounds, {
+    weight: 2,
+    color: '#e90',
+    fillOpacity: 0
+  });
 
-function proj(x) {
-    return x.clone().transform(epsg4326, map.getProjectionObject());
-}
+  box.addTo(map);
 
-function unproj(x) {
-    return x.clone().transform(map.getProjectionObject(), epsg4326);
+  return box;
 }
 
-function setMapCenter(center, zoom) {
-   zoom = parseInt(zoom, 10);
-   var numzoom = map.getNumZoomLevels();
-   if (zoom >= numzoom) zoom = numzoom - 1;
-   map.setCenter(proj(center), zoom);
-}
-
-function getEventPosition(event) {
-   return unproj(map.getLonLatFromViewPortPx(event.xy));
+function getMapBaseLayer() {
+  for (var i = 0; i < layers.length; i++) {
+    if (map.hasLayer(layers[i].layer)) {
+      return layers[i];
+    }
+  }
 }
 
 function getMapLayers() {
-   var layerConfig = "";
-
-   for (var i = 0; i < map.layers.length; i++) {
-      if (map.layers[i].layerCode && map.layers[i].getVisibility()) {
-         layerConfig += map.layers[i].layerCode;
-      }
-   }
+  for (var i = 0; i < layers.length; i++) {
+    if (map.hasLayer(layers[i].layer)) {
+      return layers[i].layerCode;
+    }
+  }
 
-   return layerConfig;
+  return "";
 }
 
 function setMapLayers(layerConfig) {
-   if (layerConfig.charAt(0) == "B" || layerConfig.charAt(0) == "0") {
-      var l = 0;
-
-      for (var layers = map.getLayersBy("isBaseLayer", true), i = 0; i < layers.length; i++) {
-         var c = layerConfig.charAt(l++);
-
-         if (c == "B") {
-            map.setBaseLayer(layers[i]);
-         } else {
-            map.layers[i].setVisibility(false);
-         }
-      }
-   } else {
-      for (var i = 0; i < map.layers.length; i++) {
-         if (map.layers[i].layerCode) {
-            if (layerConfig.indexOf(map.layers[i].layerCode) >= 0) {
-               if (map.layers[i].isBaseLayer) {
-                  map.setBaseLayer(map.layers[i]);
-               } else {
-                  map.layers[i].setVisibility(true);
-               }
-            } else if (!map.layers[i].isBaseLayer) {
-               map.layers[i].setVisibility(false);
-            }
-         }
-      }
-   }
+  var foundLayer = false;
+  for (var i = 0; i < layers.length; i++) {
+    if (layerConfig.indexOf(layers[i].layerCode) >= 0) {
+      map.addLayer(layers[i].layer);
+      foundLayer = true;
+    } else {
+      map.removeLayer(layers[i].layer);
+    }
+  }
+  if (!foundLayer) {
+    map.addLayer(layers[0].layer);
+  }
 }
diff --git a/app/assets/javascripts/openlayers.js.erb b/app/assets/javascripts/openlayers.js.erb
deleted file mode 100644 (file)
index 66d991f..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-//= require OpenLayers
-//= require OpenStreetMap
-//= require SimpleLayerSwitcher
-//= require SimplePanZoom
-
-OpenLayers.Util.imageURLs = {
-    "404.png": "<%= asset_path 'img/404.png' %>",
-    "blank.gif": "<%= asset_path 'img/blank.gif' %>",
-    "cloud-popup-relative.png": "<%= asset_path 'img/cloud-popup-relative.png' %>",
-    "drag-rectangle-off.png": "<%= asset_path 'img/drag-rectangle-off.png' %>",
-    "drag-rectangle-on.png": "<%= asset_path 'img/drag-rectangle-on.png' %>",
-    "east-mini.png": "<%= asset_path 'img/east-mini.png' %>",
-    "layer-switcher-maximize.png": "<%= asset_path 'img/layer-switcher-maximize.png' %>",
-    "layer-switcher-minimize.png": "<%= asset_path 'img/layer-switcher-minimize.png' %>",
-    "marker-blue.png": "<%= asset_path 'img/marker-blue.png' %>",
-    "marker-gold.png": "<%= asset_path 'img/marker-gold.png' %>",
-    "marker-green.png": "<%= asset_path 'img/marker-green.png' %>",
-    "marker.png": "<%= asset_path 'img/marker.png' %>",
-    "measuring-stick-off.png": "<%= asset_path 'img/measuring-stick-off.png' %>",
-    "measuring-stick-on.png": "<%= asset_path 'img/measuring-stick-on.png' %>",
-    "north-mini.png": "<%= asset_path 'img/north-mini.png' %>",
-    "panning-hand-off.png": "<%= asset_path 'img/panning-hand-off.png' %>",
-    "panning-hand-on.png": "<%= asset_path 'img/panning-hand-on.png' %>",
-    "slider.png": "<%= asset_path 'img/slider.png' %>",
-    "south-mini.png": "<%= asset_path 'img/south-mini.png' %>",
-    "west-mini.png": "<%= asset_path 'img/west-mini.png' %>",
-    "zoombar.png": "<%= asset_path 'img/zoombar.png' %>",
-    "zoom-minus-mini.png": "<%= asset_path 'img/zoom-minus-mini.png' %>",
-    "zoom-plus-mini.png": "<%= asset_path 'img/zoom-plus-mini.png' %>",
-    "zoom-world-mini.png": "<%= asset_path 'img/zoom-world-mini.png' %>"
-};
-
-OpenLayers.Util.origGetImageLocation = OpenLayers.Util.getImageLocation;
-
-OpenLayers.Util.getImageLocation = function(image) {
-    return OpenLayers.Util.imageURLs[image] || OpenLayers.Util.origGetImageLocation(image);
-};
-
-OpenLayers.Lang.setCode($('html').attr('lang'));
index e8428da3a13c53823d14637385f2386f3985141a..7a8f3da3e96407ac952197053efbe3fca1d59940 100644 (file)
@@ -7,6 +7,7 @@ OSM = {
   MAX_REQUEST_AREA: <%= MAX_REQUEST_AREA.to_json %>,
   SERVER_URL:       <%= SERVER_URL.to_json %>,
   API_VERSION:      <%= API_VERSION.to_json %>,
+  STATUS:           <%= STATUS.to_json %>,
 
   apiUrl: function (object) {
     var url = "/api/" + OSM.API_VERSION + "/" + object.type + "/" + object.id;
index a5d90698010c20e3c81402993ad9eba4df55c2ba..912197b9f2c380cfcc4732cf7033901d5b8987bf 100644 (file)
@@ -5,20 +5,17 @@ function openSidebar(options) {
 
   if (options.title) { $("#sidebar_title").html(options.title); }
 
-  if (options.width) { $("#sidebar").width(options.width); }
-  else { $("#sidebar").width("30%"); }
+  $("#sidebar").width(options.width || "30%");
+  $("#sidebar").css("display", "block").trigger("opened");
+}
 
-  $("#sidebar").css("display", "block");
-
-  $("#sidebar").trigger("opened");
-};
+function closeSidebar() {
+  $("#sidebar").css("display", "none").trigger("closed");
+}
 
 $(document).ready(function () {
   $(".sidebar_close").click(function (e) {
-    $("#sidebar").css("display", "none");
-
-    $("#sidebar").trigger("closed");
-
+    closeSidebar();
     e.preventDefault();
   });
 });
index 77cb86a8d210b670e9a9c2ba1a350f678001d936..25456d04ebb080b46564fb359b5801fac551c163 100644 (file)
@@ -1,41 +1,39 @@
 $(document).ready(function () {
-  var map = createMap("map");
+  var map = createMap("map", {
+    zoomControl: true,
+    panZoomControl: false
+  });
 
   if (OSM.home) {
-    setMapCenter(new OpenLayers.LonLat(OSM.home.lon, OSM.home.lat), 12);
+    map.setView([OSM.home.lat, OSM.home.lon], 12);
   } else {
-    setMapCenter(new OpenLayers.LonLat(0, 0), 0);
+    map.setView([0, 0], 0);
   }
 
   if ($("#map").hasClass("set_location")) {
-    var marker;
+    var marker = L.marker([0, 0], {icon: getUserIcon()});
 
     if (OSM.home) {
-      marker = addMarkerToMap(new OpenLayers.LonLat(OSM.home.lon, OSM.home.lat));
+      marker.setLatLng([OSM.home.lat, OSM.home.lon]);
+      marker.addTo(map);
     }
 
-    map.events.register("click", map, function (e) {
+    map.on("click", function (e) {
       if ($('#updatehome').is(':checked')) {
-        var lonlat = getEventPosition(e);
-
         $('#homerow').removeClass();
-        $('#home_lat').val(lonlat.lat);
-        $('#home_lon').val(lonlat.lon);
-
-        if (marker) {
-          removeMarkerFromMap(marker);
-        }
+        $('#home_lat').val(e.latlng.lat);
+        $('#home_lon').val(e.latlng.lng);
 
-        marker = addMarkerToMap(lonlat);
+        marker.setLatLng(e.latlng);
+        marker.addTo(map);
       }
     });
   } else {
     $("[data-user]").each(function () {
       var user = $(this).data('user');
       if (user.lon && user.lat) {
-        var icon = OpenLayers.Marker.defaultIcon();
-        icon.url = OpenLayers.Util.getImageLocation(user.icon);
-        addMarkerToMap(new OpenLayers.LonLat(user.lon, user.lat), icon, user.description);
+        L.marker([user.lat, user.lon], {icon: getUserIcon(user.icon)}).addTo(map)
+          .bindPopup(user.description);
       }
     });
   }
diff --git a/app/assets/openlayers/SimpleLayerSwitcher.js b/app/assets/openlayers/SimpleLayerSwitcher.js
deleted file mode 100644 (file)
index db3c720..0000000
+++ /dev/null
@@ -1,175 +0,0 @@
-var SimpleLayerSwitcher = OpenLayers.Class(OpenLayers.Control, {
-    layerStates: null,
-    layersDiv: null,
-    ascending: true,
-
-    initialize: function(options) {
-        OpenLayers.Control.prototype.initialize.apply(this, arguments);
-        this.layerStates = [];
-    },
-
-    destroy: function() {
-        OpenLayers.Event.stopObservingElement(this.div);
-
-        //clear out layers info and unregister their events 
-        this.map.events.un({
-            "addlayer": this.redraw,
-            "changelayer": this.redraw,
-            "removelayer": this.redraw,
-            "changebaselayer": this.redraw,
-            scope: this
-        });
-        OpenLayers.Control.prototype.destroy.apply(this, arguments);
-    },
-
-    setMap: function(map) {
-        OpenLayers.Control.prototype.setMap.apply(this, arguments);
-
-        this.map.events.on({
-            "addlayer": this.redraw,
-            "changelayer": this.redraw,
-            "removelayer": this.redraw,
-            "changebaselayer": this.redraw,
-            scope: this
-        });
-    },
-
-    draw: function() {
-        OpenLayers.Control.prototype.draw.apply(this);
-        this.loadContents();
-        this.redraw();
-        return this.div;
-    },
-
-    checkRedraw: function() {
-        var redraw = false;
-        if ( !this.layerStates.length ||
-             (this.map.layers.length != this.layerStates.length) ) {
-            redraw = true;
-        } else {
-            for (var i=0, len=this.layerStates.length; i<len; i++) {
-                var layerState = this.layerStates[i];
-                var layer = this.map.layers[i];
-                if ( (layerState.name != layer.name) ||
-                     (layerState.inRange != layer.inRange) ||
-                     (layerState.id != layer.id) ||
-                     (layerState.visibility != layer.visibility) ) {
-                    redraw = true;
-                    break;
-                }
-            }
-        }
-        return redraw;
-    },
-
-    redraw: function() {
-        if (!this.checkRedraw()) {
-            return this.div;
-        }
-
-        this.div.innerHTML = '';
-        var len = this.map.layers.length;
-        this.layerStates = [];
-        for (var i = 0; i < this.map.layers.length; i++) {
-            var layer = this.map.layers[i];
-            this.layerStates[i] = {
-                'name': layer.name,
-                'visibility': layer.visibility,
-                'inRange': layer.inRange,
-                'id': layer.id
-            };
-        }
-
-        var layers = this.map.layers.slice();
-        if (!this.ascending) { layers.reverse(); }
-        for (var i = 0; i < layers.length; i++) {
-            var layer = layers[i];
-            var baseLayer = layer.isBaseLayer;
-
-            if (layer.displayInLayerSwitcher && baseLayer) {
-                var on = (baseLayer) ? (layer == this.map.baseLayer)
-                          : layer.getVisibility();
-                var layerElem = document.createElement('a');
-                layerElem.id = this.id + '_input_' + layer.name;
-                layerElem.innerHTML = layer.name;
-                layerElem.href = '#';
-
-                OpenLayers.Element.addClass(layerElem, 'basey');
-                OpenLayers.Element.addClass(layerElem,
-                    'basey-' + (on ? 'on' : 'off'));
-
-                if (!baseLayer && !layer.inRange) {
-                    layerElem.disabled = true;
-                }
-                var context = {
-                    'layer': layer
-                };
-                OpenLayers.Event.observe(layerElem, 'mouseup',
-                    OpenLayers.Function.bindAsEventListener(
-                        this.onInputClick,
-                        context)
-                );
-
-                this.div.appendChild(layerElem);
-            }
-        }
-
-        return this.div;
-    },
-
-    onInputClick: function(e) {
-        if (this.layer.isBaseLayer) {
-            this.layer.map.setBaseLayer(this.layer);
-        } else {
-            this.layer.setVisibility(!this.layer.getVisibility());
-        }
-        OpenLayers.Event.stop(e);
-    },
-
-    updateMap: function() {
-
-        // set the newly selected base layer
-        for(var i=0, len=this.baseLayers.length; i<len; i++) {
-            var layerEntry = this.baseLayers[i];
-            if (layerEntry.inputElem.checked) {
-                this.map.setBaseLayer(layerEntry.layer, false);
-            }
-        }
-
-        // set the correct visibilities for the overlays
-        for(var i=0, len=this.dataLayers.length; i<len; i++) {
-            var layerEntry = this.dataLayers[i];
-            layerEntry.layer.setVisibility(layerEntry.inputElem.checked);
-        }
-
-    },
-
-    loadContents: function() {
-        //configure main div
-        OpenLayers.Event.observe(this.div, 'mouseup',
-            OpenLayers.Function.bindAsEventListener(this.mouseUp, this));
-        OpenLayers.Event.observe(this.div, 'click',
-                      this.ignoreEvent);
-        OpenLayers.Event.observe(this.div, 'mousedown',
-            OpenLayers.Function.bindAsEventListener(this.mouseDown, this));
-        OpenLayers.Event.observe(this.div, 'dblclick', this.ignoreEvent);
-    },
-
-    ignoreEvent: function(evt) {
-        OpenLayers.Event.stop(evt);
-    },
-
-    mouseDown: function(evt) {
-        this.isMouseDown = true;
-        this.ignoreEvent(evt);
-    },
-
-    mouseUp: function(evt) {
-        if (this.isMouseDown) {
-            this.isMouseDown = false;
-            this.ignoreEvent(evt);
-        }
-    },
-
-    CLASS_NAME: "SimpleLayerSwitcher"
-});
diff --git a/app/assets/openlayers/SimplePanZoom.js b/app/assets/openlayers/SimplePanZoom.js
deleted file mode 100644 (file)
index 83f6b9d..0000000
+++ /dev/null
@@ -1,356 +0,0 @@
-/* Copyright (c) 2006-2012 by OpenLayers Contributors (see authors.txt for 
- * full list of contributors). Published under the 2-clause BSD license.
- * See license.txt in the OpenLayers distribution or repository for the
- * full text of the license. */
-
-
-/**
- * @requires OpenLayers/Control/PanZoom.js
- */
-
-/**
- * Class: OpenLayers.Control.PanZoomBar
- * The PanZoomBar is a visible control composed of a
- * <OpenLayers.Control.PanPanel> and a <OpenLayers.Control.ZoomBar>. 
- * By default it is displayed in the upper left corner of the map as 4
- * directional arrows above a vertical slider.
- *
- * Inherits from:
- *  - <OpenLayers.Control.PanZoom>
- */
-OpenLayers.Control.SimplePanZoom = OpenLayers.Class(OpenLayers.Control.PanZoom, {
-
-    /** 
-     * APIProperty: zoomStopWidth
-     */
-    zoomStopWidth: 18,
-
-    /** 
-     * APIProperty: zoomStopHeight
-     */
-    zoomStopHeight: 7,
-
-    /** 
-     * Property: slider
-     */
-    slider: null,
-
-    /** 
-     * Property: sliderEvents
-     * {<OpenLayers.Events>}
-     */
-    sliderEvents: null,
-
-    /** 
-     * Property: zoombarDiv
-     * {DOMElement}
-     */
-    zoombarDiv: null,
-
-    /** 
-     * APIProperty: zoomWorldIcon
-     * {Boolean}
-     */
-    zoomWorldIcon: false,
-
-    /**
-     * APIProperty: panIcons
-     * {Boolean} Set this property to false not to display the pan icons. If
-     * false the zoom world icon is placed under the zoom bar. Defaults to
-     * true.
-     */
-    panIcons: true,
-
-    /**
-     * APIProperty: forceFixedZoomLevel
-     * {Boolean} Force a fixed zoom level even though the map has 
-     *     fractionalZoom
-     */
-    forceFixedZoomLevel: false,
-
-    /**
-     * Property: mouseDragStart
-     * {<OpenLayers.Pixel>}
-     */
-    mouseDragStart: null,
-
-    /**
-     * Property: deltaY
-     * {Number} The cumulative vertical pixel offset during a zoom bar drag.
-     */
-    deltaY: null,
-
-    /**
-     * Property: zoomStart
-     * {<OpenLayers.Pixel>}
-     */
-    zoomStart: null,
-
-    /**
-     * Constructor: OpenLayers.Control.PanZoomBar
-     */ 
-    buttons: null,
-
-    /**
-     * APIMethod: destroy
-     */
-    destroy: function() {
-
-        this._removeZoomBar();
-
-        this.map.events.un({
-            "changebaselayer": this.redraw,
-            "updatesize": this.redraw,
-            scope: this
-        });
-
-        OpenLayers.Control.PanZoom.prototype.destroy.apply(this, arguments);
-
-        delete this.mouseDragStart;
-        delete this.zoomStart;
-    },
-    
-    /**
-     * Method: setMap
-     * 
-     * Parameters:
-     * map - {<OpenLayers.Map>} 
-     */
-    setMap: function(map) {
-        OpenLayers.Control.PanZoom.prototype.setMap.apply(this, arguments);
-        this.map.events.on({
-            "changebaselayer": this.redraw,
-            "updatesize": this.redraw,
-            scope: this
-        });
-    },
-
-    /** 
-     * Method: redraw
-     * clear the div and start over.
-     */
-    redraw: function() {
-        if (this.div !== null) {
-            this.removeButtons();
-            this._removeZoomBar();
-        }  
-        this.draw();
-        this.moveZoomBar();
-    },
-    
-    /**
-    * Method: draw 
-    *
-    * Parameters:
-    * px - {<OpenLayers.Pixel>} 
-    */
-    draw: function(px) {
-        // initialize our internal div
-        OpenLayers.Control.prototype.draw.apply(this, arguments);
-        px = this.position.clone();
-
-        // place the controls
-        this.buttons = [];
-        var ids = ['panup', 'panleft', 'panright', 'pandown', 'zoomout', 'zoomin'];
-
-        for (var i = 0; i < ids.length; i++) {
-            var b = document.createElement('div');
-            b.id = ids[i];
-            b.action = ids[i];
-            b.className = 'button olButton';
-            this.div.appendChild(b);
-            this.buttons.push(b);
-        }
-
-        this._addZoomBar();
-        return this.div;
-    },
-
-    /** 
-    * Method: _addZoomBar
-    * 
-    * Parameters:
-    * centered - {<OpenLayers.Pixel>} where zoombar drawing is to start.
-    */
-    _addZoomBar:function() {
-        var id = this.id + "_" + this.map.id;
-        var zoomsToEnd = this.map.getNumZoomLevels() - 1 - this.map.getZoom();
-        var slider = document.createElement('div');
-        slider.id = 'slider';
-        slider.className = 'button';
-        slider.style.cursor = 'move';
-        this.slider = slider;
-        
-        this.sliderEvents = new OpenLayers.Events(this, slider, null, true,
-            { includeXY: true });
-        this.sliderEvents.on({
-            "touchstart": this.zoomBarDown,
-            "touchmove": this.zoomBarDrag,
-            "touchend": this.zoomBarUp,
-            "mousedown": this.zoomBarDown,
-            "mousemove": this.zoomBarDrag,
-            "mouseup": this.zoomBarUp
-        });
-        
-        var height = this.zoomStopHeight * (this.map.getNumZoomLevels());
-        
-        // this is the background image
-        var div = document.createElement('div');
-        div.className = 'button olButton';
-        div.id = 'zoombar';
-        this.zoombarDiv = div;
-        
-        this.div.appendChild(div);
-        this.startTop = 75;
-        this.div.appendChild(slider);
-
-        this.map.events.register("zoomend", this, this.moveZoomBar);
-    },
-    
-    /**
-     * Method: _removeZoomBar
-     */
-    _removeZoomBar: function() {
-        this.sliderEvents.un({
-            "touchstart": this.zoomBarDown,
-            "touchmove": this.zoomBarDrag,
-            "touchend": this.zoomBarUp,
-            "mousedown": this.zoomBarDown,
-            "mousemove": this.zoomBarDrag,
-            "mouseup": this.zoomBarUp
-        });
-        this.sliderEvents.destroy();
-        
-        this.div.removeChild(this.zoombarDiv);
-        this.zoombarDiv = null;
-        this.div.removeChild(this.slider);
-        this.slider = null;
-        
-        this.map.events.unregister("zoomend", this, this.moveZoomBar);
-    },
-    
-    /**
-     * Method: onButtonClick
-     *
-     * Parameters:
-     * evt - {Event}
-     */
-    onButtonClick: function(evt) {
-        OpenLayers.Control.PanZoom.prototype.onButtonClick.apply(this, arguments);
-        if (evt.buttonElement === this.zoombarDiv) {
-            var levels = evt.buttonXY.y / this.zoomStopHeight;
-            if (this.forceFixedZoomLevel || !this.map.fractionalZoom) {
-                levels = Math.floor(levels);
-            } 
-            var zoom = (this.map.getNumZoomLevels() - 1) - levels; 
-            zoom = Math.min(Math.max(zoom, 0), this.map.getNumZoomLevels() - 1);
-            this.map.zoomTo(zoom);
-        }
-    },
-    
-    /**
-     * Method: passEventToSlider
-     * This function is used to pass events that happen on the div, or the map,
-     * through to the slider, which then does its moving thing.
-     *
-     * Parameters:
-     * evt - {<OpenLayers.Event>} 
-     */
-    passEventToSlider:function(evt) {
-        this.sliderEvents.handleBrowserEvent(evt);
-    },
-    
-    /*
-     * Method: zoomBarDown
-     * event listener for clicks on the slider
-     *
-     * Parameters:
-     * evt - {<OpenLayers.Event>} 
-     */
-    zoomBarDown:function(evt) {
-        if (!OpenLayers.Event.isLeftClick(evt) && !OpenLayers.Event.isSingleTouch(evt)) {
-            return;
-        }
-        this.map.events.on({
-            "touchmove": this.passEventToSlider,
-            "mousemove": this.passEventToSlider,
-            "mouseup": this.passEventToSlider,
-            scope: this
-        });
-        this.mouseDragStart = evt.xy.clone();
-        this.zoomStart = evt.xy.clone();
-        this.div.style.cursor = "move";
-        // reset the div offsets just in case the div moved
-        this.zoombarDiv.offsets = null; 
-        OpenLayers.Event.stop(evt);
-    },
-    
-    /*
-     * Method: zoomBarDrag
-     * This is what happens when a click has occurred, and the client is
-     * dragging.  Here we must ensure that the slider doesn't go beyond the
-     * bottom/top of the zoombar div, as well as moving the slider to its new
-     * visual location
-     *
-     * Parameters:
-     * evt - {<OpenLayers.Event>}
-     */
-    zoomBarDrag: function(evt) {
-        if (this.mouseDragStart !== null) {
-            var deltaY = this.mouseDragStart.y - evt.xy.y;
-            var offsets = OpenLayers.Util.pagePosition(this.zoombarDiv);
-            if ((evt.clientY - offsets[1]) > 0 && 
-                (evt.clientY - offsets[1]) < 140) {
-                var newTop = parseInt(this.slider.style.top, 10) - deltaY;
-                this.slider.style.top = newTop + "px";
-                this.mouseDragStart = evt.xy.clone();
-            }
-            // set cumulative displacement
-            this.deltaY = this.zoomStart.y - evt.xy.y;
-            OpenLayers.Event.stop(evt);
-        }
-    },
-
-    /*
-     * Method: zoomBarUp
-     * Perform cleanup when a mouseup event is received -- discover new zoom
-     * level and switch to it.
-     *
-     * Parameters:
-     * evt - {<OpenLayers.Event>}
-     */
-    zoomBarUp: function(evt) {
-        if (!OpenLayers.Event.isLeftClick(evt) && evt.type !== "touchend") {
-            return;
-        }
-        if (this.mouseDragStart) {
-            this.div.style.cursor = "";
-            this.map.events.un({
-                "touchmove": this.passEventToSlider,
-                "mouseup": this.passEventToSlider,
-                "mousemove": this.passEventToSlider,
-                scope: this
-            });
-            var zoomLevel = this.map.zoom;
-            zoomLevel += this.deltaY/this.zoomStopHeight;
-            zoomLevel = Math.max(Math.round(zoomLevel), 0);
-            this.map.zoomTo(zoomLevel);
-            this.mouseDragStart = null;
-            this.zoomStart = null;
-            this.deltaY = 0;
-            OpenLayers.Event.stop(evt);
-        }
-    },
-
-    /*
-    * Method: moveZoomBar
-    * Change the location of the slider to match the current zoom level.
-    */
-    moveZoomBar:function() {
-        var newTop =
-            ((this.map.getNumZoomLevels()-1) - this.map.getZoom()) *
-            this.zoomStopHeight + this.startTop;
-        this.slider.style.top = newTop + "px";
-    },
-    CLASS_NAME: "OpenLayers.Control.SimplePanZoom"
-});
diff --git a/app/assets/openlayers/theme/openstreetmap/SimpleLayerSwitcher.css.scss b/app/assets/openlayers/theme/openstreetmap/SimpleLayerSwitcher.css.scss
deleted file mode 100644 (file)
index 48e730f..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-.SimpleLayerSwitcher {
-  position: absolute;
-  top: 10px;
-  right: 10px;
-  background: #fff;
-  border: 1px solid #ccc;
-  min-width: 150px;
-  background: #fff;
-}
-
-.SimpleLayerSwitcher a.basey {
-  display: block;
-  text-decoration: none;
-  color: #838383;
-  padding: 2px 5px 2px 20px;
-}
-
-.SimpleLayerSwitcher a.basey-on {
-  color: #000;
-  background-color: #fff;
-  background-image: image-url("theme/openstreetmap/img/carat.png");
-  background-repeat: no-repeat;
-  background-position: 7px 9px;
-}
-
-.SimpleLayerSwitcher a.basey-off {
-  display: none;
-}
-
-.SimpleLayerSwitcher:hover a {
-  border-top: 1px solid #eee;
-}
-
-.SimpleLayerSwitcher a:hover {
-  background-color: #f5f5f5;
-}
-
-.SimpleLayerSwitcher:hover a:first-child {
-  border-top: none;
-}
-
-.SimpleLayerSwitcher:hover a.basey-off {
-  display: block;
-}
diff --git a/app/assets/openlayers/theme/openstreetmap/SimplePanZoom.css.scss b/app/assets/openlayers/theme/openstreetmap/SimplePanZoom.css.scss
deleted file mode 100644 (file)
index 2788221..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-.olControlSimplePanZoom {
-  top: 10px;
-  right: 10px;
-}
-
-.olControlSimplePanZoom .button {
-  background-image: image-url("theme/openstreetmap/img/map_sprite.png");
-  position: absolute;
-  background-repeat: no-repeat;
-  cursor: hand;
-  cursor: pointer;
-}
-
-.olControlSimplePanZoom #panup {
-  left: 10px;
-  width: 25px;
-  height: 13px;
-  background-position: -15px -5px;
-}
-
-.olControlSimplePanZoom #pandown {
-  left: 10px;
-  top: 36px;
-  width: 25px;
-  height: 15px;
-  background-position: -15px -40px;
-}
-
-.olControlSimplePanZoom #panleft {
-  top: 13px;
-  width: 25px;
-  height: 24px;
-  left: 0px;
-  background-position: -5px -17px;
-}
-
-.olControlSimplePanZoom #panright {
-  top: 13px;
-  width: 25px;
-  height: 24px;
-  left: 25px;
-  background-position: -30px -17px;
-}
-
-.olControlSimplePanZoom #zoomin {
-  top: 50px;
-  width: 26px;
-  height: 20px;
-  left: 10px;
-  background-position: -15px -61px;
-}
-
-.olControlSimplePanZoom #zoomout {
-  top: 210px;
-  width: 26px;
-  height: 20px;
-  left: 10px;
-  background-position: -15px -220px;
-}
-
-.olControlSimplePanZoom #slider {
-  top: 75px;
-  width: 25px;
-  height: 10px;
-  left: 10px;
-  -webkit-transition: top 100ms linear;
-  -moz-transition: top 100ms linear;
-  -o-transition: top 100ms linear;
-  background-position: -77px -58px;
-  pointer: move;
-  cursor: move;
-}
-
-.olControlSimplePanZoom #zoombar {
-  top: 70px;
-  width: 26px;
-  height: 140px;
-  left: 10px;
-  background-position: -15px -80px;
-}
diff --git a/app/assets/openlayers/theme/openstreetmap/img/carat.png b/app/assets/openlayers/theme/openstreetmap/img/carat.png
deleted file mode 100644 (file)
index 7fdf17e..0000000
Binary files a/app/assets/openlayers/theme/openstreetmap/img/carat.png and /dev/null differ
diff --git a/app/assets/openlayers/theme/openstreetmap/img/missing-tile.png b/app/assets/openlayers/theme/openstreetmap/img/missing-tile.png
deleted file mode 100644 (file)
index e6c5461..0000000
Binary files a/app/assets/openlayers/theme/openstreetmap/img/missing-tile.png and /dev/null differ
diff --git a/app/assets/openlayers/theme/openstreetmap/style.css.scss b/app/assets/openlayers/theme/openstreetmap/style.css.scss
deleted file mode 100644 (file)
index bd755a5..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- *= require theme/default/style
- *= require theme/openstreetmap/SimpleLayerSwitcher
- *= require theme/openstreetmap/SimplePanZoom
- */
-
-.olControlOverviewMapExtentRectangle {
-    background-image: image-url("theme/default/img/blank.gif");
-}
-
-.olControlOverviewMapRectReplacement {
-    background-image: image-url("theme/default/img/overview_replacement.gif");
-}
-
-.olControlNavigationHistory {
-    background-image: image-url("theme/default/img/navigation_history.png");
-}
-
-div.olControlSaveFeaturesItemActive {
-    background-image: image-url("theme/default/img/save_features_on.png");
-}
-
-div.olControlSaveFeaturesItemInactive {
-    background-image: image-url("theme/default/img/save_features_off.png");
-}
-
-.olControlPanPanel div {
-    background-image: image-url("theme/default/img/pan-panel.png");
-}
-
-.olControlZoomPanel div {
-    background-image: image-url("theme/default/img/zoom-panel.png");
-}
-
-.olPopupCloseBox {
-    background: image-url("theme/default/img/close.gif") no-repeat;
-}
-
-.olImageLoadError {
-    // remove opacity?
-    // remove filter?
-    background: image-url("theme/openstreetmap/img/missing-tile.png") no-repeat;
-}
-
-.olControlNavToolbar div,
-.olControlEditingToolbar div {
-    background-image: image-url("theme/default/img/editing_tool_bar.png");
-}
-
-div.olControlZoom a {
-    color: black;
-    background: #ffffff;
-    border: 1px solid #cccccc;
-    margin: 0 !important;
-    // remove filter
-}
-
-div.olControlZoom a.olControlZoomIn {
-    border-bottom: 0;
-}
-
-div.olControlZoom a:hover {
-    background: #f5f5f5;
-}
-
-// remove max-width hover?
index ec06997f8fc0bf4f93200d7a312e004b68b5878f..413133e456d533a1c5ee3d0437795f01a9990bf0 100644 (file)
@@ -332,6 +332,7 @@ h2 {
 
 #top-bar {
   position: absolute;
+  z-index: 10000;
   top: 0;
   left: 185px;
   right: 0;
@@ -430,7 +431,7 @@ body.site-export #tabnav a#exportanchor {
   left: 15px;
 }
 
-/* Rules for OpenLayers maps */
+/* Rules for Leaflet maps */
 
 #map {
   margin: 0px;
@@ -438,13 +439,6 @@ body.site-export #tabnav a#exportanchor {
   padding: 0px;
 }
 
-.olControlAttribution {
-  bottom: 15px !important;
-  left: 0px !important;
-  right: 0px !important;
-  text-align: center;
-}
-
 #permalink {
   z-index: 10000;
   position: absolute;
@@ -466,11 +460,13 @@ body.site-export #tabnav a#exportanchor {
   text-decoration: none;
 }
 
-.site-index #map .SimpleLayerSwitcher,
-.site-index #map .olControlSimplePanZoom,
-.site-export #map .SimpleLayerSwitcher,
-.site-export #map .olControlSimplePanZoom {
+.site-index .leaflet-top,
+.site-export .leaflet-top {
   top: 40px !important;
+
+  .leaflet-control {
+    margin-top: 0px !important;
+  }
 }
 
 .site-index #map .olControlScaleLine,
@@ -1121,16 +1117,21 @@ p#contributorGuidance {
 
 /* Rules for the user map */
 
-.user_map .olControlSimplePanZoom {
+.user_map .leaflet-control-pan,
+.user_map .leaflet-control-zoomslider {
   display: none;
 }
 
-.user_map .olControlZoom {
+.user_map .leaflet-control-zoom {
   display: block;
 }
 
 /* Rules for user popups on maps */
 
+.user_popup {
+  min-width: 200px;
+}
+
 .user_popup p {
   padding-top: 3px;
   padding-bottom: 3px;
diff --git a/app/assets/stylesheets/embed.css.scss b/app/assets/stylesheets/embed.css.scss
new file mode 100644 (file)
index 0000000..024f73d
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ *= require leaflet
+ */
+
+html {
+  width: 100%;
+  height: 100%;
+}
+
+body {
+  width: 100%;
+  height: 100%;
+  margin: 0px;
+}
+
+#map {
+  width: 100%;
+  height: 100%;
+}
+
+.leaflet-control-zoom-in {
+  background-image: image-url("images/zoom-in.png");
+}
+
+.leaflet-control-zoom-out {
+  background-image: image-url("images/zoom-out.png");
+}
index ca58badc4097e70c98bd0a854377025756070b29..5ffaee985561db98a56138059649c8922dfaedd0 100644 (file)
@@ -8,6 +8,6 @@
 
 /* Rules for OpenLayers maps */
 
-.olControlZoom {
+.leaflet-control-zoom {
   display: none;
 }
diff --git a/app/assets/stylesheets/leaflet-all.css.scss b/app/assets/stylesheets/leaflet-all.css.scss
new file mode 100644 (file)
index 0000000..3d9c381
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ *= require leaflet
+ *= require leaflet.locationfilter
+ *= require leaflet.pan
+ *= require leaflet.zoom
+ */
+
+/* Override to serve images through the asset pipeline. */
+.leaflet-control-zoom-in {
+  background-image: image-url("images/zoom-in.png");
+}
+
+.leaflet-control-zoom-out {
+  background-image: image-url("images/zoom-out.png");
+}
+
+.leaflet-control-layers-toggle {
+  background-image: image-url("images/layers.png");
+}
+
+div.leaflet-marker-icon.location-filter.resize-marker {
+  background-image: image-url("img/resize-handle.png");
+}
+
+div.leaflet-marker-icon.location-filter.move-marker {
+  background-image: image-url("img/move-handle.png");
+}
+
+/* Override to better match the pan/zoom control. */
+.leaflet-control-layers {
+  box-shadow: 0px 0px 3px #666;
+  background: #fff;
+  border-radius: 3px;
+}
+
+/* Override some conflicting styles.
+   https://github.com/openstreetmap/openstreetmap-website/pull/121#issuecomment-10206946 */
+.leaflet-popup-content img.user_thumbnail {
+  max-width: 50px !important;
+}
+
+.user_popup p {
+  margin: 0px 2px 0px 55px !important;
+}
diff --git a/app/assets/stylesheets/leaflet.pan.css.scss b/app/assets/stylesheets/leaflet.pan.css.scss
new file mode 100644 (file)
index 0000000..4b5aa65
--- /dev/null
@@ -0,0 +1,41 @@
+.leaflet-control-pan-up,
+.leaflet-control-pan-down,
+.leaflet-control-pan-left,
+.leaflet-control-pan-right {
+  background-image: image-url("map_sprite.png");
+  position: absolute;
+  background-repeat: no-repeat;
+  cursor: hand;
+  cursor: pointer;
+}
+
+.leaflet-control-pan-up {
+  left: 10px;
+  width: 25px;
+  height: 13px;
+  background-position: -15px -5px;
+}
+
+.leaflet-control-pan-down {
+  left: 10px;
+  top: 36px;
+  width: 25px;
+  height: 15px;
+  background-position: -15px -40px;
+}
+
+.leaflet-control-pan-left {
+  left: 0px;
+  top: 13px;
+  width: 25px;
+  height: 24px;
+  background-position: -5px -17px;
+}
+
+.leaflet-control-pan-right {
+  left: 25px;
+  top: 13px;
+  width: 25px;
+  height: 24px;
+  background-position: -30px -17px;
+}
diff --git a/app/assets/stylesheets/leaflet.zoom.css.scss b/app/assets/stylesheets/leaflet.zoom.css.scss
new file mode 100644 (file)
index 0000000..f920b2f
--- /dev/null
@@ -0,0 +1,53 @@
+.leaflet-control-zoomslider-in,
+.leaflet-control-zoomslider-out,
+.leaflet-control-zoomslider-slider,
+.leaflet-control-zoomslider-slider-knob {
+  background-image: image-url("map_sprite.png");
+  position: absolute;
+  background-repeat: no-repeat;
+  cursor: hand;
+  cursor: pointer;
+}
+
+.leaflet-control-zoomslider-in,
+.leaflet-control-zoomslider-out {
+  text-indent: 26px;
+  overflow: hidden;
+  outline-style: none;
+}
+
+.leaflet-control-zoomslider-in {
+  top: 50px;
+  width: 26px;
+  height: 20px;
+  left: 10px;
+  background-position: -15px -61px;
+}
+
+.leaflet-control-zoomslider-out {
+  top: 202px;
+  width: 26px;
+  height: 20px;
+  left: 10px;
+  background-position: -15px -220px;
+}
+
+.leaflet-control-zoomslider-slider {
+  top: 70px;
+  width: 26px;
+  height: 132px;
+  left: 10px;
+  background-position: -15px -84px;
+}
+
+.leaflet-control-zoomslider-slider-knob {
+  top: 0px;
+  width: 25px;
+  height: 10px;
+  -webkit-transition: top 100ms linear;
+  -moz-transition: top 100ms linear;
+  -o-transition: top 100ms linear;
+  background-position: -77px -58px;
+  pointer: move;
+  cursor: move;
+}
index 367bd4fa566ddb6a8f8435618e60c6b771b98559..72c3255eb49869e1e64094004d432fa4fc514622 100644 (file)
   display: none;
 }
 
-.olControlZoom {
+.leaflet-control {
   display: none;
 }
 
-.olControlSimplePanZoom {
-  display: none;
-}
-
-.SimpleLayerSwitcher {
-  display: none;
-}
-
-.olControlScaleLine {
-  display: none !important;
-}
-
-.olControlAttribution {
-  display: none !important;
-}
-
 #map {
   border: 1px solid black;
   margin: auto !important;
index ba9e59d968a70fc2f5bcd1699def1cf2836b2672..5d658fdc10468c16eb0d2aaf53e8e801de590665 100644 (file)
@@ -95,15 +95,13 @@ h1 {
   border: 0;
 }
 
-.olControlSimplePanZoom {
+.leaflet-control-pan, .leaflet-control-zoomslider {
   display: none;
 }
 
-.site-index #map .olControlZoom,
-.site-index #map .SimpleLayerSwitcher,
-.site-export #map .olControlZoom,
-.site-export #map .SimpleLayerSwitcher {
-  top: 8px !important;
+.site-index .leaflet-top,
+.site-export .leaflet-top {
+  top: 47px !important;
 }
 
 /* Rules for the main content area */
@@ -115,12 +113,6 @@ h1 {
   border-right: 0px;
 }
 
-.site-export #content,
-.site-edit #content,
-.site-index #content {
-  margin-top: 40px;
-}
-
 /* Rules for search sidebar when shown */
 
 #sidebar {
index 2102a5704f4b29560387f2a4a2c2e5c84639a4fb..074a92ce82635a50e1bbc5ac5eb8ed960de7d347 100644 (file)
@@ -3,6 +3,8 @@ class ExportController < ApplicationController
   before_filter :authorize_web
   before_filter :set_locale
 
+  caches_page :embed
+
   def start
   end
 
@@ -23,4 +25,7 @@ class ExportController < ApplicationController
       redirect_to "http://parent.tile.openstreetmap.org/cgi-bin/export?bbox=#{bbox}&scale=#{scale}&format=#{format}"
     end
   end
+
+  def embed
+  end
 end
index 3777d710a3c04fdad917db673ed06374a23eee28..b0315dad99ba6155b14fa7cfe2be35581065806e 100644 (file)
@@ -1,11 +1,9 @@
 <div>
   <div style="text-align: center">
     <p style="margin-top: 10px; margin-bottom: 20px">
-      <a id="browse_select_view" href="#"><%= t'browse.start.view_data' %></a>
+      <a id="browse_filter_toggle" href="#"><%= t'browse.start_rjs.manually_select' %></a>
       <br />
-      <a id="browse_select_box" href="#"><%= t'browse.start.manually_select' %></a>  
-      <br />
-      <a id="browse_hide_areas_box" href="#"><%= t'browse.start.hide_areas' %></a>  
+      <a id="browse_hide_areas_box" href="#"><%= t'browse.start_rjs.hide_areas' %></a>
     </p>
   </div>
   <div id="browse_status" style="text-align: center; display: none">
diff --git a/app/views/export/embed.html.erb b/app/views/export/embed.html.erb
new file mode 100644 (file)
index 0000000..b683c26
--- /dev/null
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <meta charset="utf-8">
+    <title>OpenStreetMap Embedded</title>
+    <%= stylesheet_link_tag "embed", :media=> "screen" %>
+    <!--[if IE]>
+    <%= stylesheet_link_tag "leaflet.ie", :media=> "screen" %>
+    <![endif]-->
+    <%= javascript_include_tag "embed" %>
+  </head>
+  <body>
+    <div id="map"></div>
+  </body>
+</html>
index 88ad3eb392735684341bd5418656f1f7c76a54ce..19bed2bd3f7640eacc5ac3cc11e3197d0c4c9c4d 100644 (file)
@@ -5,7 +5,11 @@
   <%= stylesheet_link_tag "small-#{dir}", :media => "only screen and (max-width:641px)" %>
   <%= stylesheet_link_tag "large-#{dir}", :media => "screen and (min-width: 642px)" %>
   <%= stylesheet_link_tag "print-#{dir}", :media => "print" %>
-  <!--[if IE]><%= stylesheet_link_tag "large-#{dir}", :media => "screen" %><![endif]--> <!-- IE is totally broken with CSS media queries -->
+  <%= stylesheet_link_tag "leaflet-all", :media => "screen, print" %>
+  <!--[if IE]>
+    <%= stylesheet_link_tag "leaflet.ie" %>
+    <%= stylesheet_link_tag "large-#{dir}", :media => "screen" %>
+  <![endif]-->
   <%= favicon_link_tag "favicon.ico" %>
   <%= favicon_link_tag "osm_logo.png", :rel => "apple-touch-icon", :type => "image/png" %>
   <%= tag("link", { :rel => "publisher", :href => "https://plus.google.com/111953119785824514010" }) %>
index d4c33fcfd42900a21cb7e67273026bc3ec2d73ad..06516a16f200bbf9800da170b82fee07eec12761 100644 (file)
@@ -5,7 +5,6 @@
 <% unless STATUS == :api_offline or STATUS == :database_offline -%>
   <% content_for :editmenu do -%>
     <li><%= link_to t("browse.start_rjs.notes_layer_name"), notes_url(:format => :json), :id => "show_notes" %></li>
-    <li><%= link_to t("browse.start_rjs.data_layer_name"), { :controller => :browse, :action => :start }, :id => "show_data" %></li>
   <% end -%>
 <% end -%>
 
index 82ca579d9fc9ac5aaa7958c9b91ebc45f48e8dd4..1a28c21bd925e558e9a7125fb92e70c84662b25d 100644 (file)
@@ -3,7 +3,7 @@
      user_data = {
        :lon => contact.home_lon,
        :lat => contact.home_lat,
-       :icon => type == "friend" ? "marker-blue.png" : "marker-green.png",
+       :icon => image_path(type == "friend" ? "marker-blue.png" : "marker-green.png"),
        :description => render(:partial => "popup", :object => contact, :locals => {:type => type})
      }
   %>
index 6eed09c505537bd10cbc7201175ddd7e66ee36dc..04fc4cb3b09346184981d3f27aca73108949fcaf 100644 (file)
       user_data = {
         :lon => @user.home_lon,
         :lat => @user.home_lat,
-        :icon => "marker.png",
+        :icon => image_path("marker-red.png"),
         :description => render(:partial => "popup", :object => @user, :locals => {:type => "your location"})
       }
     %>
index 56e8f08e99ac7ca71b38af765d8e454bc142de9e..d7689ca2b5f5174cb0af0dc229f98b44a104aa25 100644 (file)
@@ -60,7 +60,8 @@ OpenStreetMap::Application.configure do
   config.assets.precompile += %w( user.js diary_entry.js pngfix.js swfobject.js )
   config.assets.precompile += %w( large-ltr.css small-ltr.css print-ltr.css )
   config.assets.precompile += %w( large-rtl.css small-rtl.css print-rtl.css )
-  config.assets.precompile += %w( browse.css theme/openstreetmap/style.css )
+  config.assets.precompile += %w( browse.css leaflet-all.css leaflet.ie.css )
+  config.assets.precompile += %w( embed.js embed.css )
 
   # Disable delivery errors, bad email addresses will be ignored
   # config.action_mailer.raise_delivery_errors = false
diff --git a/config/initializers/active_record.rb b/config/initializers/active_record.rb
deleted file mode 100644 (file)
index fdb6888..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-# We need to restore field_changed? support until CPK is updated
-module ActiveRecord
-  module AttributeMethods
-    module Dirty
-    private
-      alias_method :field_changed?, :_field_changed?
-    end
-  end
-end
index b8366f11a411b28fa013464eb57eb95d580b0ef9..f508f3fec8dbc04a7477eff6874e166e430987ea 100644 (file)
@@ -132,14 +132,10 @@ af:
         node: Node
         relation: Relasie
         way: Weg
-    start: 
-      manually_select: Kies 'n ander gebied handmatig
-      view_data: Wys data vir die huidige kaart
     start_rjs: 
       data_frame_title: Gegewens
       data_layer_name: Data
       details: Details
-      drag_a_box: Trek 'n boks op die kaart om 'n gebied te kies
       edited_by_user_at_timestamp: Verander deur %{user} om %{timestamp}
       history_for_feature: Geskiedenis van %{feature}
       load_data: Laai data
index 9913adb48f00001fc80f2982b2ff5ae379987513..3caeb60b4899e38ae8ef7822b32385bdb326284c 100644 (file)
@@ -184,14 +184,10 @@ aln:
         node: Nyje
         relation: Lidhje
         way: Udhë
-    start: 
-      manually_select: Manualisht zgedh ni zon qetër
-      view_data: Shini te dhanat per harten e tanishme
     start_rjs: 
       data_frame_title: Te dhanunat
       data_layer_name: Të dhanunat
       details: Detajet
-      drag_a_box: Tërhjeke nji kuti n'hartë për me e zgjedhë zonën
       edited_by_user_at_timestamp: E ka ndryshue %{user} në %{timestamp}
       history_for_feature: Historia për %{feature}
       load_data: Ngarkoji të dhanunat
index f7fb4249449770142fb964b08f86362a8ba2a3d9..83640c973d4641ae724fea7b3a24204e241f3a43 100644 (file)
@@ -205,14 +205,10 @@ ar:
         node: عقدة
         relation: علاقة
         way: طريق
-    start: 
-      manually_select: اختر يدويًا منطقة أخرى
-      view_data: أظهر بيانات عرض الخريطة الحالي
     start_rjs: 
       data_frame_title: البيانات
       data_layer_name: تصفح بيانات الخريطة
       details: التفاصيل
-      drag_a_box: اسحب مربع على الخريطة لاختيار منطقة
       edited_by_user_at_timestamp: عُدّل بواسطة %{user} في %{timestamp}
       hide_areas: إخفاء المناطق
       history_for_feature: تاريخ الــ%{feature}
index 0dac59bea2eb1cd3aea8b7f0a069adad87cf1e5a..ae9d59923017c6d9932ebe12b534d7d317900c4e 100644 (file)
@@ -175,14 +175,10 @@ arz:
         node: عقدة
         relation: علاقة
         way: طريق
-    start: 
-      manually_select: اختر يدويًا منطقه أخرى
-      view_data: أظهر بيانات عرض الخريطه الحالي
     start_rjs: 
       data_frame_title: البيانات
       data_layer_name: البيانات
       details: التفاصيل
-      drag_a_box: اسحب مربع على الخريطه لاختيار منطقة
       edited_by_user_at_timestamp: عُدّل بواسطه %{user} فى %{timestamp}
       history_for_feature: تاريخ الــ%{feature}
       load_data: تحميل البيانات
index fd95b11fb727abf46359f8a5f7083753c4824abc..fba983786ee4c16b8c18684c0ba9211dd9a54da1 100644 (file)
@@ -193,14 +193,10 @@ ast:
         node: Nodiu
         relation: Rellación
         way: Vía
-    start: 
-      manually_select: Seleiciona manualmente un área distinta
-      view_data: Ver datos de la vista actual del mapa
     start_rjs: 
       data_frame_title: Datos
       data_layer_name: Ver datos del mapa
       details: Detalles
-      drag_a_box: Arrastra un cuadru nel mapa pa seleicionar un área
       edited_by_user_at_timestamp: Editao por %{user} el %{timestamp}
       hide_areas: Anubrir árees
       history_for_feature: Historial de %{feature}
index 7bfc6f27ad8c519644fd74664e959cba4780d62f..33f919e1c8899fc91f840b967258d2d8722959a7 100644 (file)
@@ -192,14 +192,10 @@ be-Tarask:
         node: Вузел
         relation: Адносіны
         way: Шлях
-    start: 
-      manually_select: Выбраць іншы абшар
-      view_data: Паказаць зьвесткі для цяперашняга выгляду мапы
     start_rjs: 
       data_frame_title: Зьвесткі
       data_layer_name: Праглядзець зьвесткі мапы
       details: Падрабязнасьці
-      drag_a_box: Расьцягніце рамку для выбару абшару
       edited_by_user_at_timestamp: Рэдагаваны %{user} у %{timestamp}
       hide_areas: Схаваць вобласьці
       history_for_feature: Гісторыя %{feature}
index e3209730d52764170a9a5e564648dbf515d0586c..3cce2dd5cae15cb4f9d5fd135d4d5c2f49078ff4 100644 (file)
@@ -197,14 +197,10 @@ br:
         node: Skoulm
         relation: Darempred
         way: Hent
-    start: 
-      manually_select: Diuzañ un takad disheñvel gant an dorn
-      view_data: Gwelet ar roadennoù evit gwel red ar gartenn
     start_rjs: 
       data_frame_title: Roadennoù
       data_layer_name: Furchal e roadennoù ar gartenn
       details: Munudoù
-      drag_a_box: Tresit ur voest war ar gartenn evit diuzañ un takad
       edited_by_user_at_timestamp: Aozet gant %{user} da %{timestamp}
       hide_areas: Kuzhat an takadoù
       history_for_feature: Istor evit %{feature}
index 6d5460fa45342f53b6c936bd45d92fe5f0dded75..5558caf515cda3eb70bc4e92266214abff5b9b19 100644 (file)
@@ -73,6 +73,14 @@ bs:
       way: Putanja
       way_node: Čvor putanje
       way_tag: Oznaka putanje
+  application: 
+    require_cookies: 
+      cookies_needed: Čini se da su vam kolačići onemogućeni, molimo omogućite kolačiće u vašem pregledniku prije nastavka.
+    require_moderator: 
+      not_a_moderator: Trebate biti moderator da bi izvršili tu radnju.
+    setup_user_auth: 
+      blocked: Vaš pristup API-u je blokiran. Prijavite se na web upravljačku ploču da saznate više.
+      need_to_see_terms: Vaš pristup API-ju je privremeno suspendovan. Molimo da se prijavite na web upravljačku ploču da pogledate Uslove za doprinosioce. Ne morate se složiti, ali ih morate pogledati.
   browse: 
     changeset: 
       changeset: "Set promjena: %{id}"
@@ -191,14 +199,10 @@ bs:
         node: Čvor
         relation: Relacija
         way: Putanja
-    start: 
-      manually_select: Ručno izabrati drukčije područje
-      view_data: Prikazati podatke za trenutni prikaz karte
     start_rjs: 
       data_frame_title: Podaci
       data_layer_name: Pretražiti podatke na karti
       details: Detalji
-      drag_a_box: Povući okvir na karti za odabir područja
       edited_by_user_at_timestamp: Uređeno od strane %{user} u %{timestamp}
       hide_areas: Sakriti područja
       history_for_feature: Historija za %{feature}
@@ -315,8 +319,8 @@ bs:
       hide_link: Sakriti ovaj komentar
     diary_entry: 
       comment_count: 
-        one: 1 komentar
-        other: "%{count} komentara"
+        other: jedan=%{count} komentar
+        zero: nema komentara
       comment_link: Komentirati ovaj zapis
       confirm: Potvrditi
       edit_link: Urediti ovaj unos
@@ -912,6 +916,19 @@ bs:
           water_point: Tačka vodotoka
           waterfall: Vodopad
           weir: Brana
+  javascripts: 
+    map: 
+      base: 
+        cycle_map: Biciklistička karta
+        standard: Standardni/a
+        transport_map: Transportna karta
+    site: 
+      edit_disabled_tooltip: Uvećati za uređivanje karte
+      edit_tooltip: Urediti kartu
+      edit_zoom_alert: Morate uvećati da bi uređivali kartu
+      history_disabled_tooltip: Uvećajte da biste vidjeli uređivanja za ovo područje
+      history_tooltip: Prikazati izmjene za ovo područje
+      history_zoom_alert: Morate uvećati da bi vidjeli historiju izmjena za ovo područje
   layouts: 
     community: Zajednica
     community_blogs: Blogovi zajednice
@@ -1012,6 +1029,76 @@ bs:
       native_link: BOSANSKI verzija
       text: Vi gledate englesku verziju stranice o autorskim pravima. Možete se vratiti na %{native_link} ove stranice ili možete prestati čitati o autorskim pravima i %{mapping_link}.
       title: O ovoj stranici
+  message: 
+    delete: 
+      deleted: Poruka izbrisana
+    inbox: 
+      date: Datum
+      from: Od
+      messages: Imate %{new_messages} i %{old_messages}
+      my_inbox: Moja dolazna pošta
+      new_messages: 
+        one: "%{count} nova poruka"
+        other: "%{count} nove/ih poruke/a"
+      no_messages_yet: Još uvijek nemate poruka. Zašto nebi stupili u kontakt s nekim ljudima iz okoline %{people_mapping_nearby_link}?
+      old_messages: 
+        one: "%{count} stara poruka"
+        other: "%{count} stare poruke"
+      outbox: odlazna pošta
+      people_mapping_nearby: osobe koje uređuju kartu blizu Vas
+      subject: Predmet
+      title: Dolazna pošta
+    mark: 
+      as_read: Poruka označena kao pročitana
+      as_unread: Poruka označena kao nepročitana
+    message_summary: 
+      delete_button: Izbrisati
+      read_button: Označiti kao pročitano
+      reply_button: Odgovoriti
+      unread_button: Označiti kao nepročitano
+    new: 
+      back_to_inbox: Nazad u dolaznu poštu
+      body: Tijelo
+      limit_exceeded: Nedavno ste poslali puno poruke. Pričekajte prije nego pokušate poslati još.
+      message_sent: Poruka poslana
+      send_button: Poslati
+      send_message_to: Poslati novu poruku za %{name}
+      subject: Predmet
+      title: Poslati poruku
+    no_such_message: 
+      body: Žao nam je nema poruke s tim id.
+      heading: Nema takve poruke
+      title: Nema takve poruke
+    outbox: 
+      date: Datum
+      inbox: dolazna pošta
+      messages: 
+        one: Poslali ste %{count} poruku
+        other: Poslali ste %{count} poruke/a
+      my_inbox: Moj %{inbox_link}
+      no_sent_messages: Još uvijek nemate poslanih poruka. Zašto nebi stupili u kontakt s nekim ljudima iz okoline %{people_mapping_nearby_link}?
+      outbox: odlazna pošta
+      people_mapping_nearby: osobe koje uređuju kartu blizu Vas
+      subject: Predmet
+      title: Odlazna pošta
+      to: Za
+    read: 
+      back_to_inbox: Nazad u dolaznu poštu
+      back_to_outbox: Nazad u odlaznu poštu
+      date: Datum
+      from: Od
+      reading_your_messages: Čitanje Vaših poruka
+      reading_your_sent_messages: Čitanje Vaših poslanih poruka
+      reply_button: Odgovoriti
+      subject: Predmet
+      title: Pročitati poruku
+      to: Za
+      unread_button: Označiti kao nepročitano
+      wrong_user: "Prijavljeni ste kao: `%{user}', ali poruka za koju ste tražili da pročitate nije poslana od strane ili tom korisniku. Molimo, prijavite se kao ispravan korisnik kako bi ste je pročitali."
+    reply: 
+      wrong_user: "Prijavljeni ste kao: `%{user}', ali poruka za koju ste zamoljeni da odgovorite nije poslana tom korisniku. Molimo, prijavite se kao ispravan korisnik kako bi odgovorili."
+    sent_message_summary: 
+      delete_button: Izbrisati
   notifier: 
     diary_comment_notification: 
       footer: Možete pročitati komentare na %{readurl} i komentirati na %{commenturl} ili odgovoriti na %{replyurl}
@@ -1021,6 +1108,7 @@ bs:
     email_confirm: 
       subject: "[OpenStreetMap] Potvrdite Vašu e-mail adresu"
     email_confirm_html: 
+      click_the_link: Ako ste ovo Vi, kliknite na link naveden ispod za potvrdu promjene
       greeting: Zdravo,
       hopefully_you: Netko (nadamo se Vi) bi želio promjeniti svoju e-mail adresu sa %{server_url} na %{new_address}.
     email_confirm_plain: 
@@ -1047,6 +1135,17 @@ bs:
         subject: "[OpenStreetMap] GPX Import uspješan"
       with_description: sa opisom
       your_gpx_file: Liči na vašu GPX datoteku
+    lost_password: 
+      subject: "[OpenStreetMap] Zahtjev za ponovnim postavljanjem lozinke"
+    lost_password_html: 
+      click_the_link: Ukoliko ste ovo Vi, kliknite na link ispod za ponovno postavljanje lozinke.
+      greeting: Zdravo,
+      hopefully_you: Neko (moguće, Vi) je pitao za ponovno postavljanje lozinke na njihovim e-mail adresama openstreetmap.org računa.
+    lost_password_plain: 
+      click_the_link: Ukoliko ste ovo Vi, kliknite na link ispod za ponovno postavljanje lozinke.
+      greeting: Zdravo,
+      hopefully_you_1: Neko (moguće Vi) je pitao za ponovno postavljanje lozinke na
+      hopefully_you_2: e-mail adrese openstreetmap.org korisničkog računa.
     message_notification: 
       footer1: Možete takođe pročitati poruku na %{readurl}
       footer2: i možete odgovoriti na %{replyurl}
@@ -1078,6 +1177,694 @@ bs:
       the_wiki: "Čitajte o OpenStreetMap-u na wiki-ju:"
       user_wiki_page: Preporučeno je da napravite wiki korisničku stranicu, koja uključuje oznake kategorije gdje se nalazite, kao što je [[Category:Users_in_Sarajevo]].
       wiki_signup: "Možda želite takođe otvoriti račun i na OpenStreetMap wiki na:"
+  oauth: 
+    oauthorize: 
+      allow_read_gpx: Pročitajte Vaše privatne GPS trase.
+      allow_read_prefs: Pročitajte Vaše korisničke postavke.
+      allow_to: "Dozvoliti klijentskoj aplikaciji da:"
+      allow_write_api: Izmijenite kartu.
+      allow_write_diary: Zapište u dnevnik, komentirajte i sklopite prijateljstva.
+      allow_write_gpx: Postaviti GPS trase.
+      allow_write_prefs: Izmjenite Vaše korisničke postavke.
+      request_access: "Aplikacija %{app_name} zahtjeva pristup Vašem korisničkom računu:  %{user}. Provjerite želite li da aplikacija ima sljedeće mogućnosti. Možete odabrati koliko joj pristupa želite dopustiti."
+    revoke: 
+      flash: Opozvali ste značku za %{application}
+  oauth_clients: 
+    create: 
+      flash: Informacije su uspješno registrirane
+    destroy: 
+      flash: Uništena registracija klijent aplikacije
+    edit: 
+      submit: Urediti
+      title: Uredite Vašu aplikaciju
+    form: 
+      allow_read_gpx: Pročitajte njihove privatne GPS trase.
+      allow_read_prefs: Pročitajte njihove korisničke postavke.
+      allow_write_api: Izmijenite kartu.
+      allow_write_diary: Zapište u dnevnik, komentirajte i sklopite prijateljstva.
+      allow_write_gpx: Postaviti GPS trase.
+      allow_write_prefs: Izmjenite njhove korisničke postavke.
+      callback_url: URL za povratni poziv
+      name: Ime
+      requests: "Zahtjevamo sljedeće dozvole od korisnika:"
+      required: Obavezno
+      support_url: URL podrške
+      url: URL glavne aplikacije
+    index: 
+      application: Ime aplikacije
+      issued_at: Izdano u
+      list_tokens: "Sljedeće značke su izdane aplikacijama na vaše ime:"
+      my_apps: Moje klijentske aplikacije
+      my_tokens: Moje ovlaštene aplikacije
+      no_apps: Imate li aplikaciju koju želite registrirati za korištenje sa %{oauth} standardom? Morate registrirati Vašu web aplikaciju prije nego ona može slati OAuth zahtjeve za ovu uslugu
+      register_new: Registrirajte Vašu aplikaciju
+      registered_apps: "Imate registrirane slijedeće klijentske aplikacije:"
+      revoke: Opozvati!
+      title: Moji OAuth detalji
+    new: 
+      submit: Registrirati
+      title: Registrirajte novu aplikaciju
+    not_found: 
+      sorry: Žao mi je, taj se %{type} ne može naći.
+    show: 
+      access_url: "URL pristupa znački:"
+      allow_read_gpx: Pročitajte njihove privatne GPS trase.
+      allow_read_prefs: Pročitajte njihove korisničke postavke.
+      allow_write_api: Izmijenite kartu.
+      allow_write_diary: Zapište u dnevnik, komentirajte i sklopite prijateljstva.
+      allow_write_gpx: Postaviti GPS trase.
+      allow_write_prefs: Izmjenite njhove korisničke postavke.
+      authorize_url: "URL ovlaštenja:"
+      confirm: Da li ste sigurni?
+      delete: Izbrisati klijenta
+      edit: Urediti detalje
+      key: "Ključ korisnika:"
+      requests: "Zahtjevamo sljedeće dozvole od korisnika:"
+      secret: "Tajna korisnika:"
+      support_notice: Podržavamo HMAC-SHA1 (preporučeno) kao i obični tekst u ssl modu.
+      title: OAuth detalji za %{app_name}
+      url: "URL za zahtjev značke:"
+    update: 
+      flash: Informacije o klijentu uspješno osvježene
+  redaction: 
+    create: 
+      flash: Redakcija napravljena.
+    destroy: 
+      error: Pojavila se greška tokom uništavanja ove redakcije.
+      flash: Redakcija uništena.
+      not_empty: Redakcija nije prazna. Molimo poništite sve verzije koje ne pripadaju ovoj redakciji prije nego je uništite.
+    edit: 
+      description: Opis
+      heading: Urediti redakciju
+      submit: Spremiti redakciju
+      title: Urediti redakciju
+    index: 
+      empty: Nema redakcija za prikaz.
+      heading: Spisak redakcija
+      title: Spisak redakcija
+    new: 
+      description: Opis
+      heading: Unijeti informacije za novu redakciju
+      submit: Napraviti redakciju
+      title: Pravljenje nove redakcije
+    show: 
+      confirm: Da li ste sigurni?
+      description: "Opis:"
+      destroy: Ukloniti ovu redakciju
+      edit: Urediti ovu redakciju
+      heading: Prikaz redakcije "%{title}"
+      title: Prikaz redakcije
+      user: "Kreator:"
+    update: 
+      flash: Promjene sačuvane.
+  site: 
+    edit: 
+      anon_edits_link_text: Otkrijte zašto je to tako.
+      flash_player_required: Potreban Vam je Flash player da bi koristili Potlatch, OpenStreetMap Flash uređivač. Možete <a href="http://www.adobe.com/shockwave/download/index.cgi?P1_Prod_Version=ShockwaveFlash">preuzeti Adobe Flash Player sa Adobe.com</a>. <a href="http://wiki.openstreetmap.org/wiki/Editing">Neke druge mogućnosti</a> su takođe dostupne za uređivanje OpenStreetMap-a.
+      no_iframe_support: Vaš preglednik ne podržava HTML iframes, koji su potrebni za ovu značajku.
+      not_public: Niste postavili Vaše promjene tako da budu javne.
+      not_public_description: Ne možete više uređivati kartu dok to ne napravite. Možete promjeniti Vaše promjene u javne sa %{user_page}.
+      potlatch2_not_configured: Potlatch 2 nije konfiguriran - molimo pogledajte http://wiki.openstreetmap.org/wiki/The_Rails_Port#Potlatch_2 za više informacija
+      potlatch2_unsaved_changes: Imate nespremljene promjene. (Da bi ste spremili u Potlatch 2, trebali bi kliknuti Spremiti.)
+      potlatch_unsaved_changes: Imate nespremljene promjene. (Da bi ste spremili u Potlatch-u, morate odznačiti trenutnu putanju ili tačku, ako uređujete uživo; ili kliknite SPREMITI ako imate to dugme.)
+      user_page_link: korisnička stranica
+    index: 
+      js_1: Koristite internet preglednik koji ne podržava JavaScript, ili ste onemogućili JavaScript.
+      js_2: OpenStreetMap koristi JavaScript za slippy kartu.
+      license: 
+        copyright: Autorska prava OpenStreetMap i doprinosioci, pod otvorenom dozvolom
+      permalink: Trajni link
+      remote_failed: Uređivanje nije uspjelo - provjerite da li je JOSM ili Merkaartor učitan i da je opcija udaljenog pristupa omogućena
+      shortlink: Kratki link
+    key: 
+      map_key: Legenda
+      map_key_tooltip: Legenda karte
+      table: 
+        entry: 
+          admin: Administrativna granica
+          allotments: Vrtovi
+          apron: 
+            - Parking za avione
+            - terminal
+          bridge: Crni rubovi = most
+          bridleway: Konjička staza
+          brownfield: Gradilište
+          building: Značajna zgrada
+          byway: Prečica
+          cable: 
+            - Kabinska žičara
+            - Uspinjača sa naslonjačem
+          cemetery: Groblje
+          centre: Sportski centar
+          commercial: Poslovno područje
+          common: 
+            - Travnjaci
+            - Livada
+          construction: Ceste u izgradnji
+          cycleway: Biciklistička staza
+          destination: Pristup odredištu
+          farm: Polja, farme, njive
+          footway: Pješačka staza
+          forest: Šume (održavane, od šumarije)
+          golf: Golf teren
+          heathland: Stepa
+          industrial: Industrijsko područje
+          lake: 
+            - Jezero
+            - Rezervoar
+          military: Vojno područje
+          motorway: Autoput
+          park: Park
+          permissive: Pristup uz dozvolu
+          pitch: Sportski teren
+          primary: Državna cesta
+          private: Privatni pristup
+          rail: Željeznica
+          reserve: Rezervat prirode
+          resident: Stambeno područje
+          retail: Maloprodajno područje
+          runway: 
+            - Aerodromska pista
+            - Aerodromska rulna staza
+          school: 
+            - Škola
+            - Univerzitet
+          secondary: Sekundarna cesta
+          station: Željeznička stanica
+          subway: Podzemna željeznica
+          summit: 
+            - Vrh
+            - Kota
+          tourist: Turistička atrakcija
+          track: Staza
+          tram: 
+            - Lahka željeznica
+            - tramvaj
+          trunk: Brza cesta
+          tunnel: Iscrtkani rubovi = tunel
+          unclassified: Neklasificirana cesta
+          unsurfaced: Neasfaltirana cesta
+          wood: Šume (prirodne, neodržavane)
+    markdown_help: 
+      alt: Alt tekst
+      first: Prvi objekat
+      heading: Naslov
+      headings: Naslovi
+      image: Slika
+      link: Poveznica
+      ordered: Uređeni popis
+      second: Drugi objekat
+      subheading: Podnaslov
+      text: Tekst
+      title_html: Analizirano sa <a href="http://daringfireball.net/projects/markdown/">Markdown</a>
+      unordered: Neuređeni popis
+      url: URL
+    richtext_area: 
+      edit: Urediti
+      preview: Pregledati
+    search: 
+      search: Pretraga
+      search_help: "primjer: 'Mostar', 'Ferhadija 52, Sarajevo', ili 'željeznička stanica, Doboj' <a href='http://wiki.openstreetmap.org/wiki/Search'>više primjera...</a>"
+      submit_text: Idi
+      where_am_i: Gdje sam?
+      where_am_i_title: Opišite trenutnu lokaciju koristeći pretraživač
+    sidebar: 
+      close: Zatvoriti
+      search_results: Rezultati pretrage
   time: 
     formats: 
       friendly: "%e %B %Y u %H:%M"
+  trace: 
+    create: 
+      trace_uploaded: Vaša GPS datoteka je spremljena na server i čeka ubacivanje u bazu. Ovo se obično dogodi za pola sata i prije, a e-mail-om će te dobiti obavijest o završetku.
+      upload_trace: Postaviti GPS trasu
+    delete: 
+      scheduled_for_deletion: Trasa raspoređena za brisanje
+    edit: 
+      description: "Opis:"
+      download: preuzimanje
+      edit: urediti
+      filename: "Ime datoteke:"
+      heading: Uređivanje trase %{name}
+      map: karta
+      owner: "Vlasnik:"
+      points: "Tačaka:"
+      save_button: Sačuvati promjene
+      start_coord: "Početna koordinata:"
+      tags: "Oznake:"
+      tags_help: odvojeno zarezima
+      title: Uređivanje trase %{name}
+      uploaded_at: "Postavljeno:"
+      visibility: "Vidljivost:"
+      visibility_help: Šta ovo znači?
+    list: 
+      empty_html: JNoš uvijek nema ništa ovdje. <a href='%{upload_link}'>Postavite novu trasu</a> ili naučite više o GPS trasiranju na <a href='http://wiki.openstreetmap.org/wiki/Bs:Beginners'_guide'>wiki stranici</a>.
+      public_traces: Javne GPS trase
+      public_traces_from: Javne GPS trase korisnika %{user}
+      tagged_with: " označeno sa %{tags}"
+      your_traces: Vaše GPS trase
+    make_public: 
+      made_public: Trasa za javnost
+    offline: 
+      heading: GPX spremište je offline
+      message: Sistem za GPX spremanje i postavljanje trenutno nije u funkciji.
+    offline_warning: 
+      message: Sistem za GPX upload trenutno nije u funkciji.
+    trace: 
+      ago: prije %{time_in_words_ago}
+      by: od
+      count_points: "%{count} tačaka"
+      edit: urediti
+      edit_map: Urediti kartu
+      identifiable: MOŽE SE IDENTIFICIRATI
+      in: u
+      map: karta
+      more: više
+      pending: NA ČEKANJU
+      private: PRIVATNO
+      public: JAVNO
+      trace_details: Pogledati detalje trase
+      trackable: MOŽE SE PRATITI
+      view_map: Pogledati kartu
+    trace_form: 
+      description: "Opis:"
+      help: Pomoć
+      tags: "Oznake:"
+      tags_help: odvojeno zarezima
+      upload_button: Postaviti
+      upload_gpx: Poslati GPX datoteku
+      visibility: "Vidljivost:"
+      visibility_help: Šta ovo znači?
+    trace_header: 
+      see_all_traces: Pogledati sve trase
+      see_your_traces: Pogledati vlastite trase
+      traces_waiting: Imate %{count} trasu/e/a na čekanju za slanje. Uzmite ovo u obzir, i pričekajte da se završe prije slanja novih trasa, da ne blokirate ostale korisnike.
+      upload_trace: Poslati GPS trasu
+    trace_optionals: 
+      tags: Oznake
+    trace_paging_nav: 
+      newer: Novije trase
+      older: Starije trase
+      showing_page: Prikaz stranice %{page}
+    view: 
+      delete_track: Izbrišite ovu trasu
+      description: "Opis:"
+      download: preuzimanje
+      edit: urediti
+      edit_track: Uredite ovu trasu
+      filename: "Ime datoteke:"
+      heading: Prikaz trase %{name}
+      map: karta
+      none: Ništa
+      owner: "Vlasnik:"
+      pending: U TOKU
+      points: "Tačaka:"
+      start_coordinates: "Početna koordinata:"
+      tags: "Oznake:"
+      title: Prikaz trase %{name}
+      trace_not_found: Trasa nije pronađena!
+      uploaded: "Postavljeno:"
+      visibility: "Vidljivost:"
+    visibility: 
+      identifiable: Može se identificirati (prikazan u listi trasa kao onaj koji se može identificirati, uređene tačke sa vremenskom oznakom)
+      private: Privatni (prikazuje se kao anoniman, neuređene tačke)
+      public: Javni (prikazan u listi trasa i kao anoniman, neuređene tačke)
+      trackable: Može se pratiti (prikazuje se kao anoniman, uređene tačke sa vremenskom oznakom)
+  user: 
+    account: 
+      contributor terms: 
+        agreed: Prihvatili ste nove uslove za doprinosioce.
+        agreed_with_pd: Takođe ste proglasili da će vaše izmjene biti u javnom vlasništvu.
+        heading: "Uslovi za doprinosioce:"
+        link text: Šta je ovo?
+        not yet agreed: Niste još uvijek prihvatili nove uslove za doprinosioce.
+        review link text: Molimo Vas da slijedite ovaj link kada vam bude prikladno da pregledate i prihvatite nove uslove za doprinosioce.
+      current email address: "Trenutna e-mail adresa:"
+      delete image: Ukloniti trenutnu sliku
+      email never displayed publicly: (nikada se ne prikazuje javno)
+      flash update success: Korisničke informacije su uspješno osvježene.
+      flash update success confirm needed: Korisničke informacije su uspješno osvježene. Provjerite e-mail za porukom za potvrdu nove e-mail adrese.
+      gravatar: 
+        gravatar: Koristiti Gravatar
+        link text: Šta je ovo?
+      home location: "Matična lokacija:"
+      image: "Slika:"
+      image size hint: (kvadratne slike od barem 100x100 piksela su najbolje)
+      keep image: Zadržati trenutnu sliku
+      latitude: Geografska širina (Latitude)
+      longitude: Geografska dužina (Longitude)
+      make edits public button: Napraviti sve moje promjene javnim
+      my settings: Moja podešavanja
+      new email address: "Nova e-mail adresa:"
+      new image: Dodati sliku
+      no home location: Niste unijeli Vašu matičnu lokaciju.
+      openid: 
+        link: http://wiki.openstreetmap.org/wiki/OpenID
+        link text: Šta je ovo?
+        openid: OtvoreniID
+      preferred editor: "Preferirani uređivač:"
+      preferred languages: "Preferirani jezici:"
+      profile description: "Opis profila:"
+      public editing: 
+        disabled: Onemogućno i ne mogu se uređivati podaci, sva prethodna uređivanja su anonimna.
+        disabled link text: Zašto ja ne mogu uređivati?
+        enabled: Omogućeno. Nije anonimno i mogu se uređivati podaci.
+        enabled link: http://wiki.openstreetmap.org/wiki/Anonymous_edits
+        enabled link text: Šta je ovo?
+        heading: "Javno uređivanje:"
+      public editing note: 
+        heading: "Javno uređivanje:"
+        text: Trenutno su vaša uređivanja anonimna i ljudi vam ne mogu poslati poruke ili vidjeti vašu lokaciju. Da bi pokazali vaša uređivanja i dozvolili ljudima da vas kontaktiraju preko website-a, kliknite na dugme ispod <b>Od promjene 0.6 API-a, samo javni korisnici mogu uređivati karte</b>. (<a href="http://wiki.openstreetmap.org/wiki/Anonymous_edits">saznajte zašto</a>).<ul> <li>Vaša e-mail adrea neće biti otkrivena ako postanete javni.</li> <li>Ova akcija se ne može poništiti i svi novi korisnici su postavljeni kao javni.</li> </ul>
+      replace image: Zamijeniti trenutnu sliku
+      return to profile: Vratiti se na profil
+      save changes button: Sačuvati promjene
+      title: Urediti korisnički račun
+      update home location on click: Osvježiti matičnu lokaciju kada kliknem na kartu?
+    confirm: 
+      already active: Ovaj račun je već potvrđen.
+      before you start: Znamo da ste vjerovatno u žurbi za početak unošenja podataka na kartu, ali prije nego što počnete, možda bi željeli ispuniti neke dodatne informacije o sebi u donji obrazac.
+      button: Potvrditi
+      heading: Potvrditi korisnički  račun
+      press confirm button: Pritisnuti potvrditi da bi aktivirali Vaš korisnički račun.
+      reconfirm: Ako je prošlo neko vrijeme otkad ste se prijavili možda ćete morati <a href="%{reconfirm}">poslati sebi novi e-mail potvrde</a> .
+      success: Vaš račun je potvrđen, hvala na uključenju!
+      unknown token: Izgleda da ta značka ne postoji.
+    confirm_email: 
+      button: Potvrditi
+      failure: E-mail adresa je već potvrđena sa ovom značkom.
+      heading: Potvrditi promjenu e-mail adrese.
+      press confirm button: Pritsnite potvrdno dugme ispod da bi ste potvrdili novu e-mail adresu.
+      success: Vaša e-mail adresa je potvrđena, hvala na uključenju!
+    confirm_resend: 
+      failure: Korisnik %{name} nije pronađen.
+      success: Poslali smo novu potvrdu na e-mail %{email} a čim potvrdite svoj račun, moći ćete početi mapirati.<br /><br />Ako koristite antispam sistem koji šalje potvrdu zahtjeva, molimo Vas da provjerite jeli webmaster@openstreetmap.org na tzv. "bijeloj listi", jer nismo u mogućnosti odgovarati na potvrde zahtjeva.
+    filter: 
+      not_an_administrator: Trebate biti moderator da bi izvršili tu radnju.
+    go_public: 
+      flash success: Sva Vaša uređivanja su sada javna i sada vam je dozvoljeno uređivanje.
+    list: 
+      confirm: Potvrditi odabrane korisnike
+      empty: Nisu pronađeni odgovarajući korisnici.
+      heading: Korisnici
+      hide: Sakriti odabrane korisnike
+      showing: 
+        one: Prikaz stranice %{page} (%{first_item} od %{items})
+        other: Prikaz stranice %{page} (%{first_item}-%{last_item} od %{items})
+      summary: "%{name} napravljeno sa %{ip_address} dana %{date}"
+      summary_no_ip: "%{name} napravljeno %{date}"
+      title: Korisnici
+    login: 
+      account is suspended: Žao nam je, Vaš račun je suspendovan zbog sumnjive aktivnosti.<br />Molimo kontaktirajte <a href="%{webmaster}">webmastera</a> uoliko želite da diskutujete o ovome.
+      account not active: Žao nam je, Vaš korisnički račun još nije aktivan. <br /> Molimo vas da koristite poveznicu u e-mailu potvrde da bi ste aktivirali svoj račun, ili <a href="%{reconfirm}">zatražiti novi e-mail potvrde</a> .
+      auth failure: Žao mi je, ne možemo Vas prijaviti sa ovim detaljima.
+      create account minute: Otvorite korisnički račun. To traje samo minutu.
+      email or username: "E-mail adresa ili korisničko ime:"
+      heading: Prijava
+      login_button: Prijava
+      lost password link: Izgubili ste lozinku?
+      new to osm: Novi na OpenStreetMap?
+      no account: Nemate korisničko ime?
+      openid: "%{logo} OtvoreniID:"
+      openid invalid: Žao nam je, čini se da je Vaš OpenID zlonamjeran
+      openid missing provider: Žao nam je, ne možemo kontaktirati Vaš provider za OtvoreniID
+      openid_logo_alt: Prijavite se sa OtvorenimID
+      openid_providers: 
+        aol: 
+          alt: Prijavite se sa AOL OtvorenimID
+          title: Prijavite se sa AOL računom
+        google: 
+          alt: Prijavite se sa Google OtvorenimID
+          title: Prijavite se sa Google računom
+        myopenid: 
+          alt: Prijavite se sa MojOtvoreniID OtvorenimID
+          title: Prijavite se sa MojOtvoreniID
+        openid: 
+          alt: Prijavite se sa URL OtvorenogID
+          title: Prijavite se sa otvorenimID
+        wordpress: 
+          alt: Prijavite se sa Wordpress OtvorenimID
+          title: Prijavite se sa Wordpress računom
+        yahoo: 
+          alt: Prijavite se sa Yahoo OtvorenimID
+          title: Prijavite se sa Yahoo računom
+      password: "Lozinka:"
+      register now: Registrirajte se sada
+      remember: Zapamti me
+      title: Prijava
+      to make changes: Da bi ste mijenjali OpenStreetMap podatke, morate imati korisnički račun.
+      with openid: "Alternativno molimo koristite Vaš OtvoreniID da se prijavite:"
+      with username: "Već imate račun OpenStreetMap? Molimo prijavite se sa Vašim korisničkim imenom i lozinkom:"
+    logout: 
+      heading: Odjava iz OpenStreetMap
+      logout_button: Odjava
+      title: Odjava
+    lost_password: 
+      email address: "E-mail adresa:"
+      heading: Zaboravljena lozinka?
+      help_text: Unesite e-mail adresu koju ste koristili za otvaranje računa i poslaćemo Vam link kojim možete ponovo postaviti lozinku.
+      new password button: Ponovno postavljanje lozinke
+      notice email cannot find: Ne možemo pronaći tu e-mail adresu, žao nam je.
+      notice email on way: Žao mi je što ste je izgubili :-( ali e-mail je na putu tako da je možete pononvo postaviti uskoro.
+      title: Izgubljena lozinka
+    make_friend: 
+      already_a_friend: Već ste prijatelj sa %{name}.
+      button: Dodati kao prijatelja
+      failed: Žao mi je, nije uspjelo dodavanje %{name} kao prijatelja.
+      heading: Dodati %{user} kao prijatelja?
+      success: "%{name}  je sada Vaš prijatelj."
+    new: 
+      confirm email address: "Potvrditi e-mail adresu:"
+      confirm password: "Potvrditi lozinku:"
+      contact_webmaster: Molimo kontaktirajte <a href="mailto:webmaster@openstreetmap.org">webmastera</a> da omogući stvaranje korisničkog računa - pokušaćemo se pozabaviti sa ovim u najkraćem mogućem vremenu.
+      continue: Nastaviti
+      display name: "Ime za prikaz:"
+      display name description: Vaše javno prikazano korisničko ime. Možete ga promjeniti kasnije u postavkama.
+      email address: "E-mail adresa:"
+      fill_form: Ispunite formular i ubrzo ćete dobtiti em-ail za aktivaciju Vašeg korisničkog računa.
+      flash create success message: Hvala što ste se prijavili! Poslali smo vam e-mail za potvrdu na %{email}, i čim potvrdite svoj račun možete  mapirati<br /><br />Ako koristite antispam sistem koji šalje potvrde zahtjeva budite sigurni da je na tzv. "bijeloj listi" webmaster@openstreetmap.org jer nećemo moći odgovoriti na potvrdni zahtjev.
+      flash welcome: Hvala sa registraciju. Poslali smo poruku dobrodošlice na %{email} sa nekim savjetima kako se uključiti.
+      heading: Napravite korisnički račun
+      license_agreement: Kada potvrdite Vaš račun moraćete pristati na <a href="http://www.osmfoundation.org/wiki/License/Contributor_Terms">Uslove doprinosioca</a> .
+      no_auto_account_create: Nažalost nismo u mogućnosti automatski otvarati korisničke račune.
+      not displayed publicly: Nije javno prikazano (vidi <a href="http://wiki.openstreetmap.org/wiki/Privacy_Policy" title="wiki privacy policy including section on email addresses">politiku privatnosti</a>)
+      openid: "%{logo} OtvoreniID:"
+      openid association: "<p>Vaš OtvoreniID još nije povezan sa OpenStreetMap računom.</p>\n<ul>\n  <li>Ukoliko ste novi na projektu OpenStreetMap, molimo napravite novi račun korištenjem formulara ispod.</li>\n  <li>\n    Ukoliko već imate račun, možete se prijaviti na Vaš račun korištenjem Vašeg korisničkog imena i lozinke i onda uvezati račun sa Vašim OtvorenimID u Vašim korisničkim podešavanjima.\n  </li>\n</ul>"
+      openid no password: Lozinka nije potrebna sa OtvorenimID, ali neki ekstra alati ili server je još uvijek mogu trebati.
+      password: "Lozinka:"
+      terms accepted: Hvala za prihvatanje novih uslova za doprinosioce!
+      terms declined: Žao nam je da ste odlučili ne prihvatiti nove Uslove za doprinosioce. Za više informacije, molimo pogledajte <a href="%{url}">ovu wiki stranicu</a>.
+      title: Napravite račun
+      use openid: Kao alternativu, koristite %{logo} OtvoreniID za prijavu
+    no_such_user: 
+      body: Žao mi je, ne postoji korisnik sa imenom %{user}. Molimo provjerite Vaš unos ili da je poveznica na koju ste kliknuli ispravana.
+      heading: Korisnik %{user} ne postoji
+      title: Taj korisnik ne postoji.
+    popup: 
+      friend: Prijatelj
+      nearby mapper: Obližnji maper
+      your location: Vaša lokacija
+    remove_friend: 
+      button: Ukloniti kao prijatelja
+      heading: Ukloniti %{user} kao prijatelja?
+      not_a_friend: "%{name} nije jedan od Vaših prijatelja."
+      success: "%{name} je uklonjen iz prijatelja."
+    reset_password: 
+      confirm password: "Potvrditi lozinku:"
+      flash changed: Vaša lozinka je promjenjena.
+      flash token bad: Niste pronašli tz značku, možda da provjerite URL?
+      heading: Ponovno postavljanje lozinke za %{user}
+      password: "Lozinka:"
+      reset: Ponovno postavljanje lozinke
+      title: Ponovno postavljanje lozinke
+    set_home: 
+      flash success: Matična lokacija uspješno snimljena.
+    suspended: 
+      body: "<p>\n Žao nam je, Vaš je račun automatski suspendiran zbog \n sumnjive aktivnosti. \n</p>\n<p>\n Ova odluka će biti pregledana od strane administratora uskoro, ili \nse možete obratiti %{webmaster}, ako želite razgovarati o tome. \n</p>"
+      heading: Račun suspendiran
+      title: Račun suspendiran
+      webmaster: webmaster
+    terms: 
+      agree: Slažem se
+      consider_pd: Osim gore navedenog ugovora, smatram da su moji doprinosi u javnom vlasništvu (Public Domain)
+      consider_pd_why: Šta je ovo?
+      decline: Odbiti
+      guidance: "Informacija da bi se pomoglo u shvatanju ovih uslova: <a href=\"%{summary}\">Čitljiv sažetak</a> i neki <a href=\"%{translations}\">neformalni prevodi</a>"
+      heading: Uslovi za doprinosioce
+      legale_names: 
+        france: Francuska
+        italy: Italija
+        rest_of_world: Ostatak svijeta
+      legale_select: "Molimo odaberite Vašu zemlju prebivališta:"
+      read and accept: Molimo Vas da pročitate ugovor ispod i  dapritisnete dugme za potvrdu da prihvatate uslove ovog sporazuma za Vaše postojeće i buduće doprinose.
+      title: Uslovi za doprinosioce
+      you need to accept or decline: Molimo pročitajte i onda ili prihvatite ili odbijte nove Uslove za doprinosioce da bi ste nastavili.
+    view: 
+      activate_user: aktivirati ovog korisnika
+      add as friend: dodati kao prijatelja
+      ago: (%{time_in_words_ago} prije)
+      block_history: prikazati dobivene blokade
+      blocks by me: Blokade koje sam postavio/la
+      blocks on me: Blokade na mene
+      comments: komentari
+      confirm: Potvrditi
+      confirm_user: potvrditi ovog korisnika
+      create_block: blokirati ovog korisnika
+      created from: "Napravljeno iz:"
+      ct accepted: Prihvaćeno prije %{ago}
+      ct declined: Odbijeno
+      ct status: "Uslovi za doprinosioce:"
+      ct undecided: Neodlučen
+      deactivate_user: deaktivirati ovog korisnika
+      delete_user: izbrisati ovog korisnika
+      description: Opis
+      diary: dnevnik
+      edits: uređivanja
+      email address: "E-mail adresa:"
+      friends_changesets: Pretražiti sve setove promjena prijatelja
+      friends_diaries: Pretražiti sve unose u dnevnik od prijatelja
+      hide_user: sakriti ovog korisnika
+      if set location: Ako namjestite Vašu lokaciju, zgodna karta i stvari će se pojaviti ovdje. Možete namjestiti lokaciju Vašeg boravišta na %{settings_link} stranici.
+      km away: korisnik udaljen %{count}km
+      latest edit: "Najnovija izmjena %{ago}:"
+      m away: "%{count}m daleko"
+      mapper since: "Maper od:"
+      moderator_history: prikazati date blokade
+      my comments: Moji komentari
+      my diary: Moj dnevnik
+      my edits: Moje promjene
+      my settings: Moja podešavanja
+      my traces: Moje trase
+      nearby users: Drugi obližnji korisnici
+      nearby_changesets: Pretražiti sve setove promjena obližnjih korisnika
+      nearby_diaries: Pretražiti sve unnose u dnevnik od obližnjih korisnika
+      new diary entry: Novi unos u dnevnik
+      no friends: Niste dodali nijednog prijatelja.
+      no nearby users: Još uvijek nema drugih korisnika koji ucrtavaju na kartu u blizini.
+      oauth settings: Oauth podešavanja
+      remove as friend: ukloniti kao prijatelja
+      role: 
+        administrator: Ovaj korisnik je administrator
+        grant: 
+          administrator: Dodjeliti pristup za administratora
+          moderator: Dodjeliti pristup za moderatora
+        moderator: Ovaj korisnik je moderator
+        revoke: 
+          administrator: Opozvati pristup za administatora
+          moderator: Opozvati pristup za moderatora
+      send message: Poslati poruku
+      settings_link_text: postavke
+      spam score: "Spam ocjena:"
+      status: "Stanje:"
+      traces: trase
+      unhide_user: otkriti ovog korisnika
+      user location: Lokacija boravišta korisnika
+      your friends: Vaši prijatelji
+  user_block: 
+    blocks_by: 
+      empty: "%{name} nije napravio/la još nijednu blokadu."
+      heading: Lista blokada od %{name}
+      title: Blokade od %{name}
+    blocks_on: 
+      empty: "%{name} nije još bio blokiran."
+      heading: Lista blokada na %{name}
+      title: Blokade na %{name}
+    create: 
+      flash: Napraviti blokadu na korisnika  %{name}.
+      try_contacting: Molimo da pokušate kontaktirati korisnika prije blokiranja i dati mu razumno vrijeme za odgovor.
+      try_waiting: Molimo da date korisniku razumno vrijeme da odgovori prije nego ga blokirate.
+    edit: 
+      back: Pogledati sve blokade
+      heading: Uređivanje blokade na %{name}
+      needs_view: Da li se korisnik mora prijaviti prije nego se očisti blokada?
+      period: Koliko dugo, od sada, će korisnik biti blokiran od strane API.
+      reason: Razlog zašto je %{name} blokiran. Molimo da budete mirni i razumni što je više je moguće, dajući što više detalja o situaciji. Imajte na umu da svi korisnici ne razumiju žargon u zajednici, pa im objasnite jednostavnim jezikom.
+      show: Pogledati ovu blokadu
+      submit: Osvježiti blokadu
+      title: Uređivanje blokade na %{name}
+    filter: 
+      block_expired: Blokada je već istekla i ne može se uređivati.
+      block_period: Period blokade mora biti jedna od vrijednosti iz drop-down liste.
+    helper: 
+      time_future: Završava u %{time}.
+      time_past: Završeno prije %{time}.
+      until_login: Aktivno dok se korsnik ne prijavi.
+    index: 
+      empty: Blokade još nisu napravljene
+      heading: Lista blokada korisnika
+      title: Blokade korisnika
+    model: 
+      non_moderator_revoke: Morate biti moderator da opozovete blokadu.
+      non_moderator_update: Morate biti moderator da napravite ili osvježite blokadu.
+    new: 
+      back: Pogledati sve blokade
+      heading: Pravljenjen blokade na %{name}
+      needs_view: Korisnik se mora prijaviti da bi se blokada očistila
+      period: Koliko dugo, od sada, će korisnik biti blokiran od strane API.
+      reason: Razlog zašto je %{name} blokiran. Molim da budete mirni i razumni što je više je moguće, dajući što više detalja o situaciji, s time da će poruka biti javno vidljiva. Imajte na umu da svi korisnici ne razumiju žargon u zajednici, pa im objasnite jednostavnim jezikom.
+      submit: Napraviti blokadu
+      title: Pravljenje blokade na %{name}
+      tried_contacting: Kontaktirao sam korisnika i pitao da prestane.
+      tried_waiting: Dao sam dovoljno razumnog vremena korisniku da odgovori.
+    not_found: 
+      back: Nazad na index
+      sorry: Žao mi je, korinička blokada sa ID %{id} se ne može naći.
+    partial: 
+      confirm: Da li ste sigurni?
+      creator_name: Kreator
+      display_name: Blokirani korisnik
+      edit: Urediti
+      next: Sljedeća »
+      not_revoked: (nije opozvano)
+      previous: « Prethodna
+      reason: Razlog za blokadu
+      revoke: Opozvati!
+      revoker_name: Opozvano od strane
+      show: Pokazati
+      showing_page: Prikaz stranice %{page}
+      status: Stanje
+    period: 
+      one: 1 sat
+      other: "%{count} sata/i"
+    revoke: 
+      confirm: Jeste li sgurni da želite opozvati ovu blokadu?
+      flash: Ova blokada je opozvana.
+      heading: Opoziv blokade na %{block_on} od %{block_by}
+      past: Blokada je završila prije %{time} i ne može se opozvati sada.
+      revoke: Opozvati!
+      time_future: Blokada će završiti za %{time}.
+      title: Opoziv blokade na %{block_on}
+    show: 
+      back: Pogledati sve blokade
+      confirm: Da li ste sigurni?
+      edit: Urediti
+      heading: "%{block_on} blokiran od strane %{block_by}"
+      needs_view: Korisnik se mora prijaviti prije nego se blokada očisti.
+      reason: Razlog za blokadu
+      revoke: Opozvati!
+      revoker: "Opozivalac:"
+      show: Pokazati
+      status: Stanje
+      time_future: Završava u %{time}
+      time_past: Završeno prije %{time}
+      title: "%{block_on} blokiran od strane %{block_by}"
+    update: 
+      only_creator_can_edit: Samo moderator koji je napravio blokadu ovo može urediti.
+      success: Blokada osvježena
+  user_role: 
+    filter: 
+      already_has_role: Korisnik već ima ulogu %{role}.
+      doesnt_have_role: Korisnik nema ulogu %{role}.
+      not_a_role: String `%{role}' nije valjana uloga.
+      not_an_administrator: Samo administratori mogu upravljati ulogama korisnika, a vi niste administrator.
+    grant: 
+      are_you_sure: Jeste li sigurni da želite dodjeliti ulogu `%{role}' korisniku `%{name}'?
+      confirm: Potvrditi
+      fail: Ne možemo dodjeliti ulogu `%{role}' korisniku `%{name}'. Molimo provjeriti ispravnost i korisnika i uloge.
+      heading: Potvrditi dodjelu uloge
+      title: Potvrditi dodjelu uloge
+    revoke: 
+      are_you_sure: Jeste li sigurni da želite opozvati ulogu `%{role}' korisnika `%{name}'?
+      confirm: Potvrditi
+      fail: Ne možemo opozvati ulogu `%{role}' korisniku `%{name}'. Molimo provjeriti ispravnost i korisnika i uloge.
+      heading: Potvrditi opoziv uloge
+      title: Potvrditi opoziv uloge
index 77d7f09416da1c0a33484dcc3d91f949d0b2fc88..ffd9b4aa584c0258e4595315fda18cafdfce2ae8 100644 (file)
@@ -208,14 +208,10 @@ ca:
         node: Node
         relation: Relació
         way: Via
-    start: 
-      manually_select: Sel·leccioneu una altra àrea manualment
-      view_data: Visualitza la informació per a la vista del mapa actual
     start_rjs: 
       data_frame_title: Informació
       data_layer_name: Explora les dades del mapa
       details: Detalls
-      drag_a_box: Marqueu un rectangle al mapa per a seleccionar una àrea
       edited_by_user_at_timestamp: Editat per  %{user} el %{timestamp}
       hide_areas: Oculta les zones
       history_for_feature: Historial per a %{feature}
@@ -332,8 +328,9 @@ ca:
       hide_link: Amaga aquest comentari
     diary_entry: 
       comment_count: 
-        one: 1 comentari
+        one: "%{count} comentari"
         other: "%{count} comentaris"
+        zero: Sense comentaris
       comment_link: Comenta aquesta entrada
       confirm: Confirma
       edit_link: Edita aquesta entrada
@@ -681,6 +678,7 @@ ca:
           reservoir_watershed: Embassament de conca
           residential: Àrea residencial
           retail: Al detall
+          road: Zona de carretera
           village_green: Zona verda
           vineyard: Vinya
           wetland: Aiguamoll
index d7e5d6d571142bf49646247f8e5837ab24601fe8..da0fa51c9fe80eecbccf06c9083d144b2436c8ec 100644 (file)
@@ -208,14 +208,10 @@ cs:
         node: Uzel
         relation: Relace
         way: Cesta
-    start: 
-      manually_select: Ručně vybrat jinou oblast
-      view_data: Ukázat data k zobrazené mapě
     start_rjs: 
       data_frame_title: Data
       data_layer_name: Procházet mapová data
       details: Detaily
-      drag_a_box: Myší na mapě označte zvolenou oblast
       edited_by_user_at_timestamp: Upravil %{user} dne %{timestamp}
       hide_areas: Schovat oblasti
       history_for_feature: Historie pro %{feature}
index fd7601125749ce2278c1e2d9272f52e366fa8dfb..fad7e97bd3c07a7bf11d166faef46ff329c0cebe 100644 (file)
@@ -203,14 +203,10 @@ da:
         node: Punkt
         relation: Relation
         way: Vej
-    start: 
-      manually_select: Vælg et andet område manuelt
-      view_data: Vis data for nuværende kortvisning
     start_rjs: 
       data_frame_title: Data
       data_layer_name: Gennemse kortdata
       details: Detaljer
-      drag_a_box: Træk en kasse på kortet for at vælge et område
       edited_by_user_at_timestamp: Redigeret af %{user}, %{timestamp}
       hide_areas: Skjul områder
       history_for_feature: Historik for %{feature}
index 007850fd348b77138da65914353c605a26d4f7ad..9861997354b89e43f30052fc9bc8facfcfeb1be4 100644 (file)
@@ -218,14 +218,10 @@ de:
         node: Knoten
         relation: Relation
         way: Weg
-    start: 
-      manually_select: Einen anderen Kartenausschnitt manuell auswählen
-      view_data: Daten des aktuellen Kartenausschnitts anzeigen
     start_rjs: 
       data_frame_title: Daten
       data_layer_name: Kartendaten durchsuchen
       details: Details
-      drag_a_box: Einen Rahmen über die Karte aufziehen, um einen Bereich auszuwählen
       edited_by_user_at_timestamp: Bearbeitet von %{user} am %{timestamp}
       hide_areas: Gebiete ausblenden
       history_for_feature: Chronik für %{feature}
index bd2927c5823c1cddc6ae7f1e7f663f13b382181d..ff80a5f5c7f61d6117f29802ebda71d7efe87865 100644 (file)
@@ -754,7 +754,7 @@ diq:
   user: 
     account: 
       contributor terms: 
-        link text: No çıko?
+        link text: no çıko?
       current email address: "E-postay şımaya newki:"
       home location: "Herun:"
       image: "Resim:"
@@ -765,14 +765,14 @@ diq:
       new image: Resim deke
       openid: 
         link: http://wiki.openstreetmap.org/wiki/OpenID
-        link text: No çıko?
+        link text: no çıko?
         openid: "OpenID:"
       preferred editor: "Editorê ke tercih kerê:"
       preferred languages: "Zıwanê ke tercih kerê:"
       public editing: 
         disabled link text: Ezo çı nêşena bıvurna?
         enabled link: http://wiki.openstreetmap.org/wiki/Anonymous_edits
-        enabled link text: No çıko?
+        enabled link text: no çıko?
         heading: "Pêron rê akerde vurnayış:"
       public editing note: 
         heading: "Pêron rê akerde vurnayış:"
@@ -829,7 +829,7 @@ diq:
       logout_button: Bıvciyê
       title: Bıvciyê
     lost_password: 
-      email address: "Adresê e-posta:"
+      email address: "Adresa e-postey:"
       heading: To parola ke xo vira?
       new password button: Parola reset ke
       title: Parolaya vınibyayi
@@ -840,7 +840,7 @@ diq:
       confirm password: "Parolay tesdiq ke:"
       continue: Dewam ke
       display name: "Nameyo ke Aseno:"
-      email address: "Adresê e-posta:"
+      email address: "Adresa e-postey:"
       openid: "%{logo} OpenID:"
       password: "Parola:"
       use openid: ALternativey, %{logo} OpenID ra karfiye
@@ -890,7 +890,7 @@ diq:
       description: Şınasnayış
       diary: rocek
       edits: vurnayışi
-      email address: "Adresê e-posta:"
+      email address: "Adresa e-postey:"
       hide_user: nê karberi  bınımnê
       km away: "%{count} km duriyo"
       latest edit: "vurnayışê peyênê %{ago}:"
index 82b09781323efc526f47def5d6d0916dfbf491bb..632ad330dab0f62c4d4a113325bdacc9504fd76c 100644 (file)
@@ -199,14 +199,10 @@ dsb:
         node: Suk
         relation: Relacija
         way: Puś
-    start: 
-      manually_select: Drugi wurězk manuelnje wubraś
-      view_data: Daty aktualnego kórtowego wurězka zwobrazniś
     start_rjs: 
       data_frame_title: Daty
       data_layer_name: Kórtowe daty pśepytaś
       details: Drobnostki
-      drag_a_box: Wobłuk nad kórtu rozćěgnuś, aby se wurězk wubrał
       edited_by_user_at_timestamp: Wobźěłany wót %{user} %{timestamp}
       hide_areas: Wobcerki schowaś
       history_for_feature: Historija za %{feature}
index 9782165077a33498b57f4dc2471642db0a82a602..19fa1082a49096439b39aaf1af1b56a5f6642042 100644 (file)
@@ -192,14 +192,10 @@ el:
         node: Κόμβος
         relation: Σχέση
         way: Διαδρομή
-    start: 
-      manually_select: Χειροκίνητη επιλογή διαφορετικής περιοχής
-      view_data: Προβολή δεδομένων για την τρέχουσα προβολή χάρτη
     start_rjs: 
       data_frame_title: Δεδομένα
       data_layer_name: Περιήγηση Δεδομένων Χάρτη
       details: Λεπτομέρειες
-      drag_a_box: Σύρε ένα πλαίσιο στο χάρτη για να επιλέξεις μια περιοχή
       edited_by_user_at_timestamp: Επεξεργάστηκε από τον %{user} στις %{timestamp}
       hide_areas: Απόκρυψη περιοχών
       history_for_feature: Ιστορικό για %{feature}
index ffd180d363462201d19e817acabadd206dac8fe3..806b9ddd3df1143b0028ea7231ef320db0c79e8f 100644 (file)
@@ -221,15 +221,12 @@ en:
       relation_title: "Relation: %{relation_name}"
       download_xml: "Download XML"
       view_history: "View history"
-    start:
-      view_data: "View data for current map view"
-      manually_select: "Manually select a different area"
     start_rjs:
       notes_layer_name: "Browse Notes"
       data_layer_name: "Browse Map Data"
       data_frame_title: "Data"
       zoom_or_select: "Zoom in or select an area of the map to view"
-      drag_a_box: "Drag a box on the map to select an area"
+      view_data: "View data for current map view"
       manually_select: "Manually select a different area"
       hide_areas: "Hide areas"
       show_areas: "Show areas"
index 851e145a4bfe39dbf96a9569ee1a7198e987904b..fa1190a70e91bbda8d79f3de180e38a84758d19e 100644 (file)
@@ -183,14 +183,10 @@ eo:
         node: Nodo
         relation: Rilato
         way: Vojo
-    start: 
-      manually_select: Mane elekti alian areon
-      view_data: Vidi datumojn por la naŭa mapvidon
     start_rjs: 
       data_frame_title: Datumo
       data_layer_name: Datumo
       details: Detaloj
-      drag_a_box: Desegnu skatolon sur la mapo por elekti areon
       edited_by_user_at_timestamp: Redaktita de %{user} je %{timestamp}
       hide_areas: Kaŝi areojn
       history_for_feature: Historio por %{feature}
index 1f8d1cffc3ca243c6da2d75a20312709263c006a..4a1f851be5faccc979fcac4b0e692d97f8927157 100644 (file)
@@ -208,14 +208,10 @@ es:
         node: Nodo
         relation: Relación
         way: Vía
-    start: 
-      manually_select: Seleccionar manualmente un área diferente
-      view_data: Ver datos para el encuadre actual
     start_rjs: 
       data_frame_title: Datos
       data_layer_name: Examinar datos del mapa
       details: Detalles
-      drag_a_box: Arrastre en el mapa para dibujar un área de encuadre
       edited_by_user_at_timestamp: Editado por %{user} el %{timestamp}
       hide_areas: Ocultar áreas
       history_for_feature: Historial de %{feature}
index 72e6906993e59c63191359f17224e58d7f0a9de8..3b43727918f3b4f1221805086248a14fb12fb9c1 100644 (file)
@@ -145,13 +145,10 @@ et:
         node: Sõlm
         relation: relatsioon
         way: joon
-    start: 
-      manually_select: Vali käsitsi teine ala
     start_rjs: 
       data_frame_title: Andmed
       data_layer_name: Andmed
       details: Detailid
-      drag_a_box: Märgi kaardil hiirega uus ala
       edited_by_user_at_timestamp: Viimati muudetud kasutaja %{user} poolt kell %{timestamp}
       hide_areas: Peida alad
       history_for_feature: Omaduse %{feature} ajalugu
index ea2f6f667c6da043364febf79ed61da487045c69..70f969a10a2d6dd16227d258d2dc80d60e895e2b 100644 (file)
@@ -176,14 +176,10 @@ fa:
         node: گره
         relation: ارتباط
         way: راه
-    start: 
-      manually_select: به صورت دستی منطقه دیگری را انتخاب کنید
-      view_data: مشاهده اطلاعات برای نمایش نقشه فعلی
     start_rjs: 
       data_frame_title: داده ها
       data_layer_name: داده ها
       details: جزئیات
-      drag_a_box: " جعبه‌ای را روی نقشه برای انتخاب یک منطقه بکشید"
       edited_by_user_at_timestamp: "%{timestamp} در %{user} ویرایش توسط"
       hide_areas: پنهان‌کردن منطقه‌ها
       history_for_feature: "%{feature} تاریخچه برای"
index 6a2ca9b290c7be66eef6f4b87038aa215e3e4d8f..3bd7a3e51a2585c988b5c09a2430f596dcee3c30 100644 (file)
@@ -114,8 +114,8 @@ fi:
         one: "Sisältää seuraavan relaation:"
         other: "Sisältää seuraavat %{count} relaatiota:"
       has_ways: 
-        one: "Sisältää seuraavan viivan:"
-        other: "Sisältää seuraavat %{count} viivaa:"
+        one: "Sisältää seuraavan polun:"
+        other: "Sisältää seuraavat %{count} polkua:"
       no_bounding_box: Tässä muutoskokoelmassa ei ole rajattua aluetta.
       show_area_box: Näytä rajattu alue
     common_details: 
@@ -206,14 +206,10 @@ fi:
         node: Piste
         relation: Relaatio
         way: Polku
-    start: 
-      manually_select: Valitse pienempi alue
-      view_data: Näytä tiedot nykyisestä karttanäkymästä
     start_rjs: 
       data_frame_title: Tiedot
       data_layer_name: Selaa karttatietoja
       details: Tarkemmin
-      drag_a_box: Valitse alue kartalta hiirellä maalaamalla
       edited_by_user_at_timestamp: Viimeinen muokkaaja %{user} %{timestamp}
       hide_areas: Piilota alueet
       history_for_feature: Ominaisuuden %{feature} historia
@@ -264,8 +260,8 @@ fi:
       way_title: "Polku: %{way_name}"
     way_details: 
       also_part_of: 
-        one: on myös osana viivaa %{related_ways}
-        other: on myös osana viivoja %{related_ways}
+        one: on myös osana polkua %{related_ways}
+        other: on myös osana polkuja %{related_ways}
       nodes: "Pisteet:"
       part_of: "Relaatiojäsenyydet:"
     way_history: 
@@ -331,7 +327,7 @@ fi:
     diary_entry: 
       comment_count: 
         one: 1 kommentti
-        other: "%{count} kommenttia"
+        other: zerp=Ei kommentteja
       comment_link: Kommentoi tätä kirjoitusta
       confirm: Vahvista
       edit_link: Muokkaa päiväkirjamerkintää
@@ -1165,7 +1161,7 @@ fi:
       confirm: "Varmista, että tämän käyttäjätunnuksen on luonut sinä. Jos et ole luonut tätä käyttäjätiliä, poista se napsauttamalla tätä linkkiä:"
       created: Joku (toivottavasti sinä) on luonut käyttäjätunnuksen osoitteessa %{site_url}.
       greeting: Hei!
-      subject: "[OpenStreetMap] Sähköpostiosoitteen vahvistus"
+      subject: "[OpenStreetMap] Tervetuloa OpenStreetMap:iin"
       welcome: Tervetuloa OpenStreetMap-karttapalveluun! Haluaisimme kertoa joitain hyödyllisiä asioita, jotta pääset alkuun.
     signup_confirm_html: 
       ask_questions: OpenStreetMapista voi esittää kysymyksiä <a href="http://help.openstreetmap.org/">kysymyksiä ja vastauksia -sivulla</a>.
@@ -1578,6 +1574,9 @@ fi:
       empty: Käyttäjiä ei löytynyt.
       heading: Käyttäjät
       hide: Piilota valitut käyttäjät
+      showing: 
+        one: Näytetään sivu %{page} (%{first_item}/%{items})
+        other: Näytetään sivu %{page} (%{first_item}-%{last_item} / %{items})
       title: Käyttäjät
     login: 
       account is suspended: Valitettavasti käyttäjätilisi on jäädytetty epäilyttävän toiminnan seurauksena.<br />Ole hyvä ja ota yhteyttä <a href="%{webmaster}">webmasteriin</a> jos haluat keskustella tästä.
index 7fdb5cf7e4a2b39a8970c5978426851436b0f35e..fa77d7879ea7315b800534dc6a8e4fc98a1922c8 100644 (file)
@@ -219,14 +219,10 @@ fr:
         node: Nœud
         relation: Relation
         way: Chemin
-    start: 
-      manually_select: Sélectionner manuellement une zone différente
-      view_data: Voir les données sur la carte actuelle
     start_rjs: 
       data_frame_title: Données
       data_layer_name: Parcourir les données de la carte
       details: Détails
-      drag_a_box: Cliquez et tirez un cadre sur la carte pour sélectionner une zone
       edited_by_user_at_timestamp: Modifié par %{user} le %{timestamp}
       hide_areas: Masquer les zones
       history_for_feature: Historique pour %{feature}
index 693956ed870a5f2cb4af29f9affaf1ec5915facf..7430f2fba0fac5dc8e1d16e197d425e7b2efe283 100644 (file)
@@ -165,14 +165,10 @@ fur:
         node: Grop
         relation: Relazion
         way: Vie
-    start: 
-      manually_select: Sielç a man une aree divierse
-      view_data: Viôt i dâts pe viodude atuâl de mape
     start_rjs: 
       data_frame_title: Dâts
       data_layer_name: Dâts
       details: Detais
-      drag_a_box: Disegne un retangul su la mape par sielzi une aree
       edited_by_user_at_timestamp: Cambiât di %{user} ai %{timestamp}
       hide_areas: Plate areis
       history_for_feature: Storic par %{feature}
index f198fa73894cc37352988cac0c358b7abae7949b..51666f8115347f8680935232dc68e7b6d6eea344 100644 (file)
@@ -195,14 +195,10 @@ gl:
         node: Nodo
         relation: Relación
         way: Camiño
-    start: 
-      manually_select: Escoller manualmente unha zona distinta
-      view_data: Ver os datos para a vista do mapa actual
     start_rjs: 
       data_frame_title: Datos
       data_layer_name: Explorar os datos do mapa
       details: Detalles
-      drag_a_box: Arrastre unha caixa sobre o mapa para escoller unha zona
       edited_by_user_at_timestamp: Editado por %{user} o %{timestamp}
       hide_areas: Agochar as zonas
       history_for_feature: Historial de %{feature}
index 8af4f699e7e83dd844bfd924a6b966eec8f8b016..4d0977ba1f7311ac6931bf2594569dcb148176e5 100644 (file)
@@ -202,14 +202,10 @@ he:
         node: צומת
         relation: יחס
         way: דרך
-    start: 
-      manually_select: בחירה ידנית באזור אחר
-      view_data: לצפייה במידע עבור התצוגה הנוכחית של המפה
     start_rjs: 
       data_frame_title: נתונים
       data_layer_name: עיון בנתוני מפה
       details: פרטים
-      drag_a_box: נא לגרור את התיבה על המפה כדי לבחור אזור
       edited_by_user_at_timestamp: נערך על־ידי %{user} ב־%{timestamp}
       hide_areas: להסתרת אזורים
       history_for_feature: ההיסטוריה של %{feature}
index 5c46fdbfc9cccbebede41424d4db68fc722bfd87..bb5afb7fbccb4bdb9d85f43f09410f4c66c726b9 100644 (file)
@@ -186,14 +186,10 @@ hr:
         node: Točka
         relation: Relacija
         way: Put
-    start: 
-      manually_select: Ručno izaberi drugo područje
-      view_data: Prikaži podatke za trenutni prikaz karte
     start_rjs: 
       data_frame_title: Podaci
       data_layer_name: Podaci
       details: Detalji
-      drag_a_box: Povuci okvir na karti da bi izabrali područje
       edited_by_user_at_timestamp: Uredio %{user} u %{timestamp}
       hide_areas: Sakrij područja
       history_for_feature: Povijest za %{feature}
index a2c830f5e867399f0682e48fb4ee276f995ec323..f41a02574e0d23cc132a9c44cc2c1de44172c477 100644 (file)
@@ -199,14 +199,10 @@ hsb:
         node: Suk
         relation: Relacija
         way: Puć
-    start: 
-      manually_select: Druhi wobłuk manuelnje wubrać
-      view_data: Daty za aktualny kartowy napohlad pokazać
     start_rjs: 
       data_frame_title: Daty
       data_layer_name: Kartowe daty přepytać
       details: Podrobnosće
-      drag_a_box: Ramik na kartu ćahnyć, zo by so wobłuk wubrał
       edited_by_user_at_timestamp: Wobdźěłany wot wužiwarja %{user} dnja %{timestamp}
       hide_areas: Kónčiny schować
       history_for_feature: Historija za %{feature}
index 2e1145ea2845fc8ced66b9d029fd53a79c546e04..504e8009d8f3cd1feda8b4548f99f754a4771718 100644 (file)
@@ -200,14 +200,10 @@ hu:
         node: "Pont:"
         relation: "Kapcsolat:"
         way: "Vonal:"
-    start: 
-      manually_select: Más terület kézi kijelölése
-      view_data: Adatok megtekintése a térkép jelenlegi nézetéhez
     start_rjs: 
       data_frame_title: Adatok
       data_layer_name: Térképadatok böngészése
       details: Részletek
-      drag_a_box: Terület kijelöléséhez rajzolj egy négyzetet a térképen
       edited_by_user_at_timestamp: "%{user} szerkesztette ekkor: %{timestamp}"
       hide_areas: Területek elrejtése
       history_for_feature: "%{feature} előzményei"
index 3f0b79d617eb2a87373beb5b815bdb566008da04..4f6973eabafc320b0caddfe767c92b6dc0761310 100644 (file)
@@ -194,14 +194,10 @@ ia:
         node: Nodo
         relation: Relation
         way: Via
-    start: 
-      manually_select: Seliger manualmente un altere area
-      view_data: Vider datos pro le vista actual del carta
     start_rjs: 
       data_frame_title: Datos
       data_layer_name: Percurrer datos cartographic
       details: Detalios
-      drag_a_box: Designa un quadro super le carta pro seliger un area
       edited_by_user_at_timestamp: Modificate per %{user} le %{timestamp}
       hide_areas: Celar areas
       history_for_feature: Historia de %{feature}
@@ -320,6 +316,7 @@ ia:
       comment_count: 
         one: 1 commento
         other: "%{count} commentos"
+        zero: Nulle commento
       comment_link: Commentar iste entrata
       confirm: Confirmar
       edit_link: Modificar iste entrata
@@ -393,7 +390,7 @@ ia:
       area_to_export: Area a exportar
       embeddable_html: HTML incorporabile
       export_button: Exportar
-      export_details: Le datos de OpenStreetMap es licentiate sub le <a href="http://creativecommons.org/licenses/by-sa/2.0/">licentia Attribution-ShareAlike 2.0 de Creative Commons</a>.
+      export_details: Le datos de OpenStreetMap es licentiate sub le <a href="http://opendatacommons.org/licenses/odbl/1.0/">Open Data Commons Open Database License</a> (ODbL).
       format: "Formato:"
       format_to_export: Formato de exportation
       image_size: "Dimension del imagine:"
@@ -998,6 +995,9 @@ ia:
       text: In caso de un conflicto inter iste pagina traducite e %{english_original_link}, le pagina in anglese prevalera.
       title: A proposito de iste traduction
     legal_babble: 
+      attribution_example: 
+        alt: Exemplo de como recognoscer OpenStreetMap in un pagina web
+        title: Exemplo de recognoscentia
       contributors_at_html: "<strong>Austria</strong>: Contine datos ab le\n   <a href=\"http://data.wien.gv.at/\">Citate de Vienna</a> licentiate sub\n   <a href=\"http://creativecommons.org/licenses/by/3.0/at/deed.de\">CC BY</a>."
       contributors_ca_html: "<strong>Canada</strong>: Contine datos ab\n   GeoBase&reg;, GeoGratis (&copy; Department of Natural\n   Resources Canada), CanVec (&copy; Department of Natural\n   Resources Canada), e StatCan (Geography Division,\n   Statistics Canada)."
       contributors_footer_1_html: "Pro ulterior detalios de iste e altere fontes que ha essite usate\npro adjutar a meliorar OpenStreetMap, vide le <a\nhref=\"\"http://wiki.openstreetmap.org/wiki/Contributors\">pagina\nde contributores</a> in le wiki de OpenStreetMap."
@@ -1009,11 +1009,13 @@ ia:
       contributors_nz_html: "<strong>Nove Zelandia</strong>: Contine datos proveniente de\n   Land Information New Zealand. Crown Copyright reservate."
       contributors_title_html: Nostre contributores
       contributors_za_html: "<strong>Africa del Sud</strong>: Contine datos proveniente del\n   <a href=\"http://www.ngi.gov.za/\">Chief Directorate:\n   National Geo-Spatial Information</a>, copyright del Stato reservate."
-      credit_1_html: "  Si vos usa imagines cartographic de OpenStreetMap, nos requesta que\n  vostre recognoscentia indica al minus &ldquo;&copy; Contributores de\n  OpenStreetMap, CC BY-SA&rdquo;. Si vos usa solmente datos cartographic,\n  nos requesta &ldquo;Datos cartographic &copy; Contributores de OpenStreetMap,\n  CC BY-SA&rdquo;."
-      credit_2_html: "  Si possibile, le parola OpenStreetMap debe esser un hyperligamine a <a\n  href=\"http://www.openstreetmap.org/\">http://www.openstreetmap.org/</a>\n  e le termino CC BY-SA debe ligar a <a\n  href=\"http://creativecommons.org/licenses/by-sa/2.0/\">http://creativecommons.org/licenses/by-sa/2.0/</a>. Si\n  vos usa un medio de communication in le qual le ligamines non es possibile (p.ex. un\n  obra imprimite), nos suggere que vos dirige vostre lectores a\n  www.openstreetmap.org (forsan per inserer iste adresse\n  complete in loco del parola &lsquo;OpenStreetMap&rsquo;) e a\n  www.creativecommons.org."
+      credit_1_html: "Nos require que vos usa le recognoscentia &ldquo;&copy; OpenStreetMap\ncontributors&rdquo;."
+      credit_2_html: Vos debe anque clarificar que le datos es disponibile sub Open Database License, e si vos usa nostre tegulas cartographic, que le cartographia es licentiate sub CC-BY-SA. Vos pote facer isto con un ligamine a <a href="http://www.openstreetmap.org/copyright">iste pagina de copyright</a>. Alternativemente, e obligatorimente si vos distribue OSM in forma de datos, vos pote nominar e ligar directemente al licentia(s). Si vos usa un medio de communication in le qual le ligamines non es possibile (p.ex. un obra imprimite), nos suggere que vos dirige vostre lectores a www.openstreetmap.org (forsan per inserer iste adresse complete in loco del parola &lsquo;OpenStreetMap&rsquo;), a www.opendatacommosn.org, e (si relevante) a www.creativecommons.org.
+      credit_3_html: "Pro un carta electronic navigabile, le recognoscentia debe apparer in le angulo del carta. Per exemplo:"
       credit_title_html: Como dar recognoscentia a OpenStreetMap
       intro_1_html: "   OpenStreetMap es <i>datos aperte</i>, disponibile sub le licentia\n   <a href=\"http://creativecommons.org/licenses/by-sa/2.0/\">Creative\n   Commons Attribution-ShareAlike 2.0</a> (CC BY-SA)."
-      intro_2_html: "  Vos es libere de copiar, distribuer, transmitter e adaptar nostre cartas\n  e datos, a condition que vos da recognoscentia a OpenStreetMap e su\n  contributores. Si vos altera o extende nostre cartas e datos, vos\n  pote distribuer le resultato solmente sub le mesme licentia. Le\n  complete <a\n  href=\"http://creativecommons.org/licenses/by-sa/2.0/legalcode\">codice\n  legal</a> explica vostre derectos e responsabilitates."
+      intro_2_html: "  Vos es libere de copiar, distribuer, transmitter e adaptar nostre cartas\n  e datos, a condition que vos da recognoscentia a OpenStreetMap e su\n  contributores. Si vos altera o extende nostre cartas e datos, vos\n  pote distribuer le resultato solmente sub le mesme licentia. Le\n  complete <a href=\"http://opendatacommons.org/licenses/odbl/1.0/\">codice\n  legal</a> explica vostre derectos e responsabilitates."
+      intro_3_html: "Le cartographia in nostre tegulas de carta, e nostre documentation, son\npublicate sub licentia <a href=\"http://creativecommons.org/licenses/by-sa/2.0/\">Creative\nCommons Attribution-ShareAlike 2.0</a> (CC-BY-SA)."
       more_1_html: "  Lege plus super le uso de nostre datos al <a\n  href=\"http://wiki.openstreetmap.org/wiki/Legal_FAQ\">FAQ\n  Legal</a>."
       more_2_html: "  Nos rememora al contributores de OSM de nunquam adder datos de alcun\n  fonte sub derecto de autor (p.ex. Google Maps o cartas imprimite)\n  sin explicite permission del titulares del derecto de autor."
       more_title_html: Pro saper plus
index 7507ffea4fe50eeb88a70bd53b39ec709dc8e644..e86f6b25774e7c2fabd311a978da44de16d61349 100644 (file)
@@ -190,14 +190,10 @@ id:
         node: Node/Titik
         relation: Relasi
         way: Way/Garis
-    start: 
-      manually_select: Pilih wilayah yang berbeda secara manual
-      view_data: Lihat data untuk tampilan peta saat ini
     start_rjs: 
       data_frame_title: Data
       data_layer_name: Telusuri Data Peta
       details: Rincian
-      drag_a_box: Gambar sebuah kotak pada peta untuk memilih wilayah
       edited_by_user_at_timestamp: Diedit oleh %{user} pada %{timestamp}
       hide_areas: Sembunyikan wilayah
       history_for_feature: Riwayat untuk %{feature}
index d00c2e30be46e6d5403f8e2b4420ca6f710ac5da..45556c0b7237a030cc9e2e4e7354402de45f0b75 100644 (file)
@@ -178,14 +178,10 @@ is:
         node: Hnúturinn
         relation: Venslin
         way: Vegurinn
-    start: 
-      manually_select: Velja svæði á kortinu
-      view_data: Sýna gögn fyrir núverandi kortasýn
     start_rjs: 
       data_frame_title: Gögn
       data_layer_name: Gögn
       details: Nánar
-      drag_a_box: Teiknaðu kassa á kortið til að velja svæði
       edited_by_user_at_timestamp: Breytt af %{user} klukkan %{timestamp}
       history_for_feature: Breytingarskrá fyrir %{feature}
       load_data: Hlaða inn gögnum
index 6ce0f5aaae80e34caf8ac747288e8e037094b081..aea235d7675429e1f0eb8c234bb71bd22c1775e0 100644 (file)
@@ -207,14 +207,10 @@ it:
         node: Nodo
         relation: Relazione
         way: Percorso
-    start: 
-      manually_select: Seleziona manualmente un'area differente
-      view_data: Mostra i dati per la visualizzazione attuale della mappa
     start_rjs: 
       data_frame_title: Dati
       data_layer_name: Esplora Dati Mappa
       details: Dettagli
-      drag_a_box: Tracciare un riquadro sulla mappa per selezionare un'area
       edited_by_user_at_timestamp: Modificato da %{user} il %{timestamp}
       hide_areas: Nascondi le aree
       history_for_feature: Storico per %{feature}
index baabca81a33c1b46d001adaba2541dc0dfca1a16..77354490f9c9f7fea3202d2d909ad2ff73ef70a0 100644 (file)
@@ -3,6 +3,7 @@
 # Export driver: syck-pecl
 # Author: Atysn
 # Author: Fryed-peach
+# Author: Hayashi
 # Author: Higa4
 # Author: Hosiryuhosi
 # Author: Iwai.masaharu
@@ -201,14 +202,10 @@ ja:
         node: ノード
         relation: リレーション
         way: ウェイ
-    start: 
-      manually_select: ドラッグして別の領域を選択
-      view_data: 現在のマップのデータを表示
     start_rjs: 
       data_frame_title: データ
       data_layer_name: 地図データを参照
       details: 詳細
-      drag_a_box: 領域を選択するには地図をドラッグしてください
       edited_by_user_at_timestamp: "%{user}による%{timestamp}時点の編集"
       hide_areas: 領域を隠す
       history_for_feature: "%{feature}の履歴"
@@ -322,8 +319,9 @@ ja:
       hide_link: このコメントを隠す
     diary_entry: 
       comment_count: 
-        one: 1 コメント
+        one: "%{count} コメント"
         other: "%{count} コメント"
+        zero: コメントなし
       comment_link: このエントリにコメント
       confirm: 確認
       edit_link: この記事の編集
@@ -919,9 +917,9 @@ ja:
   javascripts: 
     map: 
       base: 
-        cycle_map: 自転車地図
+        cycle_map: サイクリングマップ
         standard: 標準
-        transport_map: 輸送地図
+        transport_map: 交通マップ
     site: 
       edit_disabled_tooltip: 地図を編集するには拡大してください
       edit_tooltip: 地図を編集
@@ -1344,12 +1342,12 @@ ja:
       headings: 見出し
       image: 画像
       link: リンク
-      ordered: 順序付きリスト
+      ordered: 番号付きリスト
       second: 項目 2
       subheading: 小見出し
       text: テキスト
       title_html: <a href="http://daringfireball.net/projects/markdown/">Markdown</a> で構文解析されます
-      unordered: 順序なしリスト
+      unordered: 番号なしリスト
       url: URL
     richtext_area: 
       edit: 編集
index c9a205be0073db6d2a165c741bbc030092e73a24..a7a783d29edf26dad0241bf3cc5ba7e172bf464f 100644 (file)
@@ -163,13 +163,10 @@ ka:
         node: კვანძი
         relation: ურთიერთობა
         way: გზა
-    start: 
-      manually_select: სხვა ტერიტორიის გამოყოფა ხელით
     start_rjs: 
       data_frame_title: მონაცემები
       data_layer_name: რუკის მონაცემების ნახვა
       details: დეტალები
-      drag_a_box: რეგიონის ასარჩევად რუკაზე გაწელეთ ჩარჩო
       edited_by_user_at_timestamp: შეცვალა %{user} %{timestamp}-ში
       hide_areas: ტერიტორიების დამალვა
       history_for_feature: ისტორია %{feature}-თვის
index 5d8ce33dfcc60378f93208b1f19c1586f11460ef..05a812e27654ad2f4a63d327de3f42264f9eed85 100644 (file)
@@ -183,14 +183,10 @@ ko:
         node: 노드
         relation: 관계
         way: 길
-    start: 
-      manually_select: 다른 지역 수동 선택
-      view_data: 현재 지도 표기로 정보 보기
     start_rjs: 
       data_frame_title: 데이터
       data_layer_name: 지도 데이터 찾아보기
       details: 자세한 사항
-      drag_a_box: 지역을 보기 위해 지도로 끌어 놓으세요
       edited_by_user_at_timestamp: "%{timestamp}에 %{user}가 수정"
       hide_areas: 지역 숨기기
       history_for_feature: "%{feature}의 역사"
index f5d0976c681a72efea95d4d9a71df0af7c739ff6..5f8acb32a106c54821d4bb19fbf802bbf526f81c 100644 (file)
@@ -120,8 +120,6 @@ lb:
         node: Knuet
         relation: Relatioun
         way: Wee
-    start: 
-      manually_select: En anere Beräich manuell eraussichen
     start_rjs: 
       data_frame_title: Donnéeën
       data_layer_name: Donnéeë vun de Kaarten duerchkucken
index 2c7ee70da4e5a2c1c4aa6eb62ff68e77dca71b9d..d2938e20bfaea82dd24bbaec606835fae3295cac 100644 (file)
@@ -197,14 +197,10 @@ lt:
         node: Taškas
         relation: Ryšys
         way: Kelias
-    start: 
-      manually_select: Rankiniu būdu parinkite kitą plotą
-      view_data: Peržiūrėti duomenis pasirinktame žemėlapyje
     start_rjs: 
       data_frame_title: Duomenys
       data_layer_name: Duomenys
       details: Detalės
-      drag_a_box: Tempkite lauką ant žemėlapio, kad pažymėtumėte plotą
       edited_by_user_at_timestamp: Keitė %{user}. Keitimo laikas %{timestamp}
       hide_areas: Slėpti sritis
       history_for_feature: Istorija apie %{feature}
index 138234b6fc12540931009d99ad1e5fb3acfbbf08..a7278afda19bfcdb04ba11b7aba77299dc110b66 100644 (file)
@@ -200,14 +200,10 @@ lv:
         node: Punkts
         relation: Relācija
         way: Līnija
-    start: 
-      manually_select: Manuāli izvēlēties citu apgabalu
-      view_data: Skatīt datus pašreizējā kartes skatā
     start_rjs: 
       data_frame_title: Dati
       data_layer_name: Pārlūkot kartes datus
       details: Sīkāka informācija
-      drag_a_box: Uzvelciet rāmi uz kartes, lai izvēlētos apgabalu
       edited_by_user_at_timestamp: Rediģēja %{user} %{timestamp}
       hide_areas: Paslēpt zonas
       history_for_feature: Vēsture %{feature}
@@ -1151,7 +1147,7 @@ lv:
       header: "OpenStreetMap lietotājs %{from_user} ir jums nosūtījis ziņu ar tematu %{subject}:"
       hi: Sveiks %{to_user},
     signup_confirm: 
-      subject: "[OpenStreetMap] Apstipriniet savu e-pasta adresi"
+      subject: "[OpenStreetMap] Laipni lūgti OpenStreetMap"
     signup_confirm_html: 
       ask_questions: Tu vari jautāt jebkurus jautājumus par OpenStreetMap mūsu <a href="http://help.openstreetmap.org/">jautājumu un atbilžu lapā</a>.
       current_user: Saraksts ar pašreizējiem lietotājiem kategorijās, pamatojoties uz to, kur tie atrodas pasaulē, ir pieejams no <a href="http://wiki.openstreetmap.org/wiki/Category:Users_by_geographical_region">Category:Users_by_geographical_region</a>.
index 743fea47ed9b9d6392de8507ce2c30cb0c4fd3a9..578d63b32e7052f6192a0d9fbab42cc18a1e8cc3 100644 (file)
@@ -194,14 +194,10 @@ mk:
         node: Јазол
         relation: Однос
         way: Пат
-    start: 
-      manually_select: Рачно избери друга површина
-      view_data: Погледајте податоци за тековниот поглед на картата
     start_rjs: 
       data_frame_title: Податоци
       data_layer_name: Прелист. податоци за картата
       details: Подробно
-      drag_a_box: Повлечете рамка на картата за да одберете простор
       edited_by_user_at_timestamp: Уредено од %{user} во %{timestamp}
       hide_areas: Скриј подрачја
       history_for_feature: Историја за %{feature}
index 683726f54d21ae24da76b3268f5fcdcb8da7cc40..46538315937ebf016a27032d1d32cb3d1394089f 100644 (file)
@@ -188,14 +188,10 @@ ms:
         node: Nod
         relation: Hubungan
         way: Jalan
-    start: 
-      manually_select: Pilih kawasan yang lain secara insani
-      view_data: Lihat data untuk paparan peta semasa
     start_rjs: 
       data_frame_title: Data
       data_layer_name: Telusuri Data Peta
       details: Butiran
-      drag_a_box: Seretkan petak di atas peta untuk memilih kawasan
       edited_by_user_at_timestamp: Disunting oleh %{user} pada %{timestamp}
       hide_areas: Sorokkan kawasan
       history_for_feature: Sejarah %{feature}
index 3840aca2d64804d3e63d0b879b98a146a9076320..2e3f21ad6bef91c169fd35bd24584fb20e052d72 100644 (file)
@@ -203,14 +203,10 @@ nb:
         node: Node
         relation: Relasjon
         way: Vei
-    start: 
-      manually_select: Velg et annet område manuelt
-      view_data: Vis data for gjeldende kartvisning
     start_rjs: 
       data_frame_title: Data
       data_layer_name: Bla gjennom kartdata
       details: Detaljer
-      drag_a_box: Dra en boks på kartet for å velge et område
       edited_by_user_at_timestamp: Redigert av %{user},  %{timestamp}
       hide_areas: Skjul områder
       history_for_feature: Historikk for %{feature}
index 22c82bca64d46728a3a20e190aa9d5b54275bd5c..ef8e28bcb94301586e5c6a484560570db11f9463 100644 (file)
@@ -200,14 +200,10 @@ nl:
         node: Node
         relation: Relatie
         way: Weg
-    start: 
-      manually_select: Handmatig een ander gebied selecteren
-      view_data: Gegevens voor de huidige kaartweergave weergeven
     start_rjs: 
       data_frame_title: Gegevens
       data_layer_name: Kaartgegevens verkennen
       details: Details
-      drag_a_box: Teken een rechthoek op de kaart om een gebied te selecteren
       edited_by_user_at_timestamp: Bewerkt door %{user} op %{timestamp}
       hide_areas: Gebieden verbergen
       history_for_feature: Geschiedenis voor %{feature}
@@ -324,8 +320,9 @@ nl:
       hide_link: Opmerking verbergen
     diary_entry: 
       comment_count: 
-        one: één reactie
+        one: Eén reactie
         other: "%{count} reacties"
+        zero: Geen reacties
       comment_link: Reactie plaatsen bij dit bericht
       confirm: Bevestigen
       edit_link: Bericht bewerken
index 82b08373317155dd37dd46d5924a0a30569d259d..49081dcd709c1a060ab8a42709b1360def13a92f 100644 (file)
@@ -209,14 +209,10 @@ nn:
         node: Node
         relation: Relasjon
         way: Veg
-    start: 
-      manually_select: Vel eit anna område manuelt
-      view_data: Vis data for gjeldande kartvising
     start_rjs: 
       data_frame_title: Data
       data_layer_name: Kartdata
       details: Detaljar
-      drag_a_box: Dra ein boks på kartet for å velje eit område
       edited_by_user_at_timestamp: Redigert av %{user},  %{timestamp}
       hide_areas: Skjul områder
       history_for_feature: Historikk for %{feature}
index 4f3a9eed0b288b7ba1ec6cad313168f324dc0084..470c4e57ccfd5deadbc7ef1142fec0edc7e45554 100644 (file)
@@ -203,14 +203,10 @@ pl:
         node: Węzeł
         relation: Relacja
         way: Droga
-    start: 
-      manually_select: Manualnie wybierz inny obszar
-      view_data: Zobacz dane w aktualnym widoku mapy
     start_rjs: 
       data_frame_title: Dane
       data_layer_name: Przeglądaj dane mapy
       details: Szczegóły
-      drag_a_box: Zaznacz myszą prostokąt na mapie, aby wybrać obszar
       edited_by_user_at_timestamp: Edytowany przez %{user} ostatni raz %{timestamp}
       hide_areas: Ukryj obszary
       history_for_feature: Historia zmian dla %{feature}
index 0b8579b081ecc92a59fd8b63eeb9670f82c969f0..ef8999a0380f3d25af5585752358550ae925aac6 100644 (file)
@@ -208,14 +208,10 @@ pt-BR:
         node: Ponto
         relation: Relação
         way: Caminho
-    start: 
-      manually_select: Selecionar manualmente uma área diferente
-      view_data: Ver dados para do mapa em visualização atual
     start_rjs: 
       data_frame_title: Dados
       data_layer_name: Navegar nos Dados do Mapa
       details: Detalhes
-      drag_a_box: Clique e arraste para selecionar uma área no mapa
       edited_by_user_at_timestamp: Editado por %{user} at %{timestamp}
       hide_areas: Ocultar áreas
       history_for_feature: Histórico para %{feature}
index e4b5c209b4a61d2fb5a4c93fcdada6cd3bd056bd..2d86a570a563b9371c4875ba4e4708454225acbc 100644 (file)
@@ -207,14 +207,10 @@ pt:
         node: Nó
         relation: Relação
         way: Linha
-    start: 
-      manually_select: Selecionar manualmente uma área diferente
-      view_data: Ver dados para a vista atual do mapa
     start_rjs: 
       data_frame_title: Dados
       data_layer_name: Inspecionar dados técnicos do mapa
       details: Ver Detalhes
-      drag_a_box: Arraste uma seleção no mapa para escolher uma área
       edited_by_user_at_timestamp: Editado por %{user} em %{timestamp}
       hide_areas: Ocultar áreas
       history_for_feature: Histórico de %{feature}
index 8d0c858a54fa17fee5dc826844c6c7618f31cbe4..4dc170478d2a36dd38847e2ef4bcc9ea90a00f9d 100644 (file)
@@ -187,14 +187,10 @@ ro:
         node: Nod
         relation: Relație
         way: Cale
-    start: 
-      manually_select: Selectare manuală a unei alte zone
-      view_data: Vizualizare date pentru perspectiva curentă a hărții
     start_rjs: 
       data_frame_title: Date
       data_layer_name: Navigare prin datele hărții
       details: Detalii
-      drag_a_box: Trageți cu mouse-ul și creați un dreptunghi pentru a selecta zona hărții
       edited_by_user_at_timestamp: Editat de %{user} la %{timestamp}
       hide_areas: Ascunde suprafețele
       history_for_feature: Istoric pentru %{feature}
index 4680779dcee134649e2c53276c30c32cb185fb50..ae9260c2d9a8fa3f8f8f1e77dab0dc14bd61ebc8 100644 (file)
@@ -222,14 +222,10 @@ ru:
         node: Точка
         relation: Отношение
         way: Линия
-    start: 
-      manually_select: Выделить другую область
-      view_data: Посмотреть данные для текущего вида
     start_rjs: 
       data_frame_title: Данные
       data_layer_name: Просмотр данных карты
       details: Подробности
-      drag_a_box: Для выбора области растяните рамку по карте
       edited_by_user_at_timestamp: Изменил %{user} в %{timestamp}
       hide_areas: Скрыть области
       history_for_feature: История %{feature}
index 5e293a288136788aa41582223727f40024f392b0..0e4bb53e19cc689b70d37c927bc92f6e7cc8eca9 100644 (file)
@@ -179,6 +179,8 @@ sk:
       of: z
       showing_page: Strana
     redacted: 
+      message_html: Verzia %{version} tohto objektu %{type} nemôže byť zobrazené, pretože bola skrytá. Viac informácií nájdete na %{redaction_link}.
+      redaction: Revízia %{id}
       type: 
         node: bod
         relation: vzťah
@@ -202,14 +204,10 @@ sk:
         node: Bod
         relation: Relácia
         way: Cesta
-    start: 
-      manually_select: Manuálne vybrať inú oblasť
-      view_data: Zobraziť údaje v aktuálnom zobrazení mapy
     start_rjs: 
       data_frame_title: Dáta
       data_layer_name: Prehľadávať mapové dáta
       details: Detaily
-      drag_a_box: Označte myšou na mape zvolenú oblasť
       edited_by_user_at_timestamp: Upravoval %{user} o %{timestamp}
       hide_areas: Skryť oblasti
       history_for_feature: História pre %{feature}
@@ -329,6 +327,7 @@ sk:
         few: "%{count} komentáre"
         one: 1 komentár
         other: "%{count} komentárov"
+        zero: Žiaden komentár
       comment_link: Komentár k záznamu
       confirm: Potvrdiť
       edit_link: Upraviť tento záznam
@@ -1022,7 +1021,7 @@ sk:
       contributors_title_html: Naši prispievatelia
       contributors_za_html: "<strong>Juhoafrická republika</strong>: Obsahuje dáta pochádzajúce z <a href=\"http://www.ngi.gov.za/\">Chief Directorate: National Geo-Spatial Information</a>, State copyright reserved."
       credit_1_html: Vyžadujeme uviesť „<em>© Prispievatelia OpenStreetMap</em>“.
-      credit_2_html: "  Ak je to možné, mal byť text <em>OpenStreetMap</em> uvedený ako hypertextový odkaz na <a href=\"http://www.openstreetmap.org/\">http://www.openstreetmap.org/</a> a <em>CC BY-SA</em> na <a href=\"http://creativecommons.org/licenses/by-sa/2.0/\">http://creativecommons.org/licenses/by-sa/2.0/</a>. Ak používate médium, ktoré odkazy neumožňuje (napr. v tlačenom diele), navrhujeme, aby ste svojho čitateľa nasmerovali na www.openstreetmap.org (zrejme doplnením <em>OpenStreetMap</em> o túto internetovú adresu) a analogicky <em>CC BY-SA</em> na www.creativecommons.org."
+      credit_2_html: "  Musíte zdôrazniť, že dáta sú dostupné pod licenciu Open Database Licence, a pri používaní mapových tajlov, že tieto sú pod licenciou CC-BY-SA. Môžete to urobiť hypertextovým odkazom na <a href=\"http://www.openstreetmap.org/copyright\">http://www.openstreetmap.org/copyright</a>. Alternatívne, ak distribuujete OSM v dátovej forme, môžete vymenovať licencie a vytvoriť hypertextový odkaz priamo na ne. Pri médiách kde hypertextové odkazy nie sú možné (napr. tlačené práce), odporúčame aby ste nasmerovali čitateľov na openstreetmap.org (napríklad rozšírením slova OpenStreetMap na plnú adresu), na opendatacommons.org a na creativecommons.org (ak je to relevenantné)."
       credit_3_html: "V prípade interaktívnej elektronickej mapy by autorstvo malo byť uvedené v rohu mapy.\nNapríklad:"
       credit_title_html: Ako uvádzať OpenStreetMap
       infringement_1_html: Prispievateľom OSM pripomíname, že by nikdy nemali pridávať dáta zo zdrojov chránených autorským právom (napr. Google Maps či tlačené mapy) bez výslovného súhlasu držiteľov práv.
@@ -1261,21 +1260,33 @@ sk:
     update: 
       flash: Úspešne aktualizované informácie o klientovi
   redaction: 
+    create: 
+      flash: Revízia vytvorená.
+    destroy: 
+      error: Pri zrušení revízie sa vyskytla chyba.
+      flash: Revízia zrušená.
+      not_empty: Revízia nie je prázdna. Pred zrušením tejto revízie zrušte skrytie všetkých verzií patriacich do tejto revízie.
     edit: 
       description: Popis
-      heading: Upraviť reláciu
-      submit: Uložiť reláciu
-      title: Upraviť reláciu
+      heading: Upraviť revíziu
+      submit: Uložiť revíziu
+      title: Upraviť revíziu
     index: 
-      heading: Zoznam relácií
-      title: Zoznam relácií
+      empty: Žiadne revízie na zobrazenie.
+      heading: Zoznam revízií
+      title: Zoznam revízií
     new: 
       description: Popis
-      submit: Vytvoriť reláciu
+      heading: Zadajte informácie k novej revízii.
+      submit: Vytvoriť revíziu
       title: Vytváranie nových revízií
     show: 
       confirm: Ste si istý?
       description: "Popis:"
+      destroy: Odstrániť túto revíziu
+      edit: Upraviť túto revíziu
+      heading: Zobrazujem revíziu "%{title}"
+      title: Zobrazenie revízie
       user: "Autor:"
     update: 
       flash: Zmeny boli uložené.
index a648cf073f86cf42b76ee13dcf7d90ad5949fe9c..f584abbc57296310926c3542338b05c350f2e7fe 100644 (file)
@@ -199,14 +199,10 @@ sl:
         node: Vozlišče
         relation: Zveza
         way: Pot
-    start: 
-      manually_select: Ročno izberite drugo področje
-      view_data: Ogled podatkov trenutno prikazanega zemljevida
     start_rjs: 
       data_frame_title: Podatki
       data_layer_name: Prebrskaj podatke zemljevida
       details: Podrobnosti
-      drag_a_box: Za izbor področja povlecite pravokotnik na zemljevidu
       edited_by_user_at_timestamp: Uredil %{user} v %{timestamp}
       hide_areas: Skrij področja
       history_for_feature: Zgodovina %{feature}
index b7f965d5181405a1e4ef1cecbafee3fea4e610a4..d69c452bad3eca4c35a1a6df1288f2802879ff9a 100644 (file)
@@ -199,14 +199,10 @@ sq:
         node: Pikë
         relation: Lidhje
         way: Udhë
-    start: 
-      manually_select: Manualisht zgedh ni zon qeter
-      view_data: Shini te dhanat per harten e tanishme
     start_rjs: 
       data_frame_title: Senet
       data_layer_name: Shfleto të dhënat e hartës
       details: Detajet
-      drag_a_box: Bone ni kuti n'hart edhe zgedhe zonen
       edited_by_user_at_timestamp: Ndryshuar nga %{user} në %{timestamp}
       hide_areas: Fshih zonat
       history_for_feature: Historija për %{feature}
index 5653796c671714c250ef4ab728fdca1757abecd8..4d3821b78129d7d8ae5b71510e1da9faf58329a1 100644 (file)
@@ -206,14 +206,10 @@ sr-Latn:
         node: Čvor
         relation: Odnos
         way: Putanja
-    start: 
-      manually_select: Ručno izaberite drugo područje
-      view_data: Pogledaj podatke trenutnog prikaza mape
     start_rjs: 
       data_frame_title: Podaci
       data_layer_name: Pregledaj podatke sa mape
       details: Detalji
-      drag_a_box: Prevucite okvir na mapi da biste izabrali područje
       edited_by_user_at_timestamp: Izmenio %{user} u %{timestamp}
       hide_areas: Sakrij područja
       history_for_feature: Istorija za %{feature}
index 10e472444e44681f532248e58ab9d90cbd93f581..43ab174d47ce330b6097a9d59dab19d204fb3eca 100644 (file)
@@ -210,14 +210,10 @@ sr:
         node: Чвор
         relation: Однос
         way: Путања
-    start: 
-      manually_select: Ручно изаберите друго подручје
-      view_data: Погледај податке тренутног приказа мапе
     start_rjs: 
       data_frame_title: Подаци
       data_layer_name: Прегледај податке са мапе
       details: Детаљи
-      drag_a_box: Превуците оквир на мапи да бисте изабрали подручје
       edited_by_user_at_timestamp: Изменио %{user} у %{timestamp}
       hide_areas: Сакриј подручја
       history_for_feature: Историја за %{feature}
index 33fb05b357cdeddf4ea0d01bf93081507e256af4..b7f0b52bec0485c26800d70b8f6ad88703f26990 100644 (file)
@@ -215,14 +215,10 @@ sv:
         node: Nod
         relation: Relation
         way: Väg
-    start: 
-      manually_select: Välj ett område manuellt
-      view_data: Visa data för denna karta
     start_rjs: 
       data_frame_title: Data
       data_layer_name: Bläddra kartdata
       details: Detaljer
-      drag_a_box: Markera ett område på kartan.
       edited_by_user_at_timestamp: Redigerad av %{user} %{timestamp}
       hide_areas: Göm område
       history_for_feature: Historik för %{feature}
@@ -341,6 +337,7 @@ sv:
       comment_count: 
         one: 1 kommentar
         other: "%{count} kommentarer"
+        zero: Inga kommentarer
       comment_link: Kommentera denna anteckning
       confirm: Bekräfta
       edit_link: Redigera denna anteckning
index 5be48e3674f882c55d6c5c1a98e8adb8d0c6448d..6a36c6bedeabc404dfca401b18963dde02657ad5 100644 (file)
@@ -177,14 +177,10 @@ ta:
         node: சந்தி
         relation: தொடர்பு
         way: வழி
-    start: 
-      manually_select: நீங்களாகவே வேறு பகுதியைத் தேர்ந்தெடுக்கவும்
-      view_data: நடப்பு வரைபடக் காட்சிக்கான தரவுகளைப் பார்க்கவும்
     start_rjs: 
       data_frame_title: தரவு
       data_layer_name: தரவு
       details: விளக்கம்
-      drag_a_box: குறிப்பிட்ட பரப்பைத் தேர்வு செய்ய ஒரு பெட்டியை வரைபடத்தின் மீது இழுத்து வரவும்
       edited_by_user_at_timestamp: தொகுத்தவர் %{user} அன்று %{timestamp}
       hide_areas: பகுதிகளை மறை
       loading: ஏற்றப்படுகிறது ...
index 501869e8ac58a58eca6b86b9a84ad4d8a095c4f1..c229aafd61f908d106fa6535de6f5949d2069f44 100644 (file)
@@ -202,14 +202,10 @@ tl:
         node: Buko
         relation: Kaugnayan
         way: Daan
-    start: 
-      manually_select: Kinakamay na pumili ng ibang lugar
-      view_data: Tingnan ang dato para sa pangkasalukuyang tanawin ng mapa
     start_rjs: 
       data_frame_title: Dato
       data_layer_name: Tumingin-tingin sa Dato ng Mapa
       details: Mga detalye
-      drag_a_box: Kumaladkad ng isang kahon sa mapa upang pumili ng isang lugar
       edited_by_user_at_timestamp: Binago ni %{user} sa ganap na %{timestamp}
       hide_areas: Itago ang mga lugar
       history_for_feature: Kasaysayan para sa %{feature}
index b272fa78c826b162e4fb7790b124818ec2b3c2ed..855327490dd561f3b4ce3162d2467041873bb581 100644 (file)
@@ -164,14 +164,10 @@ tr:
         node: Nokta
         relation: İlişki
         way: Yol
-    start: 
-      manually_select: Fare kullanarak farklı bir alan seç
-      view_data: Geçerli harita alanının verileri görüntüle
     start_rjs: 
       data_frame_title: Veri
       data_layer_name: Harita Verisi Gözat
       details: Ayrıntılar
-      drag_a_box: Fare kullanarak harita üzerinde bir alan seç
       edited_by_user_at_timestamp: "%{user} tarafından düzenlendi (%{timestamp})"
       hide_areas: Bölgeleri gizle
       history_for_feature: "%{feature} (geçmiş)"
index e9bf6ba717c3ceb8ac671c41be401786888468d8..ff5f9272a988f95f27f08ff558c20a14480e2f3b 100644 (file)
@@ -208,14 +208,10 @@ uk:
         node: Точка
         relation: Зв’язок
         way: Лінія
-    start: 
-      manually_select: Виділіть іншу ділянку
-      view_data: Переглянути дані з поточного виду
     start_rjs: 
       data_frame_title: Дані
       data_layer_name: Переглянути дані мапи
       details: Подробиці
-      drag_a_box: Для виділення ділянки пересуньте рамку на мапі
       edited_by_user_at_timestamp: Змінено %{user} %{timestamp}
       hide_areas: Приховати ділянки
       history_for_feature: Історія %{feature}
index 52ed6ce21f4279e71ff2b415402bdd1208380a0a..394d1c3595167ba07471c0cbc64b751995219016 100644 (file)
@@ -196,14 +196,10 @@ vi:
         node: Nốt
         relation: Quan hệ
         way: Lối
-    start: 
-      manually_select: Chọn vùng khác thủ công
-      view_data: Xem dữ liệu của phần bản đồ đang xem
     start_rjs: 
       data_frame_title: Dữ liệu
       data_layer_name: Xem Dữ liệu Bản đồ
       details: Chi tiết
-      drag_a_box: Kéo hộp trên bản đồ để chọn vùng
       edited_by_user_at_timestamp: Được sửa đổi bởi %{user} lúc %{timestamp}
       hide_areas: Ẩn các khu vực
       history_for_feature: Lịch sử %{đối tượng}
index 1a0ab0a071a6ba5816dead6f5b8340831d93d543..64eda1cd3c5e65b46b114119a70823751840c1ec 100644 (file)
@@ -199,14 +199,10 @@ zh-CN:
         node: 节点
         relation: 关系
         way: 路径
-    start: 
-      manually_select: 手动选择一个不同区域
-      view_data: 查看当前地图视图中的数据
     start_rjs: 
       data_frame_title: 数据
       data_layer_name: 浏览地图数据
       details: 详细信息
-      drag_a_box: 通过拖曳窗口来选择区域
       edited_by_user_at_timestamp: 由 %{user} 于 %{timestamp} 编辑
       hide_areas: 隐藏区域
       history_for_feature: 历史 %{feature}
index ae3994d473cc9152948f8354b16e2ec43fc0e2b9..fd70befe5b81e15ff4f9ce7355e211f1e009b52a 100644 (file)
@@ -195,14 +195,10 @@ zh-TW:
         node: 節點
         relation: 關係
         way: 路徑
-    start: 
-      manually_select: 手動選擇不同的區域
-      view_data: 目前地圖檢視的檢視資料
     start_rjs: 
       data_frame_title: 資料
       data_layer_name: 流覽地圖資料
       details: 詳細資訊
-      drag_a_box: 在地圖上拖曳出一個方塊來選擇一個區域
       edited_by_user_at_timestamp: 由 %{user} 於 %{timestamp} 編輯
       hide_areas: 隱藏區域
       history_for_feature: "%{feature} 的歷史"
index cec2c3e3f4412e378ff2b4728da0b3080b8f5e37..a4a8faf43d423e50fdb929867d39623f8ab4cc84 100644 (file)
@@ -225,6 +225,7 @@ OpenStreetMap::Application.routes.draw do
   # export
   match '/export/start' => 'export#start', :via => :get
   match '/export/finish' => 'export#finish', :via => :post
+  match '/export/embed' => 'export#embed', :via => :get
 
   # messages
   match '/user/:display_name/inbox' => 'message#inbox', :via => :get, :as => "inbox"
index 5b95dfca9d4755abcb31f6f46699ed3f2445ff89..b5be8b9873efa8409b93ecb873d703786448b93f 100644 (file)
@@ -5,6 +5,7 @@ module Potlatch2
     "be" => "be",
     "be-Tarask" => "be-tarask",
     "br" => "br",
+    "bs" => "bs",
     "ca" => "ca",
     "cs" => "cs_CZ",
     "da" => "da",
diff --git a/public/export/embed.html b/public/export/embed.html
deleted file mode 100644 (file)
index c0036b6..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-<!DOCTYPE html>
-<html xmlns="http://www.w3.org/1999/xhtml">
-  <head>
-    <meta charset="utf-8">
-    <title>OpenStreetMap Embedded</title>
-    <style type="text/css">
-        html {
-            width: 100%;
-            height: 100%;
-       }
-        body {
-            width: 100%;
-            height: 100%;
-            margin: 0px;
-        }
-        #map {
-            width: 100%;
-            height: 100%;
-        }
-        .olControlAttribution {
-            bottom: 3px!important;
-        }
-    </style>
-    <script src="/openlayers/OpenLayers.js" type="text/javascript"></script>
-    <script src="/openlayers/OpenStreetMap.js" type="text/javascript"></script>
-    <script type="text/javascript">
-    <!--
-        var map, layer;
-        function init(){
-            map = new OpenLayers.Map ("map", {
-              controls: [
-                  new OpenLayers.Control.Attribution(),
-                  new OpenLayers.Control.Navigation()
-              ],
-              numZoomLevels: 20,
-              displayProjection: new OpenLayers.Projection("EPSG:4326")
-            });
-
-            var attribution = '© <a target="_parent" href="http://www.openstreetmap.org">OpenStreetMap</a> and contributors, under an <a target="_parent" href="http://www.openstreetmap.org/copyright">open license</a>';
-            var args = OpenLayers.Util.getParameters();
-            if (!args.layer || args.layer == "mapnik" || args.layer == "osmarender") {
-                var mapnik = new OpenLayers.Layer.OSM.Mapnik("Mapnik", {
-                   wrapDateLine: true,
-                   attribution: attribution
-                });
-                map.addLayer(mapnik);
-            } else if (args.layer == "cyclemap" || args.layer == "cycle map") {
-                var cyclemap = new OpenLayers.Layer.OSM.CycleMap("Cycle Map", {
-                   wrapDateLine: true,
-                   attribution: attribution
-                });
-                map.addLayer(cyclemap);
-            } else if (args.layer == "transportmap") {
-                var transportmap = new OpenLayers.Layer.OSM.TransportMap("Transport Map", {
-                   wrapDateLine: true,
-                   attribution: attribution
-                });
-                map.addLayer(transportmap);
-            } else if (args.layer == "mapquest") {
-                var mapquestmap = new OpenLayers.Layer.OSM("MapQuest Open Map", [
-                    "http://otile1.mqcdn.com/tiles/1.0.0/osm/${z}/${x}/${y}.png",
-                    "http://otile2.mqcdn.com/tiles/1.0.0/osm/${z}/${x}/${y}.png",
-                    "http://otile3.mqcdn.com/tiles/1.0.0/osm/${z}/${x}/${y}.png",
-                    "http://otile4.mqcdn.com/tiles/1.0.0/osm/${z}/${x}/${y}.png"], {
-                   wrapDateLine: true,
-                   attribution: "Tiles courtesy of <a href='http://www.mapquest.com/' target='_blank'>MapQuest</a> <img src='http://developer.mapquest.com/content/osm/mq_logo.png'>"
-                });
-                map.addLayer(mapquestmap);
-            }
-
-            if (args.marker) {
-                var markers = new OpenLayers.Layer.Markers();
-                map.addLayer(markers);
-                markers.addMarker(new OpenLayers.Marker(new OpenLayers.LonLat(args.marker[1], args.marker[0]).transform(map.displayProjection, map.getProjectionObject())));
-                map.marker = true;
-            }
-
-            if (args.bbox) {
-                var bounds = OpenLayers.Bounds.fromArray(args.bbox).transform(map.displayProjection, map.getProjectionObject());
-                map.zoomToExtent(bounds)
-            } else {
-                map.zoomToMaxExtent();
-            }
-
-            var size = map.getSize();
-            if (size.h > 320) {
-                map.addControl(new OpenLayers.Control.PanZoomBar());
-            } else {
-                map.addControl(new OpenLayers.Control.Zoom());
-            }
-        }
-    // -->
-    </script>
-  </head>
-
-  <body onload="init()">
-    <div id="map"></div>
-  </body>
-</html>
index af9d2818392d364772ba096e30273be58f1531fe..61a307643414f212a506c188038d72fce6f37c07 100644 (file)
@@ -13,6 +13,10 @@ class ExportControllerTest < ActionController::TestCase
       { :path => "/export/finish", :method => :post },
       { :controller => "export", :action => "finish" }
     )
+    assert_routing(
+      { :path => "/export/embed", :method => :get },
+      { :controller => "export", :action => "embed" }
+    )
   end
 
   def test_start
diff --git a/vendor/assets/leaflet/images/layers.png b/vendor/assets/leaflet/images/layers.png
new file mode 100644 (file)
index 0000000..642e278
Binary files /dev/null and b/vendor/assets/leaflet/images/layers.png differ
diff --git a/vendor/assets/leaflet/images/marker-icon.png b/vendor/assets/leaflet/images/marker-icon.png
new file mode 100644 (file)
index 0000000..7614b7c
Binary files /dev/null and b/vendor/assets/leaflet/images/marker-icon.png differ
diff --git a/vendor/assets/leaflet/images/marker-shadow.png b/vendor/assets/leaflet/images/marker-shadow.png
new file mode 100644 (file)
index 0000000..a64f6a6
Binary files /dev/null and b/vendor/assets/leaflet/images/marker-shadow.png differ
diff --git a/vendor/assets/leaflet/images/zoom-in.png b/vendor/assets/leaflet/images/zoom-in.png
new file mode 100644 (file)
index 0000000..4a98a35
Binary files /dev/null and b/vendor/assets/leaflet/images/zoom-in.png differ
diff --git a/vendor/assets/leaflet/images/zoom-out.png b/vendor/assets/leaflet/images/zoom-out.png
new file mode 100644 (file)
index 0000000..4276166
Binary files /dev/null and b/vendor/assets/leaflet/images/zoom-out.png differ
diff --git a/vendor/assets/leaflet/img/filter-icon.png b/vendor/assets/leaflet/img/filter-icon.png
new file mode 100644 (file)
index 0000000..981d82a
Binary files /dev/null and b/vendor/assets/leaflet/img/filter-icon.png differ
diff --git a/vendor/assets/leaflet/img/move-handle.png b/vendor/assets/leaflet/img/move-handle.png
new file mode 100644 (file)
index 0000000..d6b557a
Binary files /dev/null and b/vendor/assets/leaflet/img/move-handle.png differ
diff --git a/vendor/assets/leaflet/img/resize-handle.png b/vendor/assets/leaflet/img/resize-handle.png
new file mode 100644 (file)
index 0000000..6caa604
Binary files /dev/null and b/vendor/assets/leaflet/img/resize-handle.png differ
diff --git a/vendor/assets/leaflet/leaflet.css b/vendor/assets/leaflet/leaflet.css
new file mode 100644 (file)
index 0000000..938a1be
--- /dev/null
@@ -0,0 +1,401 @@
+/* required styles */\r
+\r
+.leaflet-map-pane,\r
+.leaflet-tile,\r
+.leaflet-marker-icon,\r
+.leaflet-marker-shadow,\r
+.leaflet-tile-pane,\r
+.leaflet-overlay-pane,\r
+.leaflet-shadow-pane,\r
+.leaflet-marker-pane,\r
+.leaflet-popup-pane,\r
+.leaflet-overlay-pane svg,\r
+.leaflet-zoom-box,\r
+.leaflet-image-layer,\r
+.leaflet-layer {\r
+       position: absolute;\r
+       left: 0;\r
+       }\r
+.leaflet-container {\r
+       overflow: hidden;\r
+       -ms-touch-action: none;\r
+       }\r
+.leaflet-tile,\r
+.leaflet-marker-icon,\r
+.leaflet-marker-shadow {\r
+       -webkit-user-select: none;\r
+          -moz-user-select: none;\r
+               user-select: none;\r
+       }\r
+.leaflet-marker-icon,\r
+.leaflet-marker-shadow {\r
+       display: block;\r
+       }\r
+/* map is broken in FF if you have max-width: 100% on tiles */\r
+.leaflet-container img {\r
+       max-width: none !important;\r
+       }\r
+/* stupid Android 2 doesn't understand "max-width: none" properly */\r
+.leaflet-container img.leaflet-image-layer {\r
+       max-width: 15000px !important;\r
+       }\r
+.leaflet-tile {\r
+       filter: inherit;\r
+       visibility: hidden;\r
+       }\r
+.leaflet-tile-loaded {\r
+       visibility: inherit;\r
+       }\r
+.leaflet-zoom-box {\r
+       width: 0;\r
+       height: 0;\r
+       }\r
+\r
+.leaflet-tile-pane    { z-index: 2; }\r
+.leaflet-objects-pane { z-index: 3; }\r
+.leaflet-overlay-pane { z-index: 4; }\r
+.leaflet-shadow-pane  { z-index: 5; }\r
+.leaflet-marker-pane  { z-index: 6; }\r
+.leaflet-popup-pane   { z-index: 7; }\r
+\r
+\r
+/* control positioning */\r
+\r
+.leaflet-control {\r
+       position: relative;\r
+       z-index: 7;\r
+       pointer-events: auto;\r
+       }\r
+.leaflet-top,\r
+.leaflet-bottom {\r
+       position: absolute;\r
+       z-index: 1000;\r
+       pointer-events: none;\r
+       }\r
+.leaflet-top {\r
+       top: 0;\r
+       }\r
+.leaflet-right {\r
+       right: 0;\r
+       }\r
+.leaflet-bottom {\r
+       bottom: 0;\r
+       }\r
+.leaflet-left {\r
+       left: 0;\r
+       }\r
+.leaflet-control {\r
+       float: left;\r
+       clear: both;\r
+       }\r
+.leaflet-right .leaflet-control {\r
+       float: right;\r
+       }\r
+.leaflet-top .leaflet-control {\r
+       margin-top: 10px;\r
+       }\r
+.leaflet-bottom .leaflet-control {\r
+       margin-bottom: 10px;\r
+       }\r
+.leaflet-left .leaflet-control {\r
+       margin-left: 10px;\r
+       }\r
+.leaflet-right .leaflet-control {\r
+       margin-right: 10px;\r
+       }\r
+\r
+\r
+/* zoom and fade animations */\r
+\r
+.leaflet-fade-anim .leaflet-tile,\r
+.leaflet-fade-anim .leaflet-popup {\r
+       opacity: 0;\r
+       -webkit-transition: opacity 0.2s linear;\r
+          -moz-transition: opacity 0.2s linear;\r
+            -o-transition: opacity 0.2s linear;\r
+               transition: opacity 0.2s linear;\r
+       }\r
+.leaflet-fade-anim .leaflet-tile-loaded,\r
+.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {\r
+       opacity: 1;\r
+       }\r
+\r
+.leaflet-zoom-anim .leaflet-zoom-animated {\r
+       -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);\r
+          -moz-transition:    -moz-transform 0.25s cubic-bezier(0,0,0.25,1);\r
+            -o-transition:      -o-transform 0.25s cubic-bezier(0,0,0.25,1);\r
+               transition:         transform 0.25s cubic-bezier(0,0,0.25,1);\r
+       }\r
+.leaflet-zoom-anim .leaflet-tile,\r
+.leaflet-pan-anim .leaflet-tile,\r
+.leaflet-touching .leaflet-zoom-animated {\r
+       -webkit-transition: none;\r
+          -moz-transition: none;\r
+            -o-transition: none;\r
+               transition: none;\r
+       }\r
+\r
+.leaflet-zoom-anim .leaflet-zoom-hide {\r
+       visibility: hidden;\r
+       }\r
+\r
+\r
+/* cursors */\r
+\r
+.leaflet-clickable {\r
+       cursor: pointer;\r
+       }\r
+.leaflet-container {\r
+       cursor: -webkit-grab;\r
+       cursor:    -moz-grab;\r
+       }\r
+.leaflet-popup-pane,\r
+.leaflet-control {\r
+       cursor: auto;\r
+       }\r
+.leaflet-dragging,\r
+.leaflet-dragging .leaflet-clickable,\r
+.leaflet-dragging .leaflet-container {\r
+       cursor: move;\r
+       cursor: -webkit-grabbing;\r
+       cursor:    -moz-grabbing;\r
+       }\r
+\r
+\r
+/* visual tweaks */\r
+\r
+.leaflet-container {\r
+       background: #ddd;\r
+       outline: 0;\r
+       }\r
+.leaflet-container a {\r
+       color: #0078A8;\r
+       }\r
+.leaflet-container a.leaflet-active {\r
+       outline: 2px solid orange;\r
+       }\r
+.leaflet-zoom-box {\r
+       border: 2px dotted #05f;\r
+       background: white;\r
+       opacity: 0.5;\r
+       }\r
+\r
+\r
+/* general typography */\r
+.leaflet-container {\r
+       font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;\r
+}\r
+\r
+\r
+/* zoom control */\r
+\r
+.leaflet-control-zoom {\r
+       -webkit-border-radius: 7px;\r
+               border-radius: 7px;\r
+       }\r
+.leaflet-control-zoom {\r
+       padding: 5px;\r
+       background: rgba(0, 0, 0, 0.25);\r
+       }\r
+.leaflet-control-zoom a {\r
+       width: 19px;\r
+       height: 19px;\r
+       background-color: rgba(255, 255, 255, 0.75);\r
+       -webkit-border-radius: 4px;\r
+               border-radius: 4px;\r
+       }\r
+.leaflet-control-zoom a,\r
+.leaflet-control-layers-toggle {\r
+       background-position: 50% 50%;\r
+       background-repeat: no-repeat;\r
+       display: block;\r
+       }\r
+.leaflet-control-zoom a:hover {\r
+       background-color: #fff;\r
+       }\r
+.leaflet-touch .leaflet-control-zoom a {\r
+       width: 27px;\r
+       height: 27px;\r
+       }\r
+.leaflet-control-zoom-in {\r
+       background-image: url(images/zoom-in.png);\r
+       margin-bottom: 5px;\r
+       }\r
+.leaflet-control-zoom-out {\r
+       background-image: url(images/zoom-out.png);\r
+       }\r
+\r
+\r
+/* layers control */\r
+\r
+.leaflet-control-layers {\r
+       box-shadow: 0 1px 7px #999;\r
+       background: #f8f8f9;\r
+       -webkit-border-radius: 8px;\r
+               border-radius: 8px;\r
+       }\r
+.leaflet-control-layers-toggle {\r
+       background-image: url(images/layers.png);\r
+       width: 36px;\r
+       height: 36px;\r
+       }\r
+.leaflet-touch .leaflet-control-layers-toggle {\r
+       width: 44px;\r
+       height: 44px;\r
+       }\r
+.leaflet-control-layers .leaflet-control-layers-list,\r
+.leaflet-control-layers-expanded .leaflet-control-layers-toggle {\r
+       display: none;\r
+       }\r
+.leaflet-control-layers-expanded .leaflet-control-layers-list {\r
+       display: block;\r
+       position: relative;\r
+       }\r
+.leaflet-control-layers-expanded {\r
+       padding: 6px 10px 6px 6px;\r
+       color: #333;\r
+       background: #fff;\r
+       }\r
+.leaflet-control-layers-selector {\r
+       margin-top: 2px;\r
+       position: relative;\r
+       top: 1px;\r
+       }\r
+.leaflet-control-layers label {\r
+       display: block;\r
+       }\r
+.leaflet-control-layers-separator {\r
+       height: 0;\r
+       border-top: 1px solid #ddd;\r
+       margin: 5px -10px 5px -6px;\r
+       }\r
+\r
+\r
+/* attribution and scale controls */\r
+\r
+.leaflet-container .leaflet-control-attribution {\r
+       background-color: rgba(255, 255, 255, 0.7);\r
+       box-shadow: 0 0 5px #bbb;\r
+       margin: 0;\r
+       }\r
+.leaflet-control-attribution,\r
+.leaflet-control-scale-line {\r
+       padding: 0 5px;\r
+       color: #333;\r
+       }\r
+.leaflet-container .leaflet-control-attribution,\r
+.leaflet-container .leaflet-control-scale {\r
+       font-size: 11px;\r
+       }\r
+.leaflet-left .leaflet-control-scale {\r
+       margin-left: 5px;\r
+       }\r
+.leaflet-bottom .leaflet-control-scale {\r
+       margin-bottom: 5px;\r
+       }\r
+.leaflet-control-scale-line {\r
+       border: 2px solid #777;\r
+       border-top: none;\r
+       color: black;\r
+       line-height: 1;\r
+       font-size: 10px;\r
+       padding-bottom: 2px;\r
+       text-shadow: 1px 1px 1px #fff;\r
+       background-color: rgba(255, 255, 255, 0.5);\r
+       }\r
+.leaflet-control-scale-line:not(:first-child) {\r
+       border-top: 2px solid #777;\r
+       padding-top: 1px;\r
+       border-bottom: none;\r
+       margin-top: -2px;\r
+       }\r
+.leaflet-control-scale-line:not(:first-child):not(:last-child) {\r
+       border-bottom: 2px solid #777;\r
+       }\r
+\r
+.leaflet-touch .leaflet-control-attribution, .leaflet-touch .leaflet-control-layers {\r
+       box-shadow: none;\r
+       }\r
+.leaflet-touch .leaflet-control-layers {\r
+       border: 5px solid #bbb;\r
+       }\r
+\r
+\r
+/* popup */\r
+\r
+.leaflet-popup {\r
+       position: absolute;\r
+       text-align: center;\r
+       }\r
+.leaflet-popup-content-wrapper {\r
+       padding: 1px;\r
+       text-align: left;\r
+       -webkit-border-radius: 20px;\r
+               border-radius: 20px;\r
+       }\r
+.leaflet-popup-content {\r
+       margin: 14px 20px;\r
+       line-height: 1.4;\r
+       }\r
+.leaflet-popup-content p {\r
+       margin: 18px 0;\r
+       }\r
+.leaflet-popup-tip-container {\r
+       margin: 0 auto;\r
+       width: 40px;\r
+       height: 20px;\r
+       position: relative;\r
+       overflow: hidden;\r
+       }\r
+.leaflet-popup-tip {\r
+       width: 15px;\r
+       height: 15px;\r
+       padding: 1px;\r
+\r
+       margin: -8px auto 0;\r
+\r
+       -webkit-transform: rotate(45deg);\r
+          -moz-transform: rotate(45deg);\r
+           -ms-transform: rotate(45deg);\r
+            -o-transform: rotate(45deg);\r
+               transform: rotate(45deg);\r
+       }\r
+.leaflet-popup-content-wrapper, .leaflet-popup-tip {\r
+       background: white;\r
+\r
+       box-shadow: 0 3px 14px rgba(0,0,0,0.35);\r
+       -webkit-box-shadow: 0 3px 18px rgba(0,0,0,0.33);\r
+       }\r
+.leaflet-container a.leaflet-popup-close-button {\r
+       position: absolute;\r
+       top: 0;\r
+       right: 0;\r
+       padding: 4px 5px 0 0;\r
+       text-align: center;\r
+       width: 18px;\r
+       height: 14px;\r
+       font: 16px/14px Tahoma, Verdana, sans-serif;\r
+       color: #c3c3c3;\r
+       text-decoration: none;\r
+       font-weight: bold;\r
+       }\r
+.leaflet-container a.leaflet-popup-close-button:hover {\r
+       color: #999;\r
+       }\r
+.leaflet-popup-scrolled {\r
+       overflow: auto;\r
+       border-bottom: 1px solid #ddd;\r
+       border-top: 1px solid #ddd;\r
+       }\r
+\r
+\r
+/* div icon */\r
+\r
+.leaflet-div-icon {\r
+       background: #fff;\r
+       border: 1px solid #666;\r
+       }\r
+.leaflet-editing-icon {\r
+       -webkit-border-radius: 2px;\r
+               border-radius: 2px;\r
+       }\r
diff --git a/vendor/assets/leaflet/leaflet.ie.css b/vendor/assets/leaflet/leaflet.ie.css
new file mode 100644 (file)
index 0000000..5b65c40
--- /dev/null
@@ -0,0 +1,47 @@
+.leaflet-vml-shape {\r
+       width: 1px;\r
+       height: 1px;\r
+       }\r
+.lvml {\r
+       behavior: url(#default#VML); \r
+       display: inline-block; \r
+       position: absolute;\r
+       }\r
+       \r
+.leaflet-control {\r
+       display: inline;\r
+       }\r
+\r
+.leaflet-popup-tip {\r
+       width: 21px;\r
+       _width: 27px;\r
+       margin: 0 auto;\r
+       _margin-top: -3px;\r
+       \r
+       filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);\r
+       -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";\r
+       }\r
+.leaflet-popup-tip-container {\r
+       margin-top: -1px;\r
+       }\r
+.leaflet-popup-content-wrapper, .leaflet-popup-tip {\r
+       border: 1px solid #bbb;\r
+       }\r
+\r
+.leaflet-control-zoom {\r
+       filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='#3F000000',EndColorStr='#3F000000');\r
+       }\r
+.leaflet-control-zoom a {\r
+       background-color: #eee;\r
+       }\r
+.leaflet-control-zoom a:hover {\r
+       background-color: #fff;\r
+       }\r
+.leaflet-control-layers-toggle {\r
+       }\r
+.leaflet-control-attribution, .leaflet-control-layers {\r
+       background: white;\r
+       }\r
+.leaflet-zoom-box {\r
+       filter: alpha(opacity=50);\r
+       }
\ No newline at end of file
diff --git a/vendor/assets/leaflet/leaflet.js b/vendor/assets/leaflet/leaflet.js
new file mode 100644 (file)
index 0000000..5503592
--- /dev/null
@@ -0,0 +1,8008 @@
+/*
+ Copyright (c) 2010-2012, CloudMade, Vladimir Agafonkin
+ Leaflet is an open-source JavaScript library for mobile-friendly interactive maps.
+ http://leafletjs.com
+*/
+(function (window, undefined) {
+
+var L, originalL;
+
+if (typeof exports !== undefined + '') {
+       L = exports;
+} else {
+       originalL = window.L;
+       L = {};
+
+       L.noConflict = function () {
+               window.L = originalL;
+               return this;
+       };
+
+       window.L = L;
+}
+
+L.version = '0.4.4';
+
+
+/*
+ * L.Util is a namespace for various utility functions.
+ */
+
+L.Util = {
+       extend: function (dest) { // (Object[, Object, ...]) ->
+               var sources = Array.prototype.slice.call(arguments, 1),
+                   i, j, len, src;
+
+               for (j = 0, len = sources.length; j < len; j++) {
+                       src = sources[j] || {};
+                       for (i in src) {
+                               if (src.hasOwnProperty(i)) {
+                                       dest[i] = src[i];
+                               }
+                       }
+               }
+               return dest;
+       },
+
+       bind: function (fn, obj) { // (Function, Object) -> Function
+               var args = arguments.length > 2 ? Array.prototype.slice.call(arguments, 2) : null;
+               return function () {
+                       return fn.apply(obj, args || arguments);
+               };
+       },
+
+       stamp: (function () {
+               var lastId = 0, key = '_leaflet_id';
+               return function (/*Object*/ obj) {
+                       obj[key] = obj[key] || ++lastId;
+                       return obj[key];
+               };
+       }()),
+
+       limitExecByInterval: function (fn, time, context) {
+               var lock, execOnUnlock;
+
+               return function wrapperFn() {
+                       var args = arguments;
+
+                       if (lock) {
+                               execOnUnlock = true;
+                               return;
+                       }
+
+                       lock = true;
+
+                       setTimeout(function () {
+                               lock = false;
+
+                               if (execOnUnlock) {
+                                       wrapperFn.apply(context, args);
+                                       execOnUnlock = false;
+                               }
+                       }, time);
+
+                       fn.apply(context, args);
+               };
+       },
+
+       falseFn: function () {
+               return false;
+       },
+
+       formatNum: function (num, digits) {
+               var pow = Math.pow(10, digits || 5);
+               return Math.round(num * pow) / pow;
+       },
+
+       splitWords: function (str) {
+               return str.replace(/^\s+|\s+$/g, '').split(/\s+/);
+       },
+
+       setOptions: function (obj, options) {
+               obj.options = L.extend({}, obj.options, options);
+               return obj.options;
+       },
+
+       getParamString: function (obj) {
+               var params = [];
+               for (var i in obj) {
+                       if (obj.hasOwnProperty(i)) {
+                               params.push(i + '=' + obj[i]);
+                       }
+               }
+               return '?' + params.join('&');
+       },
+
+       template: function (str, data) {
+               return str.replace(/\{ *([\w_]+) *\}/g, function (str, key) {
+                       var value = data[key];
+                       if (!data.hasOwnProperty(key)) {
+                               throw new Error('No value provided for variable ' + str);
+                       }
+                       return value;
+               });
+       },
+
+       emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
+};
+
+(function () {
+
+       // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
+
+       function getPrefixed(name) {
+               var i, fn,
+                   prefixes = ['webkit', 'moz', 'o', 'ms'];
+
+               for (i = 0; i < prefixes.length && !fn; i++) {
+                       fn = window[prefixes[i] + name];
+               }
+
+               return fn;
+       }
+
+       var lastTime = 0;
+
+       function timeoutDefer(fn) {
+               var time = +new Date(),
+                   timeToCall = Math.max(0, 16 - (time - lastTime));
+
+               lastTime = time + timeToCall;
+               return window.setTimeout(fn, timeToCall);
+       }
+
+       var requestFn = window.requestAnimationFrame ||
+               getPrefixed('RequestAnimationFrame') || timeoutDefer;
+
+       var cancelFn = window.cancelAnimationFrame ||
+               getPrefixed('CancelAnimationFrame') ||
+               getPrefixed('CancelRequestAnimationFrame') ||
+               function (id) { window.clearTimeout(id); };
+
+
+       L.Util.requestAnimFrame = function (fn, context, immediate, element) {
+               fn = L.bind(fn, context);
+
+               if (immediate && requestFn === timeoutDefer) {
+                       fn();
+               } else {
+                       return requestFn.call(window, fn, element);
+               }
+       };
+
+       L.Util.cancelAnimFrame = function (id) {
+               if (id) {
+                       cancelFn.call(window, id);
+               }
+       };
+
+}());
+
+// shortcuts for most used utility functions
+L.extend = L.Util.extend;
+L.bind = L.Util.bind;
+L.stamp = L.Util.stamp;
+L.setOptions = L.Util.setOptions;
+
+
+/*
+ * Class powers the OOP facilities of the library. Thanks to John Resig and Dean Edwards for inspiration!
+ */
+
+L.Class = function () {};
+
+L.Class.extend = function (/*Object*/ props) /*-> Class*/ {
+
+       // extended class with the new prototype
+       var NewClass = function () {
+               if (this.initialize) {
+                       this.initialize.apply(this, arguments);
+               }
+       };
+
+       // instantiate class without calling constructor
+       var F = function () {};
+       F.prototype = this.prototype;
+
+       var proto = new F();
+       proto.constructor = NewClass;
+
+       NewClass.prototype = proto;
+
+       //inherit parent's statics
+       for (var i in this) {
+               if (this.hasOwnProperty(i) && i !== 'prototype') {
+                       NewClass[i] = this[i];
+               }
+       }
+
+       // mix static properties into the class
+       if (props.statics) {
+               L.extend(NewClass, props.statics);
+               delete props.statics;
+       }
+
+       // mix includes into the prototype
+       if (props.includes) {
+               L.Util.extend.apply(null, [proto].concat(props.includes));
+               delete props.includes;
+       }
+
+       // merge options
+       if (props.options && proto.options) {
+               props.options = L.extend({}, proto.options, props.options);
+       }
+
+       // mix given properties into the prototype
+       L.extend(proto, props);
+
+       return NewClass;
+};
+
+
+// method for adding properties to prototype
+L.Class.include = function (props) {
+       L.extend(this.prototype, props);
+};
+
+L.Class.mergeOptions = function (options) {
+       L.extend(this.prototype.options, options);
+};
+
+
+/*
+ * L.Mixin.Events adds custom events functionality to Leaflet classes
+ */
+
+var key = '_leaflet_events';
+
+L.Mixin = {};
+
+L.Mixin.Events = {
+
+       addEventListener: function (types, fn, context) { // (String, Function[, Object]) or (Object[, Object])
+               var events = this[key] = this[key] || {},
+                       type, i, len;
+
+               // Types can be a map of types/handlers
+               if (typeof types === 'object') {
+                       for (type in types) {
+                               if (types.hasOwnProperty(type)) {
+                                       this.addEventListener(type, types[type], fn);
+                               }
+                       }
+
+                       return this;
+               }
+
+               types = L.Util.splitWords(types);
+
+               for (i = 0, len = types.length; i < len; i++) {
+                       events[types[i]] = events[types[i]] || [];
+                       events[types[i]].push({
+                               action: fn,
+                               context: context || this
+                       });
+               }
+
+               return this;
+       },
+
+       hasEventListeners: function (type) { // (String) -> Boolean
+               return (key in this) && (type in this[key]) && (this[key][type].length > 0);
+       },
+
+       removeEventListener: function (types, fn, context) { // (String[, Function, Object]) or (Object[, Object])
+               var events = this[key],
+                       type, i, len, listeners, j;
+
+               if (typeof types === 'object') {
+                       for (type in types) {
+                               if (types.hasOwnProperty(type)) {
+                                       this.removeEventListener(type, types[type], fn);
+                               }
+                       }
+
+                       return this;
+               }
+
+               types = L.Util.splitWords(types);
+
+               for (i = 0, len = types.length; i < len; i++) {
+
+                       if (this.hasEventListeners(types[i])) {
+                               listeners = events[types[i]];
+
+                               for (j = listeners.length - 1; j >= 0; j--) {
+                                       if (
+                                               (!fn || listeners[j].action === fn) &&
+                                               (!context || (listeners[j].context === context))
+                                       ) {
+                                               listeners.splice(j, 1);
+                                       }
+                               }
+                       }
+               }
+
+               return this;
+       },
+
+       fireEvent: function (type, data) { // (String[, Object])
+               if (!this.hasEventListeners(type)) {
+                       return this;
+               }
+
+               var event = L.extend({
+                       type: type,
+                       target: this
+               }, data);
+
+               var listeners = this[key][type].slice();
+
+               for (var i = 0, len = listeners.length; i < len; i++) {
+                       listeners[i].action.call(listeners[i].context || this, event);
+               }
+
+               return this;
+       }
+};
+
+L.Mixin.Events.on = L.Mixin.Events.addEventListener;
+L.Mixin.Events.off = L.Mixin.Events.removeEventListener;
+L.Mixin.Events.fire = L.Mixin.Events.fireEvent;
+
+
+(function () {
+
+       var ie = !!window.ActiveXObject,
+           // http://tanalin.com/en/articles/ie-version-js/
+           ie6 = ie && !window.XMLHttpRequest,
+           ie7 = ie && !document.querySelector,
+
+           // terrible browser detection to work around Safari / iOS / Android browser bugs
+           // see TileLayer._addTile and debug/hacks/jitter.html
+
+           ua = navigator.userAgent.toLowerCase(),
+           webkit = ua.indexOf("webkit") !== -1,
+           chrome = ua.indexOf("chrome") !== -1,
+           android = ua.indexOf("android") !== -1,
+           android23 = ua.search("android [23]") !== -1,
+
+           mobile = typeof orientation !== undefined + '',
+           msTouch = (window.navigator && window.navigator.msPointerEnabled && window.navigator.msMaxTouchPoints),
+           retina = (('devicePixelRatio' in window && window.devicePixelRatio > 1) ||
+                     ('matchMedia' in window && window.matchMedia("(min-resolution:144dpi)").matches)),
+
+           doc = document.documentElement,
+           ie3d = ie && ('transition' in doc.style),
+           webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()),
+           gecko3d = 'MozPerspective' in doc.style,
+           opera3d = 'OTransition' in doc.style,
+           any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d || opera3d);
+
+
+       var touch = !window.L_NO_TOUCH && (function () {
+
+               var startName = 'ontouchstart';
+
+               // IE10+ (We simulate these into touch* events in L.DomEvent and L.DomEvent.MsTouch) or WebKit, etc.
+               if (msTouch || (startName in doc)) {
+                       return true;
+               }
+
+               // Firefox/Gecko
+               var div = document.createElement('div'),
+                   supported = false;
+
+               if (!div.setAttribute) {
+                       return false;
+               }
+               div.setAttribute(startName, 'return;');
+
+               if (typeof div[startName] === 'function') {
+                       supported = true;
+               }
+
+               div.removeAttribute(startName);
+               div = null;
+
+               return supported;
+       }());
+
+
+       L.Browser = {
+               ie6: ie6,
+               ie7: ie7,
+               webkit: webkit,
+
+               android: android,
+               android23: android23,
+
+               chrome: chrome,
+
+               ie3d: ie3d,
+               webkit3d: webkit3d,
+               gecko3d: gecko3d,
+               opera3d: opera3d,
+               any3d: any3d,
+
+               mobile: mobile,
+               mobileWebkit: mobile && webkit,
+               mobileWebkit3d: mobile && webkit3d,
+               mobileOpera: mobile && window.opera,
+
+               touch: touch,
+               msTouch: msTouch,
+
+               retina: retina
+       };
+
+}());
+
+
+/*
+ * L.Point represents a point with x and y coordinates.
+ */
+
+L.Point = function (/*Number*/ x, /*Number*/ y, /*Boolean*/ round) {
+       this.x = (round ? Math.round(x) : x);
+       this.y = (round ? Math.round(y) : y);
+};
+
+L.Point.prototype = {
+
+       clone: function () {
+               return new L.Point(this.x, this.y);
+       },
+
+       // non-destructive, returns a new point
+       add: function (point) {
+               return this.clone()._add(L.point(point));
+       },
+
+       // destructive, used directly for performance in situations where it's safe to modify existing point
+       _add: function (point) {
+               this.x += point.x;
+               this.y += point.y;
+               return this;
+       },
+
+       subtract: function (point) {
+               return this.clone()._subtract(L.point(point));
+       },
+
+       _subtract: function (point) {
+               this.x -= point.x;
+               this.y -= point.y;
+               return this;
+       },
+
+       divideBy: function (num) {
+               return this.clone()._divideBy(num);
+       },
+
+       _divideBy: function (num) {
+               this.x /= num;
+               this.y /= num;
+               return this;
+       },
+
+       multiplyBy: function (num) {
+               return this.clone()._multiplyBy(num);
+       },
+
+       _multiplyBy: function (num) {
+               this.x *= num;
+               this.y *= num;
+               return this;
+       },
+
+       round: function () {
+               return this.clone()._round();
+       },
+
+       _round: function () {
+               this.x = Math.round(this.x);
+               this.y = Math.round(this.y);
+               return this;
+       },
+
+       floor: function () {
+               return this.clone()._floor();
+       },
+
+       _floor: function () {
+               this.x = Math.floor(this.x);
+               this.y = Math.floor(this.y);
+               return this;
+       },
+
+       distanceTo: function (point) {
+               point = L.point(point);
+
+               var x = point.x - this.x,
+                   y = point.y - this.y;
+
+               return Math.sqrt(x * x + y * y);
+       },
+
+       toString: function () {
+               return 'Point(' +
+                       L.Util.formatNum(this.x) + ', ' +
+                       L.Util.formatNum(this.y) + ')';
+       }
+};
+
+L.point = function (x, y, round) {
+       if (x instanceof L.Point) {
+               return x;
+       }
+       if (x instanceof Array) {
+               return new L.Point(x[0], x[1]);
+       }
+       if (isNaN(x)) {
+               return x;
+       }
+       return new L.Point(x, y, round);
+};
+
+
+/*
+ * L.Bounds represents a rectangular area on the screen in pixel coordinates.
+ */
+
+L.Bounds = L.Class.extend({
+
+       initialize: function (a, b) {   //(Point, Point) or Point[]
+               if (!a) { return; }
+
+               var points = b ? [a, b] : a;
+
+               for (var i = 0, len = points.length; i < len; i++) {
+                       this.extend(points[i]);
+               }
+       },
+
+       // extend the bounds to contain the given point
+       extend: function (point) { // (Point)
+               point = L.point(point);
+
+               if (!this.min && !this.max) {
+                       this.min = point.clone();
+                       this.max = point.clone();
+               } else {
+                       this.min.x = Math.min(point.x, this.min.x);
+                       this.max.x = Math.max(point.x, this.max.x);
+                       this.min.y = Math.min(point.y, this.min.y);
+                       this.max.y = Math.max(point.y, this.max.y);
+               }
+               return this;
+       },
+
+       getCenter: function (round) { // (Boolean) -> Point
+               return new L.Point(
+                       (this.min.x + this.max.x) / 2,
+                       (this.min.y + this.max.y) / 2, round);
+       },
+
+       getBottomLeft: function () { // -> Point
+               return new L.Point(this.min.x, this.max.y);
+       },
+
+       getTopRight: function () { // -> Point
+               return new L.Point(this.max.x, this.min.y);
+       },
+
+       contains: function (obj) { // (Bounds) or (Point) -> Boolean
+               var min, max;
+
+               if (typeof obj[0] === 'number' || obj instanceof L.Point) {
+                       obj = L.point(obj);
+               } else {
+                       obj = L.bounds(obj);
+               }
+
+               if (obj instanceof L.Bounds) {
+                       min = obj.min;
+                       max = obj.max;
+               } else {
+                       min = max = obj;
+               }
+
+               return (min.x >= this.min.x) &&
+                      (max.x <= this.max.x) &&
+                      (min.y >= this.min.y) &&
+                      (max.y <= this.max.y);
+       },
+
+       intersects: function (bounds) { // (Bounds) -> Boolean
+               bounds = L.bounds(bounds);
+
+               var min = this.min,
+                   max = this.max,
+                   min2 = bounds.min,
+                   max2 = bounds.max,
+                   xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
+                   yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
+
+               return xIntersects && yIntersects;
+       },
+
+       isValid: function () {
+               return !!(this.min && this.max);
+       }
+});
+
+L.bounds = function (a, b) { // (Bounds) or (Point, Point) or (Point[])
+       if (!a || a instanceof L.Bounds) {
+               return a;
+       }
+       return new L.Bounds(a, b);
+};
+
+
+/*
+ * L.Transformation is an utility class to perform simple point transformations through a 2d-matrix.
+ */
+
+L.Transformation = L.Class.extend({
+       initialize: function (/*Number*/ a, /*Number*/ b, /*Number*/ c, /*Number*/ d) {
+               this._a = a;
+               this._b = b;
+               this._c = c;
+               this._d = d;
+       },
+
+       transform: function (point, scale) {
+               return this._transform(point.clone(), scale);
+       },
+
+       // destructive transform (faster)
+       _transform: function (/*Point*/ point, /*Number*/ scale) /*-> Point*/ {
+               scale = scale || 1;
+               point.x = scale * (this._a * point.x + this._b);
+               point.y = scale * (this._c * point.y + this._d);
+               return point;
+       },
+
+       untransform: function (/*Point*/ point, /*Number*/ scale) /*-> Point*/ {
+               scale = scale || 1;
+               return new L.Point(
+                       (point.x / scale - this._b) / this._a,
+                       (point.y / scale - this._d) / this._c);
+       }
+});
+
+
+/*
+ * L.DomUtil contains various utility functions for working with DOM.
+ */
+
+L.DomUtil = {
+       get: function (id) {
+               return (typeof id === 'string' ? document.getElementById(id) : id);
+       },
+
+       getStyle: function (el, style) {
+
+               var value = el.style[style];
+
+               if (!value && el.currentStyle) {
+                       value = el.currentStyle[style];
+               }
+
+               if ((!value || value === 'auto') && document.defaultView) {
+                       var css = document.defaultView.getComputedStyle(el, null);
+                       value = css ? css[style] : null;
+               }
+
+               return value === 'auto' ? null : value;
+       },
+
+       getViewportOffset: function (element) {
+
+               var top = 0,
+                   left = 0,
+                   el = element,
+                   docBody = document.body,
+                   pos,
+                   ie7 = L.Browser.ie7;
+
+               do {
+                       top  += el.offsetTop  || 0;
+                       left += el.offsetLeft || 0;
+                       pos = L.DomUtil.getStyle(el, 'position');
+
+                       if (el.offsetParent === docBody && pos === 'absolute') { break; }
+
+                       if (pos === 'fixed') {
+                               top  += docBody.scrollTop  || 0;
+                               left += docBody.scrollLeft || 0;
+                               break;
+                       }
+                       el = el.offsetParent;
+
+               } while (el);
+
+               el = element;
+
+               do {
+                       if (el === docBody) { break; }
+
+                       top  -= el.scrollTop  || 0;
+                       left -= el.scrollLeft || 0;
+
+                       // webkit (and ie <= 7) handles RTL scrollLeft different to everyone else
+                       // https://code.google.com/p/closure-library/source/browse/trunk/closure/goog/style/bidi.js
+                       if (!L.DomUtil.documentIsLtr() && (L.Browser.webkit || ie7)) {
+                               left += el.scrollWidth - el.clientWidth;
+
+                               // ie7 shows the scrollbar by default and provides clientWidth counting it, so we
+                               // need to add it back in if it is visible; scrollbar is on the left as we are RTL
+                               if (ie7 && L.DomUtil.getStyle(el, 'overflow-y') !== 'hidden' &&
+                                          L.DomUtil.getStyle(el, 'overflow') !== 'hidden') {
+                                       left += 17;
+                               }
+                       }
+
+                       el = el.parentNode;
+               } while (el);
+
+               return new L.Point(left, top);
+       },
+
+       documentIsLtr: function () {
+               if (!L.DomUtil._docIsLtrCached) {
+                       L.DomUtil._docIsLtrCached = true;
+                       L.DomUtil._docIsLtr = L.DomUtil.getStyle(document.body, 'direction') === "ltr";
+               }
+               return L.DomUtil._docIsLtr;
+       },
+
+       create: function (tagName, className, container) {
+
+               var el = document.createElement(tagName);
+               el.className = className;
+
+               if (container) {
+                       container.appendChild(el);
+               }
+
+               return el;
+       },
+
+       disableTextSelection: function () {
+               if (document.selection && document.selection.empty) {
+                       document.selection.empty();
+               }
+               if (!this._onselectstart) {
+                       this._onselectstart = document.onselectstart;
+                       document.onselectstart = L.Util.falseFn;
+               }
+       },
+
+       enableTextSelection: function () {
+               if (document.onselectstart === L.Util.falseFn) {
+                       document.onselectstart = this._onselectstart;
+                       this._onselectstart = null;
+               }
+       },
+
+       hasClass: function (el, name) {
+               return (el.className.length > 0) &&
+                       new RegExp("(^|\\s)" + name + "(\\s|$)").test(el.className);
+       },
+
+       addClass: function (el, name) {
+               if (!L.DomUtil.hasClass(el, name)) {
+                       el.className += (el.className ? ' ' : '') + name;
+               }
+       },
+
+       removeClass: function (el, name) {
+
+               function replaceFn(w, match) {
+                       if (match === name) { return ''; }
+                       return w;
+               }
+
+               el.className = el.className
+                       .replace(/(\S+)\s*/g, replaceFn)
+                       .replace(/(^\s+|\s+$)/, '');
+       },
+
+       setOpacity: function (el, value) {
+
+               if ('opacity' in el.style) {
+                       el.style.opacity = value;
+
+               } else if ('filter' in el.style) {
+
+                       var filter = false,
+                           filterName = 'DXImageTransform.Microsoft.Alpha';
+
+                       // filters collection throws an error if we try to retrieve a filter that doesn't exist
+                       try { filter = el.filters.item(filterName); } catch (e) {}
+
+                       value = Math.round(value * 100);
+
+                       if (filter) {
+                               filter.Enabled = (value !== 100);
+                               filter.Opacity = value;
+                       } else {
+                               el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
+                       }
+               }
+       },
+
+       testProp: function (props) {
+
+               var style = document.documentElement.style;
+
+               for (var i = 0; i < props.length; i++) {
+                       if (props[i] in style) {
+                               return props[i];
+                       }
+               }
+               return false;
+       },
+
+       getTranslateString: function (point) {
+               // on WebKit browsers (Chrome/Safari/iOS Safari/Android) using translate3d instead of translate
+               // makes animation smoother as it ensures HW accel is used. Firefox 13 doesn't care
+               // (same speed either way), Opera 12 doesn't support translate3d
+
+               var is3d = L.Browser.webkit3d,
+                   open = 'translate' + (is3d ? '3d' : '') + '(',
+                   close = (is3d ? ',0' : '') + ')';
+
+               return open + point.x + 'px,' + point.y + 'px' + close;
+       },
+
+       getScaleString: function (scale, origin) {
+
+               var preTranslateStr = L.DomUtil.getTranslateString(origin.add(origin.multiplyBy(-1 * scale))),
+                   scaleStr = ' scale(' + scale + ') ';
+
+               return preTranslateStr + scaleStr;
+       },
+
+       setPosition: function (el, point, disable3D) { // (HTMLElement, Point[, Boolean])
+
+               el._leaflet_pos = point;
+
+               if (!disable3D && L.Browser.any3d) {
+                       el.style[L.DomUtil.TRANSFORM] =  L.DomUtil.getTranslateString(point);
+
+                       // workaround for Android 2/3 stability (https://github.com/CloudMade/Leaflet/issues/69)
+                       if (L.Browser.mobileWebkit3d) {
+                               el.style.WebkitBackfaceVisibility = 'hidden';
+                       }
+               } else {
+                       el.style.left = point.x + 'px';
+                       el.style.top = point.y + 'px';
+               }
+       },
+
+       getPosition: function (el) {
+               // this method is only used for elements previously positioned using setPosition,
+               // so it's safe to cache the position for performance
+               return el._leaflet_pos;
+       }
+};
+
+
+// prefix style property names
+
+L.DomUtil.TRANSFORM = L.DomUtil.testProp(
+        ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
+
+L.DomUtil.TRANSITION = L.DomUtil.testProp(
+        ['transition', 'webkitTransition', 'OTransition', 'MozTransition', 'msTransition']);
+
+L.DomUtil.TRANSITION_END =
+        L.DomUtil.TRANSITION === 'webkitTransition' || L.DomUtil.TRANSITION === 'OTransition' ?
+        L.DomUtil.TRANSITION + 'End' : 'transitionend';
+
+
+/*
+       CM.LatLng represents a geographical point with latitude and longtitude coordinates.
+*/
+
+L.LatLng = function (rawLat, rawLng, noWrap) { // (Number, Number[, Boolean])
+       var lat = parseFloat(rawLat),
+           lng = parseFloat(rawLng);
+
+       if (isNaN(lat) || isNaN(lng)) {
+               throw new Error('Invalid LatLng object: (' + rawLat + ', ' + rawLng + ')');
+       }
+
+       if (noWrap !== true) {
+               lat = Math.max(Math.min(lat, 90), -90);                                 // clamp latitude into -90..90
+               lng = (lng + 180) % 360 + ((lng < -180 || lng === 180) ? 180 : -180);   // wrap longtitude into -180..180
+       }
+
+       this.lat = lat;
+       this.lng = lng;
+};
+
+L.extend(L.LatLng, {
+       DEG_TO_RAD: Math.PI / 180,
+       RAD_TO_DEG: 180 / Math.PI,
+       MAX_MARGIN: 1.0E-9 // max margin of error for the "equals" check
+});
+
+L.LatLng.prototype = {
+       equals: function (obj) { // (LatLng) -> Boolean
+               if (!obj) { return false; }
+
+               obj = L.latLng(obj);
+
+               var margin = Math.max(Math.abs(this.lat - obj.lat), Math.abs(this.lng - obj.lng));
+               return margin <= L.LatLng.MAX_MARGIN;
+       },
+
+       toString: function (precision) { // -> String
+               return 'LatLng(' +
+                       L.Util.formatNum(this.lat, precision) + ', ' +
+                       L.Util.formatNum(this.lng, precision) + ')';
+       },
+
+       // Haversine distance formula, see http://en.wikipedia.org/wiki/Haversine_formula
+       distanceTo: function (other) { // (LatLng) -> Number
+               other = L.latLng(other);
+
+               var R = 6378137, // earth radius in meters
+                   d2r = L.LatLng.DEG_TO_RAD,
+                   dLat = (other.lat - this.lat) * d2r,
+                   dLon = (other.lng - this.lng) * d2r,
+                   lat1 = this.lat * d2r,
+                   lat2 = other.lat * d2r,
+                   sin1 = Math.sin(dLat / 2),
+                   sin2 = Math.sin(dLon / 2);
+
+               var a = sin1 * sin1 + sin2 * sin2 * Math.cos(lat1) * Math.cos(lat2);
+
+               return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+       }
+};
+
+L.latLng = function (a, b, c) { // (LatLng) or ([Number, Number]) or (Number, Number, Boolean)
+       if (a instanceof L.LatLng) {
+               return a;
+       }
+       if (a instanceof Array) {
+               return new L.LatLng(a[0], a[1]);
+       }
+       if (isNaN(a)) {
+               return a;
+       }
+       return new L.LatLng(a, b, c);
+};
+
+
+
+/*
+ * L.LatLngBounds represents a rectangular area on the map in geographical coordinates.
+ */
+
+L.LatLngBounds = L.Class.extend({
+       initialize: function (southWest, northEast) {   // (LatLng, LatLng) or (LatLng[])
+               if (!southWest) { return; }
+
+               var latlngs = northEast ? [southWest, northEast] : southWest;
+
+               for (var i = 0, len = latlngs.length; i < len; i++) {
+                       this.extend(latlngs[i]);
+               }
+       },
+
+       // extend the bounds to contain the given point or bounds
+       extend: function (obj) { // (LatLng) or (LatLngBounds)
+               if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {
+                       obj = L.latLng(obj);
+               } else {
+                       obj = L.latLngBounds(obj);
+               }
+
+               if (obj instanceof L.LatLng) {
+                       if (!this._southWest && !this._northEast) {
+                               this._southWest = new L.LatLng(obj.lat, obj.lng, true);
+                               this._northEast = new L.LatLng(obj.lat, obj.lng, true);
+                       } else {
+                               this._southWest.lat = Math.min(obj.lat, this._southWest.lat);
+                               this._southWest.lng = Math.min(obj.lng, this._southWest.lng);
+
+                               this._northEast.lat = Math.max(obj.lat, this._northEast.lat);
+                               this._northEast.lng = Math.max(obj.lng, this._northEast.lng);
+                       }
+               } else if (obj instanceof L.LatLngBounds) {
+                       this.extend(obj._southWest);
+                       this.extend(obj._northEast);
+               }
+               return this;
+       },
+
+       // extend the bounds by a percentage
+       pad: function (bufferRatio) { // (Number) -> LatLngBounds
+               var sw = this._southWest,
+                   ne = this._northEast,
+                   heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
+                   widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
+
+               return new L.LatLngBounds(
+                       new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
+                       new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
+       },
+
+       getCenter: function () { // -> LatLng
+               return new L.LatLng(
+                       (this._southWest.lat + this._northEast.lat) / 2,
+                       (this._southWest.lng + this._northEast.lng) / 2);
+       },
+
+       getSouthWest: function () {
+               return this._southWest;
+       },
+
+       getNorthEast: function () {
+               return this._northEast;
+       },
+
+       getNorthWest: function () {
+               return new L.LatLng(this._northEast.lat, this._southWest.lng, true);
+       },
+
+       getSouthEast: function () {
+               return new L.LatLng(this._southWest.lat, this._northEast.lng, true);
+       },
+
+       contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
+               if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {
+                       obj = L.latLng(obj);
+               } else {
+                       obj = L.latLngBounds(obj);
+               }
+
+               var sw = this._southWest,
+                   ne = this._northEast,
+                   sw2, ne2;
+
+               if (obj instanceof L.LatLngBounds) {
+                       sw2 = obj.getSouthWest();
+                       ne2 = obj.getNorthEast();
+               } else {
+                       sw2 = ne2 = obj;
+               }
+
+               return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
+                      (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
+       },
+
+       intersects: function (bounds) { // (LatLngBounds)
+               bounds = L.latLngBounds(bounds);
+
+               var sw = this._southWest,
+                   ne = this._northEast,
+                   sw2 = bounds.getSouthWest(),
+                   ne2 = bounds.getNorthEast(),
+
+                   latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
+                   lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
+
+               return latIntersects && lngIntersects;
+       },
+
+       toBBoxString: function () {
+               var sw = this._southWest,
+                   ne = this._northEast;
+
+               return [sw.lng, sw.lat, ne.lng, ne.lat].join(',');
+       },
+
+       equals: function (bounds) { // (LatLngBounds)
+               if (!bounds) { return false; }
+
+               bounds = L.latLngBounds(bounds);
+
+               return this._southWest.equals(bounds.getSouthWest()) &&
+                      this._northEast.equals(bounds.getNorthEast());
+       },
+
+       isValid: function () {
+               return !!(this._southWest && this._northEast);
+       }
+});
+
+//TODO International date line?
+
+L.latLngBounds = function (a, b) { // (LatLngBounds) or (LatLng, LatLng)
+       if (!a || a instanceof L.LatLngBounds) {
+               return a;
+       }
+       return new L.LatLngBounds(a, b);
+};
+
+
+/*
+ * L.Projection contains various geographical projections used by CRS classes.
+ */
+
+L.Projection = {};
+
+
+
+L.Projection.SphericalMercator = {
+       MAX_LATITUDE: 85.0511287798,
+
+       project: function (latlng) { // (LatLng) -> Point
+               var d = L.LatLng.DEG_TO_RAD,
+                   max = this.MAX_LATITUDE,
+                   lat = Math.max(Math.min(max, latlng.lat), -max),
+                   x = latlng.lng * d,
+                   y = lat * d;
+
+               y = Math.log(Math.tan((Math.PI / 4) + (y / 2)));
+
+               return new L.Point(x, y);
+       },
+
+       unproject: function (point) { // (Point, Boolean) -> LatLng
+               var d = L.LatLng.RAD_TO_DEG,
+                   lng = point.x * d,
+                   lat = (2 * Math.atan(Math.exp(point.y)) - (Math.PI / 2)) * d;
+
+               // TODO refactor LatLng wrapping
+               return new L.LatLng(lat, lng, true);
+       }
+};
+
+
+
+L.Projection.LonLat = {
+       project: function (latlng) {
+               return new L.Point(latlng.lng, latlng.lat);
+       },
+
+       unproject: function (point) {
+               return new L.LatLng(point.y, point.x, true);
+       }
+};
+
+
+
+L.CRS = {
+       latLngToPoint: function (latlng, zoom) { // (LatLng, Number) -> Point
+               var projectedPoint = this.projection.project(latlng),
+                   scale = this.scale(zoom);
+
+               return this.transformation._transform(projectedPoint, scale);
+       },
+
+       pointToLatLng: function (point, zoom) { // (Point, Number[, Boolean]) -> LatLng
+               var scale = this.scale(zoom),
+                   untransformedPoint = this.transformation.untransform(point, scale);
+
+               return this.projection.unproject(untransformedPoint);
+       },
+
+       project: function (latlng) {
+               return this.projection.project(latlng);
+       },
+
+       scale: function (zoom) {
+               return 256 * Math.pow(2, zoom);
+       }
+};
+
+
+
+L.CRS.Simple = L.extend({}, L.CRS, {
+       projection: L.Projection.LonLat,
+       transformation: new L.Transformation(1, 0, 1, 0)
+});
+
+
+
+L.CRS.EPSG3857 = L.extend({}, L.CRS, {
+       code: 'EPSG:3857',
+
+       projection: L.Projection.SphericalMercator,
+       transformation: new L.Transformation(0.5 / Math.PI, 0.5, -0.5 / Math.PI, 0.5),
+
+       project: function (latlng) { // (LatLng) -> Point
+               var projectedPoint = this.projection.project(latlng),
+                   earthRadius = 6378137;
+               return projectedPoint.multiplyBy(earthRadius);
+       }
+});
+
+L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, {
+       code: 'EPSG:900913'
+});
+
+
+
+L.CRS.EPSG4326 = L.extend({}, L.CRS, {
+       code: 'EPSG:4326',
+
+       projection: L.Projection.LonLat,
+       transformation: new L.Transformation(1 / 360, 0.5, -1 / 360, 0.5)
+});
+
+
+/*
+ * L.Map is the central class of the API - it is used to create a map.
+ */
+
+L.Map = L.Class.extend({
+
+       includes: L.Mixin.Events,
+
+       options: {
+               crs: L.CRS.EPSG3857,
+
+               /*
+               center: LatLng,
+               zoom: Number,
+               layers: Array,
+               */
+
+               fadeAnimation: L.DomUtil.TRANSITION && !L.Browser.android23,
+               trackResize: true,
+               markerZoomAnimation: L.DomUtil.TRANSITION && L.Browser.any3d
+       },
+
+       initialize: function (id, options) { // (HTMLElement or String, Object)
+               options = L.setOptions(this, options);
+
+               this._initContainer(id);
+               this._initLayout();
+               this._initHooks();
+               this._initEvents();
+
+               if (options.maxBounds) {
+                       this.setMaxBounds(options.maxBounds);
+               }
+
+               if (options.center && options.zoom !== undefined) {
+                       this.setView(L.latLng(options.center), options.zoom, true);
+               }
+
+               this._initLayers(options.layers);
+       },
+
+
+       // public methods that modify map state
+
+       // replaced by animation-powered implementation in Map.PanAnimation.js
+       setView: function (center, zoom) {
+               this._resetView(L.latLng(center), this._limitZoom(zoom));
+               return this;
+       },
+
+       setZoom: function (zoom) { // (Number)
+               return this.setView(this.getCenter(), zoom);
+       },
+
+       zoomIn: function (delta) {
+               return this.setZoom(this._zoom + (delta || 1));
+       },
+
+       zoomOut: function (delta) {
+               return this.setZoom(this._zoom - (delta || 1));
+       },
+
+       fitBounds: function (bounds) { // (LatLngBounds)
+               var zoom = this.getBoundsZoom(bounds);
+               return this.setView(L.latLngBounds(bounds).getCenter(), zoom);
+       },
+
+       fitWorld: function () {
+               var sw = new L.LatLng(-60, -170),
+                   ne = new L.LatLng(85, 179);
+
+               return this.fitBounds(new L.LatLngBounds(sw, ne));
+       },
+
+       panTo: function (center) { // (LatLng)
+               return this.setView(center, this._zoom);
+       },
+
+       panBy: function (offset) { // (Point)
+               // replaced with animated panBy in Map.Animation.js
+               this.fire('movestart');
+
+               this._rawPanBy(L.point(offset));
+
+               this.fire('move');
+               return this.fire('moveend');
+       },
+
+       setMaxBounds: function (bounds) {
+               bounds = L.latLngBounds(bounds);
+
+               this.options.maxBounds = bounds;
+
+               if (!bounds) {
+                       this._boundsMinZoom = null;
+                       return this;
+               }
+
+               var minZoom = this.getBoundsZoom(bounds, true);
+
+               this._boundsMinZoom = minZoom;
+
+               if (this._loaded) {
+                       if (this._zoom < minZoom) {
+                               this.setView(bounds.getCenter(), minZoom);
+                       } else {
+                               this.panInsideBounds(bounds);
+                       }
+               }
+
+               return this;
+       },
+
+       panInsideBounds: function (bounds) {
+               bounds = L.latLngBounds(bounds);
+
+               var viewBounds = this.getBounds(),
+                   viewSw = this.project(viewBounds.getSouthWest()),
+                   viewNe = this.project(viewBounds.getNorthEast()),
+                   sw = this.project(bounds.getSouthWest()),
+                   ne = this.project(bounds.getNorthEast()),
+                   dx = 0,
+                   dy = 0;
+
+               if (viewNe.y < ne.y) { // north
+                       dy = ne.y - viewNe.y;
+               }
+               if (viewNe.x > ne.x) { // east
+                       dx = ne.x - viewNe.x;
+               }
+               if (viewSw.y > sw.y) { // south
+                       dy = sw.y - viewSw.y;
+               }
+               if (viewSw.x < sw.x) { // west
+                       dx = sw.x - viewSw.x;
+               }
+
+               return this.panBy(new L.Point(dx, dy, true));
+       },
+
+       addLayer: function (layer) {
+               // TODO method is too big, refactor
+
+               var id = L.stamp(layer);
+
+               if (this._layers[id]) { return this; }
+
+               this._layers[id] = layer;
+
+               // TODO getMaxZoom, getMinZoom in ILayer (instead of options)
+               if (layer.options && !isNaN(layer.options.maxZoom)) {
+                       this._layersMaxZoom = Math.max(this._layersMaxZoom || 0, layer.options.maxZoom);
+               }
+               if (layer.options && !isNaN(layer.options.minZoom)) {
+                       this._layersMinZoom = Math.min(this._layersMinZoom || Infinity, layer.options.minZoom);
+               }
+
+               // TODO looks ugly, refactor!!!
+               if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
+                       this._tileLayersNum++;
+            this._tileLayersToLoad++;
+            layer.on('load', this._onTileLayerLoad, this);
+               }
+
+               this.whenReady(function () {
+                       layer.onAdd(this);
+                       this.fire('layeradd', {layer: layer});
+               }, this);
+
+               return this;
+       },
+
+       removeLayer: function (layer) {
+               var id = L.stamp(layer);
+
+               if (!this._layers[id]) { return; }
+
+               layer.onRemove(this);
+
+               delete this._layers[id];
+
+               // TODO looks ugly, refactor
+               if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
+                       this._tileLayersNum--;
+            this._tileLayersToLoad--;
+            layer.off('load', this._onTileLayerLoad, this);
+               }
+
+               return this.fire('layerremove', {layer: layer});
+       },
+
+       hasLayer: function (layer) {
+               var id = L.stamp(layer);
+               return this._layers.hasOwnProperty(id);
+       },
+
+       invalidateSize: function (animate) {
+               var oldSize = this.getSize();
+
+               this._sizeChanged = true;
+
+               if (this.options.maxBounds) {
+                       this.setMaxBounds(this.options.maxBounds);
+               }
+
+               if (!this._loaded) { return this; }
+
+               var offset = oldSize._subtract(this.getSize())._divideBy(2)._round();
+
+               if (animate === true) {
+                       this.panBy(offset);
+               } else {
+                       this._rawPanBy(offset);
+
+                       this.fire('move');
+
+                       clearTimeout(this._sizeTimer);
+                       this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200);
+               }
+               return this;
+       },
+
+       // TODO handler.addTo
+       addHandler: function (name, HandlerClass) {
+               if (!HandlerClass) { return; }
+
+               this[name] = new HandlerClass(this);
+
+               if (this.options[name]) {
+                       this[name].enable();
+               }
+
+               return this;
+       },
+
+
+       // public methods for getting map state
+
+       getCenter: function () { // (Boolean) -> LatLng
+               return this.layerPointToLatLng(this._getCenterLayerPoint());
+       },
+
+       getZoom: function () {
+               return this._zoom;
+       },
+
+       getBounds: function () {
+               var bounds = this.getPixelBounds(),
+                   sw = this.unproject(bounds.getBottomLeft()),
+                   ne = this.unproject(bounds.getTopRight());
+
+               return new L.LatLngBounds(sw, ne);
+       },
+
+       getMinZoom: function () {
+               var z1 = this.options.minZoom || 0,
+                   z2 = this._layersMinZoom || 0,
+                   z3 = this._boundsMinZoom || 0;
+
+               return Math.max(z1, z2, z3);
+       },
+
+       getMaxZoom: function () {
+               var z1 = this.options.maxZoom === undefined ? Infinity : this.options.maxZoom,
+                   z2 = this._layersMaxZoom  === undefined ? Infinity : this._layersMaxZoom;
+
+               return Math.min(z1, z2);
+       },
+
+       getBoundsZoom: function (bounds, inside) { // (LatLngBounds, Boolean) -> Number
+               bounds = L.latLngBounds(bounds);
+
+               var size = this.getSize(),
+                   zoom = this.options.minZoom || 0,
+                   maxZoom = this.getMaxZoom(),
+                   ne = bounds.getNorthEast(),
+                   sw = bounds.getSouthWest(),
+                   boundsSize,
+                   nePoint,
+                   swPoint,
+                   zoomNotFound = true;
+
+               if (inside) {
+                       zoom--;
+               }
+
+               do {
+                       zoom++;
+                       nePoint = this.project(ne, zoom);
+                       swPoint = this.project(sw, zoom);
+
+                       boundsSize = new L.Point(
+                               Math.abs(nePoint.x - swPoint.x),
+                               Math.abs(swPoint.y - nePoint.y));
+
+                       if (!inside) {
+                               zoomNotFound = boundsSize.x <= size.x && boundsSize.y <= size.y;
+                       } else {
+                               zoomNotFound = boundsSize.x < size.x || boundsSize.y < size.y;
+                       }
+               } while (zoomNotFound && zoom <= maxZoom);
+
+               if (zoomNotFound && inside) {
+                       return null;
+               }
+
+               return inside ? zoom : zoom - 1;
+       },
+
+       getSize: function () {
+               if (!this._size || this._sizeChanged) {
+                       this._size = new L.Point(
+                               this._container.clientWidth,
+                               this._container.clientHeight);
+
+                       this._sizeChanged = false;
+               }
+               return this._size.clone();
+       },
+
+       getPixelBounds: function () {
+               var topLeftPoint = this._getTopLeftPoint();
+               return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
+       },
+
+       getPixelOrigin: function () {
+               return this._initialTopLeftPoint;
+       },
+
+       getPanes: function () {
+               return this._panes;
+       },
+
+       getContainer: function () {
+               return this._container;
+       },
+
+
+       // TODO replace with universal implementation after refactoring projections
+
+       getZoomScale: function (toZoom) {
+               var crs = this.options.crs;
+               return crs.scale(toZoom) / crs.scale(this._zoom);
+       },
+
+       getScaleZoom: function (scale) {
+               return this._zoom + (Math.log(scale) / Math.LN2);
+       },
+
+
+       // conversion methods
+
+       project: function (latlng, zoom) { // (LatLng[, Number]) -> Point
+               zoom = zoom === undefined ? this._zoom : zoom;
+               return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);
+       },
+
+       unproject: function (point, zoom) { // (Point[, Number]) -> LatLng
+               zoom = zoom === undefined ? this._zoom : zoom;
+               return this.options.crs.pointToLatLng(L.point(point), zoom);
+       },
+
+       layerPointToLatLng: function (point) { // (Point)
+               var projectedPoint = L.point(point).add(this._initialTopLeftPoint);
+               return this.unproject(projectedPoint);
+       },
+
+       latLngToLayerPoint: function (latlng) { // (LatLng)
+               var projectedPoint = this.project(L.latLng(latlng))._round();
+               return projectedPoint._subtract(this._initialTopLeftPoint);
+       },
+
+       containerPointToLayerPoint: function (point) { // (Point)
+               return L.point(point).subtract(this._getMapPanePos());
+       },
+
+       layerPointToContainerPoint: function (point) { // (Point)
+               return L.point(point).add(this._getMapPanePos());
+       },
+
+       containerPointToLatLng: function (point) {
+               var layerPoint = this.containerPointToLayerPoint(L.point(point));
+               return this.layerPointToLatLng(layerPoint);
+       },
+
+       latLngToContainerPoint: function (latlng) {
+               return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));
+       },
+
+       mouseEventToContainerPoint: function (e) { // (MouseEvent)
+               return L.DomEvent.getMousePosition(e, this._container);
+       },
+
+       mouseEventToLayerPoint: function (e) { // (MouseEvent)
+               return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
+       },
+
+       mouseEventToLatLng: function (e) { // (MouseEvent)
+               return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
+       },
+
+
+       // map initialization methods
+
+       _initContainer: function (id) {
+               var container = this._container = L.DomUtil.get(id);
+
+               if (container._leaflet) {
+                       throw new Error("Map container is already initialized.");
+               }
+
+               container._leaflet = true;
+       },
+
+       _initLayout: function () {
+               var container = this._container;
+
+               container.innerHTML = '';
+               L.DomUtil.addClass(container, 'leaflet-container');
+
+               if (L.Browser.touch) {
+                       L.DomUtil.addClass(container, 'leaflet-touch');
+               }
+
+               if (this.options.fadeAnimation) {
+                       L.DomUtil.addClass(container, 'leaflet-fade-anim');
+               }
+
+               var position = L.DomUtil.getStyle(container, 'position');
+
+               if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
+                       container.style.position = 'relative';
+               }
+
+               this._initPanes();
+
+               if (this._initControlPos) {
+                       this._initControlPos();
+               }
+       },
+
+       _initPanes: function () {
+               var panes = this._panes = {};
+
+               this._mapPane = panes.mapPane = this._createPane('leaflet-map-pane', this._container);
+
+               this._tilePane = panes.tilePane = this._createPane('leaflet-tile-pane', this._mapPane);
+               panes.objectsPane = this._createPane('leaflet-objects-pane', this._mapPane);
+               panes.shadowPane = this._createPane('leaflet-shadow-pane');
+               panes.overlayPane = this._createPane('leaflet-overlay-pane');
+               panes.markerPane = this._createPane('leaflet-marker-pane');
+               panes.popupPane = this._createPane('leaflet-popup-pane');
+
+               var zoomHide = ' leaflet-zoom-hide';
+
+               if (!this.options.markerZoomAnimation) {
+                       L.DomUtil.addClass(panes.markerPane, zoomHide);
+                       L.DomUtil.addClass(panes.shadowPane, zoomHide);
+                       L.DomUtil.addClass(panes.popupPane, zoomHide);
+               }
+       },
+
+       _createPane: function (className, container) {
+               return L.DomUtil.create('div', className, container || this._panes.objectsPane);
+       },
+
+       _initializers: [],
+
+       _initHooks: function () {
+               var i, len;
+               for (i = 0, len = this._initializers.length; i < len; i++) {
+                       this._initializers[i].call(this);
+               }
+       },
+
+       _initLayers: function (layers) {
+               layers = layers ? (layers instanceof Array ? layers : [layers]) : [];
+
+               this._layers = {};
+               this._tileLayersNum = 0;
+
+               var i, len;
+
+               for (i = 0, len = layers.length; i < len; i++) {
+                       this.addLayer(layers[i]);
+               }
+       },
+
+
+       // private methods that modify map state
+
+       _resetView: function (center, zoom, preserveMapOffset, afterZoomAnim) {
+
+               var zoomChanged = (this._zoom !== zoom);
+
+               if (!afterZoomAnim) {
+                       this.fire('movestart');
+
+                       if (zoomChanged) {
+                               this.fire('zoomstart');
+                       }
+               }
+
+               this._zoom = zoom;
+
+               this._initialTopLeftPoint = this._getNewTopLeftPoint(center);
+
+               if (!preserveMapOffset) {
+                       L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
+               } else {
+                       this._initialTopLeftPoint._add(this._getMapPanePos());
+               }
+
+               this._tileLayersToLoad = this._tileLayersNum;
+
+               var loading = !this._loaded;
+               this._loaded = true;
+
+               this.fire('viewreset', {hard: !preserveMapOffset});
+
+               this.fire('move');
+
+               if (zoomChanged || afterZoomAnim) {
+                       this.fire('zoomend');
+               }
+
+               this.fire('moveend', {hard: !preserveMapOffset});
+
+               if (loading) {
+                       this.fire('load');
+               }
+       },
+
+       _rawPanBy: function (offset) {
+               L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
+       },
+
+
+       // map events
+
+       _initEvents: function () {
+               if (!L.DomEvent) { return; }
+
+               L.DomEvent.on(this._container, 'click', this._onMouseClick, this);
+
+               var events = ['dblclick', 'mousedown', 'mouseup', 'mouseenter',
+                             'mouseleave', 'mousemove', 'contextmenu'],
+                   i, len;
+
+               for (i = 0, len = events.length; i < len; i++) {
+                       L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this);
+               }
+
+               if (this.options.trackResize) {
+                       L.DomEvent.on(window, 'resize', this._onResize, this);
+               }
+       },
+
+       _onResize: function () {
+               L.Util.cancelAnimFrame(this._resizeRequest);
+               this._resizeRequest = L.Util.requestAnimFrame(
+                       this.invalidateSize, this, false, this._container);
+       },
+
+       _onMouseClick: function (e) {
+               if (!this._loaded || (this.dragging && this.dragging.moved())) { return; }
+
+               this.fire('preclick');
+               this._fireMouseEvent(e);
+       },
+
+       _fireMouseEvent: function (e) {
+               if (!this._loaded) { return; }
+
+               var type = e.type;
+
+               type = (type === 'mouseenter' ? 'mouseover' : (type === 'mouseleave' ? 'mouseout' : type));
+
+               if (!this.hasEventListeners(type)) { return; }
+
+               if (type === 'contextmenu') {
+                       L.DomEvent.preventDefault(e);
+               }
+
+               var containerPoint = this.mouseEventToContainerPoint(e),
+                   layerPoint = this.containerPointToLayerPoint(containerPoint),
+                   latlng = this.layerPointToLatLng(layerPoint);
+
+               this.fire(type, {
+                       latlng: latlng,
+                       layerPoint: layerPoint,
+                       containerPoint: containerPoint,
+                       originalEvent: e
+               });
+       },
+
+       _onTileLayerLoad: function () {
+               // TODO super-ugly, refactor!!!
+               // clear scaled tiles after all new tiles are loaded (for performance)
+               this._tileLayersToLoad--;
+               if (this._tileLayersNum && !this._tileLayersToLoad && this._tileBg) {
+                       clearTimeout(this._clearTileBgTimer);
+                       this._clearTileBgTimer = setTimeout(L.bind(this._clearTileBg, this), 500);
+               }
+       },
+
+       whenReady: function (callback, context) {
+               if (this._loaded) {
+                       callback.call(context || this, this);
+               } else {
+                       this.on('load', callback, context);
+               }
+               return this;
+       },
+
+
+       // private methods for getting map state
+
+       _getMapPanePos: function () {
+               return L.DomUtil.getPosition(this._mapPane);
+       },
+
+       _getTopLeftPoint: function () {
+               if (!this._loaded) {
+                       throw new Error('Set map center and zoom first.');
+               }
+
+               return this._initialTopLeftPoint.subtract(this._getMapPanePos());
+       },
+
+       _getNewTopLeftPoint: function (center, zoom) {
+               var viewHalf = this.getSize()._divideBy(2);
+               // TODO round on display, not calculation to increase precision?
+               return this.project(center, zoom)._subtract(viewHalf)._round();
+       },
+
+       _latLngToNewLayerPoint: function (latlng, newZoom, newCenter) {
+               var topLeft = this._getNewTopLeftPoint(newCenter, newZoom).add(this._getMapPanePos());
+               return this.project(latlng, newZoom)._subtract(topLeft);
+       },
+
+       _getCenterLayerPoint: function () {
+               return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
+       },
+
+       _getCenterOffset: function (center) {
+               return this.latLngToLayerPoint(center).subtract(this._getCenterLayerPoint());
+       },
+
+       _limitZoom: function (zoom) {
+               var min = this.getMinZoom(),
+                   max = this.getMaxZoom();
+
+               return Math.max(min, Math.min(max, zoom));
+       }
+});
+
+L.Map.addInitHook = function (fn) {
+       var args = Array.prototype.slice.call(arguments, 1);
+
+       var init = typeof fn === 'function' ? fn : function () {
+               this[fn].apply(this, args);
+       };
+
+       this.prototype._initializers.push(init);
+};
+
+L.map = function (id, options) {
+       return new L.Map(id, options);
+};
+
+
+
+L.Projection.Mercator = {
+       MAX_LATITUDE: 85.0840591556,
+
+       R_MINOR: 6356752.3142,
+       R_MAJOR: 6378137,
+
+       project: function (latlng) { // (LatLng) -> Point
+               var d = L.LatLng.DEG_TO_RAD,
+                   max = this.MAX_LATITUDE,
+                   lat = Math.max(Math.min(max, latlng.lat), -max),
+                   r = this.R_MAJOR,
+                   r2 = this.R_MINOR,
+                   x = latlng.lng * d * r,
+                   y = lat * d,
+                   tmp = r2 / r,
+                   eccent = Math.sqrt(1.0 - tmp * tmp),
+                   con = eccent * Math.sin(y);
+
+               con = Math.pow((1 - con) / (1 + con), eccent * 0.5);
+
+               var ts = Math.tan(0.5 * ((Math.PI * 0.5) - y)) / con;
+               y = -r2 * Math.log(ts);
+
+               return new L.Point(x, y);
+       },
+
+       unproject: function (point) { // (Point, Boolean) -> LatLng
+               var d = L.LatLng.RAD_TO_DEG,
+                   r = this.R_MAJOR,
+                   r2 = this.R_MINOR,
+                   lng = point.x * d / r,
+                   tmp = r2 / r,
+                   eccent = Math.sqrt(1 - (tmp * tmp)),
+                   ts = Math.exp(- point.y / r2),
+                   phi = (Math.PI / 2) - 2 * Math.atan(ts),
+                   numIter = 15,
+                   tol = 1e-7,
+                   i = numIter,
+                   dphi = 0.1,
+                   con;
+
+               while ((Math.abs(dphi) > tol) && (--i > 0)) {
+                       con = eccent * Math.sin(phi);
+                       dphi = (Math.PI / 2) - 2 * Math.atan(ts *
+                                   Math.pow((1.0 - con) / (1.0 + con), 0.5 * eccent)) - phi;
+                       phi += dphi;
+               }
+
+               return new L.LatLng(phi * d, lng, true);
+       }
+};
+
+
+
+L.CRS.EPSG3395 = L.extend({}, L.CRS, {
+       code: 'EPSG:3395',
+
+       projection: L.Projection.Mercator,
+
+       transformation: (function () {
+               var m = L.Projection.Mercator,
+                   r = m.R_MAJOR,
+                   r2 = m.R_MINOR;
+
+               return new L.Transformation(0.5 / (Math.PI * r), 0.5, -0.5 / (Math.PI * r2), 0.5);
+       }())
+});
+
+
+/*
+ * L.TileLayer is used for standard xyz-numbered tile layers.
+ */
+
+L.TileLayer = L.Class.extend({
+       includes: L.Mixin.Events,
+
+       options: {
+               minZoom: 0,
+               maxZoom: 18,
+               tileSize: 256,
+               subdomains: 'abc',
+               errorTileUrl: '',
+               attribution: '',
+               zoomOffset: 0,
+               opacity: 1,
+               /* (undefined works too)
+               zIndex: null,
+               tms: false,
+               continuousWorld: false,
+               noWrap: false,
+               zoomReverse: false,
+               detectRetina: false,
+               reuseTiles: false,
+               */
+               unloadInvisibleTiles: L.Browser.mobile,
+               updateWhenIdle: L.Browser.mobile
+       },
+
+       initialize: function (url, options) {
+               options = L.setOptions(this, options);
+
+               // detecting retina displays, adjusting tileSize and zoom levels
+               if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) {
+
+                       options.tileSize = Math.floor(options.tileSize / 2);
+                       options.zoomOffset++;
+
+                       if (options.minZoom > 0) {
+                               options.minZoom--;
+                       }
+                       this.options.maxZoom--;
+               }
+
+               this._url = url;
+
+               var subdomains = this.options.subdomains;
+
+               if (typeof subdomains === 'string') {
+                       this.options.subdomains = subdomains.split('');
+               }
+       },
+
+       onAdd: function (map) {
+               this._map = map;
+
+               // create a container div for tiles
+               this._initContainer();
+
+               // create an image to clone for tiles
+               this._createTileProto();
+
+               // set up events
+               map.on({
+                       'viewreset': this._resetCallback,
+                       'moveend': this._update
+               }, this);
+
+               if (!this.options.updateWhenIdle) {
+                       this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this);
+                       map.on('move', this._limitedUpdate, this);
+               }
+
+               this._reset();
+               this._update();
+       },
+
+       addTo: function (map) {
+               map.addLayer(this);
+               return this;
+       },
+
+       onRemove: function (map) {
+               map._panes.tilePane.removeChild(this._container);
+
+               map.off({
+                       'viewreset': this._resetCallback,
+                       'moveend': this._update
+               }, this);
+
+               if (!this.options.updateWhenIdle) {
+                       map.off('move', this._limitedUpdate, this);
+               }
+
+               this._container = null;
+               this._map = null;
+       },
+
+       bringToFront: function () {
+               var pane = this._map._panes.tilePane;
+
+               if (this._container) {
+                       pane.appendChild(this._container);
+                       this._setAutoZIndex(pane, Math.max);
+               }
+
+               return this;
+       },
+
+       bringToBack: function () {
+               var pane = this._map._panes.tilePane;
+
+               if (this._container) {
+                       pane.insertBefore(this._container, pane.firstChild);
+                       this._setAutoZIndex(pane, Math.min);
+               }
+
+               return this;
+       },
+
+       getAttribution: function () {
+               return this.options.attribution;
+       },
+
+       setOpacity: function (opacity) {
+               this.options.opacity = opacity;
+
+               if (this._map) {
+                       this._updateOpacity();
+               }
+
+               return this;
+       },
+
+       setZIndex: function (zIndex) {
+               this.options.zIndex = zIndex;
+               this._updateZIndex();
+
+               return this;
+       },
+
+       setUrl: function (url, noRedraw) {
+               this._url = url;
+
+               if (!noRedraw) {
+                       this.redraw();
+               }
+
+               return this;
+       },
+
+       redraw: function () {
+               if (this._map) {
+                       this._map._panes.tilePane.empty = false;
+                       this._reset(true);
+                       this._update();
+               }
+               return this;
+       },
+
+       _updateZIndex: function () {
+               if (this._container && this.options.zIndex !== undefined) {
+                       this._container.style.zIndex = this.options.zIndex;
+               }
+       },
+
+       _setAutoZIndex: function (pane, compare) {
+
+               var layers = pane.getElementsByClassName('leaflet-layer'),
+                   edgeZIndex = -compare(Infinity, -Infinity), // -Ifinity for max, Infinity for min
+                   zIndex, i, len;
+
+               for (i = 0, len = layers.length; i < len; i++) {
+
+                       if (layers[i] !== this._container) {
+                               zIndex = parseInt(layers[i].style.zIndex, 10);
+
+                               if (!isNaN(zIndex)) {
+                                       edgeZIndex = compare(edgeZIndex, zIndex);
+                               }
+                       }
+               }
+
+               this.options.zIndex = this._container.style.zIndex =
+                       (isFinite(edgeZIndex) ? edgeZIndex : 0) + compare(1, -1);
+       },
+
+       _updateOpacity: function () {
+               L.DomUtil.setOpacity(this._container, this.options.opacity);
+
+               // stupid webkit hack to force redrawing of tiles
+               var i,
+                   tiles = this._tiles;
+
+               if (L.Browser.webkit) {
+                       for (i in tiles) {
+                               if (tiles.hasOwnProperty(i)) {
+                                       tiles[i].style.webkitTransform += ' translate(0,0)';
+                               }
+                       }
+               }
+       },
+
+       _initContainer: function () {
+               var tilePane = this._map._panes.tilePane;
+
+               if (!this._container || tilePane.empty) {
+                       this._container = L.DomUtil.create('div', 'leaflet-layer');
+
+                       this._updateZIndex();
+
+                       tilePane.appendChild(this._container);
+
+                       if (this.options.opacity < 1) {
+                               this._updateOpacity();
+                       }
+               }
+       },
+
+       _resetCallback: function (e) {
+               this._reset(e.hard);
+       },
+
+       _reset: function (clearOldContainer) {
+               var tiles = this._tiles;
+
+               for (var key in tiles) {
+                       if (tiles.hasOwnProperty(key)) {
+                               this.fire('tileunload', {tile: tiles[key]});
+                       }
+               }
+
+               this._tiles = {};
+               this._tilesToLoad = 0;
+
+               if (this.options.reuseTiles) {
+                       this._unusedTiles = [];
+               }
+
+               if (clearOldContainer && this._container) {
+                       this._container.innerHTML = "";
+               }
+
+               this._initContainer();
+       },
+
+       _update: function (e) {
+
+               if (!this._map) { return; }
+
+               var bounds = this._map.getPixelBounds(),
+                   zoom = this._map.getZoom(),
+                   tileSize = this.options.tileSize;
+
+               if (zoom > this.options.maxZoom || zoom < this.options.minZoom) {
+                       return;
+               }
+
+               var nwTilePoint = new L.Point(
+                       Math.floor(bounds.min.x / tileSize),
+                       Math.floor(bounds.min.y / tileSize)),
+
+                   seTilePoint = new L.Point(
+                       Math.floor(bounds.max.x / tileSize),
+                       Math.floor(bounds.max.y / tileSize)),
+
+                   tileBounds = new L.Bounds(nwTilePoint, seTilePoint);
+
+               this._addTilesFromCenterOut(tileBounds);
+
+               if (this.options.unloadInvisibleTiles || this.options.reuseTiles) {
+                       this._removeOtherTiles(tileBounds);
+               }
+       },
+
+       _addTilesFromCenterOut: function (bounds) {
+               var queue = [],
+                   center = bounds.getCenter();
+
+               var j, i, point;
+
+               for (j = bounds.min.y; j <= bounds.max.y; j++) {
+                       for (i = bounds.min.x; i <= bounds.max.x; i++) {
+                               point = new L.Point(i, j);
+
+                               if (this._tileShouldBeLoaded(point)) {
+                                       queue.push(point);
+                               }
+                       }
+               }
+
+               var tilesToLoad = queue.length;
+
+               if (tilesToLoad === 0) { return; }
+
+               // load tiles in order of their distance to center
+               queue.sort(function (a, b) {
+                       return a.distanceTo(center) - b.distanceTo(center);
+               });
+
+               var fragment = document.createDocumentFragment();
+
+               // if its the first batch of tiles to load
+               if (!this._tilesToLoad) {
+                       this.fire('loading');
+               }
+
+               this._tilesToLoad += tilesToLoad;
+
+               for (i = 0; i < tilesToLoad; i++) {
+                       this._addTile(queue[i], fragment);
+               }
+
+               this._container.appendChild(fragment);
+       },
+
+       _tileShouldBeLoaded: function (tilePoint) {
+               if ((tilePoint.x + ':' + tilePoint.y) in this._tiles) {
+                       return false; // already loaded
+               }
+
+               if (!this.options.continuousWorld) {
+                       var limit = this._getWrapTileNum();
+
+                       if (this.options.noWrap && (tilePoint.x < 0 || tilePoint.x >= limit) ||
+                                                       tilePoint.y < 0 || tilePoint.y >= limit) {
+                               return false; // exceeds world bounds
+                       }
+               }
+
+               return true;
+       },
+
+       _removeOtherTiles: function (bounds) {
+               var kArr, x, y, key;
+
+               for (key in this._tiles) {
+                       if (this._tiles.hasOwnProperty(key)) {
+                               kArr = key.split(':');
+                               x = parseInt(kArr[0], 10);
+                               y = parseInt(kArr[1], 10);
+
+                               // remove tile if it's out of bounds
+                               if (x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) {
+                                       this._removeTile(key);
+                               }
+                       }
+               }
+       },
+
+       _removeTile: function (key) {
+               var tile = this._tiles[key];
+
+               this.fire("tileunload", {tile: tile, url: tile.src});
+
+               if (this.options.reuseTiles) {
+                       L.DomUtil.removeClass(tile, 'leaflet-tile-loaded');
+                       this._unusedTiles.push(tile);
+
+               } else if (tile.parentNode === this._container) {
+                       this._container.removeChild(tile);
+               }
+
+               // for https://github.com/CloudMade/Leaflet/issues/137
+               if (!L.Browser.android) {
+                       tile.src = L.Util.emptyImageUrl;
+               }
+
+               delete this._tiles[key];
+       },
+
+       _addTile: function (tilePoint, container) {
+               var tilePos = this._getTilePos(tilePoint);
+
+               // get unused tile - or create a new tile
+               var tile = this._getTile();
+
+               /*
+               Chrome 20 layouts much faster with top/left (verify with timeline, frames)
+               Android 4 browser has display issues with top/left and requires transform instead
+               Android 3 browser not tested
+               Android 2 browser requires top/left or tiles disappear on load or first drag
+               (reappear after zoom) https://github.com/CloudMade/Leaflet/issues/866
+               (other browsers don't currently care) - see debug/hacks/jitter.html for an example
+               */
+               L.DomUtil.setPosition(tile, tilePos, L.Browser.chrome || L.Browser.android23);
+
+               this._tiles[tilePoint.x + ':' + tilePoint.y] = tile;
+
+               this._loadTile(tile, tilePoint);
+
+               if (tile.parentNode !== this._container) {
+                       container.appendChild(tile);
+               }
+       },
+
+       _getZoomForUrl: function () {
+
+               var options = this.options,
+                   zoom = this._map.getZoom();
+
+               if (options.zoomReverse) {
+                       zoom = options.maxZoom - zoom;
+               }
+
+               return zoom + options.zoomOffset;
+       },
+
+       _getTilePos: function (tilePoint) {
+               var origin = this._map.getPixelOrigin(),
+                   tileSize = this.options.tileSize;
+
+               return tilePoint.multiplyBy(tileSize).subtract(origin);
+       },
+
+       // image-specific code (override to implement e.g. Canvas or SVG tile layer)
+
+       getTileUrl: function (tilePoint) {
+               this._adjustTilePoint(tilePoint);
+
+               return L.Util.template(this._url, L.extend({
+                       s: this._getSubdomain(tilePoint),
+                       z: this._getZoomForUrl(),
+                       x: tilePoint.x,
+                       y: tilePoint.y
+               }, this.options));
+       },
+
+       _getWrapTileNum: function () {
+               // TODO refactor, limit is not valid for non-standard projections
+               return Math.pow(2, this._getZoomForUrl());
+       },
+
+       _adjustTilePoint: function (tilePoint) {
+
+               var limit = this._getWrapTileNum();
+
+               // wrap tile coordinates
+               if (!this.options.continuousWorld && !this.options.noWrap) {
+                       tilePoint.x = ((tilePoint.x % limit) + limit) % limit;
+               }
+
+               if (this.options.tms) {
+                       tilePoint.y = limit - tilePoint.y - 1;
+               }
+       },
+
+       _getSubdomain: function (tilePoint) {
+               var index = (tilePoint.x + tilePoint.y) % this.options.subdomains.length;
+               return this.options.subdomains[index];
+       },
+
+       _createTileProto: function () {
+               var img = this._tileImg = L.DomUtil.create('img', 'leaflet-tile');
+               img.style.width = img.style.height = this.options.tileSize + 'px';
+               img.galleryimg = 'no';
+       },
+
+       _getTile: function () {
+               if (this.options.reuseTiles && this._unusedTiles.length > 0) {
+                       var tile = this._unusedTiles.pop();
+                       this._resetTile(tile);
+                       return tile;
+               }
+               return this._createTile();
+       },
+
+       _resetTile: function (tile) {
+               // Override if data stored on a tile needs to be cleaned up before reuse
+       },
+
+       _createTile: function () {
+               var tile = this._tileImg.cloneNode(false);
+               tile.onselectstart = tile.onmousemove = L.Util.falseFn;
+               return tile;
+       },
+
+       _loadTile: function (tile, tilePoint) {
+               tile._layer  = this;
+               tile.onload  = this._tileOnLoad;
+               tile.onerror = this._tileOnError;
+
+               tile.src     = this.getTileUrl(tilePoint);
+       },
+
+    _tileLoaded: function () {
+        this._tilesToLoad--;
+        if (!this._tilesToLoad) {
+            this.fire('load');
+        }
+    },
+
+       _tileOnLoad: function (e) {
+               var layer = this._layer;
+
+               //Only if we are loading an actual image
+               if (this.src !== L.Util.emptyImageUrl) {
+                       L.DomUtil.addClass(this, 'leaflet-tile-loaded');
+
+                       layer.fire('tileload', {
+                               tile: this,
+                               url: this.src
+                       });
+               }
+
+               layer._tileLoaded();
+       },
+
+       _tileOnError: function (e) {
+               var layer = this._layer;
+
+               layer.fire('tileerror', {
+                       tile: this,
+                       url: this.src
+               });
+
+               var newUrl = layer.options.errorTileUrl;
+               if (newUrl) {
+                       this.src = newUrl;
+               }
+
+        layer._tileLoaded();
+    }
+});
+
+L.tileLayer = function (url, options) {
+       return new L.TileLayer(url, options);
+};
+
+
+L.TileLayer.WMS = L.TileLayer.extend({
+
+       defaultWmsParams: {
+               service: 'WMS',
+               request: 'GetMap',
+               version: '1.1.1',
+               layers: '',
+               styles: '',
+               format: 'image/jpeg',
+               transparent: false
+       },
+
+       initialize: function (url, options) { // (String, Object)
+
+               this._url = url;
+
+               var wmsParams = L.extend({}, this.defaultWmsParams);
+
+               if (options.detectRetina && L.Browser.retina) {
+                       wmsParams.width = wmsParams.height = this.options.tileSize * 2;
+               } else {
+                       wmsParams.width = wmsParams.height = this.options.tileSize;
+               }
+
+               for (var i in options) {
+                       // all keys that are not TileLayer options go to WMS params
+                       if (!this.options.hasOwnProperty(i)) {
+                               wmsParams[i] = options[i];
+                       }
+               }
+
+               this.wmsParams = wmsParams;
+
+               L.setOptions(this, options);
+       },
+
+       onAdd: function (map) {
+
+               var projectionKey = parseFloat(this.wmsParams.version) >= 1.3 ? 'crs' : 'srs';
+               this.wmsParams[projectionKey] = map.options.crs.code;
+
+               L.TileLayer.prototype.onAdd.call(this, map);
+       },
+
+       getTileUrl: function (tilePoint, zoom) { // (Point, Number) -> String
+
+               var map = this._map,
+                   crs = map.options.crs,
+                   tileSize = this.options.tileSize,
+
+                   nwPoint = tilePoint.multiplyBy(tileSize),
+                   sePoint = nwPoint.add(new L.Point(tileSize, tileSize)),
+
+                   nw = crs.project(map.unproject(nwPoint, zoom)),
+                   se = crs.project(map.unproject(sePoint, zoom)),
+
+                   bbox = [nw.x, se.y, se.x, nw.y].join(','),
+
+                   url = L.Util.template(this._url, {s: this._getSubdomain(tilePoint)});
+
+               return url + L.Util.getParamString(this.wmsParams) + "&bbox=" + bbox;
+       },
+
+       setParams: function (params, noRedraw) {
+
+               L.extend(this.wmsParams, params);
+
+               if (!noRedraw) {
+                       this.redraw();
+               }
+
+               return this;
+       }
+});
+
+L.tileLayer.wms = function (url, options) {
+       return new L.TileLayer.WMS(url, options);
+};
+
+
+L.TileLayer.Canvas = L.TileLayer.extend({
+       options: {
+               async: false
+       },
+
+       initialize: function (options) {
+               L.setOptions(this, options);
+       },
+
+       redraw: function () {
+               var tiles = this._tiles;
+
+               for (var i in tiles) {
+                       if (tiles.hasOwnProperty(i)) {
+                               this._redrawTile(tiles[i]);
+                       }
+               }
+       },
+
+       _redrawTile: function (tile) {
+               this.drawTile(tile, tile._tilePoint, this._map._zoom);
+       },
+
+       _createTileProto: function () {
+               var proto = this._canvasProto = L.DomUtil.create('canvas', 'leaflet-tile');
+               proto.width = proto.height = this.options.tileSize;
+       },
+
+       _createTile: function () {
+               var tile = this._canvasProto.cloneNode(false);
+               tile.onselectstart = tile.onmousemove = L.Util.falseFn;
+               return tile;
+       },
+
+       _loadTile: function (tile, tilePoint) {
+               tile._layer = this;
+               tile._tilePoint = tilePoint;
+
+               this._redrawTile(tile);
+
+               if (!this.options.async) {
+                       this.tileDrawn(tile);
+               }
+       },
+
+       drawTile: function (tile, tilePoint) {
+               // override with rendering code
+       },
+
+       tileDrawn: function (tile) {
+               this._tileOnLoad.call(tile);
+       }
+});
+
+
+L.tileLayer.canvas = function (options) {
+       return new L.TileLayer.Canvas(options);
+};
+
+
+L.ImageOverlay = L.Class.extend({
+       includes: L.Mixin.Events,
+
+       options: {
+               opacity: 1
+       },
+
+       initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
+               this._url = url;
+               this._bounds = L.latLngBounds(bounds);
+
+               L.setOptions(this, options);
+       },
+
+       onAdd: function (map) {
+               this._map = map;
+
+               if (!this._image) {
+                       this._initImage();
+               }
+
+               map._panes.overlayPane.appendChild(this._image);
+
+               map.on('viewreset', this._reset, this);
+
+               if (map.options.zoomAnimation && L.Browser.any3d) {
+                       map.on('zoomanim', this._animateZoom, this);
+               }
+
+               this._reset();
+       },
+
+       onRemove: function (map) {
+               map.getPanes().overlayPane.removeChild(this._image);
+
+               map.off('viewreset', this._reset, this);
+
+               if (map.options.zoomAnimation) {
+                       map.off('zoomanim', this._animateZoom, this);
+               }
+       },
+
+       addTo: function (map) {
+               map.addLayer(this);
+               return this;
+       },
+
+       setOpacity: function (opacity) {
+               this.options.opacity = opacity;
+               this._updateOpacity();
+               return this;
+       },
+
+       // TODO remove bringToFront/bringToBack duplication from TileLayer/Path
+       bringToFront: function () {
+               if (this._image) {
+                       this._map._panes.overlayPane.appendChild(this._image);
+               }
+               return this;
+       },
+
+       bringToBack: function () {
+               var pane = this._map._panes.overlayPane;
+               if (this._image) {
+                       pane.insertBefore(this._image, pane.firstChild);
+               }
+               return this;
+       },
+
+       _initImage: function () {
+               this._image = L.DomUtil.create('img', 'leaflet-image-layer');
+
+               if (this._map.options.zoomAnimation && L.Browser.any3d) {
+                       L.DomUtil.addClass(this._image, 'leaflet-zoom-animated');
+               } else {
+                       L.DomUtil.addClass(this._image, 'leaflet-zoom-hide');
+               }
+
+               this._updateOpacity();
+
+               //TODO createImage util method to remove duplication
+               L.extend(this._image, {
+                       galleryimg: 'no',
+                       onselectstart: L.Util.falseFn,
+                       onmousemove: L.Util.falseFn,
+                       onload: L.bind(this._onImageLoad, this),
+                       src: this._url
+               });
+       },
+
+       _animateZoom: function (e) {
+               var map = this._map,
+                   image = this._image,
+                   scale = map.getZoomScale(e.zoom),
+                   nw = this._bounds.getNorthWest(),
+                   se = this._bounds.getSouthEast(),
+
+                   topLeft = map._latLngToNewLayerPoint(nw, e.zoom, e.center),
+                   size = map._latLngToNewLayerPoint(se, e.zoom, e.center)._subtract(topLeft),
+                   currentSize = map.latLngToLayerPoint(se)._subtract(map.latLngToLayerPoint(nw)),
+                   origin = topLeft._add(size._subtract(currentSize)._divideBy(2));
+
+               image.style[L.DomUtil.TRANSFORM] =
+                       L.DomUtil.getTranslateString(origin) + ' scale(' + scale + ') ';
+       },
+
+       _reset: function () {
+               var image   = this._image,
+                   topLeft = this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
+                   size    = this._map.latLngToLayerPoint(this._bounds.getSouthEast())._subtract(topLeft);
+
+               L.DomUtil.setPosition(image, topLeft);
+
+               image.style.width  = size.x + 'px';
+               image.style.height = size.y + 'px';
+       },
+
+       _onImageLoad: function () {
+               this.fire('load');
+       },
+
+       _updateOpacity: function () {
+               L.DomUtil.setOpacity(this._image, this.options.opacity);
+       }
+});
+
+L.imageOverlay = function (url, bounds, options) {
+       return new L.ImageOverlay(url, bounds, options);
+};
+
+
+L.Icon = L.Class.extend({
+       options: {
+               /*
+               iconUrl: (String) (required)
+               iconSize: (Point) (can be set through CSS)
+               iconAnchor: (Point) (centered by default, can be set in CSS with negative margins)
+               popupAnchor: (Point) (if not specified, popup opens in the anchor point)
+               shadowUrl: (Point) (no shadow by default)
+               shadowSize: (Point)
+               shadowAnchor: (Point)
+               */
+               className: ''
+       },
+
+       initialize: function (options) {
+               L.setOptions(this, options);
+       },
+
+       createIcon: function () {
+               return this._createIcon('icon');
+       },
+
+       createShadow: function () {
+               return this._createIcon('shadow');
+       },
+
+       _createIcon: function (name) {
+               var src = this._getIconUrl(name);
+
+               if (!src) {
+                       if (name === 'icon') {
+                               throw new Error("iconUrl not set in Icon options (see the docs).");
+                       }
+                       return null;
+               }
+
+               var img = this._createImg(src);
+               this._setIconStyles(img, name);
+
+               return img;
+       },
+
+       _setIconStyles: function (img, name) {
+               var options = this.options,
+                   size = L.point(options[name + 'Size']),
+                   anchor;
+
+               if (name === 'shadow') {
+                       anchor = L.point(options.shadowAnchor || options.iconAnchor);
+               } else {
+                       anchor = L.point(options.iconAnchor);
+               }
+
+               if (!anchor && size) {
+                       anchor = size.divideBy(2, true);
+               }
+
+               img.className = 'leaflet-marker-' + name + ' ' + options.className;
+
+               if (anchor) {
+                       img.style.marginLeft = (-anchor.x) + 'px';
+                       img.style.marginTop  = (-anchor.y) + 'px';
+               }
+
+               if (size) {
+                       img.style.width  = size.x + 'px';
+                       img.style.height = size.y + 'px';
+               }
+       },
+
+       _createImg: function (src) {
+               var el;
+
+               if (!L.Browser.ie6) {
+                       el = document.createElement('img');
+                       el.src = src;
+               } else {
+                       el = document.createElement('div');
+                       el.style.filter =
+                               'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + src + '")';
+               }
+               return el;
+       },
+
+       _getIconUrl: function (name) {
+               return this.options[name + 'Url'];
+       }
+});
+
+L.icon = function (options) {
+       return new L.Icon(options);
+};
+
+
+
+L.Icon.Default = L.Icon.extend({
+
+       options: {
+               iconSize: new L.Point(25, 41),
+               iconAnchor: new L.Point(12, 41),
+               popupAnchor: new L.Point(1, -34),
+
+               shadowSize: new L.Point(41, 41)
+       },
+
+       _getIconUrl: function (name) {
+               var key = name + 'Url';
+
+               if (this.options[key]) {
+                       return this.options[key];
+               }
+
+               var path = L.Icon.Default.imagePath;
+
+               if (!path) {
+                       throw new Error("Couldn't autodetect L.Icon.Default.imagePath, set it manually.");
+               }
+
+               return path + '/marker-' + name + '.png';
+       }
+});
+
+L.Icon.Default.imagePath = (function () {
+       var scripts = document.getElementsByTagName('script'),
+           leafletRe = /\/?leaflet[\-\._]?([\w\-\._]*)\.js\??/;
+
+       var i, len, src, matches;
+
+       for (i = 0, len = scripts.length; i < len; i++) {
+               src = scripts[i].src;
+               matches = src.match(leafletRe);
+
+               if (matches) {
+                       return src.split(leafletRe)[0] + '/images';
+               }
+       }
+}());
+
+
+/*
+ * L.Marker is used to display clickable/draggable icons on the map.
+ */
+
+L.Marker = L.Class.extend({
+
+       includes: L.Mixin.Events,
+
+       options: {
+               icon: new L.Icon.Default(),
+               title: '',
+               clickable: true,
+               draggable: false,
+               zIndexOffset: 0,
+               opacity: 1,
+               riseOnHover: false,
+               riseOffset: 250
+       },
+
+       initialize: function (latlng, options) {
+               L.setOptions(this, options);
+               this._latlng = L.latLng(latlng);
+       },
+
+       onAdd: function (map) {
+               this._map = map;
+
+               map.on('viewreset', this.update, this);
+
+               this._initIcon();
+               this.update();
+
+               if (map.options.zoomAnimation && map.options.markerZoomAnimation) {
+                       map.on('zoomanim', this._animateZoom, this);
+               }
+       },
+
+       addTo: function (map) {
+               map.addLayer(this);
+               return this;
+       },
+
+       onRemove: function (map) {
+               this._removeIcon();
+
+               this.fire('remove');
+
+               map.off({
+                       'viewreset': this.update,
+                       'zoomanim': this._animateZoom
+               }, this);
+
+               this._map = null;
+       },
+
+       getLatLng: function () {
+               return this._latlng;
+       },
+
+       setLatLng: function (latlng) {
+               this._latlng = L.latLng(latlng);
+
+               this.update();
+
+               this.fire('move', { latlng: this._latlng });
+       },
+
+       setZIndexOffset: function (offset) {
+               this.options.zIndexOffset = offset;
+               this.update();
+       },
+
+       setIcon: function (icon) {
+               if (this._map) {
+                       this._removeIcon();
+               }
+
+               this.options.icon = icon;
+
+               if (this._map) {
+                       this._initIcon();
+                       this.update();
+               }
+       },
+
+       update: function () {
+               if (!this._icon) { return; }
+
+               var pos = this._map.latLngToLayerPoint(this._latlng).round();
+               this._setPos(pos);
+       },
+
+       _initIcon: function () {
+               var options = this.options,
+                   map = this._map,
+                   animation = (map.options.zoomAnimation && map.options.markerZoomAnimation),
+                   classToAdd = animation ? 'leaflet-zoom-animated' : 'leaflet-zoom-hide',
+                   needOpacityUpdate = false;
+
+               if (!this._icon) {
+                       this._icon = options.icon.createIcon();
+
+                       if (options.title) {
+                               this._icon.title = options.title;
+                       }
+
+                       this._initInteraction();
+                       needOpacityUpdate = (this.options.opacity < 1);
+
+                       L.DomUtil.addClass(this._icon, classToAdd);
+
+                       if (options.riseOnHover) {
+                               L.DomEvent
+                                       .on(this._icon, 'mouseover', this._bringToFront, this)
+                                       .on(this._icon, 'mouseout', this._resetZIndex, this);
+                       }
+               }
+
+               if (!this._shadow) {
+                       this._shadow = options.icon.createShadow();
+
+                       if (this._shadow) {
+                               L.DomUtil.addClass(this._shadow, classToAdd);
+                               needOpacityUpdate = (this.options.opacity < 1);
+                       }
+               }
+
+               if (needOpacityUpdate) {
+                       this._updateOpacity();
+               }
+
+               var panes = this._map._panes;
+
+               panes.markerPane.appendChild(this._icon);
+
+               if (this._shadow) {
+                       panes.shadowPane.appendChild(this._shadow);
+               }
+       },
+
+       _removeIcon: function () {
+               var panes = this._map._panes;
+
+               if (this.options.riseOnHover) {
+                       L.DomEvent
+                           .off(this._icon, 'mouseover', this._bringToFront)
+                           .off(this._icon, 'mouseout', this._resetZIndex);
+               }
+
+               panes.markerPane.removeChild(this._icon);
+
+               if (this._shadow) {
+                       panes.shadowPane.removeChild(this._shadow);
+               }
+
+               this._icon = this._shadow = null;
+       },
+
+       _setPos: function (pos) {
+               L.DomUtil.setPosition(this._icon, pos);
+
+               if (this._shadow) {
+                       L.DomUtil.setPosition(this._shadow, pos);
+               }
+
+               this._zIndex = pos.y + this.options.zIndexOffset;
+
+               this._resetZIndex();
+       },
+
+       _updateZIndex: function (offset) {
+               this._icon.style.zIndex = this._zIndex + offset;
+       },
+
+       _animateZoom: function (opt) {
+               var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center);
+
+               this._setPos(pos);
+       },
+
+       _initInteraction: function () {
+               if (!this.options.clickable) {
+                       return;
+               }
+
+               var icon = this._icon,
+                   events = ['dblclick', 'mousedown', 'mouseover', 'mouseout'];
+
+               L.DomUtil.addClass(icon, 'leaflet-clickable');
+               L.DomEvent.on(icon, 'click', this._onMouseClick, this);
+
+               for (var i = 0; i < events.length; i++) {
+                       L.DomEvent.on(icon, events[i], this._fireMouseEvent, this);
+               }
+
+               if (L.Handler.MarkerDrag) {
+                       this.dragging = new L.Handler.MarkerDrag(this);
+
+                       if (this.options.draggable) {
+                               this.dragging.enable();
+                       }
+               }
+       },
+
+       _onMouseClick: function (e) {
+               var wasDragged = this.dragging && this.dragging.moved();
+               if (this.hasEventListeners(e.type) || wasDragged) {
+                       L.DomEvent.stopPropagation(e);
+               }
+               if (wasDragged) { return; }
+               if (this._map.dragging && this._map.dragging.moved()) { return; }
+               this.fire(e.type, {
+                       originalEvent: e
+               });
+       },
+
+       _fireMouseEvent: function (e) {
+               this.fire(e.type, {
+                       originalEvent: e
+               });
+               if (e.type !== 'mousedown') {
+                       L.DomEvent.stopPropagation(e);
+               }
+       },
+
+       setOpacity: function (opacity) {
+               this.options.opacity = opacity;
+               if (this._map) {
+                       this._updateOpacity();
+               }
+       },
+
+       _updateOpacity: function () {
+               L.DomUtil.setOpacity(this._icon, this.options.opacity);
+               if (this._shadow) {
+                       L.DomUtil.setOpacity(this._shadow, this.options.opacity);
+               }
+       },
+
+       _bringToFront: function () {
+               this._updateZIndex(this.options.riseOffset);
+       },
+
+       _resetZIndex: function () {
+               this._updateZIndex(0);
+       }
+});
+
+L.marker = function (latlng, options) {
+       return new L.Marker(latlng, options);
+};
+
+
+L.DivIcon = L.Icon.extend({
+       options: {
+               iconSize: new L.Point(12, 12), // also can be set through CSS
+               /*
+               iconAnchor: (Point)
+               popupAnchor: (Point)
+               html: (String)
+               bgPos: (Point)
+               */
+               className: 'leaflet-div-icon'
+       },
+
+       createIcon: function () {
+               var div = document.createElement('div'),
+                   options = this.options;
+
+               if (options.html) {
+                       div.innerHTML = options.html;
+               }
+
+               if (options.bgPos) {
+                       div.style.backgroundPosition =
+                               (-options.bgPos.x) + 'px ' + (-options.bgPos.y) + 'px';
+               }
+
+               this._setIconStyles(div, 'icon');
+               return div;
+       },
+
+       createShadow: function () {
+               return null;
+       }
+});
+
+L.divIcon = function (options) {
+       return new L.DivIcon(options);
+};
+
+
+
+L.Map.mergeOptions({
+       closePopupOnClick: true
+});
+
+L.Popup = L.Class.extend({
+       includes: L.Mixin.Events,
+
+       options: {
+               minWidth: 50,
+               maxWidth: 300,
+               maxHeight: null,
+               autoPan: true,
+               closeButton: true,
+               offset: new L.Point(0, 6),
+               autoPanPadding: new L.Point(5, 5),
+               className: ''
+       },
+
+       initialize: function (options, source) {
+               L.setOptions(this, options);
+
+               this._source = source;
+       },
+
+       onAdd: function (map) {
+               this._map = map;
+
+               if (!this._container) {
+                       this._initLayout();
+               }
+               this._updateContent();
+
+               var animFade = map.options.fadeAnimation;
+
+               if (animFade) {
+                       L.DomUtil.setOpacity(this._container, 0);
+               }
+               map._panes.popupPane.appendChild(this._container);
+
+               map.on('viewreset', this._updatePosition, this);
+
+               if (L.Browser.any3d) {
+                       map.on('zoomanim', this._zoomAnimation, this);
+               }
+
+               if (map.options.closePopupOnClick) {
+                       map.on('preclick', this._close, this);
+               }
+
+               this._update();
+
+               if (animFade) {
+                       L.DomUtil.setOpacity(this._container, 1);
+               }
+       },
+
+       addTo: function (map) {
+               map.addLayer(this);
+               return this;
+       },
+
+       openOn: function (map) {
+               map.openPopup(this);
+               return this;
+       },
+
+       onRemove: function (map) {
+               map._panes.popupPane.removeChild(this._container);
+
+               L.Util.falseFn(this._container.offsetWidth); // force reflow
+
+               map.off({
+                       viewreset: this._updatePosition,
+                       preclick: this._close,
+                       zoomanim: this._zoomAnimation
+               }, this);
+
+               if (map.options.fadeAnimation) {
+                       L.DomUtil.setOpacity(this._container, 0);
+               }
+
+               this._map = null;
+       },
+
+       setLatLng: function (latlng) {
+               this._latlng = L.latLng(latlng);
+               this._update();
+               return this;
+       },
+
+       setContent: function (content) {
+               this._content = content;
+               this._update();
+               return this;
+       },
+
+       _close: function () {
+               var map = this._map;
+
+               if (map) {
+                       map._popup = null;
+
+                       map
+                           .removeLayer(this)
+                           .fire('popupclose', {popup: this});
+               }
+       },
+
+       _initLayout: function () {
+               var prefix = 'leaflet-popup',
+                       containerClass = prefix + ' ' + this.options.className + ' leaflet-zoom-animated',
+                       container = this._container = L.DomUtil.create('div', containerClass),
+                       closeButton;
+
+               if (this.options.closeButton) {
+                       closeButton = this._closeButton =
+                               L.DomUtil.create('a', prefix + '-close-button', container);
+                       closeButton.href = '#close';
+                       closeButton.innerHTML = '&#215;';
+
+                       L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this);
+               }
+
+               var wrapper = this._wrapper =
+                       L.DomUtil.create('div', prefix + '-content-wrapper', container);
+               L.DomEvent.disableClickPropagation(wrapper);
+
+               this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper);
+               L.DomEvent.on(this._contentNode, 'mousewheel', L.DomEvent.stopPropagation);
+
+               this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container);
+               this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer);
+       },
+
+       _update: function () {
+               if (!this._map) { return; }
+
+               this._container.style.visibility = 'hidden';
+
+               this._updateContent();
+               this._updateLayout();
+               this._updatePosition();
+
+               this._container.style.visibility = '';
+
+               this._adjustPan();
+       },
+
+       _updateContent: function () {
+               if (!this._content) { return; }
+
+               if (typeof this._content === 'string') {
+                       this._contentNode.innerHTML = this._content;
+               } else {
+                       while (this._contentNode.hasChildNodes()) {
+                               this._contentNode.removeChild(this._contentNode.firstChild);
+                       }
+                       this._contentNode.appendChild(this._content);
+               }
+               this.fire('contentupdate');
+       },
+
+       _updateLayout: function () {
+               var container = this._contentNode,
+                   style = container.style;
+
+               style.width = '';
+               style.whiteSpace = 'nowrap';
+
+               var width = container.offsetWidth;
+               width = Math.min(width, this.options.maxWidth);
+               width = Math.max(width, this.options.minWidth);
+
+               style.width = (width + 1) + 'px';
+               style.whiteSpace = '';
+
+               style.height = '';
+
+               var height = container.offsetHeight,
+                   maxHeight = this.options.maxHeight,
+                   scrolledClass = 'leaflet-popup-scrolled';
+
+               if (maxHeight && height > maxHeight) {
+                       style.height = maxHeight + 'px';
+                       L.DomUtil.addClass(container, scrolledClass);
+               } else {
+                       L.DomUtil.removeClass(container, scrolledClass);
+               }
+
+               this._containerWidth = this._container.offsetWidth;
+       },
+
+       _updatePosition: function () {
+               if (!this._map) { return; }
+
+               var pos = this._map.latLngToLayerPoint(this._latlng),
+                   is3d = L.Browser.any3d,
+                   offset = this.options.offset;
+
+               if (is3d) {
+                       L.DomUtil.setPosition(this._container, pos);
+               }
+
+               this._containerBottom = -offset.y - (is3d ? 0 : pos.y);
+               this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x + (is3d ? 0 : pos.x);
+
+               //Bottom position the popup in case the height of the popup changes (images loading etc)
+               this._container.style.bottom = this._containerBottom + 'px';
+               this._container.style.left = this._containerLeft + 'px';
+       },
+
+       _zoomAnimation: function (opt) {
+               var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center);
+
+               L.DomUtil.setPosition(this._container, pos);
+       },
+
+       _adjustPan: function () {
+               if (!this.options.autoPan) { return; }
+
+               var map = this._map,
+                   containerHeight = this._container.offsetHeight,
+                   containerWidth = this._containerWidth,
+
+                   layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom);
+
+               if (L.Browser.any3d) {
+                       layerPos._add(L.DomUtil.getPosition(this._container));
+               }
+
+               var containerPos = map.layerPointToContainerPoint(layerPos),
+                   padding = this.options.autoPanPadding,
+                   size = map.getSize(),
+                   dx = 0,
+                   dy = 0;
+
+               if (containerPos.x < 0) {
+                       dx = containerPos.x - padding.x;
+               }
+               if (containerPos.x + containerWidth > size.x) {
+                       dx = containerPos.x + containerWidth - size.x + padding.x;
+               }
+               if (containerPos.y < 0) {
+                       dy = containerPos.y - padding.y;
+               }
+               if (containerPos.y + containerHeight > size.y) {
+                       dy = containerPos.y + containerHeight - size.y + padding.y;
+               }
+
+               if (dx || dy) {
+                       map.panBy(new L.Point(dx, dy));
+               }
+       },
+
+       _onCloseButtonClick: function (e) {
+               this._close();
+               L.DomEvent.stop(e);
+       }
+});
+
+L.popup = function (options, source) {
+       return new L.Popup(options, source);
+};
+
+
+/*
+ * Popup extension to L.Marker, adding openPopup & bindPopup methods.
+ */
+
+L.Marker.include({
+       openPopup: function () {
+               if (this._popup && this._map) {
+                       this._popup.setLatLng(this._latlng);
+                       this._map.openPopup(this._popup);
+               }
+
+               return this;
+       },
+
+       closePopup: function () {
+               if (this._popup) {
+                       this._popup._close();
+               }
+               return this;
+       },
+
+       bindPopup: function (content, options) {
+               var anchor = L.point(this.options.icon.options.popupAnchor) || new L.Point(0, 0);
+
+               anchor = anchor.add(L.Popup.prototype.options.offset);
+
+               if (options && options.offset) {
+                       anchor = anchor.add(options.offset);
+               }
+
+               options = L.extend({offset: anchor}, options);
+
+               if (!this._popup) {
+                       this
+                           .on('click', this.openPopup, this)
+                           .on('remove', this.closePopup, this)
+                           .on('move', this._movePopup, this);
+               }
+
+               this._popup = new L.Popup(options, this)
+                       .setContent(content);
+
+               return this;
+       },
+
+       unbindPopup: function () {
+               if (this._popup) {
+                       this._popup = null;
+                       this
+                           .off('click', this.openPopup)
+                           .off('remove', this.closePopup)
+                           .off('move', this._movePopup);
+               }
+               return this;
+       },
+
+       _movePopup: function (e) {
+               this._popup.setLatLng(e.latlng);
+       }
+});
+
+
+
+L.Map.include({
+       openPopup: function (popup) {
+               this.closePopup();
+
+               this._popup = popup;
+
+               return this
+                   .addLayer(popup)
+                   .fire('popupopen', {popup: this._popup});
+       },
+
+       closePopup: function () {
+               if (this._popup) {
+                       this._popup._close();
+               }
+               return this;
+       }
+});
+
+
+/*
+ * L.LayerGroup is a class to combine several layers so you can manipulate the group (e.g. add/remove it) as one layer.
+ */
+
+L.LayerGroup = L.Class.extend({
+       initialize: function (layers) {
+               this._layers = {};
+
+               var i, len;
+
+               if (layers) {
+                       for (i = 0, len = layers.length; i < len; i++) {
+                               this.addLayer(layers[i]);
+                       }
+               }
+       },
+
+       addLayer: function (layer) {
+               var id = L.stamp(layer);
+
+               this._layers[id] = layer;
+
+               if (this._map) {
+                       this._map.addLayer(layer);
+               }
+
+               return this;
+       },
+
+       removeLayer: function (layer) {
+               var id = L.stamp(layer);
+
+               delete this._layers[id];
+
+               if (this._map) {
+                       this._map.removeLayer(layer);
+               }
+
+               return this;
+       },
+
+       clearLayers: function () {
+               this.eachLayer(this.removeLayer, this);
+               return this;
+       },
+
+       invoke: function (methodName) {
+               var args = Array.prototype.slice.call(arguments, 1),
+                   i, layer;
+
+               for (i in this._layers) {
+                       if (this._layers.hasOwnProperty(i)) {
+                               layer = this._layers[i];
+
+                               if (layer[methodName]) {
+                                       layer[methodName].apply(layer, args);
+                               }
+                       }
+               }
+
+               return this;
+       },
+
+       onAdd: function (map) {
+               this._map = map;
+               this.eachLayer(map.addLayer, map);
+       },
+
+       onRemove: function (map) {
+               this.eachLayer(map.removeLayer, map);
+               this._map = null;
+       },
+
+       addTo: function (map) {
+               map.addLayer(this);
+               return this;
+       },
+
+       eachLayer: function (method, context) {
+               for (var i in this._layers) {
+                       if (this._layers.hasOwnProperty(i)) {
+                               method.call(context, this._layers[i]);
+                       }
+               }
+       }
+});
+
+L.layerGroup = function (layers) {
+       return new L.LayerGroup(layers);
+};
+
+
+/*
+ * L.FeatureGroup extends L.LayerGroup by introducing mouse events and bindPopup method shared between a group of layers.
+ */
+
+L.FeatureGroup = L.LayerGroup.extend({
+       includes: L.Mixin.Events,
+
+       statics: {
+               EVENTS: 'click dblclick mouseover mouseout mousemove contextmenu'
+       },
+
+       addLayer: function (layer) {
+               if (this._layers[L.stamp(layer)]) {
+                       return this;
+               }
+
+               layer.on(L.FeatureGroup.EVENTS, this._propagateEvent, this);
+
+               L.LayerGroup.prototype.addLayer.call(this, layer);
+
+               if (this._popupContent && layer.bindPopup) {
+                       layer.bindPopup(this._popupContent);
+               }
+
+               return this.fire('layeradd', {layer: layer});
+       },
+
+       removeLayer: function (layer) {
+               layer.off(L.FeatureGroup.EVENTS, this._propagateEvent, this);
+
+               L.LayerGroup.prototype.removeLayer.call(this, layer);
+
+
+               if (this._popupContent) {
+                       this.invoke('unbindPopup');
+               }
+
+               return this.fire('layerremove', {layer: layer});
+       },
+
+       bindPopup: function (content) {
+               this._popupContent = content;
+               return this.invoke('bindPopup', content);
+       },
+
+       setStyle: function (style) {
+               return this.invoke('setStyle', style);
+       },
+
+       bringToFront: function () {
+               return this.invoke('bringToFront');
+       },
+
+       bringToBack: function () {
+               return this.invoke('bringToBack');
+       },
+
+       getBounds: function () {
+               var bounds = new L.LatLngBounds();
+
+               this.eachLayer(function (layer) {
+                       bounds.extend(layer instanceof L.Marker ? layer.getLatLng() : layer.getBounds());
+               });
+
+               return bounds;
+       },
+
+       _propagateEvent: function (e) {
+               e.layer  = e.target;
+               e.target = this;
+
+               this.fire(e.type, e);
+       }
+});
+
+L.featureGroup = function (layers) {
+       return new L.FeatureGroup(layers);
+};
+
+
+/*
+ * L.Path is a base class for rendering vector paths on a map. Inherited by Polyline, Circle, etc.
+ */
+
+L.Path = L.Class.extend({
+       includes: [L.Mixin.Events],
+
+       statics: {
+               // how much to extend the clip area around the map view
+               // (relative to its size, e.g. 0.5 is half the screen in each direction)
+               // set it so that SVG element doesn't exceed 1280px (vectors flicker on dragend if it is)
+               CLIP_PADDING: L.Browser.mobile ?
+                       Math.max(0, Math.min(0.5,
+                               (1280 / Math.max(window.innerWidth, window.innerHeight) - 1) / 2)) : 0.5
+       },
+
+       options: {
+               stroke: true,
+               color: '#0033ff',
+               dashArray: null,
+               weight: 5,
+               opacity: 0.5,
+
+               fill: false,
+               fillColor: null, //same as color by default
+               fillOpacity: 0.2,
+
+               clickable: true
+       },
+
+       initialize: function (options) {
+               L.setOptions(this, options);
+       },
+
+       onAdd: function (map) {
+               this._map = map;
+
+               if (!this._container) {
+                       this._initElements();
+                       this._initEvents();
+               }
+
+               this.projectLatlngs();
+               this._updatePath();
+
+               if (this._container) {
+                       this._map._pathRoot.appendChild(this._container);
+               }
+
+               map.on({
+                       'viewreset': this.projectLatlngs,
+                       'moveend': this._updatePath
+               }, this);
+       },
+
+       addTo: function (map) {
+               map.addLayer(this);
+               return this;
+       },
+
+       onRemove: function (map) {
+               map._pathRoot.removeChild(this._container);
+
+               this._map = null;
+
+               if (L.Browser.vml) {
+                       this._container = null;
+                       this._stroke = null;
+                       this._fill = null;
+               }
+
+               this.fire('remove');
+
+               map.off({
+                       'viewreset': this.projectLatlngs,
+                       'moveend': this._updatePath
+               }, this);
+       },
+
+       projectLatlngs: function () {
+               // do all projection stuff here
+       },
+
+       setStyle: function (style) {
+               L.setOptions(this, style);
+
+               if (this._container) {
+                       this._updateStyle();
+               }
+
+               return this;
+       },
+
+       redraw: function () {
+               if (this._map) {
+                       this.projectLatlngs();
+                       this._updatePath();
+               }
+               return this;
+       }
+});
+
+L.Map.include({
+       _updatePathViewport: function () {
+               var p = L.Path.CLIP_PADDING,
+                   size = this.getSize(),
+                   panePos = L.DomUtil.getPosition(this._mapPane),
+                   min = panePos.multiplyBy(-1)._subtract(size.multiplyBy(p)._round()),
+                   max = min.add(size.multiplyBy(1 + p * 2)._round());
+
+               this._pathViewport = new L.Bounds(min, max);
+       }
+});
+
+
+L.Path.SVG_NS = 'http://www.w3.org/2000/svg';
+
+L.Browser.svg = !!(document.createElementNS && document.createElementNS(L.Path.SVG_NS, 'svg').createSVGRect);
+
+L.Path = L.Path.extend({
+       statics: {
+               SVG: L.Browser.svg
+       },
+
+       bringToFront: function () {
+               var root = this._map._pathRoot,
+                   path = this._container;
+
+               if (path && root.lastChild !== path) {
+                       root.appendChild(path);
+               }
+               return this;
+       },
+
+       bringToBack: function () {
+               var root = this._map._pathRoot,
+                   path = this._container,
+                   first = root.firstChild;
+
+               if (path && first !== path) {
+                       root.insertBefore(path, first);
+               }
+               return this;
+       },
+
+       getPathString: function () {
+               // form path string here
+       },
+
+       _createElement: function (name) {
+               return document.createElementNS(L.Path.SVG_NS, name);
+       },
+
+       _initElements: function () {
+               this._map._initPathRoot();
+               this._initPath();
+               this._initStyle();
+       },
+
+       _initPath: function () {
+               this._container = this._createElement('g');
+
+               this._path = this._createElement('path');
+               this._container.appendChild(this._path);
+       },
+
+       _initStyle: function () {
+               if (this.options.stroke) {
+                       this._path.setAttribute('stroke-linejoin', 'round');
+                       this._path.setAttribute('stroke-linecap', 'round');
+               }
+               if (this.options.fill) {
+                       this._path.setAttribute('fill-rule', 'evenodd');
+               }
+               this._updateStyle();
+       },
+
+       _updateStyle: function () {
+               if (this.options.stroke) {
+                       this._path.setAttribute('stroke', this.options.color);
+                       this._path.setAttribute('stroke-opacity', this.options.opacity);
+                       this._path.setAttribute('stroke-width', this.options.weight);
+                       if (this.options.dashArray) {
+                               this._path.setAttribute('stroke-dasharray', this.options.dashArray);
+                       } else {
+                               this._path.removeAttribute('stroke-dasharray');
+                       }
+               } else {
+                       this._path.setAttribute('stroke', 'none');
+               }
+               if (this.options.fill) {
+                       this._path.setAttribute('fill', this.options.fillColor || this.options.color);
+                       this._path.setAttribute('fill-opacity', this.options.fillOpacity);
+               } else {
+                       this._path.setAttribute('fill', 'none');
+               }
+       },
+
+       _updatePath: function () {
+               var str = this.getPathString();
+               if (!str) {
+                       // fix webkit empty string parsing bug
+                       str = 'M0 0';
+               }
+               this._path.setAttribute('d', str);
+       },
+
+       // TODO remove duplication with L.Map
+       _initEvents: function () {
+               if (this.options.clickable) {
+                       if (L.Browser.svg || !L.Browser.vml) {
+                               this._path.setAttribute('class', 'leaflet-clickable');
+                       }
+
+                       L.DomEvent.on(this._container, 'click', this._onMouseClick, this);
+
+                       var events = ['dblclick', 'mousedown', 'mouseover',
+                                     'mouseout', 'mousemove', 'contextmenu'];
+                       for (var i = 0; i < events.length; i++) {
+                               L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this);
+                       }
+               }
+       },
+
+       _onMouseClick: function (e) {
+               if (this._map.dragging && this._map.dragging.moved()) { return; }
+
+               this._fireMouseEvent(e);
+       },
+
+       _fireMouseEvent: function (e) {
+               if (!this.hasEventListeners(e.type)) { return; }
+
+               var map = this._map,
+                   containerPoint = map.mouseEventToContainerPoint(e),
+                   layerPoint = map.containerPointToLayerPoint(containerPoint),
+                   latlng = map.layerPointToLatLng(layerPoint);
+
+               this.fire(e.type, {
+                       latlng: latlng,
+                       layerPoint: layerPoint,
+                       containerPoint: containerPoint,
+                       originalEvent: e
+               });
+
+               if (e.type === 'contextmenu') {
+                       L.DomEvent.preventDefault(e);
+               }
+               if (e.type !== 'mousemove') {
+                       L.DomEvent.stopPropagation(e);
+               }
+       }
+});
+
+L.Map.include({
+       _initPathRoot: function () {
+               if (!this._pathRoot) {
+                       this._pathRoot = L.Path.prototype._createElement('svg');
+                       this._panes.overlayPane.appendChild(this._pathRoot);
+
+                       if (this.options.zoomAnimation && L.Browser.any3d) {
+                               this._pathRoot.setAttribute('class', ' leaflet-zoom-animated');
+
+                               this.on({
+                                       'zoomanim': this._animatePathZoom,
+                                       'zoomend': this._endPathZoom
+                               });
+                       } else {
+                               this._pathRoot.setAttribute('class', ' leaflet-zoom-hide');
+                       }
+
+                       this.on('moveend', this._updateSvgViewport);
+                       this._updateSvgViewport();
+               }
+       },
+
+       _animatePathZoom: function (opt) {
+               var scale = this.getZoomScale(opt.zoom),
+                   offset = this._getCenterOffset(opt.center),
+                   translate = offset.multiplyBy(-scale)._add(this._pathViewport.min);
+
+               this._pathRoot.style[L.DomUtil.TRANSFORM] =
+                       L.DomUtil.getTranslateString(translate) + ' scale(' + scale + ') ';
+
+               this._pathZooming = true;
+       },
+
+       _endPathZoom: function () {
+               this._pathZooming = false;
+       },
+
+       _updateSvgViewport: function () {
+
+               if (this._pathZooming) {
+                       // Do not update SVGs while a zoom animation is going on otherwise the animation will break.
+                       // When the zoom animation ends we will be updated again anyway
+                       // This fixes the case where you do a momentum move and zoom while the move is still ongoing.
+                       return;
+               }
+
+               this._updatePathViewport();
+
+               var vp = this._pathViewport,
+                   min = vp.min,
+                   max = vp.max,
+                   width = max.x - min.x,
+                   height = max.y - min.y,
+                   root = this._pathRoot,
+                   pane = this._panes.overlayPane;
+
+               // Hack to make flicker on drag end on mobile webkit less irritating
+               if (L.Browser.mobileWebkit) {
+                       pane.removeChild(root);
+               }
+
+               L.DomUtil.setPosition(root, min);
+               root.setAttribute('width', width);
+               root.setAttribute('height', height);
+               root.setAttribute('viewBox', [min.x, min.y, width, height].join(' '));
+
+               if (L.Browser.mobileWebkit) {
+                       pane.appendChild(root);
+               }
+       }
+});
+
+
+/*
+ * Popup extension to L.Path (polylines, polygons, circles), adding bindPopup method.
+ */
+
+L.Path.include({
+
+       bindPopup: function (content, options) {
+
+               if (!this._popup || options) {
+                       this._popup = new L.Popup(options, this);
+               }
+
+               this._popup.setContent(content);
+
+               if (!this._popupHandlersAdded) {
+                       this
+                           .on('click', this._openPopup, this)
+                           .on('remove', this.closePopup, this);
+
+                       this._popupHandlersAdded = true;
+               }
+
+               return this;
+       },
+
+       unbindPopup: function () {
+               if (this._popup) {
+                       this._popup = null;
+                       this
+                           .off('click', this.openPopup)
+                           .off('remove', this.closePopup);
+
+                       this._popupHandlersAdded = false;
+               }
+               return this;
+       },
+
+       openPopup: function (latlng) {
+
+               if (this._popup) {
+                       // open the popup from one of the path's points if not specified
+                       latlng = latlng || this._latlng ||
+                                this._latlngs[Math.floor(this._latlngs.length / 2)];
+
+                       this._openPopup({latlng: latlng});
+               }
+
+               return this;
+       },
+
+       closePopup: function () {
+               if (this._popup) {
+                       this._popup._close();
+               }
+               return this;
+       },
+
+       _openPopup: function (e) {
+               this._popup.setLatLng(e.latlng);
+               this._map.openPopup(this._popup);
+       }
+});
+
+
+/*
+ * Vector rendering for IE6-8 through VML.
+ * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
+ */
+
+L.Browser.vml = !L.Browser.svg && (function () {
+       try {
+               var div = document.createElement('div');
+               div.innerHTML = '<v:shape adj="1"/>';
+
+               var shape = div.firstChild;
+               shape.style.behavior = 'url(#default#VML)';
+
+               return shape && (typeof shape.adj === 'object');
+
+       } catch (e) {
+               return false;
+       }
+}());
+
+L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({
+       statics: {
+               VML: true,
+               CLIP_PADDING: 0.02
+       },
+
+       _createElement: (function () {
+               try {
+                       document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
+                       return function (name) {
+                               return document.createElement('<lvml:' + name + ' class="lvml">');
+                       };
+               } catch (e) {
+                       return function (name) {
+                               return document.createElement(
+                                       '<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
+                       };
+               }
+       }()),
+
+       _initPath: function () {
+               var container = this._container = this._createElement('shape');
+               L.DomUtil.addClass(container, 'leaflet-vml-shape');
+               if (this.options.clickable) {
+                       L.DomUtil.addClass(container, 'leaflet-clickable');
+               }
+               container.coordsize = '1 1';
+
+               this._path = this._createElement('path');
+               container.appendChild(this._path);
+
+               this._map._pathRoot.appendChild(container);
+       },
+
+       _initStyle: function () {
+               this._updateStyle();
+       },
+
+       _updateStyle: function () {
+               var stroke = this._stroke,
+                   fill = this._fill,
+                   options = this.options,
+                   container = this._container;
+
+               container.stroked = options.stroke;
+               container.filled = options.fill;
+
+               if (options.stroke) {
+                       if (!stroke) {
+                               stroke = this._stroke = this._createElement('stroke');
+                               stroke.endcap = 'round';
+                               container.appendChild(stroke);
+                       }
+                       stroke.weight = options.weight + 'px';
+                       stroke.color = options.color;
+                       stroke.opacity = options.opacity;
+                       if (options.dashArray) {
+                               stroke.dashStyle = options.dashArray.replace(/ *, */g, ' ');
+                       } else {
+                               stroke.dashStyle = '';
+                       }
+               } else if (stroke) {
+                       container.removeChild(stroke);
+                       this._stroke = null;
+               }
+
+               if (options.fill) {
+                       if (!fill) {
+                               fill = this._fill = this._createElement('fill');
+                               container.appendChild(fill);
+                       }
+                       fill.color = options.fillColor || options.color;
+                       fill.opacity = options.fillOpacity;
+               } else if (fill) {
+                       container.removeChild(fill);
+                       this._fill = null;
+               }
+       },
+
+       _updatePath: function () {
+               var style = this._container.style;
+
+               style.display = 'none';
+               this._path.v = this.getPathString() + ' '; // the space fixes IE empty path string bug
+               style.display = '';
+       }
+});
+
+L.Map.include(L.Browser.svg || !L.Browser.vml ? {} : {
+       _initPathRoot: function () {
+               if (this._pathRoot) { return; }
+
+               var root = this._pathRoot = document.createElement('div');
+               root.className = 'leaflet-vml-container';
+               this._panes.overlayPane.appendChild(root);
+
+               this.on('moveend', this._updatePathViewport);
+               this._updatePathViewport();
+       }
+});
+
+
+/*
+ * Vector rendering for all browsers that support canvas.
+ */
+
+L.Browser.canvas = (function () {
+       return !!document.createElement('canvas').getContext;
+}());
+
+L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path : L.Path.extend({
+       statics: {
+               //CLIP_PADDING: 0.02, // not sure if there's a need to set it to a small value
+               CANVAS: true,
+               SVG: false
+       },
+
+       redraw: function () {
+               if (this._map) {
+                       this.projectLatlngs();
+                       this._requestUpdate();
+               }
+               return this;
+       },
+
+       setStyle: function (style) {
+               L.setOptions(this, style);
+
+               if (this._map) {
+                       this._updateStyle();
+                       this._requestUpdate();
+               }
+               return this;
+       },
+
+       onRemove: function (map) {
+               map
+                   .off('viewreset', this.projectLatlngs, this)
+                   .off('moveend', this._updatePath, this);
+
+               this._requestUpdate();
+
+               this._map = null;
+       },
+
+       _requestUpdate: function () {
+               if (this._map && !L.Path._updateRequest) {
+                       L.Path._updateRequest = L.Util.requestAnimFrame(this._fireMapMoveEnd, this._map);
+               }
+       },
+
+       _fireMapMoveEnd: function () {
+               L.Path._updateRequest = null;
+               this.fire('moveend');
+       },
+
+       _initElements: function () {
+               this._map._initPathRoot();
+               this._ctx = this._map._canvasCtx;
+       },
+
+       _updateStyle: function () {
+               var options = this.options;
+
+               if (options.stroke) {
+                       this._ctx.lineWidth = options.weight;
+                       this._ctx.strokeStyle = options.color;
+               }
+               if (options.fill) {
+                       this._ctx.fillStyle = options.fillColor || options.color;
+               }
+       },
+
+       _drawPath: function () {
+               var i, j, len, len2, point, drawMethod;
+
+               this._ctx.beginPath();
+
+               for (i = 0, len = this._parts.length; i < len; i++) {
+                       for (j = 0, len2 = this._parts[i].length; j < len2; j++) {
+                               point = this._parts[i][j];
+                               drawMethod = (j === 0 ? 'move' : 'line') + 'To';
+
+                               this._ctx[drawMethod](point.x, point.y);
+                       }
+                       // TODO refactor ugly hack
+                       if (this instanceof L.Polygon) {
+                               this._ctx.closePath();
+                       }
+               }
+       },
+
+       _checkIfEmpty: function () {
+               return !this._parts.length;
+       },
+
+       _updatePath: function () {
+               if (this._checkIfEmpty()) { return; }
+
+               var ctx = this._ctx,
+                   options = this.options;
+
+               this._drawPath();
+               ctx.save();
+               this._updateStyle();
+
+               if (options.fill) {
+                       if (options.fillOpacity < 1) {
+                               ctx.globalAlpha = options.fillOpacity;
+                       }
+                       ctx.fill();
+               }
+
+               if (options.stroke) {
+                       if (options.opacity < 1) {
+                               ctx.globalAlpha = options.opacity;
+                       }
+                       ctx.stroke();
+               }
+
+               ctx.restore();
+
+               // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
+       },
+
+       _initEvents: function () {
+               if (this.options.clickable) {
+                       // TODO hand cursor
+                       // TODO mouseover, mouseout, dblclick
+                       this._map.on('click', this._onClick, this);
+               }
+       },
+
+       _onClick: function (e) {
+               if (this._containsPoint(e.layerPoint)) {
+                       this.fire('click', e);
+               }
+       }
+});
+
+L.Map.include((L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? {} : {
+       _initPathRoot: function () {
+               var root = this._pathRoot,
+                   ctx;
+
+               if (!root) {
+                       root = this._pathRoot = document.createElement("canvas");
+                       root.style.position = 'absolute';
+                       ctx = this._canvasCtx = root.getContext('2d');
+
+                       ctx.lineCap = "round";
+                       ctx.lineJoin = "round";
+
+                       this._panes.overlayPane.appendChild(root);
+
+                       if (this.options.zoomAnimation) {
+                               this._pathRoot.className = 'leaflet-zoom-animated';
+                               this.on('zoomanim', this._animatePathZoom);
+                               this.on('zoomend', this._endPathZoom);
+                       }
+                       this.on('moveend', this._updateCanvasViewport);
+                       this._updateCanvasViewport();
+               }
+       },
+
+       _updateCanvasViewport: function () {
+               // don't redraw while zooming. See _updateSvgViewport for more details
+               if (this._pathZooming) { return; }
+               this._updatePathViewport();
+
+               var vp = this._pathViewport,
+                   min = vp.min,
+                   size = vp.max.subtract(min),
+                   root = this._pathRoot;
+
+               //TODO check if this works properly on mobile webkit
+               L.DomUtil.setPosition(root, min);
+               root.width = size.x;
+               root.height = size.y;
+               root.getContext('2d').translate(-min.x, -min.y);
+       }
+});
+
+
+/*
+ * L.LineUtil contains different utility functions for line segments
+ * and polylines (clipping, simplification, distances, etc.)
+ */
+
+L.LineUtil = {
+
+       // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
+       // Improves rendering performance dramatically by lessening the number of points to draw.
+
+       simplify: function (/*Point[]*/ points, /*Number*/ tolerance) {
+               if (!tolerance || !points.length) {
+                       return points.slice();
+               }
+
+               var sqTolerance = tolerance * tolerance;
+
+               // stage 1: vertex reduction
+               points = this._reducePoints(points, sqTolerance);
+
+               // stage 2: Douglas-Peucker simplification
+               points = this._simplifyDP(points, sqTolerance);
+
+               return points;
+       },
+
+       // distance from a point to a segment between two points
+       pointToSegmentDistance:  function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
+               return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true));
+       },
+
+       closestPointOnSegment: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
+               return this._sqClosestPointOnSegment(p, p1, p2);
+       },
+
+       // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
+       _simplifyDP: function (points, sqTolerance) {
+
+               var len = points.length,
+                   ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
+                   markers = new ArrayConstructor(len);
+
+               markers[0] = markers[len - 1] = 1;
+
+               this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
+
+               var i,
+                   newPoints = [];
+
+               for (i = 0; i < len; i++) {
+                       if (markers[i]) {
+                               newPoints.push(points[i]);
+                       }
+               }
+
+               return newPoints;
+       },
+
+       _simplifyDPStep: function (points, markers, sqTolerance, first, last) {
+
+               var maxSqDist = 0,
+                   index, i, sqDist;
+
+               for (i = first + 1; i <= last - 1; i++) {
+                       sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true);
+
+                       if (sqDist > maxSqDist) {
+                               index = i;
+                               maxSqDist = sqDist;
+                       }
+               }
+
+               if (maxSqDist > sqTolerance) {
+                       markers[index] = 1;
+
+                       this._simplifyDPStep(points, markers, sqTolerance, first, index);
+                       this._simplifyDPStep(points, markers, sqTolerance, index, last);
+               }
+       },
+
+       // reduce points that are too close to each other to a single point
+       _reducePoints: function (points, sqTolerance) {
+               var reducedPoints = [points[0]];
+
+               for (var i = 1, prev = 0, len = points.length; i < len; i++) {
+                       if (this._sqDist(points[i], points[prev]) > sqTolerance) {
+                               reducedPoints.push(points[i]);
+                               prev = i;
+                       }
+               }
+               if (prev < len - 1) {
+                       reducedPoints.push(points[len - 1]);
+               }
+               return reducedPoints;
+       },
+
+       /*jshint bitwise:false */ // temporarily allow bitwise oprations
+
+       // Cohen-Sutherland line clipping algorithm.
+       // Used to avoid rendering parts of a polyline that are not currently visible.
+
+       clipSegment: function (a, b, bounds, useLastCode) {
+               var min = bounds.min,
+                   max = bounds.max,
+
+                   codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds),
+                   codeB = this._getBitCode(b, bounds),
+
+                   codeOut, p, newCode;
+
+               // save 2nd code to avoid calculating it on the next segment
+               this._lastCode = codeB;
+
+               while (true) {
+                       // if a,b is inside the clip window (trivial accept)
+                       if (!(codeA | codeB)) {
+                               return [a, b];
+                       // if a,b is outside the clip window (trivial reject)
+                       } else if (codeA & codeB) {
+                               return false;
+                       // other cases
+                       } else {
+                               codeOut = codeA || codeB,
+                               p = this._getEdgeIntersection(a, b, codeOut, bounds),
+                               newCode = this._getBitCode(p, bounds);
+
+                               if (codeOut === codeA) {
+                                       a = p;
+                                       codeA = newCode;
+                               } else {
+                                       b = p;
+                                       codeB = newCode;
+                               }
+                       }
+               }
+       },
+
+       _getEdgeIntersection: function (a, b, code, bounds) {
+               var dx = b.x - a.x,
+                   dy = b.y - a.y,
+                   min = bounds.min,
+                   max = bounds.max;
+
+               if (code & 8) { // top
+                       return new L.Point(a.x + dx * (max.y - a.y) / dy, max.y);
+               } else if (code & 4) { // bottom
+                       return new L.Point(a.x + dx * (min.y - a.y) / dy, min.y);
+               } else if (code & 2) { // right
+                       return new L.Point(max.x, a.y + dy * (max.x - a.x) / dx);
+               } else if (code & 1) { // left
+                       return new L.Point(min.x, a.y + dy * (min.x - a.x) / dx);
+               }
+       },
+
+       _getBitCode: function (/*Point*/ p, bounds) {
+               var code = 0;
+
+               if (p.x < bounds.min.x) { // left
+                       code |= 1;
+               } else if (p.x > bounds.max.x) { // right
+                       code |= 2;
+               }
+               if (p.y < bounds.min.y) { // bottom
+                       code |= 4;
+               } else if (p.y > bounds.max.y) { // top
+                       code |= 8;
+               }
+
+               return code;
+       },
+
+       /*jshint bitwise:true */
+
+       // square distance (to avoid unnecessary Math.sqrt calls)
+       _sqDist: function (p1, p2) {
+               var dx = p2.x - p1.x,
+                   dy = p2.y - p1.y;
+               return dx * dx + dy * dy;
+       },
+
+       // return closest point on segment or distance to that point
+       _sqClosestPointOnSegment: function (p, p1, p2, sqDist) {
+               var x = p1.x,
+                   y = p1.y,
+                   dx = p2.x - x,
+                   dy = p2.y - y,
+                   dot = dx * dx + dy * dy,
+                   t;
+
+               if (dot > 0) {
+                       t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
+
+                       if (t > 1) {
+                               x = p2.x;
+                               y = p2.y;
+                       } else if (t > 0) {
+                               x += dx * t;
+                               y += dy * t;
+                       }
+               }
+
+               dx = p.x - x;
+               dy = p.y - y;
+
+               return sqDist ? dx * dx + dy * dy : new L.Point(x, y);
+       }
+};
+
+
+L.Polyline = L.Path.extend({
+       initialize: function (latlngs, options) {
+               L.Path.prototype.initialize.call(this, options);
+
+               this._latlngs = this._convertLatLngs(latlngs);
+
+               // TODO refactor: move to Polyline.Edit.js
+               if (L.Handler.PolyEdit) {
+                       this.editing = new L.Handler.PolyEdit(this);
+
+                       if (this.options.editable) {
+                               this.editing.enable();
+                       }
+               }
+       },
+
+       options: {
+               // how much to simplify the polyline on each zoom level
+               // more = better performance and smoother look, less = more accurate
+               smoothFactor: 1.0,
+               noClip: false
+       },
+
+       projectLatlngs: function () {
+               this._originalPoints = [];
+
+               for (var i = 0, len = this._latlngs.length; i < len; i++) {
+                       this._originalPoints[i] = this._map.latLngToLayerPoint(this._latlngs[i]);
+               }
+       },
+
+       getPathString: function () {
+               for (var i = 0, len = this._parts.length, str = ''; i < len; i++) {
+                       str += this._getPathPartStr(this._parts[i]);
+               }
+               return str;
+       },
+
+       getLatLngs: function () {
+               return this._latlngs;
+       },
+
+       setLatLngs: function (latlngs) {
+               this._latlngs = this._convertLatLngs(latlngs);
+               return this.redraw();
+       },
+
+       addLatLng: function (latlng) {
+               this._latlngs.push(L.latLng(latlng));
+               return this.redraw();
+       },
+
+       spliceLatLngs: function (index, howMany) {
+               var removed = [].splice.apply(this._latlngs, arguments);
+               this._convertLatLngs(this._latlngs);
+               this.redraw();
+               return removed;
+       },
+
+       closestLayerPoint: function (p) {
+               var minDistance = Infinity, parts = this._parts, p1, p2, minPoint = null;
+
+               for (var j = 0, jLen = parts.length; j < jLen; j++) {
+                       var points = parts[j];
+                       for (var i = 1, len = points.length; i < len; i++) {
+                               p1 = points[i - 1];
+                               p2 = points[i];
+                               var sqDist = L.LineUtil._sqClosestPointOnSegment(p, p1, p2, true);
+                               if (sqDist < minDistance) {
+                                       minDistance = sqDist;
+                                       minPoint = L.LineUtil._sqClosestPointOnSegment(p, p1, p2);
+                               }
+                       }
+               }
+               if (minPoint) {
+                       minPoint.distance = Math.sqrt(minDistance);
+               }
+               return minPoint;
+       },
+
+       getBounds: function () {
+               var bounds = new L.LatLngBounds(),
+                   latLngs = this.getLatLngs(),
+                   i, len;
+
+               for (i = 0, len = latLngs.length; i < len; i++) {
+                       bounds.extend(latLngs[i]);
+               }
+
+               return bounds;
+       },
+
+       // TODO refactor: move to Polyline.Edit.js
+       onAdd: function (map) {
+               L.Path.prototype.onAdd.call(this, map);
+
+               if (this.editing && this.editing.enabled()) {
+                       this.editing.addHooks();
+               }
+       },
+
+       onRemove: function (map) {
+               if (this.editing && this.editing.enabled()) {
+                       this.editing.removeHooks();
+               }
+
+               L.Path.prototype.onRemove.call(this, map);
+       },
+
+       _convertLatLngs: function (latlngs) {
+               var i, len;
+               for (i = 0, len = latlngs.length; i < len; i++) {
+                       if (latlngs[i] instanceof Array && typeof latlngs[i][0] !== 'number') {
+                               return;
+                       }
+                       latlngs[i] = L.latLng(latlngs[i]);
+               }
+               return latlngs;
+       },
+
+       _initEvents: function () {
+               L.Path.prototype._initEvents.call(this);
+       },
+
+       _getPathPartStr: function (points) {
+               var round = L.Path.VML;
+
+               for (var j = 0, len2 = points.length, str = '', p; j < len2; j++) {
+                       p = points[j];
+                       if (round) {
+                               p._round();
+                       }
+                       str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
+               }
+               return str;
+       },
+
+       _clipPoints: function () {
+               var points = this._originalPoints,
+                   len = points.length,
+                   i, k, segment;
+
+               if (this.options.noClip) {
+                       this._parts = [points];
+                       return;
+               }
+
+               this._parts = [];
+
+               var parts = this._parts,
+                   vp = this._map._pathViewport,
+                   lu = L.LineUtil;
+
+               for (i = 0, k = 0; i < len - 1; i++) {
+                       segment = lu.clipSegment(points[i], points[i + 1], vp, i);
+                       if (!segment) {
+                               continue;
+                       }
+
+                       parts[k] = parts[k] || [];
+                       parts[k].push(segment[0]);
+
+                       // if segment goes out of screen, or it's the last one, it's the end of the line part
+                       if ((segment[1] !== points[i + 1]) || (i === len - 2)) {
+                               parts[k].push(segment[1]);
+                               k++;
+                       }
+               }
+       },
+
+       // simplify each clipped part of the polyline
+       _simplifyPoints: function () {
+               var parts = this._parts,
+                   lu = L.LineUtil;
+
+               for (var i = 0, len = parts.length; i < len; i++) {
+                       parts[i] = lu.simplify(parts[i], this.options.smoothFactor);
+               }
+       },
+
+       _updatePath: function () {
+               if (!this._map) { return; }
+
+               this._clipPoints();
+               this._simplifyPoints();
+
+               L.Path.prototype._updatePath.call(this);
+       }
+});
+
+L.polyline = function (latlngs, options) {
+       return new L.Polyline(latlngs, options);
+};
+
+
+/*
+ * L.PolyUtil contains utilify functions for polygons (clipping, etc.).
+ */
+
+/*jshint bitwise:false */ // allow bitwise oprations here
+
+L.PolyUtil = {};
+
+/*
+ * Sutherland-Hodgeman polygon clipping algorithm.
+ * Used to avoid rendering parts of a polygon that are not currently visible.
+ */
+L.PolyUtil.clipPolygon = function (points, bounds) {
+       var min = bounds.min,
+           max = bounds.max,
+           clippedPoints,
+           edges = [1, 4, 2, 8],
+           i, j, k,
+           a, b,
+           len, edge, p,
+           lu = L.LineUtil;
+
+       for (i = 0, len = points.length; i < len; i++) {
+               points[i]._code = lu._getBitCode(points[i], bounds);
+       }
+
+       // for each edge (left, bottom, right, top)
+       for (k = 0; k < 4; k++) {
+               edge = edges[k];
+               clippedPoints = [];
+
+               for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
+                       a = points[i];
+                       b = points[j];
+
+                       // if a is inside the clip window
+                       if (!(a._code & edge)) {
+                               // if b is outside the clip window (a->b goes out of screen)
+                               if (b._code & edge) {
+                                       p = lu._getEdgeIntersection(b, a, edge, bounds);
+                                       p._code = lu._getBitCode(p, bounds);
+                                       clippedPoints.push(p);
+                               }
+                               clippedPoints.push(a);
+
+                       // else if b is inside the clip window (a->b enters the screen)
+                       } else if (!(b._code & edge)) {
+                               p = lu._getEdgeIntersection(b, a, edge, bounds);
+                               p._code = lu._getBitCode(p, bounds);
+                               clippedPoints.push(p);
+                       }
+               }
+               points = clippedPoints;
+       }
+
+       return points;
+};
+
+/*jshint bitwise:true */
+
+
+/*
+ * L.Polygon is used to display polygons on a map.
+ */
+
+L.Polygon = L.Polyline.extend({
+       options: {
+               fill: true
+       },
+
+       initialize: function (latlngs, options) {
+               L.Polyline.prototype.initialize.call(this, latlngs, options);
+
+               if (latlngs && (latlngs[0] instanceof Array) && (typeof latlngs[0][0] !== 'number')) {
+                       this._latlngs = this._convertLatLngs(latlngs[0]);
+                       this._holes = latlngs.slice(1);
+               }
+       },
+
+       projectLatlngs: function () {
+               L.Polyline.prototype.projectLatlngs.call(this);
+
+               // project polygon holes points
+               // TODO move this logic to Polyline to get rid of duplication
+               this._holePoints = [];
+
+               if (!this._holes) { return; }
+
+               var i, j, len, len2, hole;
+
+               for (i = 0, len = this._holes.length; i < len; i++) {
+                       this._holePoints[i] = [];
+
+                       for (j = 0, len2 = this._holes[i].length; j < len2; j++) {
+                               this._holePoints[i][j] = this._map.latLngToLayerPoint(this._holes[i][j]);
+                       }
+               }
+       },
+
+       _clipPoints: function () {
+               var points = this._originalPoints,
+                   newParts = [];
+
+               this._parts = [points].concat(this._holePoints);
+
+               if (this.options.noClip) { return; }
+
+               for (var i = 0, len = this._parts.length; i < len; i++) {
+                       var clipped = L.PolyUtil.clipPolygon(this._parts[i], this._map._pathViewport);
+                       if (clipped.length) {
+                               newParts.push(clipped);
+                       }
+               }
+
+               this._parts = newParts;
+       },
+
+       _getPathPartStr: function (points) {
+               var str = L.Polyline.prototype._getPathPartStr.call(this, points);
+               return str + (L.Browser.svg ? 'z' : 'x');
+       }
+});
+
+L.polygon = function (latlngs, options) {
+       return new L.Polygon(latlngs, options);
+};
+
+
+/*
+ * Contains L.MultiPolyline and L.MultiPolygon layers.
+ */
+
+(function () {
+       function createMulti(Klass) {
+
+               return L.FeatureGroup.extend({
+
+                       initialize: function (latlngs, options) {
+                               this._layers = {};
+                               this._options = options;
+                               this.setLatLngs(latlngs);
+                       },
+
+                       setLatLngs: function (latlngs) {
+                               var i = 0,
+                                   len = latlngs.length;
+
+                               this.eachLayer(function (layer) {
+                                       if (i < len) {
+                                               layer.setLatLngs(latlngs[i++]);
+                                       } else {
+                                               this.removeLayer(layer);
+                                       }
+                               }, this);
+
+                               while (i < len) {
+                                       this.addLayer(new Klass(latlngs[i++], this._options));
+                               }
+
+                               return this;
+                       }
+               });
+       }
+
+       L.MultiPolyline = createMulti(L.Polyline);
+       L.MultiPolygon = createMulti(L.Polygon);
+
+       L.multiPolyline = function (latlngs, options) {
+               return new L.MultiPolyline(latlngs, options);
+       };
+
+       L.multiPolygon = function (latlngs, options) {
+               return new L.MultiPolygon(latlngs, options);
+       };
+}());
+
+
+/*
+ * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds
+ */
+
+L.Rectangle = L.Polygon.extend({
+       initialize: function (latLngBounds, options) {
+               L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
+       },
+
+       setBounds: function (latLngBounds) {
+               this.setLatLngs(this._boundsToLatLngs(latLngBounds));
+       },
+
+       _boundsToLatLngs: function (latLngBounds) {
+               latLngBounds = L.latLngBounds(latLngBounds);
+               return [
+                       latLngBounds.getSouthWest(),
+                       latLngBounds.getNorthWest(),
+                       latLngBounds.getNorthEast(),
+                       latLngBounds.getSouthEast(),
+                       latLngBounds.getSouthWest()
+               ];
+       }
+});
+
+L.rectangle = function (latLngBounds, options) {
+       return new L.Rectangle(latLngBounds, options);
+};
+
+
+/*
+ * L.Circle is a circle overlay (with a certain radius in meters).
+ */
+
+L.Circle = L.Path.extend({
+       initialize: function (latlng, radius, options) {
+               L.Path.prototype.initialize.call(this, options);
+
+               this._latlng = L.latLng(latlng);
+               this._mRadius = radius;
+       },
+
+       options: {
+               fill: true
+       },
+
+       setLatLng: function (latlng) {
+               this._latlng = L.latLng(latlng);
+               return this.redraw();
+       },
+
+       setRadius: function (radius) {
+               this._mRadius = radius;
+               return this.redraw();
+       },
+
+       projectLatlngs: function () {
+               var lngRadius = this._getLngRadius(),
+                   latlng2 = new L.LatLng(this._latlng.lat, this._latlng.lng - lngRadius, true),
+                   point2 = this._map.latLngToLayerPoint(latlng2);
+
+               this._point = this._map.latLngToLayerPoint(this._latlng);
+               this._radius = Math.max(Math.round(this._point.x - point2.x), 1);
+       },
+
+       getBounds: function () {
+               var lngRadius = this._getLngRadius(),
+                   latRadius = (this._mRadius / 40075017) * 360,
+                   latlng = this._latlng,
+                   sw = new L.LatLng(latlng.lat - latRadius, latlng.lng - lngRadius),
+                   ne = new L.LatLng(latlng.lat + latRadius, latlng.lng + lngRadius);
+
+               return new L.LatLngBounds(sw, ne);
+       },
+
+       getLatLng: function () {
+               return this._latlng;
+       },
+
+       getPathString: function () {
+               var p = this._point,
+                   r = this._radius;
+
+               if (this._checkIfEmpty()) {
+                       return '';
+               }
+
+               if (L.Browser.svg) {
+                       return "M" + p.x + "," + (p.y - r) +
+                              "A" + r + "," + r + ",0,1,1," +
+                              (p.x - 0.1) + "," + (p.y - r) + " z";
+               } else {
+                       p._round();
+                       r = Math.round(r);
+                       return "AL " + p.x + "," + p.y + " " + r + "," + r + " 0," + (65535 * 360);
+               }
+       },
+
+       getRadius: function () {
+               return this._mRadius;
+       },
+
+       // TODO Earth hardcoded, move into projection code!
+
+       _getLatRadius: function () {
+               return (this._mRadius / 40075017) * 360;
+       },
+
+       _getLngRadius: function () {
+               return this._getLatRadius() / Math.cos(L.LatLng.DEG_TO_RAD * this._latlng.lat);
+       },
+
+       _checkIfEmpty: function () {
+               if (!this._map) {
+                       return false;
+               }
+               var vp = this._map._pathViewport,
+                   r = this._radius,
+                   p = this._point;
+
+               return p.x - r > vp.max.x || p.y - r > vp.max.y ||
+                      p.x + r < vp.min.x || p.y + r < vp.min.y;
+       }
+});
+
+L.circle = function (latlng, radius, options) {
+       return new L.Circle(latlng, radius, options);
+};
+
+
+/*
+ * L.CircleMarker is a circle overlay with a permanent pixel radius.
+ */
+
+L.CircleMarker = L.Circle.extend({
+       options: {
+               radius: 10,
+               weight: 2
+       },
+
+       initialize: function (latlng, options) {
+               L.Circle.prototype.initialize.call(this, latlng, null, options);
+               this._radius = this.options.radius;
+       },
+
+       projectLatlngs: function () {
+               this._point = this._map.latLngToLayerPoint(this._latlng);
+       },
+
+       setRadius: function (radius) {
+               this._radius = radius;
+               return this.redraw();
+       }
+});
+
+L.circleMarker = function (latlng, options) {
+       return new L.CircleMarker(latlng, options);
+};
+
+
+
+L.Polyline.include(!L.Path.CANVAS ? {} : {
+       _containsPoint: function (p, closed) {
+               var i, j, k, len, len2, dist, part,
+                   w = this.options.weight / 2;
+
+               if (L.Browser.touch) {
+                       w += 10; // polyline click tolerance on touch devices
+               }
+
+               for (i = 0, len = this._parts.length; i < len; i++) {
+                       part = this._parts[i];
+                       for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
+                               if (!closed && (j === 0)) {
+                                       continue;
+                               }
+
+                               dist = L.LineUtil.pointToSegmentDistance(p, part[k], part[j]);
+
+                               if (dist <= w) {
+                                       return true;
+                               }
+                       }
+               }
+               return false;
+       }
+});
+
+
+
+L.Polygon.include(!L.Path.CANVAS ? {} : {
+       _containsPoint: function (p) {
+               var inside = false,
+                   part, p1, p2,
+                   i, j, k,
+                   len, len2;
+
+               // TODO optimization: check if within bounds first
+
+               if (L.Polyline.prototype._containsPoint.call(this, p, true)) {
+                       // click on polygon border
+                       return true;
+               }
+
+               // ray casting algorithm for detecting if point is in polygon
+
+               for (i = 0, len = this._parts.length; i < len; i++) {
+                       part = this._parts[i];
+
+                       for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
+                               p1 = part[j];
+                               p2 = part[k];
+
+                               if (((p1.y > p.y) !== (p2.y > p.y)) &&
+                                               (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) {
+                                       inside = !inside;
+                               }
+                       }
+               }
+
+               return inside;
+       }
+});
+
+
+/*
+ * Circle canvas specific drawing parts.
+ */
+
+L.Circle.include(!L.Path.CANVAS ? {} : {
+       _drawPath: function () {
+               var p = this._point;
+               this._ctx.beginPath();
+               this._ctx.arc(p.x, p.y, this._radius, 0, Math.PI * 2, false);
+       },
+
+       _containsPoint: function (p) {
+               var center = this._point,
+                   w2 = this.options.stroke ? this.options.weight / 2 : 0;
+
+               return (p.distanceTo(center) <= this._radius + w2);
+       }
+});
+
+
+L.GeoJSON = L.FeatureGroup.extend({
+       initialize: function (geojson, options) {
+               L.setOptions(this, options);
+
+               this._layers = {};
+
+               if (geojson) {
+                       this.addData(geojson);
+               }
+       },
+
+       addData: function (geojson) {
+               var features = geojson instanceof Array ? geojson : geojson.features,
+                   i, len;
+
+               if (features) {
+                       for (i = 0, len = features.length; i < len; i++) {
+                               this.addData(features[i]);
+                       }
+                       return this;
+               }
+
+               var options = this.options;
+
+               if (options.filter && !options.filter(geojson)) { return; }
+
+               var layer = L.GeoJSON.geometryToLayer(geojson, options.pointToLayer);
+               layer.feature = geojson;
+
+               this.resetStyle(layer);
+
+               if (options.onEachFeature) {
+                       options.onEachFeature(geojson, layer);
+               }
+
+               return this.addLayer(layer);
+       },
+
+       resetStyle: function (layer) {
+               var style = this.options.style;
+               if (style) {
+                       this._setLayerStyle(layer, style);
+               }
+       },
+
+       setStyle: function (style) {
+               this.eachLayer(function (layer) {
+                       this._setLayerStyle(layer, style);
+               }, this);
+       },
+
+       _setLayerStyle: function (layer, style) {
+               if (typeof style === 'function') {
+                       style = style(layer.feature);
+               }
+               if (layer.setStyle) {
+                       layer.setStyle(style);
+               }
+       }
+});
+
+L.extend(L.GeoJSON, {
+       geometryToLayer: function (geojson, pointToLayer) {
+               var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
+                   coords = geometry.coordinates,
+                   layers = [],
+                   latlng, latlngs, i, len, layer;
+
+               switch (geometry.type) {
+               case 'Point':
+                       latlng = this.coordsToLatLng(coords);
+                       return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
+
+               case 'MultiPoint':
+                       for (i = 0, len = coords.length; i < len; i++) {
+                               latlng = this.coordsToLatLng(coords[i]);
+                               layer = pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
+                               layers.push(layer);
+                       }
+                       return new L.FeatureGroup(layers);
+
+               case 'LineString':
+                       latlngs = this.coordsToLatLngs(coords);
+                       return new L.Polyline(latlngs);
+
+               case 'Polygon':
+                       latlngs = this.coordsToLatLngs(coords, 1);
+                       return new L.Polygon(latlngs);
+
+               case 'MultiLineString':
+                       latlngs = this.coordsToLatLngs(coords, 1);
+                       return new L.MultiPolyline(latlngs);
+
+               case "MultiPolygon":
+                       latlngs = this.coordsToLatLngs(coords, 2);
+                       return new L.MultiPolygon(latlngs);
+
+               case "GeometryCollection":
+                       for (i = 0, len = geometry.geometries.length; i < len; i++) {
+                               layer = this.geometryToLayer(geometry.geometries[i], pointToLayer);
+                               layers.push(layer);
+                       }
+                       return new L.FeatureGroup(layers);
+
+               default:
+                       throw new Error('Invalid GeoJSON object.');
+               }
+       },
+
+       coordsToLatLng: function (coords, reverse) { // (Array, Boolean) -> LatLng
+               var lat = parseFloat(coords[reverse ? 0 : 1]),
+                   lng = parseFloat(coords[reverse ? 1 : 0]);
+
+               return new L.LatLng(lat, lng, true);
+       },
+
+       coordsToLatLngs: function (coords, levelsDeep, reverse) { // (Array, Number, Boolean) -> Array
+               var latlng,
+                   latlngs = [],
+                   i, len;
+
+               for (i = 0, len = coords.length; i < len; i++) {
+                       latlng = levelsDeep ?
+                               this.coordsToLatLngs(coords[i], levelsDeep - 1, reverse) :
+                               this.coordsToLatLng(coords[i], reverse);
+
+                       latlngs.push(latlng);
+               }
+
+               return latlngs;
+       }
+});
+
+L.geoJson = function (geojson, options) {
+       return new L.GeoJSON(geojson, options);
+};
+
+
+/*
+ * L.DomEvent contains functions for working with DOM events.
+ */
+
+L.DomEvent = {
+       /* inpired by John Resig, Dean Edwards and YUI addEvent implementations */
+       addListener: function (obj, type, fn, context) { // (HTMLElement, String, Function[, Object])
+
+               var id = L.stamp(fn),
+                   key = '_leaflet_' + type + id,
+                   handler, originalHandler, newType;
+
+               if (obj[key]) { return this; }
+
+               handler = function (e) {
+                       return fn.call(context || obj, e || L.DomEvent._getEvent());
+               };
+
+               if (L.Browser.msTouch && type.indexOf('touch') === 0) {
+                       return this.addMsTouchListener(obj, type, handler, id);
+               } else if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) {
+                       return this.addDoubleTapListener(obj, handler, id);
+
+               } else if ('addEventListener' in obj) {
+
+                       if (type === 'mousewheel') {
+                               obj.addEventListener('DOMMouseScroll', handler, false);
+                               obj.addEventListener(type, handler, false);
+
+                       } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
+
+                               originalHandler = handler;
+                               newType = (type === 'mouseenter' ? 'mouseover' : 'mouseout');
+
+                               handler = function (e) {
+                                       if (!L.DomEvent._checkMouse(obj, e)) { return; }
+                                       return originalHandler(e);
+                               };
+
+                               obj.addEventListener(newType, handler, false);
+
+                       } else {
+                               obj.addEventListener(type, handler, false);
+                       }
+
+               } else if ('attachEvent' in obj) {
+                       obj.attachEvent("on" + type, handler);
+               }
+
+               obj[key] = handler;
+
+               return this;
+       },
+
+       removeListener: function (obj, type, fn) {  // (HTMLElement, String, Function)
+
+               var id = L.stamp(fn),
+                   key = '_leaflet_' + type + id,
+                   handler = obj[key];
+
+               if (!handler) { return; }
+
+               if (L.Browser.msTouch && type.indexOf('touch') === 0) {
+                       this.removeMsTouchListener(obj, type, id);
+               } else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {
+                       this.removeDoubleTapListener(obj, id);
+
+               } else if ('removeEventListener' in obj) {
+
+                       if (type === 'mousewheel') {
+                               obj.removeEventListener('DOMMouseScroll', handler, false);
+                               obj.removeEventListener(type, handler, false);
+
+                       } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
+                               obj.removeEventListener((type === 'mouseenter' ? 'mouseover' : 'mouseout'), handler, false);
+                       } else {
+                               obj.removeEventListener(type, handler, false);
+                       }
+               } else if ('detachEvent' in obj) {
+                       obj.detachEvent("on" + type, handler);
+               }
+
+               obj[key] = null;
+
+               return this;
+       },
+
+       stopPropagation: function (e) {
+
+               if (e.stopPropagation) {
+                       e.stopPropagation();
+               } else {
+                       e.cancelBubble = true;
+               }
+               return this;
+       },
+
+       disableClickPropagation: function (el) {
+
+               var stop = L.DomEvent.stopPropagation;
+
+               return L.DomEvent
+                       .addListener(el, L.Draggable.START, stop)
+                       .addListener(el, 'click', stop)
+                       .addListener(el, 'dblclick', stop);
+       },
+
+       preventDefault: function (e) {
+
+               if (e.preventDefault) {
+                       e.preventDefault();
+               } else {
+                       e.returnValue = false;
+               }
+               return this;
+       },
+
+       stop: function (e) {
+               return L.DomEvent.preventDefault(e).stopPropagation(e);
+       },
+
+       getMousePosition: function (e, container) {
+
+               var body = document.body,
+                   docEl = document.documentElement,
+                   x = e.pageX ? e.pageX : e.clientX + body.scrollLeft + docEl.scrollLeft,
+                   y = e.pageY ? e.pageY : e.clientY + body.scrollTop + docEl.scrollTop,
+                   pos = new L.Point(x, y);
+
+               return (container ? pos._subtract(L.DomUtil.getViewportOffset(container)) : pos);
+       },
+
+       getWheelDelta: function (e) {
+
+               var delta = 0;
+
+               if (e.wheelDelta) {
+                       delta = e.wheelDelta / 120;
+               }
+               if (e.detail) {
+                       delta = -e.detail / 3;
+               }
+               return delta;
+       },
+
+       // check if element really left/entered the event target (for mouseenter/mouseleave)
+       _checkMouse: function (el, e) {
+
+               var related = e.relatedTarget;
+
+               if (!related) { return true; }
+
+               try {
+                       while (related && (related !== el)) {
+                               related = related.parentNode;
+                       }
+               } catch (err) {
+                       return false;
+               }
+               return (related !== el);
+       },
+
+       /*jshint noarg:false */
+       _getEvent: function () { // evil magic for IE
+
+               var e = window.event;
+               if (!e) {
+                       var caller = arguments.callee.caller;
+                       while (caller) {
+                               e = caller['arguments'][0];
+                               if (e && window.Event === e.constructor) {
+                                       break;
+                               }
+                               caller = caller.caller;
+                       }
+               }
+               return e;
+       }
+       /*jshint noarg:false */
+};
+
+L.DomEvent.on = L.DomEvent.addListener;
+L.DomEvent.off = L.DomEvent.removeListener;
+
+
+/*
+ * L.Draggable allows you to add dragging capabilities to any element. Supports mobile devices too.
+ */
+
+L.Draggable = L.Class.extend({
+       includes: L.Mixin.Events,
+
+       statics: {
+               START: L.Browser.touch ? 'touchstart' : 'mousedown',
+               END: L.Browser.touch ? 'touchend' : 'mouseup',
+               MOVE: L.Browser.touch ? 'touchmove' : 'mousemove',
+               TAP_TOLERANCE: 15
+       },
+
+       initialize: function (element, dragStartTarget, longPress) {
+               this._element = element;
+               this._dragStartTarget = dragStartTarget || element;
+               this._longPress = longPress && !L.Browser.msTouch;
+       },
+
+       enable: function () {
+               if (this._enabled) { return; }
+
+               L.DomEvent.on(this._dragStartTarget, L.Draggable.START, this._onDown, this);
+               this._enabled = true;
+       },
+
+       disable: function () {
+               if (!this._enabled) { return; }
+
+               L.DomEvent.off(this._dragStartTarget, L.Draggable.START, this._onDown);
+               this._enabled = false;
+               this._moved = false;
+       },
+
+       _onDown: function (e) {
+               if ((!L.Browser.touch && e.shiftKey) ||
+                   ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
+
+               L.DomEvent.preventDefault(e);
+               L.DomEvent.stopPropagation(e);
+
+               if (L.Draggable._disabled) { return; }
+
+               this._simulateClick = true;
+
+               if (e.touches && e.touches.length > 1) {
+                       this._simulateClick = false;
+                       clearTimeout(this._longPressTimeout);
+                       return;
+               }
+
+               var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
+                   el = first.target;
+
+               if (L.Browser.touch && el.tagName.toLowerCase() === 'a') {
+                       L.DomUtil.addClass(el, 'leaflet-active');
+               }
+
+               this._moved = false;
+               if (this._moving) { return; }
+
+               this._startPoint = new L.Point(first.clientX, first.clientY);
+               this._startPos = this._newPos = L.DomUtil.getPosition(this._element);
+
+               //Touch contextmenu event emulation
+               if (e.touches && e.touches.length === 1 && L.Browser.touch && this._longPress) {
+                       this._longPressTimeout = setTimeout(L.bind(function () {
+                               var dist = (this._newPos && this._newPos.distanceTo(this._startPos)) || 0;
+
+                               if (dist < L.Draggable.TAP_TOLERANCE) {
+                                       this._simulateClick = false;
+                                       this._onUp();
+                                       this._simulateEvent('contextmenu', first);
+                               }
+                       }, this), 1000);
+               }
+
+               L.DomEvent.on(document, L.Draggable.MOVE, this._onMove, this);
+               L.DomEvent.on(document, L.Draggable.END, this._onUp, this);
+       },
+
+       _onMove: function (e) {
+               if (e.touches && e.touches.length > 1) { return; }
+
+               var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
+                   newPoint = new L.Point(first.clientX, first.clientY),
+                   diffVec = newPoint.subtract(this._startPoint);
+
+               if (!diffVec.x && !diffVec.y) { return; }
+
+               L.DomEvent.preventDefault(e);
+
+               if (!this._moved) {
+                       this.fire('dragstart');
+                       this._moved = true;
+
+                       this._startPos = L.DomUtil.getPosition(this._element).subtract(diffVec);
+
+                       if (!L.Browser.touch) {
+                               L.DomUtil.disableTextSelection();
+                               this._setMovingCursor();
+                       }
+               }
+
+               this._newPos = this._startPos.add(diffVec);
+               this._moving = true;
+
+               L.Util.cancelAnimFrame(this._animRequest);
+               this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true, this._dragStartTarget);
+       },
+
+       _updatePosition: function () {
+               this.fire('predrag');
+               L.DomUtil.setPosition(this._element, this._newPos);
+               this.fire('drag');
+       },
+
+       _onUp: function (e) {
+               var simulateClickTouch;
+               clearTimeout(this._longPressTimeout);
+               if (this._simulateClick && e.changedTouches) {
+                       var first = e.changedTouches[0],
+                           el = first.target,
+                           dist = (this._newPos && this._newPos.distanceTo(this._startPos)) || 0;
+
+                       if (el.tagName.toLowerCase() === 'a') {
+                               L.DomUtil.removeClass(el, 'leaflet-active');
+                       }
+
+                       if (dist < L.Draggable.TAP_TOLERANCE) {
+                               simulateClickTouch = first;
+                       }
+               }
+
+               if (!L.Browser.touch) {
+                       L.DomUtil.enableTextSelection();
+                       this._restoreCursor();
+               }
+
+               L.DomEvent.off(document, L.Draggable.MOVE, this._onMove);
+               L.DomEvent.off(document, L.Draggable.END, this._onUp);
+
+               if (this._moved) {
+                       // ensure drag is not fired after dragend
+                       L.Util.cancelAnimFrame(this._animRequest);
+
+                       this.fire('dragend');
+               }
+               this._moving = false;
+
+               if (simulateClickTouch) {
+                       this._moved = false;
+                       this._simulateEvent('click', simulateClickTouch);
+               }
+       },
+
+       _setMovingCursor: function () {
+               L.DomUtil.addClass(document.body, 'leaflet-dragging');
+       },
+
+       _restoreCursor: function () {
+               L.DomUtil.removeClass(document.body, 'leaflet-dragging');
+       },
+
+       _simulateEvent: function (type, e) {
+               var simulatedEvent = document.createEvent('MouseEvents');
+
+               simulatedEvent.initMouseEvent(
+                       type, true, true, window, 1,
+                       e.screenX, e.screenY,
+                       e.clientX, e.clientY,
+                       false, false, false, false, 0, null);
+
+               e.target.dispatchEvent(simulatedEvent);
+       }
+});
+
+
+/*
+ * L.Handler classes are used internally to inject interaction features to classes like Map and Marker.
+ */
+
+L.Handler = L.Class.extend({
+       initialize: function (map) {
+               this._map = map;
+       },
+
+       enable: function () {
+               if (this._enabled) { return; }
+
+               this._enabled = true;
+               this.addHooks();
+       },
+
+       disable: function () {
+               if (!this._enabled) { return; }
+
+               this._enabled = false;
+               this.removeHooks();
+       },
+
+       enabled: function () {
+               return !!this._enabled;
+       }
+});
+
+
+/*
+ * L.Handler.MapDrag is used internally by L.Map to make the map draggable.
+ */
+
+L.Map.mergeOptions({
+       dragging: true,
+
+       inertia: !L.Browser.android23,
+       inertiaDeceleration: 3400, // px/s^2
+       inertiaMaxSpeed: Infinity, // px/s
+       inertiaThreshold: L.Browser.touch ? 32 : 18, // ms
+       easeLinearity: 0.25,
+
+       longPress: true,
+
+       // TODO refactor, move to CRS
+       worldCopyJump: true
+});
+
+L.Map.Drag = L.Handler.extend({
+       addHooks: function () {
+               if (!this._draggable) {
+                       var map = this._map;
+
+                       this._draggable = new L.Draggable(map._mapPane, map._container, map.options.longPress);
+
+                       this._draggable.on({
+                               'dragstart': this._onDragStart,
+                               'drag': this._onDrag,
+                               'dragend': this._onDragEnd
+                       }, this);
+
+                       if (map.options.worldCopyJump) {
+                               this._draggable.on('predrag', this._onPreDrag, this);
+                               map.on('viewreset', this._onViewReset, this);
+                       }
+               }
+               this._draggable.enable();
+       },
+
+       removeHooks: function () {
+               this._draggable.disable();
+       },
+
+       moved: function () {
+               return this._draggable && this._draggable._moved;
+       },
+
+       _onDragStart: function () {
+               var map = this._map;
+
+               if (map._panAnim) {
+                       map._panAnim.stop();
+               }
+
+               map
+                   .fire('movestart')
+                   .fire('dragstart');
+
+               if (map.options.inertia) {
+                       this._positions = [];
+                       this._times = [];
+               }
+       },
+
+       _onDrag: function () {
+               if (this._map.options.inertia) {
+                       var time = this._lastTime = +new Date(),
+                           pos = this._lastPos = this._draggable._newPos;
+
+                       this._positions.push(pos);
+                       this._times.push(time);
+
+                       if (time - this._times[0] > 200) {
+                               this._positions.shift();
+                               this._times.shift();
+                       }
+               }
+
+               this._map
+                   .fire('move')
+                   .fire('drag');
+       },
+
+       _onViewReset: function () {
+               var pxCenter = this._map.getSize()._divideBy(2),
+                   pxWorldCenter = this._map.latLngToLayerPoint(new L.LatLng(0, 0));
+
+               this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
+               this._worldWidth = this._map.project(new L.LatLng(0, 180)).x;
+       },
+
+       _onPreDrag: function () {
+               // TODO refactor to be able to adjust map pane position after zoom
+               var map = this._map,
+                   worldWidth = this._worldWidth,
+                   halfWidth = Math.round(worldWidth / 2),
+                   dx = this._initialWorldOffset,
+                   x = this._draggable._newPos.x,
+                   newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
+                   newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
+                   newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
+
+               this._draggable._newPos.x = newX;
+       },
+
+       _onDragEnd: function () {
+               var map = this._map,
+                   options = map.options,
+                   delay = +new Date() - this._lastTime,
+
+                   noInertia = !options.inertia ||
+                           delay > options.inertiaThreshold ||
+                           !this._positions[0];
+
+               if (noInertia) {
+                       map.fire('moveend');
+
+               } else {
+
+                       var direction = this._lastPos.subtract(this._positions[0]),
+                           duration = (this._lastTime + delay - this._times[0]) / 1000,
+
+                           speedVector = direction.multiplyBy(options.easeLinearity / duration),
+                           speed = speedVector.distanceTo(new L.Point(0, 0)),
+
+                           limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
+                           limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
+
+                           decelerationDuration = limitedSpeed / (options.inertiaDeceleration * options.easeLinearity),
+                           offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
+
+                       L.Util.requestAnimFrame(function () {
+                               map.panBy(offset, decelerationDuration, options.easeLinearity);
+                       });
+               }
+
+               map.fire('dragend');
+
+               if (options.maxBounds) {
+                       // TODO predrag validation instead of animation
+                       L.Util.requestAnimFrame(this._panInsideMaxBounds, map, true, map._container);
+               }
+       },
+
+       _panInsideMaxBounds: function () {
+               this.panInsideBounds(this.options.maxBounds);
+       }
+});
+
+L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag);
+
+
+/*
+ * L.Handler.DoubleClickZoom is used internally by L.Map to add double-click zooming.
+ */
+
+L.Map.mergeOptions({
+       doubleClickZoom: true
+});
+
+L.Map.DoubleClickZoom = L.Handler.extend({
+       addHooks: function () {
+               this._map.on('dblclick', this._onDoubleClick);
+       },
+
+       removeHooks: function () {
+               this._map.off('dblclick', this._onDoubleClick);
+       },
+
+       _onDoubleClick: function (e) {
+               this.setView(e.latlng, this._zoom + 1);
+       }
+});
+
+L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom);
+
+/*
+ * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
+ */
+
+L.Map.mergeOptions({
+       scrollWheelZoom: !L.Browser.touch || L.Browser.msTouch
+});
+
+L.Map.ScrollWheelZoom = L.Handler.extend({
+       addHooks: function () {
+               L.DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this);
+               this._delta = 0;
+       },
+
+       removeHooks: function () {
+               L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll);
+       },
+
+       _onWheelScroll: function (e) {
+               var delta = L.DomEvent.getWheelDelta(e);
+
+               this._delta += delta;
+               this._lastMousePos = this._map.mouseEventToContainerPoint(e);
+
+               if (!this._startTime) {
+                       this._startTime = +new Date();
+               }
+
+               var left = Math.max(40 - (+new Date() - this._startTime), 0);
+
+               clearTimeout(this._timer);
+               this._timer = setTimeout(L.bind(this._performZoom, this), left);
+
+               L.DomEvent.preventDefault(e);
+               L.DomEvent.stopPropagation(e);
+       },
+
+       _performZoom: function () {
+               var map = this._map,
+                   delta = Math.round(this._delta),
+                   zoom = map.getZoom();
+
+               delta = Math.max(Math.min(delta, 4), -4);
+               delta = map._limitZoom(zoom + delta) - zoom;
+
+               this._delta = 0;
+
+               this._startTime = null;
+
+               if (!delta) { return; }
+
+               var newZoom = zoom + delta,
+                   newCenter = this._getCenterForScrollWheelZoom(newZoom);
+
+               map.setView(newCenter, newZoom);
+       },
+
+       _getCenterForScrollWheelZoom: function (newZoom) {
+               var map = this._map,
+                   scale = map.getZoomScale(newZoom),
+                   viewHalf = map.getSize()._divideBy(2),
+                   centerOffset = this._lastMousePos._subtract(viewHalf)._multiplyBy(1 - 1 / scale),
+                   newCenterPoint = map._getTopLeftPoint()._add(viewHalf)._add(centerOffset);
+
+               return map.unproject(newCenterPoint);
+       }
+});
+
+L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom);
+
+
+L.extend(L.DomEvent, {
+
+       _touchstart: L.Browser.msTouch ? 'MSPointerDown' : 'touchstart',
+       _touchend: L.Browser.msTouch ? 'MSPointerUp' : 'touchend',
+
+       // inspired by Zepto touch code by Thomas Fuchs
+       addDoubleTapListener: function (obj, handler, id) {
+               var last,
+                   doubleTap = false,
+                   delay = 250,
+                   touch,
+                   pre = '_leaflet_',
+                   touchstart = this._touchstart,
+                   touchend = this._touchend,
+                   trackedTouches = [];
+
+               function onTouchStart(e) {
+                       var count;
+                       if (L.Browser.msTouch) {
+                               trackedTouches.push(e.pointerId);
+                               count = trackedTouches.length;
+                       } else {
+                               count = e.touches.length;
+                       }
+                       if (count > 1) {
+                               return;
+                       }
+
+                       var now = Date.now(),
+                               delta = now - (last || now);
+
+                       touch = e.touches ? e.touches[0] : e;
+                       doubleTap = (delta > 0 && delta <= delay);
+                       last = now;
+               }
+               function onTouchEnd(e) {
+                       if (L.Browser.msTouch) {
+                               var idx = trackedTouches.indexOf(e.pointerId);
+                               if (idx === -1) {
+                                       return;
+                               }
+                               trackedTouches.splice(idx, 1);
+                       }
+
+                       if (doubleTap) {
+                               if (L.Browser.msTouch) {
+                                       //Work around .type being readonly with MSPointer* events
+                                       var newTouch = { },
+                                               prop;
+                                       for (var i in touch) {
+                                               if (true) { //Make JSHint happy, we want to copy all properties
+                                                       prop = touch[i];
+                                                       if (typeof prop === 'function') {
+                                                               newTouch[i] = prop.bind(touch);
+                                                       } else {
+                                                               newTouch[i] = prop;
+                                                       }
+                                               }
+                                       }
+                                       touch = newTouch;
+                               }
+                               touch.type = 'dblclick';
+                               handler(touch);
+                               last = null;
+                       }
+               }
+               obj[pre + touchstart + id] = onTouchStart;
+               obj[pre + touchend + id] = onTouchEnd;
+
+               //On msTouch we need to listen on the document otherwise a drag starting on the map and moving off screen will not come through to us
+               // so we will lose track of how many touches are ongoing
+               var endElement = L.Browser.msTouch ? document.documentElement : obj;
+
+               obj.addEventListener(touchstart, onTouchStart, false);
+               endElement.addEventListener(touchend, onTouchEnd, false);
+               if (L.Browser.msTouch) {
+                       endElement.addEventListener('MSPointerCancel', onTouchEnd, false);
+               }
+               return this;
+       },
+
+       removeDoubleTapListener: function (obj, id) {
+               var pre = '_leaflet_';
+               obj.removeEventListener(this._touchstart, obj[pre + this._touchstart + id], false);
+               (L.Browser.msTouch ? document.documentElement : obj).removeEventListener(this._touchend, obj[pre + this._touchend + id], false);
+               if (L.Browser.msTouch) {
+                       document.documentElement.removeEventListener('MSPointerCancel', obj[pre + this._touchend + id], false);
+               }
+               return this;
+       }
+});
+
+
+L.extend(L.DomEvent, {
+
+       _msTouches: [],
+       _msDocumentListener: false,
+
+       // Provides a touch events wrapper for msPointer events.
+       // Based on changes by veproza https://github.com/CloudMade/Leaflet/pull/1019
+
+       addMsTouchListener: function (obj, type, handler, id) {
+
+               switch (type) {
+               case 'touchstart':
+                       return this.addMsTouchListenerStart(obj, type, handler, id);
+               case 'touchend':
+                       return this.addMsTouchListenerEnd(obj, type, handler, id);
+               case 'touchmove':
+                       return this.addMsTouchListenerMove(obj, type, handler, id);
+               default:
+                       throw 'Unknown touch event type';
+               }
+       },
+
+       addMsTouchListenerStart: function (obj, type, handler, id) {
+               var pre = '_leaflet_',
+                   touches = this._msTouches;
+
+               var cb = function (e) {
+
+                       var alreadyInArray = false;
+                       for (var i = 0; i < touches.length; i++) {
+                               if (touches[i].pointerId === e.pointerId) {
+                                       alreadyInArray = true;
+                                       break;
+                               }
+                       }
+                       if (!alreadyInArray) {
+                               touches.push(e);
+                       }
+
+                       e.touches = touches.slice();
+                       e.changedTouches = [e];
+
+                       handler(e);
+               };
+
+               obj[pre + 'touchstart' + id] = cb;
+               obj.addEventListener('MSPointerDown', cb, false);
+
+               // need to also listen for end events to keep the _msTouches list accurate
+               // this needs to be on the body and never go away
+               if (!this._msDocumentListener) {
+                       var internalCb = function (e) {
+                               for (var i = 0; i < touches.length; i++) {
+                                       if (touches[i].pointerId === e.pointerId) {
+                                               touches.splice(i, 1);
+                                               break;
+                                       }
+                               }
+                       };
+                       //We listen on the documentElement as any drags that end by moving the touch off the screen get fired there
+                       document.documentElement.addEventListener('MSPointerUp', internalCb, false);
+                       document.documentElement.addEventListener('MSPointerCancel', internalCb, false);
+
+                       this._msDocumentListener = true;
+               }
+
+               return this;
+       },
+
+       addMsTouchListenerMove: function (obj, type, handler, id) {
+               var pre = '_leaflet_',
+                   touches = this._msTouches;
+
+               function cb(e) {
+
+                       // don't fire touch moves when mouse isn't down
+                       if (e.pointerType === e.MSPOINTER_TYPE_MOUSE && e.buttons === 0) { return; }
+
+                       for (var i = 0; i < touches.length; i++) {
+                               if (touches[i].pointerId === e.pointerId) {
+                                       touches[i] = e;
+                                       break;
+                               }
+                       }
+
+                       e.touches = touches.slice();
+                       e.changedTouches = [e];
+
+                       handler(e);
+               }
+
+               obj[pre + 'touchmove' + id] = cb;
+               obj.addEventListener('MSPointerMove', cb, false);
+
+               return this;
+       },
+
+       addMsTouchListenerEnd: function (obj, type, handler, id) {
+               var pre = '_leaflet_',
+                   touches = this._msTouches;
+
+               var cb = function (e) {
+                       for (var i = 0; i < touches.length; i++) {
+                               if (touches[i].pointerId === e.pointerId) {
+                                       touches.splice(i, 1);
+                                       break;
+                               }
+                       }
+
+                       e.touches = touches.slice();
+                       e.changedTouches = [e];
+
+                       handler(e);
+               };
+
+               obj[pre + 'touchend' + id] = cb;
+               obj.addEventListener('MSPointerUp', cb, false);
+               obj.addEventListener('MSPointerCancel', cb, false);
+
+               return this;
+       },
+
+       removeMsTouchListener: function (obj, type, id) {
+               var pre = '_leaflet_',
+                   cb = obj[pre + type + id];
+
+               switch (type) {
+               case 'touchstart':
+                       obj.removeEventListener('MSPointerDown', cb, false);
+                       break;
+               case 'touchmove':
+                       obj.removeEventListener('MSPointerMove', cb, false);
+                       break;
+               case 'touchend':
+                       obj.removeEventListener('MSPointerUp', cb, false);
+                       obj.removeEventListener('MSPointerCancel', cb, false);
+                       break;
+               }
+
+               return this;
+       }
+});
+
+
+/*
+ * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
+ */
+
+L.Map.mergeOptions({
+       touchZoom: L.Browser.touch && !L.Browser.android23
+});
+
+L.Map.TouchZoom = L.Handler.extend({
+       addHooks: function () {
+               L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this);
+       },
+
+       removeHooks: function () {
+               L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this);
+       },
+
+       _onTouchStart: function (e) {
+               var map = this._map;
+
+               if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
+
+               var p1 = map.mouseEventToLayerPoint(e.touches[0]),
+                   p2 = map.mouseEventToLayerPoint(e.touches[1]),
+                   viewCenter = map._getCenterLayerPoint();
+
+               this._startCenter = p1.add(p2)._divideBy(2);
+               this._startDist = p1.distanceTo(p2);
+
+               this._moved = false;
+               this._zooming = true;
+
+               this._centerOffset = viewCenter.subtract(this._startCenter);
+
+               if (map._panAnim) {
+                       map._panAnim.stop();
+               }
+
+               L.DomEvent
+                   .on(document, 'touchmove', this._onTouchMove, this)
+                   .on(document, 'touchend', this._onTouchEnd, this);
+
+               L.DomEvent.preventDefault(e);
+       },
+
+       _onTouchMove: function (e) {
+               if (!e.touches || e.touches.length !== 2) { return; }
+
+               var map = this._map;
+
+               var p1 = map.mouseEventToLayerPoint(e.touches[0]),
+                   p2 = map.mouseEventToLayerPoint(e.touches[1]);
+
+               this._scale = p1.distanceTo(p2) / this._startDist;
+               this._delta = p1._add(p2)._divideBy(2)._subtract(this._startCenter);
+
+               if (this._scale === 1) { return; }
+
+               if (!this._moved) {
+                       L.DomUtil.addClass(map._mapPane, 'leaflet-zoom-anim leaflet-touching');
+
+                       map
+                           .fire('movestart')
+                           .fire('zoomstart')
+                           ._prepareTileBg();
+
+                       this._moved = true;
+               }
+
+               L.Util.cancelAnimFrame(this._animRequest);
+               this._animRequest = L.Util.requestAnimFrame(
+                       this._updateOnMove, this, true, this._map._container);
+
+               L.DomEvent.preventDefault(e);
+       },
+
+       _updateOnMove: function () {
+               var map = this._map,
+                   origin = this._getScaleOrigin(),
+                   center = map.layerPointToLatLng(origin);
+
+               map.fire('zoomanim', {
+                       center: center,
+                       zoom: map.getScaleZoom(this._scale)
+               });
+
+               // Used 2 translates instead of transform-origin because of a very strange bug -
+               // it didn't count the origin on the first touch-zoom but worked correctly afterwards
+
+               map._tileBg.style[L.DomUtil.TRANSFORM] =
+                       L.DomUtil.getTranslateString(this._delta) + ' ' +
+                       L.DomUtil.getScaleString(this._scale, this._startCenter);
+       },
+
+       _onTouchEnd: function (e) {
+               if (!this._moved || !this._zooming) { return; }
+
+               var map = this._map;
+
+               this._zooming = false;
+               L.DomUtil.removeClass(map._mapPane, 'leaflet-touching');
+
+               L.DomEvent
+                   .off(document, 'touchmove', this._onTouchMove)
+                   .off(document, 'touchend', this._onTouchEnd);
+
+               var origin = this._getScaleOrigin(),
+                   center = map.layerPointToLatLng(origin),
+
+                   oldZoom = map.getZoom(),
+                   floatZoomDelta = map.getScaleZoom(this._scale) - oldZoom,
+                   roundZoomDelta = (floatZoomDelta > 0 ?
+                           Math.ceil(floatZoomDelta) : Math.floor(floatZoomDelta)),
+
+                   zoom = map._limitZoom(oldZoom + roundZoomDelta);
+
+               map.fire('zoomanim', {
+                       center: center,
+                       zoom: zoom
+               });
+
+               map._runAnimation(center, zoom, map.getZoomScale(zoom) / this._scale, origin, true);
+       },
+
+       _getScaleOrigin: function () {
+               var centerOffset = this._centerOffset.subtract(this._delta).divideBy(this._scale);
+               return this._startCenter.add(centerOffset);
+       }
+});
+
+L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom);
+
+
+/*
+ * L.Handler.ShiftDragZoom is used internally by L.Map to add shift-drag zoom (zoom to a selected bounding box).
+ */
+
+L.Map.mergeOptions({
+       boxZoom: true
+});
+
+L.Map.BoxZoom = L.Handler.extend({
+       initialize: function (map) {
+               this._map = map;
+               this._container = map._container;
+               this._pane = map._panes.overlayPane;
+       },
+
+       addHooks: function () {
+               L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this);
+       },
+
+       removeHooks: function () {
+               L.DomEvent.off(this._container, 'mousedown', this._onMouseDown);
+       },
+
+       _onMouseDown: function (e) {
+               if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
+
+               L.DomUtil.disableTextSelection();
+
+               this._startLayerPoint = this._map.mouseEventToLayerPoint(e);
+
+               this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._pane);
+               L.DomUtil.setPosition(this._box, this._startLayerPoint);
+
+               //TODO refactor: move cursor to styles
+               this._container.style.cursor = 'crosshair';
+
+               L.DomEvent
+                   .on(document, 'mousemove', this._onMouseMove, this)
+                   .on(document, 'mouseup', this._onMouseUp, this)
+                   .preventDefault(e);
+
+               this._map.fire("boxzoomstart");
+       },
+
+       _onMouseMove: function (e) {
+               var startPoint = this._startLayerPoint,
+                   box = this._box,
+
+                   layerPoint = this._map.mouseEventToLayerPoint(e),
+                   offset = layerPoint.subtract(startPoint),
+
+                   newPos = new L.Point(
+                       Math.min(layerPoint.x, startPoint.x),
+                       Math.min(layerPoint.y, startPoint.y));
+
+               L.DomUtil.setPosition(box, newPos);
+
+               // TODO refactor: remove hardcoded 4 pixels
+               box.style.width  = (Math.max(0, Math.abs(offset.x) - 4)) + 'px';
+               box.style.height = (Math.max(0, Math.abs(offset.y) - 4)) + 'px';
+       },
+
+       _onMouseUp: function (e) {
+               this._pane.removeChild(this._box);
+               this._container.style.cursor = '';
+
+               L.DomUtil.enableTextSelection();
+
+               L.DomEvent
+                   .off(document, 'mousemove', this._onMouseMove)
+                   .off(document, 'mouseup', this._onMouseUp);
+
+               var map = this._map,
+                   layerPoint = map.mouseEventToLayerPoint(e),
+
+                   bounds = new L.LatLngBounds(
+                       map.layerPointToLatLng(this._startLayerPoint),
+                       map.layerPointToLatLng(layerPoint));
+
+               map.fitBounds(bounds);
+
+               map.fire("boxzoomend", {
+                       boxZoomBounds: bounds
+               });
+       }
+});
+
+L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom);
+
+
+L.Map.mergeOptions({
+       keyboard: true,
+       keyboardPanOffset: 80,
+       keyboardZoomOffset: 1
+});
+
+L.Map.Keyboard = L.Handler.extend({
+
+       // list of e.keyCode values for particular actions
+       keyCodes: {
+               left:    [37],
+               right:   [39],
+               down:    [40],
+               up:      [38],
+               zoomIn:  [187, 107, 61],
+               zoomOut: [189, 109]
+       },
+
+       initialize: function (map) {
+               this._map = map;
+
+               this._setPanOffset(map.options.keyboardPanOffset);
+               this._setZoomOffset(map.options.keyboardZoomOffset);
+       },
+
+       addHooks: function () {
+               var container = this._map._container;
+
+               // make the container focusable by tabbing
+               if (container.tabIndex === -1) {
+                       container.tabIndex = "0";
+               }
+
+               L.DomEvent
+                   .addListener(container, 'focus', this._onFocus, this)
+                   .addListener(container, 'blur', this._onBlur, this)
+                   .addListener(container, 'mousedown', this._onMouseDown, this);
+
+               this._map
+                   .on('focus', this._addHooks, this)
+                   .on('blur', this._removeHooks, this);
+       },
+
+       removeHooks: function () {
+               this._removeHooks();
+
+               var container = this._map._container;
+
+               L.DomEvent
+                   .removeListener(container, 'focus', this._onFocus, this)
+                   .removeListener(container, 'blur', this._onBlur, this)
+                   .removeListener(container, 'mousedown', this._onMouseDown, this);
+
+               this._map
+                   .off('focus', this._addHooks, this)
+                   .off('blur', this._removeHooks, this);
+       },
+
+       _onMouseDown: function () {
+               if (!this._focused) {
+                       this._map._container.focus();
+               }
+       },
+
+       _onFocus: function () {
+               this._focused = true;
+               this._map.fire('focus');
+       },
+
+       _onBlur: function () {
+               this._focused = false;
+               this._map.fire('blur');
+       },
+
+       _setPanOffset: function (pan) {
+               var keys = this._panKeys = {},
+                   codes = this.keyCodes,
+                   i, len;
+
+               for (i = 0, len = codes.left.length; i < len; i++) {
+                       keys[codes.left[i]] = [-1 * pan, 0];
+               }
+               for (i = 0, len = codes.right.length; i < len; i++) {
+                       keys[codes.right[i]] = [pan, 0];
+               }
+               for (i = 0, len = codes.down.length; i < len; i++) {
+                       keys[codes.down[i]] = [0, pan];
+               }
+               for (i = 0, len = codes.up.length; i < len; i++) {
+                       keys[codes.up[i]] = [0, -1 * pan];
+               }
+       },
+
+       _setZoomOffset: function (zoom) {
+               var keys = this._zoomKeys = {},
+                   codes = this.keyCodes,
+                   i, len;
+
+               for (i = 0, len = codes.zoomIn.length; i < len; i++) {
+                       keys[codes.zoomIn[i]] = zoom;
+               }
+               for (i = 0, len = codes.zoomOut.length; i < len; i++) {
+                       keys[codes.zoomOut[i]] = -zoom;
+               }
+       },
+
+       _addHooks: function () {
+               L.DomEvent.addListener(document, 'keydown', this._onKeyDown, this);
+       },
+
+       _removeHooks: function () {
+               L.DomEvent.removeListener(document, 'keydown', this._onKeyDown, this);
+       },
+
+       _onKeyDown: function (e) {
+               var key = e.keyCode;
+
+               if (this._panKeys.hasOwnProperty(key)) {
+                       this._map.panBy(this._panKeys[key]);
+
+               } else if (this._zoomKeys.hasOwnProperty(key)) {
+                       this._map.setZoom(this._map.getZoom() + this._zoomKeys[key]);
+
+               } else {
+                       return;
+               }
+
+               L.DomEvent.stop(e);
+       }
+});
+
+L.Map.addInitHook('addHandler', 'keyboard', L.Map.Keyboard);
+
+
+/*
+ * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
+ */
+
+L.Handler.MarkerDrag = L.Handler.extend({
+       initialize: function (marker) {
+               this._marker = marker;
+       },
+
+       addHooks: function () {
+               var icon = this._marker._icon;
+               if (!this._draggable) {
+                       this._draggable = new L.Draggable(icon, icon)
+                           .on('dragstart', this._onDragStart, this)
+                           .on('drag', this._onDrag, this)
+                           .on('dragend', this._onDragEnd, this);
+               }
+               this._draggable.enable();
+       },
+
+       removeHooks: function () {
+               this._draggable.disable();
+       },
+
+       moved: function () {
+               return this._draggable && this._draggable._moved;
+       },
+
+       _onDragStart: function (e) {
+               this._marker
+                   .closePopup()
+                   .fire('movestart')
+                   .fire('dragstart');
+       },
+
+       _onDrag: function (e) {
+               var marker = this._marker,
+                   shadow = marker._shadow,
+                   iconPos = L.DomUtil.getPosition(marker._icon),
+                   latlng = marker._map.layerPointToLatLng(iconPos);
+
+               // update shadow position
+               if (shadow) {
+                       L.DomUtil.setPosition(shadow, iconPos);
+               }
+
+               marker._latlng = latlng;
+
+               marker
+                   .fire('move', {latlng: latlng})
+                   .fire('drag');
+       },
+
+       _onDragEnd: function () {
+               this._marker
+                   .fire('moveend')
+                   .fire('dragend');
+       }
+});
+
+
+L.Handler.PolyEdit = L.Handler.extend({
+       options: {
+               icon: new L.DivIcon({
+                       iconSize: new L.Point(8, 8),
+                       className: 'leaflet-div-icon leaflet-editing-icon'
+               })
+       },
+
+       initialize: function (poly, options) {
+               this._poly = poly;
+               L.setOptions(this, options);
+       },
+
+       addHooks: function () {
+               if (this._poly._map) {
+                       if (!this._markerGroup) {
+                               this._initMarkers();
+                       }
+                       this._poly._map.addLayer(this._markerGroup);
+               }
+       },
+
+       removeHooks: function () {
+               if (this._poly._map) {
+                       this._poly._map.removeLayer(this._markerGroup);
+                       delete this._markerGroup;
+                       delete this._markers;
+               }
+       },
+
+       updateMarkers: function () {
+               this._markerGroup.clearLayers();
+               this._initMarkers();
+       },
+
+       _initMarkers: function () {
+               if (!this._markerGroup) {
+                       this._markerGroup = new L.LayerGroup();
+               }
+               this._markers = [];
+
+               var latlngs = this._poly._latlngs,
+                   i, j, len, marker;
+
+               // TODO refactor holes implementation in Polygon to support it here
+
+               for (i = 0, len = latlngs.length; i < len; i++) {
+
+                       marker = this._createMarker(latlngs[i], i);
+                       marker.on('click', this._onMarkerClick, this);
+                       this._markers.push(marker);
+               }
+
+               var markerLeft, markerRight;
+
+               for (i = 0, j = len - 1; i < len; j = i++) {
+                       if (i === 0 && !(L.Polygon && (this._poly instanceof L.Polygon))) {
+                               continue;
+                       }
+
+                       markerLeft = this._markers[j];
+                       markerRight = this._markers[i];
+
+                       this._createMiddleMarker(markerLeft, markerRight);
+                       this._updatePrevNext(markerLeft, markerRight);
+               }
+       },
+
+       _createMarker: function (latlng, index) {
+               var marker = new L.Marker(latlng, {
+                       draggable: true,
+                       icon: this.options.icon
+               });
+
+               marker._origLatLng = latlng;
+               marker._index = index;
+
+               marker.on('drag', this._onMarkerDrag, this);
+               marker.on('dragend', this._fireEdit, this);
+
+               this._markerGroup.addLayer(marker);
+
+               return marker;
+       },
+
+       _fireEdit: function () {
+               this._poly.fire('edit');
+       },
+
+       _onMarkerDrag: function (e) {
+               var marker = e.target;
+
+               L.extend(marker._origLatLng, marker._latlng);
+
+               if (marker._middleLeft) {
+                       marker._middleLeft.setLatLng(this._getMiddleLatLng(marker._prev, marker));
+               }
+               if (marker._middleRight) {
+                       marker._middleRight.setLatLng(this._getMiddleLatLng(marker, marker._next));
+               }
+
+               this._poly.redraw();
+       },
+
+       _onMarkerClick: function (e) {
+               // we want to remove the marker on click, but if latlng count < 3, polyline would be invalid
+               if (this._poly._latlngs.length < 3) { return; }
+
+               var marker = e.target,
+                   i = marker._index;
+
+               // remove the marker
+               this._markerGroup.removeLayer(marker);
+               this._markers.splice(i, 1);
+               this._poly.spliceLatLngs(i, 1);
+               this._updateIndexes(i, -1);
+
+               // update prev/next links of adjacent markers
+               this._updatePrevNext(marker._prev, marker._next);
+
+               // remove ghost markers near the removed marker
+               if (marker._middleLeft) {
+                       this._markerGroup.removeLayer(marker._middleLeft);
+               }
+               if (marker._middleRight) {
+                       this._markerGroup.removeLayer(marker._middleRight);
+               }
+
+               // create a ghost marker in place of the removed one
+               if (marker._prev && marker._next) {
+                       this._createMiddleMarker(marker._prev, marker._next);
+
+               } else if (!marker._prev) {
+                       marker._next._middleLeft = null;
+
+               } else if (!marker._next) {
+                       marker._prev._middleRight = null;
+               }
+
+               this._poly.fire('edit');
+       },
+
+       _updateIndexes: function (index, delta) {
+               this._markerGroup.eachLayer(function (marker) {
+                       if (marker._index > index) {
+                               marker._index += delta;
+                       }
+               });
+       },
+
+       _createMiddleMarker: function (marker1, marker2) {
+               var latlng = this._getMiddleLatLng(marker1, marker2),
+                   marker = this._createMarker(latlng),
+                   onClick,
+                   onDragStart,
+                   onDragEnd;
+
+               marker.setOpacity(0.6);
+
+               marker1._middleRight = marker2._middleLeft = marker;
+
+               onDragStart = function () {
+                       var i = marker2._index;
+
+                       marker._index = i;
+
+                       marker
+                           .off('click', onClick)
+                           .on('click', this._onMarkerClick, this);
+
+                       latlng.lat = marker.getLatLng().lat;
+                       latlng.lng = marker.getLatLng().lng;
+                       this._poly.spliceLatLngs(i, 0, latlng);
+                       this._markers.splice(i, 0, marker);
+
+                       marker.setOpacity(1);
+
+                       this._updateIndexes(i, 1);
+                       marker2._index++;
+                       this._updatePrevNext(marker1, marker);
+                       this._updatePrevNext(marker, marker2);
+               };
+
+               onDragEnd = function () {
+                       marker.off('dragstart', onDragStart, this);
+                       marker.off('dragend', onDragEnd, this);
+
+                       this._createMiddleMarker(marker1, marker);
+                       this._createMiddleMarker(marker, marker2);
+               };
+
+               onClick = function () {
+                       onDragStart.call(this);
+                       onDragEnd.call(this);
+                       this._poly.fire('edit');
+               };
+
+               marker
+                   .on('click', onClick, this)
+                   .on('dragstart', onDragStart, this)
+                   .on('dragend', onDragEnd, this);
+
+               this._markerGroup.addLayer(marker);
+       },
+
+       _updatePrevNext: function (marker1, marker2) {
+               if (marker1) {
+                       marker1._next = marker2;
+               }
+               if (marker2) {
+                       marker2._prev = marker1;
+               }
+       },
+
+       _getMiddleLatLng: function (marker1, marker2) {
+               var map = this._poly._map,
+                   p1 = map.latLngToLayerPoint(marker1.getLatLng()),
+                   p2 = map.latLngToLayerPoint(marker2.getLatLng());
+
+               return map.layerPointToLatLng(p1._add(p2)._divideBy(2));
+       }
+});
+
+
+
+L.Control = L.Class.extend({
+       options: {
+               position: 'topright'
+       },
+
+       initialize: function (options) {
+               L.setOptions(this, options);
+       },
+
+       getPosition: function () {
+               return this.options.position;
+       },
+
+       setPosition: function (position) {
+               var map = this._map;
+
+               if (map) {
+                       map.removeControl(this);
+               }
+
+               this.options.position = position;
+
+               if (map) {
+                       map.addControl(this);
+               }
+
+               return this;
+       },
+
+       addTo: function (map) {
+               this._map = map;
+
+               var container = this._container = this.onAdd(map),
+                   pos = this.getPosition(),
+                   corner = map._controlCorners[pos];
+
+               L.DomUtil.addClass(container, 'leaflet-control');
+
+               if (pos.indexOf('bottom') !== -1) {
+                       corner.insertBefore(container, corner.firstChild);
+               } else {
+                       corner.appendChild(container);
+               }
+
+               return this;
+       },
+
+       removeFrom: function (map) {
+               var pos = this.getPosition(),
+                   corner = map._controlCorners[pos];
+
+               corner.removeChild(this._container);
+               this._map = null;
+
+               if (this.onRemove) {
+                       this.onRemove(map);
+               }
+
+               return this;
+       }
+});
+
+L.control = function (options) {
+       return new L.Control(options);
+};
+
+
+L.Map.include({
+       addControl: function (control) {
+               control.addTo(this);
+               return this;
+       },
+
+       removeControl: function (control) {
+               control.removeFrom(this);
+               return this;
+       },
+
+       _initControlPos: function () {
+               var corners = this._controlCorners = {},
+                   l = 'leaflet-',
+                   container = this._controlContainer =
+                           L.DomUtil.create('div', l + 'control-container', this._container);
+
+               function createCorner(vSide, hSide) {
+                       var className = l + vSide + ' ' + l + hSide;
+
+                       corners[vSide + hSide] = L.DomUtil.create('div', className, container);
+               }
+
+               createCorner('top', 'left');
+               createCorner('top', 'right');
+               createCorner('bottom', 'left');
+               createCorner('bottom', 'right');
+       }
+});
+
+
+L.Control.Zoom = L.Control.extend({
+       options: {
+               position: 'topleft'
+       },
+
+       onAdd: function (map) {
+               var className = 'leaflet-control-zoom',
+                   container = L.DomUtil.create('div', className);
+
+               this._map = map;
+
+               this._createButton('Zoom in', className + '-in', container, this._zoomIn, this);
+               this._createButton('Zoom out', className + '-out', container, this._zoomOut, this);
+
+               return container;
+       },
+
+       _zoomIn: function (e) {
+               this._map.zoomIn(e.shiftKey ? 3 : 1);
+       },
+
+       _zoomOut: function (e) {
+               this._map.zoomOut(e.shiftKey ? 3 : 1);
+       },
+
+       _createButton: function (title, className, container, fn, context) {
+               var link = L.DomUtil.create('a', className, container);
+               link.href = '#';
+               link.title = title;
+
+               L.DomEvent
+                   .on(link, 'click', L.DomEvent.stopPropagation)
+                   .on(link, 'mousedown', L.DomEvent.stopPropagation)
+                   .on(link, 'dblclick', L.DomEvent.stopPropagation)
+                   .on(link, 'click', L.DomEvent.preventDefault)
+                   .on(link, 'click', fn, context);
+
+               return link;
+       }
+});
+
+L.Map.mergeOptions({
+       zoomControl: true
+});
+
+L.Map.addInitHook(function () {
+       if (this.options.zoomControl) {
+               this.zoomControl = new L.Control.Zoom();
+               this.addControl(this.zoomControl);
+       }
+});
+
+L.control.zoom = function (options) {
+       return new L.Control.Zoom(options);
+};
+
+
+
+L.Control.Attribution = L.Control.extend({
+       options: {
+               position: 'bottomright',
+               prefix: 'Powered by <a href="http://leafletjs.com">Leaflet</a>'
+       },
+
+       initialize: function (options) {
+               L.setOptions(this, options);
+
+               this._attributions = {};
+       },
+
+       onAdd: function (map) {
+               this._container = L.DomUtil.create('div', 'leaflet-control-attribution');
+               L.DomEvent.disableClickPropagation(this._container);
+
+               map
+                   .on('layeradd', this._onLayerAdd, this)
+                   .on('layerremove', this._onLayerRemove, this);
+
+               this._update();
+
+               return this._container;
+       },
+
+       onRemove: function (map) {
+               map
+                   .off('layeradd', this._onLayerAdd)
+                   .off('layerremove', this._onLayerRemove);
+
+       },
+
+       setPrefix: function (prefix) {
+               this.options.prefix = prefix;
+               this._update();
+               return this;
+       },
+
+       addAttribution: function (text) {
+               if (!text) { return; }
+
+               if (!this._attributions[text]) {
+                       this._attributions[text] = 0;
+               }
+               this._attributions[text]++;
+
+               this._update();
+
+               return this;
+       },
+
+       removeAttribution: function (text) {
+               if (!text) { return; }
+
+               this._attributions[text]--;
+               this._update();
+
+               return this;
+       },
+
+       _update: function () {
+               if (!this._map) { return; }
+
+               var attribs = [];
+
+               for (var i in this._attributions) {
+                       if (this._attributions.hasOwnProperty(i) && this._attributions[i]) {
+                               attribs.push(i);
+                       }
+               }
+
+               var prefixAndAttribs = [];
+
+               if (this.options.prefix) {
+                       prefixAndAttribs.push(this.options.prefix);
+               }
+               if (attribs.length) {
+                       prefixAndAttribs.push(attribs.join(', '));
+               }
+
+               this._container.innerHTML = prefixAndAttribs.join(' &#8212; ');
+       },
+
+       _onLayerAdd: function (e) {
+               if (e.layer.getAttribution) {
+                       this.addAttribution(e.layer.getAttribution());
+               }
+       },
+
+       _onLayerRemove: function (e) {
+               if (e.layer.getAttribution) {
+                       this.removeAttribution(e.layer.getAttribution());
+               }
+       }
+});
+
+L.Map.mergeOptions({
+       attributionControl: true
+});
+
+L.Map.addInitHook(function () {
+       if (this.options.attributionControl) {
+               this.attributionControl = (new L.Control.Attribution()).addTo(this);
+       }
+});
+
+L.control.attribution = function (options) {
+       return new L.Control.Attribution(options);
+};
+
+
+L.Control.Scale = L.Control.extend({
+       options: {
+               position: 'bottomleft',
+               maxWidth: 100,
+               metric: true,
+               imperial: true,
+               updateWhenIdle: false
+       },
+
+       onAdd: function (map) {
+               this._map = map;
+
+               var className = 'leaflet-control-scale',
+                   container = L.DomUtil.create('div', className),
+                   options = this.options;
+
+               this._addScales(options, className, container);
+
+               map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
+               map.whenReady(this._update, this);
+
+               return container;
+       },
+
+       onRemove: function (map) {
+               map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
+       },
+
+       _addScales: function (options, className, container) {
+               if (options.metric) {
+                       this._mScale = L.DomUtil.create('div', className + '-line', container);
+               }
+               if (options.imperial) {
+                       this._iScale = L.DomUtil.create('div', className + '-line', container);
+               }
+       },
+
+       _update: function () {
+               var bounds = this._map.getBounds(),
+                   centerLat = bounds.getCenter().lat,
+                   halfWorldMeters = 6378137 * Math.PI * Math.cos(centerLat * Math.PI / 180),
+                   dist = halfWorldMeters * (bounds.getNorthEast().lng - bounds.getSouthWest().lng) / 180,
+
+                   size = this._map.getSize(),
+                   options = this.options,
+                   maxMeters = 0;
+
+               if (size.x > 0) {
+                       maxMeters = dist * (options.maxWidth / size.x);
+               }
+
+               this._updateScales(options, maxMeters);
+       },
+
+       _updateScales: function (options, maxMeters) {
+               if (options.metric && maxMeters) {
+                       this._updateMetric(maxMeters);
+               }
+
+               if (options.imperial && maxMeters) {
+                       this._updateImperial(maxMeters);
+               }
+       },
+
+       _updateMetric: function (maxMeters) {
+               var meters = this._getRoundNum(maxMeters);
+
+               this._mScale.style.width = this._getScaleWidth(meters / maxMeters) + 'px';
+               this._mScale.innerHTML = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
+       },
+
+       _updateImperial: function (maxMeters) {
+               var maxFeet = maxMeters * 3.2808399,
+                   scale = this._iScale,
+                   maxMiles, miles, feet;
+
+               if (maxFeet > 5280) {
+                       maxMiles = maxFeet / 5280;
+                       miles = this._getRoundNum(maxMiles);
+
+                       scale.style.width = this._getScaleWidth(miles / maxMiles) + 'px';
+                       scale.innerHTML = miles + ' mi';
+
+               } else {
+                       feet = this._getRoundNum(maxFeet);
+
+                       scale.style.width = this._getScaleWidth(feet / maxFeet) + 'px';
+                       scale.innerHTML = feet + ' ft';
+               }
+       },
+
+       _getScaleWidth: function (ratio) {
+               return Math.round(this.options.maxWidth * ratio) - 10;
+       },
+
+       _getRoundNum: function (num) {
+               var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
+                   d = num / pow10;
+
+               d = d >= 10 ? 10 : d >= 5 ? 5 : d >= 3 ? 3 : d >= 2 ? 2 : 1;
+
+               return pow10 * d;
+       }
+});
+
+L.control.scale = function (options) {
+       return new L.Control.Scale(options);
+};
+
+
+L.Control.Layers = L.Control.extend({
+       options: {
+               collapsed: true,
+               position: 'topright',
+               autoZIndex: true
+       },
+
+       initialize: function (baseLayers, overlays, options) {
+               L.setOptions(this, options);
+
+               this._layers = {};
+               this._lastZIndex = 0;
+               this._handlingClick = false;
+
+               for (var i in baseLayers) {
+                       if (baseLayers.hasOwnProperty(i)) {
+                               this._addLayer(baseLayers[i], i);
+                       }
+               }
+
+               for (i in overlays) {
+                       if (overlays.hasOwnProperty(i)) {
+                               this._addLayer(overlays[i], i, true);
+                       }
+               }
+       },
+
+       onAdd: function (map) {
+               this._initLayout();
+               this._update();
+
+               map
+                   .on('layeradd', this._update, this)
+                   .on('layerremove', this._update, this);
+
+               return this._container;
+       },
+
+       onRemove: function (map) {
+               map
+                   .off('layeradd', this._update)
+                   .off('layerremove', this._update);
+       },
+
+       addBaseLayer: function (layer, name) {
+               this._addLayer(layer, name);
+               this._update();
+               return this;
+       },
+
+       addOverlay: function (layer, name) {
+               this._addLayer(layer, name, true);
+               this._update();
+               return this;
+       },
+
+       removeLayer: function (layer) {
+               var id = L.stamp(layer);
+               delete this._layers[id];
+               this._update();
+               return this;
+       },
+
+       _initLayout: function () {
+               var className = 'leaflet-control-layers',
+                   container = this._container = L.DomUtil.create('div', className);
+
+               if (!L.Browser.touch) {
+                       L.DomEvent.disableClickPropagation(container);
+               } else {
+                       L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation);
+               }
+
+               var form = this._form = L.DomUtil.create('form', className + '-list');
+
+               if (this.options.collapsed) {
+                       L.DomEvent
+                           .on(container, 'mouseover', this._expand, this)
+                           .on(container, 'mouseout', this._collapse, this);
+
+                       var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container);
+                       link.href = '#';
+                       link.title = 'Layers';
+
+                       if (L.Browser.touch) {
+                               L.DomEvent
+                                   .on(link, 'click', L.DomEvent.stopPropagation)
+                                   .on(link, 'click', L.DomEvent.preventDefault)
+                                   .on(link, 'click', this._expand, this);
+                       }
+                       else {
+                               L.DomEvent.on(link, 'focus', this._expand, this);
+                       }
+
+                       this._map.on('movestart', this._collapse, this);
+                       // TODO keyboard accessibility
+               } else {
+                       this._expand();
+               }
+
+               this._baseLayersList = L.DomUtil.create('div', className + '-base', form);
+               this._separator = L.DomUtil.create('div', className + '-separator', form);
+               this._overlaysList = L.DomUtil.create('div', className + '-overlays', form);
+
+               container.appendChild(form);
+       },
+
+       _addLayer: function (layer, name, overlay) {
+               var id = L.stamp(layer);
+
+               this._layers[id] = {
+                       layer: layer,
+                       name: name,
+                       overlay: overlay
+               };
+
+               if (this.options.autoZIndex && layer.setZIndex) {
+                       this._lastZIndex++;
+                       layer.setZIndex(this._lastZIndex);
+               }
+       },
+
+       _update: function () {
+               if (!this._container || this._handlingClick) {
+                       return;
+               }
+
+               this._baseLayersList.innerHTML = '';
+               this._overlaysList.innerHTML = '';
+
+               var baseLayersPresent = false,
+                   overlaysPresent = false;
+
+               for (var i in this._layers) {
+                       if (this._layers.hasOwnProperty(i)) {
+                               var obj = this._layers[i];
+                               this._addItem(obj);
+                               overlaysPresent = overlaysPresent || obj.overlay;
+                               baseLayersPresent = baseLayersPresent || !obj.overlay;
+                       }
+               }
+
+               this._separator.style.display = (overlaysPresent && baseLayersPresent ? '' : 'none');
+       },
+
+       // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
+       _createRadioElement: function (name, checked) {
+
+               var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' + name + '"';
+               if (checked) {
+                       radioHtml += ' checked="checked"';
+               }
+               radioHtml += '/>';
+
+               var radioFragment = document.createElement('div');
+               radioFragment.innerHTML = radioHtml;
+
+               return radioFragment.firstChild;
+       },
+
+       _addItem: function (obj) {
+               var label = document.createElement('label'),
+                   input,
+                   checked = this._map.hasLayer(obj.layer);
+
+               if (obj.overlay) {
+                       input = document.createElement('input');
+                       input.type = 'checkbox';
+                       input.className = 'leaflet-control-layers-selector';
+                       input.defaultChecked = checked;
+               } else {
+                       input = this._createRadioElement('leaflet-base-layers', checked);
+               }
+
+               input.layerId = L.stamp(obj.layer);
+
+               L.DomEvent.on(input, 'click', this._onInputClick, this);
+
+               var name = document.createElement('span');
+               name.innerHTML = ' ' + obj.name;
+
+               label.appendChild(input);
+               label.appendChild(name);
+
+               var container = obj.overlay ? this._overlaysList : this._baseLayersList;
+               container.appendChild(label);
+       },
+
+       _onInputClick: function () {
+               var i, input, obj,
+                   inputs = this._form.getElementsByTagName('input'),
+                   inputsLen = inputs.length,
+                   baseLayer;
+
+               this._handlingClick = true;
+
+               for (i = 0; i < inputsLen; i++) {
+                       input = inputs[i];
+                       obj = this._layers[input.layerId];
+
+                       if (input.checked && !this._map.hasLayer(obj.layer)) {
+                               this._map.addLayer(obj.layer);
+                               if (!obj.overlay) {
+                                       baseLayer = obj.layer;
+                               }
+                       } else if (!input.checked && this._map.hasLayer(obj.layer)) {
+                               this._map.removeLayer(obj.layer);
+                       }
+               }
+
+               if (baseLayer) {
+                       this._map.fire('baselayerchange', {layer: baseLayer});
+               }
+
+               this._handlingClick = false;
+       },
+
+       _expand: function () {
+               L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded');
+       },
+
+       _collapse: function () {
+               this._container.className = this._container.className.replace(' leaflet-control-layers-expanded', '');
+       }
+});
+
+L.control.layers = function (baseLayers, overlays, options) {
+       return new L.Control.Layers(baseLayers, overlays, options);
+};
+
+
+/*
+ * L.PosAnimation is used by Leaflet internally for pan animations.
+ */
+
+L.PosAnimation = L.Class.extend({
+       includes: L.Mixin.Events,
+
+       run: function (el, newPos, duration, easeLinearity) { // (HTMLElement, Point[, Number, Number])
+               this.stop();
+
+               this._el = el;
+               this._inProgress = true;
+
+               this.fire('start');
+
+               el.style[L.DomUtil.TRANSITION] = 'all ' + (duration || 0.25) +
+                       's cubic-bezier(0,0,' + (easeLinearity || 0.5) + ',1)';
+
+               L.DomEvent.on(el, L.DomUtil.TRANSITION_END, this._onTransitionEnd, this);
+               L.DomUtil.setPosition(el, newPos);
+
+               // toggle reflow, Chrome flickers for some reason if you don't do this
+               L.Util.falseFn(el.offsetWidth);
+
+               // there's no native way to track value updates of tranisitioned properties, so we imitate this
+               this._stepTimer = setInterval(L.bind(this.fire, this, 'step'), 50);
+       },
+
+       stop: function () {
+               if (!this._inProgress) { return; }
+
+               // if we just removed the transition property, the element would jump to its final position,
+               // so we need to make it stay at the current position
+
+               L.DomUtil.setPosition(this._el, this._getPos());
+               this._onTransitionEnd();
+               L.Util.falseFn(this._el.offsetWidth); // force reflow in case we are about to start a new animation
+       },
+
+       // you can't easily get intermediate values of properties animated with CSS3 Transitions,
+       // we need to parse computed style (in case of transform it returns matrix string)
+
+       _transformRe: /(-?[\d\.]+), (-?[\d\.]+)\)/,
+
+       _getPos: function () {
+               var left, top, matches,
+                   el = this._el,
+                   style = window.getComputedStyle(el);
+
+               if (L.Browser.any3d) {
+                       matches = style[L.DomUtil.TRANSFORM].match(this._transformRe);
+                       left = parseFloat(matches[1]);
+                       top  = parseFloat(matches[2]);
+               } else {
+                       left = parseFloat(style.left);
+                       top  = parseFloat(style.top);
+               }
+
+               return new L.Point(left, top, true);
+       },
+
+       _onTransitionEnd: function () {
+               L.DomEvent.off(this._el, L.DomUtil.TRANSITION_END, this._onTransitionEnd, this);
+
+               if (!this._inProgress) { return; }
+               this._inProgress = false;
+
+               this._el.style[L.DomUtil.TRANSITION] = '';
+
+               clearInterval(this._stepTimer);
+
+               this.fire('step').fire('end');
+       }
+
+});
+
+
+
+L.Map.include({
+
+       setView: function (center, zoom, forceReset) {
+               zoom = this._limitZoom(zoom);
+
+               var zoomChanged = (this._zoom !== zoom);
+
+               if (this._loaded && !forceReset && this._layers) {
+
+                       if (this._panAnim) {
+                               this._panAnim.stop();
+                       }
+
+                       var done = (zoomChanged ?
+                               this._zoomToIfClose && this._zoomToIfClose(center, zoom) :
+                               this._panByIfClose(center));
+
+                       // exit if animated pan or zoom started
+                       if (done) {
+                               clearTimeout(this._sizeTimer);
+                               return this;
+                       }
+               }
+
+               // reset the map view
+               this._resetView(center, zoom);
+
+               return this;
+       },
+
+       panBy: function (offset, duration, easeLinearity) {
+               offset = L.point(offset);
+
+               if (!(offset.x || offset.y)) {
+                       return this;
+               }
+
+               if (!this._panAnim) {
+                       this._panAnim = new L.PosAnimation();
+
+                       this._panAnim.on({
+                               'step': this._onPanTransitionStep,
+                               'end': this._onPanTransitionEnd
+                       }, this);
+               }
+
+               this.fire('movestart');
+
+               L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
+
+               var newPos = L.DomUtil.getPosition(this._mapPane).subtract(offset);
+               this._panAnim.run(this._mapPane, newPos, duration || 0.25, easeLinearity);
+
+               return this;
+       },
+
+       _onPanTransitionStep: function () {
+               this.fire('move');
+       },
+
+       _onPanTransitionEnd: function () {
+               L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim');
+               this.fire('moveend');
+       },
+
+       _panByIfClose: function (center) {
+               // difference between the new and current centers in pixels
+               var offset = this._getCenterOffset(center)._floor();
+
+               if (this._offsetIsWithinView(offset)) {
+                       this.panBy(offset);
+                       return true;
+               }
+               return false;
+       },
+
+       _offsetIsWithinView: function (offset, multiplyFactor) {
+               var m = multiplyFactor || 1,
+                   size = this.getSize();
+
+               return (Math.abs(offset.x) <= size.x * m) &&
+                      (Math.abs(offset.y) <= size.y * m);
+       }
+});
+
+
+/*
+ * L.PosAnimation fallback implementation that powers Leaflet pan animations
+ * in browsers that don't support CSS3 Transitions.
+ */
+
+L.PosAnimation = L.DomUtil.TRANSITION ? L.PosAnimation : L.PosAnimation.extend({
+
+       run: function (el, newPos, duration, easeLinearity) { // (HTMLElement, Point[, Number, Number])
+               this.stop();
+
+               this._el = el;
+               this._inProgress = true;
+               this._duration = duration || 0.25;
+               this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
+
+               this._startPos = L.DomUtil.getPosition(el);
+               this._offset = newPos.subtract(this._startPos);
+               this._startTime = +new Date();
+
+               this.fire('start');
+
+               this._animate();
+       },
+
+       stop: function () {
+               if (!this._inProgress) { return; }
+
+               this._step();
+               this._complete();
+       },
+
+       _animate: function () {
+               // animation loop
+               this._animId = L.Util.requestAnimFrame(this._animate, this);
+               this._step();
+       },
+
+       _step: function () {
+               var elapsed = (+new Date()) - this._startTime,
+                   duration = this._duration * 1000;
+
+               if (elapsed < duration) {
+                       this._runFrame(this._easeOut(elapsed / duration));
+               } else {
+                       this._runFrame(1);
+                       this._complete();
+               }
+       },
+
+       _runFrame: function (progress) {
+               var pos = this._startPos.add(this._offset.multiplyBy(progress));
+               L.DomUtil.setPosition(this._el, pos);
+
+               this.fire('step');
+       },
+
+       _complete: function () {
+               L.Util.cancelAnimFrame(this._animId);
+
+               this._inProgress = false;
+               this.fire('end');
+       },
+
+       _easeOut: function (t) {
+               return 1 - Math.pow(1 - t, this._easeOutPower);
+       }
+});
+
+
+L.Map.mergeOptions({
+       zoomAnimation: L.DomUtil.TRANSITION && !L.Browser.android23 && !L.Browser.mobileOpera
+});
+
+if (L.DomUtil.TRANSITION) {
+       L.Map.addInitHook(function () {
+               L.DomEvent.on(this._mapPane, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this);
+       });
+}
+
+L.Map.include(!L.DomUtil.TRANSITION ? {} : {
+
+       _zoomToIfClose: function (center, zoom) {
+
+               if (this._animatingZoom) { return true; }
+
+               if (!this.options.zoomAnimation) { return false; }
+
+               var scale = this.getZoomScale(zoom),
+                   offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
+
+               // if offset does not exceed half of the view
+               if (!this._offsetIsWithinView(offset, 1)) { return false; }
+
+               L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
+
+               this
+                   .fire('movestart')
+                   .fire('zoomstart');
+
+               this.fire('zoomanim', {
+                       center: center,
+                       zoom: zoom
+               });
+
+               var origin = this._getCenterLayerPoint().add(offset);
+
+               this._prepareTileBg();
+               this._runAnimation(center, zoom, scale, origin);
+
+               return true;
+       },
+
+       _catchTransitionEnd: function (e) {
+               if (this._animatingZoom) {
+                       this._onZoomTransitionEnd();
+               }
+       },
+
+       _runAnimation: function (center, zoom, scale, origin, backwardsTransform) {
+               this._animateToCenter = center;
+               this._animateToZoom = zoom;
+               this._animatingZoom = true;
+
+               if (L.Draggable) {
+                       L.Draggable._disabled = true;
+               }
+
+               var transform = L.DomUtil.TRANSFORM,
+                   tileBg = this._tileBg;
+
+               clearTimeout(this._clearTileBgTimer);
+
+               L.Util.falseFn(tileBg.offsetWidth); //hack to make sure transform is updated before running animation
+
+               var scaleStr = L.DomUtil.getScaleString(scale, origin),
+                   oldTransform = tileBg.style[transform];
+
+               tileBg.style[transform] = backwardsTransform ?
+                       oldTransform + ' ' + scaleStr :
+                       scaleStr + ' ' + oldTransform;
+       },
+
+       _prepareTileBg: function () {
+               var tilePane = this._tilePane,
+                   tileBg = this._tileBg;
+
+               // If foreground layer doesn't have many tiles but bg layer does, keep the existing bg layer and just zoom it some more
+               if (tileBg && this._getLoadedTilesPercentage(tileBg) > 0.5 &&
+                                 this._getLoadedTilesPercentage(tilePane) < 0.5) {
+
+                       tilePane.style.visibility = 'hidden';
+                       tilePane.empty = true;
+                       this._stopLoadingImages(tilePane);
+                       return;
+               }
+
+               if (!tileBg) {
+                       tileBg = this._tileBg = this._createPane('leaflet-tile-pane', this._mapPane);
+                       tileBg.style.zIndex = 1;
+               }
+
+               // prepare the background pane to become the main tile pane
+               tileBg.style[L.DomUtil.TRANSFORM] = '';
+               tileBg.style.visibility = 'hidden';
+
+               // tells tile layers to reinitialize their containers
+               tileBg.empty = true; //new FG
+               tilePane.empty = false; //new BG
+
+               //Switch out the current layer to be the new bg layer (And vice-versa)
+               this._tilePane = this._panes.tilePane = tileBg;
+               var newTileBg = this._tileBg = tilePane;
+
+               L.DomUtil.addClass(newTileBg, 'leaflet-zoom-animated');
+
+               this._stopLoadingImages(newTileBg);
+       },
+
+       _getLoadedTilesPercentage: function (container) {
+               var tiles = container.getElementsByTagName('img'),
+                   i, len, count = 0;
+
+               for (i = 0, len = tiles.length; i < len; i++) {
+                       if (tiles[i].complete) {
+                               count++;
+                       }
+               }
+               return count / len;
+       },
+
+       // stops loading all tiles in the background layer
+       _stopLoadingImages: function (container) {
+               var tiles = Array.prototype.slice.call(container.getElementsByTagName('img')),
+                   i, len, tile;
+
+               for (i = 0, len = tiles.length; i < len; i++) {
+                       tile = tiles[i];
+
+                       if (!tile.complete) {
+                               tile.onload = L.Util.falseFn;
+                               tile.onerror = L.Util.falseFn;
+                               tile.src = L.Util.emptyImageUrl;
+
+                               tile.parentNode.removeChild(tile);
+                       }
+               }
+       },
+
+       _onZoomTransitionEnd: function () {
+               this._restoreTileFront();
+               L.Util.falseFn(this._tileBg.offsetWidth); // force reflow
+               this._resetView(this._animateToCenter, this._animateToZoom, true, true);
+
+               L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
+               this._animatingZoom = false;
+
+               if (L.Draggable) {
+                       L.Draggable._disabled = false;
+               }
+       },
+
+       _restoreTileFront: function () {
+               this._tilePane.innerHTML = '';
+               this._tilePane.style.visibility = '';
+               this._tilePane.style.zIndex = 2;
+               this._tileBg.style.zIndex = 1;
+       },
+
+       _clearTileBg: function () {
+               if (!this._animatingZoom && !this.touchZoom._zooming) {
+                       this._tileBg.innerHTML = '';
+               }
+       }
+});
+
+
+/*
+ * Provides L.Map with convenient shortcuts for W3C geolocation.
+ */
+
+L.Map.include({
+       _defaultLocateOptions: {
+               watch: false,
+               setView: false,
+               maxZoom: Infinity,
+               timeout: 10000,
+               maximumAge: 0,
+               enableHighAccuracy: false
+       },
+
+       locate: function (/*Object*/ options) {
+
+               options = this._locationOptions = L.extend(this._defaultLocateOptions, options);
+
+               if (!navigator.geolocation) {
+                       this._handleGeolocationError({
+                               code: 0,
+                               message: "Geolocation not supported."
+                       });
+                       return this;
+               }
+
+               var onResponse = L.bind(this._handleGeolocationResponse, this),
+                       onError = L.bind(this._handleGeolocationError, this);
+
+               if (options.watch) {
+                       this._locationWatchId =
+                               navigator.geolocation.watchPosition(onResponse, onError, options);
+               } else {
+                       navigator.geolocation.getCurrentPosition(onResponse, onError, options);
+               }
+               return this;
+       },
+
+       stopLocate: function () {
+               if (navigator.geolocation) {
+                       navigator.geolocation.clearWatch(this._locationWatchId);
+               }
+               return this;
+       },
+
+       _handleGeolocationError: function (error) {
+               var c = error.code,
+                   message = error.message ||
+                           (c === 1 ? "permission denied" :
+                           (c === 2 ? "position unavailable" : "timeout"));
+
+               if (this._locationOptions.setView && !this._loaded) {
+                       this.fitWorld();
+               }
+
+               this.fire('locationerror', {
+                       code: c,
+                       message: "Geolocation error: " + message + "."
+               });
+       },
+
+       _handleGeolocationResponse: function (pos) {
+               var latAccuracy = 180 * pos.coords.accuracy / 4e7,
+                   lngAccuracy = latAccuracy * 2,
+
+                   lat = pos.coords.latitude,
+                   lng = pos.coords.longitude,
+                   latlng = new L.LatLng(lat, lng),
+
+                   sw = new L.LatLng(lat - latAccuracy, lng - lngAccuracy),
+                   ne = new L.LatLng(lat + latAccuracy, lng + lngAccuracy),
+                   bounds = new L.LatLngBounds(sw, ne),
+
+                   options = this._locationOptions;
+
+               if (options.setView) {
+                       var zoom = Math.min(this.getBoundsZoom(bounds), options.maxZoom);
+                       this.setView(latlng, zoom);
+               }
+
+               this.fire('locationfound', {
+                       latlng: latlng,
+                       bounds: bounds,
+                       accuracy: pos.coords.accuracy
+               });
+       }
+});
+
+
+
+
+}(this));
diff --git a/vendor/assets/leaflet/leaflet.locationfilter.css b/vendor/assets/leaflet/leaflet.locationfilter.css
new file mode 100644 (file)
index 0000000..2698d78
--- /dev/null
@@ -0,0 +1,78 @@
+div.leaflet-marker-icon.location-filter.resize-marker {
+    background: url( img/resize-handle.png ) no-repeat;  
+    cursor: move;
+}
+div.leaflet-marker-icon.location-filter.move-marker {
+    background: url( img/move-handle.png ) no-repeat;  
+    cursor: move;
+}
+
+div.location-filter.button-container {
+    background: #bfbfbf;
+    background: rgba(0, 0, 0, 0.25);
+    -moz-border-radius: 7px;
+    -webkit-border-radius: 7px;
+    border-radius: 7px;
+    padding: 5px;
+}
+.leaflet-container div.location-filter.button-container a {
+    display: inline-block;
+    color: #0F2416;
+    font-size: 11px;
+    font-weight: normal;
+    text-shadow: #A1BB9C 0 1px;
+    padding: 6px 7px;
+    border: 1px solid #9CC5A4;
+    -moz-border-radius: 3px;
+    -webkit-border-radius: 3px;
+    border-radius: 3px;
+    -webkit-box-shadow: inset rgba(255,255,255,0.75) 0 1px 1px;
+    -moz-box-shadow: inset rgba(255,255,255,0.75) 0 1px 1px;
+    box-shadow: inset rgba(255,255,255,0.75) 0 1px 1px;    
+    background: #c4e3b9;
+    background: rgba(218, 252, 205, 0.9);
+    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(218, 252, 205, 0.9)), color-stop(100%, rgba(173, 226, 176, 0.9)));
+    background: -webkit-linear-gradient(top, rgba(218, 252, 205, 0.9) 0%, rgba(173, 226, 176, 0.9) 100%);
+    background: -moz-linear-gradient(top, rgba(218, 252, 205, 0.9) 0%, rgba(173, 226, 176, 0.9) 100%);
+    background: -ms-linear-gradient(top, rgba(218, 252, 205, 0.9) 0%, rgba(173, 226, 176, 0.9) 100%);
+    background: -o-linear-gradient(top, rgba(218, 252, 205, 0.9) 0%, rgba(173, 226, 176, 0.9) 100%);
+    background: linear-gradient(top, rgba(218, 252, 205, 0.9) 0%, rgba(173, 226, 176, 0.9) 100%);
+}
+.leaflet-container div.location-filter.button-container a:hover {
+    color: #263F1C;
+    background: #dde6d8;
+    background: rgba(245, 255, 240, 0.9);
+    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(245, 255, 240, 0.9)), color-stop(100%, rgba(203, 228, 205, 0.9)));
+    background: -webkit-linear-gradient(top, rgba(245, 255, 240, 0.9) 0%, rgba(203, 228, 205, 0.9) 100%);
+    background: -moz-linear-gradient(top, rgba(245, 255, 240, 0.9) 0%, rgba(203, 228, 205, 0.9) 100%);
+    background: -ms-linear-gradient(top, rgba(245, 255, 240, 0.9) 0%, rgba(203, 228, 205, 0.9) 100%);
+    background: -o-linear-gradient(top, rgba(245, 255, 240, 0.9) 0%, rgba(203, 228, 205, 0.9) 100%);
+    background: linear-gradient(top, rgba(245, 255, 240, 0.9) 0%, rgba(203, 228, 205, 0.9) 100%);
+}
+
+.leaflet-container div.location-filter.button-container a.enable-button {
+    padding: 6px 7px 6px 25px;
+    background-image: url( img/filter-icon.png ), -webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(218, 252, 205, 0.9)), color-stop(100%, rgba(173, 226, 176, 0.9)));
+    background-image: url( img/filter-icon.png ), -webkit-linear-gradient(top, rgba(218, 252, 205, 0.9) 0%, rgba(173, 226, 176, 0.9) 100%);
+    background-image: url( img/filter-icon.png ), -moz-linear-gradient(top, rgba(218, 252, 205, 0.9) 0%, rgba(173, 226, 176, 0.9) 100%);
+    background-image: url( img/filter-icon.png ), -ms-linear-gradient(top, rgba(218, 252, 205, 0.9) 0%, rgba(173, 226, 176, 0.9) 100%);
+    background-image: url( img/filter-icon.png ), -o-linear-gradient(top, rgba(218, 252, 205, 0.9) 0%, rgba(173, 226, 176, 0.9) 100%);
+    background-image: url( img/filter-icon.png ), linear-gradient(top, rgba(218, 252, 205, 0.9) 0%, rgba(173, 226, 176, 0.9) 100%);
+    background-repeat: no-repeat;
+    background-position: left center;
+}
+.leaflet-container div.location-filter.button-container a.enable-button:hover,
+.leaflet-container div.location-filter.button-container.enabled a.enable-button {
+    background-image: url( img/filter-icon.png ), -webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(245, 255, 240, 0.9)), color-stop(100%, rgba(203, 228, 205, 0.9)));
+    background-image: url( img/filter-icon.png ), -webkit-linear-gradient(top, rgba(245, 255, 240, 0.9) 0%, rgba(203, 228, 205, 0.9) 100%);
+    background-image: url( img/filter-icon.png ), -moz-linear-gradient(top, rgba(245, 255, 240, 0.9) 0%, rgba(203, 228, 205, 0.9) 100%);
+    background-image: url( img/filter-icon.png ), -ms-linear-gradient(top, rgba(245, 255, 240, 0.9) 0%, rgba(203, 228, 205, 0.9) 100%);
+    background-image: url( img/filter-icon.png ), -o-linear-gradient(top, rgba(245, 255, 240, 0.9) 0%, rgba(203, 228, 205, 0.9) 100%);
+    background-image: url( img/filter-icon.png ), linear-gradient(top, rgba(245, 255, 240, 0.9) 0%, rgba(203, 228, 205, 0.9) 100%);
+    background-repeat: no-repeat;
+    background-position: left center;
+}
+
+.leaflet-container div.location-filter.button-container a.adjust-button {
+    margin-left: 2px;
+}
diff --git a/vendor/assets/leaflet/leaflet.locationfilter.js b/vendor/assets/leaflet/leaflet.locationfilter.js
new file mode 100644 (file)
index 0000000..8e5121f
--- /dev/null
@@ -0,0 +1,493 @@
+/*
+ * Leaflet.locationfilter - leaflet location filter plugin
+ * Copyright (C) 2012, Tripbirds.com
+ * http://tripbirds.com
+ *
+ * Licensed under the MIT License.
+ *
+ * Date: 2012-09-24
+ * Version: 0.1
+ */
+L.LatLngBounds.prototype.modify = function(map, amount) {
+    var sw = this.getSouthWest(),
+        ne = this.getNorthEast(),
+        swPoint = map.latLngToLayerPoint(sw),
+        nePoint = map.latLngToLayerPoint(ne);
+
+    sw = map.layerPointToLatLng(new L.Point(swPoint.x-amount, swPoint.y+amount));
+    ne = map.layerPointToLatLng(new L.Point(nePoint.x+amount, nePoint.y-amount));
+    
+    return new L.LatLngBounds(sw, ne);
+};
+
+L.Control.Button = L.Class.extend({
+    initialize: function(options) {
+        L.Util.setOptions(this, options);
+    },
+
+    addTo: function(container) {
+        container.addButton(this);
+        return this;
+    },
+    
+    onAdd: function (buttonContainer) {
+        this._buttonContainer = buttonContainer;
+        this._button = L.DomUtil.create('a', this.options.className, this._buttonContainer.getContainer());
+        this._button.href = '#';
+        this.setText(this.options.text);
+
+        var that = this;
+        this._onClick = function(event) {
+            that.options.onClick.call(that, event);
+        };
+
+        L.DomEvent
+            .on(this._button, 'click', L.DomEvent.stopPropagation)
+            .on(this._button, 'mousedown', L.DomEvent.stopPropagation)
+            .on(this._button, 'dblclick', L.DomEvent.stopPropagation)
+            .on(this._button, 'click', L.DomEvent.preventDefault)
+            .on(this._button, 'click', this._onClick, this);
+    },
+
+    remove: function() {
+        L.DomEvent.off(this._button, "click", this._onClick);
+        this._buttonContainer.getContainer().removeChild(this._button);
+    },
+
+    setText: function(text) {
+        this._button.title = text;
+        this._button.innerHTML = text;
+    }
+});
+
+L.Control.ButtonContainer = L.Control.extend({
+    options: {
+        position: 'topleft'
+    },
+
+    getContainer: function() {
+        if (!this._container) {
+            this._container = L.DomUtil.create('div', this.options.className);
+        }
+        return this._container;
+    },
+
+    onAdd: function (map) {
+        this._map = map;
+        return this.getContainer();
+    },
+
+    addButton: function(button) {
+        button.onAdd(this);
+    },
+
+    addClass: function(className) {
+        L.DomUtil.addClass(this.getContainer(), className);
+    },
+
+    removeClass: function(className) {
+        L.DomUtil.removeClass(this.getContainer(), className);
+    }
+});
+
+L.LocationFilter = L.Class.extend({
+    includes: L.Mixin.Events,
+
+    options: {
+        enableButton: {
+            enableText: "Select area",
+            disableText: "Remove selection"
+        },
+        adjustButton: {
+            text: "Select area within current zoom"
+        }
+    },
+
+    initialize: function(options) {
+        L.Util.setOptions(this, options);
+    },
+
+    addTo: function(map) {
+        map.addLayer(this);
+        return this;
+    },
+
+    onAdd: function(map) {
+        this._map = map;
+        this._layer = new L.LayerGroup();
+
+        if (this.options.enableButton || this.options.adjustButton) {
+            this._initializeButtonContainer();
+        }
+
+        if (this.options.enable) {
+            this.enable();
+        }
+    },
+
+    onRemove: function(map) {
+        this.disable();
+        if (this._buttonContainer) {
+            this._buttonContainer.removeFrom(map);
+        }
+    },
+
+    /* Get the current filter bounds */
+    getBounds: function() { 
+        return new L.LatLngBounds(this._sw, this._ne); 
+    },
+
+    setBounds: function(bounds) {
+        this._nw = bounds.getNorthWest();
+        this._ne = bounds.getNorthEast();
+        this._sw = bounds.getSouthWest();
+        this._se = bounds.getSouthEast();
+        if (this.isEnabled()) {
+            this._draw();
+            this.fire("change", {bounds: bounds});
+        }
+    },
+
+    isEnabled: function() {
+        return this._enabled;
+    },
+
+    /* Draw a rectangle */
+    _drawRectangle: function(bounds, options) {
+        options = options || {};
+        var defaultOptions = {
+            stroke: false,
+            fill: true,
+            fillColor: "black",
+            fillOpacity: 0.3,
+            clickable: false
+        };
+        options = L.Util.extend(defaultOptions, options);
+        var rect = new L.Rectangle(bounds, options);
+        rect.addTo(this._layer);
+        return rect;
+    },
+
+    /* Draw a draggable marker */
+    _drawImageMarker: function(point, options) {
+        var marker = new L.Marker(point, {
+            icon: new L.DivIcon({
+                iconAnchor: options.anchor,
+                iconSize: options.size,
+                className: options.className
+            }),
+            draggable: true
+        });
+        marker.addTo(this._layer);
+        return marker;
+    },
+
+    /* Draw a move marker. Sets up drag listener that updates the
+       filter corners and redraws the filter when the move marker is
+       moved */
+    _drawMoveMarker: function(point) {
+        var that = this;
+        this._moveMarker = this._drawImageMarker(point, {
+            "className": "location-filter move-marker",
+            "anchor": [-10, -10],
+            "size": [13,13]
+        });
+        this._moveMarker.on('drag', function(e) {
+            var markerPos = that._moveMarker.getLatLng(),
+                latDelta = markerPos.lat-that._nw.lat,
+                lngDelta = markerPos.lng-that._nw.lng;
+            that._nw = new L.LatLng(that._nw.lat+latDelta, that._nw.lng+lngDelta, true);
+            that._ne = new L.LatLng(that._ne.lat+latDelta, that._ne.lng+lngDelta, true);
+            that._sw = new L.LatLng(that._sw.lat+latDelta, that._sw.lng+lngDelta, true);
+            that._se = new L.LatLng(that._se.lat+latDelta, that._se.lng+lngDelta, true);
+            that._draw();
+        });
+        this._setupDragendListener(this._moveMarker);
+        return this._moveMarker;
+    },
+
+    /* Draw a resize marker */
+    _drawResizeMarker: function(point, latFollower, lngFollower, otherPos) {
+        return this._drawImageMarker(point, {
+            "className": "location-filter resize-marker",
+            "anchor": [7, 6],
+            "size": [13, 12] 
+        });
+    },
+
+    /* Track moving of the given resize marker and update the markers
+       given in options.moveAlong to match the position of the moved
+       marker. Update filter corners and redraw the filter */
+    _setupResizeMarkerTracking: function(marker, options) {
+        var that = this;
+        marker.on('drag', function(e) {
+            var curPosition = marker.getLatLng(),
+                latMarker = options.moveAlong.lat,
+                lngMarker = options.moveAlong.lng;
+            // Move follower markers when this marker is moved
+            latMarker.setLatLng(new L.LatLng(curPosition.lat, latMarker.getLatLng().lng, true));
+            lngMarker.setLatLng(new L.LatLng(lngMarker.getLatLng().lat, curPosition.lng, true));
+            // Sort marker positions in nw, ne, sw, se order
+            var corners = [that._nwMarker.getLatLng(), 
+                           that._neMarker.getLatLng(), 
+                           that._swMarker.getLatLng(), 
+                           that._seMarker.getLatLng()];
+            corners.sort(function(a, b) {
+                if (a.lat != b.lat)
+                    return b.lat-a.lat;
+                else
+                    return a.lng-b.lng;
+            });
+            // Update corner points and redraw everything except the resize markers
+            that._nw = corners[0];
+            that._ne = corners[1];
+            that._sw = corners[2];
+            that._se = corners[3];
+            that._draw({repositionResizeMarkers: false});
+        });
+        this._setupDragendListener(marker);
+    },
+
+    /* Emit a change event whenever dragend is triggered on the
+       given marker */
+    _setupDragendListener: function(marker) {
+        var that = this;
+        marker.on('dragend', function(e) {
+            that.fire("change", {bounds: that.getBounds()});
+        });
+    },
+
+    /* Create bounds for the mask rectangles and the location
+       filter rectangle */
+    _calculateBounds: function() {
+        var mapBounds = this._map.getBounds(),
+            outerBounds = new L.LatLngBounds(
+                new L.LatLng(mapBounds.getSouthWest().lat-0.1,
+                             mapBounds.getSouthWest().lng-0.1, true),
+                new L.LatLng(mapBounds.getNorthEast().lat+0.1,
+                             mapBounds.getNorthEast().lng+0.1, true)
+            );
+
+        // The south west and north east points of the mask */
+        this._osw = outerBounds.getSouthWest();
+        this._one = outerBounds.getNorthEast();
+
+        // Bounds for the mask rectangles
+        this._northBounds = new L.LatLngBounds(new L.LatLng(this._ne.lat, this._osw.lng, true), this._one);
+        this._westBounds = new L.LatLngBounds(new L.LatLng(this._sw.lat, this._osw.lng, true), this._nw);
+        this._eastBounds = new L.LatLngBounds(this._se, new L.LatLng(this._ne.lat, this._one.lng, true));
+        this._southBounds = new L.LatLngBounds(this._osw, new L.LatLng(this._sw.lat, this._one.lng, true));
+    },
+
+    /* Initializes rectangles and markers */
+    _initialDraw: function() {
+        if (this._initialDrawCalled) {
+            return;
+        }
+
+        // Calculate filter bounds
+        this._calculateBounds();
+
+        // Create rectangles
+        this._northRect = this._drawRectangle(this._northBounds);
+        this._westRect = this._drawRectangle(this._westBounds);
+        this._eastRect = this._drawRectangle(this._eastBounds);
+        this._southRect = this._drawRectangle(this._southBounds);
+        this._innerRect = this._drawRectangle(this.getBounds(), {
+            fillColor: "transparent",
+            stroke: true,
+            color: "white",
+            weight: 1,
+            opacity: 0.9
+        });
+
+        // Create resize markers
+        this._nwMarker = this._drawResizeMarker(this._nw);
+        this._neMarker = this._drawResizeMarker(this._ne);
+        this._swMarker = this._drawResizeMarker(this._sw);
+        this._seMarker = this._drawResizeMarker(this._se);
+
+        // Setup tracking of resize markers. Each marker has pair of
+        // follower markers that must be moved whenever the marker is
+        // moved. For example, whenever the north west resize marker
+        // moves, the south west marker must move along on the x-axis
+        // and the north east marker must move on the y axis
+        this._setupResizeMarkerTracking(this._nwMarker, {moveAlong: {lat: this._neMarker, lng: this._swMarker}});
+        this._setupResizeMarkerTracking(this._neMarker, {moveAlong: {lat: this._nwMarker, lng: this._seMarker}});
+        this._setupResizeMarkerTracking(this._swMarker, {moveAlong: {lat: this._seMarker, lng: this._nwMarker}});
+        this._setupResizeMarkerTracking(this._seMarker, {moveAlong: {lat: this._swMarker, lng: this._neMarker}});
+
+        // Create move marker
+        this._moveMarker = this._drawMoveMarker(this._nw);
+
+        this._initialDrawCalled = true;
+    },
+
+    /* Reposition all rectangles and markers to the current filter bounds. */    
+    _draw: function(options) {
+        options = L.Util.extend({repositionResizeMarkers: true}, options);
+
+        // Calculate filter bounds
+        this._calculateBounds();
+
+        // Reposition rectangles
+        this._northRect.setBounds(this._northBounds);
+        this._westRect.setBounds(this._westBounds);
+        this._eastRect.setBounds(this._eastBounds);
+        this._southRect.setBounds(this._southBounds);
+        this._innerRect.setBounds(this.getBounds());
+
+        // Reposition resize markers
+        if (options.repositionResizeMarkers) {
+            this._nwMarker.setLatLng(this._nw);
+            this._neMarker.setLatLng(this._ne);
+            this._swMarker.setLatLng(this._sw);
+            this._seMarker.setLatLng(this._se);
+        }
+
+        // Reposition the move marker
+        this._moveMarker.setLatLng(this._nw);
+    }, 
+
+    /* Adjust the location filter to the current map bounds */
+    _adjustToMap: function() {
+        this.setBounds(this._map.getBounds());
+        this._map.zoomOut();
+    },
+
+    /* Enable the location filter */
+    enable: function() {
+        if (this._enabled) {
+            return;
+        }
+
+        // Initialize corners
+        var bounds;
+        if (this._sw && this._ne) {
+            bounds = new L.LatLngBounds(this._sw, this._ne);
+        } else if (this.options.bounds) {
+            bounds = this.options.bounds;
+        } else {
+            bounds = this._map.getBounds();
+        }
+        this._map.invalidateSize();
+        this._nw = bounds.getNorthWest();
+        this._ne = bounds.getNorthEast();
+        this._sw = bounds.getSouthWest();
+        this._se = bounds.getSouthEast();
+            
+
+        // Update buttons
+        if (this._buttonContainer) {
+            this._buttonContainer.addClass("enabled");
+        }
+
+        if (this._enableButton) {
+            this._enableButton.setText(this.options.enableButton.disableText);
+        }
+
+        if (this.options.adjustButton) {
+            this._createAdjustButton();
+        }
+        
+        // Draw filter
+        this._initialDraw();
+        this._draw();
+
+        // Set up map move event listener
+        var that = this;
+        this._moveHandler = function() {
+            that._draw();
+        };
+        this._map.on("move", this._moveHandler);
+
+        // Add the filter layer to the map
+        this._layer.addTo(this._map);
+        
+        // Zoom out the map if necessary
+        var mapBounds = this._map.getBounds();
+        bounds = new L.LatLngBounds(this._sw, this._ne).modify(this._map, 10);
+        if (!mapBounds.contains(bounds.getSouthWest()) || !mapBounds.contains(bounds.getNorthEast())) {
+            this._map.fitBounds(bounds);
+        }
+
+        this._enabled = true;
+        
+        // Fire the enabled event
+        this.fire("enabled");
+    },
+
+    /* Disable the location filter */
+    disable: function() {
+        if (!this._enabled) {
+            return;
+        }
+
+        // Update buttons
+        if (this._buttonContainer) {
+            this._buttonContainer.removeClass("enabled");
+        }
+
+        if (this._enableButton) {
+            this._enableButton.setText(this.options.enableButton.enableText);
+        }
+
+        if (this._adjustButton) {
+            this._adjustButton.remove();
+        }
+
+        // Remove event listener
+        this._map.off("move", this._moveHandler);
+
+        // Remove rectangle layer from map
+        this._map.removeLayer(this._layer);
+
+        this._enabled = false;
+
+        // Fire the disabled event
+        this.fire("disabled");
+    },
+
+    /* Create a button that allows the user to adjust the location
+       filter to the current zoom */
+    _createAdjustButton: function() {
+        var that = this;
+        this._adjustButton = new L.Control.Button({
+            className: "adjust-button",
+            text: this.options.adjustButton.text,
+            
+            onClick: function(event) {
+                that._adjustToMap();
+                that.fire("adjustToZoomClick");
+            }
+        }).addTo(this._buttonContainer);
+    },
+
+    /* Create the location filter button container and the button that
+       toggles the location filter */
+    _initializeButtonContainer: function() {
+        var that = this;
+        this._buttonContainer = new L.Control.ButtonContainer({className: "location-filter button-container"});
+
+        if (this.options.enableButton) {
+            this._enableButton = new L.Control.Button({
+                className: "enable-button",
+                text: this.options.enableButton.enableText,
+
+                onClick: function(event) {
+                    if (!that._enabled) {
+                        // Enable the location filter
+                        that.enable();
+                        that.fire("enableClick");
+                    } else {
+                        // Disable the location filter
+                        that.disable();
+                        that.fire("disableClick");
+                    }
+                }
+            }).addTo(this._buttonContainer);
+        }
+
+        this._buttonContainer.addTo(this._map);
+    }
+});
diff --git a/vendor/assets/leaflet/leaflet.osm.js b/vendor/assets/leaflet/leaflet.osm.js
new file mode 100644 (file)
index 0000000..927d2c3
--- /dev/null
@@ -0,0 +1,200 @@
+L.OSM = {};
+
+L.OSM.TileLayer = L.TileLayer.extend({
+  options: {
+    url: 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
+    attribution: '© <a target="_parent" href="http://www.openstreetmap.org">OpenStreetMap</a> and contributors, under an <a target="_parent" href="http://www.openstreetmap.org/copyright">open license</a>'
+  },
+
+  initialize: function (options) {
+    options = L.Util.setOptions(this, options);
+    L.TileLayer.prototype.initialize.call(this, options.url);
+  }
+});
+
+L.OSM.Mapnik = L.OSM.TileLayer.extend({
+  options: {
+    url: 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
+  }
+});
+
+L.OSM.CycleMap = L.OSM.TileLayer.extend({
+  options: {
+    url: 'http://{s}.tile.opencyclemap.org/cycle/{z}/{x}/{y}.png'
+  }
+});
+
+L.OSM.TransportMap = L.OSM.TileLayer.extend({
+  options: {
+    url: 'http://{s}.tile2.opencyclemap.org/transport/{z}/{x}/{y}.png'
+  }
+});
+
+L.OSM.MapQuestOpen = L.OSM.TileLayer.extend({
+  options: {
+    url: 'http://otile{s}.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.png',
+    subdomains: '1234',
+    attribution: "Tiles courtesy of <a href='http://www.mapquest.com/' target='_blank'>MapQuest</a> <img src='http://developer.mapquest.com/content/osm/mq_logo.png'>"
+  }
+});
+
+L.OSM.DataLayer = L.FeatureGroup.extend({
+  options: {
+    areaTags: ['area', 'building', 'leisure', 'tourism', 'ruins', 'historic', 'landuse', 'military', 'natural', 'sport'],
+    uninterestingTags: ['source', 'source_ref', 'source:ref', 'history', 'attribution', 'created_by', 'tiger:county', 'tiger:tlid', 'tiger:upload_uuid'],
+    styles: {}
+  },
+
+  initialize: function (xml, options) {
+    L.Util.setOptions(this, options);
+
+    L.FeatureGroup.prototype.initialize.call(this);
+
+    if (xml) {
+      this.addData(xml);
+    }
+  },
+
+  addData: function (features) {
+    if (!(features instanceof Array)) {
+      features = this.buildFeatures(features);
+    }
+
+    for (var i = 0; i < features.length; i++) {
+      var feature = features[i], layer;
+
+      if (feature.type === "node") {
+        layer = L.circleMarker(feature.latLng, this.options.styles.node);
+      } else {
+        var latLngs = new Array(feature.nodes.length);
+
+        for (var j = 0; j < feature.nodes.length; j++) {
+          latLngs[j] = feature.nodes[j].latLng;
+        }
+
+        if (this.isWayArea(feature)) {
+          latLngs.pop(); // Remove last == first.
+          layer = L.polygon(latLngs, this.options.styles.area);
+        } else {
+          layer = L.polyline(latLngs, this.options.styles.way);
+        }
+      }
+
+      layer.addTo(this);
+      layer.feature = feature;
+    }
+  },
+
+  buildFeatures: function (xml) {
+    var features = [],
+      nodes = L.OSM.getNodes(xml),
+      ways = L.OSM.getWays(xml, nodes);
+
+    for (var node_id in nodes) {
+      var node = nodes[node_id];
+      if (this.interestingNode(node, ways)) {
+        features.push(node);
+      }
+    }
+
+    for (var i = 0; i < ways.length; i++) {
+      var way = ways[i];
+      features.push(way);
+    }
+
+    return features;
+  },
+
+  isWayArea: function (way) {
+    if (way.nodes[0] != way.nodes[way.nodes.length - 1]) {
+      return false;
+    }
+
+    for (var key in way.tags) {
+      if (~this.options.areaTags.indexOf(key)) {
+        return true;
+      }
+    }
+
+    return false;
+  },
+
+  interestingNode: function (node, ways) {
+    var used = false;
+
+    for (var i = 0; i < ways.length; i++) {
+      if (ways[i].nodes.indexOf(node) >= 0) {
+        used = true;
+        break;
+      }
+    }
+
+    if (!used) {
+      return true;
+    }
+
+    for (var key in node.tags) {
+      if (this.options.uninterestingTags.indexOf(key) < 0) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+});
+
+L.Util.extend(L.OSM, {
+  getNodes: function (xml) {
+    var result = {};
+
+    var nodes = xml.getElementsByTagName("node");
+    for (var i = 0; i < nodes.length; i++) {
+      var node = nodes[i], id = node.getAttribute("id");
+      result[id] = {
+        id: id,
+        type: "node",
+        latLng: L.latLng(node.getAttribute("lat"),
+                         node.getAttribute("lon"),
+                         true),
+        tags: this.getTags(node)
+      };
+    }
+
+    return result;
+  },
+
+  getWays: function (xml, nodes) {
+    var result = [];
+
+    var ways = xml.getElementsByTagName("way");
+    for (var i = 0; i < ways.length; i++) {
+      var way = ways[i], nds = way.getElementsByTagName("nd");
+
+      var way_object = {
+        id: way.getAttribute("id"),
+        type: "way",
+        nodes: new Array(nds.length),
+        tags: this.getTags(way)
+      };
+
+      for (var j = 0; j < nds.length; j++) {
+        way_object.nodes[j] = nodes[nds[j].getAttribute("ref")];
+      }
+
+      result.push(way_object);
+    }
+
+    return result;
+  },
+
+  getTags: function (xml) {
+    var result = {};
+
+    var tags = xml.getElementsByTagName("tag");
+    for (var j = 0; j < tags.length; j++) {
+      result[tags[j].getAttribute("k")] = tags[j].getAttribute("v");
+    }
+
+    return result;
+  }
+});
diff --git a/vendor/assets/leaflet/leaflet.pan.js b/vendor/assets/leaflet/leaflet.pan.js
new file mode 100644 (file)
index 0000000..5fa3a91
--- /dev/null
@@ -0,0 +1,52 @@
+L.Control.Pan = L.Control.extend({
+       options: {
+               position: 'topleft',
+               panOffset: 500
+       },
+
+       onAdd: function (map) {
+               var className = 'leaflet-control-pan',
+                       container = L.DomUtil.create('div', className),
+                       off = this.options.panOffset;
+
+               this._panButton('Up'   , className + '-up'   
+                                               , container, map, new L.Point(    0 , -off));
+               this._panButton('Left' , className + '-left' 
+                                               , container, map, new L.Point( -off ,  0));
+               this._panButton('Right', className + '-right'
+                                               , container, map, new L.Point(  off ,  0));
+               this._panButton('Down' , className + '-down'
+                                               , container, map, new L.Point(    0 ,  off));
+               
+               return container;
+       },
+
+       _panButton: function (title, className, container, map, offset, text) {
+               var wrapper = L.DomUtil.create('div', className + "-wrap", container);
+               var link = L.DomUtil.create('a', className, wrapper);
+               link.href = '#';
+               link.title = title;
+               L.DomEvent
+                       .on(link, 'click', L.DomEvent.stopPropagation)
+                       .on(link, 'click', L.DomEvent.preventDefault)
+                       .on(link, 'click', function(){ map.panBy(offset); }, map)
+                       .on(link, 'dblclick', L.DomEvent.stopPropagation)
+
+               return link;
+       }
+});
+
+L.Map.mergeOptions({
+    panControl: true
+});
+
+L.Map.addInitHook(function () {
+    if (this.options.panControl) {
+               this.panControl = new L.Control.Pan();
+               this.addControl(this.panControl);
+       }
+});
+
+L.control.pan = function (options) {
+    return new L.Control.Pan(options);
+};
diff --git a/vendor/assets/leaflet/leaflet.zoom.js b/vendor/assets/leaflet/leaflet.zoom.js
new file mode 100644 (file)
index 0000000..6ce9675
--- /dev/null
@@ -0,0 +1,202 @@
+L.Control.Zoomslider = L.Control.extend({\r
+       options: {\r
+               position: 'topleft',\r
+               // height in px of zoom-slider.png\r
+               stepHeight: 9\r
+       },\r
+\r
+       onAdd: function (map) {\r
+               var className = 'leaflet-control-zoomslider',\r
+                               container = L.DomUtil.create('div', className);\r
+\r
+               L.DomEvent\r
+                       .on(container, 'click', L.DomEvent.stopPropagation)\r
+                       .on(container, 'mousedown', L.DomEvent.stopPropagation)\r
+                       .on(container, 'dblclick', L.DomEvent.stopPropagation);\r
+               \r
+               this._map = map;\r
+\r
+               this._zoomInButton = this._createButton('+', 'Zoom in', className + '-in'\r
+                                                                                               , container, this._zoomIn , this);\r
+               this._createSlider(className + '-slider', container, map);\r
+               this._zoomOutButton = this._createButton('-', 'Zoom out', className + '-out'\r
+                                                                                                , container, this._zoomOut, this);\r
+               \r
+               map.on('layeradd layerremove', this._refresh, this);\r
+\r
+               map.whenReady(function(){\r
+                       this._snapToSliderValue();\r
+                       map.on('zoomend', this._snapToSliderValue, this);\r
+               }, this);\r
+\r
+               return container;\r
+       },\r
+\r
+       onRemove: function(map){\r
+               map.off('zoomend', this._snapToSliderValue);\r
+               map.off('layeradd layerremove', this._refresh);\r
+       },\r
+\r
+       _refresh: function(){\r
+               this._map\r
+                       .removeControl(this)\r
+                       .addControl(this);\r
+       },\r
+\r
+       _createSlider: function (className, container, map) {\r
+               var zoomLevels = map.getMaxZoom() - map.getMinZoom();\r
+               this._sliderHeight = this.options.stepHeight * zoomLevels;\r
+\r
+               var wrapper =  L.DomUtil.create('div', className + '-wrap', container);\r
+               wrapper.style.height = (this._sliderHeight + 5) + "px";\r
+               var slider = L.DomUtil.create('div', className, wrapper);\r
+               this._knob = L.DomUtil.create('div', className + '-knob', slider);\r
+\r
+               this._draggable = this._createDraggable();\r
+               this._draggable.enable();\r
+\r
+               L.DomEvent.on(slider, 'click', this._onSliderClick, this);\r
+\r
+               return slider;\r
+       },\r
+\r
+       _zoomIn: function (e) {\r
+           this._map.zoomIn(e.shiftKey ? 3 : 1);\r
+       },\r
+\r
+       _zoomOut: function (e) {\r
+           this._map.zoomOut(e.shiftKey ? 3 : 1);\r
+       },\r
+\r
+       _createButton: function (html, title, className, container, fn, context) {\r
+               var link = L.DomUtil.create('a', className, container);\r
+               link.innerHTML = html;\r
+               link.href = '#';\r
+               link.title = title;\r
+\r
+               L.DomEvent\r
+                   .on(link, 'click', L.DomEvent.preventDefault)\r
+                   .on(link, 'click', fn, context);\r
+\r
+               return link;\r
+       },\r
+\r
+       _createDraggable: function() {\r
+               L.DomUtil.setPosition(this._knob, new L.Point(0, 0));\r
+               L.DomEvent\r
+                       .on(this._knob\r
+                               , L.Draggable.START\r
+                               , L.DomEvent.stopPropagation)\r
+                       .on(this._knob, 'click', L.DomEvent.stopPropagation);\r
+\r
+               var bounds = new L.Bounds(\r
+                       new L.Point(0, 0),\r
+                       new L.Point(0, this._sliderHeight)\r
+               );\r
+               var draggable = new L.BoundedDraggable(this._knob,\r
+                                                                                          this._knob,\r
+                                                                                          bounds)\r
+                       .on('drag', this._snap, this)\r
+                       .on('dragend', this._setZoom, this);\r
+\r
+               return draggable;\r
+       },\r
+\r
+       _snap : function(){\r
+               this._snapToSliderValue(this._posToSliderValue());\r
+       },\r
+       _setZoom: function() {\r
+               this._map.setZoom(this._toZoomLevel(this._posToSliderValue()));\r
+       },\r
+\r
+       _onSliderClick: function(e){\r
+               var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e);\r
+           var offset = first.offsetY\r
+                       ? first.offsetY\r
+                       : L.DomEvent.getMousePosition(first).y\r
+                       - L.DomUtil.getViewportOffset(this._knob).y;\r
+               var value = this._posToSliderValue(offset - this._knob.offsetHeight / 2);\r
+               this._snapToSliderValue(value);\r
+               this._map.setZoom(this._toZoomLevel(value));\r
+       },\r
+\r
+       _posToSliderValue: function(pos) {\r
+               pos = isNaN(pos)\r
+                       ? L.DomUtil.getPosition(this._knob).y\r
+                       : pos;\r
+               return Math.round( (this._sliderHeight - pos) / this.options.stepHeight);\r
+       },\r
+\r
+       _snapToSliderValue: function(sliderValue) {\r
+               this._updateDisabled();\r
+               if(this._knob) {\r
+                       sliderValue = isNaN(sliderValue)\r
+                               ? this._getSliderValue()\r
+                               : sliderValue;\r
+                       var y = this._sliderHeight\r
+                               - (sliderValue * this.options.stepHeight);\r
+                       L.DomUtil.setPosition(this._knob, new L.Point(0, y));\r
+               }\r
+       },\r
+       _toZoomLevel: function(sliderValue) {\r
+               return sliderValue + this._map.getMinZoom();\r
+       },\r
+       _toSliderValue: function(zoomLevel) {\r
+               return zoomLevel - this._map.getMinZoom();\r
+       },\r
+       _getSliderValue: function(){\r
+               return this._toSliderValue(this._map.getZoom());\r
+       },\r
+\r
+       _updateDisabled: function () {\r
+               var map = this._map,\r
+                       className = 'leaflet-control-zoomslider-disabled';\r
+\r
+               L.DomUtil.removeClass(this._zoomInButton, className);\r
+               L.DomUtil.removeClass(this._zoomOutButton, className);\r
+\r
+               if (map.getZoom() === map.getMinZoom()) {\r
+                       L.DomUtil.addClass(this._zoomOutButton, className);\r
+               }\r
+               if (map.getZoom() === map.getMaxZoom()) {\r
+                       L.DomUtil.addClass(this._zoomInButton, className);\r
+               }\r
+       }\r
+});\r
+\r
+L.Map.mergeOptions({\r
+    zoomControl: false,\r
+    zoomsliderControl: true\r
+});\r
+\r
+L.Map.addInitHook(function () {\r
+    if (this.options.zoomsliderControl) {\r
+               L.control.zoomslider().addTo(this);\r
+       }\r
+});\r
+\r
+L.control.zoomslider = function (options) {\r
+    return new L.Control.Zoomslider(options);\r
+};\r
+\r
+\r
+L.BoundedDraggable = L.Draggable.extend({\r
+       initialize: function(element, dragStartTarget, bounds) {\r
+               L.Draggable.prototype.initialize.call(this, element, dragStartTarget);\r
+               this._bounds = bounds;\r
+               this.on('predrag', function() {\r
+                       if(!this._bounds.contains(this._newPos)){\r
+                               this._newPos = this._fitPoint(this._newPos);\r
+                       }\r
+               }, this);\r
+       },\r
+       _fitPoint: function(point){\r
+               var closest = new L.Point(\r
+                       Math.min(point.x, this._bounds.max.x),\r
+                       Math.min(point.y, this._bounds.max.y)\r
+               );\r
+               closest.x = Math.max(closest.x, this._bounds.min.x);\r
+               closest.y = Math.max(closest.y, this._bounds.min.y);\r
+               return closest;\r
+       }\r
+});\r
index 97c9b412ce1b5c3edc09d701030e9bcdbcd52097..51d5da6ef9db51a0077a2a0c775f607d286c32ee 100644 (file)
Binary files a/vendor/assets/potlatch2/potlatch2.swf and b/vendor/assets/potlatch2/potlatch2.swf differ
index 6c42d1b69531c6647b877b8c662ab009e5858d95..5533f750b4ae3845a853dfabde27acbd31f0181e 100644 (file)
Binary files a/vendor/assets/potlatch2/potlatch2/assets.zip and b/vendor/assets/potlatch2/potlatch2/assets.zip differ
diff --git a/vendor/assets/potlatch2/potlatch2/locales/bs.swf b/vendor/assets/potlatch2/potlatch2/locales/bs.swf
new file mode 100644 (file)
index 0000000..612b4d4
Binary files /dev/null and b/vendor/assets/potlatch2/potlatch2/locales/bs.swf differ
index fabe98a6bf3d729039e9b1af75164420a56d26e1..5a9171711aa1d1412d995f76f2fcdcb8697b064f 100644 (file)
Binary files a/vendor/assets/potlatch2/potlatch2/locales/ca.swf and b/vendor/assets/potlatch2/potlatch2/locales/ca.swf differ
index cdde5f19151ad2da84343937c84a8f0592c10ca6..ed30a682b37dc7a7e97b57720644789e9b933b24 100644 (file)
Binary files a/vendor/assets/potlatch2/potlatch2/locales/diq.swf and b/vendor/assets/potlatch2/potlatch2/locales/diq.swf differ